github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/lxd/credentials_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lxd_test 5 6 import ( 7 "encoding/base64" 8 "net" 9 "os" 10 "path/filepath" 11 12 "github.com/golang/mock/gomock" 13 "github.com/juju/cmd/cmdtesting" 14 "github.com/juju/errors" 15 jc "github.com/juju/testing/checkers" 16 "github.com/juju/utils" 17 "github.com/lxc/lxd/shared/api" 18 gc "gopkg.in/check.v1" 19 20 "github.com/juju/juju/cloud" 21 containerLXD "github.com/juju/juju/container/lxd" 22 "github.com/juju/juju/environs" 23 envtesting "github.com/juju/juju/environs/testing" 24 "github.com/juju/juju/juju/osenv" 25 "github.com/juju/juju/provider/lxd" 26 coretesting "github.com/juju/juju/testing" 27 ) 28 29 //go:generate mockgen -package lxd -destination net_mock_test.go net Addr 30 31 type credentialsSuite struct { 32 lxd.BaseSuite 33 } 34 35 var _ = gc.Suite(&credentialsSuite{}) 36 37 func (s *credentialsSuite) TestCredentialSchemas(c *gc.C) { 38 provider := lxd.NewProvider() 39 envtesting.AssertProviderAuthTypes(c, provider, "certificate", "interactive") 40 } 41 42 type credentialsSuiteDeps struct { 43 provider environs.EnvironProvider 44 creds environs.ProviderCredentials 45 server *lxd.MockServer 46 serverFactory *lxd.MockServerFactory 47 certReadWriter *lxd.MockCertificateReadWriter 48 certGenerator *lxd.MockCertificateGenerator 49 netLookup *lxd.MockNetLookup 50 configReader *lxd.MockLXCConfigReader 51 } 52 53 func (s *credentialsSuite) createProvider(ctrl *gomock.Controller) credentialsSuiteDeps { 54 server := lxd.NewMockServer(ctrl) 55 factory := lxd.NewMockServerFactory(ctrl) 56 factory.EXPECT().LocalServer().Return(server, nil).AnyTimes() 57 58 certReadWriter := lxd.NewMockCertificateReadWriter(ctrl) 59 certGenerator := lxd.NewMockCertificateGenerator(ctrl) 60 lookup := lxd.NewMockNetLookup(ctrl) 61 configReader := lxd.NewMockLXCConfigReader(ctrl) 62 creds := lxd.NewProviderCredentials( 63 certReadWriter, 64 certGenerator, 65 lookup, 66 factory, 67 configReader, 68 ) 69 credsRegister := creds.(environs.ProviderCredentialsRegister) 70 71 provider := lxd.NewProviderWithMocks(creds, credsRegister, factory, configReader) 72 return credentialsSuiteDeps{ 73 provider: provider, 74 creds: creds, 75 server: server, 76 serverFactory: factory, 77 certReadWriter: certReadWriter, 78 certGenerator: certGenerator, 79 netLookup: lookup, 80 configReader: configReader, 81 } 82 } 83 84 func (s *credentialsSuite) TestDetectCredentialsFailsWithJujuCert(c *gc.C) { 85 ctrl := gomock.NewController(c) 86 defer ctrl.Finish() 87 88 deps := s.createProvider(ctrl) 89 90 path := osenv.JujuXDGDataHomePath("lxd") 91 deps.certReadWriter.EXPECT().Read(path).Return(nil, nil, errors.NotValidf("certs")) 92 93 _, err := deps.provider.DetectCredentials() 94 c.Assert(errors.Cause(err), gc.ErrorMatches, "certs not valid") 95 } 96 97 func (s *credentialsSuite) TestDetectCredentialsFailsWithJujuAndLXCCert(c *gc.C) { 98 ctrl := gomock.NewController(c) 99 defer ctrl.Finish() 100 101 deps := s.createProvider(ctrl) 102 103 path := osenv.JujuXDGDataHomePath("lxd") 104 deps.certReadWriter.EXPECT().Read(path).Return(nil, nil, os.ErrNotExist) 105 106 path = filepath.Join(utils.Home(), ".config", "lxc") 107 deps.certReadWriter.EXPECT().Read(path).Return(nil, nil, errors.NotValidf("certs")) 108 109 _, err := deps.provider.DetectCredentials() 110 c.Assert(errors.Cause(err), gc.ErrorMatches, "certs not valid") 111 } 112 113 func (s *credentialsSuite) TestDetectCredentialsGeneratesCertFailsToWriteOnError(c *gc.C) { 114 ctrl := gomock.NewController(c) 115 defer ctrl.Finish() 116 117 deps := s.createProvider(ctrl) 118 119 path := osenv.JujuXDGDataHomePath("lxd") 120 deps.certReadWriter.EXPECT().Read(path).Return(nil, nil, os.ErrNotExist) 121 122 path = filepath.Join(utils.Home(), ".config", "lxc") 123 deps.certReadWriter.EXPECT().Read(path).Return(nil, nil, os.ErrNotExist) 124 125 deps.certGenerator.EXPECT().Generate(true).Return(nil, nil, errors.Errorf("bad")) 126 127 _, err := deps.provider.DetectCredentials() 128 c.Assert(errors.Cause(err), gc.ErrorMatches, "bad") 129 } 130 131 func (s *credentialsSuite) TestDetectCredentialsGeneratesCertFailsToGetCertificateOnError(c *gc.C) { 132 ctrl := gomock.NewController(c) 133 defer ctrl.Finish() 134 135 deps := s.createProvider(ctrl) 136 137 path := osenv.JujuXDGDataHomePath("lxd") 138 deps.certReadWriter.EXPECT().Read(path).Return(nil, nil, os.ErrNotExist) 139 deps.certReadWriter.EXPECT().Write(path, []byte(coretesting.CACert), []byte(coretesting.CAKey)).Return(errors.Errorf("bad")) 140 141 path = filepath.Join(utils.Home(), ".config", "lxc") 142 deps.certReadWriter.EXPECT().Read(path).Return(nil, nil, os.ErrNotExist) 143 144 deps.certGenerator.EXPECT().Generate(true).Return([]byte(coretesting.CACert), []byte(coretesting.CAKey), nil) 145 146 _, err := deps.provider.DetectCredentials() 147 c.Assert(errors.Cause(err), gc.ErrorMatches, "bad") 148 } 149 150 func (s *credentialsSuite) setupLocalhost(deps credentialsSuiteDeps, c *gc.C) { 151 path := osenv.JujuXDGDataHomePath("lxd") 152 deps.certReadWriter.EXPECT().Read(path).Return(nil, nil, os.ErrNotExist) 153 154 path = filepath.Join(utils.Home(), ".config", "lxc") 155 deps.certReadWriter.EXPECT().Read(path).Return([]byte(coretesting.CACert), []byte(coretesting.CAKey), nil) 156 } 157 158 func (s *credentialsSuite) TestRemoteDetectCredentials(c *gc.C) { 159 ctrl := gomock.NewController(c) 160 defer ctrl.Finish() 161 162 deps := s.createProvider(ctrl) 163 s.setupLocalhost(deps, c) 164 165 deps.configReader.EXPECT().ReadConfig(".config/lxc/config.yml").Return(lxd.LXCConfig{ 166 DefaultRemote: "localhost", 167 Remotes: map[string]lxd.LXCRemoteConfig{ 168 "nuc1": { 169 Addr: "https://10.0.0.1:8443", 170 AuthType: "certificate", 171 Protocol: "lxd", 172 Public: false, 173 }, 174 }, 175 }, nil) 176 deps.configReader.EXPECT().ReadCert(".config/lxc/servercerts/nuc1.crt").Return([]byte(coretesting.ServerCert), nil) 177 178 credentials, err := deps.provider.DetectCredentials() 179 c.Assert(err, jc.ErrorIsNil) 180 181 nuc1Credential := cloud.NewCredential( 182 cloud.CertificateAuthType, 183 map[string]string{ 184 "client-cert": coretesting.CACert, 185 "client-key": coretesting.CAKey, 186 "server-cert": coretesting.ServerCert, 187 }, 188 ) 189 nuc1Credential.Label = `LXD credential "nuc1"` 190 191 c.Assert(credentials, jc.DeepEquals, &cloud.CloudCredential{ 192 AuthCredentials: map[string]cloud.Credential{ 193 "nuc1": nuc1Credential, 194 }, 195 }) 196 } 197 198 func (s *credentialsSuite) TestRemoteDetectCredentialsWithConfigFailure(c *gc.C) { 199 ctrl := gomock.NewController(c) 200 defer ctrl.Finish() 201 202 deps := s.createProvider(ctrl) 203 204 s.setupLocalhost(deps, c) 205 206 deps.configReader.EXPECT().ReadConfig(".config/lxc/config.yml").Return(lxd.LXCConfig{}, errors.New("bad")) 207 208 credentials, err := deps.provider.DetectCredentials() 209 c.Assert(err, jc.ErrorIsNil) 210 c.Assert(credentials, jc.DeepEquals, &cloud.CloudCredential{ 211 AuthCredentials: map[string]cloud.Credential{}, 212 }) 213 } 214 215 func (s *credentialsSuite) TestRemoteDetectCredentialsWithCertFailure(c *gc.C) { 216 ctrl := gomock.NewController(c) 217 defer ctrl.Finish() 218 219 deps := s.createProvider(ctrl) 220 s.setupLocalhost(deps, c) 221 222 deps.configReader.EXPECT().ReadConfig(".config/lxc/config.yml").Return(lxd.LXCConfig{ 223 DefaultRemote: "localhost", 224 Remotes: map[string]lxd.LXCRemoteConfig{ 225 "nuc1": { 226 Addr: "https://10.0.0.1:8443", 227 AuthType: "certificate", 228 Protocol: "lxd", 229 Public: false, 230 }, 231 }, 232 }, nil) 233 deps.configReader.EXPECT().ReadCert(".config/lxc/servercerts/nuc1.crt").Return(nil, errors.New("bad")) 234 235 credentials, err := deps.provider.DetectCredentials() 236 c.Assert(err, jc.ErrorIsNil) 237 c.Assert(credentials, jc.DeepEquals, &cloud.CloudCredential{ 238 AuthCredentials: map[string]cloud.Credential{}, 239 }) 240 } 241 242 func (s *credentialsSuite) TestRegisterCredentials(c *gc.C) { 243 ctrl := gomock.NewController(c) 244 defer ctrl.Finish() 245 246 deps := s.createProvider(ctrl) 247 248 deps.server.EXPECT().GetCertificate(s.clientCertFingerprint(c)).Return(nil, "", nil) 249 deps.server.EXPECT().ServerCertificate().Return("server-cert") 250 251 path := osenv.JujuXDGDataHomePath("lxd") 252 deps.certReadWriter.EXPECT().Read(path).Return(nil, nil, os.ErrNotExist) 253 deps.certReadWriter.EXPECT().Write(path, []byte(coretesting.CACert), []byte(coretesting.CAKey)).Return(nil) 254 255 path = filepath.Join(utils.Home(), ".config", "lxc") 256 deps.certReadWriter.EXPECT().Read(path).Return(nil, nil, os.ErrNotExist) 257 deps.certGenerator.EXPECT().Generate(true).Return([]byte(coretesting.CACert), []byte(coretesting.CAKey), nil) 258 259 expected := cloud.NewCredential( 260 cloud.CertificateAuthType, 261 map[string]string{ 262 "client-cert": coretesting.CACert, 263 "client-key": coretesting.CAKey, 264 "server-cert": "server-cert", 265 }, 266 ) 267 expected.Label = `LXD credential "localhost"` 268 269 provider := deps.provider.(environs.ProviderCredentialsRegister) 270 credentials, err := provider.RegisterCredentials(cloud.Cloud{ 271 Name: "localhost", 272 }) 273 c.Assert(err, jc.ErrorIsNil) 274 c.Assert(credentials, jc.DeepEquals, map[string]*cloud.CloudCredential{ 275 "localhost": { 276 DefaultCredential: "localhost", 277 AuthCredentials: map[string]cloud.Credential{ 278 "localhost": expected, 279 }, 280 }, 281 }) 282 } 283 284 func (s *credentialsSuite) TestRegisterCredentialsWithAlternativeCloudName(c *gc.C) { 285 ctrl := gomock.NewController(c) 286 defer ctrl.Finish() 287 288 deps := s.createProvider(ctrl) 289 290 deps.server.EXPECT().GetCertificate(s.clientCertFingerprint(c)).Return(nil, "", nil) 291 deps.server.EXPECT().ServerCertificate().Return("server-cert") 292 293 path := osenv.JujuXDGDataHomePath("lxd") 294 deps.certReadWriter.EXPECT().Read(path).Return(nil, nil, os.ErrNotExist) 295 deps.certReadWriter.EXPECT().Write(path, []byte(coretesting.CACert), []byte(coretesting.CAKey)).Return(nil) 296 297 path = filepath.Join(utils.Home(), ".config", "lxc") 298 deps.certReadWriter.EXPECT().Read(path).Return(nil, nil, os.ErrNotExist) 299 deps.certGenerator.EXPECT().Generate(true).Return([]byte(coretesting.CACert), []byte(coretesting.CAKey), nil) 300 301 expected := cloud.NewCredential( 302 cloud.CertificateAuthType, 303 map[string]string{ 304 "client-cert": coretesting.CACert, 305 "client-key": coretesting.CAKey, 306 "server-cert": "server-cert", 307 }, 308 ) 309 expected.Label = `LXD credential "localhost"` 310 311 provider := deps.provider.(environs.ProviderCredentialsRegister) 312 credentials, err := provider.RegisterCredentials(cloud.Cloud{ 313 Name: "lxd", 314 }) 315 c.Assert(err, jc.ErrorIsNil) 316 c.Assert(credentials, jc.DeepEquals, map[string]*cloud.CloudCredential{ 317 "lxd": { 318 DefaultCredential: "lxd", 319 AuthCredentials: map[string]cloud.Credential{ 320 "lxd": expected, 321 }, 322 }, 323 }) 324 } 325 326 func (s *credentialsSuite) TestRegisterCredentialsUsesJujuCert(c *gc.C) { 327 ctrl := gomock.NewController(c) 328 defer ctrl.Finish() 329 330 deps := s.createProvider(ctrl) 331 332 deps.server.EXPECT().GetCertificate(s.clientCertFingerprint(c)).Return(nil, "", nil) 333 deps.server.EXPECT().ServerCertificate().Return("server-cert") 334 335 path := osenv.JujuXDGDataHomePath("lxd") 336 deps.certReadWriter.EXPECT().Read(path).Return([]byte(coretesting.CACert), []byte(coretesting.CAKey), nil) 337 338 provider := deps.provider.(environs.ProviderCredentialsRegister) 339 credentials, err := provider.RegisterCredentials(cloud.Cloud{ 340 Name: "localhost", 341 }) 342 c.Assert(err, jc.ErrorIsNil) 343 344 expected := cloud.NewCredential( 345 cloud.CertificateAuthType, 346 map[string]string{ 347 "client-cert": coretesting.CACert, 348 "client-key": coretesting.CAKey, 349 "server-cert": "server-cert", 350 }, 351 ) 352 expected.Label = `LXD credential "localhost"` 353 354 c.Assert(credentials, jc.DeepEquals, map[string]*cloud.CloudCredential{ 355 "localhost": { 356 DefaultCredential: "localhost", 357 AuthCredentials: map[string]cloud.Credential{ 358 "localhost": expected, 359 }, 360 }, 361 }) 362 } 363 364 func (s *credentialsSuite) TestRegisterCredentialsUsesLXCCert(c *gc.C) { 365 ctrl := gomock.NewController(c) 366 defer ctrl.Finish() 367 368 deps := s.createProvider(ctrl) 369 370 deps.server.EXPECT().GetCertificate(s.clientCertFingerprint(c)).Return(nil, "", nil) 371 deps.server.EXPECT().ServerCertificate().Return("server-cert") 372 373 path := osenv.JujuXDGDataHomePath("lxd") 374 deps.certReadWriter.EXPECT().Read(path).Return(nil, nil, os.ErrNotExist) 375 376 path = filepath.Join(utils.Home(), ".config", "lxc") 377 deps.certReadWriter.EXPECT().Read(path).Return([]byte(coretesting.CACert), []byte(coretesting.CAKey), nil) 378 379 provider := deps.provider.(environs.ProviderCredentialsRegister) 380 credentials, err := provider.RegisterCredentials(cloud.Cloud{ 381 Name: "localhost", 382 }) 383 c.Assert(err, jc.ErrorIsNil) 384 385 expected := cloud.NewCredential( 386 cloud.CertificateAuthType, 387 map[string]string{ 388 "client-cert": coretesting.CACert, 389 "client-key": coretesting.CAKey, 390 "server-cert": "server-cert", 391 }, 392 ) 393 expected.Label = `LXD credential "localhost"` 394 395 c.Assert(credentials, jc.DeepEquals, map[string]*cloud.CloudCredential{ 396 "localhost": { 397 DefaultCredential: "localhost", 398 AuthCredentials: map[string]cloud.Credential{ 399 "localhost": expected, 400 }, 401 }, 402 }) 403 } 404 405 func (s *credentialsSuite) TestFinalizeCredentialLocal(c *gc.C) { 406 ctrl := gomock.NewController(c) 407 defer ctrl.Finish() 408 409 deps := s.createProvider(ctrl) 410 411 deps.server.EXPECT().GetCertificate(s.clientCertFingerprint(c)).Return(nil, "", nil) 412 deps.server.EXPECT().ServerCertificate().Return("server-cert") 413 414 localhostIP := net.IPv4(127, 0, 0, 1) 415 ipNet := &net.IPNet{IP: localhostIP, Mask: localhostIP.DefaultMask()} 416 417 deps.netLookup.EXPECT().LookupHost("localhost").Return([]string{"127.0.0.1"}, nil) 418 deps.netLookup.EXPECT().InterfaceAddrs().Return([]net.Addr{ipNet}, nil) 419 420 out, err := deps.provider.FinalizeCredential(cmdtesting.Context(c), environs.FinalizeCredentialParams{ 421 CloudEndpoint: "localhost", 422 Credential: cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 423 "client-cert": coretesting.CACert, 424 "client-key": coretesting.CAKey, 425 }), 426 }) 427 c.Assert(err, jc.ErrorIsNil) 428 429 c.Assert(out.AuthType(), gc.Equals, cloud.CertificateAuthType) 430 c.Assert(out.Attributes(), jc.DeepEquals, map[string]string{ 431 "client-cert": coretesting.CACert, 432 "client-key": coretesting.CAKey, 433 "server-cert": "server-cert", 434 }) 435 } 436 437 func (s *credentialsSuite) TestFinalizeCredentialLocalAddCert(c *gc.C) { 438 ctrl := gomock.NewController(c) 439 defer ctrl.Finish() 440 441 deps := s.createProvider(ctrl) 442 443 deps.server.EXPECT().GetCertificate(s.clientCertFingerprint(c)).Return(nil, "", nil) 444 deps.server.EXPECT().ServerCertificate().Return("server-cert") 445 446 out, err := deps.provider.FinalizeCredential(cmdtesting.Context(c), environs.FinalizeCredentialParams{ 447 CloudEndpoint: "", 448 Credential: cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 449 "client-cert": coretesting.CACert, 450 "client-key": coretesting.CAKey, 451 }), 452 }) 453 c.Assert(err, jc.ErrorIsNil) 454 455 c.Assert(out.AuthType(), gc.Equals, cloud.CertificateAuthType) 456 c.Assert(out.Attributes(), jc.DeepEquals, map[string]string{ 457 "client-cert": coretesting.CACert, 458 "client-key": coretesting.CAKey, 459 "server-cert": "server-cert", 460 }) 461 } 462 463 func (s *credentialsSuite) TestFinalizeCredentialLocalAddCertAlreadyExists(c *gc.C) { 464 // If we get back an error from CreateClientCertificate, we'll make another 465 // call to GetCertificate. If that call succeeds, then we assume 466 // that the CreateClientCertificate failure was due to a concurrent call. 467 468 ctrl := gomock.NewController(c) 469 defer ctrl.Finish() 470 471 deps := s.createProvider(ctrl) 472 473 gomock.InOrder( 474 deps.server.EXPECT().GetCertificate(s.clientCertFingerprint(c)).Return(nil, "", errors.New("not found")), 475 deps.server.EXPECT().CreateClientCertificate(s.clientCert()).Return(errors.New("UNIQUE constraint failed: interactives.fingerprint")), 476 deps.server.EXPECT().GetCertificate(s.clientCertFingerprint(c)).Return(nil, "", nil), 477 deps.server.EXPECT().ServerCertificate().Return("server-cert"), 478 ) 479 480 out, err := deps.provider.FinalizeCredential(cmdtesting.Context(c), environs.FinalizeCredentialParams{ 481 CloudEndpoint: "", 482 Credential: cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 483 "client-cert": coretesting.CACert, 484 "client-key": coretesting.CAKey, 485 }), 486 }) 487 c.Assert(err, jc.ErrorIsNil) 488 489 c.Assert(out.AuthType(), gc.Equals, cloud.CertificateAuthType) 490 c.Assert(out.Attributes(), jc.DeepEquals, map[string]string{ 491 "client-cert": coretesting.CACert, 492 "client-key": coretesting.CAKey, 493 "server-cert": "server-cert", 494 }) 495 } 496 497 func (s *credentialsSuite) TestFinalizeCredentialLocalAddCertFatal(c *gc.C) { 498 // If we get back an error from CreateClientCertificate, we'll make another 499 // call to GetCertificate. If that call succeeds, then we assume 500 // that the CreateClientCertificate failure was due to a concurrent call. 501 502 ctrl := gomock.NewController(c) 503 defer ctrl.Finish() 504 505 deps := s.createProvider(ctrl) 506 507 gomock.InOrder( 508 deps.server.EXPECT().GetCertificate(s.clientCertFingerprint(c)).Return(nil, "", errors.New("not found")), 509 deps.server.EXPECT().CreateClientCertificate(s.clientCert()).Return(errors.New("UNIQUE constraint failed: interactives.fingerprint")), 510 deps.server.EXPECT().GetCertificate(s.clientCertFingerprint(c)).Return(nil, "", errors.New("not found")), 511 ) 512 513 _, err := deps.provider.FinalizeCredential(cmdtesting.Context(c), environs.FinalizeCredentialParams{ 514 CloudEndpoint: "", 515 Credential: cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 516 "client-cert": coretesting.CACert, 517 "client-key": coretesting.CAKey, 518 }), 519 }) 520 c.Assert(err, gc.ErrorMatches, "adding certificate \"juju\": UNIQUE constraint failed: interactives.fingerprint") 521 } 522 523 func (s *credentialsSuite) TestFinalizeCredentialLocalCertificateWithEmptyClientCert(c *gc.C) { 524 ctrl := gomock.NewController(c) 525 defer ctrl.Finish() 526 527 deps := s.createProvider(ctrl) 528 529 ctx := cmdtesting.Context(c) 530 _, err := deps.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ 531 CloudEndpoint: "localhost", 532 Credential: cloud.NewCredential("certificate", map[string]string{}), 533 }) 534 c.Assert(err, gc.ErrorMatches, `missing or empty "client-cert" attribute not valid`) 535 } 536 537 func (s *credentialsSuite) TestFinalizeCredentialLocalCertificateWithEmptyClientKey(c *gc.C) { 538 ctrl := gomock.NewController(c) 539 defer ctrl.Finish() 540 541 deps := s.createProvider(ctrl) 542 543 ctx := cmdtesting.Context(c) 544 _, err := deps.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ 545 CloudEndpoint: "localhost", 546 Credential: cloud.NewCredential("certificate", map[string]string{ 547 "client-cert": coretesting.CACert, 548 }), 549 }) 550 c.Assert(err, gc.ErrorMatches, `missing or empty "client-key" attribute not valid`) 551 } 552 553 func (s *credentialsSuite) TestFinalizeCredentialLocalCertificate(c *gc.C) { 554 ctrl := gomock.NewController(c) 555 defer ctrl.Finish() 556 557 deps := s.createProvider(ctrl) 558 559 ctx := cmdtesting.Context(c) 560 out, err := deps.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ 561 CloudEndpoint: "localhost", 562 Credential: cloud.NewCredential("certificate", map[string]string{ 563 "client-cert": "/path/to/client/cert.crt", 564 "client-key": "/path/to/client/key.key", 565 "server-cert": "server-cert", 566 }), 567 }) 568 c.Assert(err, jc.ErrorIsNil) 569 570 c.Assert(out.AuthType(), gc.Equals, cloud.AuthType("certificate")) 571 c.Assert(out.Attributes(), jc.DeepEquals, map[string]string{ 572 "client-cert": "/path/to/client/cert.crt", 573 "client-key": "/path/to/client/key.key", 574 "server-cert": "server-cert", 575 }) 576 } 577 578 func (s *credentialsSuite) TestFinalizeCredentialNonLocalCertificate(c *gc.C) { 579 ctrl := gomock.NewController(c) 580 defer ctrl.Finish() 581 582 deps := s.createProvider(ctrl) 583 584 // Patch the interface addresses for the calling machine, so 585 // it appears that we're not on the LXD server host. 586 _, err := deps.provider.FinalizeCredential(cmdtesting.Context(c), environs.FinalizeCredentialParams{ 587 CloudEndpoint: "8.8.8.8", 588 Credential: cloud.NewCredential("certificate", map[string]string{}), 589 }) 590 c.Assert(err, gc.ErrorMatches, `missing or empty "client-cert" attribute not valid`) 591 } 592 593 func (s *credentialsSuite) TestFinalizeCredentialNonLocal(c *gc.C) { 594 ctrl := gomock.NewController(c) 595 defer ctrl.Finish() 596 597 deps := s.createProvider(ctrl) 598 599 insecureCred := cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 600 "client-cert": coretesting.CACert, 601 "client-key": coretesting.CAKey, 602 "trust-password": "fred", 603 }) 604 insecureSpec := environs.CloudSpec{ 605 Endpoint: "8.8.8.8", 606 Credential: &insecureCred, 607 } 608 secureCred := cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 609 "client-cert": coretesting.CACert, 610 "client-key": coretesting.CAKey, 611 "server-cert": coretesting.ServerCert, 612 }) 613 secureSpec := environs.CloudSpec{ 614 Endpoint: "8.8.8.8", 615 Credential: &secureCred, 616 } 617 params := environs.FinalizeCredentialParams{ 618 CloudEndpoint: "8.8.8.8", 619 Credential: insecureCred, 620 } 621 clientCert := containerLXD.NewCertificate([]byte(coretesting.CACert), []byte(coretesting.CAKey)) 622 clientX509Cert, err := clientCert.X509() 623 c.Assert(err, jc.ErrorIsNil) 624 clientX509Base64 := base64.StdEncoding.EncodeToString(clientX509Cert.Raw) 625 fingerprint, err := clientCert.Fingerprint() 626 c.Assert(err, jc.ErrorIsNil) 627 628 deps.netLookup.EXPECT().LookupHost("8.8.8.8").Return([]string{}, nil) 629 deps.netLookup.EXPECT().InterfaceAddrs().Return([]net.Addr{}, nil) 630 deps.serverFactory.EXPECT().InsecureRemoteServer(insecureSpec).Return(deps.server, nil) 631 deps.server.EXPECT().GetCertificate(fingerprint).Return(nil, "", errors.New("not found")) 632 deps.server.EXPECT().CreateCertificate(api.CertificatesPost{ 633 CertificatePut: api.CertificatePut{ 634 Name: insecureCred.Label, 635 Type: "client", 636 }, 637 Certificate: clientX509Base64, 638 Password: "fred", 639 }).Return(nil) 640 deps.server.EXPECT().GetServer().Return(&api.Server{ 641 Environment: api.ServerEnvironment{ 642 Certificate: coretesting.ServerCert, 643 }, 644 }, "etag", nil) 645 deps.serverFactory.EXPECT().RemoteServer(secureSpec).Return(deps.server, nil) 646 deps.server.EXPECT().ServerCertificate().Return(coretesting.ServerCert) 647 648 expected := cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 649 "client-cert": coretesting.CACert, 650 "client-key": coretesting.CAKey, 651 "server-cert": coretesting.ServerCert, 652 }) 653 654 got, err := deps.provider.FinalizeCredential(cmdtesting.Context(c), params) 655 c.Assert(err, jc.ErrorIsNil) 656 c.Assert(got, jc.DeepEquals, &expected) 657 } 658 659 func (s *credentialsSuite) TestFinalizeCredentialNonLocalWithCertAlreadyExists(c *gc.C) { 660 ctrl := gomock.NewController(c) 661 defer ctrl.Finish() 662 663 deps := s.createProvider(ctrl) 664 665 insecureCred := cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 666 "client-cert": coretesting.CACert, 667 "client-key": coretesting.CAKey, 668 "trust-password": "fred", 669 }) 670 insecureSpec := environs.CloudSpec{ 671 Endpoint: "8.8.8.8", 672 Credential: &insecureCred, 673 } 674 secureCred := cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 675 "client-cert": coretesting.CACert, 676 "client-key": coretesting.CAKey, 677 "server-cert": coretesting.ServerCert, 678 }) 679 secureSpec := environs.CloudSpec{ 680 Endpoint: "8.8.8.8", 681 Credential: &secureCred, 682 } 683 params := environs.FinalizeCredentialParams{ 684 CloudEndpoint: "8.8.8.8", 685 Credential: insecureCred, 686 } 687 clientCert := containerLXD.NewCertificate([]byte(coretesting.CACert), []byte(coretesting.CAKey)) 688 fingerprint, err := clientCert.Fingerprint() 689 c.Assert(err, jc.ErrorIsNil) 690 691 deps.netLookup.EXPECT().LookupHost("8.8.8.8").Return([]string{}, nil) 692 deps.netLookup.EXPECT().InterfaceAddrs().Return([]net.Addr{}, nil) 693 deps.serverFactory.EXPECT().InsecureRemoteServer(insecureSpec).Return(deps.server, nil) 694 deps.server.EXPECT().GetCertificate(fingerprint).Return(&api.Certificate{}, "", nil) 695 deps.server.EXPECT().GetServer().Return(&api.Server{ 696 Environment: api.ServerEnvironment{ 697 Certificate: coretesting.ServerCert, 698 }, 699 }, "etag", nil) 700 deps.serverFactory.EXPECT().RemoteServer(secureSpec).Return(deps.server, nil) 701 deps.server.EXPECT().ServerCertificate().Return(coretesting.ServerCert) 702 703 expected := cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 704 "client-cert": coretesting.CACert, 705 "client-key": coretesting.CAKey, 706 "server-cert": coretesting.ServerCert, 707 }) 708 709 got, err := deps.provider.FinalizeCredential(cmdtesting.Context(c), params) 710 c.Assert(err, jc.ErrorIsNil) 711 c.Assert(got, jc.DeepEquals, &expected) 712 } 713 714 func (s *credentialsSuite) TestFinalizeCredentialRemoteWithInsecureError(c *gc.C) { 715 ctrl := gomock.NewController(c) 716 defer ctrl.Finish() 717 718 deps := s.createProvider(ctrl) 719 720 insecureCred := cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 721 "client-cert": coretesting.CACert, 722 "client-key": coretesting.CAKey, 723 "trust-password": "fred", 724 }) 725 insecureSpec := environs.CloudSpec{ 726 Endpoint: "8.8.8.8", 727 Credential: &insecureCred, 728 } 729 params := environs.FinalizeCredentialParams{ 730 CloudEndpoint: "8.8.8.8", 731 Credential: insecureCred, 732 } 733 734 deps.netLookup.EXPECT().LookupHost("8.8.8.8").Return([]string{}, nil) 735 deps.netLookup.EXPECT().InterfaceAddrs().Return([]net.Addr{}, nil) 736 deps.serverFactory.EXPECT().InsecureRemoteServer(insecureSpec).Return(nil, errors.New("bad")) 737 738 _, err := deps.provider.FinalizeCredential(cmdtesting.Context(c), params) 739 c.Assert(errors.Cause(err).Error(), gc.Equals, "bad") 740 } 741 742 func (s *credentialsSuite) TestFinalizeCredentialRemoteWithCreateCertificateError(c *gc.C) { 743 ctrl := gomock.NewController(c) 744 defer ctrl.Finish() 745 746 deps := s.createProvider(ctrl) 747 748 insecureCred := cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 749 "client-cert": coretesting.CACert, 750 "client-key": coretesting.CAKey, 751 "trust-password": "fred", 752 }) 753 insecureSpec := environs.CloudSpec{ 754 Endpoint: "8.8.8.8", 755 Credential: &insecureCred, 756 } 757 params := environs.FinalizeCredentialParams{ 758 CloudEndpoint: "8.8.8.8", 759 Credential: insecureCred, 760 } 761 clientCert := containerLXD.NewCertificate([]byte(coretesting.CACert), []byte(coretesting.CAKey)) 762 clientX509Cert, err := clientCert.X509() 763 c.Assert(err, jc.ErrorIsNil) 764 clientX509Base64 := base64.StdEncoding.EncodeToString(clientX509Cert.Raw) 765 fingerprint, err := clientCert.Fingerprint() 766 c.Assert(err, jc.ErrorIsNil) 767 768 deps.netLookup.EXPECT().LookupHost("8.8.8.8").Return([]string{}, nil) 769 deps.netLookup.EXPECT().InterfaceAddrs().Return([]net.Addr{}, nil) 770 deps.serverFactory.EXPECT().InsecureRemoteServer(insecureSpec).Return(deps.server, nil) 771 deps.server.EXPECT().GetCertificate(fingerprint).Return(nil, "", errors.New("not found")) 772 deps.server.EXPECT().CreateCertificate(api.CertificatesPost{ 773 CertificatePut: api.CertificatePut{ 774 Name: insecureCred.Label, 775 Type: "client", 776 }, 777 Certificate: clientX509Base64, 778 Password: "fred", 779 }).Return(errors.New("bad")) 780 781 _, err = deps.provider.FinalizeCredential(cmdtesting.Context(c), params) 782 c.Assert(errors.Cause(err).Error(), gc.Equals, "bad") 783 } 784 785 func (s *credentialsSuite) TestFinalizeCredentialRemoveWithGetServerError(c *gc.C) { 786 ctrl := gomock.NewController(c) 787 defer ctrl.Finish() 788 789 deps := s.createProvider(ctrl) 790 791 insecureCred := cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 792 "client-cert": coretesting.CACert, 793 "client-key": coretesting.CAKey, 794 "trust-password": "fred", 795 }) 796 insecureSpec := environs.CloudSpec{ 797 Endpoint: "8.8.8.8", 798 Credential: &insecureCred, 799 } 800 params := environs.FinalizeCredentialParams{ 801 CloudEndpoint: "8.8.8.8", 802 Credential: insecureCred, 803 } 804 clientCert := containerLXD.NewCertificate([]byte(coretesting.CACert), []byte(coretesting.CAKey)) 805 clientX509Cert, err := clientCert.X509() 806 c.Assert(err, jc.ErrorIsNil) 807 clientX509Base64 := base64.StdEncoding.EncodeToString(clientX509Cert.Raw) 808 fingerprint, err := clientCert.Fingerprint() 809 c.Assert(err, jc.ErrorIsNil) 810 811 deps.netLookup.EXPECT().LookupHost("8.8.8.8").Return([]string{}, nil) 812 deps.netLookup.EXPECT().InterfaceAddrs().Return([]net.Addr{}, nil) 813 deps.serverFactory.EXPECT().InsecureRemoteServer(insecureSpec).Return(deps.server, nil) 814 deps.server.EXPECT().GetCertificate(fingerprint).Return(nil, "", errors.New("not found")) 815 deps.server.EXPECT().CreateCertificate(api.CertificatesPost{ 816 CertificatePut: api.CertificatePut{ 817 Name: insecureCred.Label, 818 Type: "client", 819 }, 820 Certificate: clientX509Base64, 821 Password: "fred", 822 }).Return(nil) 823 deps.server.EXPECT().GetServer().Return(nil, "etag", errors.New("bad")) 824 825 _, err = deps.provider.FinalizeCredential(cmdtesting.Context(c), params) 826 c.Assert(errors.Cause(err).Error(), gc.Equals, "bad") 827 } 828 829 func (s *credentialsSuite) TestFinalizeCredentialRemoteWithNewRemoteServerError(c *gc.C) { 830 ctrl := gomock.NewController(c) 831 defer ctrl.Finish() 832 833 deps := s.createProvider(ctrl) 834 835 insecureCred := cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 836 "client-cert": coretesting.CACert, 837 "client-key": coretesting.CAKey, 838 "trust-password": "fred", 839 }) 840 insecureSpec := environs.CloudSpec{ 841 Endpoint: "8.8.8.8", 842 Credential: &insecureCred, 843 } 844 secureCred := cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 845 "client-cert": coretesting.CACert, 846 "client-key": coretesting.CAKey, 847 "server-cert": coretesting.ServerCert, 848 }) 849 secureSpec := environs.CloudSpec{ 850 Endpoint: "8.8.8.8", 851 Credential: &secureCred, 852 } 853 params := environs.FinalizeCredentialParams{ 854 CloudEndpoint: "8.8.8.8", 855 Credential: insecureCred, 856 } 857 clientCert := containerLXD.NewCertificate([]byte(coretesting.CACert), []byte(coretesting.CAKey)) 858 clientX509Cert, err := clientCert.X509() 859 c.Assert(err, jc.ErrorIsNil) 860 clientX509Base64 := base64.StdEncoding.EncodeToString(clientX509Cert.Raw) 861 fingerprint, err := clientCert.Fingerprint() 862 c.Assert(err, jc.ErrorIsNil) 863 864 deps.netLookup.EXPECT().LookupHost("8.8.8.8").Return([]string{}, nil) 865 deps.netLookup.EXPECT().InterfaceAddrs().Return([]net.Addr{}, nil) 866 deps.serverFactory.EXPECT().InsecureRemoteServer(insecureSpec).Return(deps.server, nil) 867 deps.server.EXPECT().GetCertificate(fingerprint).Return(nil, "", errors.New("not found")) 868 deps.server.EXPECT().CreateCertificate(api.CertificatesPost{ 869 CertificatePut: api.CertificatePut{ 870 Name: insecureCred.Label, 871 Type: "client", 872 }, 873 Certificate: clientX509Base64, 874 Password: "fred", 875 }).Return(nil) 876 deps.server.EXPECT().GetServer().Return(&api.Server{ 877 Environment: api.ServerEnvironment{ 878 Certificate: coretesting.ServerCert, 879 }, 880 }, "etag", nil) 881 deps.serverFactory.EXPECT().RemoteServer(secureSpec).Return(nil, errors.New("bad")) 882 883 _, err = deps.provider.FinalizeCredential(cmdtesting.Context(c), params) 884 c.Assert(errors.Cause(err).Error(), gc.Equals, "bad") 885 } 886 887 func (s *credentialsSuite) TestInteractiveFinalizeCredentialWithValidCredentials(c *gc.C) { 888 ctrl := gomock.NewController(c) 889 defer ctrl.Finish() 890 891 deps := s.createProvider(ctrl) 892 893 out, err := deps.provider.FinalizeCredential(cmdtesting.Context(c), environs.FinalizeCredentialParams{ 894 CloudEndpoint: "localhost", 895 Credential: cloud.NewCredential("interactive", map[string]string{ 896 "client-cert": coretesting.CACert, 897 "client-key": coretesting.CAKey, 898 "server-cert": "server-cert", 899 }), 900 }) 901 c.Assert(err, jc.ErrorIsNil) 902 903 c.Assert(out.AuthType(), gc.Equals, cloud.AuthType("interactive")) 904 c.Assert(out.Attributes(), jc.DeepEquals, map[string]string{ 905 "client-cert": coretesting.CACert, 906 "client-key": coretesting.CAKey, 907 "server-cert": "server-cert", 908 }) 909 } 910 911 func (s *credentialsSuite) TestInteractiveFinalizeCredentialWithTrustPassword(c *gc.C) { 912 ctrl := gomock.NewController(c) 913 defer ctrl.Finish() 914 915 deps := s.createProvider(ctrl) 916 917 path := osenv.JujuXDGDataHomePath("lxd") 918 deps.certReadWriter.EXPECT().Read(path).Return(nil, nil, os.ErrNotExist) 919 920 path = filepath.Join(utils.Home(), ".config", "lxc") 921 deps.certReadWriter.EXPECT().Read(path).Return([]byte(coretesting.CACert), []byte(coretesting.CAKey), nil) 922 923 deps.server.EXPECT().GetCertificate(s.clientCertFingerprint(c)).Return(nil, "", nil) 924 deps.server.EXPECT().ServerCertificate().Return("server-cert") 925 926 localhostIP := net.IPv4(127, 0, 0, 1) 927 ipNet := &net.IPNet{IP: localhostIP, Mask: localhostIP.DefaultMask()} 928 929 deps.netLookup.EXPECT().LookupHost("localhost").Return([]string{"127.0.0.1"}, nil) 930 deps.netLookup.EXPECT().InterfaceAddrs().Return([]net.Addr{ipNet}, nil) 931 932 out, err := deps.provider.FinalizeCredential(cmdtesting.Context(c), environs.FinalizeCredentialParams{ 933 CloudEndpoint: "localhost", 934 Credential: cloud.NewCredential("interactive", map[string]string{ 935 "trust-password": "password1", 936 }), 937 }) 938 c.Assert(err, jc.ErrorIsNil) 939 940 c.Assert(out.AuthType(), gc.Equals, cloud.CertificateAuthType) 941 c.Assert(out.Attributes(), jc.DeepEquals, map[string]string{ 942 "client-cert": coretesting.CACert, 943 "client-key": coretesting.CAKey, 944 "server-cert": "server-cert", 945 }) 946 } 947 948 func (s *credentialsSuite) TestInteractiveFinalizeCredentialWithCertFailure(c *gc.C) { 949 ctrl := gomock.NewController(c) 950 defer ctrl.Finish() 951 952 deps := s.createProvider(ctrl) 953 954 path := osenv.JujuXDGDataHomePath("lxd") 955 deps.certReadWriter.EXPECT().Read(path).Return(nil, nil, errors.New("bad")) 956 957 _, err := deps.provider.FinalizeCredential(cmdtesting.Context(c), environs.FinalizeCredentialParams{ 958 CloudEndpoint: "localhost", 959 Credential: cloud.NewCredential("interactive", map[string]string{ 960 "trust-password": "password1", 961 }), 962 }) 963 c.Assert(errors.Cause(err).Error(), gc.Equals, "bad") 964 } 965 966 func (s *credentialsSuite) clientCert() *containerLXD.Certificate { 967 return &containerLXD.Certificate{ 968 Name: "juju", 969 CertPEM: []byte(coretesting.CACert), 970 KeyPEM: []byte(coretesting.CAKey), 971 } 972 } 973 974 func (s *credentialsSuite) clientCertFingerprint(c *gc.C) string { 975 fp, err := s.clientCert().Fingerprint() 976 c.Assert(err, jc.ErrorIsNil) 977 return fp 978 } 979 980 func (s *credentialsSuite) TestGetCertificates(c *gc.C) { 981 cred := cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 982 "client-cert": coretesting.CACert, 983 "client-key": coretesting.CAKey, 984 "server-cert": "server.crt", 985 }) 986 cert, server, ok := lxd.GetCertificates(cred) 987 c.Assert(ok, gc.Equals, true) 988 c.Assert(cert, jc.DeepEquals, s.clientCert()) 989 c.Assert(server, gc.Equals, "server.crt") 990 } 991 992 func (s *credentialsSuite) TestGetCertificatesMissingClientCert(c *gc.C) { 993 cred := cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 994 "client-key": coretesting.CAKey, 995 "server-cert": "server.crt", 996 }) 997 _, _, ok := lxd.GetCertificates(cred) 998 c.Assert(ok, gc.Equals, false) 999 } 1000 1001 func (s *credentialsSuite) TestGetCertificatesMissingClientKey(c *gc.C) { 1002 cred := cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 1003 "client-cert": coretesting.CACert, 1004 "server-cert": "server.crt", 1005 }) 1006 _, _, ok := lxd.GetCertificates(cred) 1007 c.Assert(ok, gc.Equals, false) 1008 } 1009 1010 func (s *credentialsSuite) TestGetCertificatesMissingServerCert(c *gc.C) { 1011 cred := cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 1012 "client-cert": coretesting.CACert, 1013 "client-key": coretesting.CAKey, 1014 }) 1015 _, _, ok := lxd.GetCertificates(cred) 1016 c.Assert(ok, gc.Equals, false) 1017 }