shared_ini_file_credentials.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. var AWS = require('../core');
  2. var STS = require('../../clients/sts');
  3. var iniLoader = AWS.util.iniLoader;
  4. var ASSUME_ROLE_DEFAULT_REGION = 'us-east-1';
  5. /**
  6. * Represents credentials loaded from shared credentials file
  7. * (defaulting to ~/.aws/credentials or defined by the
  8. * `AWS_SHARED_CREDENTIALS_FILE` environment variable).
  9. *
  10. * ## Using the shared credentials file
  11. *
  12. * This provider is checked by default in the Node.js environment. To use the
  13. * credentials file provider, simply add your access and secret keys to the
  14. * ~/.aws/credentials file in the following format:
  15. *
  16. * [default]
  17. * aws_access_key_id = AKID...
  18. * aws_secret_access_key = YOUR_SECRET_KEY
  19. *
  20. * ## Using custom profiles
  21. *
  22. * The SDK supports loading credentials for separate profiles. This can be done
  23. * in two ways:
  24. *
  25. * 1. Set the `AWS_PROFILE` environment variable in your process prior to
  26. * loading the SDK.
  27. * 2. Directly load the AWS.SharedIniFileCredentials provider:
  28. *
  29. * ```javascript
  30. * var creds = new AWS.SharedIniFileCredentials({profile: 'myprofile'});
  31. * AWS.config.credentials = creds;
  32. * ```
  33. *
  34. * @!macro nobrowser
  35. */
  36. AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
  37. /**
  38. * Creates a new SharedIniFileCredentials object.
  39. *
  40. * @param options [map] a set of options
  41. * @option options profile [String] (AWS_PROFILE env var or 'default')
  42. * the name of the profile to load.
  43. * @option options filename [String] ('~/.aws/credentials' or defined by
  44. * AWS_SHARED_CREDENTIALS_FILE process env var)
  45. * the filename to use when loading credentials.
  46. * @option options disableAssumeRole [Boolean] (false) True to disable
  47. * support for profiles that assume an IAM role. If true, and an assume
  48. * role profile is selected, an error is raised.
  49. * @option options preferStaticCredentials [Boolean] (false) True to
  50. * prefer static credentials to role_arn if both are present.
  51. * @option options tokenCodeFn [Function] (null) Function to provide
  52. * STS Assume Role TokenCode, if mfa_serial is provided for profile in ini
  53. * file. Function is called with value of mfa_serial and callback, and
  54. * should provide the TokenCode or an error to the callback in the format
  55. * callback(err, token)
  56. * @option options callback [Function] (err) Credentials are eagerly loaded
  57. * by the constructor. When the callback is called with no error, the
  58. * credentials have been loaded successfully.
  59. * @option options httpOptions [map] A set of options to pass to the low-level
  60. * HTTP request. Currently supported options are:
  61. * * **proxy** [String] — the URL to proxy requests through
  62. * * **agent** [http.Agent, https.Agent] — the Agent object to perform
  63. * HTTP requests with. Used for connection pooling. Defaults to the global
  64. * agent (`http.globalAgent`) for non-SSL connections. Note that for
  65. * SSL connections, a special Agent object is used in order to enable
  66. * peer certificate verification. This feature is only available in the
  67. * Node.js environment.
  68. * * **connectTimeout** [Integer] — Sets the socket to timeout after
  69. * failing to establish a connection with the server after
  70. * `connectTimeout` milliseconds. This timeout has no effect once a socket
  71. * connection has been established.
  72. * * **timeout** [Integer] — The number of milliseconds a request can
  73. * take before automatically being terminated.
  74. * Defaults to two minutes (120000).
  75. */
  76. constructor: function SharedIniFileCredentials(options) {
  77. AWS.Credentials.call(this);
  78. options = options || {};
  79. this.filename = options.filename;
  80. this.profile = options.profile || process.env.AWS_PROFILE || AWS.util.defaultProfile;
  81. this.disableAssumeRole = Boolean(options.disableAssumeRole);
  82. this.preferStaticCredentials = Boolean(options.preferStaticCredentials);
  83. this.tokenCodeFn = options.tokenCodeFn || null;
  84. this.httpOptions = options.httpOptions || null;
  85. this.get(options.callback || AWS.util.fn.noop);
  86. },
  87. /**
  88. * @api private
  89. */
  90. load: function load(callback) {
  91. var self = this;
  92. try {
  93. var profiles = AWS.util.getProfilesFromSharedConfig(iniLoader, this.filename);
  94. var profile = profiles[this.profile] || {};
  95. if (Object.keys(profile).length === 0) {
  96. throw AWS.util.error(
  97. new Error('Profile ' + this.profile + ' not found'),
  98. { code: 'SharedIniFileCredentialsProviderFailure' }
  99. );
  100. }
  101. /*
  102. In the CLI, the presence of both a role_arn and static credentials have
  103. different meanings depending on how many profiles have been visited. For
  104. the first profile processed, role_arn takes precedence over any static
  105. credentials, but for all subsequent profiles, static credentials are
  106. used if present, and only in their absence will the profile's
  107. source_profile and role_arn keys be used to load another set of
  108. credentials. This var is intended to yield compatible behaviour in this
  109. sdk.
  110. */
  111. var preferStaticCredentialsToRoleArn = Boolean(
  112. this.preferStaticCredentials
  113. && profile['aws_access_key_id']
  114. && profile['aws_secret_access_key']
  115. );
  116. if (profile['role_arn'] && !preferStaticCredentialsToRoleArn) {
  117. this.loadRoleProfile(profiles, profile, function(err, data) {
  118. if (err) {
  119. callback(err);
  120. } else {
  121. self.expired = false;
  122. self.accessKeyId = data.Credentials.AccessKeyId;
  123. self.secretAccessKey = data.Credentials.SecretAccessKey;
  124. self.sessionToken = data.Credentials.SessionToken;
  125. self.expireTime = data.Credentials.Expiration;
  126. callback(null);
  127. }
  128. });
  129. return;
  130. }
  131. this.accessKeyId = profile['aws_access_key_id'];
  132. this.secretAccessKey = profile['aws_secret_access_key'];
  133. this.sessionToken = profile['aws_session_token'];
  134. if (!this.accessKeyId || !this.secretAccessKey) {
  135. throw AWS.util.error(
  136. new Error('Credentials not set for profile ' + this.profile),
  137. { code: 'SharedIniFileCredentialsProviderFailure' }
  138. );
  139. }
  140. this.expired = false;
  141. callback(null);
  142. } catch (err) {
  143. callback(err);
  144. }
  145. },
  146. /**
  147. * Loads the credentials from the shared credentials file
  148. *
  149. * @callback callback function(err)
  150. * Called after the shared INI file on disk is read and parsed. When this
  151. * callback is called with no error, it means that the credentials
  152. * information has been loaded into the object (as the `accessKeyId`,
  153. * `secretAccessKey`, and `sessionToken` properties).
  154. * @param err [Error] if an error occurred, this value will be filled
  155. * @see get
  156. */
  157. refresh: function refresh(callback) {
  158. iniLoader.clearCachedFiles();
  159. this.coalesceRefresh(
  160. callback || AWS.util.fn.callback,
  161. this.disableAssumeRole
  162. );
  163. },
  164. /**
  165. * @api private
  166. */
  167. loadRoleProfile: function loadRoleProfile(creds, roleProfile, callback) {
  168. if (this.disableAssumeRole) {
  169. throw AWS.util.error(
  170. new Error('Role assumption profiles are disabled. ' +
  171. 'Failed to load profile ' + this.profile +
  172. ' from ' + creds.filename),
  173. { code: 'SharedIniFileCredentialsProviderFailure' }
  174. );
  175. }
  176. var self = this;
  177. var roleArn = roleProfile['role_arn'];
  178. var roleSessionName = roleProfile['role_session_name'];
  179. var externalId = roleProfile['external_id'];
  180. var mfaSerial = roleProfile['mfa_serial'];
  181. var sourceProfileName = roleProfile['source_profile'];
  182. var durationSeconds = parseInt(roleProfile['duration_seconds'], 10) || undefined;
  183. // From experimentation, the following behavior mimics the AWS CLI:
  184. //
  185. // 1. Use region from the profile if present.
  186. // 2. Otherwise fall back to N. Virginia (global endpoint).
  187. //
  188. // It is necessary to do the fallback explicitly, because if
  189. // 'AWS_STS_REGIONAL_ENDPOINTS=regional', the underlying STS client will
  190. // otherwise throw an error if region is left 'undefined'.
  191. //
  192. // Experimentation shows that the AWS CLI (tested at version 1.18.136)
  193. // ignores the following potential sources of a region for the purposes of
  194. // this AssumeRole call:
  195. //
  196. // - The [default] profile
  197. // - The AWS_REGION environment variable
  198. //
  199. // Ignoring the [default] profile for the purposes of AssumeRole is arguably
  200. // a bug in the CLI since it does use the [default] region for service
  201. // calls... but right now we're matching behavior of the other tool.
  202. var profileRegion = roleProfile['region'] || ASSUME_ROLE_DEFAULT_REGION;
  203. if (!sourceProfileName) {
  204. throw AWS.util.error(
  205. new Error('source_profile is not set using profile ' + this.profile),
  206. { code: 'SharedIniFileCredentialsProviderFailure' }
  207. );
  208. }
  209. var sourceProfileExistanceTest = creds[sourceProfileName];
  210. if (typeof sourceProfileExistanceTest !== 'object') {
  211. throw AWS.util.error(
  212. new Error('source_profile ' + sourceProfileName + ' using profile '
  213. + this.profile + ' does not exist'),
  214. { code: 'SharedIniFileCredentialsProviderFailure' }
  215. );
  216. }
  217. var sourceCredentials = new AWS.SharedIniFileCredentials(
  218. AWS.util.merge(this.options || {}, {
  219. profile: sourceProfileName,
  220. preferStaticCredentials: true
  221. })
  222. );
  223. this.roleArn = roleArn;
  224. var sts = new STS({
  225. credentials: sourceCredentials,
  226. region: profileRegion,
  227. httpOptions: this.httpOptions
  228. });
  229. var roleParams = {
  230. DurationSeconds: durationSeconds,
  231. RoleArn: roleArn,
  232. RoleSessionName: roleSessionName || 'aws-sdk-js-' + Date.now()
  233. };
  234. if (externalId) {
  235. roleParams.ExternalId = externalId;
  236. }
  237. if (mfaSerial && self.tokenCodeFn) {
  238. roleParams.SerialNumber = mfaSerial;
  239. self.tokenCodeFn(mfaSerial, function(err, token) {
  240. if (err) {
  241. var message;
  242. if (err instanceof Error) {
  243. message = err.message;
  244. } else {
  245. message = err;
  246. }
  247. callback(
  248. AWS.util.error(
  249. new Error('Error fetching MFA token: ' + message),
  250. { code: 'SharedIniFileCredentialsProviderFailure' }
  251. ));
  252. return;
  253. }
  254. roleParams.TokenCode = token;
  255. sts.assumeRole(roleParams, callback);
  256. });
  257. return;
  258. }
  259. sts.assumeRole(roleParams, callback);
  260. }
  261. });