1package session
2
3import (
4	"fmt"
5	"os"
6	"strconv"
7	"strings"
8
9	"github.com/aws/aws-sdk-go/aws"
10	"github.com/aws/aws-sdk-go/aws/credentials"
11	"github.com/aws/aws-sdk-go/aws/defaults"
12	"github.com/aws/aws-sdk-go/aws/endpoints"
13)
14
15// EnvProviderName provides a name of the provider when config is loaded from environment.
16const EnvProviderName = "EnvConfigCredentials"
17
18// envConfig is a collection of environment values the SDK will read
19// setup config from. All environment values are optional. But some values
20// such as credentials require multiple values to be complete or the values
21// will be ignored.
22type envConfig struct {
23	// Environment configuration values. If set both Access Key ID and Secret Access
24	// Key must be provided. Session Token and optionally also be provided, but is
25	// not required.
26	//
27	//	# Access Key ID
28	//	AWS_ACCESS_KEY_ID=AKID
29	//	AWS_ACCESS_KEY=AKID # only read if AWS_ACCESS_KEY_ID is not set.
30	//
31	//	# Secret Access Key
32	//	AWS_SECRET_ACCESS_KEY=SECRET
33	//	AWS_SECRET_KEY=SECRET=SECRET # only read if AWS_SECRET_ACCESS_KEY is not set.
34	//
35	//	# Session Token
36	//	AWS_SESSION_TOKEN=TOKEN
37	Creds credentials.Value
38
39	// Region value will instruct the SDK where to make service API requests to. If is
40	// not provided in the environment the region must be provided before a service
41	// client request is made.
42	//
43	//	AWS_REGION=us-east-1
44	//
45	//	# AWS_DEFAULT_REGION is only read if AWS_SDK_LOAD_CONFIG is also set,
46	//	# and AWS_REGION is not also set.
47	//	AWS_DEFAULT_REGION=us-east-1
48	Region string
49
50	// Profile name the SDK should load use when loading shared configuration from the
51	// shared configuration files. If not provided "default" will be used as the
52	// profile name.
53	//
54	//	AWS_PROFILE=my_profile
55	//
56	//	# AWS_DEFAULT_PROFILE is only read if AWS_SDK_LOAD_CONFIG is also set,
57	//	# and AWS_PROFILE is not also set.
58	//	AWS_DEFAULT_PROFILE=my_profile
59	Profile string
60
61	// SDK load config instructs the SDK to load the shared config in addition to
62	// shared credentials. This also expands the configuration loaded from the shared
63	// credentials to have parity with the shared config file. This also enables
64	// Region and Profile support for the AWS_DEFAULT_REGION and AWS_DEFAULT_PROFILE
65	// env values as well.
66	//
67	//	AWS_SDK_LOAD_CONFIG=1
68	EnableSharedConfig bool
69
70	// Shared credentials file path can be set to instruct the SDK to use an alternate
71	// file for the shared credentials. If not set the file will be loaded from
72	// $HOME/.aws/credentials on Linux/Unix based systems, and
73	// %USERPROFILE%\.aws\credentials on Windows.
74	//
75	//	AWS_SHARED_CREDENTIALS_FILE=$HOME/my_shared_credentials
76	SharedCredentialsFile string
77
78	// Shared config file path can be set to instruct the SDK to use an alternate
79	// file for the shared config. If not set the file will be loaded from
80	// $HOME/.aws/config on Linux/Unix based systems, and
81	// %USERPROFILE%\.aws\config on Windows.
82	//
83	//	AWS_CONFIG_FILE=$HOME/my_shared_config
84	SharedConfigFile string
85
86	// Sets the path to a custom Credentials Authority (CA) Bundle PEM file
87	// that the SDK will use instead of the system's root CA bundle.
88	// Only use this if you want to configure the SDK to use a custom set
89	// of CAs.
90	//
91	// Enabling this option will attempt to merge the Transport
92	// into the SDK's HTTP client. If the client's Transport is
93	// not a http.Transport an error will be returned. If the
94	// Transport's TLS config is set this option will cause the
95	// SDK to overwrite the Transport's TLS config's  RootCAs value.
96	//
97	// Setting a custom HTTPClient in the aws.Config options will override this setting.
98	// To use this option and custom HTTP client, the HTTP client needs to be provided
99	// when creating the session. Not the service client.
100	//
101	//  AWS_CA_BUNDLE=$HOME/my_custom_ca_bundle
102	CustomCABundle string
103
104	// Sets the TLC client certificate that should be used by the SDK's HTTP transport
105	// when making requests. The certificate must be paired with a TLS client key file.
106	//
107	//  AWS_SDK_GO_CLIENT_TLS_CERT=$HOME/my_client_cert
108	ClientTLSCert string
109
110	// Sets the TLC client key that should be used by the SDK's HTTP transport
111	// when making requests. The key must be paired with a TLS client certificate file.
112	//
113	//  AWS_SDK_GO_CLIENT_TLS_KEY=$HOME/my_client_key
114	ClientTLSKey string
115
116	csmEnabled  string
117	CSMEnabled  *bool
118	CSMPort     string
119	CSMHost     string
120	CSMClientID string
121
122	// Enables endpoint discovery via environment variables.
123	//
124	//	AWS_ENABLE_ENDPOINT_DISCOVERY=true
125	EnableEndpointDiscovery *bool
126	enableEndpointDiscovery string
127
128	// Specifies the WebIdentity token the SDK should use to assume a role
129	// with.
130	//
131	//  AWS_WEB_IDENTITY_TOKEN_FILE=file_path
132	WebIdentityTokenFilePath string
133
134	// Specifies the IAM role arn to use when assuming an role.
135	//
136	//  AWS_ROLE_ARN=role_arn
137	RoleARN string
138
139	// Specifies the IAM role session name to use when assuming a role.
140	//
141	//  AWS_ROLE_SESSION_NAME=session_name
142	RoleSessionName string
143
144	// Specifies the STS Regional Endpoint flag for the SDK to resolve the endpoint
145	// for a service.
146	//
147	// AWS_STS_REGIONAL_ENDPOINTS=regional
148	// This can take value as `regional` or `legacy`
149	STSRegionalEndpoint endpoints.STSRegionalEndpoint
150
151	// Specifies the S3 Regional Endpoint flag for the SDK to resolve the
152	// endpoint for a service.
153	//
154	// AWS_S3_US_EAST_1_REGIONAL_ENDPOINT=regional
155	// This can take value as `regional` or `legacy`
156	S3UsEast1RegionalEndpoint endpoints.S3UsEast1RegionalEndpoint
157
158	// Specifies if the S3 service should allow ARNs to direct the region
159	// the client's requests are sent to.
160	//
161	// AWS_S3_USE_ARN_REGION=true
162	S3UseARNRegion bool
163
164	// Specifies the EC2 Instance Metadata Service endpoint to use. If specified it overrides EC2IMDSEndpointMode.
165	//
166	// AWS_EC2_METADATA_SERVICE_ENDPOINT=http://[::1]
167	EC2IMDSEndpoint string
168
169	// Specifies the EC2 Instance Metadata Service default endpoint selection mode (IPv4 or IPv6)
170	//
171	// AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE=IPv6
172	EC2IMDSEndpointMode endpoints.EC2IMDSEndpointModeState
173}
174
175var (
176	csmEnabledEnvKey = []string{
177		"AWS_CSM_ENABLED",
178	}
179	csmHostEnvKey = []string{
180		"AWS_CSM_HOST",
181	}
182	csmPortEnvKey = []string{
183		"AWS_CSM_PORT",
184	}
185	csmClientIDEnvKey = []string{
186		"AWS_CSM_CLIENT_ID",
187	}
188	credAccessEnvKey = []string{
189		"AWS_ACCESS_KEY_ID",
190		"AWS_ACCESS_KEY",
191	}
192	credSecretEnvKey = []string{
193		"AWS_SECRET_ACCESS_KEY",
194		"AWS_SECRET_KEY",
195	}
196	credSessionEnvKey = []string{
197		"AWS_SESSION_TOKEN",
198	}
199
200	enableEndpointDiscoveryEnvKey = []string{
201		"AWS_ENABLE_ENDPOINT_DISCOVERY",
202	}
203
204	regionEnvKeys = []string{
205		"AWS_REGION",
206		"AWS_DEFAULT_REGION", // Only read if AWS_SDK_LOAD_CONFIG is also set
207	}
208	profileEnvKeys = []string{
209		"AWS_PROFILE",
210		"AWS_DEFAULT_PROFILE", // Only read if AWS_SDK_LOAD_CONFIG is also set
211	}
212	sharedCredsFileEnvKey = []string{
213		"AWS_SHARED_CREDENTIALS_FILE",
214	}
215	sharedConfigFileEnvKey = []string{
216		"AWS_CONFIG_FILE",
217	}
218	webIdentityTokenFilePathEnvKey = []string{
219		"AWS_WEB_IDENTITY_TOKEN_FILE",
220	}
221	roleARNEnvKey = []string{
222		"AWS_ROLE_ARN",
223	}
224	roleSessionNameEnvKey = []string{
225		"AWS_ROLE_SESSION_NAME",
226	}
227	stsRegionalEndpointKey = []string{
228		"AWS_STS_REGIONAL_ENDPOINTS",
229	}
230	s3UsEast1RegionalEndpoint = []string{
231		"AWS_S3_US_EAST_1_REGIONAL_ENDPOINT",
232	}
233	s3UseARNRegionEnvKey = []string{
234		"AWS_S3_USE_ARN_REGION",
235	}
236	ec2IMDSEndpointEnvKey = []string{
237		"AWS_EC2_METADATA_SERVICE_ENDPOINT",
238	}
239	ec2IMDSEndpointModeEnvKey = []string{
240		"AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE",
241	}
242	useCABundleKey = []string{
243		"AWS_CA_BUNDLE",
244	}
245	useClientTLSCert = []string{
246		"AWS_SDK_GO_CLIENT_TLS_CERT",
247	}
248	useClientTLSKey = []string{
249		"AWS_SDK_GO_CLIENT_TLS_KEY",
250	}
251)
252
253// loadEnvConfig retrieves the SDK's environment configuration.
254// See `envConfig` for the values that will be retrieved.
255//
256// If the environment variable `AWS_SDK_LOAD_CONFIG` is set to a truthy value
257// the shared SDK config will be loaded in addition to the SDK's specific
258// configuration values.
259func loadEnvConfig() (envConfig, error) {
260	enableSharedConfig, _ := strconv.ParseBool(os.Getenv("AWS_SDK_LOAD_CONFIG"))
261	return envConfigLoad(enableSharedConfig)
262}
263
264// loadEnvSharedConfig retrieves the SDK's environment configuration, and the
265// SDK shared config. See `envConfig` for the values that will be retrieved.
266//
267// Loads the shared configuration in addition to the SDK's specific configuration.
268// This will load the same values as `loadEnvConfig` if the `AWS_SDK_LOAD_CONFIG`
269// environment variable is set.
270func loadSharedEnvConfig() (envConfig, error) {
271	return envConfigLoad(true)
272}
273
274func envConfigLoad(enableSharedConfig bool) (envConfig, error) {
275	cfg := envConfig{}
276
277	cfg.EnableSharedConfig = enableSharedConfig
278
279	// Static environment credentials
280	var creds credentials.Value
281	setFromEnvVal(&creds.AccessKeyID, credAccessEnvKey)
282	setFromEnvVal(&creds.SecretAccessKey, credSecretEnvKey)
283	setFromEnvVal(&creds.SessionToken, credSessionEnvKey)
284	if creds.HasKeys() {
285		// Require logical grouping of credentials
286		creds.ProviderName = EnvProviderName
287		cfg.Creds = creds
288	}
289
290	// Role Metadata
291	setFromEnvVal(&cfg.RoleARN, roleARNEnvKey)
292	setFromEnvVal(&cfg.RoleSessionName, roleSessionNameEnvKey)
293
294	// Web identity environment variables
295	setFromEnvVal(&cfg.WebIdentityTokenFilePath, webIdentityTokenFilePathEnvKey)
296
297	// CSM environment variables
298	setFromEnvVal(&cfg.csmEnabled, csmEnabledEnvKey)
299	setFromEnvVal(&cfg.CSMHost, csmHostEnvKey)
300	setFromEnvVal(&cfg.CSMPort, csmPortEnvKey)
301	setFromEnvVal(&cfg.CSMClientID, csmClientIDEnvKey)
302
303	if len(cfg.csmEnabled) != 0 {
304		v, _ := strconv.ParseBool(cfg.csmEnabled)
305		cfg.CSMEnabled = &v
306	}
307
308	regionKeys := regionEnvKeys
309	profileKeys := profileEnvKeys
310	if !cfg.EnableSharedConfig {
311		regionKeys = regionKeys[:1]
312		profileKeys = profileKeys[:1]
313	}
314
315	setFromEnvVal(&cfg.Region, regionKeys)
316	setFromEnvVal(&cfg.Profile, profileKeys)
317
318	// endpoint discovery is in reference to it being enabled.
319	setFromEnvVal(&cfg.enableEndpointDiscovery, enableEndpointDiscoveryEnvKey)
320	if len(cfg.enableEndpointDiscovery) > 0 {
321		cfg.EnableEndpointDiscovery = aws.Bool(cfg.enableEndpointDiscovery != "false")
322	}
323
324	setFromEnvVal(&cfg.SharedCredentialsFile, sharedCredsFileEnvKey)
325	setFromEnvVal(&cfg.SharedConfigFile, sharedConfigFileEnvKey)
326
327	if len(cfg.SharedCredentialsFile) == 0 {
328		cfg.SharedCredentialsFile = defaults.SharedCredentialsFilename()
329	}
330	if len(cfg.SharedConfigFile) == 0 {
331		cfg.SharedConfigFile = defaults.SharedConfigFilename()
332	}
333
334	setFromEnvVal(&cfg.CustomCABundle, useCABundleKey)
335	setFromEnvVal(&cfg.ClientTLSCert, useClientTLSCert)
336	setFromEnvVal(&cfg.ClientTLSKey, useClientTLSKey)
337
338	var err error
339	// STS Regional Endpoint variable
340	for _, k := range stsRegionalEndpointKey {
341		if v := os.Getenv(k); len(v) != 0 {
342			cfg.STSRegionalEndpoint, err = endpoints.GetSTSRegionalEndpoint(v)
343			if err != nil {
344				return cfg, fmt.Errorf("failed to load, %v from env config, %v", k, err)
345			}
346		}
347	}
348
349	// S3 Regional Endpoint variable
350	for _, k := range s3UsEast1RegionalEndpoint {
351		if v := os.Getenv(k); len(v) != 0 {
352			cfg.S3UsEast1RegionalEndpoint, err = endpoints.GetS3UsEast1RegionalEndpoint(v)
353			if err != nil {
354				return cfg, fmt.Errorf("failed to load, %v from env config, %v", k, err)
355			}
356		}
357	}
358
359	var s3UseARNRegion string
360	setFromEnvVal(&s3UseARNRegion, s3UseARNRegionEnvKey)
361	if len(s3UseARNRegion) != 0 {
362		switch {
363		case strings.EqualFold(s3UseARNRegion, "false"):
364			cfg.S3UseARNRegion = false
365		case strings.EqualFold(s3UseARNRegion, "true"):
366			cfg.S3UseARNRegion = true
367		default:
368			return envConfig{}, fmt.Errorf(
369				"invalid value for environment variable, %s=%s, need true or false",
370				s3UseARNRegionEnvKey[0], s3UseARNRegion)
371		}
372	}
373
374	setFromEnvVal(&cfg.EC2IMDSEndpoint, ec2IMDSEndpointEnvKey)
375	if err := setEC2IMDSEndpointMode(&cfg.EC2IMDSEndpointMode, ec2IMDSEndpointModeEnvKey); err != nil {
376		return envConfig{}, err
377	}
378
379	return cfg, nil
380}
381
382func setFromEnvVal(dst *string, keys []string) {
383	for _, k := range keys {
384		if v := os.Getenv(k); len(v) != 0 {
385			*dst = v
386			break
387		}
388	}
389}
390
391func setEC2IMDSEndpointMode(mode *endpoints.EC2IMDSEndpointModeState, keys []string) error {
392	for _, k := range keys {
393		value := os.Getenv(k)
394		if len(value) == 0 {
395			continue
396		}
397		if err := mode.SetFromString(value); err != nil {
398			return fmt.Errorf("invalid value for environment variable, %s=%s, %v", k, value, err)
399		}
400		return nil
401	}
402	return nil
403}
404