sts.js 3.9 KB

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