-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
183 lines (167 loc) · 5.37 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
const { STSClient, AssumeRoleCommand } = require('@aws-sdk/client-sts');
const { fromIni, fromEnv } = require('@aws-sdk/credential-providers');
const aws4 = require('aws4');
/**
* @typedef { import("superagent").Plugin } Plugin
*/
/**
* @description Helper utility to sign aws request, to invoke aws resources protected by IAM role.
*/
class AwsSignRequest {
/**
* @description Default service name for the request.
*/
defaultService;
/**
* @description AWS region for the request.
*/
region;
/**
* @description AWS credentials used for signing requests.
*/
#credentials;
/**
* @description AWS session credentials used for signing requests.
*/
session;
/**
* @param {string} defaultService - Default service name for the request. (optional)
* @default
*/
constructor(defaultService = 'execute-api') {
this.defaultService = defaultService;
}
/**
* @description Set aws credentials manually, e.g., env
* @param {aws4.Credentials} credentials - The AWS credentials to set.
* @returns {aws4.Credentials} - The set AWS credentials.
*/
setCredentials(credentials) {
this.#credentials = credentials;
return this.#credentials;
}
/**
* @description Get and set aws credentials from local ~.aws/credentials
* @param {string} profile - The profile name in the credentials file.
* @returns {Promise<aws4.Credentials>} - The set AWS credentials.
*/
async setCredentialsFromConfig(profile) {
const getShared = fromIni({ profile });
this.#credentials = await getShared();
return this.#credentials;
}
/**
* @description Get and set aws credentials from environment variables
* @returns {Promise<aws4.Credentials>} - The set AWS credentials.
*/
async setCredentialsFromEnv() {
const env = fromEnv();
this.#credentials = await env();
return this.#credentials;
}
/**
* @description Create a session login.
* @param {object} params - The parameters for assuming a role.
* @returns {Promise<void>}
*/
async assumeRole(params) {
if (!this.#credentials)
throw new Error('No credentials set');
if (!this.region)
throw new Error('No region set');
const client = new STSClient({
credentials: this.#credentials,
region: this.region,
});
const command = new AssumeRoleCommand(params);
try {
const data = await client.send(command);
if (!data.Credentials)
throw new Error('No credentials received');
this.session = {
accessKeyId: data.Credentials.AccessKeyId,
secretAccessKey: data.Credentials.SecretAccessKey,
sessionToken: data.Credentials.SessionToken,
};
}
catch (error) {
console.error(error);
throw new Error('Could not create session credentials');
}
}
/**
* @description Remove possible previous set session.
*/
removeRole() {
this.session = null;
}
/**
* @description Set aws region.
* @param {string} region - The AWS region to set.
* @returns {string} - The set AWS region.
*/
setRegion(region) {
this.region = region;
return this.region;
}
/**
* @description Create custom req.end which intercepts the request and signs it off with all the needed data, returns the original end function.
* @param {string} [requestService] - The service name for the request. (optional)
* @returns {Plugin} - The signRequest function.
*/
add(requestService = undefined) {
const service = requestService ?? this.defaultService;
const region = this.region;
const sign = this.sign;
const cred = this.session ?? this.#credentials;
return function signRequest(req) {
req._originalEnd = req.end;
// Replace end function, which is called after .send() to get all the headers before the actual call
req.end = function (callback) {
const headers = req.header;
const body
= req.header['Content-Type'] === 'application/json'
? JSON.stringify(req._data)
: req._formData;
const parsedUrl = new URL(req.url);
let path = parsedUrl.pathname;
if (req.qs) {
const query = new URLSearchParams(req.qs);
path = path + (path.includes('?') ? '&' : '?') + query.toString();
}
const request = {
host: parsedUrl.host,
method: req.method,
path,
body,
service,
region,
headers,
};
const signedOptions = sign(request, cred);
// Add signed header which actually does the IAM role check
req.header = signedOptions.headers;
// set the original end function back and end the call
req.end = req._originalEnd;
req.end(callback);
return this;
};
return req;
};
}
/**
* @description Sign the request with the credentials.
* @param {object} request - The request object.
* @param {aws4.Credentials} [credentials] - The AWS credentials to use for signing. (optional)
* @returns {object} - The aws4 signed request object.
*/
sign(request, credentials = undefined) {
const cred = credentials ?? this.session ?? this.#credentials;
// If using assumeRole() the credentials will also hold a session token
if (cred.sessionToken) {
request.headers['X-Amz-Security-Token'] = cred.sessionToken;
}
return aws4.sign(request, cred);
}
}
module.exports = AwsSignRequest;