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 }