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