github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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 "io" 9 10 "github.com/juju/cmd/v3/cmdtesting" 11 "github.com/juju/errors" 12 "github.com/juju/testing" 13 jc "github.com/juju/testing/checkers" 14 gc "gopkg.in/check.v1" 15 16 "github.com/juju/juju/cloud" 17 "github.com/juju/juju/environs" 18 envtesting "github.com/juju/juju/environs/testing" 19 "github.com/juju/juju/provider/azure" 20 "github.com/juju/juju/provider/azure/internal/azureauth" 21 "github.com/juju/juju/provider/azure/internal/azurecli" 22 "github.com/juju/juju/provider/azure/internal/azuretesting" 23 ) 24 25 type credentialsSuite struct { 26 testing.IsolationSuite 27 servicePrincipalCreator servicePrincipalCreator 28 azureCLI azureCLI 29 provider environs.EnvironProvider 30 sender azuretesting.Senders 31 } 32 33 var _ = gc.Suite(&credentialsSuite{}) 34 35 func (s *credentialsSuite) SetUpTest(c *gc.C) { 36 s.IsolationSuite.SetUpTest(c) 37 s.servicePrincipalCreator = servicePrincipalCreator{} 38 s.azureCLI = azureCLI{} 39 s.provider = newProvider(c, azure.ProviderConfig{ 40 ServicePrincipalCreator: &s.servicePrincipalCreator, 41 AzureCLI: &s.azureCLI, 42 Sender: azuretesting.NewSerialSender(&s.sender), 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 func (s *credentialsSuite) TestServicePrincipalSecretCredentialsValid(c *gc.C) { 54 envtesting.AssertProviderCredentialsValid(c, s.provider, "service-principal-secret", map[string]string{ 55 "application-id": "application", 56 "application-password": "password", 57 "subscription-id": "subscription", 58 "managed-subscription-id": "managed-subscription", 59 }) 60 } 61 62 func (s *credentialsSuite) TestServicePrincipalSecretHiddenAttributes(c *gc.C) { 63 envtesting.AssertProviderCredentialsAttributesHidden(c, s.provider, "service-principal-secret", "application-password") 64 } 65 66 func (s *credentialsSuite) TestDetectCredentialsNoAccounts(c *gc.C) { 67 _, err := s.provider.DetectCredentials("") 68 c.Assert(err, jc.Satisfies, errors.IsNotFound) 69 calls := s.azureCLI.Calls() 70 c.Assert(calls, gc.HasLen, 1) 71 c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts") 72 } 73 74 func (s *credentialsSuite) TestDetectCredentialsListError(c *gc.C) { 75 s.azureCLI.SetErrors(errors.New("test error")) 76 _, err := s.provider.DetectCredentials("") 77 c.Assert(err, jc.Satisfies, errors.IsNotFound) 78 } 79 80 func (s *credentialsSuite) TestDetectCredentialsOneAccount(c *gc.C) { 81 s.azureCLI.Accounts = []azurecli.Account{{ 82 CloudName: "AzureCloud", 83 ID: "test-account-id", 84 IsDefault: true, 85 Name: "test-account", 86 State: "Enabled", 87 TenantId: "tenant-id", 88 HomeTenantId: "home-tenant-id", 89 }} 90 s.azureCLI.Clouds = []azurecli.Cloud{{ 91 IsActive: true, 92 Name: "AzureCloud", 93 }} 94 cred, err := s.provider.DetectCredentials("") 95 c.Assert(err, jc.ErrorIsNil) 96 c.Assert(cred, gc.Not(gc.IsNil)) 97 c.Assert(cred.DefaultCredential, gc.Equals, "test-account") 98 c.Assert(cred.DefaultRegion, gc.Equals, "") 99 c.Assert(cred.AuthCredentials, gc.HasLen, 1) 100 c.Assert(cred.AuthCredentials["test-account"].Label, gc.Equals, "AzureCloud subscription test-account") 101 102 calls := s.azureCLI.Calls() 103 c.Assert(calls, gc.HasLen, 2) 104 c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts") 105 c.Assert(calls[1].FuncName, gc.Equals, "ListClouds") 106 107 calls = s.servicePrincipalCreator.Calls() 108 c.Assert(calls, gc.HasLen, 1) 109 c.Assert(calls[0].FuncName, gc.Equals, "Create") 110 params, ok := calls[0].Args[1].(azureauth.ServicePrincipalParams) 111 c.Assert(ok, jc.IsTrue) 112 params.Credential = nil 113 c.Assert(params, jc.DeepEquals, azureauth.ServicePrincipalParams{ 114 SubscriptionId: "test-account-id", 115 TenantId: "tenant-id", 116 }) 117 } 118 119 func (s *credentialsSuite) TestDetectCredentialsCloudError(c *gc.C) { 120 s.azureCLI.Accounts = []azurecli.Account{{ 121 CloudName: "AzureCloud", 122 ID: "test-account-id", 123 IsDefault: true, 124 Name: "test-account", 125 State: "Enabled", 126 TenantId: "tenant-id", 127 }} 128 s.azureCLI.Clouds = []azurecli.Cloud{{ 129 IsActive: true, 130 Name: "AzureCloud", 131 }} 132 s.azureCLI.SetErrors(nil, errors.New("test error")) 133 cred, err := s.provider.DetectCredentials("") 134 c.Assert(err, jc.Satisfies, errors.IsNotFound) 135 c.Assert(cred, gc.IsNil) 136 137 calls := s.azureCLI.Calls() 138 c.Assert(calls, gc.HasLen, 2) 139 c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts") 140 c.Assert(calls[1].FuncName, gc.Equals, "ListClouds") 141 142 calls = s.servicePrincipalCreator.Calls() 143 c.Assert(calls, gc.HasLen, 0) 144 } 145 146 func (s *credentialsSuite) TestDetectCredentialsTwoAccounts(c *gc.C) { 147 s.azureCLI.Accounts = []azurecli.Account{{ 148 CloudName: "AzureCloud", 149 ID: "test-account1-id", 150 IsDefault: true, 151 Name: "test-account1", 152 State: "Enabled", 153 TenantId: "tenant-id", 154 }, { 155 CloudName: "AzureCloud", 156 ID: "test-account2-id", 157 IsDefault: false, 158 Name: "test-account2", 159 State: "Enabled", 160 TenantId: "tenant-id2", 161 }} 162 s.azureCLI.Clouds = []azurecli.Cloud{{ 163 IsActive: true, 164 Name: "AzureCloud", 165 }} 166 cred, err := s.provider.DetectCredentials("") 167 c.Assert(err, jc.ErrorIsNil) 168 c.Assert(cred, gc.Not(gc.IsNil)) 169 c.Assert(cred.DefaultCredential, gc.Equals, "test-account1") 170 c.Assert(cred.DefaultRegion, gc.Equals, "") 171 c.Assert(cred.AuthCredentials, gc.HasLen, 2) 172 c.Assert(cred.AuthCredentials["test-account1"].Label, gc.Equals, "AzureCloud subscription test-account1") 173 c.Assert(cred.AuthCredentials["test-account2"].Label, gc.Equals, "AzureCloud subscription test-account2") 174 175 calls := s.azureCLI.Calls() 176 c.Assert(calls, gc.HasLen, 2) 177 c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts") 178 c.Assert(calls[1].FuncName, gc.Equals, "ListClouds") 179 180 calls = s.servicePrincipalCreator.Calls() 181 c.Assert(calls, gc.HasLen, 2) 182 c.Assert(calls[0].FuncName, gc.Equals, "Create") 183 params, ok := calls[0].Args[1].(azureauth.ServicePrincipalParams) 184 c.Assert(ok, jc.IsTrue) 185 params.Credential = nil 186 c.Assert(params, jc.DeepEquals, azureauth.ServicePrincipalParams{ 187 SubscriptionId: "test-account1-id", 188 TenantId: "tenant-id", 189 }) 190 c.Assert(calls[1].FuncName, gc.Equals, "Create") 191 params, ok = calls[1].Args[1].(azureauth.ServicePrincipalParams) 192 c.Assert(ok, jc.IsTrue) 193 params.Credential = nil 194 c.Assert(params, jc.DeepEquals, azureauth.ServicePrincipalParams{ 195 SubscriptionId: "test-account2-id", 196 TenantId: "tenant-id2", 197 }) 198 } 199 200 func (s *credentialsSuite) TestDetectCredentialsTwoAccountsOneError(c *gc.C) { 201 s.azureCLI.Accounts = []azurecli.Account{{ 202 CloudName: "AzureCloud", 203 ID: "test-account1-id", 204 IsDefault: false, 205 Name: "test-account1", 206 State: "Enabled", 207 TenantId: "tenant-id", 208 }, { 209 CloudName: "AzureCloud", 210 ID: "test-account2-id", 211 IsDefault: true, 212 Name: "test-account2", 213 State: "Enabled", 214 TenantId: "tenant-id2", 215 }} 216 s.azureCLI.Clouds = []azurecli.Cloud{{ 217 IsActive: true, 218 Name: "AzureCloud", 219 }} 220 s.servicePrincipalCreator.SetErrors(nil, errors.New("test error")) 221 cred, err := s.provider.DetectCredentials("") 222 c.Assert(err, jc.ErrorIsNil) 223 c.Assert(cred, gc.Not(gc.IsNil)) 224 c.Assert(cred.DefaultCredential, gc.Equals, "") 225 c.Assert(cred.DefaultRegion, gc.Equals, "") 226 c.Assert(cred.AuthCredentials, gc.HasLen, 1) 227 c.Assert(cred.AuthCredentials["test-account1"].Label, gc.Equals, "AzureCloud subscription test-account1") 228 229 calls := s.azureCLI.Calls() 230 c.Assert(calls, gc.HasLen, 2) 231 c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts") 232 c.Assert(calls[1].FuncName, gc.Equals, "ListClouds") 233 234 calls = s.servicePrincipalCreator.Calls() 235 c.Assert(calls, gc.HasLen, 2) 236 c.Assert(calls[0].FuncName, gc.Equals, "Create") 237 params, ok := calls[0].Args[1].(azureauth.ServicePrincipalParams) 238 c.Assert(ok, jc.IsTrue) 239 params.Credential = nil 240 c.Assert(params, jc.DeepEquals, azureauth.ServicePrincipalParams{ 241 SubscriptionId: "test-account1-id", 242 TenantId: "tenant-id", 243 }) 244 c.Assert(calls[1].FuncName, gc.Equals, "Create") 245 params, ok = calls[1].Args[1].(azureauth.ServicePrincipalParams) 246 c.Assert(ok, jc.IsTrue) 247 params.Credential = nil 248 c.Assert(params, jc.DeepEquals, azureauth.ServicePrincipalParams{ 249 SubscriptionId: "test-account2-id", 250 TenantId: "tenant-id2", 251 }) 252 } 253 254 func (s *credentialsSuite) TestFinalizeCredentialInteractive(c *gc.C) { 255 s.sender = azuretesting.Senders{discoverAuthSender()} 256 in := cloud.NewCredential("interactive", map[string]string{"subscription-id": fakeSubscriptionId}) 257 ctx := cmdtesting.Context(c) 258 out, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ 259 CloudName: "azure", 260 Credential: in, 261 CloudEndpoint: "https://arm.invalid", 262 CloudStorageEndpoint: "https://core.invalid", 263 CloudIdentityEndpoint: "https://graph.invalid", 264 }) 265 c.Assert(err, jc.ErrorIsNil) 266 c.Assert(out, gc.NotNil) 267 c.Assert(out.AuthType(), gc.Equals, cloud.AuthType("service-principal-secret")) 268 c.Assert(out.Attributes(), jc.DeepEquals, map[string]string{ 269 "application-id": "appid", 270 "application-password": "service-principal-password", 271 "application-object-id": "application-object-id", 272 "subscription-id": fakeSubscriptionId, 273 }) 274 275 s.servicePrincipalCreator.CheckCallNames(c, "InteractiveCreate") 276 args := s.servicePrincipalCreator.Calls()[0].Args 277 c.Assert(args[2], jc.DeepEquals, azureauth.ServicePrincipalParams{ 278 CloudName: "AzureCloud", 279 SubscriptionId: fakeSubscriptionId, 280 TenantId: fakeTenantId, 281 }) 282 } 283 284 func (s *credentialsSuite) TestFinalizeCredentialInteractiveError(c *gc.C) { 285 s.sender = azuretesting.Senders{discoverAuthSender()} 286 in := cloud.NewCredential("interactive", map[string]string{"subscription-id": fakeSubscriptionId}) 287 s.servicePrincipalCreator.SetErrors(errors.New("blargh")) 288 ctx := cmdtesting.Context(c) 289 _, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ 290 CloudName: "azure", 291 Credential: in, 292 CloudEndpoint: "https://arm.invalid", 293 CloudIdentityEndpoint: "https://graph.invalid", 294 }) 295 c.Assert(err, gc.ErrorMatches, "blargh") 296 } 297 298 func (s *credentialsSuite) TestFinalizeCredentialAzureCLI(c *gc.C) { 299 s.azureCLI.Accounts = []azurecli.Account{{ 300 CloudName: "AzureCloud", 301 ID: "test-account1-id", 302 IsDefault: true, 303 Name: "test-account1", 304 State: "Enabled", 305 TenantId: "tenant-id", 306 }, { 307 CloudName: "AzureCloud", 308 ID: "test-account2-id", 309 IsDefault: false, 310 Name: "test-account2", 311 State: "Enabled", 312 TenantId: "tenant-id", 313 }} 314 s.azureCLI.Clouds = []azurecli.Cloud{{ 315 IsActive: true, 316 Name: "AzureCloud", 317 }} 318 in := cloud.NewCredential("interactive", nil) 319 ctx := cmdtesting.Context(c) 320 cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ 321 Credential: in, 322 CloudName: "azure", 323 }) 324 c.Assert(err, jc.ErrorIsNil) 325 c.Assert(cred, gc.Not(gc.IsNil)) 326 c.Assert(cred.AuthType(), gc.Equals, cloud.AuthType("service-principal-secret")) 327 attrs := cred.Attributes() 328 c.Assert(attrs["subscription-id"], gc.Equals, "test-account1-id") 329 c.Assert(attrs["application-id"], gc.Equals, "appid") 330 c.Assert(attrs["application-password"], gc.Equals, "service-principal-password") 331 c.Assert(attrs["application-object-id"], gc.Equals, "application-object-id") 332 } 333 334 func (s *credentialsSuite) TestFinalizeCredentialAzureCLIShowAccountError(c *gc.C) { 335 s.azureCLI.Accounts = []azurecli.Account{{ 336 CloudName: "AzureCloud", 337 ID: "test-account1-id", 338 IsDefault: true, 339 Name: "test-account1", 340 State: "Enabled", 341 TenantId: "tenant-id", 342 }, { 343 CloudName: "AzureCloud", 344 ID: "test-account2-id", 345 IsDefault: false, 346 Name: "test-account2", 347 State: "Enabled", 348 TenantId: "tenant-id", 349 }} 350 s.azureCLI.Clouds = []azurecli.Cloud{{ 351 IsActive: true, 352 Name: "AzureCloud", 353 }} 354 s.azureCLI.SetErrors(errors.New("test error")) 355 in := cloud.NewCredential("interactive", nil) 356 ctx := cmdtesting.Context(c) 357 cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ 358 Credential: in, 359 CloudName: "azure", 360 }) 361 c.Assert(err, gc.ErrorMatches, `cannot get accounts: test error`) 362 c.Assert(cred, gc.IsNil) 363 } 364 365 func (s *credentialsSuite) TestFinalizeCredentialAzureCLIGraphTokenError(c *gc.C) { 366 s.azureCLI.Accounts = []azurecli.Account{{ 367 CloudName: "AzureCloud", 368 ID: "test-account1-id", 369 IsDefault: true, 370 Name: "test-account1", 371 State: "Enabled", 372 TenantId: "tenant-id", 373 }, { 374 CloudName: "AzureCloud", 375 ID: "test-account2-id", 376 IsDefault: false, 377 Name: "test-account2", 378 State: "Enabled", 379 TenantId: "tenant-id", 380 }} 381 s.azureCLI.Clouds = []azurecli.Cloud{{ 382 IsActive: true, 383 Name: "AzureCloud", 384 }} 385 s.servicePrincipalCreator.SetErrors(errors.New("test error")) 386 in := cloud.NewCredential("interactive", nil) 387 ctx := cmdtesting.Context(c) 388 cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ 389 Credential: in, 390 CloudName: "azure", 391 }) 392 c.Assert(err, gc.ErrorMatches, `cannot create service principal: test error`) 393 c.Assert(cred, gc.IsNil) 394 } 395 396 func (s *credentialsSuite) TestFinalizeCredentialAzureCLIServicePrincipalError(c *gc.C) { 397 s.azureCLI.Accounts = []azurecli.Account{{ 398 CloudName: "AzureCloud", 399 ID: "test-account1-id", 400 IsDefault: true, 401 Name: "test-account1", 402 State: "Enabled", 403 TenantId: "tenant-id", 404 }, { 405 CloudName: "AzureCloud", 406 ID: "test-account2-id", 407 IsDefault: false, 408 Name: "test-account2", 409 State: "Enabled", 410 TenantId: "tenant-id", 411 }} 412 s.azureCLI.Clouds = []azurecli.Cloud{{ 413 IsActive: true, 414 Name: "AzureCloud", 415 }} 416 s.servicePrincipalCreator.SetErrors(errors.New("test error")) 417 in := cloud.NewCredential("interactive", nil) 418 ctx := cmdtesting.Context(c) 419 cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ 420 Credential: in, 421 CloudName: "azure", 422 }) 423 c.Assert(err, gc.ErrorMatches, `cannot create service principal: test error`) 424 c.Assert(cred, gc.IsNil) 425 } 426 427 type servicePrincipalCreator struct { 428 testing.Stub 429 } 430 431 func (c *servicePrincipalCreator) InteractiveCreate(sdkCtx context.Context, stderr io.Writer, params azureauth.ServicePrincipalParams) (appId, spId, password string, _ error) { 432 c.MethodCall(c, "InteractiveCreate", sdkCtx, stderr, params) 433 return "appid", "application-object-id", "service-principal-password", c.NextErr() 434 } 435 436 func (c *servicePrincipalCreator) Create(sdkCtx context.Context, params azureauth.ServicePrincipalParams) (appId, spId, password string, _ error) { 437 c.MethodCall(c, "Create", sdkCtx, params) 438 return "appid", "application-object-id", "service-principal-password", c.NextErr() 439 } 440 441 type azureCLI struct { 442 testing.Stub 443 Accounts []azurecli.Account 444 Clouds []azurecli.Cloud 445 } 446 447 func (e *azureCLI) ListAccounts() ([]azurecli.Account, error) { 448 e.MethodCall(e, "ListAccounts") 449 if err := e.NextErr(); err != nil { 450 return nil, err 451 } 452 return e.Accounts, nil 453 } 454 455 func (e *azureCLI) FindAccountsWithCloudName(name string) ([]azurecli.Account, error) { 456 e.MethodCall(e, "FindAccountsWithCloudName", name) 457 if err := e.NextErr(); err != nil { 458 return nil, err 459 } 460 var accs []azurecli.Account 461 for _, acc := range e.Accounts { 462 if acc.CloudName == name { 463 accs = append(accs, acc) 464 } 465 } 466 return accs, nil 467 } 468 469 func (e *azureCLI) ShowAccount(subscription string) (*azurecli.Account, error) { 470 e.MethodCall(e, "ShowAccount", subscription) 471 if err := e.NextErr(); err != nil { 472 return nil, err 473 } 474 return e.findAccount(subscription) 475 } 476 477 func (e *azureCLI) findAccount(tenant string) (*azurecli.Account, error) { 478 for _, acc := range e.Accounts { 479 if acc.AuthTenantId() == tenant { 480 return &acc, nil 481 } 482 if tenant == "" && acc.IsDefault { 483 return &acc, nil 484 } 485 } 486 return nil, errors.New("account not found") 487 } 488 489 func (e *azureCLI) ShowCloud(name string) (*azurecli.Cloud, error) { 490 e.MethodCall(e, "ShowCloud", name) 491 if err := e.NextErr(); err != nil { 492 return nil, err 493 } 494 for _, cloud := range e.Clouds { 495 if cloud.Name == name || name == "" { 496 return &cloud, nil 497 } 498 } 499 return nil, errors.New("cloud not found") 500 } 501 502 func (e *azureCLI) ListClouds() ([]azurecli.Cloud, error) { 503 e.MethodCall(e, "ListClouds") 504 if err := e.NextErr(); err != nil { 505 return nil, err 506 } 507 return e.Clouds, nil 508 }