var AWS = require('../core'); var STS = require('../../clients/sts'); /** * Represents temporary credentials retrieved from {AWS.STS}. Without any * extra parameters, credentials will be fetched from the * {AWS.STS.getSessionToken} operation. If an IAM role is provided, the * {AWS.STS.assumeRole} operation will be used to fetch credentials for the * role instead. * * AWS.ChainableTemporaryCredentials differs from AWS.TemporaryCredentials in * the way masterCredentials and refreshes are handled. * AWS.ChainableTemporaryCredentials refreshes expired credentials using the * masterCredentials passed by the user to support chaining of STS credentials. * However, AWS.TemporaryCredentials recursively collapses the masterCredentials * during instantiation, precluding the ability to refresh credentials which * require intermediate, temporary credentials. * * For example, if the application should use RoleA, which must be assumed from * RoleB, and the environment provides credentials which can assume RoleB, then * AWS.ChainableTemporaryCredentials must be used to support refreshing the * temporary credentials for RoleA: * * ```javascript * var roleACreds = new AWS.ChainableTemporaryCredentials({ * params: {RoleArn: 'RoleA'}, * masterCredentials: new AWS.ChainableTemporaryCredentials({ * params: {RoleArn: 'RoleB'}, * masterCredentials: new AWS.EnvironmentCredentials('AWS') * }) * }); * ``` * * If AWS.TemporaryCredentials had been used in the previous example, * `roleACreds` would fail to refresh because `roleACreds` would * use the environment credentials for the AssumeRole request. * * Another difference is that AWS.ChainableTemporaryCredentials creates the STS * service instance during instantiation while AWS.TemporaryCredentials creates * the STS service instance during the first refresh. Creating the service * instance during instantiation effectively captures the master credentials * from the global config, so that subsequent changes to the global config do * not affect the master credentials used to refresh the temporary credentials. * * This allows an instance of AWS.ChainableTemporaryCredentials to be assigned * to AWS.config.credentials: * * ```javascript * var envCreds = new AWS.EnvironmentCredentials('AWS'); * AWS.config.credentials = envCreds; * // masterCredentials will be envCreds * AWS.config.credentials = new AWS.ChainableTemporaryCredentials({ * params: {RoleArn: '...'} * }); * ``` * * Similarly, to use the CredentialProviderChain's default providers as the * master credentials, simply create a new instance of * AWS.ChainableTemporaryCredentials: * * ```javascript * AWS.config.credentials = new ChainableTemporaryCredentials({ * params: {RoleArn: '...'} * }); * ``` * * @!attribute service * @return [AWS.STS] the STS service instance used to * get and refresh temporary credentials from AWS STS. * @note (see constructor) */ AWS.ChainableTemporaryCredentials = AWS.util.inherit(AWS.Credentials, { /** * Creates a new temporary credentials object. * * @param options [map] a set of options * @option options params [map] ({}) a map of options that are passed to the * {AWS.STS.assumeRole} or {AWS.STS.getSessionToken} operations. * If a `RoleArn` parameter is passed in, credentials will be based on the * IAM role. If a `SerialNumber` parameter is passed in, {tokenCodeFn} must * also be passed in or an error will be thrown. * @option options masterCredentials [AWS.Credentials] the master credentials * used to get and refresh temporary credentials from AWS STS. By default, * AWS.config.credentials or AWS.config.credentialProvider will be used. * @option options tokenCodeFn [Function] (null) Function to provide * `TokenCode`, if `SerialNumber` is provided for profile in {params}. Function * is called with value of `SerialNumber` and `callback`, and should provide * the `TokenCode` or an error to the callback in the format * `callback(err, token)`. * @example Creating a new credentials object for generic temporary credentials * AWS.config.credentials = new AWS.ChainableTemporaryCredentials(); * @example Creating a new credentials object for an IAM role * AWS.config.credentials = new AWS.ChainableTemporaryCredentials({ * params: { * RoleArn: 'arn:aws:iam::1234567890:role/TemporaryCredentials' * } * }); * @see AWS.STS.assumeRole * @see AWS.STS.getSessionToken */ constructor: function ChainableTemporaryCredentials(options) { AWS.Credentials.call(this); options = options || {}; this.errorCode = 'ChainableTemporaryCredentialsProviderFailure'; this.expired = true; this.tokenCodeFn = null; var params = AWS.util.copy(options.params) || {}; if (params.RoleArn) { params.RoleSessionName = params.RoleSessionName || 'temporary-credentials'; } if (params.SerialNumber) { if (!options.tokenCodeFn || (typeof options.tokenCodeFn !== 'function')) { throw new AWS.util.error( new Error('tokenCodeFn must be a function when params.SerialNumber is given'), {code: this.errorCode} ); } else { this.tokenCodeFn = options.tokenCodeFn; } } var config = AWS.util.merge( { params: params, credentials: options.masterCredentials || AWS.config.credentials }, options.stsConfig || {} ); this.service = new STS(config); }, /** * Refreshes credentials using {AWS.STS.assumeRole} or * {AWS.STS.getSessionToken}, depending on whether an IAM role ARN was passed * to the credentials {constructor}. * * @callback callback function(err) * Called when the STS service 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`, and `sessionToken` properties). * @param err [Error] if an error occurred, this value will be filled * @see AWS.Credentials.get */ refresh: function refresh(callback) { this.coalesceRefresh(callback || AWS.util.fn.callback); }, /** * @api private * @param callback */ load: function load(callback) { var self = this; var operation = self.service.config.params.RoleArn ? 'assumeRole' : 'getSessionToken'; this.getTokenCode(function (err, tokenCode) { var params = {}; if (err) { callback(err); return; } if (tokenCode) { params.TokenCode = tokenCode; } self.service[operation](params, function (err, data) { if (!err) { self.service.credentialsFrom(data, self); } callback(err); }); }); }, /** * @api private */ getTokenCode: function getTokenCode(callback) { var self = this; if (this.tokenCodeFn) { this.tokenCodeFn(this.service.config.params.SerialNumber, function (err, token) { if (err) { var message = err; if (err instanceof Error) { message = err.message; } callback( AWS.util.error( new Error('Error fetching MFA token: ' + message), { code: self.errorCode} ) ); return; } callback(null, token); }); } else { callback(null); } } });