launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/utils/ssh/authorisedkeys_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package ssh_test
     5  
     6  import (
     7  	"encoding/base64"
     8  	gc "launchpad.net/gocheck"
     9  	"strings"
    10  	stdtesting "testing"
    11  
    12  	coretesting "launchpad.net/juju-core/testing"
    13  	"launchpad.net/juju-core/testing/testbase"
    14  	"launchpad.net/juju-core/utils/ssh"
    15  	sshtesting "launchpad.net/juju-core/utils/ssh/testing"
    16  )
    17  
    18  func Test(t *stdtesting.T) {
    19  	gc.TestingT(t)
    20  }
    21  
    22  type AuthorisedKeysKeysSuite struct {
    23  	testbase.LoggingSuite
    24  }
    25  
    26  const (
    27  	// We'll use the current user for ssh tests.
    28  	testSSHUser = ""
    29  )
    30  
    31  var _ = gc.Suite(&AuthorisedKeysKeysSuite{})
    32  
    33  func (s *AuthorisedKeysKeysSuite) SetUpTest(c *gc.C) {
    34  	s.LoggingSuite.SetUpTest(c)
    35  	fakeHome := coretesting.MakeEmptyFakeHomeWithoutJuju(c)
    36  	s.AddCleanup(func(*gc.C) { fakeHome.Restore() })
    37  }
    38  
    39  func writeAuthKeysFile(c *gc.C, keys []string) {
    40  	err := ssh.WriteAuthorisedKeys(testSSHUser, keys)
    41  	c.Assert(err, gc.IsNil)
    42  }
    43  
    44  func (s *AuthorisedKeysKeysSuite) TestListKeys(c *gc.C) {
    45  	keys := []string{
    46  		sshtesting.ValidKeyOne.Key + " user@host",
    47  		sshtesting.ValidKeyTwo.Key,
    48  	}
    49  	writeAuthKeysFile(c, keys)
    50  	keys, err := ssh.ListKeys(testSSHUser, ssh.Fingerprints)
    51  	c.Assert(err, gc.IsNil)
    52  	c.Assert(
    53  		keys, gc.DeepEquals,
    54  		[]string{sshtesting.ValidKeyOne.Fingerprint + " (user@host)", sshtesting.ValidKeyTwo.Fingerprint})
    55  }
    56  
    57  func (s *AuthorisedKeysKeysSuite) TestListKeysFull(c *gc.C) {
    58  	keys := []string{
    59  		sshtesting.ValidKeyOne.Key + " user@host",
    60  		sshtesting.ValidKeyTwo.Key + " anotheruser@host",
    61  	}
    62  	writeAuthKeysFile(c, keys)
    63  	actual, err := ssh.ListKeys(testSSHUser, ssh.FullKeys)
    64  	c.Assert(err, gc.IsNil)
    65  	c.Assert(actual, gc.DeepEquals, keys)
    66  }
    67  
    68  func (s *AuthorisedKeysKeysSuite) TestAddNewKey(c *gc.C) {
    69  	key := sshtesting.ValidKeyOne.Key + " user@host"
    70  	err := ssh.AddKeys(testSSHUser, key)
    71  	c.Assert(err, gc.IsNil)
    72  	actual, err := ssh.ListKeys(testSSHUser, ssh.FullKeys)
    73  	c.Assert(err, gc.IsNil)
    74  	c.Assert(actual, gc.DeepEquals, []string{key})
    75  }
    76  
    77  func (s *AuthorisedKeysKeysSuite) TestAddMoreKeys(c *gc.C) {
    78  	firstKey := sshtesting.ValidKeyOne.Key + " user@host"
    79  	writeAuthKeysFile(c, []string{firstKey})
    80  	moreKeys := []string{
    81  		sshtesting.ValidKeyTwo.Key + " anotheruser@host",
    82  		sshtesting.ValidKeyThree.Key + " yetanotheruser@host",
    83  	}
    84  	err := ssh.AddKeys(testSSHUser, moreKeys...)
    85  	c.Assert(err, gc.IsNil)
    86  	actual, err := ssh.ListKeys(testSSHUser, ssh.FullKeys)
    87  	c.Assert(err, gc.IsNil)
    88  	c.Assert(actual, gc.DeepEquals, append([]string{firstKey}, moreKeys...))
    89  }
    90  
    91  func (s *AuthorisedKeysKeysSuite) TestAddDuplicateKey(c *gc.C) {
    92  	key := sshtesting.ValidKeyOne.Key + " user@host"
    93  	err := ssh.AddKeys(testSSHUser, key)
    94  	c.Assert(err, gc.IsNil)
    95  	moreKeys := []string{
    96  		sshtesting.ValidKeyOne.Key + " user@host",
    97  		sshtesting.ValidKeyTwo.Key + " yetanotheruser@host",
    98  	}
    99  	err = ssh.AddKeys(testSSHUser, moreKeys...)
   100  	c.Assert(err, gc.ErrorMatches, "cannot add duplicate ssh key: "+sshtesting.ValidKeyOne.Fingerprint)
   101  }
   102  
   103  func (s *AuthorisedKeysKeysSuite) TestAddDuplicateComment(c *gc.C) {
   104  	key := sshtesting.ValidKeyOne.Key + " user@host"
   105  	err := ssh.AddKeys(testSSHUser, key)
   106  	c.Assert(err, gc.IsNil)
   107  	moreKeys := []string{
   108  		sshtesting.ValidKeyTwo.Key + " user@host",
   109  		sshtesting.ValidKeyThree.Key + " yetanotheruser@host",
   110  	}
   111  	err = ssh.AddKeys(testSSHUser, moreKeys...)
   112  	c.Assert(err, gc.ErrorMatches, "cannot add ssh key with duplicate comment: user@host")
   113  }
   114  
   115  func (s *AuthorisedKeysKeysSuite) TestAddKeyWithoutComment(c *gc.C) {
   116  	keys := []string{
   117  		sshtesting.ValidKeyOne.Key + " user@host",
   118  		sshtesting.ValidKeyTwo.Key,
   119  	}
   120  	err := ssh.AddKeys(testSSHUser, keys...)
   121  	c.Assert(err, gc.ErrorMatches, "cannot add ssh key without comment")
   122  }
   123  
   124  func (s *AuthorisedKeysKeysSuite) TestAddKeepsUnrecognised(c *gc.C) {
   125  	writeAuthKeysFile(c, []string{sshtesting.ValidKeyOne.Key, "invalid-key"})
   126  	anotherKey := sshtesting.ValidKeyTwo.Key + " anotheruser@host"
   127  	err := ssh.AddKeys(testSSHUser, anotherKey)
   128  	c.Assert(err, gc.IsNil)
   129  	actual, err := ssh.ReadAuthorisedKeys(testSSHUser)
   130  	c.Assert(err, gc.IsNil)
   131  	c.Assert(actual, gc.DeepEquals, []string{sshtesting.ValidKeyOne.Key, "invalid-key", anotherKey})
   132  }
   133  
   134  func (s *AuthorisedKeysKeysSuite) TestDeleteKeys(c *gc.C) {
   135  	firstKey := sshtesting.ValidKeyOne.Key + " user@host"
   136  	anotherKey := sshtesting.ValidKeyTwo.Key
   137  	thirdKey := sshtesting.ValidKeyThree.Key + " anotheruser@host"
   138  	writeAuthKeysFile(c, []string{firstKey, anotherKey, thirdKey})
   139  	err := ssh.DeleteKeys(testSSHUser, "user@host", sshtesting.ValidKeyTwo.Fingerprint)
   140  	c.Assert(err, gc.IsNil)
   141  	actual, err := ssh.ListKeys(testSSHUser, ssh.FullKeys)
   142  	c.Assert(err, gc.IsNil)
   143  	c.Assert(actual, gc.DeepEquals, []string{thirdKey})
   144  }
   145  
   146  func (s *AuthorisedKeysKeysSuite) TestDeleteKeysKeepsUnrecognised(c *gc.C) {
   147  	firstKey := sshtesting.ValidKeyOne.Key + " user@host"
   148  	writeAuthKeysFile(c, []string{firstKey, sshtesting.ValidKeyTwo.Key, "invalid-key"})
   149  	err := ssh.DeleteKeys(testSSHUser, "user@host")
   150  	c.Assert(err, gc.IsNil)
   151  	actual, err := ssh.ReadAuthorisedKeys(testSSHUser)
   152  	c.Assert(err, gc.IsNil)
   153  	c.Assert(actual, gc.DeepEquals, []string{"invalid-key", sshtesting.ValidKeyTwo.Key})
   154  }
   155  
   156  func (s *AuthorisedKeysKeysSuite) TestDeleteNonExistentComment(c *gc.C) {
   157  	firstKey := sshtesting.ValidKeyOne.Key + " user@host"
   158  	writeAuthKeysFile(c, []string{firstKey})
   159  	err := ssh.DeleteKeys(testSSHUser, "someone@host")
   160  	c.Assert(err, gc.ErrorMatches, "cannot delete non existent key: someone@host")
   161  }
   162  
   163  func (s *AuthorisedKeysKeysSuite) TestDeleteNonExistentFingerprint(c *gc.C) {
   164  	firstKey := sshtesting.ValidKeyOne.Key + " user@host"
   165  	writeAuthKeysFile(c, []string{firstKey})
   166  	err := ssh.DeleteKeys(testSSHUser, sshtesting.ValidKeyTwo.Fingerprint)
   167  	c.Assert(err, gc.ErrorMatches, "cannot delete non existent key: "+sshtesting.ValidKeyTwo.Fingerprint)
   168  }
   169  
   170  func (s *AuthorisedKeysKeysSuite) TestDeleteLastKeyForbidden(c *gc.C) {
   171  	keys := []string{
   172  		sshtesting.ValidKeyOne.Key + " user@host",
   173  		sshtesting.ValidKeyTwo.Key + " yetanotheruser@host",
   174  	}
   175  	writeAuthKeysFile(c, keys)
   176  	err := ssh.DeleteKeys(testSSHUser, "user@host", sshtesting.ValidKeyTwo.Fingerprint)
   177  	c.Assert(err, gc.ErrorMatches, "cannot delete all keys")
   178  }
   179  
   180  func (s *AuthorisedKeysKeysSuite) TestReplaceKeys(c *gc.C) {
   181  	firstKey := sshtesting.ValidKeyOne.Key + " user@host"
   182  	anotherKey := sshtesting.ValidKeyTwo.Key
   183  	writeAuthKeysFile(c, []string{firstKey, anotherKey})
   184  
   185  	replaceKey := sshtesting.ValidKeyThree.Key + " anotheruser@host"
   186  	err := ssh.ReplaceKeys(testSSHUser, replaceKey)
   187  	c.Assert(err, gc.IsNil)
   188  	actual, err := ssh.ListKeys(testSSHUser, ssh.FullKeys)
   189  	c.Assert(err, gc.IsNil)
   190  	c.Assert(actual, gc.DeepEquals, []string{replaceKey})
   191  }
   192  
   193  func (s *AuthorisedKeysKeysSuite) TestReplaceKeepsUnrecognised(c *gc.C) {
   194  	writeAuthKeysFile(c, []string{sshtesting.ValidKeyOne.Key, "invalid-key"})
   195  	anotherKey := sshtesting.ValidKeyTwo.Key + " anotheruser@host"
   196  	err := ssh.ReplaceKeys(testSSHUser, anotherKey)
   197  	c.Assert(err, gc.IsNil)
   198  	actual, err := ssh.ReadAuthorisedKeys(testSSHUser)
   199  	c.Assert(err, gc.IsNil)
   200  	c.Assert(actual, gc.DeepEquals, []string{"invalid-key", anotherKey})
   201  }
   202  
   203  func (s *AuthorisedKeysKeysSuite) TestEnsureJujuComment(c *gc.C) {
   204  	sshKey := sshtesting.ValidKeyOne.Key
   205  	for _, test := range []struct {
   206  		key      string
   207  		expected string
   208  	}{
   209  		{"invalid-key", "invalid-key"},
   210  		{sshKey, sshKey + " Juju:sshkey"},
   211  		{sshKey + " user@host", sshKey + " Juju:user@host"},
   212  		{sshKey + " Juju:user@host", sshKey + " Juju:user@host"},
   213  		{sshKey + " " + sshKey[3:5], sshKey + " Juju:" + sshKey[3:5]},
   214  	} {
   215  		actual := ssh.EnsureJujuComment(test.key)
   216  		c.Assert(actual, gc.Equals, test.expected)
   217  	}
   218  }
   219  
   220  func (s *AuthorisedKeysKeysSuite) TestSplitAuthorisedKeys(c *gc.C) {
   221  	sshKey := sshtesting.ValidKeyOne.Key
   222  	for _, test := range []struct {
   223  		keyData  string
   224  		expected []string
   225  	}{
   226  		{"", nil},
   227  		{sshKey, []string{sshKey}},
   228  		{sshKey + "\n", []string{sshKey}},
   229  		{sshKey + "\n\n", []string{sshKey}},
   230  		{sshKey + "\n#comment\n", []string{sshKey}},
   231  		{sshKey + "\n #comment\n", []string{sshKey}},
   232  		{sshKey + "\ninvalid\n", []string{sshKey, "invalid"}},
   233  	} {
   234  		actual := ssh.SplitAuthorisedKeys(test.keyData)
   235  		c.Assert(actual, gc.DeepEquals, test.expected)
   236  	}
   237  }
   238  
   239  func b64decode(c *gc.C, s string) []byte {
   240  	b, err := base64.StdEncoding.DecodeString(s)
   241  	c.Assert(err, gc.IsNil)
   242  	return b
   243  }
   244  
   245  func (s *AuthorisedKeysKeysSuite) TestParseAuthorisedKey(c *gc.C) {
   246  	for i, test := range []struct {
   247  		line    string
   248  		key     []byte
   249  		comment string
   250  		err     string
   251  	}{{
   252  		line: sshtesting.ValidKeyOne.Key,
   253  		key:  b64decode(c, strings.Fields(sshtesting.ValidKeyOne.Key)[1]),
   254  	}, {
   255  		line:    sshtesting.ValidKeyOne.Key + " a b c",
   256  		key:     b64decode(c, strings.Fields(sshtesting.ValidKeyOne.Key)[1]),
   257  		comment: "a b c",
   258  	}, {
   259  		line: "ssh-xsa blah",
   260  		err:  "invalid authorized_key \"ssh-xsa blah\"",
   261  	}, {
   262  		// options should be skipped
   263  		line: `no-pty,principals="\"",command="\!" ` + sshtesting.ValidKeyOne.Key,
   264  		key:  b64decode(c, strings.Fields(sshtesting.ValidKeyOne.Key)[1]),
   265  	}, {
   266  		line: "ssh-rsa",
   267  		err:  "invalid authorized_key \"ssh-rsa\"",
   268  	}} {
   269  		c.Logf("test %d: %s", i, test.line)
   270  		ak, err := ssh.ParseAuthorisedKey(test.line)
   271  		if test.err != "" {
   272  			c.Assert(err, gc.ErrorMatches, test.err)
   273  		} else {
   274  			c.Assert(err, gc.IsNil)
   275  			c.Assert(ak, gc.Not(gc.IsNil))
   276  			c.Assert(ak.Key, gc.DeepEquals, test.key)
   277  			c.Assert(ak.Comment, gc.Equals, test.comment)
   278  		}
   279  	}
   280  }