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 }