diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index d24b973d499..0bbfeddce54 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -3,5 +3,7 @@ * See https://aws.amazon.com/blogs/developer/aws-sdk-for-go-aligns-with-go-release-policy-on-supported-runtimes/. ### SDK Enhancements +* `aws/ec2metadata`: Added environment and shared config support for disabling IMDSv1 fallback. + * Use env `AWS_EC2_METADATA_V1_DISABLED` or shared config `ec2_metadata_v1_disabled` accordingly. ### SDK Bugs diff --git a/aws/session/env_config.go b/aws/session/env_config.go index d6fa24776cf..93bb5de6470 100644 --- a/aws/session/env_config.go +++ b/aws/session/env_config.go @@ -171,6 +171,12 @@ type envConfig struct { // AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE=IPv6 EC2IMDSEndpointMode endpoints.EC2IMDSEndpointModeState + // Specifies that IMDS clients should not fallback to IMDSv1 if token + // requests fail. + // + // AWS_EC2_METADATA_V1_DISABLED=true + EC2IMDSv1Disabled *bool + // Specifies that SDK clients must resolve a dual-stack endpoint for // services. // @@ -251,6 +257,9 @@ var ( ec2IMDSEndpointModeEnvKey = []string{ "AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE", } + ec2MetadataV1DisabledEnvKey = []string{ + "AWS_EC2_METADATA_V1_DISABLED", + } useCABundleKey = []string{ "AWS_CA_BUNDLE", } @@ -393,6 +402,7 @@ func envConfigLoad(enableSharedConfig bool) (envConfig, error) { if err := setEC2IMDSEndpointMode(&cfg.EC2IMDSEndpointMode, ec2IMDSEndpointModeEnvKey); err != nil { return envConfig{}, err } + setBoolPtrFromEnvVal(&cfg.EC2IMDSv1Disabled, ec2MetadataV1DisabledEnvKey) if err := setUseDualStackEndpointFromEnvVal(&cfg.UseDualStackEndpoint, awsUseDualStackEndpoint); err != nil { return cfg, err @@ -414,6 +424,24 @@ func setFromEnvVal(dst *string, keys []string) { } } +func setBoolPtrFromEnvVal(dst **bool, keys []string) { + for _, k := range keys { + value := os.Getenv(k) + if len(value) == 0 { + continue + } + + switch { + case strings.EqualFold(value, "false"): + *dst = new(bool) + **dst = false + case strings.EqualFold(value, "true"): + *dst = new(bool) + **dst = true + } + } +} + func setEC2IMDSEndpointMode(mode *endpoints.EC2IMDSEndpointModeState, keys []string) error { for _, k := range keys { value := os.Getenv(k) diff --git a/aws/session/env_config_test.go b/aws/session/env_config_test.go index cd63d1030ad..87275a92cce 100644 --- a/aws/session/env_config_test.go +++ b/aws/session/env_config_test.go @@ -9,6 +9,7 @@ import ( "strconv" "testing" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/awstesting" @@ -467,6 +468,35 @@ func TestLoadEnvConfig(t *testing.T) { }, WantErr: true, }, + 33: { + Env: map[string]string{ + "AWS_EC2_METADATA_V1_DISABLED": "fAlSe", + }, + Config: envConfig{ + SharedCredentialsFile: shareddefaults.SharedCredentialsFilename(), + SharedConfigFile: shareddefaults.SharedConfigFilename(), + EC2IMDSv1Disabled: aws.Bool(false), + }, + }, + 34: { + Env: map[string]string{ + "AWS_EC2_METADATA_V1_DISABLED": "tRuE", + }, + Config: envConfig{ + SharedCredentialsFile: shareddefaults.SharedCredentialsFilename(), + SharedConfigFile: shareddefaults.SharedConfigFilename(), + EC2IMDSv1Disabled: aws.Bool(true), + }, + }, + 35: { + Env: map[string]string{ + "AWS_EC2_METADATA_V1_DISABLED": "invalid", + }, + Config: envConfig{ + SharedCredentialsFile: shareddefaults.SharedCredentialsFilename(), + SharedConfigFile: shareddefaults.SharedConfigFilename(), + }, + }, } for i, c := range cases { diff --git a/aws/session/session.go b/aws/session/session.go index 8127c99a9a1..3c88dee526d 100644 --- a/aws/session/session.go +++ b/aws/session/session.go @@ -779,6 +779,14 @@ func mergeConfigSrcs(cfg, userCfg *aws.Config, cfg.EndpointResolver = wrapEC2IMDSEndpoint(cfg.EndpointResolver, ec2IMDSEndpoint, endpointMode) } + cfg.EC2MetadataEnableFallback = userCfg.EC2MetadataEnableFallback + if cfg.EC2MetadataEnableFallback == nil && envCfg.EC2IMDSv1Disabled != nil { + cfg.EC2MetadataEnableFallback = aws.Bool(!*envCfg.EC2IMDSv1Disabled) + } + if cfg.EC2MetadataEnableFallback == nil && sharedCfg.EC2IMDSv1Disabled != nil { + cfg.EC2MetadataEnableFallback = aws.Bool(!*sharedCfg.EC2IMDSv1Disabled) + } + cfg.S3UseARNRegion = userCfg.S3UseARNRegion if cfg.S3UseARNRegion == nil { cfg.S3UseARNRegion = &envCfg.S3UseARNRegion diff --git a/aws/session/shared_config.go b/aws/session/shared_config.go index 8f1388f9fbf..f3ce8183dd9 100644 --- a/aws/session/shared_config.go +++ b/aws/session/shared_config.go @@ -80,6 +80,9 @@ const ( // EC2 IMDS Endpoint ec2MetadataServiceEndpointKey = "ec2_metadata_service_endpoint" + // ECS IMDSv1 disable fallback + ec2MetadataV1DisabledKey = "ec2_metadata_v1_disabled" + // Use DualStack Endpoint Resolution useDualStackEndpoint = "use_dualstack_endpoint" @@ -179,6 +182,12 @@ type sharedConfig struct { // ec2_metadata_service_endpoint=http://fd00:ec2::254 EC2IMDSEndpoint string + // Specifies that IMDS clients should not fallback to IMDSv1 if token + // requests fail. + // + // ec2_metadata_v1_disabled=true + EC2IMDSv1Disabled *bool + // Specifies that SDK clients must resolve a dual-stack endpoint for // services. // @@ -434,6 +443,7 @@ func (cfg *sharedConfig) setFromIniFile(profile string, file sharedConfigFile, e ec2MetadataServiceEndpointModeKey, file.Filename, err) } updateString(&cfg.EC2IMDSEndpoint, section, ec2MetadataServiceEndpointKey) + updateBoolPtr(&cfg.EC2IMDSv1Disabled, section, ec2MetadataV1DisabledKey) updateUseDualStackEndpoint(&cfg.UseDualStackEndpoint, section, useDualStackEndpoint) diff --git a/aws/session/shared_config_test.go b/aws/session/shared_config_test.go index d2b945014a6..d011d014419 100644 --- a/aws/session/shared_config_test.go +++ b/aws/session/shared_config_test.go @@ -11,6 +11,7 @@ import ( "strings" "testing" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/internal/ini" @@ -411,6 +412,30 @@ func TestLoadSharedConfig(t *testing.T) { Profile: "sso-session-not-exist", Err: fmt.Errorf("failed to find SSO session section, sso-session-lost"), }, + { + Filenames: []string{testConfigFilename}, + Profile: "ec2-metadata-v1-disabled-false", + Expected: sharedConfig{ + Profile: "ec2-metadata-v1-disabled-false", + EC2IMDSv1Disabled: aws.Bool(false), + }, + }, + { + Filenames: []string{testConfigFilename}, + Profile: "ec2-metadata-v1-disabled-true", + Expected: sharedConfig{ + Profile: "ec2-metadata-v1-disabled-true", + EC2IMDSv1Disabled: aws.Bool(true), + }, + }, + { + Filenames: []string{testConfigFilename}, + Profile: "ec2-metadata-v1-disabled-invalid", + Expected: sharedConfig{ + Profile: "ec2-metadata-v1-disabled-invalid", + EC2IMDSv1Disabled: aws.Bool(false), + }, + }, } for i, c := range cases { diff --git a/aws/session/testdata/shared_config b/aws/session/testdata/shared_config index 55ce6b6468e..c785b0318af 100644 --- a/aws/session/testdata/shared_config +++ b/aws/session/testdata/shared_config @@ -203,4 +203,13 @@ sso_registration_scopes = sso:account:access region = us-east-1 sso_session = sso-session-lost sso_account_id = 123456789012 -sso_role_name = testRole \ No newline at end of file +sso_role_name = testRole + +[profile ec2-metadata-v1-disabled-false] +ec2_metadata_v1_disabled=False + +[profile ec2-metadata-v1-disabled-true] +ec2_metadata_v1_disabled=True + +[profile ec2-metadata-v1-disabled-invalid] +ec2_metadata_v1_disabled=invalid