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