shared_config.go (40771B)
1 package config 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "os/user" 12 "path/filepath" 13 "strings" 14 "time" 15 16 "github.com/aws/aws-sdk-go-v2/aws" 17 "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" 18 "github.com/aws/aws-sdk-go-v2/internal/ini" 19 "github.com/aws/smithy-go/logging" 20 ) 21 22 const ( 23 // Prefix to use for filtering profiles. The profile prefix should only 24 // exist in the shared config file, not the credentials file. 25 profilePrefix = `profile ` 26 27 // Prefix to be used for SSO sections. These are supposed to only exist in 28 // the shared config file, not the credentials file. 29 ssoSectionPrefix = `sso-session ` 30 31 // string equivalent for boolean 32 endpointDiscoveryDisabled = `false` 33 endpointDiscoveryEnabled = `true` 34 endpointDiscoveryAuto = `auto` 35 36 // Static Credentials group 37 accessKeyIDKey = `aws_access_key_id` // group required 38 secretAccessKey = `aws_secret_access_key` // group required 39 sessionTokenKey = `aws_session_token` // optional 40 41 // Assume Role Credentials group 42 roleArnKey = `role_arn` // group required 43 sourceProfileKey = `source_profile` // group required 44 credentialSourceKey = `credential_source` // group required (or source_profile) 45 externalIDKey = `external_id` // optional 46 mfaSerialKey = `mfa_serial` // optional 47 roleSessionNameKey = `role_session_name` // optional 48 roleDurationSecondsKey = "duration_seconds" // optional 49 50 // AWS Single Sign-On (AWS SSO) group 51 ssoSessionNameKey = "sso_session" 52 53 ssoRegionKey = "sso_region" 54 ssoStartURLKey = "sso_start_url" 55 56 ssoAccountIDKey = "sso_account_id" 57 ssoRoleNameKey = "sso_role_name" 58 59 // Additional Config fields 60 regionKey = `region` 61 62 // endpoint discovery group 63 enableEndpointDiscoveryKey = `endpoint_discovery_enabled` // optional 64 65 // External Credential process 66 credentialProcessKey = `credential_process` // optional 67 68 // Web Identity Token File 69 webIdentityTokenFileKey = `web_identity_token_file` // optional 70 71 // S3 ARN Region Usage 72 s3UseARNRegionKey = "s3_use_arn_region" 73 74 ec2MetadataServiceEndpointModeKey = "ec2_metadata_service_endpoint_mode" 75 76 ec2MetadataServiceEndpointKey = "ec2_metadata_service_endpoint" 77 78 // Use DualStack Endpoint Resolution 79 useDualStackEndpoint = "use_dualstack_endpoint" 80 81 // DefaultSharedConfigProfile is the default profile to be used when 82 // loading configuration from the config files if another profile name 83 // is not provided. 84 DefaultSharedConfigProfile = `default` 85 86 // S3 Disable Multi-Region AccessPoints 87 s3DisableMultiRegionAccessPointsKey = `s3_disable_multiregion_access_points` 88 89 useFIPSEndpointKey = "use_fips_endpoint" 90 91 defaultsModeKey = "defaults_mode" 92 93 // Retry options 94 retryMaxAttemptsKey = "max_attempts" 95 retryModeKey = "retry_mode" 96 97 caBundleKey = "ca_bundle" 98 ) 99 100 // defaultSharedConfigProfile allows for swapping the default profile for testing 101 var defaultSharedConfigProfile = DefaultSharedConfigProfile 102 103 // DefaultSharedCredentialsFilename returns the SDK's default file path 104 // for the shared credentials file. 105 // 106 // Builds the shared config file path based on the OS's platform. 107 // 108 // - Linux/Unix: $HOME/.aws/credentials 109 // - Windows: %USERPROFILE%\.aws\credentials 110 func DefaultSharedCredentialsFilename() string { 111 return filepath.Join(userHomeDir(), ".aws", "credentials") 112 } 113 114 // DefaultSharedConfigFilename returns the SDK's default file path for 115 // the shared config file. 116 // 117 // Builds the shared config file path based on the OS's platform. 118 // 119 // - Linux/Unix: $HOME/.aws/config 120 // - Windows: %USERPROFILE%\.aws\config 121 func DefaultSharedConfigFilename() string { 122 return filepath.Join(userHomeDir(), ".aws", "config") 123 } 124 125 // DefaultSharedConfigFiles is a slice of the default shared config files that 126 // the will be used in order to load the SharedConfig. 127 var DefaultSharedConfigFiles = []string{ 128 DefaultSharedConfigFilename(), 129 } 130 131 // DefaultSharedCredentialsFiles is a slice of the default shared credentials 132 // files that the will be used in order to load the SharedConfig. 133 var DefaultSharedCredentialsFiles = []string{ 134 DefaultSharedCredentialsFilename(), 135 } 136 137 // SSOSession provides the shared configuration parameters of the sso-session 138 // section. 139 type SSOSession struct { 140 Name string 141 SSORegion string 142 SSOStartURL string 143 } 144 145 func (s *SSOSession) setFromIniSection(section ini.Section) error { 146 updateString(&s.SSORegion, section, ssoRegionKey) 147 updateString(&s.SSOStartURL, section, ssoStartURLKey) 148 149 if s.SSORegion == "" || s.SSOStartURL == "" { 150 return fmt.Errorf( 151 "%v and %v are required parameters in sso-session section", 152 ssoRegionKey, ssoStartURLKey, 153 ) 154 } 155 156 return nil 157 } 158 159 // SharedConfig represents the configuration fields of the SDK config files. 160 type SharedConfig struct { 161 Profile string 162 163 // Credentials values from the config file. Both aws_access_key_id 164 // and aws_secret_access_key must be provided together in the same file 165 // to be considered valid. The values will be ignored if not a complete group. 166 // aws_session_token is an optional field that can be provided if both of the 167 // other two fields are also provided. 168 // 169 // aws_access_key_id 170 // aws_secret_access_key 171 // aws_session_token 172 Credentials aws.Credentials 173 174 CredentialSource string 175 CredentialProcess string 176 WebIdentityTokenFile string 177 178 // SSO session options 179 SSOSessionName string 180 SSOSession *SSOSession 181 182 // Legacy SSO session options 183 SSORegion string 184 SSOStartURL string 185 186 // SSO fields not used 187 SSOAccountID string 188 SSORoleName string 189 190 RoleARN string 191 ExternalID string 192 MFASerial string 193 RoleSessionName string 194 RoleDurationSeconds *time.Duration 195 196 SourceProfileName string 197 Source *SharedConfig 198 199 // Region is the region the SDK should use for looking up AWS service endpoints 200 // and signing requests. 201 // 202 // region = us-west-2 203 Region string 204 205 // EnableEndpointDiscovery can be enabled or disabled in the shared config 206 // by setting endpoint_discovery_enabled to true, or false respectively. 207 // 208 // endpoint_discovery_enabled = true 209 EnableEndpointDiscovery aws.EndpointDiscoveryEnableState 210 211 // Specifies if the S3 service should allow ARNs to direct the region 212 // the client's requests are sent to. 213 // 214 // s3_use_arn_region=true 215 S3UseARNRegion *bool 216 217 // Specifies the EC2 Instance Metadata Service default endpoint selection 218 // mode (IPv4 or IPv6) 219 // 220 // ec2_metadata_service_endpoint_mode=IPv6 221 EC2IMDSEndpointMode imds.EndpointModeState 222 223 // Specifies the EC2 Instance Metadata Service endpoint to use. If 224 // specified it overrides EC2IMDSEndpointMode. 225 // 226 // ec2_metadata_service_endpoint=http://fd00:ec2::254 227 EC2IMDSEndpoint string 228 229 // Specifies if the S3 service should disable support for Multi-Region 230 // access-points 231 // 232 // s3_disable_multiregion_access_points=true 233 S3DisableMultiRegionAccessPoints *bool 234 235 // Specifies that SDK clients must resolve a dual-stack endpoint for 236 // services. 237 // 238 // use_dualstack_endpoint=true 239 UseDualStackEndpoint aws.DualStackEndpointState 240 241 // Specifies that SDK clients must resolve a FIPS endpoint for 242 // services. 243 // 244 // use_fips_endpoint=true 245 UseFIPSEndpoint aws.FIPSEndpointState 246 247 // Specifies which defaults mode should be used by services. 248 // 249 // defaults_mode=standard 250 DefaultsMode aws.DefaultsMode 251 252 // Specifies the maximum number attempts an API client will call an 253 // operation that fails with a retryable error. 254 // 255 // max_attempts=3 256 RetryMaxAttempts int 257 258 // Specifies the retry model the API client will be created with. 259 // 260 // retry_mode=standard 261 RetryMode aws.RetryMode 262 263 // Sets the path to a custom Credentials Authority (CA) Bundle PEM file 264 // that the SDK will use instead of the system's root CA bundle. Only use 265 // this if you want to configure the SDK to use a custom set of CAs. 266 // 267 // Enabling this option will attempt to merge the Transport into the SDK's 268 // HTTP client. If the client's Transport is not a http.Transport an error 269 // will be returned. If the Transport's TLS config is set this option will 270 // cause the SDK to overwrite the Transport's TLS config's RootCAs value. 271 // 272 // Setting a custom HTTPClient in the aws.Config options will override this 273 // setting. To use this option and custom HTTP client, the HTTP client 274 // needs to be provided when creating the config. Not the service client. 275 // 276 // ca_bundle=$HOME/my_custom_ca_bundle 277 CustomCABundle string 278 } 279 280 func (c SharedConfig) getDefaultsMode(ctx context.Context) (value aws.DefaultsMode, ok bool, err error) { 281 if len(c.DefaultsMode) == 0 { 282 return "", false, nil 283 } 284 285 return c.DefaultsMode, true, nil 286 } 287 288 // GetRetryMaxAttempts returns the maximum number of attempts an API client 289 // created Retryer should attempt an operation call before failing. 290 func (c SharedConfig) GetRetryMaxAttempts(ctx context.Context) (value int, ok bool, err error) { 291 if c.RetryMaxAttempts == 0 { 292 return 0, false, nil 293 } 294 295 return c.RetryMaxAttempts, true, nil 296 } 297 298 // GetRetryMode returns the model the API client should create its Retryer in. 299 func (c SharedConfig) GetRetryMode(ctx context.Context) (value aws.RetryMode, ok bool, err error) { 300 if len(c.RetryMode) == 0 { 301 return "", false, nil 302 } 303 304 return c.RetryMode, true, nil 305 } 306 307 // GetS3UseARNRegion returns if the S3 service should allow ARNs to direct the region 308 // the client's requests are sent to. 309 func (c SharedConfig) GetS3UseARNRegion(ctx context.Context) (value, ok bool, err error) { 310 if c.S3UseARNRegion == nil { 311 return false, false, nil 312 } 313 314 return *c.S3UseARNRegion, true, nil 315 } 316 317 // GetEnableEndpointDiscovery returns if the enable_endpoint_discovery is set. 318 func (c SharedConfig) GetEnableEndpointDiscovery(ctx context.Context) (value aws.EndpointDiscoveryEnableState, ok bool, err error) { 319 if c.EnableEndpointDiscovery == aws.EndpointDiscoveryUnset { 320 return aws.EndpointDiscoveryUnset, false, nil 321 } 322 323 return c.EnableEndpointDiscovery, true, nil 324 } 325 326 // GetS3DisableMultiRegionAccessPoints returns if the S3 service should disable support for Multi-Region 327 // access-points. 328 func (c SharedConfig) GetS3DisableMultiRegionAccessPoints(ctx context.Context) (value, ok bool, err error) { 329 if c.S3DisableMultiRegionAccessPoints == nil { 330 return false, false, nil 331 } 332 333 return *c.S3DisableMultiRegionAccessPoints, true, nil 334 } 335 336 // GetRegion returns the region for the profile if a region is set. 337 func (c SharedConfig) getRegion(ctx context.Context) (string, bool, error) { 338 if len(c.Region) == 0 { 339 return "", false, nil 340 } 341 return c.Region, true, nil 342 } 343 344 // GetCredentialsProvider returns the credentials for a profile if they were set. 345 func (c SharedConfig) getCredentialsProvider() (aws.Credentials, bool, error) { 346 return c.Credentials, true, nil 347 } 348 349 // GetEC2IMDSEndpointMode implements a EC2IMDSEndpointMode option resolver interface. 350 func (c SharedConfig) GetEC2IMDSEndpointMode() (imds.EndpointModeState, bool, error) { 351 if c.EC2IMDSEndpointMode == imds.EndpointModeStateUnset { 352 return imds.EndpointModeStateUnset, false, nil 353 } 354 355 return c.EC2IMDSEndpointMode, true, nil 356 } 357 358 // GetEC2IMDSEndpoint implements a EC2IMDSEndpoint option resolver interface. 359 func (c SharedConfig) GetEC2IMDSEndpoint() (string, bool, error) { 360 if len(c.EC2IMDSEndpoint) == 0 { 361 return "", false, nil 362 } 363 364 return c.EC2IMDSEndpoint, true, nil 365 } 366 367 // GetUseDualStackEndpoint returns whether the service's dual-stack endpoint should be 368 // used for requests. 369 func (c SharedConfig) GetUseDualStackEndpoint(ctx context.Context) (value aws.DualStackEndpointState, found bool, err error) { 370 if c.UseDualStackEndpoint == aws.DualStackEndpointStateUnset { 371 return aws.DualStackEndpointStateUnset, false, nil 372 } 373 374 return c.UseDualStackEndpoint, true, nil 375 } 376 377 // GetUseFIPSEndpoint returns whether the service's FIPS endpoint should be 378 // used for requests. 379 func (c SharedConfig) GetUseFIPSEndpoint(ctx context.Context) (value aws.FIPSEndpointState, found bool, err error) { 380 if c.UseFIPSEndpoint == aws.FIPSEndpointStateUnset { 381 return aws.FIPSEndpointStateUnset, false, nil 382 } 383 384 return c.UseFIPSEndpoint, true, nil 385 } 386 387 // GetCustomCABundle returns the custom CA bundle's PEM bytes if the file was 388 func (c SharedConfig) getCustomCABundle(context.Context) (io.Reader, bool, error) { 389 if len(c.CustomCABundle) == 0 { 390 return nil, false, nil 391 } 392 393 b, err := ioutil.ReadFile(c.CustomCABundle) 394 if err != nil { 395 return nil, false, err 396 } 397 return bytes.NewReader(b), true, nil 398 } 399 400 // loadSharedConfigIgnoreNotExist is an alias for loadSharedConfig with the 401 // addition of ignoring when none of the files exist or when the profile 402 // is not found in any of the files. 403 func loadSharedConfigIgnoreNotExist(ctx context.Context, configs configs) (Config, error) { 404 cfg, err := loadSharedConfig(ctx, configs) 405 if err != nil { 406 if _, ok := err.(SharedConfigProfileNotExistError); ok { 407 return SharedConfig{}, nil 408 } 409 return nil, err 410 } 411 412 return cfg, nil 413 } 414 415 // loadSharedConfig uses the configs passed in to load the SharedConfig from file 416 // The file names and profile name are sourced from the configs. 417 // 418 // If profile name is not provided DefaultSharedConfigProfile (default) will 419 // be used. 420 // 421 // If shared config filenames are not provided DefaultSharedConfigFiles will 422 // be used. 423 // 424 // Config providers used: 425 // * sharedConfigProfileProvider 426 // * sharedConfigFilesProvider 427 func loadSharedConfig(ctx context.Context, configs configs) (Config, error) { 428 var profile string 429 var configFiles []string 430 var credentialsFiles []string 431 var ok bool 432 var err error 433 434 profile, ok, err = getSharedConfigProfile(ctx, configs) 435 if err != nil { 436 return nil, err 437 } 438 if !ok { 439 profile = defaultSharedConfigProfile 440 } 441 442 configFiles, ok, err = getSharedConfigFiles(ctx, configs) 443 if err != nil { 444 return nil, err 445 } 446 447 credentialsFiles, ok, err = getSharedCredentialsFiles(ctx, configs) 448 if err != nil { 449 return nil, err 450 } 451 452 // setup logger if log configuration warning is seti 453 var logger logging.Logger 454 logWarnings, found, err := getLogConfigurationWarnings(ctx, configs) 455 if err != nil { 456 return SharedConfig{}, err 457 } 458 if found && logWarnings { 459 logger, found, err = getLogger(ctx, configs) 460 if err != nil { 461 return SharedConfig{}, err 462 } 463 if !found { 464 logger = logging.NewStandardLogger(os.Stderr) 465 } 466 } 467 468 return LoadSharedConfigProfile(ctx, profile, 469 func(o *LoadSharedConfigOptions) { 470 o.Logger = logger 471 o.ConfigFiles = configFiles 472 o.CredentialsFiles = credentialsFiles 473 }, 474 ) 475 } 476 477 // LoadSharedConfigOptions struct contains optional values that can be used to load the config. 478 type LoadSharedConfigOptions struct { 479 480 // CredentialsFiles are the shared credentials files 481 CredentialsFiles []string 482 483 // ConfigFiles are the shared config files 484 ConfigFiles []string 485 486 // Logger is the logger used to log shared config behavior 487 Logger logging.Logger 488 } 489 490 // LoadSharedConfigProfile retrieves the configuration from the list of files 491 // using the profile provided. The order the files are listed will determine 492 // precedence. Values in subsequent files will overwrite values defined in 493 // earlier files. 494 // 495 // For example, given two files A and B. Both define credentials. If the order 496 // of the files are A then B, B's credential values will be used instead of A's. 497 // 498 // If config files are not set, SDK will default to using a file at location `.aws/config` if present. 499 // If credentials files are not set, SDK will default to using a file at location `.aws/credentials` if present. 500 // No default files are set, if files set to an empty slice. 501 // 502 // You can read more about shared config and credentials file location at 503 // https://docs.aws.amazon.com/credref/latest/refdocs/file-location.html#file-location 504 func LoadSharedConfigProfile(ctx context.Context, profile string, optFns ...func(*LoadSharedConfigOptions)) (SharedConfig, error) { 505 var option LoadSharedConfigOptions 506 for _, fn := range optFns { 507 fn(&option) 508 } 509 510 if option.ConfigFiles == nil { 511 option.ConfigFiles = DefaultSharedConfigFiles 512 } 513 514 if option.CredentialsFiles == nil { 515 option.CredentialsFiles = DefaultSharedCredentialsFiles 516 } 517 518 // load shared configuration sections from shared configuration INI options 519 configSections, err := loadIniFiles(option.ConfigFiles) 520 if err != nil { 521 return SharedConfig{}, err 522 } 523 524 // check for profile prefix and drop duplicates or invalid profiles 525 err = processConfigSections(ctx, &configSections, option.Logger) 526 if err != nil { 527 return SharedConfig{}, err 528 } 529 530 // load shared credentials sections from shared credentials INI options 531 credentialsSections, err := loadIniFiles(option.CredentialsFiles) 532 if err != nil { 533 return SharedConfig{}, err 534 } 535 536 // check for profile prefix and drop duplicates or invalid profiles 537 err = processCredentialsSections(ctx, &credentialsSections, option.Logger) 538 if err != nil { 539 return SharedConfig{}, err 540 } 541 542 err = mergeSections(&configSections, credentialsSections) 543 if err != nil { 544 return SharedConfig{}, err 545 } 546 547 cfg := SharedConfig{} 548 profiles := map[string]struct{}{} 549 if err = cfg.setFromIniSections(profiles, profile, configSections, option.Logger); err != nil { 550 return SharedConfig{}, err 551 } 552 553 return cfg, nil 554 } 555 556 func processConfigSections(ctx context.Context, sections *ini.Sections, logger logging.Logger) error { 557 skipSections := map[string]struct{}{} 558 559 for _, section := range sections.List() { 560 if _, ok := skipSections[section]; ok { 561 continue 562 } 563 564 // drop sections from config file that do not have expected prefixes. 565 switch { 566 case strings.HasPrefix(section, profilePrefix): 567 // Rename sections to remove "profile " prefixing to match with 568 // credentials file. If default is already present, it will be 569 // dropped. 570 newName, err := renameProfileSection(section, sections, logger) 571 if err != nil { 572 return fmt.Errorf("failed to rename profile section, %w", err) 573 } 574 skipSections[newName] = struct{}{} 575 576 case strings.HasPrefix(section, ssoSectionPrefix): 577 case strings.EqualFold(section, "default"): 578 default: 579 // drop this section, as invalid profile name 580 sections.DeleteSection(section) 581 582 if logger != nil { 583 logger.Logf(logging.Debug, "A profile defined with name `%v` is ignored. "+ 584 "For use within a shared configuration file, "+ 585 "a non-default profile must have `profile ` "+ 586 "prefixed to the profile name.", 587 section, 588 ) 589 } 590 } 591 } 592 return nil 593 } 594 595 func renameProfileSection(section string, sections *ini.Sections, logger logging.Logger) (string, error) { 596 v, ok := sections.GetSection(section) 597 if !ok { 598 return "", fmt.Errorf("error processing profiles within the shared configuration files") 599 } 600 601 // delete section with profile as prefix 602 sections.DeleteSection(section) 603 604 // set the value to non-prefixed name in sections. 605 section = strings.TrimPrefix(section, profilePrefix) 606 if sections.HasSection(section) { 607 oldSection, _ := sections.GetSection(section) 608 v.Logs = append(v.Logs, 609 fmt.Sprintf("A non-default profile not prefixed with `profile ` found in %s, "+ 610 "overriding non-default profile from %s", 611 v.SourceFile, oldSection.SourceFile)) 612 sections.DeleteSection(section) 613 } 614 615 // assign non-prefixed name to section 616 v.Name = section 617 sections.SetSection(section, v) 618 619 return section, nil 620 } 621 622 func processCredentialsSections(ctx context.Context, sections *ini.Sections, logger logging.Logger) error { 623 for _, section := range sections.List() { 624 // drop profiles with prefix for credential files 625 if strings.HasPrefix(section, profilePrefix) { 626 // drop this section, as invalid profile name 627 sections.DeleteSection(section) 628 629 if logger != nil { 630 logger.Logf(logging.Debug, 631 "The profile defined with name `%v` is ignored. A profile with the `profile ` prefix is invalid "+ 632 "for the shared credentials file.\n", 633 section, 634 ) 635 } 636 } 637 } 638 return nil 639 } 640 641 func loadIniFiles(filenames []string) (ini.Sections, error) { 642 mergedSections := ini.NewSections() 643 644 for _, filename := range filenames { 645 sections, err := ini.OpenFile(filename) 646 var v *ini.UnableToReadFile 647 if ok := errors.As(err, &v); ok { 648 // Skip files which can't be opened and read for whatever reason. 649 // We treat such files as empty, and do not fall back to other locations. 650 continue 651 } else if err != nil { 652 return ini.Sections{}, SharedConfigLoadError{Filename: filename, Err: err} 653 } 654 655 // mergeSections into mergedSections 656 err = mergeSections(&mergedSections, sections) 657 if err != nil { 658 return ini.Sections{}, SharedConfigLoadError{Filename: filename, Err: err} 659 } 660 } 661 662 return mergedSections, nil 663 } 664 665 // mergeSections merges source section properties into destination section properties 666 func mergeSections(dst *ini.Sections, src ini.Sections) error { 667 for _, sectionName := range src.List() { 668 srcSection, _ := src.GetSection(sectionName) 669 670 if (!srcSection.Has(accessKeyIDKey) && srcSection.Has(secretAccessKey)) || 671 (srcSection.Has(accessKeyIDKey) && !srcSection.Has(secretAccessKey)) { 672 srcSection.Errors = append(srcSection.Errors, 673 fmt.Errorf("partial credentials found for profile %v", sectionName)) 674 } 675 676 if !dst.HasSection(sectionName) { 677 dst.SetSection(sectionName, srcSection) 678 continue 679 } 680 681 // merge with destination srcSection 682 dstSection, _ := dst.GetSection(sectionName) 683 684 // errors should be overriden if any 685 dstSection.Errors = srcSection.Errors 686 687 // Access key id update 688 if srcSection.Has(accessKeyIDKey) && srcSection.Has(secretAccessKey) { 689 accessKey := srcSection.String(accessKeyIDKey) 690 secretKey := srcSection.String(secretAccessKey) 691 692 if dstSection.Has(accessKeyIDKey) { 693 dstSection.Logs = append(dstSection.Logs, newMergeKeyLogMessage(sectionName, accessKeyIDKey, 694 dstSection.SourceFile[accessKeyIDKey], srcSection.SourceFile[accessKeyIDKey])) 695 } 696 697 // update access key 698 v, err := ini.NewStringValue(accessKey) 699 if err != nil { 700 return fmt.Errorf("error merging access key, %w", err) 701 } 702 dstSection.UpdateValue(accessKeyIDKey, v) 703 704 // update secret key 705 v, err = ini.NewStringValue(secretKey) 706 if err != nil { 707 return fmt.Errorf("error merging secret key, %w", err) 708 } 709 dstSection.UpdateValue(secretAccessKey, v) 710 711 // update session token 712 if err = mergeStringKey(&srcSection, &dstSection, sectionName, sessionTokenKey); err != nil { 713 return err 714 } 715 716 // update source file to reflect where the static creds came from 717 dstSection.UpdateSourceFile(accessKeyIDKey, srcSection.SourceFile[accessKeyIDKey]) 718 dstSection.UpdateSourceFile(secretAccessKey, srcSection.SourceFile[secretAccessKey]) 719 } 720 721 stringKeys := []string{ 722 roleArnKey, 723 sourceProfileKey, 724 credentialSourceKey, 725 externalIDKey, 726 mfaSerialKey, 727 roleSessionNameKey, 728 regionKey, 729 enableEndpointDiscoveryKey, 730 credentialProcessKey, 731 webIdentityTokenFileKey, 732 s3UseARNRegionKey, 733 s3DisableMultiRegionAccessPointsKey, 734 ec2MetadataServiceEndpointModeKey, 735 ec2MetadataServiceEndpointKey, 736 useDualStackEndpoint, 737 useFIPSEndpointKey, 738 defaultsModeKey, 739 retryModeKey, 740 caBundleKey, 741 742 ssoSessionNameKey, 743 ssoAccountIDKey, 744 ssoRegionKey, 745 ssoRoleNameKey, 746 ssoStartURLKey, 747 } 748 for i := range stringKeys { 749 if err := mergeStringKey(&srcSection, &dstSection, sectionName, stringKeys[i]); err != nil { 750 return err 751 } 752 } 753 754 intKeys := []string{ 755 roleDurationSecondsKey, 756 retryMaxAttemptsKey, 757 } 758 for i := range intKeys { 759 if err := mergeIntKey(&srcSection, &dstSection, sectionName, intKeys[i]); err != nil { 760 return err 761 } 762 } 763 764 // set srcSection on dst srcSection 765 *dst = dst.SetSection(sectionName, dstSection) 766 } 767 768 return nil 769 } 770 771 func mergeStringKey(srcSection *ini.Section, dstSection *ini.Section, sectionName, key string) error { 772 if srcSection.Has(key) { 773 srcValue := srcSection.String(key) 774 val, err := ini.NewStringValue(srcValue) 775 if err != nil { 776 return fmt.Errorf("error merging %s, %w", key, err) 777 } 778 779 if dstSection.Has(key) { 780 dstSection.Logs = append(dstSection.Logs, newMergeKeyLogMessage(sectionName, key, 781 dstSection.SourceFile[key], srcSection.SourceFile[key])) 782 } 783 784 dstSection.UpdateValue(key, val) 785 dstSection.UpdateSourceFile(key, srcSection.SourceFile[key]) 786 } 787 return nil 788 } 789 790 func mergeIntKey(srcSection *ini.Section, dstSection *ini.Section, sectionName, key string) error { 791 if srcSection.Has(key) { 792 srcValue := srcSection.Int(key) 793 v, err := ini.NewIntValue(srcValue) 794 if err != nil { 795 return fmt.Errorf("error merging %s, %w", key, err) 796 } 797 798 if dstSection.Has(key) { 799 dstSection.Logs = append(dstSection.Logs, newMergeKeyLogMessage(sectionName, key, 800 dstSection.SourceFile[key], srcSection.SourceFile[key])) 801 802 } 803 804 dstSection.UpdateValue(key, v) 805 dstSection.UpdateSourceFile(key, srcSection.SourceFile[key]) 806 } 807 return nil 808 } 809 810 func newMergeKeyLogMessage(sectionName, key, dstSourceFile, srcSourceFile string) string { 811 return fmt.Sprintf("For profile: %v, overriding %v value, defined in %v "+ 812 "with a %v value found in a duplicate profile defined at file %v. \n", 813 sectionName, key, dstSourceFile, key, srcSourceFile) 814 } 815 816 // Returns an error if all of the files fail to load. If at least one file is 817 // successfully loaded and contains the profile, no error will be returned. 818 func (c *SharedConfig) setFromIniSections(profiles map[string]struct{}, profile string, 819 sections ini.Sections, logger logging.Logger) error { 820 c.Profile = profile 821 822 section, ok := sections.GetSection(profile) 823 if !ok { 824 return SharedConfigProfileNotExistError{ 825 Profile: profile, 826 } 827 } 828 829 // if logs are appended to the section, log them 830 if section.Logs != nil && logger != nil { 831 for _, log := range section.Logs { 832 logger.Logf(logging.Debug, log) 833 } 834 } 835 836 // set config from the provided INI section 837 err := c.setFromIniSection(profile, section) 838 if err != nil { 839 return fmt.Errorf("error fetching config from profile, %v, %w", profile, err) 840 } 841 842 if _, ok := profiles[profile]; ok { 843 // if this is the second instance of the profile the Assume Role 844 // options must be cleared because they are only valid for the 845 // first reference of a profile. The self linked instance of the 846 // profile only have credential provider options. 847 c.clearAssumeRoleOptions() 848 } else { 849 // First time a profile has been seen, It must either be a assume role 850 // credentials, or SSO. Assert if the credential type requires a role ARN, 851 // the ARN is also set, or validate that the SSO configuration is complete. 852 if err := c.validateCredentialsConfig(profile); err != nil { 853 return err 854 } 855 } 856 857 // if not top level profile and has credentials, return with credentials. 858 if len(profiles) != 0 && c.Credentials.HasKeys() { 859 return nil 860 } 861 862 profiles[profile] = struct{}{} 863 864 // validate no colliding credentials type are present 865 if err := c.validateCredentialType(); err != nil { 866 return err 867 } 868 869 // Link source profiles for assume roles 870 if len(c.SourceProfileName) != 0 { 871 // Linked profile via source_profile ignore credential provider 872 // options, the source profile must provide the credentials. 873 c.clearCredentialOptions() 874 875 srcCfg := &SharedConfig{} 876 err := srcCfg.setFromIniSections(profiles, c.SourceProfileName, sections, logger) 877 if err != nil { 878 // SourceProfileName that doesn't exist is an error in configuration. 879 if _, ok := err.(SharedConfigProfileNotExistError); ok { 880 err = SharedConfigAssumeRoleError{ 881 RoleARN: c.RoleARN, 882 Profile: c.SourceProfileName, 883 Err: err, 884 } 885 } 886 return err 887 } 888 889 if !srcCfg.hasCredentials() { 890 return SharedConfigAssumeRoleError{ 891 RoleARN: c.RoleARN, 892 Profile: c.SourceProfileName, 893 } 894 } 895 896 c.Source = srcCfg 897 } 898 899 // If the profile contains an SSO session parameter, the session MUST exist 900 // as a section in the config file. Load the SSO session using the name 901 // provided. If the session section is not found or incomplete an error 902 // will be returned. 903 if c.SSOSessionName != "" { 904 c.SSOSession, err = getSSOSession(c.SSOSessionName, sections, logger) 905 if err != nil { 906 return err 907 } 908 } 909 910 return nil 911 } 912 913 func getSSOSession(name string, sections ini.Sections, logger logging.Logger) (*SSOSession, error) { 914 section, ok := sections.GetSection(ssoSectionPrefix + strings.TrimSpace(name)) 915 if !ok { 916 return nil, fmt.Errorf("failed to find SSO session section, %v", name) 917 } 918 919 var ssoSession SSOSession 920 if err := ssoSession.setFromIniSection(section); err != nil { 921 return nil, fmt.Errorf("failed to load SSO session %v, %w", name, err) 922 } 923 ssoSession.Name = name 924 925 return &ssoSession, nil 926 } 927 928 // setFromIniSection loads the configuration from the profile section defined in 929 // the provided INI file. A SharedConfig pointer type value is used so that 930 // multiple config file loadings can be chained. 931 // 932 // Only loads complete logically grouped values, and will not set fields in cfg 933 // for incomplete grouped values in the config. Such as credentials. For example 934 // if a config file only includes aws_access_key_id but no aws_secret_access_key 935 // the aws_access_key_id will be ignored. 936 func (c *SharedConfig) setFromIniSection(profile string, section ini.Section) error { 937 if len(section.Name) == 0 { 938 sources := make([]string, 0) 939 for _, v := range section.SourceFile { 940 sources = append(sources, v) 941 } 942 943 return fmt.Errorf("parsing error : could not find profile section name after processing files: %v", sources) 944 } 945 946 if len(section.Errors) != 0 { 947 var errStatement string 948 for i, e := range section.Errors { 949 errStatement = fmt.Sprintf("%d, %v\n", i+1, e.Error()) 950 } 951 return fmt.Errorf("Error using profile: \n %v", errStatement) 952 } 953 954 // Assume Role 955 updateString(&c.RoleARN, section, roleArnKey) 956 updateString(&c.ExternalID, section, externalIDKey) 957 updateString(&c.MFASerial, section, mfaSerialKey) 958 updateString(&c.RoleSessionName, section, roleSessionNameKey) 959 updateString(&c.SourceProfileName, section, sourceProfileKey) 960 updateString(&c.CredentialSource, section, credentialSourceKey) 961 updateString(&c.Region, section, regionKey) 962 963 // AWS Single Sign-On (AWS SSO) 964 // SSO session options 965 updateString(&c.SSOSessionName, section, ssoSessionNameKey) 966 967 // Legacy SSO session options 968 updateString(&c.SSORegion, section, ssoRegionKey) 969 updateString(&c.SSOStartURL, section, ssoStartURLKey) 970 971 // SSO fields not used 972 updateString(&c.SSOAccountID, section, ssoAccountIDKey) 973 updateString(&c.SSORoleName, section, ssoRoleNameKey) 974 975 if section.Has(roleDurationSecondsKey) { 976 d := time.Duration(section.Int(roleDurationSecondsKey)) * time.Second 977 c.RoleDurationSeconds = &d 978 } 979 980 updateString(&c.CredentialProcess, section, credentialProcessKey) 981 updateString(&c.WebIdentityTokenFile, section, webIdentityTokenFileKey) 982 983 updateEndpointDiscoveryType(&c.EnableEndpointDiscovery, section, enableEndpointDiscoveryKey) 984 updateBoolPtr(&c.S3UseARNRegion, section, s3UseARNRegionKey) 985 updateBoolPtr(&c.S3DisableMultiRegionAccessPoints, section, s3DisableMultiRegionAccessPointsKey) 986 987 if err := updateEC2MetadataServiceEndpointMode(&c.EC2IMDSEndpointMode, section, ec2MetadataServiceEndpointModeKey); err != nil { 988 return fmt.Errorf("failed to load %s from shared config, %v", ec2MetadataServiceEndpointModeKey, err) 989 } 990 updateString(&c.EC2IMDSEndpoint, section, ec2MetadataServiceEndpointKey) 991 992 updateUseDualStackEndpoint(&c.UseDualStackEndpoint, section, useDualStackEndpoint) 993 updateUseFIPSEndpoint(&c.UseFIPSEndpoint, section, useFIPSEndpointKey) 994 995 if err := updateDefaultsMode(&c.DefaultsMode, section, defaultsModeKey); err != nil { 996 return fmt.Errorf("failed to load %s from shared config, %w", defaultsModeKey, err) 997 } 998 999 if err := updateInt(&c.RetryMaxAttempts, section, retryMaxAttemptsKey); err != nil { 1000 return fmt.Errorf("failed to load %s from shared config, %w", retryMaxAttemptsKey, err) 1001 } 1002 if err := updateRetryMode(&c.RetryMode, section, retryModeKey); err != nil { 1003 return fmt.Errorf("failed to load %s from shared config, %w", retryModeKey, err) 1004 } 1005 1006 updateString(&c.CustomCABundle, section, caBundleKey) 1007 1008 // Shared Credentials 1009 creds := aws.Credentials{ 1010 AccessKeyID: section.String(accessKeyIDKey), 1011 SecretAccessKey: section.String(secretAccessKey), 1012 SessionToken: section.String(sessionTokenKey), 1013 Source: fmt.Sprintf("SharedConfigCredentials: %s", section.SourceFile[accessKeyIDKey]), 1014 } 1015 1016 if creds.HasKeys() { 1017 c.Credentials = creds 1018 } 1019 1020 return nil 1021 } 1022 1023 func updateDefaultsMode(mode *aws.DefaultsMode, section ini.Section, key string) error { 1024 if !section.Has(key) { 1025 return nil 1026 } 1027 value := section.String(key) 1028 if ok := mode.SetFromString(value); !ok { 1029 return fmt.Errorf("invalid value: %s", value) 1030 } 1031 return nil 1032 } 1033 1034 func updateRetryMode(mode *aws.RetryMode, section ini.Section, key string) (err error) { 1035 if !section.Has(key) { 1036 return nil 1037 } 1038 value := section.String(key) 1039 if *mode, err = aws.ParseRetryMode(value); err != nil { 1040 return err 1041 } 1042 return nil 1043 } 1044 1045 func updateEC2MetadataServiceEndpointMode(endpointMode *imds.EndpointModeState, section ini.Section, key string) error { 1046 if !section.Has(key) { 1047 return nil 1048 } 1049 value := section.String(key) 1050 return endpointMode.SetFromString(value) 1051 } 1052 1053 func (c *SharedConfig) validateCredentialsConfig(profile string) error { 1054 if err := c.validateCredentialsRequireARN(profile); err != nil { 1055 return err 1056 } 1057 1058 return nil 1059 } 1060 1061 func (c *SharedConfig) validateCredentialsRequireARN(profile string) error { 1062 var credSource string 1063 1064 switch { 1065 case len(c.SourceProfileName) != 0: 1066 credSource = sourceProfileKey 1067 case len(c.CredentialSource) != 0: 1068 credSource = credentialSourceKey 1069 case len(c.WebIdentityTokenFile) != 0: 1070 credSource = webIdentityTokenFileKey 1071 } 1072 1073 if len(credSource) != 0 && len(c.RoleARN) == 0 { 1074 return CredentialRequiresARNError{ 1075 Type: credSource, 1076 Profile: profile, 1077 } 1078 } 1079 1080 return nil 1081 } 1082 1083 func (c *SharedConfig) validateCredentialType() error { 1084 // Only one or no credential type can be defined. 1085 if !oneOrNone( 1086 len(c.SourceProfileName) != 0, 1087 len(c.CredentialSource) != 0, 1088 len(c.CredentialProcess) != 0, 1089 len(c.WebIdentityTokenFile) != 0, 1090 ) { 1091 return fmt.Errorf("only one credential type may be specified per profile: source profile, credential source, credential process, web identity token, or sso") 1092 } 1093 1094 return nil 1095 } 1096 1097 func (c *SharedConfig) validateSSOConfiguration() error { 1098 if !c.hasSSOConfiguration() { 1099 return nil 1100 } 1101 1102 var missing []string 1103 1104 if len(c.SSORegion) == 0 { 1105 missing = append(missing, ssoRegionKey) 1106 } 1107 1108 if len(c.SSOStartURL) == 0 { 1109 missing = append(missing, ssoStartURLKey) 1110 } 1111 1112 if len(missing) > 0 { 1113 return fmt.Errorf("profile %q is configured to use SSO but is missing required configuration: %s", 1114 c.Profile, strings.Join(missing, ", ")) 1115 } 1116 1117 return nil 1118 } 1119 1120 func (c *SharedConfig) hasCredentials() bool { 1121 switch { 1122 case len(c.SourceProfileName) != 0: 1123 case len(c.CredentialSource) != 0: 1124 case len(c.CredentialProcess) != 0: 1125 case len(c.WebIdentityTokenFile) != 0: 1126 case c.hasSSOConfiguration(): 1127 case c.Credentials.HasKeys(): 1128 default: 1129 return false 1130 } 1131 1132 return true 1133 } 1134 1135 func (c *SharedConfig) hasSSOConfiguration() bool { 1136 switch { 1137 case len(c.SSOAccountID) != 0: 1138 case len(c.SSORegion) != 0: 1139 case len(c.SSORoleName) != 0: 1140 case len(c.SSOStartURL) != 0: 1141 default: 1142 return false 1143 } 1144 return true 1145 } 1146 1147 func (c *SharedConfig) clearAssumeRoleOptions() { 1148 c.RoleARN = "" 1149 c.ExternalID = "" 1150 c.MFASerial = "" 1151 c.RoleSessionName = "" 1152 c.SourceProfileName = "" 1153 } 1154 1155 func (c *SharedConfig) clearCredentialOptions() { 1156 c.CredentialSource = "" 1157 c.CredentialProcess = "" 1158 c.WebIdentityTokenFile = "" 1159 c.Credentials = aws.Credentials{} 1160 c.SSOAccountID = "" 1161 c.SSORegion = "" 1162 c.SSORoleName = "" 1163 c.SSOStartURL = "" 1164 } 1165 1166 // SharedConfigLoadError is an error for the shared config file failed to load. 1167 type SharedConfigLoadError struct { 1168 Filename string 1169 Err error 1170 } 1171 1172 // Unwrap returns the underlying error that caused the failure. 1173 func (e SharedConfigLoadError) Unwrap() error { 1174 return e.Err 1175 } 1176 1177 func (e SharedConfigLoadError) Error() string { 1178 return fmt.Sprintf("failed to load shared config file, %s, %v", e.Filename, e.Err) 1179 } 1180 1181 // SharedConfigProfileNotExistError is an error for the shared config when 1182 // the profile was not find in the config file. 1183 type SharedConfigProfileNotExistError struct { 1184 Filename []string 1185 Profile string 1186 Err error 1187 } 1188 1189 // Unwrap returns the underlying error that caused the failure. 1190 func (e SharedConfigProfileNotExistError) Unwrap() error { 1191 return e.Err 1192 } 1193 1194 func (e SharedConfigProfileNotExistError) Error() string { 1195 return fmt.Sprintf("failed to get shared config profile, %s", e.Profile) 1196 } 1197 1198 // SharedConfigAssumeRoleError is an error for the shared config when the 1199 // profile contains assume role information, but that information is invalid 1200 // or not complete. 1201 type SharedConfigAssumeRoleError struct { 1202 Profile string 1203 RoleARN string 1204 Err error 1205 } 1206 1207 // Unwrap returns the underlying error that caused the failure. 1208 func (e SharedConfigAssumeRoleError) Unwrap() error { 1209 return e.Err 1210 } 1211 1212 func (e SharedConfigAssumeRoleError) Error() string { 1213 return fmt.Sprintf("failed to load assume role %s, of profile %s, %v", 1214 e.RoleARN, e.Profile, e.Err) 1215 } 1216 1217 // CredentialRequiresARNError provides the error for shared config credentials 1218 // that are incorrectly configured in the shared config or credentials file. 1219 type CredentialRequiresARNError struct { 1220 // type of credentials that were configured. 1221 Type string 1222 1223 // Profile name the credentials were in. 1224 Profile string 1225 } 1226 1227 // Error satisfies the error interface. 1228 func (e CredentialRequiresARNError) Error() string { 1229 return fmt.Sprintf( 1230 "credential type %s requires role_arn, profile %s", 1231 e.Type, e.Profile, 1232 ) 1233 } 1234 1235 func userHomeDir() string { 1236 // Ignore errors since we only care about Windows and *nix. 1237 home, _ := os.UserHomeDir() 1238 1239 if len(home) > 0 { 1240 return home 1241 } 1242 1243 currUser, _ := user.Current() 1244 if currUser != nil { 1245 home = currUser.HomeDir 1246 } 1247 1248 return home 1249 } 1250 1251 func oneOrNone(bs ...bool) bool { 1252 var count int 1253 1254 for _, b := range bs { 1255 if b { 1256 count++ 1257 if count > 1 { 1258 return false 1259 } 1260 } 1261 } 1262 1263 return true 1264 } 1265 1266 // updateString will only update the dst with the value in the section key, key 1267 // is present in the section. 1268 func updateString(dst *string, section ini.Section, key string) { 1269 if !section.Has(key) { 1270 return 1271 } 1272 *dst = section.String(key) 1273 } 1274 1275 // updateInt will only update the dst with the value in the section key, key 1276 // is present in the section. 1277 // 1278 // Down casts the INI integer value from a int64 to an int, which could be 1279 // different bit size depending on platform. 1280 func updateInt(dst *int, section ini.Section, key string) error { 1281 if !section.Has(key) { 1282 return nil 1283 } 1284 if vt, _ := section.ValueType(key); vt != ini.IntegerType { 1285 return fmt.Errorf("invalid value %s=%s, expect integer", 1286 key, section.String(key)) 1287 1288 } 1289 *dst = int(section.Int(key)) 1290 return nil 1291 } 1292 1293 // updateBool will only update the dst with the value in the section key, key 1294 // is present in the section. 1295 func updateBool(dst *bool, section ini.Section, key string) { 1296 if !section.Has(key) { 1297 return 1298 } 1299 *dst = section.Bool(key) 1300 } 1301 1302 // updateBoolPtr will only update the dst with the value in the section key, 1303 // key is present in the section. 1304 func updateBoolPtr(dst **bool, section ini.Section, key string) { 1305 if !section.Has(key) { 1306 return 1307 } 1308 *dst = new(bool) 1309 **dst = section.Bool(key) 1310 } 1311 1312 // updateEndpointDiscoveryType will only update the dst with the value in the section, if 1313 // a valid key and corresponding EndpointDiscoveryType is found. 1314 func updateEndpointDiscoveryType(dst *aws.EndpointDiscoveryEnableState, section ini.Section, key string) { 1315 if !section.Has(key) { 1316 return 1317 } 1318 1319 value := section.String(key) 1320 if len(value) == 0 { 1321 return 1322 } 1323 1324 switch { 1325 case strings.EqualFold(value, endpointDiscoveryDisabled): 1326 *dst = aws.EndpointDiscoveryDisabled 1327 case strings.EqualFold(value, endpointDiscoveryEnabled): 1328 *dst = aws.EndpointDiscoveryEnabled 1329 case strings.EqualFold(value, endpointDiscoveryAuto): 1330 *dst = aws.EndpointDiscoveryAuto 1331 } 1332 } 1333 1334 // updateEndpointDiscoveryType will only update the dst with the value in the section, if 1335 // a valid key and corresponding EndpointDiscoveryType is found. 1336 func updateUseDualStackEndpoint(dst *aws.DualStackEndpointState, section ini.Section, key string) { 1337 if !section.Has(key) { 1338 return 1339 } 1340 1341 if section.Bool(key) { 1342 *dst = aws.DualStackEndpointStateEnabled 1343 } else { 1344 *dst = aws.DualStackEndpointStateDisabled 1345 } 1346 1347 return 1348 } 1349 1350 // updateEndpointDiscoveryType will only update the dst with the value in the section, if 1351 // a valid key and corresponding EndpointDiscoveryType is found. 1352 func updateUseFIPSEndpoint(dst *aws.FIPSEndpointState, section ini.Section, key string) { 1353 if !section.Has(key) { 1354 return 1355 } 1356 1357 if section.Bool(key) { 1358 *dst = aws.FIPSEndpointStateEnabled 1359 } else { 1360 *dst = aws.FIPSEndpointStateDisabled 1361 } 1362 1363 return 1364 }