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'; /** * Represents credentials received from specified URI. * * This class will request refreshable credentials from the relative URI * specified by the AWS_CONTAINER_CREDENTIALS_RELATIVE_URI or the * AWS_CONTAINER_CREDENTIALS_FULL_URI environment variable. If valid credentials * are returned in the response, these will be used with zero configuration. * * This credentials class will by default timeout after 1 second of inactivity * and retry 3 times. * 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.RemoteCredentials({ * httpOptions: { timeout: 5000 }, // 5 second timeout * maxRetries: 10, // retry 10 times * retryDelayOptions: { base: 200 } // see AWS.Config for information * }); * ``` * * @see AWS.Config.retryDelayOptions * * @!macro nobrowser */ AWS.RemoteCredentials = AWS.util.inherit(AWS.Credentials, { constructor: function RemoteCredentials(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 */ 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.RemoteCredentials 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.RemoteCredentials only supports ' + FULL_URI_ALLOWED_HOSTNAMES.join(',') + ' for ' + parsed.protocol + '; ' + parsed.protocol + '//' + parsed.hostname + ' requested.'), { code: 'ECSCredentialsProviderFailure' } ); } return full; } else { throw AWS.util.error( new Error('Variable ' + ENV_RELATIVE_URI + ' or ' + ENV_FULL_URI + ' must be set to use AWS.RemoteCredentials.'), { code: 'ECSCredentialsProviderFailure' } ); } } else { throw AWS.util.error( new Error('No process info available'), { code: 'ECSCredentialsProviderFailure' } ); } }, /** * @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); }, /** * 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) { this.coalesceRefresh(callback || AWS.util.fn.callback); }, /** * @api private */ load: function load(callback) { var self = this; var fullUri; try { fullUri = this.getECSFullUri(); } catch (err) { callback(err); 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' } ); } AWS.util.update(self, creds); } catch (dataError) { err = dataError; } } callback(err, creds); }); } });