sts.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. const debug = require('debug')('ali-oss:sts');
  2. const crypto = require('crypto');
  3. const querystring = require('querystring');
  4. const copy = require('copy-to');
  5. const AgentKeepalive = require('agentkeepalive');
  6. const is = require('is-type-of');
  7. const ms = require('humanize-ms');
  8. const urllib = require('urllib');
  9. const globalHttpAgent = new AgentKeepalive();
  10. function STS(options) {
  11. if (!(this instanceof STS)) {
  12. return new STS(options);
  13. }
  14. if (!options
  15. || !options.accessKeyId
  16. || !options.accessKeySecret) {
  17. throw new Error('require accessKeyId, accessKeySecret');
  18. }
  19. this.options = {
  20. endpoint: options.endpoint || 'https://sts.aliyuncs.com',
  21. format: 'JSON',
  22. apiVersion: '2015-04-01',
  23. sigMethod: 'HMAC-SHA1',
  24. sigVersion: '1.0',
  25. timeout: '60s'
  26. };
  27. copy(options).to(this.options);
  28. // support custom agent and urllib client
  29. if (this.options.urllib) {
  30. this.urllib = this.options.urllib;
  31. } else {
  32. this.urllib = urllib;
  33. this.agent = this.options.agent || globalHttpAgent;
  34. }
  35. }
  36. module.exports = STS;
  37. const proto = STS.prototype;
  38. /**
  39. * STS opertaions
  40. */
  41. proto.assumeRole = async function assumeRole(role, policy, expiration, session, options) {
  42. const opts = this.options;
  43. const params = {
  44. Action: 'AssumeRole',
  45. RoleArn: role,
  46. RoleSessionName: session || 'app',
  47. DurationSeconds: expiration || 3600,
  48. Format: opts.format,
  49. Version: opts.apiVersion,
  50. AccessKeyId: opts.accessKeyId,
  51. SignatureMethod: opts.sigMethod,
  52. SignatureVersion: opts.sigVersion,
  53. SignatureNonce: Math.random(),
  54. Timestamp: new Date().toISOString()
  55. };
  56. if (policy) {
  57. let policyStr;
  58. if (is.string(policy)) {
  59. try {
  60. policyStr = JSON.stringify(JSON.parse(policy));
  61. } catch (err) {
  62. throw new Error(`Policy string is not a valid JSON: ${err.message}`);
  63. }
  64. } else {
  65. policyStr = JSON.stringify(policy);
  66. }
  67. params.Policy = policyStr;
  68. }
  69. const signature = this._getSignature('POST', params, opts.accessKeySecret);
  70. params.Signature = signature;
  71. const reqUrl = opts.endpoint;
  72. const reqParams = {
  73. agent: this.agent,
  74. timeout: ms((options && options.timeout) || opts.timeout),
  75. method: 'POST',
  76. content: querystring.stringify(params),
  77. headers: {
  78. 'Content-Type': 'application/x-www-form-urlencoded'
  79. },
  80. ctx: options && options.ctx
  81. };
  82. const result = await this.urllib.request(reqUrl, reqParams);
  83. debug(
  84. 'response %s %s, got %s, headers: %j',
  85. reqParams.method, reqUrl, result.status, result.headers
  86. );
  87. if (Math.floor(result.status / 100) !== 2) {
  88. const err = await this._requestError(result);
  89. err.params = reqParams;
  90. throw err;
  91. }
  92. result.data = JSON.parse(result.data);
  93. return {
  94. res: result.res,
  95. credentials: result.data.Credentials
  96. };
  97. };
  98. proto._requestError = async function _requestError(result) {
  99. const err = new Error();
  100. err.status = result.status;
  101. try {
  102. const resp = await JSON.parse(result.data) || {};
  103. err.code = resp.Code;
  104. err.message = `${resp.Code}: ${resp.Message}`;
  105. err.requestId = resp.RequestId;
  106. } catch (e) {
  107. err.message = `UnknownError: ${String(result.data)}`;
  108. }
  109. return err;
  110. };
  111. proto._getSignature = function _getSignature(method, params, key) {
  112. const that = this;
  113. const canoQuery = Object.keys(params).sort().map(k => `${that._escape(k)}=${that._escape(params[k])}`).join('&');
  114. const stringToSign =
  115. `${method.toUpperCase()
  116. }&${this._escape('/')
  117. }&${this._escape(canoQuery)}`;
  118. debug('string to sign: %s', stringToSign);
  119. let signature = crypto.createHmac('sha1', `${key}&`);
  120. signature = signature.update(stringToSign).digest('base64');
  121. debug('signature: %s', signature);
  122. return signature;
  123. };
  124. /**
  125. * Since `encodeURIComponent` doesn't encode '*', which causes
  126. * 'SignatureDoesNotMatch'. We need do it ourselves.
  127. */
  128. proto._escape = function _escape(str) {
  129. return encodeURIComponent(str)
  130. .replace(/!/g, '%21')
  131. .replace(/'/g, '%27')
  132. .replace(/\(/g, '%28')
  133. .replace(/\)/g, '%29')
  134. .replace(/\*/g, '%2A');
  135. };