github.com/kjmkznr/terraform@v0.5.2-0.20180216194316-1d0f5fdac99e/communicator/ssh/communicator_test.go (about)

     1  // +build !race
     2  
     3  package ssh
     4  
     5  import (
     6  	"bufio"
     7  	"bytes"
     8  	"encoding/base64"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"math/rand"
    13  	"net"
    14  	"os"
    15  	"path/filepath"
    16  	"regexp"
    17  	"strconv"
    18  	"strings"
    19  	"testing"
    20  
    21  	"github.com/hashicorp/terraform/communicator/remote"
    22  	"github.com/hashicorp/terraform/terraform"
    23  	"golang.org/x/crypto/ssh"
    24  )
    25  
    26  // private key for mock server
    27  const testServerPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
    28  MIIEpAIBAAKCAQEA19lGVsTqIT5iiNYRgnoY1CwkbETW5cq+Rzk5v/kTlf31XpSU
    29  70HVWkbTERECjaYdXM2gGcbb+sxpq6GtXf1M3kVomycqhxwhPv4Cr6Xp4WT/jkFx
    30  9z+FFzpeodGJWjOH6L2H5uX1Cvr9EDdQp9t9/J32/qBFntY8GwoUI/y/1MSTmMiF
    31  tupdMODN064vd3gyMKTwrlQ8tZM6aYuyOPsutLlUY7M5x5FwMDYvnPDSeyT/Iw0z
    32  s3B+NCyqeeMd2T7YzQFnRATj0M7rM5LoSs7DVqVriOEABssFyLj31PboaoLhOKgc
    33  qoM9khkNzr7FHVvi+DhYM2jD0DwvqZLN6NmnLwIDAQABAoIBAQCGVj+kuSFOV1lT
    34  +IclQYA6bM6uY5mroqcSBNegVxCNhWU03BxlW//BE9tA/+kq53vWylMeN9mpGZea
    35  riEMIh25KFGWXqXlOOioH8bkMsqA8S7sBmc7jljyv+0toQ9vCCtJ+sueNPhxQQxH
    36  D2YvUjfzBQ04I9+wn30BByDJ1QA/FoPsunxIOUCcRBE/7jxuLYcpR+JvEF68yYIh
    37  atXRld4W4in7T65YDR8jK1Uj9XAcNeDYNpT/M6oFLx1aPIlkG86aCWRO19S1jLPT
    38  b1ZAKHHxPMCVkSYW0RqvIgLXQOR62D0Zne6/2wtzJkk5UCjkSQ2z7ZzJpMkWgDgN
    39  ifCULFPBAoGBAPoMZ5q1w+zB+knXUD33n1J+niN6TZHJulpf2w5zsW+m2K6Zn62M
    40  MXndXlVAHtk6p02q9kxHdgov34Uo8VpuNjbS1+abGFTI8NZgFo+bsDxJdItemwC4
    41  KJ7L1iz39hRN/ZylMRLz5uTYRGddCkeIHhiG2h7zohH/MaYzUacXEEy3AoGBANz8
    42  e/msleB+iXC0cXKwds26N4hyMdAFE5qAqJXvV3S2W8JZnmU+sS7vPAWMYPlERPk1
    43  D8Q2eXqdPIkAWBhrx4RxD7rNc5qFNcQWEhCIxC9fccluH1y5g2M+4jpMX2CT8Uv+
    44  3z+NoJ5uDTXZTnLCfoZzgZ4nCZVZ+6iU5U1+YXFJAoGBANLPpIV920n/nJmmquMj
    45  orI1R/QXR9Cy56cMC65agezlGOfTYxk5Cfl5Ve+/2IJCfgzwJyjWUsFx7RviEeGw
    46  64o7JoUom1HX+5xxdHPsyZ96OoTJ5RqtKKoApnhRMamau0fWydH1yeOEJd+TRHhc
    47  XStGfhz8QNa1dVFvENczja1vAoGABGWhsd4VPVpHMc7lUvrf4kgKQtTC2PjA4xoc
    48  QJ96hf/642sVE76jl+N6tkGMzGjnVm4P2j+bOy1VvwQavKGoXqJBRd5Apppv727g
    49  /SM7hBXKFc/zH80xKBBgP/i1DR7kdjakCoeu4ngeGywvu2jTS6mQsqzkK+yWbUxJ
    50  I7mYBsECgYB/KNXlTEpXtz/kwWCHFSYA8U74l7zZbVD8ul0e56JDK+lLcJ0tJffk
    51  gqnBycHj6AhEycjda75cs+0zybZvN4x65KZHOGW/O/7OAWEcZP5TPb3zf9ned3Hl
    52  NsZoFj52ponUM6+99A2CmezFCN16c4mbA//luWF+k3VVqR6BpkrhKw==
    53  -----END RSA PRIVATE KEY-----`
    54  
    55  // this cert was signed by the key from testCAPublicKey
    56  const testServerHostCert = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgvQ3Bs1ex7277b9q6I0fNaWsVEC16f+LcT8RLPSVMEVMAAAADAQABAAABAQDX2UZWxOohPmKI1hGCehjULCRsRNblyr5HOTm/+ROV/fVelJTvQdVaRtMREQKNph1czaAZxtv6zGmroa1d/UzeRWibJyqHHCE+/gKvpenhZP+OQXH3P4UXOl6h0YlaM4fovYfm5fUK+v0QN1Cn2338nfb+oEWe1jwbChQj/L/UxJOYyIW26l0w4M3Tri93eDIwpPCuVDy1kzppi7I4+y60uVRjsznHkXAwNi+c8NJ7JP8jDTOzcH40LKp54x3ZPtjNAWdEBOPQzuszkuhKzsNWpWuI4QAGywXIuPfU9uhqguE4qByqgz2SGQ3OvsUdW+L4OFgzaMPQPC+pks3o2acvAAAAAAAAAAAAAAACAAAAB2NhLXRlc3QAAAANAAAACTEyNy4wLjAuMQAAAABag0jkAAAAAHDcHtAAAAAAAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQCrozyZIhdEvalCn+eSzHH94cO9ykiywA13ntWI7mJcHBwYTeCYWG8E9zGXyp2iDOjCGudM0Tdt8o0OofKChk9Z/qiUN0G8y1kmaXBlBM3qA5R9NPpvMYMNkYLfX6ivtZCnqrsbzaoqN2Oc/7H2StHzJWh/XCGu9otQZA6vdv1oSmAsZOjw/xIGaGQqDUaLq21J280PP1qSbdJHf76iSHE+TWe3YpqV946JWM5tCh0DykZ10VznvxYpUjzhr07IN3tVKxOXbPnnU7lX6IaLIWgfzLqwSyheeux05c3JLF9iF4sFu8ou4hwQz1iuUTU1jxgwZP0w/bkXgFFs0949lW81AAABDwAAAAdzc2gtcnNhAAABAEyoiVkZ5z79nh3WSU5mU2U7e2BItnnEqsJIm9EN+35uG0yORSXmQoaa9mtli7G3r79tyqEJd/C95EdNvU/9TjaoDcbH8OHP+Ue9XSfUzBuQ6bGSXe6mlZlO7QJ1cIyWphFP3MkrweDSiJ+SpeXzLzZkiJ7zKv5czhBEyG/MujFgvikotL+eUNG42y2cgsesXSjENSBS3l11q55a+RM2QKt3W32im8CsSxrH6Mz6p4JXQNgsVvZRknLxNlWXULFB2HLTunPKzJNMTf6xZf66oivSBAXVIdNKhlVpAQ3dT/dW5K6J4aQF/hjWByyLprFwZ16cPDqvtalnTCpbRYelNbw=`
    57  
    58  const testCAPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrozyZIhdEvalCn+eSzHH94cO9ykiywA13ntWI7mJcHBwYTeCYWG8E9zGXyp2iDOjCGudM0Tdt8o0OofKChk9Z/qiUN0G8y1kmaXBlBM3qA5R9NPpvMYMNkYLfX6ivtZCnqrsbzaoqN2Oc/7H2StHzJWh/XCGu9otQZA6vdv1oSmAsZOjw/xIGaGQqDUaLq21J280PP1qSbdJHf76iSHE+TWe3YpqV946JWM5tCh0DykZ10VznvxYpUjzhr07IN3tVKxOXbPnnU7lX6IaLIWgfzLqwSyheeux05c3JLF9iF4sFu8ou4hwQz1iuUTU1jxgwZP0w/bkXgFFs0949lW81`
    59  
    60  func newMockLineServer(t *testing.T, signer ssh.Signer) string {
    61  	serverConfig := &ssh.ServerConfig{
    62  		PasswordCallback:  acceptUserPass("user", "pass"),
    63  		PublicKeyCallback: acceptPublicKey(testClientPublicKey),
    64  	}
    65  
    66  	var err error
    67  	if signer == nil {
    68  		signer, err = ssh.ParsePrivateKey([]byte(testServerPrivateKey))
    69  		if err != nil {
    70  			t.Fatalf("unable to parse private key: %s", err)
    71  		}
    72  	}
    73  	serverConfig.AddHostKey(signer)
    74  
    75  	l, err := net.Listen("tcp", "127.0.0.1:0")
    76  	if err != nil {
    77  		t.Fatalf("Unable to listen for connection: %s", err)
    78  	}
    79  
    80  	go func() {
    81  		defer l.Close()
    82  		c, err := l.Accept()
    83  		if err != nil {
    84  			t.Errorf("Unable to accept incoming connection: %s", err)
    85  		}
    86  		defer c.Close()
    87  		conn, chans, _, err := ssh.NewServerConn(c, serverConfig)
    88  		if err != nil {
    89  			t.Logf("Handshaking error: %v", err)
    90  		}
    91  		t.Log("Accepted SSH connection")
    92  
    93  		for newChannel := range chans {
    94  			channel, requests, err := newChannel.Accept()
    95  			if err != nil {
    96  				t.Errorf("Unable to accept channel.")
    97  			}
    98  			t.Log("Accepted channel")
    99  
   100  			go func(in <-chan *ssh.Request) {
   101  				for req := range in {
   102  					if req.WantReply {
   103  						req.Reply(true, nil)
   104  					}
   105  				}
   106  			}(requests)
   107  
   108  			go func(newChannel ssh.NewChannel) {
   109  				conn.OpenChannel(newChannel.ChannelType(), nil)
   110  			}(newChannel)
   111  
   112  			defer channel.Close()
   113  		}
   114  		conn.Close()
   115  	}()
   116  
   117  	return l.Addr().String()
   118  }
   119  
   120  func TestNew_Invalid(t *testing.T) {
   121  	address := newMockLineServer(t, nil)
   122  	parts := strings.Split(address, ":")
   123  
   124  	r := &terraform.InstanceState{
   125  		Ephemeral: terraform.EphemeralState{
   126  			ConnInfo: map[string]string{
   127  				"type":     "ssh",
   128  				"user":     "user",
   129  				"password": "i-am-invalid",
   130  				"host":     parts[0],
   131  				"port":     parts[1],
   132  				"timeout":  "30s",
   133  			},
   134  		},
   135  	}
   136  
   137  	c, err := New(r)
   138  	if err != nil {
   139  		t.Fatalf("error creating communicator: %s", err)
   140  	}
   141  
   142  	err = c.Connect(nil)
   143  	if err == nil {
   144  		t.Fatal("should have had an error connecting")
   145  	}
   146  }
   147  
   148  func TestStart(t *testing.T) {
   149  	address := newMockLineServer(t, nil)
   150  	parts := strings.Split(address, ":")
   151  
   152  	r := &terraform.InstanceState{
   153  		Ephemeral: terraform.EphemeralState{
   154  			ConnInfo: map[string]string{
   155  				"type":     "ssh",
   156  				"user":     "user",
   157  				"password": "pass",
   158  				"host":     parts[0],
   159  				"port":     parts[1],
   160  				"timeout":  "30s",
   161  			},
   162  		},
   163  	}
   164  
   165  	c, err := New(r)
   166  	if err != nil {
   167  		t.Fatalf("error creating communicator: %s", err)
   168  	}
   169  
   170  	var cmd remote.Cmd
   171  	stdout := new(bytes.Buffer)
   172  	cmd.Command = "echo foo"
   173  	cmd.Stdout = stdout
   174  
   175  	err = c.Start(&cmd)
   176  	if err != nil {
   177  		t.Fatalf("error executing remote command: %s", err)
   178  	}
   179  }
   180  
   181  func TestHostKey(t *testing.T) {
   182  	// get the server's public key
   183  	signer, err := ssh.ParsePrivateKey([]byte(testServerPrivateKey))
   184  	if err != nil {
   185  		panic("unable to parse private key: " + err.Error())
   186  	}
   187  	pubKey := fmt.Sprintf("ssh-rsa %s", base64.StdEncoding.EncodeToString(signer.PublicKey().Marshal()))
   188  
   189  	address := newMockLineServer(t, nil)
   190  	host, p, _ := net.SplitHostPort(address)
   191  	port, _ := strconv.Atoi(p)
   192  
   193  	connInfo := &connectionInfo{
   194  		User:     "user",
   195  		Password: "pass",
   196  		Host:     host,
   197  		HostKey:  pubKey,
   198  		Port:     port,
   199  		Timeout:  "30s",
   200  	}
   201  
   202  	cfg, err := prepareSSHConfig(connInfo)
   203  	if err != nil {
   204  		t.Fatal(err)
   205  	}
   206  
   207  	c := &Communicator{
   208  		connInfo: connInfo,
   209  		config:   cfg,
   210  	}
   211  
   212  	var cmd remote.Cmd
   213  	stdout := new(bytes.Buffer)
   214  	cmd.Command = "echo foo"
   215  	cmd.Stdout = stdout
   216  
   217  	if err := c.Start(&cmd); err != nil {
   218  		t.Fatal(err)
   219  	}
   220  	if err := c.Disconnect(); err != nil {
   221  		t.Fatal(err)
   222  	}
   223  
   224  	// now check with the wrong HostKey
   225  	address = newMockLineServer(t, nil)
   226  	_, p, _ = net.SplitHostPort(address)
   227  	port, _ = strconv.Atoi(p)
   228  
   229  	connInfo.HostKey = testClientPublicKey
   230  	connInfo.Port = port
   231  
   232  	cfg, err = prepareSSHConfig(connInfo)
   233  	if err != nil {
   234  		t.Fatal(err)
   235  	}
   236  
   237  	c = &Communicator{
   238  		connInfo: connInfo,
   239  		config:   cfg,
   240  	}
   241  
   242  	err = c.Start(&cmd)
   243  	if err == nil || !strings.Contains(err.Error(), "mismatch") {
   244  		t.Fatalf("expected host key mismatch, got error:%v", err)
   245  	}
   246  }
   247  
   248  func TestHostCert(t *testing.T) {
   249  	pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(testServerHostCert))
   250  	if err != nil {
   251  		t.Fatal(err)
   252  	}
   253  
   254  	signer, err := ssh.ParsePrivateKey([]byte(testServerPrivateKey))
   255  	if err != nil {
   256  		t.Fatal(err)
   257  	}
   258  
   259  	signer, err = ssh.NewCertSigner(pk.(*ssh.Certificate), signer)
   260  	if err != nil {
   261  		t.Fatal(err)
   262  	}
   263  
   264  	address := newMockLineServer(t, signer)
   265  	host, p, _ := net.SplitHostPort(address)
   266  	port, _ := strconv.Atoi(p)
   267  
   268  	connInfo := &connectionInfo{
   269  		User:     "user",
   270  		Password: "pass",
   271  		Host:     host,
   272  		HostKey:  testCAPublicKey,
   273  		Port:     port,
   274  		Timeout:  "30s",
   275  	}
   276  
   277  	cfg, err := prepareSSHConfig(connInfo)
   278  	if err != nil {
   279  		t.Fatal(err)
   280  	}
   281  
   282  	c := &Communicator{
   283  		connInfo: connInfo,
   284  		config:   cfg,
   285  	}
   286  
   287  	var cmd remote.Cmd
   288  	stdout := new(bytes.Buffer)
   289  	cmd.Command = "echo foo"
   290  	cmd.Stdout = stdout
   291  
   292  	if err := c.Start(&cmd); err != nil {
   293  		t.Fatal(err)
   294  	}
   295  	if err := c.Disconnect(); err != nil {
   296  		t.Fatal(err)
   297  	}
   298  
   299  	// now check with the wrong HostKey
   300  	address = newMockLineServer(t, signer)
   301  	_, p, _ = net.SplitHostPort(address)
   302  	port, _ = strconv.Atoi(p)
   303  
   304  	connInfo.HostKey = testClientPublicKey
   305  	connInfo.Port = port
   306  
   307  	cfg, err = prepareSSHConfig(connInfo)
   308  	if err != nil {
   309  		t.Fatal(err)
   310  	}
   311  
   312  	c = &Communicator{
   313  		connInfo: connInfo,
   314  		config:   cfg,
   315  	}
   316  
   317  	err = c.Start(&cmd)
   318  	if err == nil || !strings.Contains(err.Error(), "authorities") {
   319  		t.Fatalf("expected host key mismatch, got error:%v", err)
   320  	}
   321  }
   322  
   323  func TestAccUploadFile(t *testing.T) {
   324  	// use the local ssh server and scp binary to check uploads
   325  	if ok := os.Getenv("SSH_UPLOAD_TEST"); ok == "" {
   326  		t.Log("Skipping Upload Acceptance without SSH_UPLOAD_TEST set")
   327  		t.Skip()
   328  	}
   329  
   330  	r := &terraform.InstanceState{
   331  		Ephemeral: terraform.EphemeralState{
   332  			ConnInfo: map[string]string{
   333  				"type":    "ssh",
   334  				"user":    os.Getenv("USER"),
   335  				"host":    "127.0.0.1",
   336  				"port":    "22",
   337  				"timeout": "30s",
   338  			},
   339  		},
   340  	}
   341  
   342  	c, err := New(r)
   343  	if err != nil {
   344  		t.Fatalf("error creating communicator: %s", err)
   345  	}
   346  
   347  	tmpDir, err := ioutil.TempDir("", "communicator")
   348  	if err != nil {
   349  		t.Fatal(err)
   350  	}
   351  	defer os.RemoveAll(tmpDir)
   352  
   353  	content := []byte("this is the file content")
   354  	source := bytes.NewReader(content)
   355  	tmpFile := filepath.Join(tmpDir, "tempFile.out")
   356  	err = c.Upload(tmpFile, source)
   357  	if err != nil {
   358  		t.Fatalf("error uploading file: %s", err)
   359  	}
   360  
   361  	data, err := ioutil.ReadFile(tmpFile)
   362  	if err != nil {
   363  		t.Fatal(err)
   364  	}
   365  
   366  	if !bytes.Equal(data, content) {
   367  		t.Fatalf("bad: %s", data)
   368  	}
   369  }
   370  
   371  func TestAccHugeUploadFile(t *testing.T) {
   372  	// use the local ssh server and scp binary to check uploads
   373  	if ok := os.Getenv("SSH_UPLOAD_TEST"); ok == "" {
   374  		t.Log("Skipping Upload Acceptance without SSH_UPLOAD_TEST set")
   375  		t.Skip()
   376  	}
   377  
   378  	r := &terraform.InstanceState{
   379  		Ephemeral: terraform.EphemeralState{
   380  			ConnInfo: map[string]string{
   381  				"type":    "ssh",
   382  				"user":    os.Getenv("USER"),
   383  				"host":    "127.0.0.1",
   384  				"port":    "22",
   385  				"timeout": "30s",
   386  			},
   387  		},
   388  	}
   389  
   390  	c, err := New(r)
   391  	if err != nil {
   392  		t.Fatalf("error creating communicator: %s", err)
   393  	}
   394  
   395  	// copy 4GB of data, random to prevent compression.
   396  	size := int64(1 << 32)
   397  	source := io.LimitReader(rand.New(rand.NewSource(0)), size)
   398  
   399  	dest, err := ioutil.TempFile("", "communicator")
   400  	if err != nil {
   401  		t.Fatal(err)
   402  	}
   403  	destName := dest.Name()
   404  	dest.Close()
   405  	defer os.Remove(destName)
   406  
   407  	t.Log("Uploading to", destName)
   408  
   409  	// bypass the Upload method so we can directly supply the file size
   410  	// preventing the extra copy of the huge file.
   411  	targetDir := filepath.Dir(destName)
   412  	targetFile := filepath.Base(destName)
   413  
   414  	scpFunc := func(w io.Writer, stdoutR *bufio.Reader) error {
   415  		return scpUploadFile(targetFile, source, w, stdoutR, size)
   416  	}
   417  
   418  	err = c.scpSession("scp -vt "+targetDir, scpFunc)
   419  	if err != nil {
   420  		t.Fatal(err)
   421  	}
   422  
   423  	// check the final file size
   424  	fs, err := os.Stat(destName)
   425  	if err != nil {
   426  		t.Fatal(err)
   427  	}
   428  
   429  	if fs.Size() != size {
   430  		t.Fatalf("expected file size of %d, got %d", size, fs.Size())
   431  	}
   432  }
   433  
   434  func TestScriptPath(t *testing.T) {
   435  	cases := []struct {
   436  		Input   string
   437  		Pattern string
   438  	}{
   439  		{
   440  			"/tmp/script.sh",
   441  			`^/tmp/script\.sh$`,
   442  		},
   443  		{
   444  			"/tmp/script_%RAND%.sh",
   445  			`^/tmp/script_(\d+)\.sh$`,
   446  		},
   447  	}
   448  
   449  	for _, tc := range cases {
   450  		r := &terraform.InstanceState{
   451  			Ephemeral: terraform.EphemeralState{
   452  				ConnInfo: map[string]string{
   453  					"type":        "ssh",
   454  					"script_path": tc.Input,
   455  				},
   456  			},
   457  		}
   458  		comm, err := New(r)
   459  		if err != nil {
   460  			t.Fatalf("err: %s", err)
   461  		}
   462  		output := comm.ScriptPath()
   463  
   464  		match, err := regexp.Match(tc.Pattern, []byte(output))
   465  		if err != nil {
   466  			t.Fatalf("bad: %s\n\nerr: %s", tc.Input, err)
   467  		}
   468  		if !match {
   469  			t.Fatalf("bad: %s\n\n%s", tc.Input, output)
   470  		}
   471  	}
   472  }
   473  
   474  func TestScriptPath_randSeed(t *testing.T) {
   475  	// Pre GH-4186 fix, this value was the deterministic start the pseudorandom
   476  	// chain of unseeded math/rand values for Int31().
   477  	staticSeedPath := "/tmp/terraform_1298498081.sh"
   478  	c, err := New(&terraform.InstanceState{})
   479  	if err != nil {
   480  		t.Fatalf("err: %s", err)
   481  	}
   482  	path := c.ScriptPath()
   483  	if path == staticSeedPath {
   484  		t.Fatalf("rand not seeded! got: %s", path)
   485  	}
   486  }
   487  
   488  const testClientPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
   489  MIIEpQIBAAKCAQEAxOgNXOJ/jrRDxBZTSk2X9otNy9zcpUmJr5ifDi5sy7j2ZiQS
   490  beBt1Wf+tLNWis8Cyq06ttEvjjRuM75yucyD6GrqDTXVCSm4PeOIQeDhPhw26wYZ
   491  O0h/mFgrAiCwaEl8AFXVBInOhVn/0nqgUpkwckh76bjTsNeifkiugK3cfJOuBdrU
   492  ZGbgugJjmYo4Cmv7ECo1gCFT5N+BAjOji3z3N5ClBH5HaWC77jH7kTH0k5cZ+ZRQ
   493  tG9EqLyvWlnzTAR/Yly0JInkOa16Ms1Au5sIJwEoJfHKsNVK06IlLob53nblwYW0
   494  H5gv1Kb/rS+nUkpPtA5YFShB7iZnPLPPv6qXSwIDAQABAoIBAC0UY1rMkB9/rbQK
   495  2G6+bPgI1HrDydAdkeQdsOxyPH43jlG8GGwHYZ3l/S4pkLqewijcmACay6Rm5IP8
   496  Kg/XfquLLqJvnKJIZuHkYaGTdn3dv8T21Hf6FRwvs0j9auW1TSpWfDpZwmpNPIBX
   497  irTeVXUUmynbIrvt4km/IhRbuYrbbb964CLYD1DCl3XssXxoRNvPpc5EtOuyDorA
   498  5g1hvZR1FqbOAmOuNQMYJociMuWB8mCaHb+o1Sg4A65OLXxoKs0cuwInJ/n/R4Z3
   499  +GrV+x5ypBMxXgjjQtKMLEOujkvxs1cp34hkbhKMHHXxbMu5jl74YtGGsLLk90rq
   500  ieZGIgECgYEA49OM9mMCrDoFUTZdJaSARA/MOXkdQgrqVTv9kUHee7oeMZZ6lS0i
   501  bPU7g+Bq+UAN0qcw9x992eAElKjBA71Q5UbZYWh29BDMZd8bRJmwz4P6aSMoYLWI
   502  Sr31caJU9LdmPFatarNeehjSJtlTuoZD9+NElnnUwNaTeOOo5UdhTQsCgYEA3UGm
   503  QWoDUttFwK9oL2KL8M54Bx6EzNhnyk03WrqBbR7PJcPKnsF0R/0soQ+y0FW0r8RJ
   504  TqG6ze5fUJII72B4GlMTQdP+BIvaKQttwWQTNIjbbv4NksF445gdVOO1xi9SvQ7k
   505  uvMVxOb+1jL3HAFa3furWu2tJRDs6dhuaILLxsECgYEAhnhlKUBDYZhVbxvhWsh/
   506  lKymY/3ikQqUSX7BKa1xPiIalDY3YDllql4MpMgfG8L85asdMZ96ztB0o7H/Ss/B
   507  IbLxt5bLLz+DBVXsaE82lyVU9h10RbCgI01/w3SHJHHjfBXFAcehKfvgfmGkE+IP
   508  2A5ie1aphrCgFqh5FetNuQUCgYEAibL42I804FUtFR1VduAa/dRRqQSaW6528dWa
   509  lLGsKRBalUNEEAeP6dmr89UEUVp1qEo94V0QGGe5FDi+rNPaC3AWdQqNdaDgNlkx
   510  hoFU3oYqIuqj4ejc5rBd2N4a2+vJz3W8bokozDGC+iYf2mMRfUPKwj1XW9Er0OFs
   511  3UhBsEECgYEAto/iJB7ZlCM7EyV9JW0tsEt83rbKMQ/Ex0ShbBIejej0Xx7bwx60
   512  tVgay+bzJnNkXu6J4XVI98A/WsdI2kW4hL0STYdHV5HVA1l87V4ZbvTF2Bx8a8RJ
   513  OF3UjpMTWKqOprw9nAu5VuwNRVzORF8ER8rgGeaR2/gsSvIYFy9VXq8=
   514  -----END RSA PRIVATE KEY-----`
   515  
   516  var testClientPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDE6A1c4n+OtEPEFlNKTZf2i03L3NylSYmvmJ8OLmzLuPZmJBJt4G3VZ/60s1aKzwLKrTq20S+ONG4zvnK5zIPoauoNNdUJKbg944hB4OE+HDbrBhk7SH+YWCsCILBoSXwAVdUEic6FWf/SeqBSmTBySHvpuNOw16J+SK6Ardx8k64F2tRkZuC6AmOZijgKa/sQKjWAIVPk34ECM6OLfPc3kKUEfkdpYLvuMfuRMfSTlxn5lFC0b0SovK9aWfNMBH9iXLQkieQ5rXoyzUC7mwgnASgl8cqw1UrToiUuhvneduXBhbQfmC/Upv+tL6dSSk+0DlgVKEHuJmc8s8+/qpdL`
   517  
   518  func acceptUserPass(goodUser, goodPass string) func(ssh.ConnMetadata, []byte) (*ssh.Permissions, error) {
   519  	return func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
   520  		if c.User() == goodUser && string(pass) == goodPass {
   521  			return nil, nil
   522  		}
   523  		return nil, fmt.Errorf("password rejected for %q", c.User())
   524  	}
   525  }
   526  
   527  func acceptPublicKey(keystr string) func(ssh.ConnMetadata, ssh.PublicKey) (*ssh.Permissions, error) {
   528  	goodkey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keystr))
   529  	if err != nil {
   530  		panic(fmt.Errorf("error parsing key: %s", err))
   531  	}
   532  	return func(_ ssh.ConnMetadata, inkey ssh.PublicKey) (*ssh.Permissions, error) {
   533  		if bytes.Equal(inkey.Marshal(), goodkey.Marshal()) {
   534  			return nil, nil
   535  		}
   536  
   537  		return nil, fmt.Errorf("public key rejected")
   538  	}
   539  }