Skip to content

ECSCredentials #1058

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion lib/aws.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,25 @@ require('./services');

// Load custom credential providers
require('./credentials/ec2_metadata_credentials');
require('./credentials/ecs_credentials');
require('./credentials/environment_credentials');
require('./credentials/file_system_credentials');
require('./credentials/shared_ini_file_credentials');

// Setup default chain providers
// If this changes, please update documentation for
// AWS.CredentialProviderChain.defaultProviders in
// credentials/credential_provider_chain.js
AWS.CredentialProviderChain.defaultProviders = [
function () { return new AWS.EnvironmentCredentials('AWS'); },
function () { return new AWS.EnvironmentCredentials('AMAZON'); },
function () { return new AWS.SharedIniFileCredentials(); },
function () { return new AWS.EC2MetadataCredentials(); }
function () {
if (AWS.ECSCredentials.prototype.getECSRelativeUri() !== undefined) {
return new AWS.ECSCredentials();
}
return new AWS.EC2MetadataCredentials();
}
];

// Update configuration keys
Expand Down
7 changes: 6 additions & 1 deletion lib/credentials/credential_provider_chain.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,12 @@ AWS.CredentialProviderChain = AWS.util.inherit(AWS.Credentials, {
* function () { return new AWS.EnvironmentCredentials('AWS'); },
* function () { return new AWS.EnvironmentCredentials('AMAZON'); },
* function () { return new AWS.SharedIniFileCredentials(); },
* function () { return new AWS.EC2MetadataCredentials(); }
* function () {
* // if AWS_CONTAINER_CREDENTIALS_RELATIVE_URI is set
* return new AWS.ECSCredentials();
* // else
* return new AWS.EC2MetadataCredentials();
* }
* ]
* ```
*/
Expand Down
142 changes: 142 additions & 0 deletions lib/credentials/ecs_credentials.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
var AWS = require('../core');

/**
* Represents credentials received from relative URI specified in the ECS container.
*
* This class will request refreshable credentials from the relative URI
* specified by the AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment variable
* in the container. If valid credentials are returned in the response, these
* will be used with zero configuration.
*
* This credentials class will timeout after 1 second of inactivity by default.
* If your requests to the relative URI are timing out, you can increase
* the value by configuring them directly:
*
* ```javascript
* AWS.config.credentials = new AWS.ECSCredentials({
* httpOptions: { timeout: 5000 } // 5 second timeout
* });
* ```
*
* @!macro nobrowser
*/
AWS.ECSCredentials = AWS.util.inherit(AWS.Credentials, {
constructor: function ECSCredentials(options) {
AWS.Credentials.call(this);
options = options ? AWS.util.copy(options) : {};
if (!options.httpOptions) options.httpOptions = {};
options.httpOptions = AWS.util.merge(
this.httpOptions, options.httpOptions);
AWS.util.update(this, options);
},

/**
* @api private
*/
httpOptions: { timeout: 1000 },

/**
* @api private
*/
host: '169.254.170.2',

/**
* Sets the name of the ECS environment variable to check for relative URI
* If changed, please change the name in the documentation for defaultProvider
* in credential_provider_chain.js and in all tests in test/credentials.spec.coffee
*
* @api private
*/
environmentVar: 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI',

/**
* @api private
*/
getECSRelativeUri: function getECSRelativeUri() {
if (process && process.env) return process.env[this.environmentVar];
},

/**
* @api private
*/
credsFormatIsValid: function credsFormatIsValid(credData) {
return (!!credData.AccessKeyId && !!credData.SecretAccessKey &&
!!credData.Token && !!credData.Expiration);
},

/**
* @api private
*/
request: function request(path, callback) {
path = path || '/';

var data = '';
var http = AWS.HttpClient.getInstance();
var httpRequest = new AWS.HttpRequest('http://' + this.host + path);
httpRequest.method = 'GET';
httpRequest.headers.Accept = 'application/json';
var httpOptions = this.httpOptions;

process.nextTick(function() {
http.handleRequest(httpRequest, httpOptions, function(httpResponse) {
httpResponse.on('data', function(chunk) { data += chunk.toString(); });
httpResponse.on('end', function() { callback(null, data); });
}, callback);
});
},

/**
* Loads the credentials from the relative URI specified by container
*
* @callback callback function(err)
* Called when the request to the relative URI responds (or fails). When
* this callback is called with no error, it means that the credentials
* information has been loaded into the object (as the `accessKeyId`,
* `secretAccessKey`, `sessionToken`, and `expireTime` properties).
* @param err [Error] if an error occurred, this value will be filled
* @see get
*/
refresh: function refresh(callback) {
var self = this;
if (!callback) callback = function(err) { if (err) throw err; };

if (process === undefined) {
callback(AWS.util.error(
new Error('No process info available'),
{ code: 'ECSCredentialsProviderFailure' }
));
return;
}
var relativeUri = this.getECSRelativeUri();
if (relativeUri === undefined) {
callback(AWS.util.error(
new Error('Variable ' + this.environmentVar + ' not set.'),
{ code: 'ECSCredentialsProviderFailure' }
));
return;
}

this.request(relativeUri, function(err, data) {
if (!err) {
try {
var creds = JSON.parse(data);
if (self.credsFormatIsValid(creds)) {
self.expired = false;
self.accessKeyId = creds.AccessKeyId;
self.secretAccessKey = creds.SecretAccessKey;
self.sessionToken = creds.Token;
self.expireTime = new Date(creds.Expiration);
} else {
throw AWS.util.error(
new Error('Response data is not in valid format'),
{ code: 'ECSCredentialsProviderFailure' }
);
}
} catch (dataError) {
err = dataError;
}
}
callback(err);
});
}
});
12 changes: 9 additions & 3 deletions lib/credentials/environment_credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,11 @@ AWS.EnvironmentCredentials = AWS.util.inherit(AWS.Credentials, {
refresh: function refresh(callback) {
if (!callback) callback = function(err) { if (err) throw err; };

if (process === undefined) {
callback(new Error('No process info available'));
if (!process || !process.env) {
callback(AWS.util.error(
new Error('No process info or environment variables available'),
{ code: 'EnvironmentCredentialsProviderFailure' }
));
return;
}

Expand All @@ -72,7 +75,10 @@ AWS.EnvironmentCredentials = AWS.util.inherit(AWS.Credentials, {
if (this.envPrefix) prefix = this.envPrefix + '_';
values[i] = process.env[prefix + keys[i]];
if (!values[i] && keys[i] !== 'SESSION_TOKEN') {
callback(new Error('Variable ' + prefix + keys[i] + ' not set.'));
callback(AWS.util.error(
new Error('Variable ' + prefix + keys[i] + ' not set.'),
{ code: 'EnvironmentCredentialsProviderFailure' }
));
return;
}
}
Expand Down
5 changes: 4 additions & 1 deletion lib/credentials/file_system_credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ AWS.FileSystemCredentials = AWS.util.inherit(AWS.Credentials, {
var creds = JSON.parse(AWS.util.readFileSync(this.filename));
AWS.Credentials.call(this, creds);
if (!this.accessKeyId || !this.secretAccessKey) {
throw new Error('Credentials not set in ' + this.filename);
throw AWS.util.error(
new Error('Credentials not set in ' + this.filename),
{ code: 'FileSystemCredentialsProviderFailure' }
);
}
this.expired = false;
callback();
Expand Down
51 changes: 35 additions & 16 deletions lib/credentials/shared_ini_file_credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,10 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
var profile = creds[this.profile];

if (typeof profile !== 'object') {
throw new Error('Profile ' + this.profile + ' not found in ' +
this.filename);
throw AWS.util.error(
new Error('Profile ' + this.profile + ' not found in ' + this.filename),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}

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

if (!this.accessKeyId || !this.secretAccessKey) {
throw new Error('Credentials not set in ' + this.filename +
' using profile ' + this.profile);
throw AWS.util.error(
new Error('Credentials not set in ' + this.filename +
' using profile ' + this.profile),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}
this.expired = false;
callback();
Expand All @@ -103,9 +108,12 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
*/
loadRoleProfile: function loadRoleProfile(creds, roleProfile, callback) {
if (this.disableAssumeRole) {
throw new Error('Role assumption profiles are disabled. ' +
'Failed to load profile ' + this.profile + ' from ' +
this.filename);
throw AWS.util.error(
new Error('Role assumption profiles are disabled. ' +
'Failed to load profile ' + this.profile + ' from ' +
this.filename),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}

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

if (!sourceProfileName) {
throw new Error('source_profile is not set in ' + this.filename +
' using profile ' + this.profile);
throw AWS.util.error(
new Error('source_profile is not set in ' + this.filename +
' using profile ' + this.profile),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}

var sourceProfile = creds[sourceProfileName];

if (typeof sourceProfile !== 'object') {
throw new Error('source_profile ' + sourceProfileName + ' set in ' +
this.filename + ' using profile ' + this.profile +
' does not exist')
throw AWS.util.error(
new Error('source_profile ' + sourceProfileName + ' set in ' +
this.filename + ' using profile ' + this.profile +
' does not exist'),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}

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

if (!sourceCredentials.accessKeyId || !sourceCredentials.secretAccessKey) {
throw new Error('Credentials not set in source_profile ' +
sourceProfileName + ' set in ' + this.filename +
' using profile ' + this.profile);
throw AWS.util.error(
new Error('Credentials not set in source_profile ' +
sourceProfileName + ' set in ' + this.filename +
' using profile ' + this.profile),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}

var sts = new AWS.STS({
Expand Down Expand Up @@ -176,7 +193,9 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
(env.HOMEPATH ? ((env.HOMEDRIVE || 'C:/') + env.HOMEPATH) : null);
if (!home) {
throw AWS.util.error(
new Error('Cannot load credentials, HOME path not set'));
new Error('Cannot load credentials, HOME path not set'),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}

this.filename = path.join(home, '.aws', 'credentials');
Expand Down
Loading