remote_credentials.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. var fs = require('fs');
  2. var AWS = require('../core'),
  3. ENV_RELATIVE_URI = 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI',
  4. ENV_FULL_URI = 'AWS_CONTAINER_CREDENTIALS_FULL_URI',
  5. ENV_AUTH_TOKEN = 'AWS_CONTAINER_AUTHORIZATION_TOKEN',
  6. ENV_AUTH_TOKEN_FILE = 'AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE',
  7. FULL_URI_UNRESTRICTED_PROTOCOLS = ['https:'],
  8. FULL_URI_ALLOWED_PROTOCOLS = ['http:', 'https:'],
  9. FULL_URI_ALLOWED_HOSTNAMES = ['localhost', '127.0.0.1', '169.254.170.23'],
  10. RELATIVE_URI_HOST = '169.254.170.2';
  11. /**
  12. * Represents credentials received from specified URI.
  13. *
  14. * This class will request refreshable credentials from the relative URI
  15. * specified by the AWS_CONTAINER_CREDENTIALS_RELATIVE_URI or the
  16. * AWS_CONTAINER_CREDENTIALS_FULL_URI environment variable. If valid credentials
  17. * are returned in the response, these will be used with zero configuration.
  18. *
  19. * This credentials class will by default timeout after 1 second of inactivity
  20. * and retry 3 times.
  21. * If your requests to the relative URI are timing out, you can increase
  22. * the value by configuring them directly:
  23. *
  24. * ```javascript
  25. * AWS.config.credentials = new AWS.RemoteCredentials({
  26. * httpOptions: { timeout: 5000 }, // 5 second timeout
  27. * maxRetries: 10, // retry 10 times
  28. * retryDelayOptions: { base: 200 } // see AWS.Config for information
  29. * });
  30. * ```
  31. *
  32. * @see AWS.Config.retryDelayOptions
  33. *
  34. * @!macro nobrowser
  35. */
  36. AWS.RemoteCredentials = AWS.util.inherit(AWS.Credentials, {
  37. constructor: function RemoteCredentials(options) {
  38. AWS.Credentials.call(this);
  39. options = options ? AWS.util.copy(options) : {};
  40. if (!options.httpOptions) options.httpOptions = {};
  41. options.httpOptions = AWS.util.merge(
  42. this.httpOptions, options.httpOptions);
  43. AWS.util.update(this, options);
  44. },
  45. /**
  46. * @api private
  47. */
  48. httpOptions: { timeout: 1000 },
  49. /**
  50. * @api private
  51. */
  52. maxRetries: 3,
  53. /**
  54. * @api private
  55. */
  56. isConfiguredForEcsCredentials: function isConfiguredForEcsCredentials() {
  57. return Boolean(
  58. process &&
  59. process.env &&
  60. (process.env[ENV_RELATIVE_URI] || process.env[ENV_FULL_URI])
  61. );
  62. },
  63. /**
  64. * @api private
  65. */
  66. getECSFullUri: function getECSFullUri() {
  67. if (process && process.env) {
  68. var relative = process.env[ENV_RELATIVE_URI],
  69. full = process.env[ENV_FULL_URI];
  70. if (relative) {
  71. return 'http://' + RELATIVE_URI_HOST + relative;
  72. } else if (full) {
  73. var parsed = AWS.util.urlParse(full);
  74. if (FULL_URI_ALLOWED_PROTOCOLS.indexOf(parsed.protocol) < 0) {
  75. throw AWS.util.error(
  76. new Error('Unsupported protocol: AWS.RemoteCredentials supports '
  77. + FULL_URI_ALLOWED_PROTOCOLS.join(',') + ' only; '
  78. + parsed.protocol + ' requested.'),
  79. { code: 'ECSCredentialsProviderFailure' }
  80. );
  81. }
  82. if (FULL_URI_UNRESTRICTED_PROTOCOLS.indexOf(parsed.protocol) < 0 &&
  83. FULL_URI_ALLOWED_HOSTNAMES.indexOf(parsed.hostname) < 0) {
  84. throw AWS.util.error(
  85. new Error('Unsupported hostname: AWS.RemoteCredentials only supports '
  86. + FULL_URI_ALLOWED_HOSTNAMES.join(',') + ' for ' + parsed.protocol + '; '
  87. + parsed.protocol + '//' + parsed.hostname + ' requested.'),
  88. { code: 'ECSCredentialsProviderFailure' }
  89. );
  90. }
  91. return full;
  92. } else {
  93. throw AWS.util.error(
  94. new Error('Variable ' + ENV_RELATIVE_URI + ' or ' + ENV_FULL_URI +
  95. ' must be set to use AWS.RemoteCredentials.'),
  96. { code: 'ECSCredentialsProviderFailure' }
  97. );
  98. }
  99. } else {
  100. throw AWS.util.error(
  101. new Error('No process info available'),
  102. { code: 'ECSCredentialsProviderFailure' }
  103. );
  104. }
  105. },
  106. /**
  107. * @api private
  108. */
  109. getECSAuthToken: function getECSAuthToken() {
  110. if (process && process.env && (process.env[ENV_FULL_URI] || process.env[ENV_AUTH_TOKEN_FILE])) {
  111. if (!process.env[ENV_AUTH_TOKEN] && process.env[ENV_AUTH_TOKEN_FILE]) {
  112. try {
  113. var data = fs.readFileSync(process.env[ENV_AUTH_TOKEN_FILE]).toString();
  114. return data;
  115. } catch (error) {
  116. console.error('Error reading token file:', error);
  117. throw error; // Re-throw the error to propagate it
  118. }
  119. }
  120. return process.env[ENV_AUTH_TOKEN];
  121. }
  122. },
  123. /**
  124. * @api private
  125. */
  126. credsFormatIsValid: function credsFormatIsValid(credData) {
  127. return (!!credData.accessKeyId && !!credData.secretAccessKey &&
  128. !!credData.sessionToken && !!credData.expireTime);
  129. },
  130. /**
  131. * @api private
  132. */
  133. formatCreds: function formatCreds(credData) {
  134. if (!!credData.credentials) {
  135. credData = credData.credentials;
  136. }
  137. return {
  138. expired: false,
  139. accessKeyId: credData.accessKeyId || credData.AccessKeyId,
  140. secretAccessKey: credData.secretAccessKey || credData.SecretAccessKey,
  141. sessionToken: credData.sessionToken || credData.Token,
  142. expireTime: new Date(credData.expiration || credData.Expiration)
  143. };
  144. },
  145. /**
  146. * @api private
  147. */
  148. request: function request(url, callback) {
  149. var httpRequest = new AWS.HttpRequest(url);
  150. httpRequest.method = 'GET';
  151. httpRequest.headers.Accept = 'application/json';
  152. var token = this.getECSAuthToken();
  153. if (token) {
  154. httpRequest.headers.Authorization = token;
  155. }
  156. AWS.util.handleRequestWithRetries(httpRequest, this, callback);
  157. },
  158. /**
  159. * Loads the credentials from the relative URI specified by container
  160. *
  161. * @callback callback function(err)
  162. * Called when the request to the relative URI responds (or fails). When
  163. * this callback is called with no error, it means that the credentials
  164. * information has been loaded into the object (as the `accessKeyId`,
  165. * `secretAccessKey`, `sessionToken`, and `expireTime` properties).
  166. * @param err [Error] if an error occurred, this value will be filled
  167. * @see get
  168. */
  169. refresh: function refresh(callback) {
  170. this.coalesceRefresh(callback || AWS.util.fn.callback);
  171. },
  172. /**
  173. * @api private
  174. */
  175. load: function load(callback) {
  176. var self = this;
  177. var fullUri;
  178. try {
  179. fullUri = this.getECSFullUri();
  180. } catch (err) {
  181. callback(err);
  182. return;
  183. }
  184. this.request(fullUri, function(err, data) {
  185. if (!err) {
  186. try {
  187. data = JSON.parse(data);
  188. var creds = self.formatCreds(data);
  189. if (!self.credsFormatIsValid(creds)) {
  190. throw AWS.util.error(
  191. new Error('Response data is not in valid format'),
  192. { code: 'ECSCredentialsProviderFailure' }
  193. );
  194. }
  195. AWS.util.update(self, creds);
  196. } catch (dataError) {
  197. err = dataError;
  198. }
  199. }
  200. callback(err, creds);
  201. });
  202. }
  203. });