1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384 |
- package config
- import (
- "bytes"
- "context"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
- "time"
- "github.com/aws/aws-sdk-go-v2/aws"
- "github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
- "github.com/aws/aws-sdk-go-v2/internal/ini"
- "github.com/aws/aws-sdk-go-v2/internal/shareddefaults"
- "github.com/aws/smithy-go/logging"
- )
- const (
- // Prefix to use for filtering profiles. The profile prefix should only
- // exist in the shared config file, not the credentials file.
- profilePrefix = `profile `
- // Prefix to be used for SSO sections. These are supposed to only exist in
- // the shared config file, not the credentials file.
- ssoSectionPrefix = `sso-session `
- // string equivalent for boolean
- endpointDiscoveryDisabled = `false`
- endpointDiscoveryEnabled = `true`
- endpointDiscoveryAuto = `auto`
- // Static Credentials group
- accessKeyIDKey = `aws_access_key_id` // group required
- secretAccessKey = `aws_secret_access_key` // group required
- sessionTokenKey = `aws_session_token` // optional
- // Assume Role Credentials group
- roleArnKey = `role_arn` // group required
- sourceProfileKey = `source_profile` // group required
- credentialSourceKey = `credential_source` // group required (or source_profile)
- externalIDKey = `external_id` // optional
- mfaSerialKey = `mfa_serial` // optional
- roleSessionNameKey = `role_session_name` // optional
- roleDurationSecondsKey = "duration_seconds" // optional
- // AWS Single Sign-On (AWS SSO) group
- ssoSessionNameKey = "sso_session"
- ssoRegionKey = "sso_region"
- ssoStartURLKey = "sso_start_url"
- ssoAccountIDKey = "sso_account_id"
- ssoRoleNameKey = "sso_role_name"
- // Additional Config fields
- regionKey = `region`
- // endpoint discovery group
- enableEndpointDiscoveryKey = `endpoint_discovery_enabled` // optional
- // External Credential process
- credentialProcessKey = `credential_process` // optional
- // Web Identity Token File
- webIdentityTokenFileKey = `web_identity_token_file` // optional
- // S3 ARN Region Usage
- s3UseARNRegionKey = "s3_use_arn_region"
- ec2MetadataServiceEndpointModeKey = "ec2_metadata_service_endpoint_mode"
- ec2MetadataServiceEndpointKey = "ec2_metadata_service_endpoint"
- // Use DualStack Endpoint Resolution
- useDualStackEndpoint = "use_dualstack_endpoint"
- // DefaultSharedConfigProfile is the default profile to be used when
- // loading configuration from the config files if another profile name
- // is not provided.
- DefaultSharedConfigProfile = `default`
- // S3 Disable Multi-Region AccessPoints
- s3DisableMultiRegionAccessPointsKey = `s3_disable_multiregion_access_points`
- useFIPSEndpointKey = "use_fips_endpoint"
- defaultsModeKey = "defaults_mode"
- // Retry options
- retryMaxAttemptsKey = "max_attempts"
- retryModeKey = "retry_mode"
- caBundleKey = "ca_bundle"
- )
- // defaultSharedConfigProfile allows for swapping the default profile for testing
- var defaultSharedConfigProfile = DefaultSharedConfigProfile
- // DefaultSharedCredentialsFilename returns the SDK's default file path
- // for the shared credentials file.
- //
- // Builds the shared config file path based on the OS's platform.
- //
- // - Linux/Unix: $HOME/.aws/credentials
- // - Windows: %USERPROFILE%\.aws\credentials
- func DefaultSharedCredentialsFilename() string {
- return filepath.Join(shareddefaults.UserHomeDir(), ".aws", "credentials")
- }
- // DefaultSharedConfigFilename returns the SDK's default file path for
- // the shared config file.
- //
- // Builds the shared config file path based on the OS's platform.
- //
- // - Linux/Unix: $HOME/.aws/config
- // - Windows: %USERPROFILE%\.aws\config
- func DefaultSharedConfigFilename() string {
- return filepath.Join(shareddefaults.UserHomeDir(), ".aws", "config")
- }
- // DefaultSharedConfigFiles is a slice of the default shared config files that
- // the will be used in order to load the SharedConfig.
- var DefaultSharedConfigFiles = []string{
- DefaultSharedConfigFilename(),
- }
- // DefaultSharedCredentialsFiles is a slice of the default shared credentials
- // files that the will be used in order to load the SharedConfig.
- var DefaultSharedCredentialsFiles = []string{
- DefaultSharedCredentialsFilename(),
- }
- // SSOSession provides the shared configuration parameters of the sso-session
- // section.
- type SSOSession struct {
- Name string
- SSORegion string
- SSOStartURL string
- }
- func (s *SSOSession) setFromIniSection(section ini.Section) {
- updateString(&s.Name, section, ssoSessionNameKey)
- updateString(&s.SSORegion, section, ssoRegionKey)
- updateString(&s.SSOStartURL, section, ssoStartURLKey)
- }
- // SharedConfig represents the configuration fields of the SDK config files.
- type SharedConfig struct {
- Profile string
- // Credentials values from the config file. Both aws_access_key_id
- // and aws_secret_access_key must be provided together in the same file
- // to be considered valid. The values will be ignored if not a complete group.
- // aws_session_token is an optional field that can be provided if both of the
- // other two fields are also provided.
- //
- // aws_access_key_id
- // aws_secret_access_key
- // aws_session_token
- Credentials aws.Credentials
- CredentialSource string
- CredentialProcess string
- WebIdentityTokenFile string
- // SSO session options
- SSOSessionName string
- SSOSession *SSOSession
- // Legacy SSO session options
- SSORegion string
- SSOStartURL string
- // SSO fields not used
- SSOAccountID string
- SSORoleName string
- RoleARN string
- ExternalID string
- MFASerial string
- RoleSessionName string
- RoleDurationSeconds *time.Duration
- SourceProfileName string
- Source *SharedConfig
- // Region is the region the SDK should use for looking up AWS service endpoints
- // and signing requests.
- //
- // region = us-west-2
- Region string
- // EnableEndpointDiscovery can be enabled or disabled in the shared config
- // by setting endpoint_discovery_enabled to true, or false respectively.
- //
- // endpoint_discovery_enabled = true
- EnableEndpointDiscovery aws.EndpointDiscoveryEnableState
- // Specifies if the S3 service should allow ARNs to direct the region
- // the client's requests are sent to.
- //
- // s3_use_arn_region=true
- S3UseARNRegion *bool
- // Specifies the EC2 Instance Metadata Service default endpoint selection
- // mode (IPv4 or IPv6)
- //
- // ec2_metadata_service_endpoint_mode=IPv6
- EC2IMDSEndpointMode imds.EndpointModeState
- // Specifies the EC2 Instance Metadata Service endpoint to use. If
- // specified it overrides EC2IMDSEndpointMode.
- //
- // ec2_metadata_service_endpoint=http://fd00:ec2::254
- EC2IMDSEndpoint string
- // Specifies if the S3 service should disable support for Multi-Region
- // access-points
- //
- // s3_disable_multiregion_access_points=true
- S3DisableMultiRegionAccessPoints *bool
- // Specifies that SDK clients must resolve a dual-stack endpoint for
- // services.
- //
- // use_dualstack_endpoint=true
- UseDualStackEndpoint aws.DualStackEndpointState
- // Specifies that SDK clients must resolve a FIPS endpoint for
- // services.
- //
- // use_fips_endpoint=true
- UseFIPSEndpoint aws.FIPSEndpointState
- // Specifies which defaults mode should be used by services.
- //
- // defaults_mode=standard
- DefaultsMode aws.DefaultsMode
- // Specifies the maximum number attempts an API client will call an
- // operation that fails with a retryable error.
- //
- // max_attempts=3
- RetryMaxAttempts int
- // Specifies the retry model the API client will be created with.
- //
- // retry_mode=standard
- RetryMode aws.RetryMode
- // Sets the path to a custom Credentials Authority (CA) Bundle PEM file
- // that the SDK will use instead of the system's root CA bundle. Only use
- // this if you want to configure the SDK to use a custom set of CAs.
- //
- // Enabling this option will attempt to merge the Transport into the SDK's
- // HTTP client. If the client's Transport is not a http.Transport an error
- // will be returned. If the Transport's TLS config is set this option will
- // cause the SDK to overwrite the Transport's TLS config's RootCAs value.
- //
- // Setting a custom HTTPClient in the aws.Config options will override this
- // setting. To use this option and custom HTTP client, the HTTP client
- // needs to be provided when creating the config. Not the service client.
- //
- // ca_bundle=$HOME/my_custom_ca_bundle
- CustomCABundle string
- }
- func (c SharedConfig) getDefaultsMode(ctx context.Context) (value aws.DefaultsMode, ok bool, err error) {
- if len(c.DefaultsMode) == 0 {
- return "", false, nil
- }
- return c.DefaultsMode, true, nil
- }
- // GetRetryMaxAttempts returns the maximum number of attempts an API client
- // created Retryer should attempt an operation call before failing.
- func (c SharedConfig) GetRetryMaxAttempts(ctx context.Context) (value int, ok bool, err error) {
- if c.RetryMaxAttempts == 0 {
- return 0, false, nil
- }
- return c.RetryMaxAttempts, true, nil
- }
- // GetRetryMode returns the model the API client should create its Retryer in.
- func (c SharedConfig) GetRetryMode(ctx context.Context) (value aws.RetryMode, ok bool, err error) {
- if len(c.RetryMode) == 0 {
- return "", false, nil
- }
- return c.RetryMode, true, nil
- }
- // GetS3UseARNRegion returns if the S3 service should allow ARNs to direct the region
- // the client's requests are sent to.
- func (c SharedConfig) GetS3UseARNRegion(ctx context.Context) (value, ok bool, err error) {
- if c.S3UseARNRegion == nil {
- return false, false, nil
- }
- return *c.S3UseARNRegion, true, nil
- }
- // GetEnableEndpointDiscovery returns if the enable_endpoint_discovery is set.
- func (c SharedConfig) GetEnableEndpointDiscovery(ctx context.Context) (value aws.EndpointDiscoveryEnableState, ok bool, err error) {
- if c.EnableEndpointDiscovery == aws.EndpointDiscoveryUnset {
- return aws.EndpointDiscoveryUnset, false, nil
- }
- return c.EnableEndpointDiscovery, true, nil
- }
- // GetS3DisableMultiRegionAccessPoints returns if the S3 service should disable support for Multi-Region
- // access-points.
- func (c SharedConfig) GetS3DisableMultiRegionAccessPoints(ctx context.Context) (value, ok bool, err error) {
- if c.S3DisableMultiRegionAccessPoints == nil {
- return false, false, nil
- }
- return *c.S3DisableMultiRegionAccessPoints, true, nil
- }
- // GetRegion returns the region for the profile if a region is set.
- func (c SharedConfig) getRegion(ctx context.Context) (string, bool, error) {
- if len(c.Region) == 0 {
- return "", false, nil
- }
- return c.Region, true, nil
- }
- // GetCredentialsProvider returns the credentials for a profile if they were set.
- func (c SharedConfig) getCredentialsProvider() (aws.Credentials, bool, error) {
- return c.Credentials, true, nil
- }
- // GetEC2IMDSEndpointMode implements a EC2IMDSEndpointMode option resolver interface.
- func (c SharedConfig) GetEC2IMDSEndpointMode() (imds.EndpointModeState, bool, error) {
- if c.EC2IMDSEndpointMode == imds.EndpointModeStateUnset {
- return imds.EndpointModeStateUnset, false, nil
- }
- return c.EC2IMDSEndpointMode, true, nil
- }
- // GetEC2IMDSEndpoint implements a EC2IMDSEndpoint option resolver interface.
- func (c SharedConfig) GetEC2IMDSEndpoint() (string, bool, error) {
- if len(c.EC2IMDSEndpoint) == 0 {
- return "", false, nil
- }
- return c.EC2IMDSEndpoint, true, nil
- }
- // GetUseDualStackEndpoint returns whether the service's dual-stack endpoint should be
- // used for requests.
- func (c SharedConfig) GetUseDualStackEndpoint(ctx context.Context) (value aws.DualStackEndpointState, found bool, err error) {
- if c.UseDualStackEndpoint == aws.DualStackEndpointStateUnset {
- return aws.DualStackEndpointStateUnset, false, nil
- }
- return c.UseDualStackEndpoint, true, nil
- }
- // GetUseFIPSEndpoint returns whether the service's FIPS endpoint should be
- // used for requests.
- func (c SharedConfig) GetUseFIPSEndpoint(ctx context.Context) (value aws.FIPSEndpointState, found bool, err error) {
- if c.UseFIPSEndpoint == aws.FIPSEndpointStateUnset {
- return aws.FIPSEndpointStateUnset, false, nil
- }
- return c.UseFIPSEndpoint, true, nil
- }
- // GetCustomCABundle returns the custom CA bundle's PEM bytes if the file was
- func (c SharedConfig) getCustomCABundle(context.Context) (io.Reader, bool, error) {
- if len(c.CustomCABundle) == 0 {
- return nil, false, nil
- }
- b, err := ioutil.ReadFile(c.CustomCABundle)
- if err != nil {
- return nil, false, err
- }
- return bytes.NewReader(b), true, nil
- }
- // loadSharedConfigIgnoreNotExist is an alias for loadSharedConfig with the
- // addition of ignoring when none of the files exist or when the profile
- // is not found in any of the files.
- func loadSharedConfigIgnoreNotExist(ctx context.Context, configs configs) (Config, error) {
- cfg, err := loadSharedConfig(ctx, configs)
- if err != nil {
- if _, ok := err.(SharedConfigProfileNotExistError); ok {
- return SharedConfig{}, nil
- }
- return nil, err
- }
- return cfg, nil
- }
- // loadSharedConfig uses the configs passed in to load the SharedConfig from file
- // The file names and profile name are sourced from the configs.
- //
- // If profile name is not provided DefaultSharedConfigProfile (default) will
- // be used.
- //
- // If shared config filenames are not provided DefaultSharedConfigFiles will
- // be used.
- //
- // Config providers used:
- // * sharedConfigProfileProvider
- // * sharedConfigFilesProvider
- func loadSharedConfig(ctx context.Context, configs configs) (Config, error) {
- var profile string
- var configFiles []string
- var credentialsFiles []string
- var ok bool
- var err error
- profile, ok, err = getSharedConfigProfile(ctx, configs)
- if err != nil {
- return nil, err
- }
- if !ok {
- profile = defaultSharedConfigProfile
- }
- configFiles, ok, err = getSharedConfigFiles(ctx, configs)
- if err != nil {
- return nil, err
- }
- credentialsFiles, ok, err = getSharedCredentialsFiles(ctx, configs)
- if err != nil {
- return nil, err
- }
- // setup logger if log configuration warning is seti
- var logger logging.Logger
- logWarnings, found, err := getLogConfigurationWarnings(ctx, configs)
- if err != nil {
- return SharedConfig{}, err
- }
- if found && logWarnings {
- logger, found, err = getLogger(ctx, configs)
- if err != nil {
- return SharedConfig{}, err
- }
- if !found {
- logger = logging.NewStandardLogger(os.Stderr)
- }
- }
- return LoadSharedConfigProfile(ctx, profile,
- func(o *LoadSharedConfigOptions) {
- o.Logger = logger
- o.ConfigFiles = configFiles
- o.CredentialsFiles = credentialsFiles
- },
- )
- }
- // LoadSharedConfigOptions struct contains optional values that can be used to load the config.
- type LoadSharedConfigOptions struct {
- // CredentialsFiles are the shared credentials files
- CredentialsFiles []string
- // ConfigFiles are the shared config files
- ConfigFiles []string
- // Logger is the logger used to log shared config behavior
- Logger logging.Logger
- }
- // LoadSharedConfigProfile retrieves the configuration from the list of files
- // using the profile provided. The order the files are listed will determine
- // precedence. Values in subsequent files will overwrite values defined in
- // earlier files.
- //
- // For example, given two files A and B. Both define credentials. If the order
- // of the files are A then B, B's credential values will be used instead of A's.
- //
- // If config files are not set, SDK will default to using a file at location `.aws/config` if present.
- // If credentials files are not set, SDK will default to using a file at location `.aws/credentials` if present.
- // No default files are set, if files set to an empty slice.
- //
- // You can read more about shared config and credentials file location at
- // https://docs.aws.amazon.com/credref/latest/refdocs/file-location.html#file-location
- func LoadSharedConfigProfile(ctx context.Context, profile string, optFns ...func(*LoadSharedConfigOptions)) (SharedConfig, error) {
- var option LoadSharedConfigOptions
- for _, fn := range optFns {
- fn(&option)
- }
- if option.ConfigFiles == nil {
- option.ConfigFiles = DefaultSharedConfigFiles
- }
- if option.CredentialsFiles == nil {
- option.CredentialsFiles = DefaultSharedCredentialsFiles
- }
- // load shared configuration sections from shared configuration INI options
- configSections, err := loadIniFiles(option.ConfigFiles)
- if err != nil {
- return SharedConfig{}, err
- }
- // check for profile prefix and drop duplicates or invalid profiles
- err = processConfigSections(ctx, &configSections, option.Logger)
- if err != nil {
- return SharedConfig{}, err
- }
- // load shared credentials sections from shared credentials INI options
- credentialsSections, err := loadIniFiles(option.CredentialsFiles)
- if err != nil {
- return SharedConfig{}, err
- }
- // check for profile prefix and drop duplicates or invalid profiles
- err = processCredentialsSections(ctx, &credentialsSections, option.Logger)
- if err != nil {
- return SharedConfig{}, err
- }
- err = mergeSections(&configSections, credentialsSections)
- if err != nil {
- return SharedConfig{}, err
- }
- cfg := SharedConfig{}
- profiles := map[string]struct{}{}
- if err = cfg.setFromIniSections(profiles, profile, configSections, option.Logger); err != nil {
- return SharedConfig{}, err
- }
- return cfg, nil
- }
- func processConfigSections(ctx context.Context, sections *ini.Sections, logger logging.Logger) error {
- skipSections := map[string]struct{}{}
- for _, section := range sections.List() {
- if _, ok := skipSections[section]; ok {
- continue
- }
- // drop sections from config file that do not have expected prefixes.
- switch {
- case strings.HasPrefix(section, profilePrefix):
- // Rename sections to remove "profile " prefixing to match with
- // credentials file. If default is already present, it will be
- // dropped.
- newName, err := renameProfileSection(section, sections, logger)
- if err != nil {
- return fmt.Errorf("failed to rename profile section, %w", err)
- }
- skipSections[newName] = struct{}{}
- case strings.HasPrefix(section, ssoSectionPrefix):
- case strings.EqualFold(section, "default"):
- default:
- // drop this section, as invalid profile name
- sections.DeleteSection(section)
- if logger != nil {
- logger.Logf(logging.Debug, "A profile defined with name `%v` is ignored. "+
- "For use within a shared configuration file, "+
- "a non-default profile must have `profile ` "+
- "prefixed to the profile name.",
- section,
- )
- }
- }
- }
- return nil
- }
- func renameProfileSection(section string, sections *ini.Sections, logger logging.Logger) (string, error) {
- v, ok := sections.GetSection(section)
- if !ok {
- return "", fmt.Errorf("error processing profiles within the shared configuration files")
- }
- // delete section with profile as prefix
- sections.DeleteSection(section)
- // set the value to non-prefixed name in sections.
- section = strings.TrimPrefix(section, profilePrefix)
- if sections.HasSection(section) {
- oldSection, _ := sections.GetSection(section)
- v.Logs = append(v.Logs,
- fmt.Sprintf("A non-default profile not prefixed with `profile ` found in %s, "+
- "overriding non-default profile from %s",
- v.SourceFile, oldSection.SourceFile))
- sections.DeleteSection(section)
- }
- // assign non-prefixed name to section
- v.Name = section
- sections.SetSection(section, v)
- return section, nil
- }
- func processCredentialsSections(ctx context.Context, sections *ini.Sections, logger logging.Logger) error {
- for _, section := range sections.List() {
- // drop profiles with prefix for credential files
- if strings.HasPrefix(section, profilePrefix) {
- // drop this section, as invalid profile name
- sections.DeleteSection(section)
- if logger != nil {
- logger.Logf(logging.Debug,
- "The profile defined with name `%v` is ignored. A profile with the `profile ` prefix is invalid "+
- "for the shared credentials file.\n",
- section,
- )
- }
- }
- }
- return nil
- }
- func loadIniFiles(filenames []string) (ini.Sections, error) {
- mergedSections := ini.NewSections()
- for _, filename := range filenames {
- sections, err := ini.OpenFile(filename)
- var v *ini.UnableToReadFile
- if ok := errors.As(err, &v); ok {
- // Skip files which can't be opened and read for whatever reason.
- // We treat such files as empty, and do not fall back to other locations.
- continue
- } else if err != nil {
- return ini.Sections{}, SharedConfigLoadError{Filename: filename, Err: err}
- }
- // mergeSections into mergedSections
- err = mergeSections(&mergedSections, sections)
- if err != nil {
- return ini.Sections{}, SharedConfigLoadError{Filename: filename, Err: err}
- }
- }
- return mergedSections, nil
- }
- // mergeSections merges source section properties into destination section properties
- func mergeSections(dst *ini.Sections, src ini.Sections) error {
- for _, sectionName := range src.List() {
- srcSection, _ := src.GetSection(sectionName)
- if (!srcSection.Has(accessKeyIDKey) && srcSection.Has(secretAccessKey)) ||
- (srcSection.Has(accessKeyIDKey) && !srcSection.Has(secretAccessKey)) {
- srcSection.Errors = append(srcSection.Errors,
- fmt.Errorf("partial credentials found for profile %v", sectionName))
- }
- if !dst.HasSection(sectionName) {
- dst.SetSection(sectionName, srcSection)
- continue
- }
- // merge with destination srcSection
- dstSection, _ := dst.GetSection(sectionName)
- // errors should be overriden if any
- dstSection.Errors = srcSection.Errors
- // Access key id update
- if srcSection.Has(accessKeyIDKey) && srcSection.Has(secretAccessKey) {
- accessKey := srcSection.String(accessKeyIDKey)
- secretKey := srcSection.String(secretAccessKey)
- if dstSection.Has(accessKeyIDKey) {
- dstSection.Logs = append(dstSection.Logs, newMergeKeyLogMessage(sectionName, accessKeyIDKey,
- dstSection.SourceFile[accessKeyIDKey], srcSection.SourceFile[accessKeyIDKey]))
- }
- // update access key
- v, err := ini.NewStringValue(accessKey)
- if err != nil {
- return fmt.Errorf("error merging access key, %w", err)
- }
- dstSection.UpdateValue(accessKeyIDKey, v)
- // update secret key
- v, err = ini.NewStringValue(secretKey)
- if err != nil {
- return fmt.Errorf("error merging secret key, %w", err)
- }
- dstSection.UpdateValue(secretAccessKey, v)
- // update session token
- if err = mergeStringKey(&srcSection, &dstSection, sectionName, sessionTokenKey); err != nil {
- return err
- }
- // update source file to reflect where the static creds came from
- dstSection.UpdateSourceFile(accessKeyIDKey, srcSection.SourceFile[accessKeyIDKey])
- dstSection.UpdateSourceFile(secretAccessKey, srcSection.SourceFile[secretAccessKey])
- }
- stringKeys := []string{
- roleArnKey,
- sourceProfileKey,
- credentialSourceKey,
- externalIDKey,
- mfaSerialKey,
- roleSessionNameKey,
- regionKey,
- enableEndpointDiscoveryKey,
- credentialProcessKey,
- webIdentityTokenFileKey,
- s3UseARNRegionKey,
- s3DisableMultiRegionAccessPointsKey,
- ec2MetadataServiceEndpointModeKey,
- ec2MetadataServiceEndpointKey,
- useDualStackEndpoint,
- useFIPSEndpointKey,
- defaultsModeKey,
- retryModeKey,
- caBundleKey,
- ssoSessionNameKey,
- ssoAccountIDKey,
- ssoRegionKey,
- ssoRoleNameKey,
- ssoStartURLKey,
- }
- for i := range stringKeys {
- if err := mergeStringKey(&srcSection, &dstSection, sectionName, stringKeys[i]); err != nil {
- return err
- }
- }
- intKeys := []string{
- roleDurationSecondsKey,
- retryMaxAttemptsKey,
- }
- for i := range intKeys {
- if err := mergeIntKey(&srcSection, &dstSection, sectionName, intKeys[i]); err != nil {
- return err
- }
- }
- // set srcSection on dst srcSection
- *dst = dst.SetSection(sectionName, dstSection)
- }
- return nil
- }
- func mergeStringKey(srcSection *ini.Section, dstSection *ini.Section, sectionName, key string) error {
- if srcSection.Has(key) {
- srcValue := srcSection.String(key)
- val, err := ini.NewStringValue(srcValue)
- if err != nil {
- return fmt.Errorf("error merging %s, %w", key, err)
- }
- if dstSection.Has(key) {
- dstSection.Logs = append(dstSection.Logs, newMergeKeyLogMessage(sectionName, key,
- dstSection.SourceFile[key], srcSection.SourceFile[key]))
- }
- dstSection.UpdateValue(key, val)
- dstSection.UpdateSourceFile(key, srcSection.SourceFile[key])
- }
- return nil
- }
- func mergeIntKey(srcSection *ini.Section, dstSection *ini.Section, sectionName, key string) error {
- if srcSection.Has(key) {
- srcValue := srcSection.Int(key)
- v, err := ini.NewIntValue(srcValue)
- if err != nil {
- return fmt.Errorf("error merging %s, %w", key, err)
- }
- if dstSection.Has(key) {
- dstSection.Logs = append(dstSection.Logs, newMergeKeyLogMessage(sectionName, key,
- dstSection.SourceFile[key], srcSection.SourceFile[key]))
- }
- dstSection.UpdateValue(key, v)
- dstSection.UpdateSourceFile(key, srcSection.SourceFile[key])
- }
- return nil
- }
- func newMergeKeyLogMessage(sectionName, key, dstSourceFile, srcSourceFile string) string {
- return fmt.Sprintf("For profile: %v, overriding %v value, defined in %v "+
- "with a %v value found in a duplicate profile defined at file %v. \n",
- sectionName, key, dstSourceFile, key, srcSourceFile)
- }
- // Returns an error if all of the files fail to load. If at least one file is
- // successfully loaded and contains the profile, no error will be returned.
- func (c *SharedConfig) setFromIniSections(profiles map[string]struct{}, profile string,
- sections ini.Sections, logger logging.Logger) error {
- c.Profile = profile
- section, ok := sections.GetSection(profile)
- if !ok {
- return SharedConfigProfileNotExistError{
- Profile: profile,
- }
- }
- // if logs are appended to the section, log them
- if section.Logs != nil && logger != nil {
- for _, log := range section.Logs {
- logger.Logf(logging.Debug, log)
- }
- }
- // set config from the provided INI section
- err := c.setFromIniSection(profile, section)
- if err != nil {
- return fmt.Errorf("error fetching config from profile, %v, %w", profile, err)
- }
- if _, ok := profiles[profile]; ok {
- // if this is the second instance of the profile the Assume Role
- // options must be cleared because they are only valid for the
- // first reference of a profile. The self linked instance of the
- // profile only have credential provider options.
- c.clearAssumeRoleOptions()
- } else {
- // First time a profile has been seen. Assert if the credential type
- // requires a role ARN, the ARN is also set
- if err := c.validateCredentialsConfig(profile); err != nil {
- return err
- }
- }
- // if not top level profile and has credentials, return with credentials.
- if len(profiles) != 0 && c.Credentials.HasKeys() {
- return nil
- }
- profiles[profile] = struct{}{}
- // validate no colliding credentials type are present
- if err := c.validateCredentialType(); err != nil {
- return err
- }
- // Link source profiles for assume roles
- if len(c.SourceProfileName) != 0 {
- // Linked profile via source_profile ignore credential provider
- // options, the source profile must provide the credentials.
- c.clearCredentialOptions()
- srcCfg := &SharedConfig{}
- err := srcCfg.setFromIniSections(profiles, c.SourceProfileName, sections, logger)
- if err != nil {
- // SourceProfileName that doesn't exist is an error in configuration.
- if _, ok := err.(SharedConfigProfileNotExistError); ok {
- err = SharedConfigAssumeRoleError{
- RoleARN: c.RoleARN,
- Profile: c.SourceProfileName,
- Err: err,
- }
- }
- return err
- }
- if !srcCfg.hasCredentials() {
- return SharedConfigAssumeRoleError{
- RoleARN: c.RoleARN,
- Profile: c.SourceProfileName,
- }
- }
- c.Source = srcCfg
- }
- // If the profile contains an SSO session parameter, the session MUST exist
- // as a section in the config file. Load the SSO session using the name
- // provided. If the session section is not found or incomplete an error
- // will be returned.
- if c.hasSSOTokenProviderConfiguration() {
- section, ok := sections.GetSection(ssoSectionPrefix + strings.TrimSpace(c.SSOSessionName))
- if !ok {
- return fmt.Errorf("failed to find SSO session section, %v", c.SSOSessionName)
- }
- var ssoSession SSOSession
- ssoSession.setFromIniSection(section)
- ssoSession.Name = c.SSOSessionName
- c.SSOSession = &ssoSession
- }
- return nil
- }
- // setFromIniSection loads the configuration from the profile section defined in
- // the provided INI file. A SharedConfig pointer type value is used so that
- // multiple config file loadings can be chained.
- //
- // Only loads complete logically grouped values, and will not set fields in cfg
- // for incomplete grouped values in the config. Such as credentials. For example
- // if a config file only includes aws_access_key_id but no aws_secret_access_key
- // the aws_access_key_id will be ignored.
- func (c *SharedConfig) setFromIniSection(profile string, section ini.Section) error {
- if len(section.Name) == 0 {
- sources := make([]string, 0)
- for _, v := range section.SourceFile {
- sources = append(sources, v)
- }
- return fmt.Errorf("parsing error : could not find profile section name after processing files: %v", sources)
- }
- if len(section.Errors) != 0 {
- var errStatement string
- for i, e := range section.Errors {
- errStatement = fmt.Sprintf("%d, %v\n", i+1, e.Error())
- }
- return fmt.Errorf("Error using profile: \n %v", errStatement)
- }
- // Assume Role
- updateString(&c.RoleARN, section, roleArnKey)
- updateString(&c.ExternalID, section, externalIDKey)
- updateString(&c.MFASerial, section, mfaSerialKey)
- updateString(&c.RoleSessionName, section, roleSessionNameKey)
- updateString(&c.SourceProfileName, section, sourceProfileKey)
- updateString(&c.CredentialSource, section, credentialSourceKey)
- updateString(&c.Region, section, regionKey)
- // AWS Single Sign-On (AWS SSO)
- // SSO session options
- updateString(&c.SSOSessionName, section, ssoSessionNameKey)
- // Legacy SSO session options
- updateString(&c.SSORegion, section, ssoRegionKey)
- updateString(&c.SSOStartURL, section, ssoStartURLKey)
- // SSO fields not used
- updateString(&c.SSOAccountID, section, ssoAccountIDKey)
- updateString(&c.SSORoleName, section, ssoRoleNameKey)
- if section.Has(roleDurationSecondsKey) {
- d := time.Duration(section.Int(roleDurationSecondsKey)) * time.Second
- c.RoleDurationSeconds = &d
- }
- updateString(&c.CredentialProcess, section, credentialProcessKey)
- updateString(&c.WebIdentityTokenFile, section, webIdentityTokenFileKey)
- updateEndpointDiscoveryType(&c.EnableEndpointDiscovery, section, enableEndpointDiscoveryKey)
- updateBoolPtr(&c.S3UseARNRegion, section, s3UseARNRegionKey)
- updateBoolPtr(&c.S3DisableMultiRegionAccessPoints, section, s3DisableMultiRegionAccessPointsKey)
- if err := updateEC2MetadataServiceEndpointMode(&c.EC2IMDSEndpointMode, section, ec2MetadataServiceEndpointModeKey); err != nil {
- return fmt.Errorf("failed to load %s from shared config, %v", ec2MetadataServiceEndpointModeKey, err)
- }
- updateString(&c.EC2IMDSEndpoint, section, ec2MetadataServiceEndpointKey)
- updateUseDualStackEndpoint(&c.UseDualStackEndpoint, section, useDualStackEndpoint)
- updateUseFIPSEndpoint(&c.UseFIPSEndpoint, section, useFIPSEndpointKey)
- if err := updateDefaultsMode(&c.DefaultsMode, section, defaultsModeKey); err != nil {
- return fmt.Errorf("failed to load %s from shared config, %w", defaultsModeKey, err)
- }
- if err := updateInt(&c.RetryMaxAttempts, section, retryMaxAttemptsKey); err != nil {
- return fmt.Errorf("failed to load %s from shared config, %w", retryMaxAttemptsKey, err)
- }
- if err := updateRetryMode(&c.RetryMode, section, retryModeKey); err != nil {
- return fmt.Errorf("failed to load %s from shared config, %w", retryModeKey, err)
- }
- updateString(&c.CustomCABundle, section, caBundleKey)
- // Shared Credentials
- creds := aws.Credentials{
- AccessKeyID: section.String(accessKeyIDKey),
- SecretAccessKey: section.String(secretAccessKey),
- SessionToken: section.String(sessionTokenKey),
- Source: fmt.Sprintf("SharedConfigCredentials: %s", section.SourceFile[accessKeyIDKey]),
- }
- if creds.HasKeys() {
- c.Credentials = creds
- }
- return nil
- }
- func updateDefaultsMode(mode *aws.DefaultsMode, section ini.Section, key string) error {
- if !section.Has(key) {
- return nil
- }
- value := section.String(key)
- if ok := mode.SetFromString(value); !ok {
- return fmt.Errorf("invalid value: %s", value)
- }
- return nil
- }
- func updateRetryMode(mode *aws.RetryMode, section ini.Section, key string) (err error) {
- if !section.Has(key) {
- return nil
- }
- value := section.String(key)
- if *mode, err = aws.ParseRetryMode(value); err != nil {
- return err
- }
- return nil
- }
- func updateEC2MetadataServiceEndpointMode(endpointMode *imds.EndpointModeState, section ini.Section, key string) error {
- if !section.Has(key) {
- return nil
- }
- value := section.String(key)
- return endpointMode.SetFromString(value)
- }
- func (c *SharedConfig) validateCredentialsConfig(profile string) error {
- if err := c.validateCredentialsRequireARN(profile); err != nil {
- return err
- }
- return nil
- }
- func (c *SharedConfig) validateCredentialsRequireARN(profile string) error {
- var credSource string
- switch {
- case len(c.SourceProfileName) != 0:
- credSource = sourceProfileKey
- case len(c.CredentialSource) != 0:
- credSource = credentialSourceKey
- case len(c.WebIdentityTokenFile) != 0:
- credSource = webIdentityTokenFileKey
- }
- if len(credSource) != 0 && len(c.RoleARN) == 0 {
- return CredentialRequiresARNError{
- Type: credSource,
- Profile: profile,
- }
- }
- return nil
- }
- func (c *SharedConfig) validateCredentialType() error {
- // Only one or no credential type can be defined.
- if !oneOrNone(
- len(c.SourceProfileName) != 0,
- len(c.CredentialSource) != 0,
- len(c.CredentialProcess) != 0,
- len(c.WebIdentityTokenFile) != 0,
- ) {
- return fmt.Errorf("only one credential type may be specified per profile: source profile, credential source, credential process, web identity token")
- }
- return nil
- }
- func (c *SharedConfig) validateSSOConfiguration() error {
- if c.hasSSOTokenProviderConfiguration() {
- err := c.validateSSOTokenProviderConfiguration()
- if err != nil {
- return err
- }
- return nil
- }
- if c.hasLegacySSOConfiguration() {
- err := c.validateLegacySSOConfiguration()
- if err != nil {
- return err
- }
- }
- return nil
- }
- func (c *SharedConfig) validateSSOTokenProviderConfiguration() error {
- var missing []string
- if len(c.SSOSessionName) == 0 {
- missing = append(missing, ssoSessionNameKey)
- }
- if c.SSOSession == nil {
- missing = append(missing, ssoSectionPrefix)
- } else {
- if len(c.SSOSession.SSORegion) == 0 {
- missing = append(missing, ssoRegionKey)
- }
- if len(c.SSOSession.SSOStartURL) == 0 {
- missing = append(missing, ssoStartURLKey)
- }
- }
- if len(missing) > 0 {
- return fmt.Errorf("profile %q is configured to use SSO but is missing required configuration: %s",
- c.Profile, strings.Join(missing, ", "))
- }
- if len(c.SSORegion) > 0 && c.SSORegion != c.SSOSession.SSORegion {
- return fmt.Errorf("%s in profile %q must match %s in %s", ssoRegionKey, c.Profile, ssoRegionKey, ssoSectionPrefix)
- }
- if len(c.SSOStartURL) > 0 && c.SSOStartURL != c.SSOSession.SSOStartURL {
- return fmt.Errorf("%s in profile %q must match %s in %s", ssoStartURLKey, c.Profile, ssoStartURLKey, ssoSectionPrefix)
- }
- return nil
- }
- func (c *SharedConfig) validateLegacySSOConfiguration() error {
- var missing []string
- if len(c.SSORegion) == 0 {
- missing = append(missing, ssoRegionKey)
- }
- if len(c.SSOStartURL) == 0 {
- missing = append(missing, ssoStartURLKey)
- }
- if len(c.SSOAccountID) == 0 {
- missing = append(missing, ssoAccountIDKey)
- }
- if len(c.SSORoleName) == 0 {
- missing = append(missing, ssoRoleNameKey)
- }
- if len(missing) > 0 {
- return fmt.Errorf("profile %q is configured to use SSO but is missing required configuration: %s",
- c.Profile, strings.Join(missing, ", "))
- }
- return nil
- }
- func (c *SharedConfig) hasCredentials() bool {
- switch {
- case len(c.SourceProfileName) != 0:
- case len(c.CredentialSource) != 0:
- case len(c.CredentialProcess) != 0:
- case len(c.WebIdentityTokenFile) != 0:
- case c.hasSSOConfiguration():
- case c.Credentials.HasKeys():
- default:
- return false
- }
- return true
- }
- func (c *SharedConfig) hasSSOConfiguration() bool {
- return c.hasSSOTokenProviderConfiguration() || c.hasLegacySSOConfiguration()
- }
- func (c *SharedConfig) hasSSOTokenProviderConfiguration() bool {
- return len(c.SSOSessionName) > 0
- }
- func (c *SharedConfig) hasLegacySSOConfiguration() bool {
- return len(c.SSORegion) > 0 || len(c.SSOAccountID) > 0 || len(c.SSOStartURL) > 0 || len(c.SSORoleName) > 0
- }
- func (c *SharedConfig) clearAssumeRoleOptions() {
- c.RoleARN = ""
- c.ExternalID = ""
- c.MFASerial = ""
- c.RoleSessionName = ""
- c.SourceProfileName = ""
- }
- func (c *SharedConfig) clearCredentialOptions() {
- c.CredentialSource = ""
- c.CredentialProcess = ""
- c.WebIdentityTokenFile = ""
- c.Credentials = aws.Credentials{}
- c.SSOAccountID = ""
- c.SSORegion = ""
- c.SSORoleName = ""
- c.SSOStartURL = ""
- }
- // SharedConfigLoadError is an error for the shared config file failed to load.
- type SharedConfigLoadError struct {
- Filename string
- Err error
- }
- // Unwrap returns the underlying error that caused the failure.
- func (e SharedConfigLoadError) Unwrap() error {
- return e.Err
- }
- func (e SharedConfigLoadError) Error() string {
- return fmt.Sprintf("failed to load shared config file, %s, %v", e.Filename, e.Err)
- }
- // SharedConfigProfileNotExistError is an error for the shared config when
- // the profile was not find in the config file.
- type SharedConfigProfileNotExistError struct {
- Filename []string
- Profile string
- Err error
- }
- // Unwrap returns the underlying error that caused the failure.
- func (e SharedConfigProfileNotExistError) Unwrap() error {
- return e.Err
- }
- func (e SharedConfigProfileNotExistError) Error() string {
- return fmt.Sprintf("failed to get shared config profile, %s", e.Profile)
- }
- // SharedConfigAssumeRoleError is an error for the shared config when the
- // profile contains assume role information, but that information is invalid
- // or not complete.
- type SharedConfigAssumeRoleError struct {
- Profile string
- RoleARN string
- Err error
- }
- // Unwrap returns the underlying error that caused the failure.
- func (e SharedConfigAssumeRoleError) Unwrap() error {
- return e.Err
- }
- func (e SharedConfigAssumeRoleError) Error() string {
- return fmt.Sprintf("failed to load assume role %s, of profile %s, %v",
- e.RoleARN, e.Profile, e.Err)
- }
- // CredentialRequiresARNError provides the error for shared config credentials
- // that are incorrectly configured in the shared config or credentials file.
- type CredentialRequiresARNError struct {
- // type of credentials that were configured.
- Type string
- // Profile name the credentials were in.
- Profile string
- }
- // Error satisfies the error interface.
- func (e CredentialRequiresARNError) Error() string {
- return fmt.Sprintf(
- "credential type %s requires role_arn, profile %s",
- e.Type, e.Profile,
- )
- }
- func oneOrNone(bs ...bool) bool {
- var count int
- for _, b := range bs {
- if b {
- count++
- if count > 1 {
- return false
- }
- }
- }
- return true
- }
- // updateString will only update the dst with the value in the section key, key
- // is present in the section.
- func updateString(dst *string, section ini.Section, key string) {
- if !section.Has(key) {
- return
- }
- *dst = section.String(key)
- }
- // updateInt will only update the dst with the value in the section key, key
- // is present in the section.
- //
- // Down casts the INI integer value from a int64 to an int, which could be
- // different bit size depending on platform.
- func updateInt(dst *int, section ini.Section, key string) error {
- if !section.Has(key) {
- return nil
- }
- if vt, _ := section.ValueType(key); vt != ini.IntegerType {
- return fmt.Errorf("invalid value %s=%s, expect integer",
- key, section.String(key))
- }
- *dst = int(section.Int(key))
- return nil
- }
- // updateBool will only update the dst with the value in the section key, key
- // is present in the section.
- func updateBool(dst *bool, section ini.Section, key string) {
- if !section.Has(key) {
- return
- }
- *dst = section.Bool(key)
- }
- // updateBoolPtr will only update the dst with the value in the section key,
- // key is present in the section.
- func updateBoolPtr(dst **bool, section ini.Section, key string) {
- if !section.Has(key) {
- return
- }
- *dst = new(bool)
- **dst = section.Bool(key)
- }
- // updateEndpointDiscoveryType will only update the dst with the value in the section, if
- // a valid key and corresponding EndpointDiscoveryType is found.
- func updateEndpointDiscoveryType(dst *aws.EndpointDiscoveryEnableState, section ini.Section, key string) {
- if !section.Has(key) {
- return
- }
- value := section.String(key)
- if len(value) == 0 {
- return
- }
- switch {
- case strings.EqualFold(value, endpointDiscoveryDisabled):
- *dst = aws.EndpointDiscoveryDisabled
- case strings.EqualFold(value, endpointDiscoveryEnabled):
- *dst = aws.EndpointDiscoveryEnabled
- case strings.EqualFold(value, endpointDiscoveryAuto):
- *dst = aws.EndpointDiscoveryAuto
- }
- }
- // updateEndpointDiscoveryType will only update the dst with the value in the section, if
- // a valid key and corresponding EndpointDiscoveryType is found.
- func updateUseDualStackEndpoint(dst *aws.DualStackEndpointState, section ini.Section, key string) {
- if !section.Has(key) {
- return
- }
- if section.Bool(key) {
- *dst = aws.DualStackEndpointStateEnabled
- } else {
- *dst = aws.DualStackEndpointStateDisabled
- }
- return
- }
- // updateEndpointDiscoveryType will only update the dst with the value in the section, if
- // a valid key and corresponding EndpointDiscoveryType is found.
- func updateUseFIPSEndpoint(dst *aws.FIPSEndpointState, section ini.Section, key string) {
- if !section.Has(key) {
- return
- }
- if section.Bool(key) {
- *dst = aws.FIPSEndpointStateEnabled
- } else {
- *dst = aws.FIPSEndpointStateDisabled
- }
- return
- }
|