]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package session |
2 | ||
3 | import ( | |
4 | "fmt" | |
bae9f6d2 JC |
5 | |
6 | "github.com/aws/aws-sdk-go/aws/awserr" | |
7 | "github.com/aws/aws-sdk-go/aws/credentials" | |
107c1cdb ND |
8 | |
9 | "github.com/aws/aws-sdk-go/internal/ini" | |
bae9f6d2 JC |
10 | ) |
11 | ||
12 | const ( | |
13 | // Static Credentials group | |
14 | accessKeyIDKey = `aws_access_key_id` // group required | |
15 | secretAccessKey = `aws_secret_access_key` // group required | |
16 | sessionTokenKey = `aws_session_token` // optional | |
17 | ||
18 | // Assume Role Credentials group | |
107c1cdb ND |
19 | roleArnKey = `role_arn` // group required |
20 | sourceProfileKey = `source_profile` // group required (or credential_source) | |
21 | credentialSourceKey = `credential_source` // group required (or source_profile) | |
22 | externalIDKey = `external_id` // optional | |
23 | mfaSerialKey = `mfa_serial` // optional | |
24 | roleSessionNameKey = `role_session_name` // optional | |
bae9f6d2 JC |
25 | |
26 | // Additional Config fields | |
27 | regionKey = `region` | |
28 | ||
107c1cdb ND |
29 | // endpoint discovery group |
30 | enableEndpointDiscoveryKey = `endpoint_discovery_enabled` // optional | |
31 | // External Credential Process | |
32 | credentialProcessKey = `credential_process` | |
33 | ||
bae9f6d2 JC |
34 | // DefaultSharedConfigProfile is the default profile to be used when |
35 | // loading configuration from the config files if another profile name | |
36 | // is not provided. | |
37 | DefaultSharedConfigProfile = `default` | |
38 | ) | |
39 | ||
40 | type assumeRoleConfig struct { | |
107c1cdb ND |
41 | RoleARN string |
42 | SourceProfile string | |
43 | CredentialSource string | |
44 | ExternalID string | |
45 | MFASerial string | |
46 | RoleSessionName string | |
bae9f6d2 JC |
47 | } |
48 | ||
49 | // sharedConfig represents the configuration fields of the SDK config files. | |
50 | type sharedConfig struct { | |
51 | // Credentials values from the config file. Both aws_access_key_id | |
52 | // and aws_secret_access_key must be provided together in the same file | |
53 | // to be considered valid. The values will be ignored if not a complete group. | |
54 | // aws_session_token is an optional field that can be provided if both of the | |
55 | // other two fields are also provided. | |
56 | // | |
57 | // aws_access_key_id | |
58 | // aws_secret_access_key | |
59 | // aws_session_token | |
60 | Creds credentials.Value | |
61 | ||
62 | AssumeRole assumeRoleConfig | |
63 | AssumeRoleSource *sharedConfig | |
64 | ||
107c1cdb ND |
65 | // An external process to request credentials |
66 | CredentialProcess string | |
67 | ||
bae9f6d2 JC |
68 | // Region is the region the SDK should use for looking up AWS service endpoints |
69 | // and signing requests. | |
70 | // | |
71 | // region | |
72 | Region string | |
107c1cdb ND |
73 | |
74 | // EnableEndpointDiscovery can be enabled in the shared config by setting | |
75 | // endpoint_discovery_enabled to true | |
76 | // | |
77 | // endpoint_discovery_enabled = true | |
78 | EnableEndpointDiscovery *bool | |
bae9f6d2 JC |
79 | } |
80 | ||
81 | type sharedConfigFile struct { | |
82 | Filename string | |
107c1cdb | 83 | IniData ini.Sections |
bae9f6d2 JC |
84 | } |
85 | ||
86 | // loadSharedConfig retrieves the configuration from the list of files | |
87 | // using the profile provided. The order the files are listed will determine | |
88 | // precedence. Values in subsequent files will overwrite values defined in | |
89 | // earlier files. | |
90 | // | |
91 | // For example, given two files A and B. Both define credentials. If the order | |
92 | // of the files are A then B, B's credential values will be used instead of A's. | |
93 | // | |
94 | // See sharedConfig.setFromFile for information how the config files | |
95 | // will be loaded. | |
96 | func loadSharedConfig(profile string, filenames []string) (sharedConfig, error) { | |
97 | if len(profile) == 0 { | |
98 | profile = DefaultSharedConfigProfile | |
99 | } | |
100 | ||
101 | files, err := loadSharedConfigIniFiles(filenames) | |
102 | if err != nil { | |
103 | return sharedConfig{}, err | |
104 | } | |
105 | ||
106 | cfg := sharedConfig{} | |
107 | if err = cfg.setFromIniFiles(profile, files); err != nil { | |
108 | return sharedConfig{}, err | |
109 | } | |
110 | ||
111 | if len(cfg.AssumeRole.SourceProfile) > 0 { | |
112 | if err := cfg.setAssumeRoleSource(profile, files); err != nil { | |
113 | return sharedConfig{}, err | |
114 | } | |
115 | } | |
116 | ||
117 | return cfg, nil | |
118 | } | |
119 | ||
120 | func loadSharedConfigIniFiles(filenames []string) ([]sharedConfigFile, error) { | |
121 | files := make([]sharedConfigFile, 0, len(filenames)) | |
122 | ||
123 | for _, filename := range filenames { | |
107c1cdb ND |
124 | sections, err := ini.OpenFile(filename) |
125 | if aerr, ok := err.(awserr.Error); ok && aerr.Code() == ini.ErrCodeUnableToReadFile { | |
bae9f6d2 JC |
126 | // Skip files which can't be opened and read for whatever reason |
127 | continue | |
107c1cdb | 128 | } else if err != nil { |
9b12e4fe | 129 | return nil, SharedConfigLoadError{Filename: filename, Err: err} |
bae9f6d2 JC |
130 | } |
131 | ||
132 | files = append(files, sharedConfigFile{ | |
107c1cdb | 133 | Filename: filename, IniData: sections, |
bae9f6d2 JC |
134 | }) |
135 | } | |
136 | ||
137 | return files, nil | |
138 | } | |
139 | ||
140 | func (cfg *sharedConfig) setAssumeRoleSource(origProfile string, files []sharedConfigFile) error { | |
141 | var assumeRoleSrc sharedConfig | |
142 | ||
107c1cdb ND |
143 | if len(cfg.AssumeRole.CredentialSource) > 0 { |
144 | // setAssumeRoleSource is only called when source_profile is found. | |
145 | // If both source_profile and credential_source are set, then | |
146 | // ErrSharedConfigSourceCollision will be returned | |
147 | return ErrSharedConfigSourceCollision | |
148 | } | |
149 | ||
bae9f6d2 JC |
150 | // Multiple level assume role chains are not support |
151 | if cfg.AssumeRole.SourceProfile == origProfile { | |
152 | assumeRoleSrc = *cfg | |
153 | assumeRoleSrc.AssumeRole = assumeRoleConfig{} | |
154 | } else { | |
155 | err := assumeRoleSrc.setFromIniFiles(cfg.AssumeRole.SourceProfile, files) | |
156 | if err != nil { | |
157 | return err | |
158 | } | |
159 | } | |
160 | ||
161 | if len(assumeRoleSrc.Creds.AccessKeyID) == 0 { | |
162 | return SharedConfigAssumeRoleError{RoleARN: cfg.AssumeRole.RoleARN} | |
163 | } | |
164 | ||
165 | cfg.AssumeRoleSource = &assumeRoleSrc | |
166 | ||
167 | return nil | |
168 | } | |
169 | ||
170 | func (cfg *sharedConfig) setFromIniFiles(profile string, files []sharedConfigFile) error { | |
171 | // Trim files from the list that don't exist. | |
172 | for _, f := range files { | |
173 | if err := cfg.setFromIniFile(profile, f); err != nil { | |
174 | if _, ok := err.(SharedConfigProfileNotExistsError); ok { | |
175 | // Ignore proviles missings | |
176 | continue | |
177 | } | |
178 | return err | |
179 | } | |
180 | } | |
181 | ||
182 | return nil | |
183 | } | |
184 | ||
185 | // setFromFile loads the configuration from the file using | |
186 | // the profile provided. A sharedConfig pointer type value is used so that | |
187 | // multiple config file loadings can be chained. | |
188 | // | |
189 | // Only loads complete logically grouped values, and will not set fields in cfg | |
190 | // for incomplete grouped values in the config. Such as credentials. For example | |
191 | // if a config file only includes aws_access_key_id but no aws_secret_access_key | |
192 | // the aws_access_key_id will be ignored. | |
193 | func (cfg *sharedConfig) setFromIniFile(profile string, file sharedConfigFile) error { | |
107c1cdb ND |
194 | section, ok := file.IniData.GetSection(profile) |
195 | if !ok { | |
bae9f6d2 | 196 | // Fallback to to alternate profile name: profile <name> |
107c1cdb ND |
197 | section, ok = file.IniData.GetSection(fmt.Sprintf("profile %s", profile)) |
198 | if !ok { | |
199 | return SharedConfigProfileNotExistsError{Profile: profile, Err: nil} | |
bae9f6d2 JC |
200 | } |
201 | } | |
202 | ||
203 | // Shared Credentials | |
107c1cdb ND |
204 | akid := section.String(accessKeyIDKey) |
205 | secret := section.String(secretAccessKey) | |
bae9f6d2 JC |
206 | if len(akid) > 0 && len(secret) > 0 { |
207 | cfg.Creds = credentials.Value{ | |
208 | AccessKeyID: akid, | |
209 | SecretAccessKey: secret, | |
107c1cdb | 210 | SessionToken: section.String(sessionTokenKey), |
bae9f6d2 JC |
211 | ProviderName: fmt.Sprintf("SharedConfigCredentials: %s", file.Filename), |
212 | } | |
213 | } | |
214 | ||
215 | // Assume Role | |
107c1cdb ND |
216 | roleArn := section.String(roleArnKey) |
217 | srcProfile := section.String(sourceProfileKey) | |
218 | credentialSource := section.String(credentialSourceKey) | |
219 | hasSource := len(srcProfile) > 0 || len(credentialSource) > 0 | |
220 | if len(roleArn) > 0 && hasSource { | |
bae9f6d2 | 221 | cfg.AssumeRole = assumeRoleConfig{ |
107c1cdb ND |
222 | RoleARN: roleArn, |
223 | SourceProfile: srcProfile, | |
224 | CredentialSource: credentialSource, | |
225 | ExternalID: section.String(externalIDKey), | |
226 | MFASerial: section.String(mfaSerialKey), | |
227 | RoleSessionName: section.String(roleSessionNameKey), | |
bae9f6d2 JC |
228 | } |
229 | } | |
230 | ||
107c1cdb ND |
231 | // `credential_process` |
232 | if credProc := section.String(credentialProcessKey); len(credProc) > 0 { | |
233 | cfg.CredentialProcess = credProc | |
234 | } | |
235 | ||
bae9f6d2 | 236 | // Region |
107c1cdb | 237 | if v := section.String(regionKey); len(v) > 0 { |
bae9f6d2 JC |
238 | cfg.Region = v |
239 | } | |
240 | ||
107c1cdb ND |
241 | // Endpoint discovery |
242 | if section.Has(enableEndpointDiscoveryKey) { | |
243 | v := section.Bool(enableEndpointDiscoveryKey) | |
244 | cfg.EnableEndpointDiscovery = &v | |
245 | } | |
246 | ||
bae9f6d2 JC |
247 | return nil |
248 | } | |
249 | ||
250 | // SharedConfigLoadError is an error for the shared config file failed to load. | |
251 | type SharedConfigLoadError struct { | |
252 | Filename string | |
253 | Err error | |
254 | } | |
255 | ||
256 | // Code is the short id of the error. | |
257 | func (e SharedConfigLoadError) Code() string { | |
258 | return "SharedConfigLoadError" | |
259 | } | |
260 | ||
261 | // Message is the description of the error | |
262 | func (e SharedConfigLoadError) Message() string { | |
263 | return fmt.Sprintf("failed to load config file, %s", e.Filename) | |
264 | } | |
265 | ||
266 | // OrigErr is the underlying error that caused the failure. | |
267 | func (e SharedConfigLoadError) OrigErr() error { | |
268 | return e.Err | |
269 | } | |
270 | ||
271 | // Error satisfies the error interface. | |
272 | func (e SharedConfigLoadError) Error() string { | |
273 | return awserr.SprintError(e.Code(), e.Message(), "", e.Err) | |
274 | } | |
275 | ||
276 | // SharedConfigProfileNotExistsError is an error for the shared config when | |
277 | // the profile was not find in the config file. | |
278 | type SharedConfigProfileNotExistsError struct { | |
279 | Profile string | |
280 | Err error | |
281 | } | |
282 | ||
283 | // Code is the short id of the error. | |
284 | func (e SharedConfigProfileNotExistsError) Code() string { | |
285 | return "SharedConfigProfileNotExistsError" | |
286 | } | |
287 | ||
288 | // Message is the description of the error | |
289 | func (e SharedConfigProfileNotExistsError) Message() string { | |
290 | return fmt.Sprintf("failed to get profile, %s", e.Profile) | |
291 | } | |
292 | ||
293 | // OrigErr is the underlying error that caused the failure. | |
294 | func (e SharedConfigProfileNotExistsError) OrigErr() error { | |
295 | return e.Err | |
296 | } | |
297 | ||
298 | // Error satisfies the error interface. | |
299 | func (e SharedConfigProfileNotExistsError) Error() string { | |
300 | return awserr.SprintError(e.Code(), e.Message(), "", e.Err) | |
301 | } | |
302 | ||
303 | // SharedConfigAssumeRoleError is an error for the shared config when the | |
304 | // profile contains assume role information, but that information is invalid | |
305 | // or not complete. | |
306 | type SharedConfigAssumeRoleError struct { | |
307 | RoleARN string | |
308 | } | |
309 | ||
310 | // Code is the short id of the error. | |
311 | func (e SharedConfigAssumeRoleError) Code() string { | |
312 | return "SharedConfigAssumeRoleError" | |
313 | } | |
314 | ||
315 | // Message is the description of the error | |
316 | func (e SharedConfigAssumeRoleError) Message() string { | |
317 | return fmt.Sprintf("failed to load assume role for %s, source profile has no shared credentials", | |
318 | e.RoleARN) | |
319 | } | |
320 | ||
321 | // OrigErr is the underlying error that caused the failure. | |
322 | func (e SharedConfigAssumeRoleError) OrigErr() error { | |
323 | return nil | |
324 | } | |
325 | ||
326 | // Error satisfies the error interface. | |
327 | func (e SharedConfigAssumeRoleError) Error() string { | |
328 | return awserr.SprintError(e.Code(), e.Message(), "", nil) | |
329 | } |