You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
157 lines
4.0 KiB
157 lines
4.0 KiB
1 month ago
|
const debug = require('debug')('ali-oss:sts');
|
||
|
const crypto = require('crypto');
|
||
|
const querystring = require('querystring');
|
||
|
const copy = require('copy-to');
|
||
|
const AgentKeepalive = require('agentkeepalive');
|
||
|
const is = require('is-type-of');
|
||
|
const ms = require('humanize-ms');
|
||
|
const urllib = require('urllib');
|
||
|
|
||
|
const globalHttpAgent = new AgentKeepalive();
|
||
|
|
||
|
function STS(options) {
|
||
|
if (!(this instanceof STS)) {
|
||
|
return new STS(options);
|
||
|
}
|
||
|
|
||
|
if (!options || !options.accessKeyId || !options.accessKeySecret) {
|
||
|
throw new Error('require accessKeyId, accessKeySecret');
|
||
|
}
|
||
|
|
||
|
this.options = {
|
||
|
endpoint: options.endpoint || 'https://sts.aliyuncs.com',
|
||
|
format: 'JSON',
|
||
|
apiVersion: '2015-04-01',
|
||
|
sigMethod: 'HMAC-SHA1',
|
||
|
sigVersion: '1.0',
|
||
|
timeout: '60s'
|
||
|
};
|
||
|
copy(options).to(this.options);
|
||
|
|
||
|
// support custom agent and urllib client
|
||
|
if (this.options.urllib) {
|
||
|
this.urllib = this.options.urllib;
|
||
|
} else {
|
||
|
this.urllib = urllib;
|
||
|
this.agent = this.options.agent || globalHttpAgent;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = STS;
|
||
|
|
||
|
const proto = STS.prototype;
|
||
|
|
||
|
/**
|
||
|
* STS opertaions
|
||
|
*/
|
||
|
|
||
|
proto.assumeRole = async function assumeRole(role, policy, expiration, session, options) {
|
||
|
const opts = this.options;
|
||
|
const params = {
|
||
|
Action: 'AssumeRole',
|
||
|
RoleArn: role,
|
||
|
RoleSessionName: session || 'app',
|
||
|
DurationSeconds: expiration || 3600,
|
||
|
|
||
|
Format: opts.format,
|
||
|
Version: opts.apiVersion,
|
||
|
AccessKeyId: opts.accessKeyId,
|
||
|
SignatureMethod: opts.sigMethod,
|
||
|
SignatureVersion: opts.sigVersion,
|
||
|
SignatureNonce: Math.random(),
|
||
|
Timestamp: new Date().toISOString()
|
||
|
};
|
||
|
|
||
|
if (policy) {
|
||
|
let policyStr;
|
||
|
if (is.string(policy)) {
|
||
|
try {
|
||
|
policyStr = JSON.stringify(JSON.parse(policy));
|
||
|
} catch (err) {
|
||
|
throw new Error(`Policy string is not a valid JSON: ${err.message}`);
|
||
|
}
|
||
|
} else {
|
||
|
policyStr = JSON.stringify(policy);
|
||
|
}
|
||
|
params.Policy = policyStr;
|
||
|
}
|
||
|
|
||
|
const signature = this._getSignature('POST', params, opts.accessKeySecret);
|
||
|
params.Signature = signature;
|
||
|
|
||
|
const reqUrl = opts.endpoint;
|
||
|
const reqParams = {
|
||
|
agent: this.agent,
|
||
|
timeout: ms((options && options.timeout) || opts.timeout),
|
||
|
method: 'POST',
|
||
|
content: querystring.stringify(params),
|
||
|
headers: {
|
||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||
|
},
|
||
|
ctx: options && options.ctx
|
||
|
};
|
||
|
|
||
|
const result = await this.urllib.request(reqUrl, reqParams);
|
||
|
debug('response %s %s, got %s, headers: %j', reqParams.method, reqUrl, result.status, result.headers);
|
||
|
|
||
|
if (Math.floor(result.status / 100) !== 2) {
|
||
|
const err = await this._requestError(result);
|
||
|
err.params = reqParams;
|
||
|
throw err;
|
||
|
}
|
||
|
result.data = JSON.parse(result.data);
|
||
|
|
||
|
return {
|
||
|
res: result.res,
|
||
|
credentials: result.data.Credentials
|
||
|
};
|
||
|
};
|
||
|
|
||
|
proto._requestError = async function _requestError(result) {
|
||
|
const err = new Error();
|
||
|
err.status = result.status;
|
||
|
|
||
|
try {
|
||
|
const resp = (await JSON.parse(result.data)) || {};
|
||
|
err.code = resp.Code;
|
||
|
err.message = `${resp.Code}: ${resp.Message}`;
|
||
|
err.requestId = resp.RequestId;
|
||
|
} catch (e) {
|
||
|
err.message = `UnknownError: ${String(result.data)}`;
|
||
|
}
|
||
|
|
||
|
return err;
|
||
|
};
|
||
|
|
||
|
proto._getSignature = function _getSignature(method, params, key) {
|
||
|
const that = this;
|
||
|
const canoQuery = Object.keys(params)
|
||
|
.sort()
|
||
|
.map(k => `${that._escape(k)}=${that._escape(params[k])}`)
|
||
|
.join('&');
|
||
|
|
||
|
const stringToSign = `${method.toUpperCase()}&${this._escape('/')}&${this._escape(canoQuery)}`;
|
||
|
|
||
|
debug('string to sign: %s', stringToSign);
|
||
|
|
||
|
let signature = crypto.createHmac('sha1', `${key}&`);
|
||
|
signature = signature.update(stringToSign).digest('base64');
|
||
|
|
||
|
debug('signature: %s', signature);
|
||
|
|
||
|
return signature;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Since `encodeURIComponent` doesn't encode '*', which causes
|
||
|
* 'SignatureDoesNotMatch'. We need do it ourselves.
|
||
|
*/
|
||
|
proto._escape = function _escape(str) {
|
||
|
return encodeURIComponent(str)
|
||
|
.replace(/!/g, '%21')
|
||
|
.replace(/'/g, '%27')
|
||
|
.replace(/\(/g, '%28')
|
||
|
.replace(/\)/g, '%29')
|
||
|
.replace(/\*/g, '%2A');
|
||
|
};
|