chainable_temporary_credentials.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. var AWS = require('../core');
  2. var STS = require('../../clients/sts');
  3. /**
  4. * Represents temporary credentials retrieved from {AWS.STS}. Without any
  5. * extra parameters, credentials will be fetched from the
  6. * {AWS.STS.getSessionToken} operation. If an IAM role is provided, the
  7. * {AWS.STS.assumeRole} operation will be used to fetch credentials for the
  8. * role instead.
  9. *
  10. * AWS.ChainableTemporaryCredentials differs from AWS.TemporaryCredentials in
  11. * the way masterCredentials and refreshes are handled.
  12. * AWS.ChainableTemporaryCredentials refreshes expired credentials using the
  13. * masterCredentials passed by the user to support chaining of STS credentials.
  14. * However, AWS.TemporaryCredentials recursively collapses the masterCredentials
  15. * during instantiation, precluding the ability to refresh credentials which
  16. * require intermediate, temporary credentials.
  17. *
  18. * For example, if the application should use RoleA, which must be assumed from
  19. * RoleB, and the environment provides credentials which can assume RoleB, then
  20. * AWS.ChainableTemporaryCredentials must be used to support refreshing the
  21. * temporary credentials for RoleA:
  22. *
  23. * ```javascript
  24. * var roleACreds = new AWS.ChainableTemporaryCredentials({
  25. * params: {RoleArn: 'RoleA'},
  26. * masterCredentials: new AWS.ChainableTemporaryCredentials({
  27. * params: {RoleArn: 'RoleB'},
  28. * masterCredentials: new AWS.EnvironmentCredentials('AWS')
  29. * })
  30. * });
  31. * ```
  32. *
  33. * If AWS.TemporaryCredentials had been used in the previous example,
  34. * `roleACreds` would fail to refresh because `roleACreds` would
  35. * use the environment credentials for the AssumeRole request.
  36. *
  37. * Another difference is that AWS.ChainableTemporaryCredentials creates the STS
  38. * service instance during instantiation while AWS.TemporaryCredentials creates
  39. * the STS service instance during the first refresh. Creating the service
  40. * instance during instantiation effectively captures the master credentials
  41. * from the global config, so that subsequent changes to the global config do
  42. * not affect the master credentials used to refresh the temporary credentials.
  43. *
  44. * This allows an instance of AWS.ChainableTemporaryCredentials to be assigned
  45. * to AWS.config.credentials:
  46. *
  47. * ```javascript
  48. * var envCreds = new AWS.EnvironmentCredentials('AWS');
  49. * AWS.config.credentials = envCreds;
  50. * // masterCredentials will be envCreds
  51. * AWS.config.credentials = new AWS.ChainableTemporaryCredentials({
  52. * params: {RoleArn: '...'}
  53. * });
  54. * ```
  55. *
  56. * Similarly, to use the CredentialProviderChain's default providers as the
  57. * master credentials, simply create a new instance of
  58. * AWS.ChainableTemporaryCredentials:
  59. *
  60. * ```javascript
  61. * AWS.config.credentials = new ChainableTemporaryCredentials({
  62. * params: {RoleArn: '...'}
  63. * });
  64. * ```
  65. *
  66. * @!attribute service
  67. * @return [AWS.STS] the STS service instance used to
  68. * get and refresh temporary credentials from AWS STS.
  69. * @note (see constructor)
  70. */
  71. AWS.ChainableTemporaryCredentials = AWS.util.inherit(AWS.Credentials, {
  72. /**
  73. * Creates a new temporary credentials object.
  74. *
  75. * @param options [map] a set of options
  76. * @option options params [map] ({}) a map of options that are passed to the
  77. * {AWS.STS.assumeRole} or {AWS.STS.getSessionToken} operations.
  78. * If a `RoleArn` parameter is passed in, credentials will be based on the
  79. * IAM role. If a `SerialNumber` parameter is passed in, {tokenCodeFn} must
  80. * also be passed in or an error will be thrown.
  81. * @option options masterCredentials [AWS.Credentials] the master credentials
  82. * used to get and refresh temporary credentials from AWS STS. By default,
  83. * AWS.config.credentials or AWS.config.credentialProvider will be used.
  84. * @option options tokenCodeFn [Function] (null) Function to provide
  85. * `TokenCode`, if `SerialNumber` is provided for profile in {params}. Function
  86. * is called with value of `SerialNumber` and `callback`, and should provide
  87. * the `TokenCode` or an error to the callback in the format
  88. * `callback(err, token)`.
  89. * @example Creating a new credentials object for generic temporary credentials
  90. * AWS.config.credentials = new AWS.ChainableTemporaryCredentials();
  91. * @example Creating a new credentials object for an IAM role
  92. * AWS.config.credentials = new AWS.ChainableTemporaryCredentials({
  93. * params: {
  94. * RoleArn: 'arn:aws:iam::1234567890:role/TemporaryCredentials'
  95. * }
  96. * });
  97. * @see AWS.STS.assumeRole
  98. * @see AWS.STS.getSessionToken
  99. */
  100. constructor: function ChainableTemporaryCredentials(options) {
  101. AWS.Credentials.call(this);
  102. options = options || {};
  103. this.errorCode = 'ChainableTemporaryCredentialsProviderFailure';
  104. this.expired = true;
  105. this.tokenCodeFn = null;
  106. var params = AWS.util.copy(options.params) || {};
  107. if (params.RoleArn) {
  108. params.RoleSessionName = params.RoleSessionName || 'temporary-credentials';
  109. }
  110. if (params.SerialNumber) {
  111. if (!options.tokenCodeFn || (typeof options.tokenCodeFn !== 'function')) {
  112. throw new AWS.util.error(
  113. new Error('tokenCodeFn must be a function when params.SerialNumber is given'),
  114. {code: this.errorCode}
  115. );
  116. } else {
  117. this.tokenCodeFn = options.tokenCodeFn;
  118. }
  119. }
  120. var config = AWS.util.merge(
  121. {
  122. params: params,
  123. credentials: options.masterCredentials || AWS.config.credentials
  124. },
  125. options.stsConfig || {}
  126. );
  127. this.service = new STS(config);
  128. },
  129. /**
  130. * Refreshes credentials using {AWS.STS.assumeRole} or
  131. * {AWS.STS.getSessionToken}, depending on whether an IAM role ARN was passed
  132. * to the credentials {constructor}.
  133. *
  134. * @callback callback function(err)
  135. * Called when the STS service responds (or fails). When
  136. * this callback is called with no error, it means that the credentials
  137. * information has been loaded into the object (as the `accessKeyId`,
  138. * `secretAccessKey`, and `sessionToken` properties).
  139. * @param err [Error] if an error occurred, this value will be filled
  140. * @see AWS.Credentials.get
  141. */
  142. refresh: function refresh(callback) {
  143. this.coalesceRefresh(callback || AWS.util.fn.callback);
  144. },
  145. /**
  146. * @api private
  147. * @param callback
  148. */
  149. load: function load(callback) {
  150. var self = this;
  151. var operation = self.service.config.params.RoleArn ? 'assumeRole' : 'getSessionToken';
  152. this.getTokenCode(function (err, tokenCode) {
  153. var params = {};
  154. if (err) {
  155. callback(err);
  156. return;
  157. }
  158. if (tokenCode) {
  159. params.TokenCode = tokenCode;
  160. }
  161. self.service[operation](params, function (err, data) {
  162. if (!err) {
  163. self.service.credentialsFrom(data, self);
  164. }
  165. callback(err);
  166. });
  167. });
  168. },
  169. /**
  170. * @api private
  171. */
  172. getTokenCode: function getTokenCode(callback) {
  173. var self = this;
  174. if (this.tokenCodeFn) {
  175. this.tokenCodeFn(this.service.config.params.SerialNumber, function (err, token) {
  176. if (err) {
  177. var message = err;
  178. if (err instanceof Error) {
  179. message = err.message;
  180. }
  181. callback(
  182. AWS.util.error(
  183. new Error('Error fetching MFA token: ' + message),
  184. { code: self.errorCode}
  185. )
  186. );
  187. return;
  188. }
  189. callback(null, token);
  190. });
  191. } else {
  192. callback(null);
  193. }
  194. }
  195. });