github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/azure/credentials_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package azure_test 5 6 import ( 7 "context" 8 "fmt" 9 "io" 10 11 "github.com/Azure/go-autorest/autorest" 12 "github.com/Azure/go-autorest/autorest/adal" 13 "github.com/juju/cmd/cmdtesting" 14 "github.com/juju/errors" 15 "github.com/juju/testing" 16 jc "github.com/juju/testing/checkers" 17 gc "gopkg.in/check.v1" 18 19 "github.com/juju/juju/cloud" 20 "github.com/juju/juju/environs" 21 envtesting "github.com/juju/juju/environs/testing" 22 "github.com/juju/juju/provider/azure" 23 "github.com/juju/juju/provider/azure/internal/azureauth" 24 "github.com/juju/juju/provider/azure/internal/azurecli" 25 ) 26 27 type credentialsSuite struct { 28 testing.IsolationSuite 29 servicePrincipalCreator servicePrincipalCreator 30 azureCLI azureCLI 31 provider environs.EnvironProvider 32 } 33 34 var _ = gc.Suite(&credentialsSuite{}) 35 36 func (s *credentialsSuite) SetUpTest(c *gc.C) { 37 s.IsolationSuite.SetUpTest(c) 38 s.servicePrincipalCreator = servicePrincipalCreator{} 39 s.azureCLI = azureCLI{} 40 s.provider = newProvider(c, azure.ProviderConfig{ 41 ServicePrincipalCreator: &s.servicePrincipalCreator, 42 AzureCLI: &s.azureCLI, 43 }) 44 } 45 46 func (s *credentialsSuite) TestCredentialSchemas(c *gc.C) { 47 envtesting.AssertProviderAuthTypes(c, s.provider, 48 "interactive", 49 "service-principal-secret", 50 ) 51 } 52 53 var sampleCredentialAttributes = map[string]string{ 54 "application-id": "application", 55 "application-password": "password", 56 "subscription-id": "subscription", 57 } 58 59 func (s *credentialsSuite) TestServicePrincipalSecretCredentialsValid(c *gc.C) { 60 envtesting.AssertProviderCredentialsValid(c, s.provider, "service-principal-secret", map[string]string{ 61 "application-id": "application", 62 "application-password": "password", 63 "subscription-id": "subscription", 64 }) 65 } 66 67 func (s *credentialsSuite) TestServicePrincipalSecretHiddenAttributes(c *gc.C) { 68 envtesting.AssertProviderCredentialsAttributesHidden(c, s.provider, "service-principal-secret", "application-password") 69 } 70 71 func (s *credentialsSuite) TestDetectCredentialsNoAccounts(c *gc.C) { 72 _, err := s.provider.DetectCredentials() 73 c.Assert(err, jc.Satisfies, errors.IsNotFound) 74 calls := s.azureCLI.Calls() 75 c.Assert(calls, gc.HasLen, 1) 76 c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts") 77 } 78 79 func (s *credentialsSuite) TestDetectCredentialsListError(c *gc.C) { 80 s.azureCLI.SetErrors(errors.New("test error")) 81 _, err := s.provider.DetectCredentials() 82 c.Assert(err, jc.Satisfies, errors.IsNotFound) 83 } 84 85 func (s *credentialsSuite) TestDetectCredentialsOneAccount(c *gc.C) { 86 s.azureCLI.Accounts = []azurecli.Account{{ 87 CloudName: "AzureCloud", 88 ID: "test-account-id", 89 IsDefault: true, 90 Name: "test-account", 91 State: "Enabled", 92 TenantId: "tenant-id", 93 }} 94 s.azureCLI.Clouds = []azurecli.Cloud{{ 95 Endpoints: azurecli.CloudEndpoints{ 96 ActiveDirectoryGraphResourceID: "https://graph.invalid/", 97 ResourceManager: "https://arm.invalid/", 98 }, 99 IsActive: true, 100 Name: "AzureCloud", 101 }} 102 cred, err := s.provider.DetectCredentials() 103 c.Assert(err, jc.ErrorIsNil) 104 c.Assert(cred, gc.Not(gc.IsNil)) 105 c.Assert(cred.DefaultCredential, gc.Equals, "test-account") 106 c.Assert(cred.DefaultRegion, gc.Equals, "") 107 c.Assert(cred.AuthCredentials, gc.HasLen, 1) 108 c.Assert(cred.AuthCredentials["test-account"].Label, gc.Equals, "AzureCloud subscription test-account") 109 110 calls := s.azureCLI.Calls() 111 c.Assert(calls, gc.HasLen, 4) 112 c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts") 113 c.Assert(calls[1].FuncName, gc.Equals, "ListClouds") 114 c.Assert(calls[2].FuncName, gc.Equals, "GetAccessToken") 115 c.Assert(calls[2].Args, jc.DeepEquals, []interface{}{"test-account-id", "https://graph.invalid/"}) 116 c.Assert(calls[3].FuncName, gc.Equals, "GetAccessToken") 117 c.Assert(calls[3].Args, jc.DeepEquals, []interface{}{"test-account-id", "https://arm.invalid/"}) 118 119 calls = s.servicePrincipalCreator.Calls() 120 c.Assert(calls, gc.HasLen, 1) 121 c.Assert(calls[0].FuncName, gc.Equals, "Create") 122 c.Assert(calls[0].Args[1], jc.DeepEquals, azureauth.ServicePrincipalParams{ 123 GraphEndpoint: "https://graph.invalid/", 124 GraphResourceId: "https://graph.invalid/", 125 GraphAuthorizer: autorest.NewBearerAuthorizer(&adal.Token{ 126 AccessToken: "test-account-id|https://graph.invalid/|access-token", 127 Type: "Bearer", 128 }), 129 ResourceManagerEndpoint: "https://arm.invalid/", 130 ResourceManagerResourceId: "https://arm.invalid/", 131 ResourceManagerAuthorizer: autorest.NewBearerAuthorizer(&adal.Token{ 132 AccessToken: "test-account-id|https://arm.invalid/|access-token", 133 Type: "Bearer", 134 }), 135 SubscriptionId: "test-account-id", 136 TenantId: "tenant-id", 137 }) 138 } 139 140 func (s *credentialsSuite) TestDetectCredentialsCloudError(c *gc.C) { 141 s.azureCLI.Accounts = []azurecli.Account{{ 142 CloudName: "AzureCloud", 143 ID: "test-account-id", 144 IsDefault: true, 145 Name: "test-account", 146 State: "Enabled", 147 TenantId: "tenant-id", 148 }} 149 s.azureCLI.Clouds = []azurecli.Cloud{{ 150 Endpoints: azurecli.CloudEndpoints{ 151 ActiveDirectoryGraphResourceID: "https://graph.invalid", 152 ResourceManager: "https://arm.invalid", 153 }, 154 IsActive: true, 155 Name: "AzureCloud", 156 }} 157 s.azureCLI.SetErrors(nil, errors.New("test error")) 158 cred, err := s.provider.DetectCredentials() 159 c.Assert(err, jc.Satisfies, errors.IsNotFound) 160 c.Assert(cred, gc.IsNil) 161 162 calls := s.azureCLI.Calls() 163 c.Assert(calls, gc.HasLen, 2) 164 c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts") 165 c.Assert(calls[1].FuncName, gc.Equals, "ListClouds") 166 167 calls = s.servicePrincipalCreator.Calls() 168 c.Assert(calls, gc.HasLen, 0) 169 } 170 171 func (s *credentialsSuite) TestDetectCredentialsTwoAccounts(c *gc.C) { 172 s.azureCLI.Accounts = []azurecli.Account{{ 173 CloudName: "AzureCloud", 174 ID: "test-account1-id", 175 IsDefault: true, 176 Name: "test-account1", 177 State: "Enabled", 178 TenantId: "tenant-id", 179 }, { 180 CloudName: "AzureCloud", 181 ID: "test-account2-id", 182 IsDefault: false, 183 Name: "test-account2", 184 State: "Enabled", 185 TenantId: "tenant-id", 186 }} 187 s.azureCLI.Clouds = []azurecli.Cloud{{ 188 Endpoints: azurecli.CloudEndpoints{ 189 ActiveDirectoryGraphResourceID: "https://graph.invalid/", 190 ResourceManager: "https://arm.invalid/", 191 }, 192 IsActive: true, 193 Name: "AzureCloud", 194 }} 195 cred, err := s.provider.DetectCredentials() 196 c.Assert(err, jc.ErrorIsNil) 197 c.Assert(cred, gc.Not(gc.IsNil)) 198 c.Assert(cred.DefaultCredential, gc.Equals, "test-account1") 199 c.Assert(cred.DefaultRegion, gc.Equals, "") 200 c.Assert(cred.AuthCredentials, gc.HasLen, 2) 201 c.Assert(cred.AuthCredentials["test-account1"].Label, gc.Equals, "AzureCloud subscription test-account1") 202 c.Assert(cred.AuthCredentials["test-account2"].Label, gc.Equals, "AzureCloud subscription test-account2") 203 204 calls := s.azureCLI.Calls() 205 c.Assert(calls, gc.HasLen, 6) 206 c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts") 207 c.Assert(calls[1].FuncName, gc.Equals, "ListClouds") 208 c.Assert(calls[2].FuncName, gc.Equals, "GetAccessToken") 209 c.Assert(calls[2].Args, jc.DeepEquals, []interface{}{"test-account1-id", "https://graph.invalid/"}) 210 c.Assert(calls[3].FuncName, gc.Equals, "GetAccessToken") 211 c.Assert(calls[3].Args, jc.DeepEquals, []interface{}{"test-account1-id", "https://arm.invalid/"}) 212 c.Assert(calls[4].FuncName, gc.Equals, "GetAccessToken") 213 c.Assert(calls[4].Args, jc.DeepEquals, []interface{}{"test-account2-id", "https://graph.invalid/"}) 214 c.Assert(calls[5].FuncName, gc.Equals, "GetAccessToken") 215 c.Assert(calls[5].Args, jc.DeepEquals, []interface{}{"test-account2-id", "https://arm.invalid/"}) 216 217 calls = s.servicePrincipalCreator.Calls() 218 c.Assert(calls, gc.HasLen, 2) 219 c.Assert(calls[0].FuncName, gc.Equals, "Create") 220 c.Assert(calls[0].Args[1], jc.DeepEquals, azureauth.ServicePrincipalParams{ 221 GraphEndpoint: "https://graph.invalid/", 222 GraphResourceId: "https://graph.invalid/", 223 GraphAuthorizer: autorest.NewBearerAuthorizer(&adal.Token{ 224 AccessToken: "test-account1-id|https://graph.invalid/|access-token", 225 Type: "Bearer", 226 }), 227 ResourceManagerEndpoint: "https://arm.invalid/", 228 ResourceManagerResourceId: "https://arm.invalid/", 229 ResourceManagerAuthorizer: autorest.NewBearerAuthorizer(&adal.Token{ 230 AccessToken: "test-account1-id|https://arm.invalid/|access-token", 231 Type: "Bearer", 232 }), 233 SubscriptionId: "test-account1-id", 234 TenantId: "tenant-id", 235 }) 236 c.Assert(calls[1].FuncName, gc.Equals, "Create") 237 c.Assert(calls[1].Args[1], jc.DeepEquals, azureauth.ServicePrincipalParams{ 238 GraphEndpoint: "https://graph.invalid/", 239 GraphResourceId: "https://graph.invalid/", 240 GraphAuthorizer: autorest.NewBearerAuthorizer(&adal.Token{ 241 AccessToken: "test-account2-id|https://graph.invalid/|access-token", 242 Type: "Bearer", 243 }), 244 ResourceManagerEndpoint: "https://arm.invalid/", 245 ResourceManagerResourceId: "https://arm.invalid/", 246 ResourceManagerAuthorizer: autorest.NewBearerAuthorizer(&adal.Token{ 247 AccessToken: "test-account2-id|https://arm.invalid/|access-token", 248 Type: "Bearer", 249 }), 250 SubscriptionId: "test-account2-id", 251 TenantId: "tenant-id", 252 }) 253 } 254 255 func (s *credentialsSuite) TestDetectCredentialsTwoAccountsOneError(c *gc.C) { 256 s.azureCLI.Accounts = []azurecli.Account{{ 257 CloudName: "AzureCloud", 258 ID: "test-account1-id", 259 IsDefault: false, 260 Name: "test-account1", 261 State: "Enabled", 262 TenantId: "tenant-id", 263 }, { 264 CloudName: "AzureCloud", 265 ID: "test-account2-id", 266 IsDefault: true, 267 Name: "test-account2", 268 State: "Enabled", 269 TenantId: "tenant-id", 270 }} 271 s.azureCLI.Clouds = []azurecli.Cloud{{ 272 Endpoints: azurecli.CloudEndpoints{ 273 ActiveDirectoryGraphResourceID: "https://graph.invalid/", 274 ResourceManager: "https://arm.invalid/", 275 }, 276 IsActive: true, 277 Name: "AzureCloud", 278 }} 279 s.azureCLI.SetErrors(nil, nil, nil, nil, errors.New("test error")) 280 cred, err := s.provider.DetectCredentials() 281 c.Assert(err, jc.ErrorIsNil) 282 c.Assert(cred, gc.Not(gc.IsNil)) 283 c.Assert(cred.DefaultCredential, gc.Equals, "") 284 c.Assert(cred.DefaultRegion, gc.Equals, "") 285 c.Assert(cred.AuthCredentials, gc.HasLen, 1) 286 c.Assert(cred.AuthCredentials["test-account1"].Label, gc.Equals, "AzureCloud subscription test-account1") 287 288 calls := s.azureCLI.Calls() 289 c.Assert(calls, gc.HasLen, 5) 290 c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts") 291 c.Assert(calls[1].FuncName, gc.Equals, "ListClouds") 292 c.Assert(calls[2].FuncName, gc.Equals, "GetAccessToken") 293 c.Assert(calls[2].Args, jc.DeepEquals, []interface{}{"test-account1-id", "https://graph.invalid/"}) 294 c.Assert(calls[3].FuncName, gc.Equals, "GetAccessToken") 295 c.Assert(calls[3].Args, jc.DeepEquals, []interface{}{"test-account1-id", "https://arm.invalid/"}) 296 c.Assert(calls[4].FuncName, gc.Equals, "GetAccessToken") 297 c.Assert(calls[4].Args, jc.DeepEquals, []interface{}{"test-account2-id", "https://graph.invalid/"}) 298 299 calls = s.servicePrincipalCreator.Calls() 300 c.Assert(calls, gc.HasLen, 1) 301 c.Assert(calls[0].FuncName, gc.Equals, "Create") 302 c.Assert(calls[0].Args[1], jc.DeepEquals, azureauth.ServicePrincipalParams{ 303 GraphEndpoint: "https://graph.invalid/", 304 GraphResourceId: "https://graph.invalid/", 305 GraphAuthorizer: autorest.NewBearerAuthorizer(&adal.Token{ 306 AccessToken: "test-account1-id|https://graph.invalid/|access-token", 307 Type: "Bearer", 308 }), 309 ResourceManagerEndpoint: "https://arm.invalid/", 310 ResourceManagerResourceId: "https://arm.invalid/", 311 ResourceManagerAuthorizer: autorest.NewBearerAuthorizer(&adal.Token{ 312 AccessToken: "test-account1-id|https://arm.invalid/|access-token", 313 Type: "Bearer", 314 }), 315 SubscriptionId: "test-account1-id", 316 TenantId: "tenant-id", 317 }) 318 } 319 320 func (s *credentialsSuite) TestFinalizeCredentialInteractive(c *gc.C) { 321 in := cloud.NewCredential("interactive", map[string]string{"subscription-id": "subscription"}) 322 ctx := cmdtesting.Context(c) 323 out, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ 324 Credential: in, 325 CloudEndpoint: "https://arm.invalid", 326 CloudStorageEndpoint: "https://core.invalid", 327 CloudIdentityEndpoint: "https://graph.invalid", 328 }) 329 c.Assert(err, jc.ErrorIsNil) 330 c.Assert(out, gc.NotNil) 331 c.Assert(out.AuthType(), gc.Equals, cloud.AuthType("service-principal-secret")) 332 c.Assert(out.Attributes(), jc.DeepEquals, map[string]string{ 333 "application-id": "appid", 334 "application-password": "service-principal-password", 335 "subscription-id": "subscription", 336 }) 337 338 s.servicePrincipalCreator.CheckCallNames(c, "InteractiveCreate") 339 args := s.servicePrincipalCreator.Calls()[0].Args 340 c.Assert(args[2], jc.DeepEquals, azureauth.ServicePrincipalParams{ 341 GraphEndpoint: "https://graph.invalid", 342 GraphResourceId: "https://graph.invalid/", 343 ResourceManagerEndpoint: "https://arm.invalid", 344 ResourceManagerResourceId: "https://management.core.invalid/", 345 SubscriptionId: "subscription", 346 }) 347 } 348 349 func (s *credentialsSuite) TestFinalizeCredentialInteractiveError(c *gc.C) { 350 in := cloud.NewCredential("interactive", map[string]string{"subscription-id": "subscription"}) 351 s.servicePrincipalCreator.SetErrors(errors.New("blargh")) 352 ctx := cmdtesting.Context(c) 353 _, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ 354 Credential: in, 355 CloudEndpoint: "https://arm.invalid", 356 CloudIdentityEndpoint: "https://graph.invalid", 357 }) 358 c.Assert(err, gc.ErrorMatches, "blargh") 359 } 360 361 func (s *credentialsSuite) TestFinalizeCredentialAzureCLI(c *gc.C) { 362 s.azureCLI.Accounts = []azurecli.Account{{ 363 CloudName: "AzureCloud", 364 ID: "test-account1-id", 365 IsDefault: true, 366 Name: "test-account1", 367 State: "Enabled", 368 TenantId: "tenant-id", 369 }, { 370 CloudName: "AzureCloud", 371 ID: "test-account2-id", 372 IsDefault: false, 373 Name: "test-account2", 374 State: "Enabled", 375 TenantId: "tenant-id", 376 }} 377 s.azureCLI.Clouds = []azurecli.Cloud{{ 378 Endpoints: azurecli.CloudEndpoints{ 379 ActiveDirectoryGraphResourceID: "https://graph.invalid/", 380 ResourceManager: "https://arm.invalid/", 381 }, 382 IsActive: true, 383 Name: "AzureCloud", 384 }} 385 in := cloud.NewCredential("interactive", nil) 386 ctx := cmdtesting.Context(c) 387 cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ 388 Credential: in, 389 CloudEndpoint: "https://arm.invalid", 390 CloudIdentityEndpoint: "https://graph.invalid", 391 }) 392 c.Assert(err, jc.ErrorIsNil) 393 c.Assert(cred, gc.Not(gc.IsNil)) 394 c.Assert(cred.AuthType(), gc.Equals, cloud.AuthType("service-principal-secret")) 395 attrs := cred.Attributes() 396 c.Assert(attrs["subscription-id"], gc.Equals, "test-account1-id") 397 c.Assert(attrs["application-id"], gc.Equals, "appid") 398 c.Assert(attrs["application-password"], gc.Equals, "service-principal-password") 399 } 400 401 func (s *credentialsSuite) TestFinalizeCredentialAzureCLIShowAccountError(c *gc.C) { 402 s.azureCLI.Accounts = []azurecli.Account{{ 403 CloudName: "AzureCloud", 404 ID: "test-account1-id", 405 IsDefault: true, 406 Name: "test-account1", 407 State: "Enabled", 408 TenantId: "tenant-id", 409 }, { 410 CloudName: "AzureCloud", 411 ID: "test-account2-id", 412 IsDefault: false, 413 Name: "test-account2", 414 State: "Enabled", 415 TenantId: "tenant-id", 416 }} 417 s.azureCLI.Clouds = []azurecli.Cloud{{ 418 Endpoints: azurecli.CloudEndpoints{ 419 ActiveDirectoryGraphResourceID: "https://graph.invalid/", 420 ResourceManager: "https://arm.invalid/", 421 }, 422 IsActive: true, 423 Name: "AzureCloud", 424 }} 425 s.azureCLI.SetErrors(nil, errors.New("test error")) 426 in := cloud.NewCredential("interactive", nil) 427 ctx := cmdtesting.Context(c) 428 cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ 429 Credential: in, 430 CloudEndpoint: "https://arm.invalid", 431 CloudIdentityEndpoint: "https://graph.invalid", 432 }) 433 c.Assert(err, gc.ErrorMatches, `cannot get accounts: test error`) 434 c.Assert(cred, gc.IsNil) 435 } 436 437 func (s *credentialsSuite) TestFinalizeCredentialAzureCLIGraphTokenError(c *gc.C) { 438 s.azureCLI.Accounts = []azurecli.Account{{ 439 CloudName: "AzureCloud", 440 ID: "test-account1-id", 441 IsDefault: true, 442 Name: "test-account1", 443 State: "Enabled", 444 TenantId: "tenant-id", 445 }, { 446 CloudName: "AzureCloud", 447 ID: "test-account2-id", 448 IsDefault: false, 449 Name: "test-account2", 450 State: "Enabled", 451 TenantId: "tenant-id", 452 }} 453 s.azureCLI.Clouds = []azurecli.Cloud{{ 454 Endpoints: azurecli.CloudEndpoints{ 455 ActiveDirectoryGraphResourceID: "https://graph.invalid/", 456 ResourceManager: "https://arm.invalid/", 457 }, 458 IsActive: true, 459 Name: "AzureCloud", 460 }} 461 s.azureCLI.SetErrors(nil, nil, nil, errors.New("test error")) 462 in := cloud.NewCredential("interactive", nil) 463 ctx := cmdtesting.Context(c) 464 cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ 465 Credential: in, 466 CloudEndpoint: "https://arm.invalid", 467 CloudIdentityEndpoint: "https://graph.invalid", 468 }) 469 c.Assert(err, gc.ErrorMatches, `cannot get access token for test-account1-id: test error`) 470 c.Assert(cred, gc.IsNil) 471 } 472 473 func (s *credentialsSuite) TestFinalizeCredentialAzureCLIResourceManagerTokenError(c *gc.C) { 474 s.azureCLI.Accounts = []azurecli.Account{{ 475 CloudName: "AzureCloud", 476 ID: "test-account1-id", 477 IsDefault: true, 478 Name: "test-account1", 479 State: "Enabled", 480 TenantId: "tenant-id", 481 }, { 482 CloudName: "AzureCloud", 483 ID: "test-account2-id", 484 IsDefault: false, 485 Name: "test-account2", 486 State: "Enabled", 487 TenantId: "tenant-id", 488 }} 489 s.azureCLI.Clouds = []azurecli.Cloud{{ 490 Endpoints: azurecli.CloudEndpoints{ 491 ActiveDirectoryGraphResourceID: "https://graph.invalid/", 492 ResourceManager: "https://arm.invalid/", 493 }, 494 IsActive: true, 495 Name: "AzureCloud", 496 }} 497 s.azureCLI.SetErrors(nil, nil, nil, errors.New("test error")) 498 in := cloud.NewCredential("interactive", nil) 499 ctx := cmdtesting.Context(c) 500 cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ 501 Credential: in, 502 CloudEndpoint: "https://arm.invalid", 503 CloudIdentityEndpoint: "https://graph.invalid", 504 }) 505 c.Assert(err, gc.ErrorMatches, `cannot get access token for test-account1-id: test error`) 506 c.Assert(cred, gc.IsNil) 507 } 508 509 func (s *credentialsSuite) TestFinalizeCredentialAzureCLIServicePrincipalError(c *gc.C) { 510 s.azureCLI.Accounts = []azurecli.Account{{ 511 CloudName: "AzureCloud", 512 ID: "test-account1-id", 513 IsDefault: true, 514 Name: "test-account1", 515 State: "Enabled", 516 TenantId: "tenant-id", 517 }, { 518 CloudName: "AzureCloud", 519 ID: "test-account2-id", 520 IsDefault: false, 521 Name: "test-account2", 522 State: "Enabled", 523 TenantId: "tenant-id", 524 }} 525 s.azureCLI.Clouds = []azurecli.Cloud{{ 526 Endpoints: azurecli.CloudEndpoints{ 527 ActiveDirectoryGraphResourceID: "https://graph.invalid/", 528 ResourceManager: "https://arm.invalid/", 529 }, 530 IsActive: true, 531 Name: "AzureCloud", 532 }} 533 s.servicePrincipalCreator.SetErrors(errors.New("test error")) 534 in := cloud.NewCredential("interactive", nil) 535 ctx := cmdtesting.Context(c) 536 cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ 537 Credential: in, 538 CloudEndpoint: "https://arm.invalid", 539 CloudIdentityEndpoint: "https://graph.invalid", 540 }) 541 c.Assert(err, gc.ErrorMatches, `cannot get service principal: test error`) 542 c.Assert(cred, gc.IsNil) 543 } 544 545 func (s *credentialsSuite) TestFinalizeCredentialAzureCLIDeviceFallback(c *gc.C) { 546 s.azureCLI.Accounts = []azurecli.Account{{ 547 CloudName: "AzureCloud", 548 ID: "test-account1-id", 549 IsDefault: true, 550 Name: "test-account1", 551 State: "Enabled", 552 TenantId: "tenant-id", 553 }, { 554 CloudName: "AzureCloud", 555 ID: "test-account2-id", 556 IsDefault: false, 557 Name: "test-account2", 558 State: "Enabled", 559 TenantId: "tenant-id", 560 }} 561 s.azureCLI.Clouds = []azurecli.Cloud{{ 562 Endpoints: azurecli.CloudEndpoints{ 563 ActiveDirectoryGraphResourceID: "https://graph.invalid/", 564 ResourceManager: "https://arm.invalid/", 565 }, 566 IsActive: true, 567 Name: "AzureCloud", 568 }} 569 s.azureCLI.SetErrors(nil, nil, errors.New("test error")) 570 in := cloud.NewCredential("interactive", nil) 571 ctx := cmdtesting.Context(c) 572 cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ 573 Credential: in, 574 CloudEndpoint: "https://arm.invalid", 575 CloudIdentityEndpoint: "https://graph.invalid", 576 }) 577 c.Assert(err, jc.ErrorIsNil) 578 c.Assert(cred, gc.Not(gc.IsNil)) 579 c.Assert(cred.AuthType(), gc.Equals, cloud.AuthType("service-principal-secret")) 580 attrs := cred.Attributes() 581 c.Assert(attrs["subscription-id"], gc.Equals, "test-account1-id") 582 c.Assert(attrs["application-id"], gc.Equals, "appid") 583 c.Assert(attrs["application-password"], gc.Equals, "service-principal-password") 584 s.servicePrincipalCreator.CheckCallNames(c, "InteractiveCreate") 585 } 586 587 type servicePrincipalCreator struct { 588 testing.Stub 589 } 590 591 func (c *servicePrincipalCreator) InteractiveCreate(sdkCtx context.Context, stderr io.Writer, params azureauth.ServicePrincipalParams) (appId, password string, _ error) { 592 c.MethodCall(c, "InteractiveCreate", sdkCtx, stderr, params) 593 return "appid", "service-principal-password", c.NextErr() 594 } 595 596 func (c *servicePrincipalCreator) Create(sdkCtx context.Context, params azureauth.ServicePrincipalParams) (appId, password string, _ error) { 597 c.MethodCall(c, "Create", sdkCtx, params) 598 return "appid", "service-principal-password", c.NextErr() 599 } 600 601 type azureCLI struct { 602 testing.Stub 603 Accounts []azurecli.Account 604 Clouds []azurecli.Cloud 605 } 606 607 func (e *azureCLI) ListAccounts() ([]azurecli.Account, error) { 608 e.MethodCall(e, "ListAccounts") 609 if err := e.NextErr(); err != nil { 610 return nil, err 611 } 612 return e.Accounts, nil 613 } 614 615 func (e *azureCLI) FindAccountsWithCloudName(name string) ([]azurecli.Account, error) { 616 e.MethodCall(e, "FindAccountsWithCloudName", name) 617 if err := e.NextErr(); err != nil { 618 return nil, err 619 } 620 var accs []azurecli.Account 621 for _, acc := range e.Accounts { 622 if acc.CloudName == name { 623 accs = append(accs, acc) 624 } 625 } 626 return accs, nil 627 } 628 629 func (e *azureCLI) ShowAccount(subscription string) (*azurecli.Account, error) { 630 e.MethodCall(e, "ShowAccount", subscription) 631 if err := e.NextErr(); err != nil { 632 return nil, err 633 } 634 return e.findAccount(subscription) 635 } 636 637 func (e *azureCLI) findAccount(subscription string) (*azurecli.Account, error) { 638 for _, acc := range e.Accounts { 639 if acc.ID == subscription { 640 return &acc, nil 641 } 642 if subscription == "" && acc.IsDefault { 643 return &acc, nil 644 } 645 } 646 return nil, errors.New("account not found") 647 } 648 649 func (e *azureCLI) GetAccessToken(subscription, resource string) (*azurecli.AccessToken, error) { 650 e.MethodCall(e, "GetAccessToken", subscription, resource) 651 if err := e.NextErr(); err != nil { 652 return nil, err 653 } 654 acc, err := e.findAccount(subscription) 655 if err != nil { 656 return nil, err 657 } 658 return &azurecli.AccessToken{ 659 AccessToken: fmt.Sprintf("%s|%s|access-token", subscription, resource), 660 Tenant: acc.TenantId, 661 TokenType: "Bearer", 662 }, nil 663 } 664 665 func (e *azureCLI) ShowCloud(name string) (*azurecli.Cloud, error) { 666 e.MethodCall(e, "ShowCloud", name) 667 if err := e.NextErr(); err != nil { 668 return nil, err 669 } 670 for _, cloud := range e.Clouds { 671 if cloud.Name == name || name == "" { 672 return &cloud, nil 673 } 674 } 675 return nil, errors.New("cloud not found") 676 } 677 678 func (e *azureCLI) FindCloudsWithResourceManagerEndpoint(url string) ([]azurecli.Cloud, error) { 679 e.MethodCall(e, "FindCloudsWithResourceManagerEndpoint", url) 680 if err := e.NextErr(); err != nil { 681 return nil, err 682 } 683 for _, cloud := range e.Clouds { 684 if cloud.Endpoints.ResourceManager == url { 685 return []azurecli.Cloud{cloud}, nil 686 } 687 } 688 return nil, errors.New("cloud not found") 689 } 690 691 func (e *azureCLI) ListClouds() ([]azurecli.Cloud, error) { 692 e.MethodCall(e, "ListClouds") 693 if err := e.NextErr(); err != nil { 694 return nil, err 695 } 696 return e.Clouds, nil 697 }