shared_ini_file_credentials.js
10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
var AWS = require('../core');
var STS = require('../../clients/sts');
var iniLoader = AWS.util.iniLoader;
var ASSUME_ROLE_DEFAULT_REGION = 'us-east-1';
/**
* Represents credentials loaded from shared credentials file
* (defaulting to ~/.aws/credentials or defined by the
* `AWS_SHARED_CREDENTIALS_FILE` environment variable).
*
* ## Using the shared credentials file
*
* This provider is checked by default in the Node.js environment. To use the
* credentials file provider, simply add your access and secret keys to the
* ~/.aws/credentials file in the following format:
*
* [default]
* aws_access_key_id = AKID...
* aws_secret_access_key = YOUR_SECRET_KEY
*
* ## Using custom profiles
*
* The SDK supports loading credentials for separate profiles. This can be done
* in two ways:
*
* 1. Set the `AWS_PROFILE` environment variable in your process prior to
* loading the SDK.
* 2. Directly load the AWS.SharedIniFileCredentials provider:
*
* ```javascript
* var creds = new AWS.SharedIniFileCredentials({profile: 'myprofile'});
* AWS.config.credentials = creds;
* ```
*
* @!macro nobrowser
*/
AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
/**
* Creates a new SharedIniFileCredentials object.
*
* @param options [map] a set of options
* @option options profile [String] (AWS_PROFILE env var or 'default')
* the name of the profile to load.
* @option options filename [String] ('~/.aws/credentials' or defined by
* AWS_SHARED_CREDENTIALS_FILE process env var)
* the filename to use when loading credentials.
* @option options disableAssumeRole [Boolean] (false) True to disable
* support for profiles that assume an IAM role. If true, and an assume
* role profile is selected, an error is raised.
* @option options preferStaticCredentials [Boolean] (false) True to
* prefer static credentials to role_arn if both are present.
* @option options tokenCodeFn [Function] (null) Function to provide
* STS Assume Role TokenCode, if mfa_serial is provided for profile in ini
* file. Function is called with value of mfa_serial and callback, and
* should provide the TokenCode or an error to the callback in the format
* callback(err, token)
* @option options callback [Function] (err) Credentials are eagerly loaded
* by the constructor. When the callback is called with no error, the
* credentials have been loaded successfully.
* @option options httpOptions [map] A set of options to pass to the low-level
* HTTP request. Currently supported options are:
* * **proxy** [String] — the URL to proxy requests through
* * **agent** [http.Agent, https.Agent] — the Agent object to perform
* HTTP requests with. Used for connection pooling. Defaults to the global
* agent (`http.globalAgent`) for non-SSL connections. Note that for
* SSL connections, a special Agent object is used in order to enable
* peer certificate verification. This feature is only available in the
* Node.js environment.
* * **connectTimeout** [Integer] — Sets the socket to timeout after
* failing to establish a connection with the server after
* `connectTimeout` milliseconds. This timeout has no effect once a socket
* connection has been established.
* * **timeout** [Integer] — Sets the socket to timeout after timeout
* milliseconds of inactivity on the socket. Defaults to two minutes
* (120000).
*/
constructor: function SharedIniFileCredentials(options) {
AWS.Credentials.call(this);
options = options || {};
this.filename = options.filename;
this.profile = options.profile || process.env.AWS_PROFILE || AWS.util.defaultProfile;
this.disableAssumeRole = Boolean(options.disableAssumeRole);
this.preferStaticCredentials = Boolean(options.preferStaticCredentials);
this.tokenCodeFn = options.tokenCodeFn || null;
this.httpOptions = options.httpOptions || null;
this.get(options.callback || AWS.util.fn.noop);
},
/**
* @api private
*/
load: function load(callback) {
var self = this;
try {
var profiles = AWS.util.getProfilesFromSharedConfig(iniLoader, this.filename);
var profile = profiles[this.profile] || {};
if (Object.keys(profile).length === 0) {
throw AWS.util.error(
new Error('Profile ' + this.profile + ' not found'),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}
/*
In the CLI, the presence of both a role_arn and static credentials have
different meanings depending on how many profiles have been visited. For
the first profile processed, role_arn takes precedence over any static
credentials, but for all subsequent profiles, static credentials are
used if present, and only in their absence will the profile's
source_profile and role_arn keys be used to load another set of
credentials. This var is intended to yield compatible behaviour in this
sdk.
*/
var preferStaticCredentialsToRoleArn = Boolean(
this.preferStaticCredentials
&& profile['aws_access_key_id']
&& profile['aws_secret_access_key']
);
if (profile['role_arn'] && !preferStaticCredentialsToRoleArn) {
this.loadRoleProfile(profiles, profile, function(err, data) {
if (err) {
callback(err);
} else {
self.expired = false;
self.accessKeyId = data.Credentials.AccessKeyId;
self.secretAccessKey = data.Credentials.SecretAccessKey;
self.sessionToken = data.Credentials.SessionToken;
self.expireTime = data.Credentials.Expiration;
callback(null);
}
});
return;
}
this.accessKeyId = profile['aws_access_key_id'];
this.secretAccessKey = profile['aws_secret_access_key'];
this.sessionToken = profile['aws_session_token'];
if (!this.accessKeyId || !this.secretAccessKey) {
throw AWS.util.error(
new Error('Credentials not set for profile ' + this.profile),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}
this.expired = false;
callback(null);
} catch (err) {
callback(err);
}
},
/**
* Loads the credentials from the shared credentials file
*
* @callback callback function(err)
* Called after the shared INI file on disk is read and parsed. When this
* callback is called with no error, it means that the credentials
* information has been loaded into the object (as the `accessKeyId`,
* `secretAccessKey`, and `sessionToken` properties).
* @param err [Error] if an error occurred, this value will be filled
* @see get
*/
refresh: function refresh(callback) {
iniLoader.clearCachedFiles();
this.coalesceRefresh(
callback || AWS.util.fn.callback,
this.disableAssumeRole
);
},
/**
* @api private
*/
loadRoleProfile: function loadRoleProfile(creds, roleProfile, callback) {
if (this.disableAssumeRole) {
throw AWS.util.error(
new Error('Role assumption profiles are disabled. ' +
'Failed to load profile ' + this.profile +
' from ' + creds.filename),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}
var self = this;
var roleArn = roleProfile['role_arn'];
var roleSessionName = roleProfile['role_session_name'];
var externalId = roleProfile['external_id'];
var mfaSerial = roleProfile['mfa_serial'];
var sourceProfileName = roleProfile['source_profile'];
// From experimentation, the following behavior mimics the AWS CLI:
//
// 1. Use region from the profile if present.
// 2. Otherwise fall back to N. Virginia (global endpoint).
//
// It is necessary to do the fallback explicitly, because if
// 'AWS_STS_REGIONAL_ENDPOINTS=regional', the underlying STS client will
// otherwise throw an error if region is left 'undefined'.
//
// Experimentation shows that the AWS CLI (tested at version 1.18.136)
// ignores the following potential sources of a region for the purposes of
// this AssumeRole call:
//
// - The [default] profile
// - The AWS_REGION environment variable
//
// Ignoring the [default] profile for the purposes of AssumeRole is arguably
// a bug in the CLI since it does use the [default] region for service
// calls... but right now we're matching behavior of the other tool.
var profileRegion = roleProfile['region'] || ASSUME_ROLE_DEFAULT_REGION;
if (!sourceProfileName) {
throw AWS.util.error(
new Error('source_profile is not set using profile ' + this.profile),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}
var sourceProfileExistanceTest = creds[sourceProfileName];
if (typeof sourceProfileExistanceTest !== 'object') {
throw AWS.util.error(
new Error('source_profile ' + sourceProfileName + ' using profile '
+ this.profile + ' does not exist'),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}
var sourceCredentials = new AWS.SharedIniFileCredentials(
AWS.util.merge(this.options || {}, {
profile: sourceProfileName,
preferStaticCredentials: true
})
);
this.roleArn = roleArn;
var sts = new STS({
credentials: sourceCredentials,
region: profileRegion,
httpOptions: this.httpOptions
});
var roleParams = {
RoleArn: roleArn,
RoleSessionName: roleSessionName || 'aws-sdk-js-' + Date.now()
};
if (externalId) {
roleParams.ExternalId = externalId;
}
if (mfaSerial && self.tokenCodeFn) {
roleParams.SerialNumber = mfaSerial;
self.tokenCodeFn(mfaSerial, function(err, token) {
if (err) {
var message;
if (err instanceof Error) {
message = err.message;
} else {
message = err;
}
callback(
AWS.util.error(
new Error('Error fetching MFA token: ' + message),
{ code: 'SharedIniFileCredentialsProviderFailure' }
));
return;
}
roleParams.TokenCode = token;
sts.assumeRole(roleParams, callback);
});
return;
}
sts.assumeRole(roleParams, callback);
}
});