Skip to content

Commit

Permalink
Allow credentials to be loaded asynchronously
Browse files Browse the repository at this point in the history
* Add Config.getCredentials
* Add Credentials.{get,needsRefresh}
* Add EC2MetadataCredentials
  • Loading branch information
lsegal committed Mar 18, 2013
1 parent 187bc6c commit 2041e09
Show file tree
Hide file tree
Showing 9 changed files with 420 additions and 129 deletions.
224 changes: 193 additions & 31 deletions lib/config.js
Expand Up @@ -16,6 +16,7 @@
var AWS = require('./core'); var AWS = require('./core');
require('./event_listeners'); require('./event_listeners');
require('./event_emitter'); require('./event_emitter');
require('./metadata_service');
var inherit = AWS.util.inherit; var inherit = AWS.util.inherit;


/** /**
Expand Down Expand Up @@ -126,6 +127,59 @@ AWS.Config = inherit({
}); });
}, },


/**
* @api private
*/
getCredentials: function getCredentials(callback) {
var self = this;

function finish(err) {
callback(err, err ? null : self.credentials);
}

function credError(msg, err) {
return new AWS.util.error(err || new Error(), {
code: 'CredentialsError', message: msg
});
}

function getAsyncCredentials() {
self.credentials.get(function(err) {
if (err) {
var msg = 'Could not load credentials from ' + self.credentials.name;
err = credError(msg, err);
}
finish(err);
});
}

function getStaticCredentials() {
var err = null;
if (!self.credentials.accessKeyId || !self.credentials.secretAccessKey) {
err = credError('Missing credentials');
}
finish(err);
}

if (self.credentials) {
if (typeof self.credentials.get === 'function') {
getAsyncCredentials();
} else { // static credentials
getStaticCredentials();
}
} else if (self.credentialProvider) {
self.credentialProvider.resolve(function(err, creds) {
if (err) {
err = credError('Could not load credentials from any providers', err);
}
self.credentials = creds;
finish(err);
});
} else {
finish(credError('No credentials to load'));
}
},

/** /**
* Loads configuration data from a JSON file into this config object. * Loads configuration data from a JSON file into this config object.
* @note Loading configuration will reset all existing configuration * @note Loading configuration will reset all existing configuration
Expand All @@ -140,7 +194,10 @@ AWS.Config = inherit({
var fileSystemCreds = new AWS.FileSystemCredentials(path, this); var fileSystemCreds = new AWS.FileSystemCredentials(path, this);
var chain = new AWS.CredentialProviderChain(); var chain = new AWS.CredentialProviderChain();
chain.providers.unshift(fileSystemCreds); chain.providers.unshift(fileSystemCreds);
options.credentials = chain.resolve(); chain.resolve(function (err, creds) {
if (err) throw err;
else options.credentials = creds;
});


this.constructor(options); this.constructor(options);


Expand All @@ -160,6 +217,7 @@ AWS.Config = inherit({


// reset credential provider // reset credential provider
this.set('credentials', undefined); this.set('credentials', undefined);
this.set('credentialProvider', undefined);
}, },


/** /**
Expand Down Expand Up @@ -190,7 +248,19 @@ AWS.Config = inherit({
*/ */
keys: { keys: {
credentials: function () { credentials: function () {
return new AWS.CredentialProviderChain().resolve(); var credentials = null;
new AWS.CredentialProviderChain([
function () { return new AWS.EnvironmentCredentials('AWS'); },
function () { return new AWS.EnvironmentCredentials('AMAZON'); }
]).resolve(function(err, creds) {
if (!err) credentials = creds;
});
return credentials;
},
credentialProvider: function() {
return new AWS.CredentialProviderChain([
function() { return new AWS.EC2MetadataCredentials(); }
]);
}, },
region: function() { region: function() {
return process.env.AWS_REGION || process.env.AMAZON_REGION; return process.env.AWS_REGION || process.env.AMAZON_REGION;
Expand Down Expand Up @@ -244,6 +314,9 @@ AWS.Config = inherit({
* some network storage. The method should reset the credential attributes * some network storage. The method should reset the credential attributes
* on the object. * on the object.
* *
* @!attribute expired
* @return [Boolean] whether the credentials have been expired and
* require a refresh
* @!attribute accessKeyId * @!attribute accessKeyId
* @return [String] the AWS access key ID * @return [String] the AWS access key ID
* @!attribute secretAccessKey * @!attribute secretAccessKey
Expand All @@ -252,7 +325,6 @@ AWS.Config = inherit({
* @return [String] an optional AWS session token * @return [String] an optional AWS session token
*/ */
AWS.Credentials = inherit({ AWS.Credentials = inherit({

/** /**
* A credentials object can be created using positional arguments or an options * A credentials object can be created using positional arguments or an options
* hash. * hash.
Expand All @@ -277,6 +349,7 @@ AWS.Credentials = inherit({
* }); * });
*/ */
constructor: function Credentials() { constructor: function Credentials() {
this.expired = false;
if (arguments.length == 1 && typeof arguments[0] === 'object') { if (arguments.length == 1 && typeof arguments[0] === 'object') {
var creds = arguments[0].credentials || arguments[0]; var creds = arguments[0].credentials || arguments[0];
this.accessKeyId = creds.accessKeyId; this.accessKeyId = creds.accessKeyId;
Expand All @@ -289,15 +362,32 @@ AWS.Credentials = inherit({
} }
}, },


needsRefresh: function needsRefresh() {
return this.expired || !this.accessKeyId || !this.secretAccessKey;
},

get: function get(callback) {
var self = this;
if (this.needsRefresh()) {
this.refresh(function(err) {
if (!err) self.expired = false; // reset expired flag
callback(err);
});
} else {
callback();
}
},

/** /**
* Refreshes the credentials. * Refreshes the credentials.
* *
* @note Subclasses should override this class to reset the * @note Subclasses should override this class to reset the
* {accessKeyId}, {secretAccessKey} and optional {sessionToken} * {accessKeyId}, {secretAccessKey} and optional {sessionToken}
* on the credentials object. * on the credentials object.
*/ */
refresh: function refresh() { } refresh: function refresh(callback) {

callback();
}
}); });


/** /**
Expand Down Expand Up @@ -334,14 +424,23 @@ AWS.FileSystemCredentials = inherit(AWS.Credentials, {
constructor: function FileSystemCredentials(filename, initialCredentials) { constructor: function FileSystemCredentials(filename, initialCredentials) {
this.filename = filename; this.filename = filename;
AWS.Credentials.call(this, initialCredentials); AWS.Credentials.call(this, initialCredentials);
if (!this.accessKeyId) this.refresh(); this.get(function() {});
}, },


/** /**
* Refreshes the credentials from the {filename} on disk. * Loads the credentials from the {filename} on disk.
*/ */
refresh: function refresh() { refresh: function refresh(callback) {
AWS.Credentials.call(this, JSON.parse(AWS.util.readFileSync(this.filename))); if (!callback) callback = function(err) { if (err) throw err; };
try {
AWS.Credentials.call(this, JSON.parse(AWS.util.readFileSync(this.filename)));
if (!this.accessKeyId || !this.secretAccessKey) {
throw new Error('Credentials not set in ' + this.filename);
}
callback();
} catch (err) {
callback(err);
}
} }


}); });
Expand Down Expand Up @@ -385,31 +484,66 @@ AWS.EnvironmentCredentials = inherit(AWS.Credentials, {
*/ */
constructor: function EnvironmentCredentials(envPrefix) { constructor: function EnvironmentCredentials(envPrefix) {
this.envPrefix = envPrefix; this.envPrefix = envPrefix;
this.refresh(); this.get(function() {});
}, },


/** /**
* Refreshes credentials from the environment using the prefixed * Loads credentials from the environment using the prefixed
* environment variables. * environment variables.
*/ */
refresh: function refresh() { refresh: function refresh(callback) {
if (process === undefined) return; /*jshint maxcomplexity:10*/
if (!callback) callback = function(err) { if (err) throw err; };

if (process === undefined) {
callback(new Error('No process info available'));
return;
}


var keys = ['ACCESS_KEY_ID', 'SECRET_ACCESS_KEY', 'SESSION_TOKEN']; var keys = ['ACCESS_KEY_ID', 'SECRET_ACCESS_KEY', 'SESSION_TOKEN'];
var values = []; var values = [];


/*jshint forin:false*/ for (var i = 0; i < keys.length; i++) {
for (var i in keys) {
var prefix = ''; var prefix = '';
if (this.envPrefix) prefix = this.envPrefix + '_'; if (this.envPrefix) prefix = this.envPrefix + '_';
values[i] = process.env[prefix + keys[i]]; values[i] = process.env[prefix + keys[i]];
if (!values[i] && keys[i] !== 'SESSION_TOKEN') {
callback(new Error('Variable ' + prefix + keys[i] + ' not set.'));
return;
}
} }


AWS.Credentials.apply(this, values); AWS.Credentials.apply(this, values);
callback();
} }


}); });


AWS.EC2MetadataCredentials = inherit(AWS.Credentials, {
constructor: function EC2MetadataCredentials(options) {
this.serviceError = null;
this.metadataService = new AWS.MetadataService(options);
},

refresh: function refresh(callback) {
var self = this;
if (!callback) callback = function(err) { if (err) throw err; };
if (self.serviceError) {
callback(self.serviceError);
return;
}

self.metadataService.loadCredentials(function (err, creds) {
if (err) {
self.serviceError = err;
} else {
AWS.util.update(self, creds);
}
callback(err, creds);
});
}
});

/** /**
* Creates a credential provider chain that searches for AWS credentials * Creates a credential provider chain that searches for AWS credentials
* in a list of credential providers specified by the {providers} property. * in a list of credential providers specified by the {providers} property.
Expand Down Expand Up @@ -452,40 +586,67 @@ AWS.EnvironmentCredentials = inherit(AWS.Credentials, {
* {defaultProviders}. * {defaultProviders}.
* @see defaultProviders * @see defaultProviders
*/ */
AWS.CredentialProviderChain = inherit({ AWS.CredentialProviderChain = inherit(AWS.Credentials, {


/** /**
* Creates a new CredentialProviderChain with a default set of providers * Creates a new CredentialProviderChain with a default set of providers
* specified by {defaultProviders}. * specified by {defaultProviders}.
*/ */
constructor: function CredentialProviderChain() { constructor: function CredentialProviderChain(providers) {
this.providers = AWS.CredentialProviderChain.defaultProviders.slice(0); if (providers) {
this.providers = providers;
} else {
this.providers = AWS.CredentialProviderChain.defaultProviders.slice(0);
}
}, },


/** /**
* Resolves the provider chain by searching for the first set of * Resolves the provider chain by searching for the first set of
* credentials in {providers}. * credentials in {providers}.
* *
* @return [AWS.Credentials] the first set of credentials discovered * @callback callback function(err, credentials)
* in the chain of {providers}. * Called when the provider resolves the chain to a credentials object
* @return [null] if no credentials are found. * or null if no credentials can be found.
*
* @param err [Error] the error object returned if no credentials are
* found.
* @param credentials [AWS.Credentials] the credentials object resolved
* by the provider chain.
* @return [AWS.CredentialProviderChain] the provider, for chaining.
*/ */
resolve: function resolve() { resolve: function resolve(callback) {
var finalCreds; if (this.providers.length === 0) {
AWS.util.arrayEach(this.providers, function (provider) { callback(new Error('No providers'));
var creds; return;
}

var index = 0;
var providers = this.providers.slice(0);

function resolveNext(err, creds) {
if ((!err && creds) || index === providers.length) {
callback(err, creds);
return;
}

var provider = providers[index++];
if (typeof provider === 'function') { if (typeof provider === 'function') {
creds = provider.call(); creds = provider.call();
} else { } else {
creds = provider; creds = provider;
} }


if (creds.accessKeyId) { if (creds.get) {
finalCreds = creds; creds.get(function(err) {
return AWS.util.abort; resolveNext(err, err ? null : creds);
});
} else {
resolveNext(null, creds);
} }
}); }
return finalCreds ? finalCreds : new AWS.Credentials();
resolveNext();
return this;
} }


}); });
Expand All @@ -495,7 +656,8 @@ AWS.CredentialProviderChain = inherit({
*/ */
AWS.CredentialProviderChain.defaultProviders = [ AWS.CredentialProviderChain.defaultProviders = [
function () { return new AWS.EnvironmentCredentials('AWS'); }, function () { return new AWS.EnvironmentCredentials('AWS'); },
function () { return new AWS.EnvironmentCredentials('AMAZON'); } function () { return new AWS.EnvironmentCredentials('AMAZON'); },
function () { return new AWS.EC2MetadataCredentials(); }
]; ];


/** /**
Expand Down
1 change: 1 addition & 0 deletions lib/core.js
Expand Up @@ -53,6 +53,7 @@ require('./client');
require('./service'); require('./service');
require('./signers/request_signer'); require('./signers/request_signer');
require('./param_validator'); require('./param_validator');
require('./metadata_service');


/** /**
* @readonly * @readonly
Expand Down

0 comments on commit 2041e09

Please sign in to comment.