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 }