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