github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/client/keymanager/keymanager_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package keymanager_test 5 6 import ( 7 "fmt" 8 "strings" 9 10 "github.com/juju/names/v5" 11 "github.com/juju/testing" 12 jc "github.com/juju/testing/checkers" 13 "github.com/juju/utils/v3/ssh" 14 sshtesting "github.com/juju/utils/v3/ssh/testing" 15 "go.uber.org/mock/gomock" 16 gc "gopkg.in/check.v1" 17 18 "github.com/juju/juju/apiserver/errors" 19 "github.com/juju/juju/apiserver/facades/client/keymanager" 20 "github.com/juju/juju/apiserver/facades/client/keymanager/mocks" 21 keymanagertesting "github.com/juju/juju/apiserver/facades/client/keymanager/testing" 22 apiservertesting "github.com/juju/juju/apiserver/testing" 23 "github.com/juju/juju/environs/config" 24 "github.com/juju/juju/rpc/params" 25 coretesting "github.com/juju/juju/testing" 26 ) 27 28 type keyManagerSuite struct { 29 testing.CleanupSuite 30 31 model *mocks.MockModel 32 blockChecker *mocks.MockBlockChecker 33 apiUser names.UserTag 34 api *keymanager.KeyManagerAPI 35 36 authorizer apiservertesting.FakeAuthorizer 37 } 38 39 var _ = gc.Suite(&keyManagerSuite{}) 40 41 func (s *keyManagerSuite) SetUpTest(c *gc.C) { 42 s.PatchValue(&keymanager.RunSSHImportId, keymanagertesting.FakeImport) 43 s.apiUser = names.NewUserTag("admin") 44 } 45 46 func (s *keyManagerSuite) setup(c *gc.C) *gomock.Controller { 47 ctrl := gomock.NewController(c) 48 s.model = mocks.NewMockModel(ctrl) 49 s.model.EXPECT().ModelTag().Return(coretesting.ModelTag).AnyTimes() 50 s.blockChecker = mocks.NewMockBlockChecker(ctrl) 51 s.authorizer = apiservertesting.FakeAuthorizer{ 52 Tag: s.apiUser, 53 } 54 55 s.api = keymanager.NewKeyManagerAPI(s.model, s.authorizer, s.blockChecker, coretesting.ControllerTag) 56 57 return ctrl 58 } 59 60 func (s *keyManagerSuite) setAuthorizedKeys(c *gc.C, keys ...string) { 61 joined := strings.Join(keys, "\n") 62 attrs := coretesting.FakeConfig().Merge(coretesting.Attrs{ 63 "authorized-keys": joined, 64 }) 65 s.model.EXPECT().ModelConfig().Return(config.New(config.UseDefaults, attrs)).AnyTimes() 66 } 67 68 func (s *keyManagerSuite) TestListKeys(c *gc.C) { 69 defer s.setup(c).Finish() 70 71 key1 := sshtesting.ValidKeyOne.Key + " user@host" 72 key2 := sshtesting.ValidKeyTwo.Key 73 s.setAuthorizedKeys(c, key1, key2, "bad key") 74 75 args := params.ListSSHKeys{ 76 Entities: params.Entities{Entities: []params.Entity{ 77 {Tag: names.NewUserTag("admin").String()}, 78 {Tag: "invalid"}, 79 }}, 80 Mode: ssh.FullKeys, 81 } 82 results, err := s.api.ListKeys(args) 83 c.Assert(err, jc.ErrorIsNil) 84 c.Assert(results, gc.DeepEquals, params.StringsResults{ 85 Results: []params.StringsResult{ 86 {Result: []string{key1, key2, "Invalid key: bad key"}}, 87 {Result: []string{key1, key2, "Invalid key: bad key"}}, 88 }, 89 }) 90 } 91 92 func (s *keyManagerSuite) TestListKeysHidesJujuInternal(c *gc.C) { 93 defer s.setup(c).Finish() 94 95 key1 := sshtesting.ValidKeyOne.Key + " juju-client-key" 96 key2 := sshtesting.ValidKeyTwo.Key + " " + config.JujuSystemKey 97 s.setAuthorizedKeys(c, key1, key2) 98 99 args := params.ListSSHKeys{ 100 Entities: params.Entities{Entities: []params.Entity{ 101 {Tag: names.NewUserTag("admin").String()}, 102 }}, 103 Mode: ssh.FullKeys, 104 } 105 results, err := s.api.ListKeys(args) 106 c.Assert(err, jc.ErrorIsNil) 107 c.Assert(results, gc.DeepEquals, params.StringsResults{ 108 Results: []params.StringsResult{ 109 {Result: nil}, 110 }, 111 }) 112 } 113 114 func (s *keyManagerSuite) TestListJujuSystemKey(c *gc.C) { 115 defer s.setup(c).Finish() 116 117 key1 := sshtesting.ValidKeyOne.Key 118 s.setAuthorizedKeys(c, key1) 119 120 args := params.ListSSHKeys{ 121 Entities: params.Entities{Entities: []params.Entity{ 122 {Tag: config.JujuSystemKey}, 123 }}, 124 Mode: ssh.FullKeys, 125 } 126 results, err := s.api.ListKeys(args) 127 c.Assert(err, jc.ErrorIsNil) 128 c.Assert(results.Results, gc.HasLen, 1) 129 c.Assert(results.Results[0].Error, gc.ErrorMatches, "permission denied") 130 } 131 132 func (s *keyManagerSuite) assertAddKeys(c *gc.C) { 133 key1 := sshtesting.ValidKeyOne.Key + " user@host" 134 key2 := sshtesting.ValidKeyTwo.Key 135 s.setAuthorizedKeys(c, key1, key2, "bad key") 136 137 newKey := sshtesting.ValidKeyThree.Key + " newuser@host" 138 newLineKey := sshtesting.ValidKeyFour.Key + " line1\nline2" 139 140 newAttrs := map[string]interface{}{ 141 config.AuthorizedKeysKey: strings.Join([]string{key1, key2, "bad key", newKey}, "\n"), 142 } 143 s.model.EXPECT().UpdateModelConfig(newAttrs, nil) 144 145 args := params.ModifyUserSSHKeys{ 146 User: names.NewUserTag("admin").Name(), 147 Keys: []string{key2, newKey, newKey, "invalid-key", newLineKey}, 148 } 149 results, err := s.api.AddKeys(args) 150 c.Assert(err, jc.ErrorIsNil) 151 152 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 153 Results: []params.ErrorResult{ 154 {Error: apiservertesting.ServerError(fmt.Sprintf("duplicate ssh key: %s", key2))}, 155 {Error: nil}, 156 {Error: apiservertesting.ServerError(fmt.Sprintf("duplicate ssh key: %s", newKey))}, 157 {Error: apiservertesting.ServerError("invalid ssh key: invalid-key")}, 158 {Error: apiservertesting.ServerError(fmt.Sprintf("invalid ssh key: %s", newLineKey))}, 159 }, 160 }) 161 } 162 163 func (s *keyManagerSuite) TestAddKeys(c *gc.C) { 164 defer s.setup(c).Finish() 165 s.blockChecker.EXPECT().ChangeAllowed().Return(nil) 166 s.assertAddKeys(c) 167 } 168 169 func (s *keyManagerSuite) TestAddKeysSuperUser(c *gc.C) { 170 s.apiUser = names.NewUserTag("superuser-fred") 171 defer s.setup(c).Finish() 172 s.blockChecker.EXPECT().ChangeAllowed().Return(nil) 173 s.assertAddKeys(c) 174 } 175 176 func (s *keyManagerSuite) TestAddKeysModelAdmin(c *gc.C) { 177 s.apiUser = names.NewUserTag("admin" + coretesting.ModelTag.String()) 178 defer s.setup(c).Finish() 179 s.blockChecker.EXPECT().ChangeAllowed().Return(nil) 180 s.assertAddKeys(c) 181 } 182 183 func (s *keyManagerSuite) TestAddKeysNonAuthorised(c *gc.C) { 184 s.apiUser = names.NewUserTag("fred") 185 defer s.setup(c).Finish() 186 187 _, err := s.api.AddKeys(params.ModifyUserSSHKeys{}) 188 c.Assert(err, gc.ErrorMatches, "permission denied") 189 c.Assert(params.ErrCode(err), gc.Equals, params.CodeUnauthorized) 190 } 191 192 func (s *keyManagerSuite) TestBlockAddKeys(c *gc.C) { 193 defer s.setup(c).Finish() 194 s.blockChecker.EXPECT().ChangeAllowed().Return(errors.OperationBlockedError("TestAddKeys")) 195 196 _, err := s.api.AddKeys(params.ModifyUserSSHKeys{}) 197 198 c.Assert(params.IsCodeOperationBlocked(err), jc.IsTrue) 199 } 200 201 func (s *keyManagerSuite) TestAddJujuSystemKey(c *gc.C) { 202 defer s.setup(c).Finish() 203 s.blockChecker.EXPECT().ChangeAllowed().Return(nil) 204 s.setAuthorizedKeys(c, sshtesting.ValidKeyOne.Key) 205 206 newAttrs := map[string]interface{}{ 207 config.AuthorizedKeysKey: sshtesting.ValidKeyOne.Key, 208 } 209 s.model.EXPECT().UpdateModelConfig(newAttrs, nil) 210 211 newKey := sshtesting.ValidKeyThree.Key + " " + config.JujuSystemKey 212 args := params.ModifyUserSSHKeys{ 213 User: names.NewUserTag("admin").Name(), 214 Keys: []string{newKey}, 215 } 216 results, err := s.api.AddKeys(args) 217 c.Assert(err, jc.ErrorIsNil) 218 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 219 Results: []params.ErrorResult{ 220 {Error: apiservertesting.ServerError("may not add key with comment juju-system-key: " + newKey)}, 221 }, 222 }) 223 } 224 225 func (s *keyManagerSuite) assertDeleteKeys(c *gc.C) { 226 key1 := sshtesting.ValidKeyOne.Key + " user@host" 227 key2 := sshtesting.ValidKeyTwo.Key 228 s.setAuthorizedKeys(c, key1, key2, "bad key 1", "bad key 2") 229 230 newAttrs := map[string]interface{}{ 231 config.AuthorizedKeysKey: strings.Join([]string{key1, "bad key 1"}, "\n"), 232 } 233 s.model.EXPECT().UpdateModelConfig(newAttrs, nil) 234 235 args := params.ModifyUserSSHKeys{ 236 User: names.NewUserTag("admin").String(), 237 Keys: []string{sshtesting.ValidKeyTwo.Fingerprint, sshtesting.ValidKeyThree.Fingerprint, "invalid-key", "bad key 2"}, 238 } 239 results, err := s.api.DeleteKeys(args) 240 c.Assert(err, jc.ErrorIsNil) 241 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 242 Results: []params.ErrorResult{ 243 {Error: nil}, 244 {Error: apiservertesting.ServerError("key not found: " + sshtesting.ValidKeyThree.Fingerprint)}, 245 {Error: apiservertesting.ServerError("key not found: invalid-key")}, 246 {Error: nil}, 247 }, 248 }) 249 } 250 251 func (s *keyManagerSuite) TestDeleteKeys(c *gc.C) { 252 defer s.setup(c).Finish() 253 s.blockChecker.EXPECT().RemoveAllowed().Return(nil) 254 s.assertDeleteKeys(c) 255 } 256 257 func (s *keyManagerSuite) TestDeleteKeysSuperUser(c *gc.C) { 258 s.apiUser = names.NewUserTag("superuser-fred") 259 defer s.setup(c).Finish() 260 s.blockChecker.EXPECT().RemoveAllowed().Return(nil) 261 s.assertDeleteKeys(c) 262 } 263 264 func (s *keyManagerSuite) TestDeleteKeysModelAdmin(c *gc.C) { 265 s.apiUser = names.NewUserTag("admin" + coretesting.ModelTag.String()) 266 defer s.setup(c).Finish() 267 s.blockChecker.EXPECT().RemoveAllowed().Return(nil) 268 s.assertDeleteKeys(c) 269 } 270 271 func (s *keyManagerSuite) TestDeleteKeysNonAuthorised(c *gc.C) { 272 s.apiUser = names.NewUserTag("fred") 273 defer s.setup(c).Finish() 274 275 _, err := s.api.DeleteKeys(params.ModifyUserSSHKeys{}) 276 c.Assert(err, gc.ErrorMatches, "permission denied") 277 c.Assert(params.ErrCode(err), gc.Equals, params.CodeUnauthorized) 278 } 279 280 func (s *keyManagerSuite) TestBlockDeleteKeys(c *gc.C) { 281 defer s.setup(c).Finish() 282 s.blockChecker.EXPECT().RemoveAllowed().Return(errors.OperationBlockedError("TestDeleteKeys")) 283 284 _, err := s.api.DeleteKeys(params.ModifyUserSSHKeys{}) 285 286 c.Assert(params.IsCodeOperationBlocked(err), jc.IsTrue) 287 } 288 289 func (s *keyManagerSuite) TestDeleteJujuSystemKey(c *gc.C) { 290 defer s.setup(c).Finish() 291 s.blockChecker.EXPECT().RemoveAllowed().Return(nil) 292 293 key1 := sshtesting.ValidKeyOne.Key + " juju-client-key" 294 key2 := sshtesting.ValidKeyTwo.Key + " " + config.JujuSystemKey 295 key3 := sshtesting.ValidKeyThree.Key + " a user key" 296 s.setAuthorizedKeys(c, key1, key2, key3) 297 298 newAttrs := map[string]interface{}{ 299 config.AuthorizedKeysKey: strings.Join([]string{key1, key2, key3}, "\n"), 300 } 301 s.model.EXPECT().UpdateModelConfig(newAttrs, nil) 302 303 args := params.ModifyUserSSHKeys{ 304 User: names.NewUserTag("admin").Name(), 305 Keys: []string{"juju-client-key", config.JujuSystemKey}, 306 } 307 results, err := s.api.DeleteKeys(args) 308 c.Assert(err, jc.ErrorIsNil) 309 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 310 Results: []params.ErrorResult{ 311 {Error: apiservertesting.ServerError("may not delete internal key: juju-client-key")}, 312 {Error: apiservertesting.ServerError("may not delete internal key: " + config.JujuSystemKey)}, 313 }, 314 }) 315 } 316 317 // This should be impossible to do anyway since it's impossible to request 318 // to remove the client and system key 319 func (s *keyManagerSuite) TestCannotDeleteAllKeys(c *gc.C) { 320 defer s.setup(c).Finish() 321 s.blockChecker.EXPECT().RemoveAllowed().Return(nil) 322 323 key1 := sshtesting.ValidKeyOne.Key + " user@host" 324 key2 := sshtesting.ValidKeyTwo.Key 325 s.setAuthorizedKeys(c, key1, key2) 326 327 args := params.ModifyUserSSHKeys{ 328 User: names.NewUserTag("admin").String(), 329 Keys: []string{sshtesting.ValidKeyTwo.Fingerprint, "user@host"}, 330 } 331 _, err := s.api.DeleteKeys(args) 332 c.Assert(err, gc.ErrorMatches, "cannot delete all keys") 333 } 334 335 func (s *keyManagerSuite) assertImportKeys(c *gc.C) { 336 key1 := sshtesting.ValidKeyOne.Key + " user@host" 337 key2 := sshtesting.ValidKeyTwo.Key 338 key3 := sshtesting.ValidKeyThree.Key 339 key4 := sshtesting.ValidKeyFour.Key 340 keymv := strings.Split(sshtesting.ValidKeyMulti, "\n") 341 keymp := strings.Split(sshtesting.PartValidKeyMulti, "\n") 342 keymi := strings.Split(sshtesting.MultiInvalid, "\n") 343 s.setAuthorizedKeys(c, key1, key2, "bad key") 344 345 newAttrs := map[string]interface{}{ 346 config.AuthorizedKeysKey: strings.Join([]string{ 347 key1, key2, "bad key", key3, keymv[0], keymv[1], keymp[0], key4, 348 }, "\n"), 349 } 350 s.model.EXPECT().UpdateModelConfig(newAttrs, nil) 351 352 args := params.ModifyUserSSHKeys{ 353 User: names.NewUserTag("admin").String(), 354 Keys: []string{ 355 "lp:existing", 356 "lp:validuser", 357 "invalid-key", 358 "lp:multi", 359 "lp:multiempty", 360 "lp:multipartial", 361 "lp:multiinvalid", 362 "lp:multionedup", 363 }, 364 } 365 results, err := s.api.ImportKeys(args) 366 367 c.Assert(err, jc.ErrorIsNil) 368 c.Assert(results.Results, gc.HasLen, 8) 369 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 370 Results: []params.ErrorResult{ 371 {Error: apiservertesting.ServerError(fmt.Sprintf("duplicate ssh key: %s", key2))}, 372 {Error: nil}, 373 {Error: apiservertesting.ServerError("invalid ssh key id: invalid-key")}, 374 {Error: nil}, 375 {Error: apiservertesting.ServerError("invalid ssh key id: lp:multiempty")}, 376 {Error: apiservertesting.ServerError(fmt.Sprintf( 377 `invalid ssh key for lp:multipartial: `+ 378 `generating key fingerprint: `+ 379 `invalid authorized_key "%s"`, keymp[1]))}, 380 {Error: apiservertesting.ServerError(fmt.Sprintf( 381 `invalid ssh key for lp:multiinvalid: `+ 382 `generating key fingerprint: `+ 383 `invalid authorized_key "%s"`+"\n"+ 384 `invalid ssh key for lp:multiinvalid: `+ 385 `generating key fingerprint: `+ 386 `invalid authorized_key "%s"`, keymi[0], keymi[1]))}, 387 {Error: apiservertesting.ServerError(fmt.Sprintf("duplicate ssh key: %s", key2))}, 388 }, 389 }) 390 } 391 392 func (s *keyManagerSuite) TestImportKeys(c *gc.C) { 393 defer s.setup(c).Finish() 394 s.blockChecker.EXPECT().ChangeAllowed().Return(nil) 395 s.assertImportKeys(c) 396 } 397 398 func (s *keyManagerSuite) TestImportKeysSuperUser(c *gc.C) { 399 s.apiUser = names.NewUserTag("superuser-fred") 400 defer s.setup(c).Finish() 401 s.blockChecker.EXPECT().ChangeAllowed().Return(nil) 402 s.assertImportKeys(c) 403 } 404 405 func (s *keyManagerSuite) TestImportKeysModelAdmin(c *gc.C) { 406 s.apiUser = names.NewUserTag("admin" + coretesting.ModelTag.String()) 407 defer s.setup(c).Finish() 408 s.blockChecker.EXPECT().ChangeAllowed().Return(nil) 409 s.assertImportKeys(c) 410 } 411 412 func (s *keyManagerSuite) TestImportKeysNonAuthorised(c *gc.C) { 413 s.apiUser = names.NewUserTag("fred") 414 defer s.setup(c).Finish() 415 416 _, err := s.api.ImportKeys(params.ModifyUserSSHKeys{}) 417 c.Assert(err, gc.ErrorMatches, "permission denied") 418 c.Assert(params.ErrCode(err), gc.Equals, params.CodeUnauthorized) 419 } 420 421 func (s *keyManagerSuite) TestImportJujuSystemKey(c *gc.C) { 422 defer s.setup(c).Finish() 423 s.blockChecker.EXPECT().ChangeAllowed().Return(nil) 424 425 key1 := sshtesting.ValidKeyOne.Key 426 s.setAuthorizedKeys(c, key1) 427 newAttrs := map[string]interface{}{ 428 config.AuthorizedKeysKey: key1, 429 } 430 s.model.EXPECT().UpdateModelConfig(newAttrs, nil) 431 432 args := params.ModifyUserSSHKeys{ 433 User: names.NewUserTag("admin").String(), 434 Keys: []string{"lp:systemkey"}, 435 } 436 results, err := s.api.ImportKeys(args) 437 c.Assert(err, gc.IsNil) 438 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 439 Results: []params.ErrorResult{ 440 {Error: apiservertesting.ServerError("may not add key with comment juju-system-key: " + keymanagertesting.SystemKey)}, 441 }, 442 }) 443 } 444 445 func (s *keyManagerSuite) TestBlockImportKeys(c *gc.C) { 446 defer s.setup(c).Finish() 447 s.blockChecker.EXPECT().ChangeAllowed().Return(errors.OperationBlockedError("TestImportKeys")) 448 449 _, err := s.api.ImportKeys(params.ModifyUserSSHKeys{}) 450 451 c.Assert(params.IsCodeOperationBlocked(err), jc.IsTrue) 452 }