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 }