github.com/opentofu/opentofu@v1.7.1/internal/backend/remote-state/s3/backend_complete_test.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package s3 7 8 import ( 9 "context" 10 "fmt" 11 "os" 12 "path/filepath" 13 "regexp" 14 "testing" 15 16 "github.com/aws/aws-sdk-go-v2/aws" 17 "github.com/google/go-cmp/cmp" 18 "github.com/google/go-cmp/cmp/cmpopts" 19 "github.com/hashicorp/aws-sdk-go-base/v2/mockdata" 20 "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" 21 "github.com/opentofu/opentofu/internal/configs/hcl2shim" 22 "github.com/opentofu/opentofu/internal/encryption" 23 "github.com/opentofu/opentofu/internal/tfdiags" 24 ) 25 26 const mockStsAssumeRolePolicy = `{ 27 "Version": "2012-10-17", 28 "Statement": { 29 "Effect": "Allow", 30 "Action": "*", 31 "Resource": "*" 32 } 33 }` 34 35 func ExpectNoDiags(t *testing.T, diags tfdiags.Diagnostics) { 36 expectDiagsCount(t, diags, 0) 37 } 38 39 func expectDiagsCount(t *testing.T, diags tfdiags.Diagnostics, c int) { 40 if l := len(diags); l != c { 41 t.Fatalf("Diagnostics: expected %d element, got %d\n%#v", c, l, diags) 42 } 43 } 44 45 func ExpectDiagsEqual(expected tfdiags.Diagnostics) diagsValidator { 46 return func(t *testing.T, diags tfdiags.Diagnostics) { 47 if diff := cmp.Diff(diags, expected, cmp.Comparer(diagnosticComparer)); diff != "" { 48 t.Fatalf("unexpected diagnostics difference: %s", diff) 49 } 50 } 51 } 52 53 type diagsValidator func(*testing.T, tfdiags.Diagnostics) 54 55 // ExpectDiagsMatching returns a validator expeceting a single Diagnostic with fields matching the expectation 56 func ExpectDiagsMatching(severity tfdiags.Severity, summary matcher, detail matcher) diagsValidator { 57 return func(t *testing.T, diags tfdiags.Diagnostics) { 58 for _, d := range diags { 59 if !summary.Match(d.Description().Summary) || !detail.Match(d.Description().Detail) { 60 t.Fatalf("expected Diagnostic matching %#v, got %#v", 61 tfdiags.Sourceless( 62 severity, 63 summary.String(), 64 detail.String(), 65 ), 66 d, 67 ) 68 } 69 } 70 71 expectDiagsCount(t, diags, 1) 72 } 73 } 74 75 type diagValidator func(*testing.T, tfdiags.Diagnostic) 76 77 func ExpectDiagMatching(severity tfdiags.Severity, summary matcher, detail matcher) diagValidator { 78 return func(t *testing.T, d tfdiags.Diagnostic) { 79 if !summary.Match(d.Description().Summary) || !detail.Match(d.Description().Detail) { 80 t.Fatalf("expected Diagnostic matching %#v, got %#v", 81 tfdiags.Sourceless( 82 severity, 83 summary.String(), 84 detail.String(), 85 ), 86 d, 87 ) 88 } 89 } 90 } 91 92 func ExpectMultipleDiags(validators ...diagValidator) diagsValidator { 93 return func(t *testing.T, diags tfdiags.Diagnostics) { 94 count := len(validators) 95 if diagCount := len(diags); diagCount < count { 96 count = diagCount 97 } 98 99 for i := 0; i < count; i++ { 100 validators[i](t, diags[i]) 101 } 102 103 expectDiagsCount(t, diags, len(validators)) 104 } 105 } 106 107 type matcher interface { 108 fmt.Stringer 109 Match(string) bool 110 } 111 112 type equalsMatcher string 113 114 func (m equalsMatcher) Match(s string) bool { 115 return string(m) == s 116 } 117 118 func (m equalsMatcher) String() string { 119 return string(m) 120 } 121 122 type regexpMatcher struct { 123 re *regexp.Regexp 124 } 125 126 func newRegexpMatcher(re string) regexpMatcher { 127 return regexpMatcher{ 128 re: regexp.MustCompile(re), 129 } 130 } 131 132 func (m regexpMatcher) Match(s string) bool { 133 return m.re.MatchString(s) 134 } 135 136 func (m regexpMatcher) String() string { 137 return m.re.String() 138 } 139 140 type noopMatcher struct{} 141 142 func (m noopMatcher) Match(s string) bool { 143 return true 144 } 145 146 func (m noopMatcher) String() string { 147 return "" 148 } 149 150 func TestBackendConfig_Authentication(t *testing.T) { 151 testCases := map[string]struct { 152 config map[string]any 153 EnableEc2MetadataServer bool 154 EnableEcsCredentialsServer bool 155 EnableWebIdentityEnvVars bool 156 // EnableWebIdentityConfig bool // Not supported 157 EnvironmentVariables map[string]string 158 ExpectedCredentialsValue aws.Credentials 159 MockStsEndpoints []*servicemocks.MockEndpoint 160 SharedConfigurationFile string 161 SharedCredentialsFile string 162 ValidateDiags diagsValidator 163 }{ 164 "empty config": { 165 config: map[string]any{}, 166 MockStsEndpoints: []*servicemocks.MockEndpoint{ 167 servicemocks.MockStsGetCallerIdentityValidEndpoint, 168 }, 169 ValidateDiags: ExpectDiagsMatching( 170 tfdiags.Error, 171 equalsMatcher("No valid credential sources found"), 172 newRegexpMatcher("^Please see.+"), 173 ), 174 }, 175 176 "config AccessKey": { 177 config: map[string]any{ 178 "access_key": servicemocks.MockStaticAccessKey, 179 "secret_key": servicemocks.MockStaticSecretKey, 180 }, 181 MockStsEndpoints: []*servicemocks.MockEndpoint{ 182 servicemocks.MockStsGetCallerIdentityValidEndpoint, 183 }, 184 ExpectedCredentialsValue: mockdata.MockStaticCredentials, 185 ValidateDiags: ExpectNoDiags, 186 }, 187 188 "config AccessKey forbidden account": { 189 config: map[string]any{ 190 "access_key": servicemocks.MockStaticAccessKey, 191 "secret_key": servicemocks.MockStaticSecretKey, 192 "forbidden_account_ids": []any{"222222222222"}, 193 }, 194 MockStsEndpoints: []*servicemocks.MockEndpoint{ 195 servicemocks.MockStsGetCallerIdentityValidEndpoint, 196 }, 197 ExpectedCredentialsValue: mockdata.MockStaticCredentials, 198 ValidateDiags: ExpectDiagsMatching( 199 tfdiags.Error, 200 equalsMatcher("Invalid account ID"), 201 equalsMatcher("AWS account ID not allowed: 222222222222"), 202 ), 203 }, 204 205 "config Profile shared credentials profile aws_access_key_id": { 206 config: map[string]any{ 207 "profile": "SharedCredentialsProfile", 208 }, 209 ExpectedCredentialsValue: aws.Credentials{ 210 AccessKeyID: "ProfileSharedCredentialsAccessKey", 211 SecretAccessKey: "ProfileSharedCredentialsSecretKey", 212 Source: "SharedConfigCredentials", 213 }, 214 MockStsEndpoints: []*servicemocks.MockEndpoint{ 215 servicemocks.MockStsGetCallerIdentityValidEndpoint, 216 }, 217 SharedCredentialsFile: ` 218 [default] 219 aws_access_key_id = DefaultSharedCredentialsAccessKey 220 aws_secret_access_key = DefaultSharedCredentialsSecretKey 221 222 [SharedCredentialsProfile] 223 aws_access_key_id = ProfileSharedCredentialsAccessKey 224 aws_secret_access_key = ProfileSharedCredentialsSecretKey 225 `, 226 ValidateDiags: ExpectNoDiags, 227 }, 228 229 "environment AWS_ACCESS_KEY_ID overrides config Profile": { // Legacy behavior 230 config: map[string]any{ 231 "profile": "SharedCredentialsProfile", 232 "use_legacy_workflow": true, 233 }, 234 EnvironmentVariables: map[string]string{ 235 "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey, 236 "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey, 237 }, 238 ExpectedCredentialsValue: mockdata.MockEnvCredentials, 239 MockStsEndpoints: []*servicemocks.MockEndpoint{ 240 servicemocks.MockStsGetCallerIdentityValidEndpoint, 241 }, 242 SharedCredentialsFile: ` 243 [default] 244 aws_access_key_id = DefaultSharedCredentialsAccessKey 245 aws_secret_access_key = DefaultSharedCredentialsSecretKey 246 [SharedCredentialsProfile] 247 aws_access_key_id = ProfileSharedCredentialsAccessKey 248 aws_secret_access_key = ProfileSharedCredentialsSecretKey 249 `, 250 ValidateDiags: ExpectDiagsMatching( 251 tfdiags.Warning, 252 equalsMatcher("Deprecated Parameter"), 253 noopMatcher{}, 254 ), 255 }, 256 257 "environment AWS_ACCESS_KEY_ID does not override config Profile": { 258 config: map[string]any{ 259 "profile": "SharedCredentialsProfile", 260 }, 261 EnvironmentVariables: map[string]string{ 262 "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey, 263 "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey, 264 }, 265 ExpectedCredentialsValue: aws.Credentials{ 266 AccessKeyID: "ProfileSharedCredentialsAccessKey", 267 SecretAccessKey: "ProfileSharedCredentialsSecretKey", 268 Source: "SharedConfigCredentials", 269 }, 270 MockStsEndpoints: []*servicemocks.MockEndpoint{ 271 servicemocks.MockStsGetCallerIdentityValidEndpoint, 272 }, 273 SharedCredentialsFile: ` 274 [default] 275 aws_access_key_id = DefaultSharedCredentialsAccessKey 276 aws_secret_access_key = DefaultSharedCredentialsSecretKey 277 [SharedCredentialsProfile] 278 aws_access_key_id = ProfileSharedCredentialsAccessKey 279 aws_secret_access_key = ProfileSharedCredentialsSecretKey 280 `, 281 ValidateDiags: ExpectNoDiags, 282 }, 283 284 "environment AWS_ACCESS_KEY_ID": { 285 config: map[string]any{}, 286 EnvironmentVariables: map[string]string{ 287 "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey, 288 "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey, 289 }, 290 ExpectedCredentialsValue: mockdata.MockEnvCredentials, 291 MockStsEndpoints: []*servicemocks.MockEndpoint{ 292 servicemocks.MockStsGetCallerIdentityValidEndpoint, 293 }, 294 ValidateDiags: ExpectNoDiags, 295 }, 296 297 "environment AWS_PROFILE shared credentials profile aws_access_key_id": { 298 config: map[string]any{}, 299 EnvironmentVariables: map[string]string{ 300 "AWS_PROFILE": "SharedCredentialsProfile", 301 }, 302 ExpectedCredentialsValue: aws.Credentials{ 303 AccessKeyID: "ProfileSharedCredentialsAccessKey", 304 SecretAccessKey: "ProfileSharedCredentialsSecretKey", 305 Source: "SharedConfigCredentials", 306 }, 307 MockStsEndpoints: []*servicemocks.MockEndpoint{ 308 servicemocks.MockStsGetCallerIdentityValidEndpoint, 309 }, 310 SharedCredentialsFile: ` 311 [default] 312 aws_access_key_id = DefaultSharedCredentialsAccessKey 313 aws_secret_access_key = DefaultSharedCredentialsSecretKey 314 315 [SharedCredentialsProfile] 316 aws_access_key_id = ProfileSharedCredentialsAccessKey 317 aws_secret_access_key = ProfileSharedCredentialsSecretKey 318 `, 319 ValidateDiags: ExpectNoDiags, 320 }, 321 322 "environment AWS_SESSION_TOKEN": { 323 config: map[string]any{}, 324 EnvironmentVariables: map[string]string{ 325 "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey, 326 "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey, 327 "AWS_SESSION_TOKEN": servicemocks.MockEnvSessionToken, 328 }, 329 ExpectedCredentialsValue: mockdata.MockEnvCredentialsWithSessionToken, 330 MockStsEndpoints: []*servicemocks.MockEndpoint{ 331 servicemocks.MockStsGetCallerIdentityValidEndpoint, 332 }, 333 }, 334 335 "shared credentials default aws_access_key_id": { 336 config: map[string]any{}, 337 ExpectedCredentialsValue: aws.Credentials{ 338 AccessKeyID: "DefaultSharedCredentialsAccessKey", 339 SecretAccessKey: "DefaultSharedCredentialsSecretKey", 340 Source: "SharedConfigCredentials", 341 }, 342 MockStsEndpoints: []*servicemocks.MockEndpoint{ 343 servicemocks.MockStsGetCallerIdentityValidEndpoint, 344 }, 345 SharedCredentialsFile: ` 346 [default] 347 aws_access_key_id = DefaultSharedCredentialsAccessKey 348 aws_secret_access_key = DefaultSharedCredentialsSecretKey 349 `, 350 }, 351 352 "web identity token access key": { 353 config: map[string]any{}, 354 EnableWebIdentityEnvVars: true, 355 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials, 356 MockStsEndpoints: []*servicemocks.MockEndpoint{ 357 servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint, 358 servicemocks.MockStsGetCallerIdentityValidEndpoint, 359 }, 360 }, 361 362 "EC2 metadata access key": { 363 config: map[string]any{}, 364 EnableEc2MetadataServer: true, 365 ExpectedCredentialsValue: mockdata.MockEc2MetadataCredentials, 366 MockStsEndpoints: []*servicemocks.MockEndpoint{ 367 servicemocks.MockStsGetCallerIdentityValidEndpoint, 368 }, 369 ValidateDiags: ExpectNoDiags, 370 }, 371 372 "ECS credentials access key": { 373 config: map[string]any{}, 374 EnableEcsCredentialsServer: true, 375 ExpectedCredentialsValue: mockdata.MockEcsCredentialsCredentials, 376 MockStsEndpoints: []*servicemocks.MockEndpoint{ 377 servicemocks.MockStsGetCallerIdentityValidEndpoint, 378 }, 379 }, 380 381 "AssumeWebIdentity envvar AssumeRoleARN access key": { 382 config: map[string]any{ 383 "role_arn": servicemocks.MockStsAssumeRoleArn, 384 "session_name": servicemocks.MockStsAssumeRoleSessionName, 385 }, 386 EnableWebIdentityEnvVars: true, 387 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 388 MockStsEndpoints: []*servicemocks.MockEndpoint{ 389 servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint, 390 servicemocks.MockStsAssumeRoleValidEndpoint, 391 servicemocks.MockStsGetCallerIdentityValidEndpoint, 392 }, 393 ValidateDiags: ExpectDiagsMatching( 394 tfdiags.Warning, 395 equalsMatcher("Deprecated Parameters"), 396 noopMatcher{}, 397 ), 398 }, 399 400 "config AccessKey over environment AWS_ACCESS_KEY_ID": { 401 config: map[string]any{ 402 "access_key": servicemocks.MockStaticAccessKey, 403 "secret_key": servicemocks.MockStaticSecretKey, 404 }, 405 EnvironmentVariables: map[string]string{ 406 "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey, 407 "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey, 408 }, 409 ExpectedCredentialsValue: mockdata.MockStaticCredentials, 410 MockStsEndpoints: []*servicemocks.MockEndpoint{ 411 servicemocks.MockStsGetCallerIdentityValidEndpoint, 412 }, 413 ValidateDiags: ExpectNoDiags, 414 }, 415 416 "config AccessKey over shared credentials default aws_access_key_id": { 417 config: map[string]any{ 418 "access_key": servicemocks.MockStaticAccessKey, 419 "secret_key": servicemocks.MockStaticSecretKey, 420 }, 421 ExpectedCredentialsValue: mockdata.MockStaticCredentials, 422 MockStsEndpoints: []*servicemocks.MockEndpoint{ 423 servicemocks.MockStsGetCallerIdentityValidEndpoint, 424 }, 425 SharedCredentialsFile: ` 426 [default] 427 aws_access_key_id = DefaultSharedCredentialsAccessKey 428 aws_secret_access_key = DefaultSharedCredentialsSecretKey 429 `, 430 ValidateDiags: ExpectNoDiags, 431 }, 432 433 "config AccessKey over EC2 metadata access key": { 434 config: map[string]any{ 435 "access_key": servicemocks.MockStaticAccessKey, 436 "secret_key": servicemocks.MockStaticSecretKey, 437 }, 438 ExpectedCredentialsValue: mockdata.MockStaticCredentials, 439 MockStsEndpoints: []*servicemocks.MockEndpoint{ 440 servicemocks.MockStsGetCallerIdentityValidEndpoint, 441 }, 442 }, 443 444 "config AccessKey over ECS credentials access key": { 445 config: map[string]any{ 446 "access_key": servicemocks.MockStaticAccessKey, 447 "secret_key": servicemocks.MockStaticSecretKey, 448 }, 449 EnableEcsCredentialsServer: true, 450 ExpectedCredentialsValue: mockdata.MockStaticCredentials, 451 MockStsEndpoints: []*servicemocks.MockEndpoint{ 452 servicemocks.MockStsGetCallerIdentityValidEndpoint, 453 }, 454 }, 455 456 "environment AWS_ACCESS_KEY_ID over shared credentials default aws_access_key_id": { 457 config: map[string]any{}, 458 EnvironmentVariables: map[string]string{ 459 "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey, 460 "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey, 461 }, 462 ExpectedCredentialsValue: mockdata.MockEnvCredentials, 463 MockStsEndpoints: []*servicemocks.MockEndpoint{ 464 servicemocks.MockStsGetCallerIdentityValidEndpoint, 465 }, 466 SharedCredentialsFile: ` 467 [default] 468 aws_access_key_id = DefaultSharedCredentialsAccessKey 469 aws_secret_access_key = DefaultSharedCredentialsSecretKey 470 `, 471 ValidateDiags: ExpectNoDiags, 472 }, 473 474 "environment AWS_ACCESS_KEY_ID over EC2 metadata access key": { 475 config: map[string]any{}, 476 EnvironmentVariables: map[string]string{ 477 "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey, 478 "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey, 479 }, 480 ExpectedCredentialsValue: mockdata.MockEnvCredentials, 481 MockStsEndpoints: []*servicemocks.MockEndpoint{ 482 servicemocks.MockStsGetCallerIdentityValidEndpoint, 483 }, 484 }, 485 486 "environment AWS_ACCESS_KEY_ID over ECS credentials access key": { 487 config: map[string]any{}, 488 EnvironmentVariables: map[string]string{ 489 "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey, 490 "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey, 491 }, 492 EnableEcsCredentialsServer: true, 493 ExpectedCredentialsValue: mockdata.MockEnvCredentials, 494 MockStsEndpoints: []*servicemocks.MockEndpoint{ 495 servicemocks.MockStsGetCallerIdentityValidEndpoint, 496 }, 497 }, 498 499 "shared credentials default aws_access_key_id over EC2 metadata access key": { 500 config: map[string]any{}, 501 ExpectedCredentialsValue: aws.Credentials{ 502 AccessKeyID: "DefaultSharedCredentialsAccessKey", 503 SecretAccessKey: "DefaultSharedCredentialsSecretKey", 504 Source: "SharedConfigCredentials", 505 }, 506 MockStsEndpoints: []*servicemocks.MockEndpoint{ 507 servicemocks.MockStsGetCallerIdentityValidEndpoint, 508 }, 509 SharedCredentialsFile: ` 510 [default] 511 aws_access_key_id = DefaultSharedCredentialsAccessKey 512 aws_secret_access_key = DefaultSharedCredentialsSecretKey 513 `, 514 }, 515 516 "shared credentials default aws_access_key_id over ECS credentials access key": { 517 config: map[string]any{}, 518 EnableEcsCredentialsServer: true, 519 ExpectedCredentialsValue: aws.Credentials{ 520 AccessKeyID: "DefaultSharedCredentialsAccessKey", 521 SecretAccessKey: "DefaultSharedCredentialsSecretKey", 522 Source: "SharedConfigCredentials", 523 }, 524 MockStsEndpoints: []*servicemocks.MockEndpoint{ 525 servicemocks.MockStsGetCallerIdentityValidEndpoint, 526 }, 527 SharedCredentialsFile: ` 528 [default] 529 aws_access_key_id = DefaultSharedCredentialsAccessKey 530 aws_secret_access_key = DefaultSharedCredentialsSecretKey 531 `, 532 }, 533 534 "ECS credentials access key over EC2 metadata access key": { 535 config: map[string]any{}, 536 EnableEcsCredentialsServer: true, 537 ExpectedCredentialsValue: mockdata.MockEcsCredentialsCredentials, 538 MockStsEndpoints: []*servicemocks.MockEndpoint{ 539 servicemocks.MockStsGetCallerIdentityValidEndpoint, 540 }, 541 }, 542 543 "retrieve region from shared configuration file": { 544 config: map[string]any{ 545 "access_key": servicemocks.MockStaticAccessKey, 546 "secret_key": servicemocks.MockStaticSecretKey, 547 }, 548 ExpectedCredentialsValue: mockdata.MockStaticCredentials, 549 MockStsEndpoints: []*servicemocks.MockEndpoint{ 550 servicemocks.MockStsGetCallerIdentityValidEndpoint, 551 }, 552 SharedConfigurationFile: ` 553 [default] 554 region = us-east-1 555 `, 556 }, 557 558 "skip EC2 Metadata API check": { 559 config: map[string]any{ 560 "skip_metadata_api_check": true, 561 }, 562 // The IMDS server must be enabled so that auth will succeed if the IMDS is called 563 EnableEc2MetadataServer: true, 564 MockStsEndpoints: []*servicemocks.MockEndpoint{ 565 servicemocks.MockStsGetCallerIdentityValidEndpoint, 566 }, 567 ValidateDiags: ExpectDiagsMatching( 568 tfdiags.Error, 569 equalsMatcher("No valid credential sources found"), 570 newRegexpMatcher("^Please see.+"), 571 ), 572 }, 573 574 "invalid profile name from envvar": { 575 config: map[string]any{}, 576 EnvironmentVariables: map[string]string{ 577 "AWS_PROFILE": "no-such-profile", 578 }, 579 MockStsEndpoints: []*servicemocks.MockEndpoint{ 580 servicemocks.MockStsGetCallerIdentityValidEndpoint, 581 }, 582 SharedCredentialsFile: ` 583 [some-profile] 584 aws_access_key_id = DefaultSharedCredentialsAccessKey 585 aws_secret_access_key = DefaultSharedCredentialsSecretKey 586 `, 587 ValidateDiags: ExpectDiagsMatching( 588 tfdiags.Error, 589 equalsMatcher("failed to get shared config profile, no-such-profile"), 590 equalsMatcher(""), 591 ), 592 }, 593 594 "invalid profile name from config": { 595 config: map[string]any{ 596 "profile": "no-such-profile", 597 }, 598 SharedCredentialsFile: ` 599 [some-profile] 600 aws_access_key_id = DefaultSharedCredentialsAccessKey 601 aws_secret_access_key = DefaultSharedCredentialsSecretKey 602 `, 603 MockStsEndpoints: []*servicemocks.MockEndpoint{ 604 servicemocks.MockStsGetCallerIdentityValidEndpoint, 605 }, 606 ValidateDiags: ExpectDiagsMatching( 607 tfdiags.Error, 608 equalsMatcher("failed to get shared config profile, no-such-profile"), 609 equalsMatcher(""), 610 ), 611 }, 612 613 "AWS_ACCESS_KEY_ID overrides AWS_PROFILE": { 614 config: map[string]any{}, 615 EnvironmentVariables: map[string]string{ 616 "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey, 617 "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey, 618 "AWS_PROFILE": "SharedCredentialsProfile", 619 }, 620 SharedCredentialsFile: ` 621 [default] 622 aws_access_key_id = DefaultSharedCredentialsAccessKey 623 aws_secret_access_key = DefaultSharedCredentialsSecretKey 624 625 [SharedCredentialsProfile] 626 aws_access_key_id = ProfileSharedCredentialsAccessKey 627 aws_secret_access_key = ProfileSharedCredentialsSecretKey 628 `, 629 MockStsEndpoints: []*servicemocks.MockEndpoint{ 630 servicemocks.MockStsGetCallerIdentityValidEndpoint, 631 }, 632 ExpectedCredentialsValue: mockdata.MockEnvCredentials, 633 ValidateDiags: ExpectNoDiags, 634 }, 635 636 "AWS_ACCESS_KEY_ID does not override invalid profile name from envvar": { 637 config: map[string]any{}, 638 EnvironmentVariables: map[string]string{ 639 "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey, 640 "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey, 641 "AWS_PROFILE": "no-such-profile", 642 }, 643 MockStsEndpoints: []*servicemocks.MockEndpoint{ 644 servicemocks.MockStsGetCallerIdentityValidEndpoint, 645 }, 646 SharedCredentialsFile: ` 647 [some-profile] 648 aws_access_key_id = DefaultSharedCredentialsAccessKey 649 aws_secret_access_key = DefaultSharedCredentialsSecretKey 650 `, 651 ValidateDiags: ExpectDiagsMatching( 652 tfdiags.Error, 653 equalsMatcher("failed to get shared config profile, no-such-profile"), 654 equalsMatcher(""), 655 ), 656 }, 657 } 658 659 for name, tc := range testCases { 660 tc := tc 661 662 t.Run(name, func(t *testing.T) { 663 servicemocks.InitSessionTestEnv(t) 664 665 // Populate required fields 666 tc.config["region"] = "us-east-1" 667 tc.config["bucket"] = "bucket" 668 tc.config["key"] = "key" 669 670 if tc.ValidateDiags == nil { 671 tc.ValidateDiags = ExpectNoDiags 672 } 673 674 if tc.EnableEc2MetadataServer { 675 closeEc2Metadata := servicemocks.AwsMetadataApiMock(append( 676 servicemocks.Ec2metadata_securityCredentialsEndpoints, 677 servicemocks.Ec2metadata_instanceIdEndpoint, 678 servicemocks.Ec2metadata_iamInfoEndpoint, 679 )) 680 defer closeEc2Metadata() 681 } 682 683 if tc.EnableEcsCredentialsServer { 684 closeEcsCredentials := servicemocks.EcsCredentialsApiMock() 685 defer closeEcsCredentials() 686 } 687 688 if tc.EnableWebIdentityEnvVars /*|| tc.EnableWebIdentityConfig*/ { 689 file, err := os.CreateTemp("", "aws-sdk-go-base-web-identity-token-file") 690 if err != nil { 691 t.Fatalf("unexpected error creating temporary web identity token file: %s", err) 692 } 693 694 defer os.Remove(file.Name()) 695 696 err = os.WriteFile(file.Name(), []byte(servicemocks.MockWebIdentityToken), 0600) 697 698 if err != nil { 699 t.Fatalf("unexpected error writing web identity token file: %s", err) 700 } 701 702 if tc.EnableWebIdentityEnvVars { 703 t.Setenv("AWS_ROLE_ARN", servicemocks.MockStsAssumeRoleWithWebIdentityArn) 704 t.Setenv("AWS_ROLE_SESSION_NAME", servicemocks.MockStsAssumeRoleWithWebIdentitySessionName) 705 t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", file.Name()) 706 } /*else if tc.EnableWebIdentityConfig { 707 tc.Config.AssumeRoleWithWebIdentity = &AssumeRoleWithWebIdentity{ 708 RoleARN: servicemocks.MockStsAssumeRoleWithWebIdentityArn, 709 SessionName: servicemocks.MockStsAssumeRoleWithWebIdentitySessionName, 710 WebIdentityTokenFile: file.Name(), 711 } 712 }*/ 713 } 714 715 ts := servicemocks.MockAwsApiServer("STS", tc.MockStsEndpoints) 716 defer ts.Close() 717 718 tc.config["sts_endpoint"] = ts.URL 719 720 if tc.SharedConfigurationFile != "" { 721 file, err := os.CreateTemp("", "aws-sdk-go-base-shared-configuration-file") 722 723 if err != nil { 724 t.Fatalf("unexpected error creating temporary shared configuration file: %s", err) 725 } 726 727 defer os.Remove(file.Name()) 728 729 err = os.WriteFile(file.Name(), []byte(tc.SharedConfigurationFile), 0600) 730 731 if err != nil { 732 t.Fatalf("unexpected error writing shared configuration file: %s", err) 733 } 734 735 setSharedConfigFile(t, file.Name()) 736 } 737 738 if tc.SharedCredentialsFile != "" { 739 file, err := os.CreateTemp("", "aws-sdk-go-base-shared-credentials-file") 740 741 if err != nil { 742 t.Fatalf("unexpected error creating temporary shared credentials file: %s", err) 743 } 744 745 defer os.Remove(file.Name()) 746 747 err = os.WriteFile(file.Name(), []byte(tc.SharedCredentialsFile), 0600) 748 749 if err != nil { 750 t.Fatalf("unexpected error writing shared credentials file: %s", err) 751 } 752 753 tc.config["shared_credentials_files"] = []any{file.Name()} 754 if tc.ExpectedCredentialsValue.Source == "SharedConfigCredentials" { 755 tc.ExpectedCredentialsValue.Source = fmt.Sprintf("SharedConfigCredentials: %s", file.Name()) 756 } 757 758 tc.config["shared_config_files"] = []any{file.Name()} 759 } 760 761 for k, v := range tc.EnvironmentVariables { 762 t.Setenv(k, v) 763 } 764 765 b, diags := configureBackend(t, tc.config) 766 767 tc.ValidateDiags(t, diags) 768 769 if diags.HasErrors() { 770 return 771 } 772 773 credentials, err := b.awsConfig.Credentials.Retrieve(context.TODO()) 774 if err != nil { 775 t.Fatalf("Error when requesting credentials: %s", err) 776 } 777 778 if diff := cmp.Diff(credentials, tc.ExpectedCredentialsValue, cmpopts.IgnoreFields(aws.Credentials{}, "Expires")); diff != "" { 779 t.Fatalf("unexpected credentials: (- got, + expected)\n%s", diff) 780 } 781 }) 782 } 783 } 784 func TestBackendConfig_Authentication_AssumeRoleInline(t *testing.T) { 785 testCases := map[string]struct { 786 config map[string]any 787 EnableEc2MetadataServer bool 788 EnableEcsCredentialsServer bool 789 EnvironmentVariables map[string]string 790 ExpectedCredentialsValue aws.Credentials 791 MockStsEndpoints []*servicemocks.MockEndpoint 792 SharedConfigurationFile string 793 SharedCredentialsFile string 794 ValidateDiags diagsValidator 795 }{ 796 "from config access_key": { 797 config: map[string]any{ 798 "access_key": servicemocks.MockStaticAccessKey, 799 "secret_key": servicemocks.MockStaticSecretKey, 800 "role_arn": servicemocks.MockStsAssumeRoleArn, 801 "session_name": servicemocks.MockStsAssumeRoleSessionName, 802 }, 803 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 804 MockStsEndpoints: []*servicemocks.MockEndpoint{ 805 servicemocks.MockStsAssumeRoleValidEndpoint, 806 servicemocks.MockStsGetCallerIdentityValidEndpoint, 807 }, 808 ValidateDiags: ExpectDiagsMatching( 809 tfdiags.Warning, 810 equalsMatcher("Deprecated Parameters"), 811 noopMatcher{}, 812 ), 813 }, 814 815 "from environment AWS_ACCESS_KEY_ID": { 816 config: map[string]any{ 817 "role_arn": servicemocks.MockStsAssumeRoleArn, 818 "session_name": servicemocks.MockStsAssumeRoleSessionName, 819 }, 820 EnvironmentVariables: map[string]string{ 821 "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey, 822 "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey, 823 }, 824 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 825 MockStsEndpoints: []*servicemocks.MockEndpoint{ 826 servicemocks.MockStsAssumeRoleValidEndpoint, 827 servicemocks.MockStsGetCallerIdentityValidEndpoint, 828 }, 829 ValidateDiags: ExpectDiagsMatching( 830 tfdiags.Warning, 831 equalsMatcher("Deprecated Parameters"), 832 noopMatcher{}, 833 ), 834 }, 835 836 "from config Profile with Ec2InstanceMetadata source": { 837 config: map[string]any{ 838 "profile": "SharedConfigurationProfile", 839 }, 840 EnableEc2MetadataServer: true, 841 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 842 MockStsEndpoints: []*servicemocks.MockEndpoint{ 843 servicemocks.MockStsAssumeRoleValidEndpoint, 844 servicemocks.MockStsGetCallerIdentityValidEndpoint, 845 }, 846 SharedConfigurationFile: fmt.Sprintf(` 847 [profile SharedConfigurationProfile] 848 credential_source = Ec2InstanceMetadata 849 role_arn = %[1]s 850 role_session_name = %[2]s 851 `, servicemocks.MockStsAssumeRoleArn, servicemocks.MockStsAssumeRoleSessionName), 852 }, 853 854 "from environment AWS_PROFILE with Ec2InstanceMetadata source": { 855 config: map[string]any{}, 856 EnableEc2MetadataServer: true, 857 EnvironmentVariables: map[string]string{ 858 "AWS_PROFILE": "SharedConfigurationProfile", 859 }, 860 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 861 MockStsEndpoints: []*servicemocks.MockEndpoint{ 862 servicemocks.MockStsAssumeRoleValidEndpoint, 863 servicemocks.MockStsGetCallerIdentityValidEndpoint, 864 }, 865 SharedConfigurationFile: fmt.Sprintf(` 866 [profile SharedConfigurationProfile] 867 credential_source = Ec2InstanceMetadata 868 role_arn = %[1]s 869 role_session_name = %[2]s 870 `, servicemocks.MockStsAssumeRoleArn, servicemocks.MockStsAssumeRoleSessionName), 871 }, 872 873 "from config Profile with source profile": { 874 config: map[string]any{ 875 "profile": "SharedConfigurationProfile", 876 }, 877 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 878 MockStsEndpoints: []*servicemocks.MockEndpoint{ 879 servicemocks.MockStsAssumeRoleValidEndpoint, 880 servicemocks.MockStsGetCallerIdentityValidEndpoint, 881 }, 882 SharedConfigurationFile: fmt.Sprintf(` 883 [profile SharedConfigurationProfile] 884 role_arn = %[1]s 885 role_session_name = %[2]s 886 source_profile = SharedConfigurationSourceProfile 887 888 [profile SharedConfigurationSourceProfile] 889 aws_access_key_id = SharedConfigurationSourceAccessKey 890 aws_secret_access_key = SharedConfigurationSourceSecretKey 891 `, servicemocks.MockStsAssumeRoleArn, servicemocks.MockStsAssumeRoleSessionName), 892 }, 893 894 "from environment AWS_PROFILE with source profile": { 895 config: map[string]any{}, 896 EnvironmentVariables: map[string]string{ 897 "AWS_PROFILE": "SharedConfigurationProfile", 898 }, 899 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 900 MockStsEndpoints: []*servicemocks.MockEndpoint{ 901 servicemocks.MockStsAssumeRoleValidEndpoint, 902 servicemocks.MockStsGetCallerIdentityValidEndpoint, 903 }, 904 SharedConfigurationFile: fmt.Sprintf(` 905 [profile SharedConfigurationProfile] 906 role_arn = %[1]s 907 role_session_name = %[2]s 908 source_profile = SharedConfigurationSourceProfile 909 910 [profile SharedConfigurationSourceProfile] 911 aws_access_key_id = SharedConfigurationSourceAccessKey 912 aws_secret_access_key = SharedConfigurationSourceSecretKey 913 `, servicemocks.MockStsAssumeRoleArn, servicemocks.MockStsAssumeRoleSessionName), 914 }, 915 916 "from default profile": { 917 config: map[string]any{ 918 "role_arn": servicemocks.MockStsAssumeRoleArn, 919 "session_name": servicemocks.MockStsAssumeRoleSessionName, 920 }, 921 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 922 MockStsEndpoints: []*servicemocks.MockEndpoint{ 923 servicemocks.MockStsAssumeRoleValidEndpoint, 924 servicemocks.MockStsGetCallerIdentityValidEndpoint, 925 }, 926 SharedCredentialsFile: ` 927 [default] 928 aws_access_key_id = DefaultSharedCredentialsAccessKey 929 aws_secret_access_key = DefaultSharedCredentialsSecretKey 930 `, 931 ValidateDiags: ExpectDiagsMatching( 932 tfdiags.Warning, 933 equalsMatcher("Deprecated Parameters"), 934 noopMatcher{}, 935 ), 936 }, 937 938 "from EC2 metadata": { 939 config: map[string]any{ 940 "role_arn": servicemocks.MockStsAssumeRoleArn, 941 "session_name": servicemocks.MockStsAssumeRoleSessionName, 942 }, 943 EnableEc2MetadataServer: true, 944 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 945 MockStsEndpoints: []*servicemocks.MockEndpoint{ 946 servicemocks.MockStsAssumeRoleValidEndpoint, 947 servicemocks.MockStsGetCallerIdentityValidEndpoint, 948 }, 949 ValidateDiags: ExpectDiagsMatching( 950 tfdiags.Warning, 951 equalsMatcher("Deprecated Parameters"), 952 noopMatcher{}, 953 ), 954 }, 955 956 "from ECS credentials": { 957 config: map[string]any{ 958 "role_arn": servicemocks.MockStsAssumeRoleArn, 959 "session_name": servicemocks.MockStsAssumeRoleSessionName, 960 }, 961 EnableEcsCredentialsServer: true, 962 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 963 MockStsEndpoints: []*servicemocks.MockEndpoint{ 964 servicemocks.MockStsAssumeRoleValidEndpoint, 965 servicemocks.MockStsGetCallerIdentityValidEndpoint, 966 }, 967 ValidateDiags: ExpectDiagsMatching( 968 tfdiags.Warning, 969 equalsMatcher("Deprecated Parameters"), 970 noopMatcher{}, 971 ), 972 }, 973 974 "with duration": { 975 config: map[string]any{ 976 "access_key": servicemocks.MockStaticAccessKey, 977 "secret_key": servicemocks.MockStaticSecretKey, 978 "role_arn": servicemocks.MockStsAssumeRoleArn, 979 "session_name": servicemocks.MockStsAssumeRoleSessionName, 980 "assume_role_duration_seconds": 3600, 981 }, 982 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 983 MockStsEndpoints: []*servicemocks.MockEndpoint{ 984 servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"DurationSeconds": "3600"}), 985 servicemocks.MockStsGetCallerIdentityValidEndpoint, 986 }, 987 ValidateDiags: ExpectDiagsMatching( 988 tfdiags.Warning, 989 equalsMatcher("Deprecated Parameters"), 990 noopMatcher{}, 991 ), 992 }, 993 994 "with external ID": { 995 config: map[string]any{ 996 "access_key": servicemocks.MockStaticAccessKey, 997 "secret_key": servicemocks.MockStaticSecretKey, 998 "role_arn": servicemocks.MockStsAssumeRoleArn, 999 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1000 "external_id": servicemocks.MockStsAssumeRoleExternalId, 1001 }, 1002 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1003 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1004 servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"ExternalId": servicemocks.MockStsAssumeRoleExternalId}), 1005 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1006 }, 1007 ValidateDiags: ExpectDiagsMatching( 1008 tfdiags.Warning, 1009 equalsMatcher("Deprecated Parameters"), 1010 noopMatcher{}, 1011 ), 1012 }, 1013 1014 "with policy": { 1015 config: map[string]any{ 1016 "access_key": servicemocks.MockStaticAccessKey, 1017 "secret_key": servicemocks.MockStaticSecretKey, 1018 "role_arn": servicemocks.MockStsAssumeRoleArn, 1019 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1020 "assume_role_policy": mockStsAssumeRolePolicy, 1021 }, 1022 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1023 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1024 servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"Policy": mockStsAssumeRolePolicy}), 1025 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1026 }, 1027 ValidateDiags: ExpectDiagsMatching( 1028 tfdiags.Warning, 1029 equalsMatcher("Deprecated Parameters"), 1030 noopMatcher{}, 1031 ), 1032 }, 1033 1034 "with policy ARNs": { 1035 config: map[string]any{ 1036 "access_key": servicemocks.MockStaticAccessKey, 1037 "secret_key": servicemocks.MockStaticSecretKey, 1038 "role_arn": servicemocks.MockStsAssumeRoleArn, 1039 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1040 "assume_role_policy_arns": []any{servicemocks.MockStsAssumeRolePolicyArn}, 1041 }, 1042 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1043 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1044 servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"PolicyArns.member.1.arn": servicemocks.MockStsAssumeRolePolicyArn}), 1045 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1046 }, 1047 ValidateDiags: ExpectDiagsMatching( 1048 tfdiags.Warning, 1049 equalsMatcher("Deprecated Parameters"), 1050 noopMatcher{}, 1051 ), 1052 }, 1053 1054 "with tags": { 1055 config: map[string]any{ 1056 "access_key": servicemocks.MockStaticAccessKey, 1057 "secret_key": servicemocks.MockStaticSecretKey, 1058 "role_arn": servicemocks.MockStsAssumeRoleArn, 1059 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1060 "assume_role_tags": map[string]any{ 1061 servicemocks.MockStsAssumeRoleTagKey: servicemocks.MockStsAssumeRoleTagValue, 1062 }, 1063 }, 1064 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1065 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1066 servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"Tags.member.1.Key": servicemocks.MockStsAssumeRoleTagKey, "Tags.member.1.Value": servicemocks.MockStsAssumeRoleTagValue}), 1067 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1068 }, 1069 ValidateDiags: ExpectDiagsMatching( 1070 tfdiags.Warning, 1071 equalsMatcher("Deprecated Parameters"), 1072 noopMatcher{}, 1073 ), 1074 }, 1075 1076 "with transitive tags": { 1077 config: map[string]any{ 1078 "access_key": servicemocks.MockStaticAccessKey, 1079 "secret_key": servicemocks.MockStaticSecretKey, 1080 "role_arn": servicemocks.MockStsAssumeRoleArn, 1081 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1082 "assume_role_tags": map[string]any{ 1083 servicemocks.MockStsAssumeRoleTagKey: servicemocks.MockStsAssumeRoleTagValue, 1084 }, 1085 "assume_role_transitive_tag_keys": []any{servicemocks.MockStsAssumeRoleTagKey}, 1086 }, 1087 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1088 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1089 servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"Tags.member.1.Key": servicemocks.MockStsAssumeRoleTagKey, "Tags.member.1.Value": servicemocks.MockStsAssumeRoleTagValue, "TransitiveTagKeys.member.1": servicemocks.MockStsAssumeRoleTagKey}), 1090 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1091 }, 1092 ValidateDiags: ExpectDiagsMatching( 1093 tfdiags.Warning, 1094 equalsMatcher("Deprecated Parameters"), 1095 noopMatcher{}, 1096 ), 1097 }, 1098 1099 "error": { 1100 config: map[string]any{ 1101 "access_key": servicemocks.MockStaticAccessKey, 1102 "secret_key": servicemocks.MockStaticSecretKey, 1103 "role_arn": servicemocks.MockStsAssumeRoleArn, 1104 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1105 }, 1106 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1107 servicemocks.MockStsAssumeRoleInvalidEndpointInvalidClientTokenId, 1108 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1109 }, 1110 ValidateDiags: ExpectMultipleDiags( 1111 ExpectDiagMatching( 1112 tfdiags.Warning, 1113 equalsMatcher("Deprecated Parameters"), 1114 noopMatcher{}, 1115 ), 1116 ExpectDiagMatching( 1117 tfdiags.Error, 1118 equalsMatcher("Cannot assume IAM Role"), 1119 noopMatcher{}, 1120 ), 1121 ), 1122 }, 1123 } 1124 1125 for name, tc := range testCases { 1126 tc := tc 1127 1128 t.Run(name, func(t *testing.T) { 1129 servicemocks.InitSessionTestEnv(t) 1130 1131 ctx := context.TODO() 1132 1133 // Populate required fields 1134 tc.config["region"] = "us-east-1" 1135 tc.config["bucket"] = "bucket" 1136 tc.config["key"] = "key" 1137 1138 if tc.ValidateDiags == nil { 1139 tc.ValidateDiags = ExpectNoDiags 1140 } 1141 1142 if tc.EnableEc2MetadataServer { 1143 closeEc2Metadata := servicemocks.AwsMetadataApiMock(append( 1144 servicemocks.Ec2metadata_securityCredentialsEndpoints, 1145 servicemocks.Ec2metadata_instanceIdEndpoint, 1146 servicemocks.Ec2metadata_iamInfoEndpoint, 1147 )) 1148 defer closeEc2Metadata() 1149 } 1150 1151 if tc.EnableEcsCredentialsServer { 1152 closeEcsCredentials := servicemocks.EcsCredentialsApiMock() 1153 defer closeEcsCredentials() 1154 } 1155 1156 ts := servicemocks.MockAwsApiServer("STS", tc.MockStsEndpoints) 1157 defer ts.Close() 1158 1159 tc.config["sts_endpoint"] = ts.URL 1160 1161 if tc.SharedConfigurationFile != "" { 1162 file, err := os.CreateTemp("", "aws-sdk-go-base-shared-configuration-file") 1163 1164 if err != nil { 1165 t.Fatalf("unexpected error creating temporary shared configuration file: %s", err) 1166 } 1167 1168 defer os.Remove(file.Name()) 1169 1170 err = os.WriteFile(file.Name(), []byte(tc.SharedConfigurationFile), 0600) 1171 1172 if err != nil { 1173 t.Fatalf("unexpected error writing shared configuration file: %s", err) 1174 } 1175 1176 setSharedConfigFile(t, file.Name()) 1177 } 1178 1179 if tc.SharedCredentialsFile != "" { 1180 file, err := os.CreateTemp("", "aws-sdk-go-base-shared-credentials-file") 1181 1182 if err != nil { 1183 t.Fatalf("unexpected error creating temporary shared credentials file: %s", err) 1184 } 1185 1186 defer os.Remove(file.Name()) 1187 1188 err = os.WriteFile(file.Name(), []byte(tc.SharedCredentialsFile), 0600) 1189 1190 if err != nil { 1191 t.Fatalf("unexpected error writing shared credentials file: %s", err) 1192 } 1193 1194 tc.config["shared_credentials_files"] = []any{file.Name()} 1195 if tc.ExpectedCredentialsValue.Source == "SharedConfigCredentials" { 1196 tc.ExpectedCredentialsValue.Source = fmt.Sprintf("SharedConfigCredentials: %s", file.Name()) 1197 } 1198 } 1199 1200 for k, v := range tc.EnvironmentVariables { 1201 t.Setenv(k, v) 1202 } 1203 1204 b, diags := configureBackend(t, tc.config) 1205 1206 tc.ValidateDiags(t, diags) 1207 1208 if diags.HasErrors() { 1209 return 1210 } 1211 1212 credentials, err := b.awsConfig.Credentials.Retrieve(ctx) 1213 if err != nil { 1214 t.Fatalf("Error when requesting credentials: %s", err) 1215 } 1216 1217 if diff := cmp.Diff(credentials, tc.ExpectedCredentialsValue, cmpopts.IgnoreFields(aws.Credentials{}, "Expires")); diff != "" { 1218 t.Fatalf("unexpected credentials: (- got, + expected)\n%s", diff) 1219 } 1220 }) 1221 } 1222 } 1223 1224 func TestBackendConfig_Authentication_AssumeRoleNested(t *testing.T) { 1225 testCases := map[string]struct { 1226 config map[string]any 1227 EnableEc2MetadataServer bool 1228 EnableEcsCredentialsServer bool 1229 EnvironmentVariables map[string]string 1230 ExpectedCredentialsValue aws.Credentials 1231 MockStsEndpoints []*servicemocks.MockEndpoint 1232 SharedConfigurationFile string 1233 SharedCredentialsFile string 1234 ValidateDiags diagsValidator 1235 }{ 1236 "from config access_key": { 1237 config: map[string]any{ 1238 "access_key": servicemocks.MockStaticAccessKey, 1239 "secret_key": servicemocks.MockStaticSecretKey, 1240 "assume_role": map[string]any{ 1241 "role_arn": servicemocks.MockStsAssumeRoleArn, 1242 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1243 }, 1244 }, 1245 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1246 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1247 servicemocks.MockStsAssumeRoleValidEndpoint, 1248 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1249 }, 1250 }, 1251 1252 "from environment AWS_ACCESS_KEY_ID": { 1253 config: map[string]any{ 1254 "assume_role": map[string]any{ 1255 "role_arn": servicemocks.MockStsAssumeRoleArn, 1256 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1257 }, 1258 }, 1259 EnvironmentVariables: map[string]string{ 1260 "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey, 1261 "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey, 1262 }, 1263 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1264 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1265 servicemocks.MockStsAssumeRoleValidEndpoint, 1266 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1267 }, 1268 }, 1269 1270 "from config Profile with Ec2InstanceMetadata source": { 1271 config: map[string]any{ 1272 "profile": "SharedConfigurationProfile", 1273 }, 1274 EnableEc2MetadataServer: true, 1275 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1276 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1277 servicemocks.MockStsAssumeRoleValidEndpoint, 1278 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1279 }, 1280 SharedConfigurationFile: fmt.Sprintf(` 1281 [profile SharedConfigurationProfile] 1282 credential_source = Ec2InstanceMetadata 1283 role_arn = %[1]s 1284 role_session_name = %[2]s 1285 `, servicemocks.MockStsAssumeRoleArn, servicemocks.MockStsAssumeRoleSessionName), 1286 }, 1287 1288 "from environment AWS_PROFILE with Ec2InstanceMetadata source": { 1289 config: map[string]any{}, 1290 EnableEc2MetadataServer: true, 1291 EnvironmentVariables: map[string]string{ 1292 "AWS_PROFILE": "SharedConfigurationProfile", 1293 }, 1294 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1295 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1296 servicemocks.MockStsAssumeRoleValidEndpoint, 1297 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1298 }, 1299 SharedConfigurationFile: fmt.Sprintf(` 1300 [profile SharedConfigurationProfile] 1301 credential_source = Ec2InstanceMetadata 1302 role_arn = %[1]s 1303 role_session_name = %[2]s 1304 `, servicemocks.MockStsAssumeRoleArn, servicemocks.MockStsAssumeRoleSessionName), 1305 }, 1306 1307 "from config Profile with source profile": { 1308 config: map[string]any{ 1309 "profile": "SharedConfigurationProfile", 1310 }, 1311 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1312 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1313 servicemocks.MockStsAssumeRoleValidEndpoint, 1314 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1315 }, 1316 SharedConfigurationFile: fmt.Sprintf(` 1317 [profile SharedConfigurationProfile] 1318 role_arn = %[1]s 1319 role_session_name = %[2]s 1320 source_profile = SharedConfigurationSourceProfile 1321 1322 [profile SharedConfigurationSourceProfile] 1323 aws_access_key_id = SharedConfigurationSourceAccessKey 1324 aws_secret_access_key = SharedConfigurationSourceSecretKey 1325 `, servicemocks.MockStsAssumeRoleArn, servicemocks.MockStsAssumeRoleSessionName), 1326 }, 1327 1328 "from environment AWS_PROFILE with source profile": { 1329 config: map[string]any{}, 1330 EnvironmentVariables: map[string]string{ 1331 "AWS_PROFILE": "SharedConfigurationProfile", 1332 }, 1333 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1334 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1335 servicemocks.MockStsAssumeRoleValidEndpoint, 1336 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1337 }, 1338 SharedConfigurationFile: fmt.Sprintf(` 1339 [profile SharedConfigurationProfile] 1340 role_arn = %[1]s 1341 role_session_name = %[2]s 1342 source_profile = SharedConfigurationSourceProfile 1343 1344 [profile SharedConfigurationSourceProfile] 1345 aws_access_key_id = SharedConfigurationSourceAccessKey 1346 aws_secret_access_key = SharedConfigurationSourceSecretKey 1347 `, servicemocks.MockStsAssumeRoleArn, servicemocks.MockStsAssumeRoleSessionName), 1348 }, 1349 1350 "from default profile": { 1351 config: map[string]any{ 1352 "assume_role": map[string]any{ 1353 "role_arn": servicemocks.MockStsAssumeRoleArn, 1354 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1355 }, 1356 }, 1357 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1358 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1359 servicemocks.MockStsAssumeRoleValidEndpoint, 1360 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1361 }, 1362 SharedCredentialsFile: ` 1363 [default] 1364 aws_access_key_id = DefaultSharedCredentialsAccessKey 1365 aws_secret_access_key = DefaultSharedCredentialsSecretKey 1366 `, 1367 }, 1368 1369 "from EC2 metadata": { 1370 config: map[string]any{ 1371 "assume_role": map[string]any{ 1372 "role_arn": servicemocks.MockStsAssumeRoleArn, 1373 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1374 }, 1375 }, 1376 EnableEc2MetadataServer: true, 1377 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1378 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1379 servicemocks.MockStsAssumeRoleValidEndpoint, 1380 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1381 }, 1382 }, 1383 1384 "from ECS credentials": { 1385 config: map[string]any{ 1386 "assume_role": map[string]any{ 1387 "role_arn": servicemocks.MockStsAssumeRoleArn, 1388 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1389 }, 1390 }, 1391 EnableEcsCredentialsServer: true, 1392 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1393 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1394 servicemocks.MockStsAssumeRoleValidEndpoint, 1395 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1396 }, 1397 }, 1398 1399 "with duration": { 1400 config: map[string]any{ 1401 "access_key": servicemocks.MockStaticAccessKey, 1402 "secret_key": servicemocks.MockStaticSecretKey, 1403 "assume_role": map[string]any{ 1404 "role_arn": servicemocks.MockStsAssumeRoleArn, 1405 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1406 "duration": "1h", 1407 }, 1408 }, 1409 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1410 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1411 servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"DurationSeconds": "3600"}), 1412 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1413 }, 1414 }, 1415 1416 "with external ID": { 1417 config: map[string]any{ 1418 "access_key": servicemocks.MockStaticAccessKey, 1419 "secret_key": servicemocks.MockStaticSecretKey, 1420 "assume_role": map[string]any{ 1421 "role_arn": servicemocks.MockStsAssumeRoleArn, 1422 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1423 "external_id": servicemocks.MockStsAssumeRoleExternalId, 1424 }, 1425 }, 1426 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1427 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1428 servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"ExternalId": servicemocks.MockStsAssumeRoleExternalId}), 1429 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1430 }, 1431 }, 1432 1433 "with policy": { 1434 config: map[string]any{ 1435 "access_key": servicemocks.MockStaticAccessKey, 1436 "secret_key": servicemocks.MockStaticSecretKey, 1437 "assume_role": map[string]any{ 1438 "role_arn": servicemocks.MockStsAssumeRoleArn, 1439 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1440 "policy": mockStsAssumeRolePolicy, 1441 }, 1442 }, 1443 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1444 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1445 servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"Policy": mockStsAssumeRolePolicy}), 1446 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1447 }, 1448 }, 1449 1450 "with policy ARNs": { 1451 config: map[string]any{ 1452 "access_key": servicemocks.MockStaticAccessKey, 1453 "secret_key": servicemocks.MockStaticSecretKey, 1454 "assume_role": map[string]any{ 1455 "role_arn": servicemocks.MockStsAssumeRoleArn, 1456 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1457 "policy_arns": []any{servicemocks.MockStsAssumeRolePolicyArn}, 1458 }, 1459 }, 1460 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1461 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1462 servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"PolicyArns.member.1.arn": servicemocks.MockStsAssumeRolePolicyArn}), 1463 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1464 }, 1465 }, 1466 1467 "with tags": { 1468 config: map[string]any{ 1469 "access_key": servicemocks.MockStaticAccessKey, 1470 "secret_key": servicemocks.MockStaticSecretKey, 1471 "assume_role": map[string]any{ 1472 "role_arn": servicemocks.MockStsAssumeRoleArn, 1473 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1474 "tags": map[string]any{ 1475 servicemocks.MockStsAssumeRoleTagKey: servicemocks.MockStsAssumeRoleTagValue, 1476 }, 1477 }, 1478 }, 1479 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1480 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1481 servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"Tags.member.1.Key": servicemocks.MockStsAssumeRoleTagKey, "Tags.member.1.Value": servicemocks.MockStsAssumeRoleTagValue}), 1482 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1483 }, 1484 }, 1485 1486 "with transitive tags": { 1487 config: map[string]any{ 1488 "access_key": servicemocks.MockStaticAccessKey, 1489 "secret_key": servicemocks.MockStaticSecretKey, 1490 "assume_role": map[string]any{ 1491 "role_arn": servicemocks.MockStsAssumeRoleArn, 1492 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1493 "tags": map[string]any{ 1494 servicemocks.MockStsAssumeRoleTagKey: servicemocks.MockStsAssumeRoleTagValue, 1495 }, 1496 "transitive_tag_keys": []any{servicemocks.MockStsAssumeRoleTagKey}, 1497 }, 1498 }, 1499 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials, 1500 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1501 servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"Tags.member.1.Key": servicemocks.MockStsAssumeRoleTagKey, "Tags.member.1.Value": servicemocks.MockStsAssumeRoleTagValue, "TransitiveTagKeys.member.1": servicemocks.MockStsAssumeRoleTagKey}), 1502 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1503 }, 1504 }, 1505 1506 "error": { 1507 config: map[string]any{ 1508 "access_key": servicemocks.MockStaticAccessKey, 1509 "secret_key": servicemocks.MockStaticSecretKey, 1510 "assume_role": map[string]any{ 1511 "role_arn": servicemocks.MockStsAssumeRoleArn, 1512 "session_name": servicemocks.MockStsAssumeRoleSessionName, 1513 }, 1514 }, 1515 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1516 servicemocks.MockStsAssumeRoleInvalidEndpointInvalidClientTokenId, 1517 servicemocks.MockStsGetCallerIdentityValidEndpoint, 1518 }, 1519 ValidateDiags: ExpectDiagsMatching( 1520 tfdiags.Error, 1521 equalsMatcher("Cannot assume IAM Role"), 1522 noopMatcher{}, 1523 ), 1524 }, 1525 } 1526 1527 for name, tc := range testCases { 1528 tc := tc 1529 1530 t.Run(name, func(t *testing.T) { 1531 servicemocks.InitSessionTestEnv(t) 1532 1533 ctx := context.TODO() 1534 1535 // Populate required fields 1536 tc.config["region"] = "us-east-1" 1537 tc.config["bucket"] = "bucket" 1538 tc.config["key"] = "key" 1539 1540 if tc.ValidateDiags == nil { 1541 tc.ValidateDiags = ExpectNoDiags 1542 } 1543 1544 if tc.EnableEc2MetadataServer { 1545 closeEc2Metadata := servicemocks.AwsMetadataApiMock(append( 1546 servicemocks.Ec2metadata_securityCredentialsEndpoints, 1547 servicemocks.Ec2metadata_instanceIdEndpoint, 1548 servicemocks.Ec2metadata_iamInfoEndpoint, 1549 )) 1550 defer closeEc2Metadata() 1551 } 1552 1553 if tc.EnableEcsCredentialsServer { 1554 closeEcsCredentials := servicemocks.EcsCredentialsApiMock() 1555 defer closeEcsCredentials() 1556 } 1557 1558 ts := servicemocks.MockAwsApiServer("STS", tc.MockStsEndpoints) 1559 defer ts.Close() 1560 1561 tc.config["sts_endpoint"] = ts.URL 1562 1563 if tc.SharedConfigurationFile != "" { 1564 file, err := os.CreateTemp("", "aws-sdk-go-base-shared-configuration-file") 1565 1566 if err != nil { 1567 t.Fatalf("unexpected error creating temporary shared configuration file: %s", err) 1568 } 1569 1570 defer os.Remove(file.Name()) 1571 1572 err = os.WriteFile(file.Name(), []byte(tc.SharedConfigurationFile), 0600) 1573 1574 if err != nil { 1575 t.Fatalf("unexpected error writing shared configuration file: %s", err) 1576 } 1577 1578 setSharedConfigFile(t, file.Name()) 1579 } 1580 1581 if tc.SharedCredentialsFile != "" { 1582 file, err := os.CreateTemp("", "aws-sdk-go-base-shared-credentials-file") 1583 1584 if err != nil { 1585 t.Fatalf("unexpected error creating temporary shared credentials file: %s", err) 1586 } 1587 1588 defer os.Remove(file.Name()) 1589 1590 err = os.WriteFile(file.Name(), []byte(tc.SharedCredentialsFile), 0600) 1591 1592 if err != nil { 1593 t.Fatalf("unexpected error writing shared credentials file: %s", err) 1594 } 1595 1596 tc.config["shared_credentials_files"] = []any{file.Name()} 1597 if tc.ExpectedCredentialsValue.Source == "SharedConfigCredentials" { 1598 tc.ExpectedCredentialsValue.Source = fmt.Sprintf("SharedConfigCredentials: %s", file.Name()) 1599 } 1600 1601 tc.config["shared_config_files"] = []any{file.Name()} 1602 } 1603 1604 for k, v := range tc.EnvironmentVariables { 1605 t.Setenv(k, v) 1606 } 1607 1608 b, diags := configureBackend(t, tc.config) 1609 1610 tc.ValidateDiags(t, diags) 1611 1612 if diags.HasErrors() { 1613 return 1614 } 1615 1616 credentials, err := b.awsConfig.Credentials.Retrieve(ctx) 1617 if err != nil { 1618 t.Fatalf("Error when requesting credentials: %s", err) 1619 } 1620 1621 if diff := cmp.Diff(credentials, tc.ExpectedCredentialsValue, cmpopts.IgnoreFields(aws.Credentials{}, "Expires")); diff != "" { 1622 t.Fatalf("unexpected credentials: (- got, + expected)\n%s", diff) 1623 } 1624 }) 1625 } 1626 } 1627 1628 func TestBackendConfig_Authentication_AssumeRoleWithWebIdentity(t *testing.T) { 1629 testCases := map[string]struct { 1630 config map[string]any 1631 SetConfig bool 1632 ExpandEnvVars bool 1633 EnvironmentVariables map[string]string 1634 SetTokenFileEnvironmentVariable bool 1635 SharedConfigurationFile string 1636 SetSharedConfigurationFile bool 1637 ExpectedCredentialsValue aws.Credentials 1638 ValidateDiags diagsValidator 1639 MockStsEndpoints []*servicemocks.MockEndpoint 1640 }{ 1641 "config with inline token": { 1642 config: map[string]any{ 1643 "assume_role_with_web_identity": map[string]any{ 1644 "role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn, 1645 "session_name": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName, 1646 "web_identity_token": servicemocks.MockWebIdentityToken, 1647 }, 1648 }, 1649 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials, 1650 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1651 servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint, 1652 }, 1653 }, 1654 1655 "config with token file": { 1656 config: map[string]any{ 1657 "assume_role_with_web_identity": map[string]any{ 1658 "role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn, 1659 "session_name": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName, 1660 }, 1661 }, 1662 SetConfig: true, 1663 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials, 1664 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1665 servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint, 1666 }, 1667 }, 1668 1669 "config with expanded path": { 1670 config: map[string]any{ 1671 "assume_role_with_web_identity": map[string]any{ 1672 "role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn, 1673 "session_name": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName, 1674 }, 1675 }, 1676 SetConfig: true, 1677 ExpandEnvVars: true, 1678 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials, 1679 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1680 servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint, 1681 }, 1682 }, 1683 1684 "envvar": { 1685 config: map[string]any{}, 1686 EnvironmentVariables: map[string]string{ 1687 "AWS_ROLE_ARN": servicemocks.MockStsAssumeRoleWithWebIdentityArn, 1688 "AWS_ROLE_SESSION_NAME": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName, 1689 }, 1690 SetTokenFileEnvironmentVariable: true, 1691 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials, 1692 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1693 servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint, 1694 }, 1695 }, 1696 1697 "shared configuration file": { 1698 config: map[string]any{}, 1699 SharedConfigurationFile: fmt.Sprintf(` 1700 [default] 1701 role_arn = %[1]s 1702 role_session_name = %[2]s 1703 `, servicemocks.MockStsAssumeRoleWithWebIdentityArn, servicemocks.MockStsAssumeRoleWithWebIdentitySessionName), 1704 SetSharedConfigurationFile: true, 1705 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials, 1706 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1707 servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint, 1708 }, 1709 }, 1710 1711 "config overrides envvar": { 1712 config: map[string]any{ 1713 "assume_role_with_web_identity": map[string]any{ 1714 "role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn, 1715 "session_name": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName, 1716 "web_identity_token": servicemocks.MockWebIdentityToken, 1717 }, 1718 }, 1719 EnvironmentVariables: map[string]string{ 1720 "AWS_ROLE_ARN": servicemocks.MockStsAssumeRoleWithWebIdentityAlternateArn, 1721 "AWS_ROLE_SESSION_NAME": servicemocks.MockStsAssumeRoleWithWebIdentityAlternateSessionName, 1722 "AWS_WEB_IDENTITY_TOKEN_FILE": "no-such-file", 1723 }, 1724 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials, 1725 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1726 servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint, 1727 }, 1728 }, 1729 1730 "envvar overrides shared configuration": { 1731 config: map[string]any{}, 1732 EnvironmentVariables: map[string]string{ 1733 "AWS_ROLE_ARN": servicemocks.MockStsAssumeRoleWithWebIdentityArn, 1734 "AWS_ROLE_SESSION_NAME": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName, 1735 }, 1736 SetTokenFileEnvironmentVariable: true, 1737 SharedConfigurationFile: fmt.Sprintf(` 1738 [default] 1739 role_arn = %[1]s 1740 role_session_name = %[2]s 1741 web_identity_token_file = no-such-file 1742 `, servicemocks.MockStsAssumeRoleWithWebIdentityAlternateArn, servicemocks.MockStsAssumeRoleWithWebIdentityAlternateSessionName), 1743 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials, 1744 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1745 servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint, 1746 }, 1747 }, 1748 1749 "config overrides shared configuration": { 1750 config: map[string]any{ 1751 "assume_role_with_web_identity": map[string]any{ 1752 "role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn, 1753 "session_name": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName, 1754 "web_identity_token": servicemocks.MockWebIdentityToken, 1755 }, 1756 }, 1757 SharedConfigurationFile: fmt.Sprintf(` 1758 [default] 1759 role_arn = %[1]s 1760 role_session_name = %[2]s 1761 web_identity_token_file = no-such-file 1762 `, servicemocks.MockStsAssumeRoleWithWebIdentityAlternateArn, servicemocks.MockStsAssumeRoleWithWebIdentityAlternateSessionName), 1763 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials, 1764 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1765 servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint, 1766 }, 1767 }, 1768 1769 "with duration": { 1770 config: map[string]any{ 1771 "assume_role_with_web_identity": map[string]any{ 1772 "role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn, 1773 "session_name": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName, 1774 "web_identity_token": servicemocks.MockWebIdentityToken, 1775 "duration": "1h", 1776 }, 1777 }, 1778 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials, 1779 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1780 servicemocks.MockStsAssumeRoleWithWebIdentityValidWithOptions(map[string]string{"DurationSeconds": "3600"}), 1781 }, 1782 }, 1783 1784 "with policy": { 1785 config: map[string]any{ 1786 "assume_role_with_web_identity": map[string]any{ 1787 "role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn, 1788 "session_name": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName, 1789 "web_identity_token": servicemocks.MockWebIdentityToken, 1790 "policy": "{}", 1791 }, 1792 }, 1793 ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials, 1794 MockStsEndpoints: []*servicemocks.MockEndpoint{ 1795 servicemocks.MockStsAssumeRoleWithWebIdentityValidWithOptions(map[string]string{"Policy": "{}"}), 1796 }, 1797 }, 1798 } 1799 1800 for name, tc := range testCases { 1801 tc := tc 1802 1803 t.Run(name, func(t *testing.T) { 1804 servicemocks.InitSessionTestEnv(t) 1805 1806 ctx := context.TODO() 1807 1808 // Populate required fields 1809 tc.config["region"] = "us-east-1" 1810 tc.config["bucket"] = "bucket" 1811 tc.config["key"] = "key" 1812 1813 if tc.ValidateDiags == nil { 1814 tc.ValidateDiags = ExpectNoDiags 1815 } 1816 1817 for k, v := range tc.EnvironmentVariables { 1818 t.Setenv(k, v) 1819 } 1820 1821 ts := servicemocks.MockAwsApiServer("STS", tc.MockStsEndpoints) 1822 defer ts.Close() 1823 1824 tc.config["sts_endpoint"] = ts.URL 1825 1826 t.Setenv("TMPDIR", t.TempDir()) 1827 1828 tokenFile, err := os.CreateTemp("", "aws-sdk-go-base-web-identity-token-file") 1829 if err != nil { 1830 t.Fatalf("unexpected error creating temporary web identity token file: %s", err) 1831 } 1832 tokenFileName := tokenFile.Name() 1833 1834 defer os.Remove(tokenFileName) 1835 1836 err = os.WriteFile(tokenFileName, []byte(servicemocks.MockWebIdentityToken), 0600) 1837 1838 if err != nil { 1839 t.Fatalf("unexpected error writing web identity token file: %s", err) 1840 } 1841 1842 if tc.ExpandEnvVars { 1843 tmpdir := os.Getenv("TMPDIR") 1844 rel, err := filepath.Rel(tmpdir, tokenFileName) 1845 if err != nil { 1846 t.Fatalf("error making path relative: %s", err) 1847 } 1848 t.Logf("relative: %s", rel) 1849 tokenFileName = filepath.Join("$TMPDIR", rel) 1850 t.Logf("env tempfile: %s", tokenFileName) 1851 } 1852 1853 if tc.SetConfig { 1854 ar := tc.config["assume_role_with_web_identity"].(map[string]any) 1855 ar["web_identity_token_file"] = tokenFileName 1856 } 1857 1858 if tc.SetTokenFileEnvironmentVariable { 1859 t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", tokenFileName) 1860 } 1861 1862 if tc.SharedConfigurationFile != "" { 1863 file, err := os.CreateTemp("", "aws-sdk-go-base-shared-configuration-file") 1864 1865 if err != nil { 1866 t.Fatalf("unexpected error creating temporary shared configuration file: %s", err) 1867 } 1868 1869 defer os.Remove(file.Name()) 1870 1871 if tc.SetSharedConfigurationFile { 1872 tc.SharedConfigurationFile += fmt.Sprintf("web_identity_token_file = %s\n", tokenFileName) 1873 } 1874 1875 err = os.WriteFile(file.Name(), []byte(tc.SharedConfigurationFile), 0600) 1876 1877 if err != nil { 1878 t.Fatalf("unexpected error writing shared configuration file: %s", err) 1879 } 1880 1881 tc.config["shared_config_files"] = []any{file.Name()} 1882 } 1883 1884 tc.config["skip_credentials_validation"] = true 1885 1886 b, diags := configureBackend(t, tc.config) 1887 1888 tc.ValidateDiags(t, diags) 1889 1890 if diags.HasErrors() { 1891 return 1892 } 1893 1894 credentials, err := b.awsConfig.Credentials.Retrieve(ctx) 1895 if err != nil { 1896 t.Fatalf("Error when requesting credentials: %s", err) 1897 } 1898 1899 if diff := cmp.Diff(credentials, tc.ExpectedCredentialsValue, cmpopts.IgnoreFields(aws.Credentials{}, "Expires")); diff != "" { 1900 t.Fatalf("unexpected credentials: (- got, + expected)\n%s", diff) 1901 } 1902 }) 1903 } 1904 } 1905 1906 func TestBackendConfig_Region(t *testing.T) { 1907 testCases := map[string]struct { 1908 config map[string]any 1909 EnvironmentVariables map[string]string 1910 IMDSRegion string 1911 SharedConfigurationFile string 1912 ExpectedRegion string 1913 }{ 1914 // NOT SUPPORTED: region is required 1915 // "no configuration": { 1916 // config: map[string]any{ 1917 // "access_key": servicemocks.MockStaticAccessKey, 1918 // "secret_key": servicemocks.MockStaticSecretKey, 1919 // }, 1920 // ExpectedRegion: "", 1921 // }, 1922 1923 "config": { 1924 config: map[string]any{ 1925 "access_key": servicemocks.MockStaticAccessKey, 1926 "secret_key": servicemocks.MockStaticSecretKey, 1927 "region": "us-east-1", 1928 }, 1929 ExpectedRegion: "us-east-1", 1930 }, 1931 1932 "AWS_REGION": { 1933 config: map[string]any{ 1934 "access_key": servicemocks.MockStaticAccessKey, 1935 "secret_key": servicemocks.MockStaticSecretKey, 1936 }, 1937 EnvironmentVariables: map[string]string{ 1938 "AWS_REGION": "us-east-1", 1939 }, 1940 ExpectedRegion: "us-east-1", 1941 }, 1942 "AWS_DEFAULT_REGION": { 1943 config: map[string]any{ 1944 "access_key": servicemocks.MockStaticAccessKey, 1945 "secret_key": servicemocks.MockStaticSecretKey, 1946 }, 1947 EnvironmentVariables: map[string]string{ 1948 "AWS_DEFAULT_REGION": "us-east-1", 1949 }, 1950 ExpectedRegion: "us-east-1", 1951 }, 1952 "AWS_REGION overrides AWS_DEFAULT_REGION": { 1953 config: map[string]any{ 1954 "access_key": servicemocks.MockStaticAccessKey, 1955 "secret_key": servicemocks.MockStaticSecretKey, 1956 }, 1957 EnvironmentVariables: map[string]string{ 1958 "AWS_REGION": "us-east-1", 1959 "AWS_DEFAULT_REGION": "us-west-2", 1960 }, 1961 ExpectedRegion: "us-east-1", 1962 }, 1963 1964 // NOT SUPPORTED: region from shared configuration file 1965 // "shared configuration file": { 1966 // config: map[string]any{ 1967 // "access_key": servicemocks.MockStaticAccessKey, 1968 // "secret_key": servicemocks.MockStaticSecretKey, 1969 // }, 1970 // SharedConfigurationFile: ` 1971 // [default] 1972 // region = us-east-1 1973 // `, 1974 // ExpectedRegion: "us-east-1", 1975 // }, 1976 1977 // NOT SUPPORTED: region from IMDS 1978 // "IMDS": { 1979 // config: map[string]any{}, 1980 // IMDSRegion: "us-east-1", 1981 // ExpectedRegion: "us-east-1", 1982 // }, 1983 1984 "config overrides AWS_REGION": { 1985 config: map[string]any{ 1986 "access_key": servicemocks.MockStaticAccessKey, 1987 "secret_key": servicemocks.MockStaticSecretKey, 1988 "region": "us-east-1", 1989 }, 1990 EnvironmentVariables: map[string]string{ 1991 "AWS_REGION": "us-west-2", 1992 }, 1993 ExpectedRegion: "us-east-1", 1994 }, 1995 "config overrides AWS_DEFAULT_REGION": { 1996 config: map[string]any{ 1997 "access_key": servicemocks.MockStaticAccessKey, 1998 "secret_key": servicemocks.MockStaticSecretKey, 1999 "region": "us-east-1", 2000 }, 2001 EnvironmentVariables: map[string]string{ 2002 "AWS_DEFAULT_REGION": "us-west-2", 2003 }, 2004 ExpectedRegion: "us-east-1", 2005 }, 2006 2007 "config overrides IMDS": { 2008 config: map[string]any{ 2009 "access_key": servicemocks.MockStaticAccessKey, 2010 "secret_key": servicemocks.MockStaticSecretKey, 2011 "region": "us-west-2", 2012 }, 2013 IMDSRegion: "us-east-1", 2014 ExpectedRegion: "us-west-2", 2015 }, 2016 2017 "AWS_REGION overrides shared configuration": { 2018 config: map[string]any{ 2019 "access_key": servicemocks.MockStaticAccessKey, 2020 "secret_key": servicemocks.MockStaticSecretKey, 2021 }, 2022 EnvironmentVariables: map[string]string{ 2023 "AWS_REGION": "us-east-1", 2024 }, 2025 SharedConfigurationFile: ` 2026 [default] 2027 region = us-west-2 2028 `, 2029 ExpectedRegion: "us-east-1", 2030 }, 2031 "AWS_DEFAULT_REGION overrides shared configuration": { 2032 config: map[string]any{ 2033 "access_key": servicemocks.MockStaticAccessKey, 2034 "secret_key": servicemocks.MockStaticSecretKey, 2035 }, 2036 EnvironmentVariables: map[string]string{ 2037 "AWS_DEFAULT_REGION": "us-east-1", 2038 }, 2039 SharedConfigurationFile: ` 2040 [default] 2041 region = us-west-2 2042 `, 2043 ExpectedRegion: "us-east-1", 2044 }, 2045 2046 "AWS_REGION overrides IMDS": { 2047 config: map[string]any{ 2048 "access_key": servicemocks.MockStaticAccessKey, 2049 "secret_key": servicemocks.MockStaticSecretKey, 2050 }, 2051 EnvironmentVariables: map[string]string{ 2052 "AWS_REGION": "us-east-1", 2053 }, 2054 IMDSRegion: "us-west-2", 2055 ExpectedRegion: "us-east-1", 2056 }, 2057 } 2058 2059 for name, tc := range testCases { 2060 tc := tc 2061 2062 t.Run(name, func(t *testing.T) { 2063 servicemocks.InitSessionTestEnv(t) 2064 2065 // Populate required fields 2066 tc.config["bucket"] = "bucket" 2067 tc.config["key"] = "key" 2068 2069 for k, v := range tc.EnvironmentVariables { 2070 t.Setenv(k, v) 2071 } 2072 2073 if tc.IMDSRegion != "" { 2074 closeEc2Metadata := servicemocks.AwsMetadataApiMock(append( 2075 servicemocks.Ec2metadata_securityCredentialsEndpoints, 2076 servicemocks.Ec2metadata_instanceIdEndpoint, 2077 servicemocks.Ec2metadata_iamInfoEndpoint, 2078 servicemocks.Ec2metadata_instanceIdentityEndpoint(tc.IMDSRegion), 2079 )) 2080 defer closeEc2Metadata() 2081 } 2082 2083 sts := servicemocks.MockAwsApiServer("STS", []*servicemocks.MockEndpoint{ 2084 servicemocks.MockStsGetCallerIdentityValidEndpoint, 2085 }) 2086 defer sts.Close() 2087 2088 tc.config["sts_endpoint"] = sts.URL 2089 2090 if tc.SharedConfigurationFile != "" { 2091 file, err := os.CreateTemp("", "aws-sdk-go-base-shared-configuration-file") 2092 2093 if err != nil { 2094 t.Fatalf("unexpected error creating temporary shared configuration file: %s", err) 2095 } 2096 2097 defer os.Remove(file.Name()) 2098 2099 err = os.WriteFile(file.Name(), []byte(tc.SharedConfigurationFile), 0600) 2100 2101 if err != nil { 2102 t.Fatalf("unexpected error writing shared configuration file: %s", err) 2103 } 2104 2105 setSharedConfigFile(t, file.Name()) 2106 } 2107 2108 tc.config["skip_credentials_validation"] = true 2109 2110 b, diags := configureBackend(t, tc.config) 2111 if diags.HasErrors() { 2112 t.Fatalf("configuring backend: %s", diagnosticsString(diags)) 2113 } 2114 2115 if a, e := b.awsConfig.Region, tc.ExpectedRegion; a != e { 2116 t.Errorf("expected Region %q, got: %q", e, a) 2117 } 2118 }) 2119 } 2120 } 2121 2122 func TestBackendConfig_RetryMode(t *testing.T) { 2123 testCases := map[string]struct { 2124 config map[string]any 2125 EnvironmentVariables map[string]string 2126 ExpectedMode aws.RetryMode 2127 }{ 2128 "no config": { 2129 config: map[string]any{ 2130 "access_key": servicemocks.MockStaticAccessKey, 2131 "secret_key": servicemocks.MockStaticSecretKey, 2132 }, 2133 ExpectedMode: "", 2134 }, 2135 2136 "config": { 2137 config: map[string]any{ 2138 "access_key": servicemocks.MockStaticAccessKey, 2139 "secret_key": servicemocks.MockStaticSecretKey, 2140 "retry_mode": "standard", 2141 }, 2142 ExpectedMode: aws.RetryModeStandard, 2143 }, 2144 2145 "AWS_RETRY_MODE": { 2146 config: map[string]any{ 2147 "access_key": servicemocks.MockStaticAccessKey, 2148 "secret_key": servicemocks.MockStaticSecretKey, 2149 }, 2150 EnvironmentVariables: map[string]string{ 2151 "AWS_RETRY_MODE": "adaptive", 2152 }, 2153 ExpectedMode: aws.RetryModeAdaptive, 2154 }, 2155 "config overrides AWS_RETRY_MODE": { 2156 config: map[string]any{ 2157 "access_key": servicemocks.MockStaticAccessKey, 2158 "secret_key": servicemocks.MockStaticSecretKey, 2159 "retry_mode": "standard", 2160 }, 2161 EnvironmentVariables: map[string]string{ 2162 "AWS_RETRY_MODE": "adaptive", 2163 }, 2164 ExpectedMode: aws.RetryModeStandard, 2165 }, 2166 } 2167 2168 for name, tc := range testCases { 2169 tc := tc 2170 2171 t.Run(name, func(t *testing.T) { 2172 servicemocks.InitSessionTestEnv(t) 2173 2174 // Populate required fields 2175 tc.config["bucket"] = "bucket" 2176 tc.config["key"] = "key" 2177 tc.config["region"] = "us-east-1" 2178 2179 for k, v := range tc.EnvironmentVariables { 2180 t.Setenv(k, v) 2181 } 2182 2183 sts := servicemocks.MockAwsApiServer("STS", []*servicemocks.MockEndpoint{ 2184 servicemocks.MockStsGetCallerIdentityValidEndpoint, 2185 }) 2186 defer sts.Close() 2187 2188 tc.config["sts_endpoint"] = sts.URL 2189 tc.config["skip_credentials_validation"] = true 2190 2191 b, diags := configureBackend(t, tc.config) 2192 if diags.HasErrors() { 2193 t.Fatalf("configuring backend: %s", diagnosticsString(diags)) 2194 } 2195 2196 if a, e := b.awsConfig.RetryMode, tc.ExpectedMode; a != e { 2197 t.Errorf("expected mode %q, got: %q", e, a) 2198 } 2199 }) 2200 } 2201 } 2202 2203 func setSharedConfigFile(t *testing.T, filename string) { 2204 t.Helper() 2205 t.Setenv("AWS_SDK_LOAD_CONFIG", "1") 2206 t.Setenv("AWS_CONFIG_FILE", filename) 2207 } 2208 2209 func configureBackend(t *testing.T, config map[string]any) (*Backend, tfdiags.Diagnostics) { 2210 b := New(encryption.StateEncryptionDisabled()).(*Backend) 2211 configSchema := populateSchema(t, b.ConfigSchema(), hcl2shim.HCL2ValueFromConfigValue(config)) 2212 2213 configSchema, diags := b.PrepareConfig(configSchema) 2214 2215 if diags.HasErrors() { 2216 return b, diags 2217 } 2218 2219 confDiags := b.Configure(configSchema) 2220 diags = diags.Append(confDiags) 2221 2222 return b, diags 2223 }