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  }