Skip to content

Commit f5b68d1

Browse files
authored
EC2 IMDS IPv6 Support (#1337)
1 parent a38059c commit f5b68d1

17 files changed

+984
-53
lines changed

config/env_config.go

Lines changed: 94 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"fmt"
7+
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
78
"io"
89
"io/ioutil"
910
"os"
@@ -42,14 +43,20 @@ const (
4243

4344
awsCustomCABundleEnvVar = "AWS_CA_BUNDLE"
4445

45-
awsWebIdentityTokenFilePathEnvKey = "AWS_WEB_IDENTITY_TOKEN_FILE"
46+
awsWebIdentityTokenFilePathEnvVar = "AWS_WEB_IDENTITY_TOKEN_FILE"
4647

47-
awsRoleARNEnvKey = "AWS_ROLE_ARN"
48-
awsRoleSessionNameEnvKey = "AWS_ROLE_SESSION_NAME"
48+
awsRoleARNEnvVar = "AWS_ROLE_ARN"
49+
awsRoleSessionNameEnvVar = "AWS_ROLE_SESSION_NAME"
4950

50-
awsEnableEndpointDiscoveryEnvKey = "AWS_ENABLE_ENDPOINT_DISCOVERY"
51+
awsEnableEndpointDiscoveryEnvVar = "AWS_ENABLE_ENDPOINT_DISCOVERY"
5152

5253
awsS3UseARNRegionEnvVar = "AWS_S3_USE_ARN_REGION"
54+
55+
awsEc2MetadataServiceEndpointModeEnvVar = "AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE"
56+
57+
awsEc2MetadataServiceEndpointEnvVar = "AWS_EC2_METADATA_SERVICE_ENDPOINT"
58+
59+
awsEc2MetadataDisabled = "AWS_EC2_METADATA_DISABLED"
5360
)
5461

5562
var (
@@ -180,6 +187,21 @@ type EnvConfig struct {
180187
//
181188
// AWS_S3_USE_ARN_REGION=true
182189
S3UseARNRegion *bool
190+
191+
// Specifies if the EC2 IMDS service client is enabled.
192+
//
193+
// AWS_EC2_METADATA_DISABLED=true
194+
EC2IMDSClientEnableState imds.ClientEnableState
195+
196+
// Specifies the EC2 Instance Metadata Service default endpoint selection mode (IPv4 or IPv6)
197+
//
198+
// AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE=IPv6
199+
EC2IMDSEndpointMode imds.EndpointModeState
200+
201+
// Specifies the EC2 Instance Metadata Service endpoint to use. If specified it overrides EC2IMDSEndpointMode.
202+
//
203+
// AWS_EC2_METADATA_SERVICE_ENDPOINT=http://fd00:ec2::254
204+
EC2IMDSEndpoint string
183205
}
184206

185207
// loadEnvConfig reads configuration values from the OS's environment variables.
@@ -215,22 +237,59 @@ func NewEnvConfig() (EnvConfig, error) {
215237

216238
cfg.CustomCABundle = os.Getenv(awsCustomCABundleEnvVar)
217239

218-
cfg.WebIdentityTokenFilePath = os.Getenv(awsWebIdentityTokenFilePathEnvKey)
240+
cfg.WebIdentityTokenFilePath = os.Getenv(awsWebIdentityTokenFilePathEnvVar)
219241

220-
cfg.RoleARN = os.Getenv(awsRoleARNEnvKey)
221-
cfg.RoleSessionName = os.Getenv(awsRoleSessionNameEnvKey)
242+
cfg.RoleARN = os.Getenv(awsRoleARNEnvVar)
243+
cfg.RoleSessionName = os.Getenv(awsRoleSessionNameEnvVar)
222244

223-
if err := setEndpointDiscoveryTypeFromEnvVal(&cfg.EnableEndpointDiscovery, []string{awsEnableEndpointDiscoveryEnvKey}); err != nil {
245+
if err := setEndpointDiscoveryTypeFromEnvVal(&cfg.EnableEndpointDiscovery, []string{awsEnableEndpointDiscoveryEnvVar}); err != nil {
224246
return cfg, err
225247
}
226248

227249
if err := setBoolPtrFromEnvVal(&cfg.S3UseARNRegion, []string{awsS3UseARNRegionEnvVar}); err != nil {
228250
return cfg, err
229251
}
230252

253+
setEC2IMDSClientEnableState(&cfg.EC2IMDSClientEnableState, []string{awsEc2MetadataDisabled})
254+
if err := setEC2IMDSEndpointMode(&cfg.EC2IMDSEndpointMode, []string{awsEc2MetadataServiceEndpointModeEnvVar}); err != nil {
255+
return cfg, err
256+
}
257+
cfg.EC2IMDSEndpoint = os.Getenv(awsEc2MetadataServiceEndpointEnvVar)
258+
231259
return cfg, nil
232260
}
233261

262+
func setEC2IMDSClientEnableState(state *imds.ClientEnableState, keys []string) {
263+
for _, k := range keys {
264+
value := os.Getenv(k)
265+
if len(value) == 0 {
266+
continue
267+
}
268+
switch {
269+
case strings.EqualFold(value, "true"):
270+
*state = imds.ClientDisabled
271+
case strings.EqualFold(value, "false"):
272+
*state = imds.ClientEnabled
273+
default:
274+
continue
275+
}
276+
break
277+
}
278+
}
279+
280+
func setEC2IMDSEndpointMode(mode *imds.EndpointModeState, keys []string) error {
281+
for _, k := range keys {
282+
value := os.Getenv(k)
283+
if len(value) == 0 {
284+
continue
285+
}
286+
if err := mode.SetFromString(value); err != nil {
287+
return fmt.Errorf("invalid value for environment variable, %s=%s, %v", k, value, err)
288+
}
289+
}
290+
return nil
291+
}
292+
234293
// GetRegion returns the AWS Region if set in the environment. Returns an empty
235294
// string if not set.
236295
func (c EnvConfig) getRegion(ctx context.Context) (string, bool, error) {
@@ -371,3 +430,30 @@ func (c EnvConfig) GetEnableEndpointDiscovery(ctx context.Context) (value aws.En
371430

372431
return c.EnableEndpointDiscovery, true, nil
373432
}
433+
434+
// GetEC2IMDSClientEnableState implements a EC2IMDSClientEnableState options resolver interface.
435+
func (c EnvConfig) GetEC2IMDSClientEnableState() (imds.ClientEnableState, bool, error) {
436+
if c.EC2IMDSClientEnableState == imds.ClientDefaultEnableState {
437+
return imds.ClientDefaultEnableState, false, nil
438+
}
439+
440+
return c.EC2IMDSClientEnableState, true, nil
441+
}
442+
443+
// GetEC2IMDSEndpointMode implements a EC2IMDSEndpointMode option resolver interface.
444+
func (c EnvConfig) GetEC2IMDSEndpointMode() (imds.EndpointModeState, bool, error) {
445+
if c.EC2IMDSEndpointMode == imds.EndpointModeStateUnset {
446+
return imds.EndpointModeStateUnset, false, nil
447+
}
448+
449+
return c.EC2IMDSEndpointMode, true, nil
450+
}
451+
452+
// GetEC2IMDSEndpoint implements a EC2IMDSEndpoint option resolver interface.
453+
func (c EnvConfig) GetEC2IMDSEndpoint() (string, bool, error) {
454+
if len(c.EC2IMDSEndpoint) == 0 {
455+
return "", false, nil
456+
}
457+
458+
return c.EC2IMDSEndpoint, true, nil
459+
}

config/env_config_test.go

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package config
22

33
import (
4+
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
5+
"github.com/google/go-cmp/cmp"
46
"os"
57
"reflect"
68
"strconv"
@@ -105,8 +107,9 @@ func TestNewEnvConfig(t *testing.T) {
105107
defer awstesting.PopEnv(restoreEnv)
106108

107109
cases := []struct {
108-
Env map[string]string
109-
Config EnvConfig
110+
Env map[string]string
111+
Config EnvConfig
112+
WantErr bool
110113
}{
111114
0: {
112115
Env: map[string]string{
@@ -250,6 +253,69 @@ func TestNewEnvConfig(t *testing.T) {
250253
Env: map[string]string{},
251254
Config: EnvConfig{},
252255
},
256+
16: {
257+
Env: map[string]string{
258+
"AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE": "IPv6",
259+
},
260+
Config: EnvConfig{
261+
EC2IMDSEndpointMode: imds.EndpointModeStateIPv6,
262+
},
263+
},
264+
17: {
265+
Env: map[string]string{
266+
"AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE": "IPv4",
267+
},
268+
Config: EnvConfig{
269+
EC2IMDSEndpointMode: imds.EndpointModeStateIPv4,
270+
},
271+
},
272+
18: {
273+
Env: map[string]string{
274+
"AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE": "foobar",
275+
},
276+
Config: EnvConfig{},
277+
WantErr: true,
278+
},
279+
19: {
280+
Env: map[string]string{
281+
"AWS_EC2_METADATA_SERVICE_ENDPOINT": "http://endpoint.localhost",
282+
},
283+
Config: EnvConfig{
284+
EC2IMDSEndpoint: "http://endpoint.localhost",
285+
},
286+
},
287+
20: {
288+
Env: map[string]string{
289+
"AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE": "IPv6",
290+
"AWS_EC2_METADATA_SERVICE_ENDPOINT": "http://endpoint.localhost",
291+
},
292+
Config: EnvConfig{
293+
EC2IMDSEndpoint: "http://endpoint.localhost",
294+
EC2IMDSEndpointMode: imds.EndpointModeStateIPv6,
295+
},
296+
},
297+
21: {
298+
Env: map[string]string{
299+
"AWS_EC2_METADATA_DISABLED": "false",
300+
},
301+
Config: EnvConfig{
302+
EC2IMDSClientEnableState: imds.ClientEnabled,
303+
},
304+
},
305+
22: {
306+
Env: map[string]string{
307+
"AWS_EC2_METADATA_DISABLED": "true",
308+
},
309+
Config: EnvConfig{
310+
EC2IMDSClientEnableState: imds.ClientDisabled,
311+
},
312+
},
313+
23: {
314+
Env: map[string]string{
315+
"AWS_EC2_METADATA_DISABLED": "foobar",
316+
},
317+
Config: EnvConfig{},
318+
},
253319
}
254320

255321
for i, c := range cases {
@@ -261,13 +327,13 @@ func TestNewEnvConfig(t *testing.T) {
261327
}
262328

263329
cfg, err := NewEnvConfig()
264-
if err != nil {
265-
t.Fatalf("expect no error, got %v", err)
330+
if (err != nil) != c.WantErr {
331+
t.Fatalf("WantErr=%v, got err=%v", c.WantErr, err)
266332
}
267333

268-
if !reflect.DeepEqual(c.Config, cfg) {
334+
if diff := cmp.Diff(c.Config, cfg); len(diff) > 0 {
269335
t.Errorf("expect config to match.\n%s",
270-
awstesting.SprintExpectActual(c.Config, cfg))
336+
diff)
271337
}
272338
})
273339
}

config/load_options.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,17 @@ type LoadOptions struct {
126126
// EnableEndpointDiscovery specifies if endpoint discovery is enable for
127127
// the client.
128128
EnableEndpointDiscovery aws.EndpointDiscoveryEnableState
129+
130+
// Specifies if the EC2 IMDS service client is enabled.
131+
//
132+
// AWS_EC2_METADATA_DISABLED=true
133+
EC2IMDSClientEnableState imds.ClientEnableState
134+
135+
// Specifies the EC2 Instance Metadata Service default endpoint selection mode (IPv4 or IPv6)
136+
EC2IMDSEndpointMode imds.EndpointModeState
137+
138+
// Specifies the EC2 Instance Metadata Service endpoint to use. If specified it overrides EC2IMDSEndpointMode.
139+
EC2IMDSEndpoint string
129140
}
130141

131142
// getRegion returns Region from config's LoadOptions
@@ -642,3 +653,54 @@ func WithSSOProviderOptions(v func(*ssocreds.Options)) LoadOptionsFunc {
642653
return nil
643654
}
644655
}
656+
657+
// GetEC2IMDSClientEnableState implements a EC2IMDSClientEnableState options resolver interface.
658+
func (o LoadOptions) GetEC2IMDSClientEnableState() (imds.ClientEnableState, bool, error) {
659+
if o.EC2IMDSClientEnableState == imds.ClientDefaultEnableState {
660+
return imds.ClientDefaultEnableState, false, nil
661+
}
662+
663+
return o.EC2IMDSClientEnableState, true, nil
664+
}
665+
666+
// GetEC2IMDSEndpointMode implements a EC2IMDSEndpointMode option resolver interface.
667+
func (o LoadOptions) GetEC2IMDSEndpointMode() (imds.EndpointModeState, bool, error) {
668+
if o.EC2IMDSEndpointMode == imds.EndpointModeStateUnset {
669+
return imds.EndpointModeStateUnset, false, nil
670+
}
671+
672+
return o.EC2IMDSEndpointMode, true, nil
673+
}
674+
675+
// GetEC2IMDSEndpoint implements a EC2IMDSEndpoint option resolver interface.
676+
func (o LoadOptions) GetEC2IMDSEndpoint() (string, bool, error) {
677+
if len(o.EC2IMDSEndpoint) == 0 {
678+
return "", false, nil
679+
}
680+
681+
return o.EC2IMDSEndpoint, true, nil
682+
}
683+
684+
// WithEC2IMDSClientEnableState is a helper function to construct functional options that sets the EC2IMDSClientEnableState.
685+
func WithEC2IMDSClientEnableState(v imds.ClientEnableState) LoadOptionsFunc {
686+
return func(o *LoadOptions) error {
687+
o.EC2IMDSClientEnableState = v
688+
return nil
689+
}
690+
}
691+
692+
// WithEC2IMDSEndpointMode is a helper function to construct functional options that sets the EC2IMDSEndpointMode.
693+
func WithEC2IMDSEndpointMode(v imds.EndpointModeState) LoadOptionsFunc {
694+
return func(o *LoadOptions) error {
695+
o.EC2IMDSEndpointMode = v
696+
return nil
697+
}
698+
}
699+
700+
// WithEC2IMDSEndpoint is a helper function to construct functional options that sets the EC2IMDSEndpoint.
701+
func WithEC2IMDSEndpoint(v string) LoadOptionsFunc {
702+
return func(o *LoadOptions) error {
703+
o.EC2IMDSEndpoint = v
704+
return nil
705+
}
706+
}

config/resolve_credentials.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -289,14 +289,8 @@ func resolveEC2RoleCredentials(ctx context.Context, cfg *aws.Config, configs con
289289

290290
optFns = append(optFns, func(o *ec2rolecreds.Options) {
291291
// Only define a client from config if not already defined.
292-
if o.Client != nil {
293-
options := imds.Options{
294-
HTTPClient: cfg.HTTPClient,
295-
}
296-
if cfg.Retryer != nil {
297-
options.Retryer = cfg.Retryer()
298-
}
299-
o.Client = imds.New(options)
292+
if o.Client == nil {
293+
o.Client = imds.NewFromConfig(*cfg)
300294
}
301295
})
302296

0 commit comments

Comments
 (0)