github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/utils/ssh/ssh_gocrypto_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package ssh_test
     5  
     6  import (
     7  	"encoding/binary"
     8  	"errors"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"sync"
    15  
    16  	cryptossh "code.google.com/p/go.crypto/ssh"
    17  	"github.com/juju/testing"
    18  	jc "github.com/juju/testing/checkers"
    19  	gc "gopkg.in/check.v1"
    20  
    21  	"github.com/juju/juju/utils/ssh"
    22  )
    23  
    24  var (
    25  	testCommand     = []string{"echo", "$abc"}
    26  	testCommandFlat = `echo "\$abc"`
    27  )
    28  
    29  type sshServer struct {
    30  	cfg      *cryptossh.ServerConfig
    31  	listener net.Listener
    32  	client   *cryptossh.Client
    33  }
    34  
    35  func newServer(c *gc.C) *sshServer {
    36  	private, _, err := ssh.GenerateKey("test-server")
    37  	c.Assert(err, jc.ErrorIsNil)
    38  	key, err := cryptossh.ParsePrivateKey([]byte(private))
    39  	c.Assert(err, jc.ErrorIsNil)
    40  	server := &sshServer{
    41  		cfg: &cryptossh.ServerConfig{},
    42  	}
    43  	server.cfg.AddHostKey(key)
    44  	server.listener, err = net.Listen("tcp", "127.0.0.1:0")
    45  	c.Assert(err, jc.ErrorIsNil)
    46  	return server
    47  }
    48  
    49  func (s *sshServer) run(c *gc.C) {
    50  	netconn, err := s.listener.Accept()
    51  	c.Assert(err, jc.ErrorIsNil)
    52  	defer func() {
    53  		err = netconn.Close()
    54  		c.Assert(err, jc.ErrorIsNil)
    55  	}()
    56  	conn, chans, reqs, err := cryptossh.NewServerConn(netconn, s.cfg)
    57  	c.Assert(err, jc.ErrorIsNil)
    58  	s.client = cryptossh.NewClient(conn, chans, reqs)
    59  	var wg sync.WaitGroup
    60  	defer wg.Wait()
    61  	sessionChannels := s.client.HandleChannelOpen("session")
    62  	c.Assert(sessionChannels, gc.NotNil)
    63  	for newChannel := range sessionChannels {
    64  		c.Assert(newChannel.ChannelType(), gc.Equals, "session")
    65  		channel, reqs, err := newChannel.Accept()
    66  		c.Assert(err, jc.ErrorIsNil)
    67  		wg.Add(1)
    68  		go func() {
    69  			defer wg.Done()
    70  			defer channel.Close()
    71  			for req := range reqs {
    72  				switch req.Type {
    73  				case "exec":
    74  					c.Assert(req.WantReply, jc.IsTrue)
    75  					n := binary.BigEndian.Uint32(req.Payload[:4])
    76  					command := string(req.Payload[4 : n+4])
    77  					c.Assert(command, gc.Equals, testCommandFlat)
    78  					req.Reply(true, nil)
    79  					channel.Write([]byte("abc value\n"))
    80  					_, err := channel.SendRequest("exit-status", false, cryptossh.Marshal(&struct{ n uint32 }{0}))
    81  					c.Assert(err, jc.ErrorIsNil)
    82  					return
    83  				default:
    84  					c.Fatalf("Unexpected request type: %v", req.Type)
    85  				}
    86  			}
    87  		}()
    88  	}
    89  }
    90  
    91  type SSHGoCryptoCommandSuite struct {
    92  	testing.IsolationSuite
    93  	client ssh.Client
    94  }
    95  
    96  var _ = gc.Suite(&SSHGoCryptoCommandSuite{})
    97  
    98  func (s *SSHGoCryptoCommandSuite) SetUpTest(c *gc.C) {
    99  	s.IsolationSuite.SetUpTest(c)
   100  	generateKeyRestorer := overrideGenerateKey(c)
   101  	s.AddCleanup(func(*gc.C) { generateKeyRestorer.Restore() })
   102  	client, err := ssh.NewGoCryptoClient()
   103  	c.Assert(err, jc.ErrorIsNil)
   104  	s.client = client
   105  }
   106  
   107  func (s *SSHGoCryptoCommandSuite) TestNewGoCryptoClient(c *gc.C) {
   108  	_, err := ssh.NewGoCryptoClient()
   109  	c.Assert(err, jc.ErrorIsNil)
   110  	private, _, err := ssh.GenerateKey("test-client")
   111  	c.Assert(err, jc.ErrorIsNil)
   112  	key, err := cryptossh.ParsePrivateKey([]byte(private))
   113  	c.Assert(err, jc.ErrorIsNil)
   114  	_, err = ssh.NewGoCryptoClient(key)
   115  	c.Assert(err, jc.ErrorIsNil)
   116  }
   117  
   118  func (s *SSHGoCryptoCommandSuite) TestClientNoKeys(c *gc.C) {
   119  	client, err := ssh.NewGoCryptoClient()
   120  	c.Assert(err, jc.ErrorIsNil)
   121  	cmd := client.Command("0.1.2.3", []string{"echo", "123"}, nil)
   122  	_, err = cmd.Output()
   123  	c.Assert(err, gc.ErrorMatches, "no private keys available")
   124  	defer ssh.ClearClientKeys()
   125  	err = ssh.LoadClientKeys(c.MkDir())
   126  	c.Assert(err, jc.ErrorIsNil)
   127  
   128  	s.PatchValue(ssh.SSHDial, func(network, address string, cfg *cryptossh.ClientConfig) (*cryptossh.Client, error) {
   129  		return nil, errors.New("ssh.Dial failed")
   130  	})
   131  	cmd = client.Command("0.1.2.3", []string{"echo", "123"}, nil)
   132  	_, err = cmd.Output()
   133  	// error message differs based on whether using cgo or not
   134  	c.Assert(err, gc.ErrorMatches, "ssh.Dial failed")
   135  }
   136  
   137  func (s *SSHGoCryptoCommandSuite) TestCommand(c *gc.C) {
   138  	private, _, err := ssh.GenerateKey("test-server")
   139  	c.Assert(err, jc.ErrorIsNil)
   140  	key, err := cryptossh.ParsePrivateKey([]byte(private))
   141  	client, err := ssh.NewGoCryptoClient(key)
   142  	c.Assert(err, jc.ErrorIsNil)
   143  	server := newServer(c)
   144  	var opts ssh.Options
   145  	opts.SetPort(server.listener.Addr().(*net.TCPAddr).Port)
   146  	cmd := client.Command("127.0.0.1", testCommand, &opts)
   147  	checkedKey := false
   148  	server.cfg.PublicKeyCallback = func(conn cryptossh.ConnMetadata, pubkey cryptossh.PublicKey) (*cryptossh.Permissions, error) {
   149  		c.Check(pubkey, gc.DeepEquals, key.PublicKey())
   150  		checkedKey = true
   151  		return nil, nil
   152  	}
   153  	go server.run(c)
   154  	out, err := cmd.Output()
   155  	c.Assert(err, jc.ErrorIsNil)
   156  	c.Assert(string(out), gc.Equals, "abc value\n")
   157  	c.Assert(checkedKey, jc.IsTrue)
   158  }
   159  
   160  func (s *SSHGoCryptoCommandSuite) TestCopy(c *gc.C) {
   161  	client, err := ssh.NewGoCryptoClient()
   162  	c.Assert(err, jc.ErrorIsNil)
   163  	err = client.Copy([]string{"0.1.2.3:b", c.MkDir()}, nil)
   164  	c.Assert(err, gc.ErrorMatches, `scp command is not implemented \(OpenSSH scp not available in PATH\)`)
   165  }
   166  
   167  func (s *SSHGoCryptoCommandSuite) TestProxyCommand(c *gc.C) {
   168  	realNetcat, err := exec.LookPath("nc")
   169  	if err != nil {
   170  		c.Skip("skipping test, couldn't find netcat: %v")
   171  		return
   172  	}
   173  	netcat := filepath.Join(c.MkDir(), "nc")
   174  	err = ioutil.WriteFile(netcat, []byte("#!/bin/sh\necho $0 \"$@\" > $0.args && exec "+realNetcat+" \"$@\""), 0755)
   175  	c.Assert(err, jc.ErrorIsNil)
   176  
   177  	private, _, err := ssh.GenerateKey("test-server")
   178  	c.Assert(err, jc.ErrorIsNil)
   179  	key, err := cryptossh.ParsePrivateKey([]byte(private))
   180  	client, err := ssh.NewGoCryptoClient(key)
   181  	c.Assert(err, jc.ErrorIsNil)
   182  	server := newServer(c)
   183  	var opts ssh.Options
   184  	port := server.listener.Addr().(*net.TCPAddr).Port
   185  	opts.SetProxyCommand(netcat, "-q0", "%h", "%p")
   186  	opts.SetPort(port)
   187  	cmd := client.Command("127.0.0.1", testCommand, &opts)
   188  	server.cfg.PublicKeyCallback = func(_ cryptossh.ConnMetadata, pubkey cryptossh.PublicKey) (*cryptossh.Permissions, error) {
   189  		return nil, nil
   190  	}
   191  	go server.run(c)
   192  	out, err := cmd.Output()
   193  	c.Assert(err, jc.ErrorIsNil)
   194  	c.Assert(string(out), gc.Equals, "abc value\n")
   195  	// Ensure the proxy command was executed with the appropriate arguments.
   196  	data, err := ioutil.ReadFile(netcat + ".args")
   197  	c.Assert(err, jc.ErrorIsNil)
   198  	c.Assert(string(data), gc.Equals, fmt.Sprintf("%s -q0 127.0.0.1 %v\n", netcat, port))
   199  }