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	csmEnabled  string
105	CSMEnabled  *bool
106	CSMPort     string
107	CSMHost     string
108	CSMClientID string
109
110	// Enables endpoint discovery via environment variables.
111	//
112	//	AWS_ENABLE_ENDPOINT_DISCOVERY=true
113	EnableEndpointDiscovery *bool
114	enableEndpointDiscovery string
115
116	// Specifies the WebIdentity token the SDK should use to assume a role
117	// with.
118	//
119	//  AWS_WEB_IDENTITY_TOKEN_FILE=file_path
120	WebIdentityTokenFilePath string
121
122	// Specifies the IAM role arn to use when assuming an role.
123	//
124	//  AWS_ROLE_ARN=role_arn
125	RoleARN string
126
127	// Specifies the IAM role session name to use when assuming a role.
128	//
129	//  AWS_ROLE_SESSION_NAME=session_name
130	RoleSessionName string
131
132	// Specifies the STS Regional Endpoint flag for the SDK to resolve the endpoint
133	// for a service.
134	//
135	// AWS_STS_REGIONAL_ENDPOINTS=regional
136	// This can take value as `regional` or `legacy`
137	STSRegionalEndpoint endpoints.STSRegionalEndpoint
138
139	// Specifies the S3 Regional Endpoint flag for the SDK to resolve the
140	// endpoint for a service.
141	//
142	// AWS_S3_US_EAST_1_REGIONAL_ENDPOINT=regional
143	// This can take value as `regional` or `legacy`
144	S3UsEast1RegionalEndpoint endpoints.S3UsEast1RegionalEndpoint
145
146	// Specifies if the S3 service should allow ARNs to direct the region
147	// the client's requests are sent to.
148	//
149	// AWS_S3_USE_ARN_REGION=true
150	S3UseARNRegion bool
151}
152
153var (
154	csmEnabledEnvKey = []string{
155		"AWS_CSM_ENABLED",
156	}
157	csmHostEnvKey = []string{
158		"AWS_CSM_HOST",
159	}
160	csmPortEnvKey = []string{
161		"AWS_CSM_PORT",
162	}
163	csmClientIDEnvKey = []string{
164		"AWS_CSM_CLIENT_ID",
165	}
166	credAccessEnvKey = []string{
167		"AWS_ACCESS_KEY_ID",
168		"AWS_ACCESS_KEY",
169	}
170	credSecretEnvKey = []string{
171		"AWS_SECRET_ACCESS_KEY",
172		"AWS_SECRET_KEY",
173	}
174	credSessionEnvKey = []string{
175		"AWS_SESSION_TOKEN",
176	}
177
178	enableEndpointDiscoveryEnvKey = []string{
179		"AWS_ENABLE_ENDPOINT_DISCOVERY",
180	}
181
182	regionEnvKeys = []string{
183		"AWS_REGION",
184		"AWS_DEFAULT_REGION", // Only read if AWS_SDK_LOAD_CONFIG is also set
185	}
186	profileEnvKeys = []string{
187		"AWS_PROFILE",
188		"AWS_DEFAULT_PROFILE", // Only read if AWS_SDK_LOAD_CONFIG is also set
189	}
190	sharedCredsFileEnvKey = []string{
191		"AWS_SHARED_CREDENTIALS_FILE",
192	}
193	sharedConfigFileEnvKey = []string{
194		"AWS_CONFIG_FILE",
195	}
196	webIdentityTokenFilePathEnvKey = []string{
197		"AWS_WEB_IDENTITY_TOKEN_FILE",
198	}
199	roleARNEnvKey = []string{
200		"AWS_ROLE_ARN",
201	}
202	roleSessionNameEnvKey = []string{
203		"AWS_ROLE_SESSION_NAME",
204	}
205	stsRegionalEndpointKey = []string{
206		"AWS_STS_REGIONAL_ENDPOINTS",
207	}
208	s3UsEast1RegionalEndpoint = []string{
209		"AWS_S3_US_EAST_1_REGIONAL_ENDPOINT",
210	}
211	s3UseARNRegionEnvKey = []string{
212		"AWS_S3_USE_ARN_REGION",
213	}
214)
215
216// loadEnvConfig retrieves the SDK's environment configuration.
217// See `envConfig` for the values that will be retrieved.
218//
219// If the environment variable `AWS_SDK_LOAD_CONFIG` is set to a truthy value
220// the shared SDK config will be loaded in addition to the SDK's specific
221// configuration values.
222func loadEnvConfig() (envConfig, error) {
223	enableSharedConfig, _ := strconv.ParseBool(os.Getenv("AWS_SDK_LOAD_CONFIG"))
224	return envConfigLoad(enableSharedConfig)
225}
226
227// loadEnvSharedConfig retrieves the SDK's environment configuration, and the
228// SDK shared config. See `envConfig` for the values that will be retrieved.
229//
230// Loads the shared configuration in addition to the SDK's specific configuration.
231// This will load the same values as `loadEnvConfig` if the `AWS_SDK_LOAD_CONFIG`
232// environment variable is set.
233func loadSharedEnvConfig() (envConfig, error) {
234	return envConfigLoad(true)
235}
236
237func envConfigLoad(enableSharedConfig bool) (envConfig, error) {
238	cfg := envConfig{}
239
240	cfg.EnableSharedConfig = enableSharedConfig
241
242	// Static environment credentials
243	var creds credentials.Value
244	setFromEnvVal(&creds.AccessKeyID, credAccessEnvKey)
245	setFromEnvVal(&creds.SecretAccessKey, credSecretEnvKey)
246	setFromEnvVal(&creds.SessionToken, credSessionEnvKey)
247	if creds.HasKeys() {
248		// Require logical grouping of credentials
249		creds.ProviderName = EnvProviderName
250		cfg.Creds = creds
251	}
252
253	// Role Metadata
254	setFromEnvVal(&cfg.RoleARN, roleARNEnvKey)
255	setFromEnvVal(&cfg.RoleSessionName, roleSessionNameEnvKey)
256
257	// Web identity environment variables
258	setFromEnvVal(&cfg.WebIdentityTokenFilePath, webIdentityTokenFilePathEnvKey)
259
260	// CSM environment variables
261	setFromEnvVal(&cfg.csmEnabled, csmEnabledEnvKey)
262	setFromEnvVal(&cfg.CSMHost, csmHostEnvKey)
263	setFromEnvVal(&cfg.CSMPort, csmPortEnvKey)
264	setFromEnvVal(&cfg.CSMClientID, csmClientIDEnvKey)
265
266	if len(cfg.csmEnabled) != 0 {
267		v, _ := strconv.ParseBool(cfg.csmEnabled)
268		cfg.CSMEnabled = &v
269	}
270
271	regionKeys := regionEnvKeys
272	profileKeys := profileEnvKeys
273	if !cfg.EnableSharedConfig {
274		regionKeys = regionKeys[:1]
275		profileKeys = profileKeys[:1]
276	}
277
278	setFromEnvVal(&cfg.Region, regionKeys)
279	setFromEnvVal(&cfg.Profile, profileKeys)
280
281	// endpoint discovery is in reference to it being enabled.
282	setFromEnvVal(&cfg.enableEndpointDiscovery, enableEndpointDiscoveryEnvKey)
283	if len(cfg.enableEndpointDiscovery) > 0 {
284		cfg.EnableEndpointDiscovery = aws.Bool(cfg.enableEndpointDiscovery != "false")
285	}
286
287	setFromEnvVal(&cfg.SharedCredentialsFile, sharedCredsFileEnvKey)
288	setFromEnvVal(&cfg.SharedConfigFile, sharedConfigFileEnvKey)
289
290	if len(cfg.SharedCredentialsFile) == 0 {
291		cfg.SharedCredentialsFile = defaults.SharedCredentialsFilename()
292	}
293	if len(cfg.SharedConfigFile) == 0 {
294		cfg.SharedConfigFile = defaults.SharedConfigFilename()
295	}
296
297	cfg.CustomCABundle = os.Getenv("AWS_CA_BUNDLE")
298
299	var err error
300	// STS Regional Endpoint variable
301	for _, k := range stsRegionalEndpointKey {
302		if v := os.Getenv(k); len(v) != 0 {
303			cfg.STSRegionalEndpoint, err = endpoints.GetSTSRegionalEndpoint(v)
304			if err != nil {
305				return cfg, fmt.Errorf("failed to load, %v from env config, %v", k, err)
306			}
307		}
308	}
309
310	// S3 Regional Endpoint variable
311	for _, k := range s3UsEast1RegionalEndpoint {
312		if v := os.Getenv(k); len(v) != 0 {
313			cfg.S3UsEast1RegionalEndpoint, err = endpoints.GetS3UsEast1RegionalEndpoint(v)
314			if err != nil {
315				return cfg, fmt.Errorf("failed to load, %v from env config, %v", k, err)
316			}
317		}
318	}
319
320	var s3UseARNRegion string
321	setFromEnvVal(&s3UseARNRegion, s3UseARNRegionEnvKey)
322	if len(s3UseARNRegion) != 0 {
323		switch {
324		case strings.EqualFold(s3UseARNRegion, "false"):
325			cfg.S3UseARNRegion = false
326		case strings.EqualFold(s3UseARNRegion, "true"):
327			cfg.S3UseARNRegion = true
328		default:
329			return envConfig{}, fmt.Errorf(
330				"invalid value for environment variable, %s=%s, need true or false",
331				s3UseARNRegionEnvKey[0], s3UseARNRegion)
332		}
333	}
334
335	return cfg, nil
336}
337
338func setFromEnvVal(dst *string, keys []string) {
339	for _, k := range keys {
340		if v := os.Getenv(k); len(v) != 0 {
341			*dst = v
342			break
343		}
344	}
345}
346