var AWS = require('../core'); var STS = require('../../clients/sts'); var iniLoader = AWS.util.iniLoader; var ASSUME_ROLE_DEFAULT_REGION = 'us-east-1'; /** * Represents credentials loaded from shared credentials file * (defaulting to ~/.aws/credentials or defined by the * `AWS_SHARED_CREDENTIALS_FILE` environment variable). * * ## Using the shared credentials file * * This provider is checked by default in the Node.js environment. To use the * credentials file provider, simply add your access and secret keys to the * ~/.aws/credentials file in the following format: * * [default] * aws_access_key_id = AKID... * aws_secret_access_key = YOUR_SECRET_KEY * * ## Using custom profiles * * The SDK supports loading credentials for separate profiles. This can be done * in two ways: * * 1. Set the `AWS_PROFILE` environment variable in your process prior to * loading the SDK. * 2. Directly load the AWS.SharedIniFileCredentials provider: * * ```javascript * var creds = new AWS.SharedIniFileCredentials({profile: 'myprofile'}); * AWS.config.credentials = creds; * ``` * * @!macro nobrowser */ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, { /** * Creates a new SharedIniFileCredentials object. * * @param options [map] a set of options * @option options profile [String] (AWS_PROFILE env var or 'default') * the name of the profile to load. * @option options filename [String] ('~/.aws/credentials' or defined by * AWS_SHARED_CREDENTIALS_FILE process env var) * the filename to use when loading credentials. * @option options disableAssumeRole [Boolean] (false) True to disable * support for profiles that assume an IAM role. If true, and an assume * role profile is selected, an error is raised. * @option options preferStaticCredentials [Boolean] (false) True to * prefer static credentials to role_arn if both are present. * @option options tokenCodeFn [Function] (null) Function to provide * STS Assume Role TokenCode, if mfa_serial is provided for profile in ini * file. Function is called with value of mfa_serial and callback, and * should provide the TokenCode or an error to the callback in the format * callback(err, token) * @option options callback [Function] (err) Credentials are eagerly loaded * by the constructor. When the callback is called with no error, the * credentials have been loaded successfully. * @option options httpOptions [map] A set of options to pass to the low-level * HTTP request. Currently supported options are: * * **proxy** [String] — the URL to proxy requests through * * **agent** [http.Agent, https.Agent] — the Agent object to perform * HTTP requests with. Used for connection pooling. Defaults to the global * agent (`http.globalAgent`) for non-SSL connections. Note that for * SSL connections, a special Agent object is used in order to enable * peer certificate verification. This feature is only available in the * Node.js environment. * * **connectTimeout** [Integer] — Sets the socket to timeout after * failing to establish a connection with the server after * `connectTimeout` milliseconds. This timeout has no effect once a socket * connection has been established. * * **timeout** [Integer] — The number of milliseconds a request can * take before automatically being terminated. * Defaults to two minutes (120000). */ constructor: function SharedIniFileCredentials(options) { AWS.Credentials.call(this); options = options || {}; this.filename = options.filename; this.profile = options.profile || process.env.AWS_PROFILE || AWS.util.defaultProfile; this.disableAssumeRole = Boolean(options.disableAssumeRole); this.preferStaticCredentials = Boolean(options.preferStaticCredentials); this.tokenCodeFn = options.tokenCodeFn || null; this.httpOptions = options.httpOptions || null; this.get(options.callback || AWS.util.fn.noop); }, /** * @api private */ load: function load(callback) { var self = this; try { var profiles = AWS.util.getProfilesFromSharedConfig(iniLoader, this.filename); var profile = profiles[this.profile] || {}; if (Object.keys(profile).length === 0) { throw AWS.util.error( new Error('Profile ' + this.profile + ' not found'), { code: 'SharedIniFileCredentialsProviderFailure' } ); } /* In the CLI, the presence of both a role_arn and static credentials have different meanings depending on how many profiles have been visited. For the first profile processed, role_arn takes precedence over any static credentials, but for all subsequent profiles, static credentials are used if present, and only in their absence will the profile's source_profile and role_arn keys be used to load another set of credentials. This var is intended to yield compatible behaviour in this sdk. */ var preferStaticCredentialsToRoleArn = Boolean( this.preferStaticCredentials && profile['aws_access_key_id'] && profile['aws_secret_access_key'] ); if (profile['role_arn'] && !preferStaticCredentialsToRoleArn) { this.loadRoleProfile(profiles, profile, function(err, data) { if (err) { callback(err); } else { self.expired = false; self.accessKeyId = data.Credentials.AccessKeyId; self.secretAccessKey = data.Credentials.SecretAccessKey; self.sessionToken = data.Credentials.SessionToken; self.expireTime = data.Credentials.Expiration; callback(null); } }); return; } this.accessKeyId = profile['aws_access_key_id']; this.secretAccessKey = profile['aws_secret_access_key']; this.sessionToken = profile['aws_session_token']; if (!this.accessKeyId || !this.secretAccessKey) { throw AWS.util.error( new Error('Credentials not set for profile ' + this.profile), { code: 'SharedIniFileCredentialsProviderFailure' } ); } this.expired = false; callback(null); } catch (err) { callback(err); } }, /** * Loads the credentials from the shared credentials file * * @callback callback function(err) * Called after the shared INI file on disk is read and parsed. 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 get */ refresh: function refresh(callback) { iniLoader.clearCachedFiles(); this.coalesceRefresh( callback || AWS.util.fn.callback, this.disableAssumeRole ); }, /** * @api private */ loadRoleProfile: function loadRoleProfile(creds, roleProfile, callback) { if (this.disableAssumeRole) { throw AWS.util.error( new Error('Role assumption profiles are disabled. ' + 'Failed to load profile ' + this.profile + ' from ' + creds.filename), { code: 'SharedIniFileCredentialsProviderFailure' } ); } var self = this; var roleArn = roleProfile['role_arn']; var roleSessionName = roleProfile['role_session_name']; var externalId = roleProfile['external_id']; var mfaSerial = roleProfile['mfa_serial']; var sourceProfileName = roleProfile['source_profile']; var durationSeconds = parseInt(roleProfile['duration_seconds'], 10) || undefined; // From experimentation, the following behavior mimics the AWS CLI: // // 1. Use region from the profile if present. // 2. Otherwise fall back to N. Virginia (global endpoint). // // It is necessary to do the fallback explicitly, because if // 'AWS_STS_REGIONAL_ENDPOINTS=regional', the underlying STS client will // otherwise throw an error if region is left 'undefined'. // // Experimentation shows that the AWS CLI (tested at version 1.18.136) // ignores the following potential sources of a region for the purposes of // this AssumeRole call: // // - The [default] profile // - The AWS_REGION environment variable // // Ignoring the [default] profile for the purposes of AssumeRole is arguably // a bug in the CLI since it does use the [default] region for service // calls... but right now we're matching behavior of the other tool. var profileRegion = roleProfile['region'] || ASSUME_ROLE_DEFAULT_REGION; if (!sourceProfileName) { throw AWS.util.error( new Error('source_profile is not set using profile ' + this.profile), { code: 'SharedIniFileCredentialsProviderFailure' } ); } var sourceProfileExistanceTest = creds[sourceProfileName]; if (typeof sourceProfileExistanceTest !== 'object') { throw AWS.util.error( new Error('source_profile ' + sourceProfileName + ' using profile ' + this.profile + ' does not exist'), { code: 'SharedIniFileCredentialsProviderFailure' } ); } var sourceCredentials = new AWS.SharedIniFileCredentials( AWS.util.merge(this.options || {}, { profile: sourceProfileName, preferStaticCredentials: true }) ); this.roleArn = roleArn; var sts = new STS({ credentials: sourceCredentials, region: profileRegion, httpOptions: this.httpOptions }); var roleParams = { DurationSeconds: durationSeconds, RoleArn: roleArn, RoleSessionName: roleSessionName || 'aws-sdk-js-' + Date.now() }; if (externalId) { roleParams.ExternalId = externalId; } if (mfaSerial && self.tokenCodeFn) { roleParams.SerialNumber = mfaSerial; self.tokenCodeFn(mfaSerial, function(err, token) { if (err) { var message; if (err instanceof Error) { message = err.message; } else { message = err; } callback( AWS.util.error( new Error('Error fetching MFA token: ' + message), { code: 'SharedIniFileCredentialsProviderFailure' } )); return; } roleParams.TokenCode = token; sts.assumeRole(roleParams, callback); }); return; } sts.assumeRole(roleParams, callback); } });