123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- 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);
- }
- });
|