Skip to content

Commit

Permalink
Add general purpose RemoteCredentials and derive ECSCredentials from it
Browse files Browse the repository at this point in the history
  • Loading branch information
AWSSteveHa authored and jeskew committed Nov 28, 2017
1 parent dc6215a commit 314c917
Show file tree
Hide file tree
Showing 10 changed files with 566 additions and 212 deletions.
@@ -0,0 +1,5 @@
{
"type": "feature",
"category": "credentials_provider",
"description": "Add general purpose RemoteCredentials and derive ECSCredentials from it"
}
1 change: 1 addition & 0 deletions lib/core.d.ts
Expand Up @@ -3,6 +3,7 @@ export {Credentials} from './credentials';
export {CognitoIdentityCredentials} from './credentials/cognito_identity_credentials';
export {CredentialProviderChain} from './credentials/credential_provider_chain';
export {EC2MetadataCredentials} from './credentials/ec2_metadata_credentials';
export {RemoteCredentials} from './credentials/remote_credentials';
export {ECSCredentials} from './credentials/ecs_credentials';
export {EnvironmentCredentials} from './credentials/environment_credentials';
export {FileSystemCredentials} from './credentials/file_system_credentials';
Expand Down
16 changes: 8 additions & 8 deletions lib/credentials/ecs_credentials.d.ts
@@ -1,11 +1,11 @@
import {Credentials} from '../credentials';
export class ECSCredentials extends Credentials {
/**
* Represents credentials received from relative URI specified in the ECS container.
* @param {object} options - Override the default (1s) timeout period.
*/
constructor(options?: ECSCredentialsOptions);
}
import {RemoteCredentials} from './remote_credentials';
export class ECSCredentials extends RemoteCredentials {
/**
* Represents credentials received.
* @param {object} options - Override the default (1s) timeout period.
*/
constructor(options?: ECSCredentialsOptions);
}
interface ECSCredentialsOptions {
httpOptions?: {
/**
Expand Down
189 changes: 5 additions & 184 deletions lib/credentials/ecs_credentials.js
@@ -1,11 +1,4 @@
var AWS = require('../core'),
ENV_RELATIVE_URI = 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI',
ENV_FULL_URI = 'AWS_CONTAINER_CREDENTIALS_FULL_URI',
ENV_AUTH_TOKEN = 'AWS_CONTAINER_AUTHORIZATION_TOKEN',
FULL_URI_UNRESTRICTED_PROTOCOLS = ['https:'],
FULL_URI_ALLOWED_PROTOCOLS = ['http:', 'https:'],
FULL_URI_ALLOWED_HOSTNAMES = ['localhost', '127.0.0.1'],
RELATIVE_URI_HOST = '169.254.170.2';
var AWS = require('../core');

/**
* Represents credentials received from relative URI specified in the ECS container.
Expand All @@ -32,188 +25,16 @@ var AWS = require('../core'),
*
* @!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.ECSCredentials = AWS.util.inherit(AWS.RemoteCredentials, {
constructor: function RemoteCredentials(options) {
AWS.util.update(this, options);
AWS.util.update(this, { providerName: 'ECSCredentials' });
},

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

/**
* @api private
*/
maxRetries: 3,

/**
* @api private
*/
isConfiguredForEcsCredentials: function isConfiguredForEcsCredentials() {
return Boolean(
process &&
process.env &&
(process.env[ENV_RELATIVE_URI] || process.env[ENV_FULL_URI])
);
},

/**
* @api private
*/
getECSFullUri: function getECSFullUri() {
if (process && process.env) {
var relative = process.env[ENV_RELATIVE_URI],
full = process.env[ENV_FULL_URI];
if (relative) {
return 'http://' + RELATIVE_URI_HOST + relative;
} else if (full) {
var parsed = AWS.util.urlParse(full);
if (FULL_URI_ALLOWED_PROTOCOLS.indexOf(parsed.protocol) < 0) {
throw AWS.util.error(
new Error('Unsupported protocol: AWS.ECSCredentials supports '
+ FULL_URI_ALLOWED_PROTOCOLS.join(',') + ' only; '
+ parsed.protocol + ' requested.'),
{ code: 'ECSCredentialsProviderFailure' }
);
}

if (FULL_URI_UNRESTRICTED_PROTOCOLS.indexOf(parsed.protocol) < 0 &&
FULL_URI_ALLOWED_HOSTNAMES.indexOf(parsed.hostname) < 0) {
throw AWS.util.error(
new Error('Unsupported hostname: AWS.ECSCredentials only supports '
+ FULL_URI_ALLOWED_HOSTNAMES.join(',') + ' for ' + parsed.protocol + '; '
+ parsed.protocol + parsed.hostname + ' requested.'),
{ code: 'ECSCredentialsProviderFailure' }
);
}

return full;
}
}
},

/**
* @api private
*/
getECSAuthToken: function getECSAuthToken() {
if (process && process.env && process.env[ENV_FULL_URI]) {
return process.env[ENV_AUTH_TOKEN];
}
},

/**
* @api private
*/
credsFormatIsValid: function credsFormatIsValid(credData) {
return (!!credData.accessKeyId && !!credData.secretAccessKey &&
!!credData.sessionToken && !!credData.expireTime);
},

/**
* @api private
*/
formatCreds: function formatCreds(credData) {
if (!!credData.credentials) {
credData = credData.credentials;
}

return {
expired: false,
accessKeyId: credData.accessKeyId || credData.AccessKeyId,
secretAccessKey: credData.secretAccessKey || credData.SecretAccessKey,
sessionToken: credData.sessionToken || credData.Token,
expireTime: new Date(credData.expiration || credData.Expiration)
};
},

/**
* @api private
*/
request: function request(url, callback) {
var httpRequest = new AWS.HttpRequest(url);
httpRequest.method = 'GET';
httpRequest.headers.Accept = 'application/json';
var token = this.getECSAuthToken();
if (token) {
httpRequest.headers.Authorization = token;
}
AWS.util.handleRequestWithRetries(httpRequest, this, callback);
},

/**
* @api private
*/
refreshQueue: [],

/**
* 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;
var refreshQueue = self.refreshQueue;
if (!callback) callback = function(err) { if (err) throw err; };
refreshQueue.push({
provider: self,
errCallback: callback
});
if (refreshQueue.length > 1) { return; }

function callbacks(err, creds) {
var call, cb;
while ((call = refreshQueue.shift()) !== undefined) {
cb = call.errCallback;
if (!err) AWS.util.update(call.provider, creds);
cb(err);
}
}

if (process === undefined) {
callbacks(AWS.util.error(
new Error('No process info available'),
{ code: 'ECSCredentialsProviderFailure' }
));
return;
}
var fullUri = this.getECSFullUri();
if (fullUri === undefined) {
callbacks(AWS.util.error(
new Error('Variable ' + ENV_RELATIVE_URI + ' or ' + ENV_FULL_URI +
' must be set to use AWS.ECSCredentials.'),
{ code: 'ECSCredentialsProviderFailure' }
));
return;
}

this.request(fullUri, function(err, data) {
if (!err) {
try {
data = JSON.parse(data);
var creds = self.formatCreds(data);
if (!self.credsFormatIsValid(creds)) {
throw AWS.util.error(
new Error('Response data is not in valid format'),
{ code: 'ECSCredentialsProviderFailure' }
);
}
} catch (dataError) {
err = dataError;
}
}
callbacks(err, creds);
});
return isConfiguredForRemoteCredentials();
}
});
17 changes: 17 additions & 0 deletions lib/credentials/remote_credentials.d.ts
@@ -0,0 +1,17 @@
import {Credentials} from '../credentials';
export class RemoteCredentials extends Credentials {
/**
* Represents credentials received.
* @param {object} options - Override the default (1s) timeout period.
*/
constructor(options?: RemoteCredentialsOptions);
}
interface RemoteCredentialsOptions {
httpOptions?: {
/**
* Timeout in milliseconds.
*/
timeout?: number
}
maxRetries?: number
}

0 comments on commit 314c917

Please sign in to comment.