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