github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/network/ssh/testing/sshserver.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package testing 5 6 import ( 7 "net" 8 "time" 9 10 "github.com/juju/errors" 11 jc "github.com/juju/testing/checkers" 12 "golang.org/x/crypto/ssh" 13 gc "gopkg.in/check.v1" 14 ) 15 16 // SSHKey1 generated with `ssh-keygen -b 256 -C test-only -t ecdsa -f test-key` 17 var SSHKey1 = `-----BEGIN EC PRIVATE KEY----- 18 MHcCAQEEILhuaRN6CI4h85SjOFV2+SU1uslRirsyyhGdsVmkKaC2oAoGCCqGSM49 19 AwEHoUQDQgAESKoQ2r2l3hdXf9K+j+KsTwpTHNWMdd7gsl0tgy+77DYbz7DUDml1 20 vIBDwimK29kn9WpPU8WSW23ZFPLk53mNTw== 21 -----END EC PRIVATE KEY----- 22 ` 23 24 var SSHPub1 = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEiqENq9pd4XV3/Svo/irE8KUxzVjHXe4LJdLYMvu+w2G8+w1A5pdbyAQ8IpitvZJ/VqT1PFkltt2RTy5Od5jU8= test-only" 25 26 // SSHKey2 generated with `ssh-keygen -b 256 -C test-only -t ed25519 -f test-key` 27 var SSHKey2 = `-----BEGIN OPENSSH PRIVATE KEY----- 28 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 29 QyNTUxOQAAACANk3iR1VrTsEfIyQDXkrZajtOIwmKBdz+hAN90VXdxOQAAAJDQ4EH60OBB 30 +gAAAAtzc2gtZWQyNTUxOQAAACANk3iR1VrTsEfIyQDXkrZajtOIwmKBdz+hAN90VXdxOQ 31 AAAEB0Vb6XYd1aFm1dl+37KgqgEeZDuFRlSHjeHrXEDFP4Iw2TeJHVWtOwR8jJANeStlqO 32 04jCYoF3P6EA33RVd3E5AAAACXRlc3Qtb25seQECAwQ= 33 -----END OPENSSH PRIVATE KEY----- 34 ` 35 36 var SSHPub2 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA2TeJHVWtOwR8jJANeStlqO04jCYoF3P6EA33RVd3E5 test-only" 37 38 // denyPublicKey implements the SSH PublicKeyCallback API, but just always 39 // denies any public key it gets. 40 func denyPublicKey(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { 41 return nil, errors.Errorf("public key denied") 42 } 43 44 // CreateTCPServer launches a TCP server that just Accepts connections and 45 // triggers the callback function. The callback assumes responsibility for 46 // closing the connection. 47 // We return the address+port of the TCP server, and a channel that can be 48 // closed when you want the TCP server to stop. 49 func CreateTCPServer(c *gc.C, callback func(net.Conn)) (string, chan struct{}) { 50 // We explicitly listen on IPv4 loopback instead of 'localhost' 51 listener, err := net.Listen("tcp", "127.0.0.1:0") 52 c.Assert(err, jc.ErrorIsNil) 53 localAddress := listener.Addr().String() 54 55 shutdown := make(chan struct{}, 0) 56 57 go func() { 58 for { 59 select { 60 case <-shutdown: 61 // no more listening 62 c.Logf("shutting down %s", localAddress) 63 listener.Close() 64 return 65 default: 66 } 67 // Don't get hung on Accept, set a deadline 68 if tcpListener, ok := listener.(*net.TCPListener); ok { 69 tcpListener.SetDeadline(time.Now().Add(1 * time.Second)) 70 } 71 tcpConn, err := listener.Accept() 72 if err != nil { 73 if netErr, ok := err.(net.Error); ok { 74 if netErr.Timeout() { 75 // Try again, so we reevaluate if we need to shut down 76 continue 77 } 78 } 79 } 80 if err != nil { 81 c.Logf("failed to accept connection on %s: %v", localAddress, err) 82 continue 83 } 84 remoteAddress := tcpConn.RemoteAddr().String() 85 c.Logf("accepted tcp connection on %s from %s", localAddress, remoteAddress) 86 callback(tcpConn) 87 } 88 }() 89 return localAddress, shutdown 90 } 91 92 // CreateSSHServer launches an SSH server that will use the described private 93 // key to allow SSH connections. Note that it explicitly doesn't actually 94 // support any Auth mechanisms, so nobody can complete connections, but it will 95 // do Key exchange to set up the encrypted conversation. 96 // We return the address where the SSH service is listening, and a channel 97 // callers must close when they want the service to stop. 98 func CreateSSHServer(c *gc.C, privateKeys ...string) (string, chan struct{}) { 99 serverConf := &ssh.ServerConfig{ 100 // We have to set up at least one Auth method, or the SSH server 101 // doesn't even try to do key-exchange 102 PublicKeyCallback: denyPublicKey, 103 } 104 for _, privateStr := range privateKeys { 105 privateKey, err := ssh.ParsePrivateKey([]byte(privateStr)) 106 c.Assert(err, jc.ErrorIsNil) 107 serverConf.AddHostKey(privateKey) 108 } 109 return CreateTCPServer(c, func(tcpConn net.Conn) { 110 remoteAddress := tcpConn.RemoteAddr().String() 111 c.Logf("initiating ssh handshake for %s", remoteAddress) 112 sshConn, _, _, err := ssh.NewServerConn(tcpConn, serverConf) 113 if err != nil { 114 // TODO: some errors are expected, as we don't support Auth, we 115 // should probably not log things that aren't genuine errors. 116 c.Logf("error initiating ssh connection for %s: %v", remoteAddress, err) 117 } else { 118 // We don't expect to get here, but if we do, make sure we close the connection. 119 sshConn.Close() 120 } 121 }) 122 }