github.com/aavshr/aws-sdk-go@v1.41.3/aws/session/shared_config.go (about) 1 package session 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 8 "github.com/aavshr/aws-sdk-go/aws/awserr" 9 "github.com/aavshr/aws-sdk-go/aws/credentials" 10 "github.com/aavshr/aws-sdk-go/aws/endpoints" 11 "github.com/aavshr/aws-sdk-go/internal/ini" 12 ) 13 14 const ( 15 // Static Credentials group 16 accessKeyIDKey = `aws_access_key_id` // group required 17 secretAccessKey = `aws_secret_access_key` // group required 18 sessionTokenKey = `aws_session_token` // optional 19 20 // Assume Role Credentials group 21 roleArnKey = `role_arn` // group required 22 sourceProfileKey = `source_profile` // group required (or credential_source) 23 credentialSourceKey = `credential_source` // group required (or source_profile) 24 externalIDKey = `external_id` // optional 25 mfaSerialKey = `mfa_serial` // optional 26 roleSessionNameKey = `role_session_name` // optional 27 roleDurationSecondsKey = "duration_seconds" // optional 28 29 // AWS Single Sign-On (AWS SSO) group 30 ssoAccountIDKey = "sso_account_id" 31 ssoRegionKey = "sso_region" 32 ssoRoleNameKey = "sso_role_name" 33 ssoStartURL = "sso_start_url" 34 35 // CSM options 36 csmEnabledKey = `csm_enabled` 37 csmHostKey = `csm_host` 38 csmPortKey = `csm_port` 39 csmClientIDKey = `csm_client_id` 40 41 // Additional Config fields 42 regionKey = `region` 43 44 // custom CA Bundle filename 45 customCABundleKey = `ca_bundle` 46 47 // endpoint discovery group 48 enableEndpointDiscoveryKey = `endpoint_discovery_enabled` // optional 49 50 // External Credential Process 51 credentialProcessKey = `credential_process` // optional 52 53 // Web Identity Token File 54 webIdentityTokenFileKey = `web_identity_token_file` // optional 55 56 // Additional config fields for regional or legacy endpoints 57 stsRegionalEndpointSharedKey = `sts_regional_endpoints` 58 59 // Additional config fields for regional or legacy endpoints 60 s3UsEast1RegionalSharedKey = `s3_us_east_1_regional_endpoint` 61 62 // DefaultSharedConfigProfile is the default profile to be used when 63 // loading configuration from the config files if another profile name 64 // is not provided. 65 DefaultSharedConfigProfile = `default` 66 67 // S3 ARN Region Usage 68 s3UseARNRegionKey = "s3_use_arn_region" 69 70 // EC2 IMDS Endpoint Mode 71 ec2MetadataServiceEndpointModeKey = "ec2_metadata_service_endpoint_mode" 72 73 // EC2 IMDS Endpoint 74 ec2MetadataServiceEndpointKey = "ec2_metadata_service_endpoint" 75 ) 76 77 // sharedConfig represents the configuration fields of the SDK config files. 78 type sharedConfig struct { 79 Profile string 80 81 // Credentials values from the config file. Both aws_access_key_id and 82 // aws_secret_access_key must be provided together in the same file to be 83 // considered valid. The values will be ignored if not a complete group. 84 // aws_session_token is an optional field that can be provided if both of 85 // the other two fields are also provided. 86 // 87 // aws_access_key_id 88 // aws_secret_access_key 89 // aws_session_token 90 Creds credentials.Value 91 92 CredentialSource string 93 CredentialProcess string 94 WebIdentityTokenFile string 95 96 SSOAccountID string 97 SSORegion string 98 SSORoleName string 99 SSOStartURL string 100 101 RoleARN string 102 RoleSessionName string 103 ExternalID string 104 MFASerial string 105 AssumeRoleDuration *time.Duration 106 107 SourceProfileName string 108 SourceProfile *sharedConfig 109 110 // Region is the region the SDK should use for looking up AWS service 111 // endpoints and signing requests. 112 // 113 // region 114 Region string 115 116 // CustomCABundle is the file path to a PEM file the SDK will read and 117 // use to configure the HTTP transport with additional CA certs that are 118 // not present in the platforms default CA store. 119 // 120 // This value will be ignored if the file does not exist. 121 // 122 // ca_bundle 123 CustomCABundle string 124 125 // EnableEndpointDiscovery can be enabled in the shared config by setting 126 // endpoint_discovery_enabled to true 127 // 128 // endpoint_discovery_enabled = true 129 EnableEndpointDiscovery *bool 130 131 // CSM Options 132 CSMEnabled *bool 133 CSMHost string 134 CSMPort string 135 CSMClientID string 136 137 // Specifies the Regional Endpoint flag for the SDK to resolve the endpoint for a service 138 // 139 // sts_regional_endpoints = regional 140 // This can take value as `LegacySTSEndpoint` or `RegionalSTSEndpoint` 141 STSRegionalEndpoint endpoints.STSRegionalEndpoint 142 143 // Specifies the Regional Endpoint flag for the SDK to resolve the endpoint for a service 144 // 145 // s3_us_east_1_regional_endpoint = regional 146 // This can take value as `LegacyS3UsEast1Endpoint` or `RegionalS3UsEast1Endpoint` 147 S3UsEast1RegionalEndpoint endpoints.S3UsEast1RegionalEndpoint 148 149 // Specifies if the S3 service should allow ARNs to direct the region 150 // the client's requests are sent to. 151 // 152 // s3_use_arn_region=true 153 S3UseARNRegion bool 154 155 // Specifies the EC2 Instance Metadata Service default endpoint selection mode (IPv4 or IPv6) 156 // 157 // ec2_metadata_service_endpoint_mode=IPv6 158 EC2IMDSEndpointMode endpoints.EC2IMDSEndpointModeState 159 160 // Specifies the EC2 Instance Metadata Service endpoint to use. If specified it overrides EC2IMDSEndpointMode. 161 // 162 // ec2_metadata_service_endpoint=http://fd00:ec2::254 163 EC2IMDSEndpoint string 164 } 165 166 type sharedConfigFile struct { 167 Filename string 168 IniData ini.Sections 169 } 170 171 // loadSharedConfig retrieves the configuration from the list of files using 172 // the profile provided. The order the files are listed will determine 173 // precedence. Values in subsequent files will overwrite values defined in 174 // earlier files. 175 // 176 // For example, given two files A and B. Both define credentials. If the order 177 // of the files are A then B, B's credential values will be used instead of 178 // A's. 179 // 180 // See sharedConfig.setFromFile for information how the config files 181 // will be loaded. 182 func loadSharedConfig(profile string, filenames []string, exOpts bool) (sharedConfig, error) { 183 if len(profile) == 0 { 184 profile = DefaultSharedConfigProfile 185 } 186 187 files, err := loadSharedConfigIniFiles(filenames) 188 if err != nil { 189 return sharedConfig{}, err 190 } 191 192 cfg := sharedConfig{} 193 profiles := map[string]struct{}{} 194 if err = cfg.setFromIniFiles(profiles, profile, files, exOpts); err != nil { 195 return sharedConfig{}, err 196 } 197 198 return cfg, nil 199 } 200 201 func loadSharedConfigIniFiles(filenames []string) ([]sharedConfigFile, error) { 202 files := make([]sharedConfigFile, 0, len(filenames)) 203 204 for _, filename := range filenames { 205 sections, err := ini.OpenFile(filename) 206 if aerr, ok := err.(awserr.Error); ok && aerr.Code() == ini.ErrCodeUnableToReadFile { 207 // Skip files which can't be opened and read for whatever reason 208 continue 209 } else if err != nil { 210 return nil, SharedConfigLoadError{Filename: filename, Err: err} 211 } 212 213 files = append(files, sharedConfigFile{ 214 Filename: filename, IniData: sections, 215 }) 216 } 217 218 return files, nil 219 } 220 221 func (cfg *sharedConfig) setFromIniFiles(profiles map[string]struct{}, profile string, files []sharedConfigFile, exOpts bool) error { 222 cfg.Profile = profile 223 224 // Trim files from the list that don't exist. 225 var skippedFiles int 226 var profileNotFoundErr error 227 for _, f := range files { 228 if err := cfg.setFromIniFile(profile, f, exOpts); err != nil { 229 if _, ok := err.(SharedConfigProfileNotExistsError); ok { 230 // Ignore profiles not defined in individual files. 231 profileNotFoundErr = err 232 skippedFiles++ 233 continue 234 } 235 return err 236 } 237 } 238 if skippedFiles == len(files) { 239 // If all files were skipped because the profile is not found, return 240 // the original profile not found error. 241 return profileNotFoundErr 242 } 243 244 if _, ok := profiles[profile]; ok { 245 // if this is the second instance of the profile the Assume Role 246 // options must be cleared because they are only valid for the 247 // first reference of a profile. The self linked instance of the 248 // profile only have credential provider options. 249 cfg.clearAssumeRoleOptions() 250 } else { 251 // First time a profile has been seen, It must either be a assume role 252 // credentials, or SSO. Assert if the credential type requires a role ARN, 253 // the ARN is also set, or validate that the SSO configuration is complete. 254 if err := cfg.validateCredentialsConfig(profile); err != nil { 255 return err 256 } 257 } 258 profiles[profile] = struct{}{} 259 260 if err := cfg.validateCredentialType(); err != nil { 261 return err 262 } 263 264 // Link source profiles for assume roles 265 if len(cfg.SourceProfileName) != 0 { 266 // Linked profile via source_profile ignore credential provider 267 // options, the source profile must provide the credentials. 268 cfg.clearCredentialOptions() 269 270 srcCfg := &sharedConfig{} 271 err := srcCfg.setFromIniFiles(profiles, cfg.SourceProfileName, files, exOpts) 272 if err != nil { 273 // SourceProfile that doesn't exist is an error in configuration. 274 if _, ok := err.(SharedConfigProfileNotExistsError); ok { 275 err = SharedConfigAssumeRoleError{ 276 RoleARN: cfg.RoleARN, 277 SourceProfile: cfg.SourceProfileName, 278 } 279 } 280 return err 281 } 282 283 if !srcCfg.hasCredentials() { 284 return SharedConfigAssumeRoleError{ 285 RoleARN: cfg.RoleARN, 286 SourceProfile: cfg.SourceProfileName, 287 } 288 } 289 290 cfg.SourceProfile = srcCfg 291 } 292 293 return nil 294 } 295 296 // setFromFile loads the configuration from the file using the profile 297 // provided. A sharedConfig pointer type value is used so that multiple config 298 // file loadings can be chained. 299 // 300 // Only loads complete logically grouped values, and will not set fields in cfg 301 // for incomplete grouped values in the config. Such as credentials. For 302 // example if a config file only includes aws_access_key_id but no 303 // aws_secret_access_key the aws_access_key_id will be ignored. 304 func (cfg *sharedConfig) setFromIniFile(profile string, file sharedConfigFile, exOpts bool) error { 305 section, ok := file.IniData.GetSection(profile) 306 if !ok { 307 // Fallback to to alternate profile name: profile <name> 308 section, ok = file.IniData.GetSection(fmt.Sprintf("profile %s", profile)) 309 if !ok { 310 return SharedConfigProfileNotExistsError{Profile: profile, Err: nil} 311 } 312 } 313 314 if exOpts { 315 // Assume Role Parameters 316 updateString(&cfg.RoleARN, section, roleArnKey) 317 updateString(&cfg.ExternalID, section, externalIDKey) 318 updateString(&cfg.MFASerial, section, mfaSerialKey) 319 updateString(&cfg.RoleSessionName, section, roleSessionNameKey) 320 updateString(&cfg.SourceProfileName, section, sourceProfileKey) 321 updateString(&cfg.CredentialSource, section, credentialSourceKey) 322 updateString(&cfg.Region, section, regionKey) 323 updateString(&cfg.CustomCABundle, section, customCABundleKey) 324 325 if section.Has(roleDurationSecondsKey) { 326 d := time.Duration(section.Int(roleDurationSecondsKey)) * time.Second 327 cfg.AssumeRoleDuration = &d 328 } 329 330 if v := section.String(stsRegionalEndpointSharedKey); len(v) != 0 { 331 sre, err := endpoints.GetSTSRegionalEndpoint(v) 332 if err != nil { 333 return fmt.Errorf("failed to load %s from shared config, %s, %v", 334 stsRegionalEndpointSharedKey, file.Filename, err) 335 } 336 cfg.STSRegionalEndpoint = sre 337 } 338 339 if v := section.String(s3UsEast1RegionalSharedKey); len(v) != 0 { 340 sre, err := endpoints.GetS3UsEast1RegionalEndpoint(v) 341 if err != nil { 342 return fmt.Errorf("failed to load %s from shared config, %s, %v", 343 s3UsEast1RegionalSharedKey, file.Filename, err) 344 } 345 cfg.S3UsEast1RegionalEndpoint = sre 346 } 347 348 // AWS Single Sign-On (AWS SSO) 349 updateString(&cfg.SSOAccountID, section, ssoAccountIDKey) 350 updateString(&cfg.SSORegion, section, ssoRegionKey) 351 updateString(&cfg.SSORoleName, section, ssoRoleNameKey) 352 updateString(&cfg.SSOStartURL, section, ssoStartURL) 353 354 if err := updateEC2MetadataServiceEndpointMode(&cfg.EC2IMDSEndpointMode, section, ec2MetadataServiceEndpointModeKey); err != nil { 355 return fmt.Errorf("failed to load %s from shared config, %s, %v", 356 ec2MetadataServiceEndpointModeKey, file.Filename, err) 357 } 358 updateString(&cfg.EC2IMDSEndpoint, section, ec2MetadataServiceEndpointKey) 359 } 360 361 updateString(&cfg.CredentialProcess, section, credentialProcessKey) 362 updateString(&cfg.WebIdentityTokenFile, section, webIdentityTokenFileKey) 363 364 // Shared Credentials 365 creds := credentials.Value{ 366 AccessKeyID: section.String(accessKeyIDKey), 367 SecretAccessKey: section.String(secretAccessKey), 368 SessionToken: section.String(sessionTokenKey), 369 ProviderName: fmt.Sprintf("SharedConfigCredentials: %s", file.Filename), 370 } 371 if creds.HasKeys() { 372 cfg.Creds = creds 373 } 374 375 // Endpoint discovery 376 updateBoolPtr(&cfg.EnableEndpointDiscovery, section, enableEndpointDiscoveryKey) 377 378 // CSM options 379 updateBoolPtr(&cfg.CSMEnabled, section, csmEnabledKey) 380 updateString(&cfg.CSMHost, section, csmHostKey) 381 updateString(&cfg.CSMPort, section, csmPortKey) 382 updateString(&cfg.CSMClientID, section, csmClientIDKey) 383 384 updateBool(&cfg.S3UseARNRegion, section, s3UseARNRegionKey) 385 386 return nil 387 } 388 389 func updateEC2MetadataServiceEndpointMode(endpointMode *endpoints.EC2IMDSEndpointModeState, section ini.Section, key string) error { 390 if !section.Has(key) { 391 return nil 392 } 393 value := section.String(key) 394 return endpointMode.SetFromString(value) 395 } 396 397 func (cfg *sharedConfig) validateCredentialsConfig(profile string) error { 398 if err := cfg.validateCredentialsRequireARN(profile); err != nil { 399 return err 400 } 401 402 return nil 403 } 404 405 func (cfg *sharedConfig) validateCredentialsRequireARN(profile string) error { 406 var credSource string 407 408 switch { 409 case len(cfg.SourceProfileName) != 0: 410 credSource = sourceProfileKey 411 case len(cfg.CredentialSource) != 0: 412 credSource = credentialSourceKey 413 case len(cfg.WebIdentityTokenFile) != 0: 414 credSource = webIdentityTokenFileKey 415 } 416 417 if len(credSource) != 0 && len(cfg.RoleARN) == 0 { 418 return CredentialRequiresARNError{ 419 Type: credSource, 420 Profile: profile, 421 } 422 } 423 424 return nil 425 } 426 427 func (cfg *sharedConfig) validateCredentialType() error { 428 // Only one or no credential type can be defined. 429 if !oneOrNone( 430 len(cfg.SourceProfileName) != 0, 431 len(cfg.CredentialSource) != 0, 432 len(cfg.CredentialProcess) != 0, 433 len(cfg.WebIdentityTokenFile) != 0, 434 ) { 435 return ErrSharedConfigSourceCollision 436 } 437 438 return nil 439 } 440 441 func (cfg *sharedConfig) validateSSOConfiguration() error { 442 if !cfg.hasSSOConfiguration() { 443 return nil 444 } 445 446 var missing []string 447 if len(cfg.SSOAccountID) == 0 { 448 missing = append(missing, ssoAccountIDKey) 449 } 450 451 if len(cfg.SSORegion) == 0 { 452 missing = append(missing, ssoRegionKey) 453 } 454 455 if len(cfg.SSORoleName) == 0 { 456 missing = append(missing, ssoRoleNameKey) 457 } 458 459 if len(cfg.SSOStartURL) == 0 { 460 missing = append(missing, ssoStartURL) 461 } 462 463 if len(missing) > 0 { 464 return fmt.Errorf("profile %q is configured to use SSO but is missing required configuration: %s", 465 cfg.Profile, strings.Join(missing, ", ")) 466 } 467 468 return nil 469 } 470 471 func (cfg *sharedConfig) hasCredentials() bool { 472 switch { 473 case len(cfg.SourceProfileName) != 0: 474 case len(cfg.CredentialSource) != 0: 475 case len(cfg.CredentialProcess) != 0: 476 case len(cfg.WebIdentityTokenFile) != 0: 477 case cfg.hasSSOConfiguration(): 478 case cfg.Creds.HasKeys(): 479 default: 480 return false 481 } 482 483 return true 484 } 485 486 func (cfg *sharedConfig) clearCredentialOptions() { 487 cfg.CredentialSource = "" 488 cfg.CredentialProcess = "" 489 cfg.WebIdentityTokenFile = "" 490 cfg.Creds = credentials.Value{} 491 cfg.SSOAccountID = "" 492 cfg.SSORegion = "" 493 cfg.SSORoleName = "" 494 cfg.SSOStartURL = "" 495 } 496 497 func (cfg *sharedConfig) clearAssumeRoleOptions() { 498 cfg.RoleARN = "" 499 cfg.ExternalID = "" 500 cfg.MFASerial = "" 501 cfg.RoleSessionName = "" 502 cfg.SourceProfileName = "" 503 } 504 505 func (cfg *sharedConfig) hasSSOConfiguration() bool { 506 switch { 507 case len(cfg.SSOAccountID) != 0: 508 case len(cfg.SSORegion) != 0: 509 case len(cfg.SSORoleName) != 0: 510 case len(cfg.SSOStartURL) != 0: 511 default: 512 return false 513 } 514 return true 515 } 516 517 func oneOrNone(bs ...bool) bool { 518 var count int 519 520 for _, b := range bs { 521 if b { 522 count++ 523 if count > 1 { 524 return false 525 } 526 } 527 } 528 529 return true 530 } 531 532 // updateString will only update the dst with the value in the section key, key 533 // is present in the section. 534 func updateString(dst *string, section ini.Section, key string) { 535 if !section.Has(key) { 536 return 537 } 538 *dst = section.String(key) 539 } 540 541 // updateBool will only update the dst with the value in the section key, key 542 // is present in the section. 543 func updateBool(dst *bool, section ini.Section, key string) { 544 if !section.Has(key) { 545 return 546 } 547 *dst = section.Bool(key) 548 } 549 550 // updateBoolPtr will only update the dst with the value in the section key, 551 // key is present in the section. 552 func updateBoolPtr(dst **bool, section ini.Section, key string) { 553 if !section.Has(key) { 554 return 555 } 556 *dst = new(bool) 557 **dst = section.Bool(key) 558 } 559 560 // SharedConfigLoadError is an error for the shared config file failed to load. 561 type SharedConfigLoadError struct { 562 Filename string 563 Err error 564 } 565 566 // Code is the short id of the error. 567 func (e SharedConfigLoadError) Code() string { 568 return "SharedConfigLoadError" 569 } 570 571 // Message is the description of the error 572 func (e SharedConfigLoadError) Message() string { 573 return fmt.Sprintf("failed to load config file, %s", e.Filename) 574 } 575 576 // OrigErr is the underlying error that caused the failure. 577 func (e SharedConfigLoadError) OrigErr() error { 578 return e.Err 579 } 580 581 // Error satisfies the error interface. 582 func (e SharedConfigLoadError) Error() string { 583 return awserr.SprintError(e.Code(), e.Message(), "", e.Err) 584 } 585 586 // SharedConfigProfileNotExistsError is an error for the shared config when 587 // the profile was not find in the config file. 588 type SharedConfigProfileNotExistsError struct { 589 Profile string 590 Err error 591 } 592 593 // Code is the short id of the error. 594 func (e SharedConfigProfileNotExistsError) Code() string { 595 return "SharedConfigProfileNotExistsError" 596 } 597 598 // Message is the description of the error 599 func (e SharedConfigProfileNotExistsError) Message() string { 600 return fmt.Sprintf("failed to get profile, %s", e.Profile) 601 } 602 603 // OrigErr is the underlying error that caused the failure. 604 func (e SharedConfigProfileNotExistsError) OrigErr() error { 605 return e.Err 606 } 607 608 // Error satisfies the error interface. 609 func (e SharedConfigProfileNotExistsError) Error() string { 610 return awserr.SprintError(e.Code(), e.Message(), "", e.Err) 611 } 612 613 // SharedConfigAssumeRoleError is an error for the shared config when the 614 // profile contains assume role information, but that information is invalid 615 // or not complete. 616 type SharedConfigAssumeRoleError struct { 617 RoleARN string 618 SourceProfile string 619 } 620 621 // Code is the short id of the error. 622 func (e SharedConfigAssumeRoleError) Code() string { 623 return "SharedConfigAssumeRoleError" 624 } 625 626 // Message is the description of the error 627 func (e SharedConfigAssumeRoleError) Message() string { 628 return fmt.Sprintf( 629 "failed to load assume role for %s, source profile %s has no shared credentials", 630 e.RoleARN, e.SourceProfile, 631 ) 632 } 633 634 // OrigErr is the underlying error that caused the failure. 635 func (e SharedConfigAssumeRoleError) OrigErr() error { 636 return nil 637 } 638 639 // Error satisfies the error interface. 640 func (e SharedConfigAssumeRoleError) Error() string { 641 return awserr.SprintError(e.Code(), e.Message(), "", nil) 642 } 643 644 // CredentialRequiresARNError provides the error for shared config credentials 645 // that are incorrectly configured in the shared config or credentials file. 646 type CredentialRequiresARNError struct { 647 // type of credentials that were configured. 648 Type string 649 650 // Profile name the credentials were in. 651 Profile string 652 } 653 654 // Code is the short id of the error. 655 func (e CredentialRequiresARNError) Code() string { 656 return "CredentialRequiresARNError" 657 } 658 659 // Message is the description of the error 660 func (e CredentialRequiresARNError) Message() string { 661 return fmt.Sprintf( 662 "credential type %s requires role_arn, profile %s", 663 e.Type, e.Profile, 664 ) 665 } 666 667 // OrigErr is the underlying error that caused the failure. 668 func (e CredentialRequiresARNError) OrigErr() error { 669 return nil 670 } 671 672 // Error satisfies the error interface. 673 func (e CredentialRequiresARNError) Error() string { 674 return awserr.SprintError(e.Code(), e.Message(), "", nil) 675 }