github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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  	"github.com/juju/testing"
    17  	jc "github.com/juju/testing/checkers"
    18  	cryptossh "golang.org/x/crypto/ssh"
    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 netconn.Close()
    53  	conn, chans, reqs, err := cryptossh.NewServerConn(netconn, s.cfg)
    54  	c.Assert(err, jc.ErrorIsNil)
    55  	s.client = cryptossh.NewClient(conn, chans, reqs)
    56  	var wg sync.WaitGroup
    57  	defer wg.Wait()
    58  	sessionChannels := s.client.HandleChannelOpen("session")
    59  	c.Assert(sessionChannels, gc.NotNil)
    60  	for newChannel := range sessionChannels {
    61  		c.Assert(newChannel.ChannelType(), gc.Equals, "session")
    62  		channel, reqs, err := newChannel.Accept()
    63  		c.Assert(err, jc.ErrorIsNil)
    64  		wg.Add(1)
    65  		go func() {
    66  			defer wg.Done()
    67  			defer channel.Close()
    68  			for req := range reqs {
    69  				switch req.Type {
    70  				case "exec":
    71  					c.Assert(req.WantReply, jc.IsTrue)
    72  					n := binary.BigEndian.Uint32(req.Payload[:4])
    73  					command := string(req.Payload[4 : n+4])
    74  					c.Assert(command, gc.Equals, testCommandFlat)
    75  					req.Reply(true, nil)
    76  					channel.Write([]byte("abc value\n"))
    77  					_, err := channel.SendRequest("exit-status", false, cryptossh.Marshal(&struct{ n uint32 }{0}))
    78  					c.Check(err, jc.ErrorIsNil)
    79  					return
    80  				default:
    81  					c.Errorf("Unexpected request type: %v", req.Type)
    82  					return
    83  				}
    84  			}
    85  		}()
    86  	}
    87  }
    88  
    89  type SSHGoCryptoCommandSuite struct {
    90  	testing.IsolationSuite
    91  	client ssh.Client
    92  }
    93  
    94  var _ = gc.Suite(&SSHGoCryptoCommandSuite{})
    95  
    96  func (s *SSHGoCryptoCommandSuite) SetUpTest(c *gc.C) {
    97  	s.IsolationSuite.SetUpTest(c)
    98  	generateKeyRestorer := overrideGenerateKey(c)
    99  	s.AddCleanup(func(*gc.C) { generateKeyRestorer.Restore() })
   100  	client, err := ssh.NewGoCryptoClient()
   101  	c.Assert(err, jc.ErrorIsNil)
   102  	s.client = client
   103  }
   104  
   105  func (s *SSHGoCryptoCommandSuite) TestNewGoCryptoClient(c *gc.C) {
   106  	_, err := ssh.NewGoCryptoClient()
   107  	c.Assert(err, jc.ErrorIsNil)
   108  	private, _, err := ssh.GenerateKey("test-client")
   109  	c.Assert(err, jc.ErrorIsNil)
   110  	key, err := cryptossh.ParsePrivateKey([]byte(private))
   111  	c.Assert(err, jc.ErrorIsNil)
   112  	_, err = ssh.NewGoCryptoClient(key)
   113  	c.Assert(err, jc.ErrorIsNil)
   114  }
   115  
   116  func (s *SSHGoCryptoCommandSuite) TestClientNoKeys(c *gc.C) {
   117  	client, err := ssh.NewGoCryptoClient()
   118  	c.Assert(err, jc.ErrorIsNil)
   119  	cmd := client.Command("0.1.2.3", []string{"echo", "123"}, nil)
   120  	_, err = cmd.Output()
   121  	c.Assert(err, gc.ErrorMatches, "no private keys available")
   122  	defer ssh.ClearClientKeys()
   123  	err = ssh.LoadClientKeys(c.MkDir())
   124  	c.Assert(err, jc.ErrorIsNil)
   125  
   126  	s.PatchValue(ssh.SSHDial, func(network, address string, cfg *cryptossh.ClientConfig) (*cryptossh.Client, error) {
   127  		return nil, errors.New("ssh.Dial failed")
   128  	})
   129  	cmd = client.Command("0.1.2.3", []string{"echo", "123"}, nil)
   130  	_, err = cmd.Output()
   131  	// error message differs based on whether using cgo or not
   132  	c.Assert(err, gc.ErrorMatches, "ssh.Dial failed")
   133  }
   134  
   135  func (s *SSHGoCryptoCommandSuite) TestCommand(c *gc.C) {
   136  	private, _, err := ssh.GenerateKey("test-server")
   137  	c.Assert(err, jc.ErrorIsNil)
   138  	key, err := cryptossh.ParsePrivateKey([]byte(private))
   139  	client, err := ssh.NewGoCryptoClient(key)
   140  	c.Assert(err, jc.ErrorIsNil)
   141  	server := newServer(c)
   142  	var opts ssh.Options
   143  	opts.SetPort(server.listener.Addr().(*net.TCPAddr).Port)
   144  	cmd := client.Command("127.0.0.1", testCommand, &opts)
   145  	checkedKey := false
   146  	server.cfg.PublicKeyCallback = func(conn cryptossh.ConnMetadata, pubkey cryptossh.PublicKey) (*cryptossh.Permissions, error) {
   147  		c.Check(pubkey, gc.DeepEquals, key.PublicKey())
   148  		checkedKey = true
   149  		return nil, nil
   150  	}
   151  	go server.run(c)
   152  	out, err := cmd.Output()
   153  	c.Assert(err, jc.ErrorIsNil)
   154  	c.Assert(string(out), gc.Equals, "abc value\n")
   155  	c.Assert(checkedKey, jc.IsTrue)
   156  }
   157  
   158  func (s *SSHGoCryptoCommandSuite) TestCopy(c *gc.C) {
   159  	client, err := ssh.NewGoCryptoClient()
   160  	c.Assert(err, jc.ErrorIsNil)
   161  	err = client.Copy([]string{"0.1.2.3:b", c.MkDir()}, nil)
   162  	c.Assert(err, gc.ErrorMatches, `scp command is not implemented \(OpenSSH scp not available in PATH\)`)
   163  }
   164  
   165  func (s *SSHGoCryptoCommandSuite) TestProxyCommand(c *gc.C) {
   166  	realNetcat, err := exec.LookPath("nc")
   167  	if err != nil {
   168  		c.Skip("skipping test, couldn't find netcat: %v")
   169  		return
   170  	}
   171  	netcat := filepath.Join(c.MkDir(), "nc")
   172  	err = ioutil.WriteFile(netcat, []byte("#!/bin/sh\necho $0 \"$@\" > $0.args && exec "+realNetcat+" \"$@\""), 0755)
   173  	c.Assert(err, jc.ErrorIsNil)
   174  
   175  	private, _, err := ssh.GenerateKey("test-server")
   176  	c.Assert(err, jc.ErrorIsNil)
   177  	key, err := cryptossh.ParsePrivateKey([]byte(private))
   178  	client, err := ssh.NewGoCryptoClient(key)
   179  	c.Assert(err, jc.ErrorIsNil)
   180  	server := newServer(c)
   181  	var opts ssh.Options
   182  	port := server.listener.Addr().(*net.TCPAddr).Port
   183  	opts.SetProxyCommand(netcat, "-q0", "%h", "%p")
   184  	opts.SetPort(port)
   185  	cmd := client.Command("127.0.0.1", testCommand, &opts)
   186  	server.cfg.PublicKeyCallback = func(_ cryptossh.ConnMetadata, pubkey cryptossh.PublicKey) (*cryptossh.Permissions, error) {
   187  		return nil, nil
   188  	}
   189  	go server.run(c)
   190  	out, err := cmd.Output()
   191  	c.Assert(err, jc.ErrorIsNil)
   192  	c.Assert(string(out), gc.Equals, "abc value\n")
   193  	// Ensure the proxy command was executed with the appropriate arguments.
   194  	data, err := ioutil.ReadFile(netcat + ".args")
   195  	c.Assert(err, jc.ErrorIsNil)
   196  	c.Assert(string(data), gc.Equals, fmt.Sprintf("%s -q0 127.0.0.1 %v\n", netcat, port))
   197  }