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