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