Skip to content

Commit 4fdaa97

Browse files
authored
Merge pull request #1058 from LiuJoyceC/ecsCred
ECSCredentials
2 parents 9787eaa + d15a412 commit 4fdaa97

7 files changed

+300
-22
lines changed

lib/aws.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,25 @@ require('./services');
1515

1616
// Load custom credential providers
1717
require('./credentials/ec2_metadata_credentials');
18+
require('./credentials/ecs_credentials');
1819
require('./credentials/environment_credentials');
1920
require('./credentials/file_system_credentials');
2021
require('./credentials/shared_ini_file_credentials');
2122

2223
// Setup default chain providers
24+
// If this changes, please update documentation for
25+
// AWS.CredentialProviderChain.defaultProviders in
26+
// credentials/credential_provider_chain.js
2327
AWS.CredentialProviderChain.defaultProviders = [
2428
function () { return new AWS.EnvironmentCredentials('AWS'); },
2529
function () { return new AWS.EnvironmentCredentials('AMAZON'); },
2630
function () { return new AWS.SharedIniFileCredentials(); },
27-
function () { return new AWS.EC2MetadataCredentials(); }
31+
function () {
32+
if (AWS.ECSCredentials.prototype.getECSRelativeUri() !== undefined) {
33+
return new AWS.ECSCredentials();
34+
}
35+
return new AWS.EC2MetadataCredentials();
36+
}
2837
];
2938

3039
// Update configuration keys

lib/credentials/credential_provider_chain.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,12 @@ AWS.CredentialProviderChain = AWS.util.inherit(AWS.Credentials, {
123123
* function () { return new AWS.EnvironmentCredentials('AWS'); },
124124
* function () { return new AWS.EnvironmentCredentials('AMAZON'); },
125125
* function () { return new AWS.SharedIniFileCredentials(); },
126-
* function () { return new AWS.EC2MetadataCredentials(); }
126+
* function () {
127+
* // if AWS_CONTAINER_CREDENTIALS_RELATIVE_URI is set
128+
* return new AWS.ECSCredentials();
129+
* // else
130+
* return new AWS.EC2MetadataCredentials();
131+
* }
127132
* ]
128133
* ```
129134
*/

lib/credentials/ecs_credentials.js

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
var AWS = require('../core');
2+
3+
/**
4+
* Represents credentials received from relative URI specified in the ECS container.
5+
*
6+
* This class will request refreshable credentials from the relative URI
7+
* specified by the AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment variable
8+
* in the container. If valid credentials are returned in the response, these
9+
* will be used with zero configuration.
10+
*
11+
* This credentials class will timeout after 1 second of inactivity by default.
12+
* If your requests to the relative URI are timing out, you can increase
13+
* the value by configuring them directly:
14+
*
15+
* ```javascript
16+
* AWS.config.credentials = new AWS.ECSCredentials({
17+
* httpOptions: { timeout: 5000 } // 5 second timeout
18+
* });
19+
* ```
20+
*
21+
* @!macro nobrowser
22+
*/
23+
AWS.ECSCredentials = AWS.util.inherit(AWS.Credentials, {
24+
constructor: function ECSCredentials(options) {
25+
AWS.Credentials.call(this);
26+
options = options ? AWS.util.copy(options) : {};
27+
if (!options.httpOptions) options.httpOptions = {};
28+
options.httpOptions = AWS.util.merge(
29+
this.httpOptions, options.httpOptions);
30+
AWS.util.update(this, options);
31+
},
32+
33+
/**
34+
* @api private
35+
*/
36+
httpOptions: { timeout: 1000 },
37+
38+
/**
39+
* @api private
40+
*/
41+
host: '169.254.170.2',
42+
43+
/**
44+
* Sets the name of the ECS environment variable to check for relative URI
45+
* If changed, please change the name in the documentation for defaultProvider
46+
* in credential_provider_chain.js and in all tests in test/credentials.spec.coffee
47+
*
48+
* @api private
49+
*/
50+
environmentVar: 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI',
51+
52+
/**
53+
* @api private
54+
*/
55+
getECSRelativeUri: function getECSRelativeUri() {
56+
if (process && process.env) return process.env[this.environmentVar];
57+
},
58+
59+
/**
60+
* @api private
61+
*/
62+
credsFormatIsValid: function credsFormatIsValid(credData) {
63+
return (!!credData.AccessKeyId && !!credData.SecretAccessKey &&
64+
!!credData.Token && !!credData.Expiration);
65+
},
66+
67+
/**
68+
* @api private
69+
*/
70+
request: function request(path, callback) {
71+
path = path || '/';
72+
73+
var data = '';
74+
var http = AWS.HttpClient.getInstance();
75+
var httpRequest = new AWS.HttpRequest('http://' + this.host + path);
76+
httpRequest.method = 'GET';
77+
httpRequest.headers.Accept = 'application/json';
78+
var httpOptions = this.httpOptions;
79+
80+
process.nextTick(function() {
81+
http.handleRequest(httpRequest, httpOptions, function(httpResponse) {
82+
httpResponse.on('data', function(chunk) { data += chunk.toString(); });
83+
httpResponse.on('end', function() { callback(null, data); });
84+
}, callback);
85+
});
86+
},
87+
88+
/**
89+
* Loads the credentials from the relative URI specified by container
90+
*
91+
* @callback callback function(err)
92+
* Called when the request to the relative URI responds (or fails). When
93+
* this callback is called with no error, it means that the credentials
94+
* information has been loaded into the object (as the `accessKeyId`,
95+
* `secretAccessKey`, `sessionToken`, and `expireTime` properties).
96+
* @param err [Error] if an error occurred, this value will be filled
97+
* @see get
98+
*/
99+
refresh: function refresh(callback) {
100+
var self = this;
101+
if (!callback) callback = function(err) { if (err) throw err; };
102+
103+
if (process === undefined) {
104+
callback(AWS.util.error(
105+
new Error('No process info available'),
106+
{ code: 'ECSCredentialsProviderFailure' }
107+
));
108+
return;
109+
}
110+
var relativeUri = this.getECSRelativeUri();
111+
if (relativeUri === undefined) {
112+
callback(AWS.util.error(
113+
new Error('Variable ' + this.environmentVar + ' not set.'),
114+
{ code: 'ECSCredentialsProviderFailure' }
115+
));
116+
return;
117+
}
118+
119+
this.request(relativeUri, function(err, data) {
120+
if (!err) {
121+
try {
122+
var creds = JSON.parse(data);
123+
if (self.credsFormatIsValid(creds)) {
124+
self.expired = false;
125+
self.accessKeyId = creds.AccessKeyId;
126+
self.secretAccessKey = creds.SecretAccessKey;
127+
self.sessionToken = creds.Token;
128+
self.expireTime = new Date(creds.Expiration);
129+
} else {
130+
throw AWS.util.error(
131+
new Error('Response data is not in valid format'),
132+
{ code: 'ECSCredentialsProviderFailure' }
133+
);
134+
}
135+
} catch (dataError) {
136+
err = dataError;
137+
}
138+
}
139+
callback(err);
140+
});
141+
}
142+
});

lib/credentials/environment_credentials.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,11 @@ AWS.EnvironmentCredentials = AWS.util.inherit(AWS.Credentials, {
5959
refresh: function refresh(callback) {
6060
if (!callback) callback = function(err) { if (err) throw err; };
6161

62-
if (process === undefined) {
63-
callback(new Error('No process info available'));
62+
if (!process || !process.env) {
63+
callback(AWS.util.error(
64+
new Error('No process info or environment variables available'),
65+
{ code: 'EnvironmentCredentialsProviderFailure' }
66+
));
6467
return;
6568
}
6669

@@ -72,7 +75,10 @@ AWS.EnvironmentCredentials = AWS.util.inherit(AWS.Credentials, {
7275
if (this.envPrefix) prefix = this.envPrefix + '_';
7376
values[i] = process.env[prefix + keys[i]];
7477
if (!values[i] && keys[i] !== 'SESSION_TOKEN') {
75-
callback(new Error('Variable ' + prefix + keys[i] + ' not set.'));
78+
callback(AWS.util.error(
79+
new Error('Variable ' + prefix + keys[i] + ' not set.'),
80+
{ code: 'EnvironmentCredentialsProviderFailure' }
81+
));
7682
return;
7783
}
7884
}

lib/credentials/file_system_credentials.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ AWS.FileSystemCredentials = AWS.util.inherit(AWS.Credentials, {
5353
var creds = JSON.parse(AWS.util.readFileSync(this.filename));
5454
AWS.Credentials.call(this, creds);
5555
if (!this.accessKeyId || !this.secretAccessKey) {
56-
throw new Error('Credentials not set in ' + this.filename);
56+
throw AWS.util.error(
57+
new Error('Credentials not set in ' + this.filename),
58+
{ code: 'FileSystemCredentialsProviderFailure' }
59+
);
5760
}
5861
this.expired = false;
5962
callback();

lib/credentials/shared_ini_file_credentials.js

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,10 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
7474
var profile = creds[this.profile];
7575

7676
if (typeof profile !== 'object') {
77-
throw new Error('Profile ' + this.profile + ' not found in ' +
78-
this.filename);
77+
throw AWS.util.error(
78+
new Error('Profile ' + this.profile + ' not found in ' + this.filename),
79+
{ code: 'SharedIniFileCredentialsProviderFailure' }
80+
);
7981
}
8082

8183
if (profile['role_arn']) {
@@ -88,8 +90,11 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
8890
this.sessionToken = profile['aws_session_token'];
8991

9092
if (!this.accessKeyId || !this.secretAccessKey) {
91-
throw new Error('Credentials not set in ' + this.filename +
92-
' using profile ' + this.profile);
93+
throw AWS.util.error(
94+
new Error('Credentials not set in ' + this.filename +
95+
' using profile ' + this.profile),
96+
{ code: 'SharedIniFileCredentialsProviderFailure' }
97+
);
9398
}
9499
this.expired = false;
95100
callback();
@@ -103,9 +108,12 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
103108
*/
104109
loadRoleProfile: function loadRoleProfile(creds, roleProfile, callback) {
105110
if (this.disableAssumeRole) {
106-
throw new Error('Role assumption profiles are disabled. ' +
107-
'Failed to load profile ' + this.profile + ' from ' +
108-
this.filename);
111+
throw AWS.util.error(
112+
new Error('Role assumption profiles are disabled. ' +
113+
'Failed to load profile ' + this.profile + ' from ' +
114+
this.filename),
115+
{ code: 'SharedIniFileCredentialsProviderFailure' }
116+
);
109117
}
110118

111119
var self = this;
@@ -115,16 +123,22 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
115123
var sourceProfileName = roleProfile['source_profile'];
116124

117125
if (!sourceProfileName) {
118-
throw new Error('source_profile is not set in ' + this.filename +
119-
' using profile ' + this.profile);
126+
throw AWS.util.error(
127+
new Error('source_profile is not set in ' + this.filename +
128+
' using profile ' + this.profile),
129+
{ code: 'SharedIniFileCredentialsProviderFailure' }
130+
);
120131
}
121132

122133
var sourceProfile = creds[sourceProfileName];
123134

124135
if (typeof sourceProfile !== 'object') {
125-
throw new Error('source_profile ' + sourceProfileName + ' set in ' +
126-
this.filename + ' using profile ' + this.profile +
127-
' does not exist')
136+
throw AWS.util.error(
137+
new Error('source_profile ' + sourceProfileName + ' set in ' +
138+
this.filename + ' using profile ' + this.profile +
139+
' does not exist'),
140+
{ code: 'SharedIniFileCredentialsProviderFailure' }
141+
);
128142
}
129143

130144
var sourceCredentials = {
@@ -134,9 +148,12 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
134148
};
135149

136150
if (!sourceCredentials.accessKeyId || !sourceCredentials.secretAccessKey) {
137-
throw new Error('Credentials not set in source_profile ' +
138-
sourceProfileName + ' set in ' + this.filename +
139-
' using profile ' + this.profile);
151+
throw AWS.util.error(
152+
new Error('Credentials not set in source_profile ' +
153+
sourceProfileName + ' set in ' + this.filename +
154+
' using profile ' + this.profile),
155+
{ code: 'SharedIniFileCredentialsProviderFailure' }
156+
);
140157
}
141158

142159
var sts = new AWS.STS({
@@ -176,7 +193,9 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
176193
(env.HOMEPATH ? ((env.HOMEDRIVE || 'C:/') + env.HOMEPATH) : null);
177194
if (!home) {
178195
throw AWS.util.error(
179-
new Error('Cannot load credentials, HOME path not set'));
196+
new Error('Cannot load credentials, HOME path not set'),
197+
{ code: 'SharedIniFileCredentialsProviderFailure' }
198+
);
180199
}
181200

182201
this.filename = path.join(home, '.aws', 'credentials');

0 commit comments

Comments
 (0)