@fishawack/lab-env 5.7.0-beta.1 → 5.7.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/_Test/provision.js +4 -9
- package/_Test/prune.js +8 -5
- package/bitbucket-pipelines.yml +2 -2
- package/cli.js +1 -0
- package/commands/create/cmds/dekey.js +13 -9
- package/commands/create/cmds/deprovision.js +36 -0
- package/commands/create/cmds/key.js +60 -41
- package/commands/create/cmds/provision.js +573 -69
- package/commands/create/cmds/rekey.js +218 -0
- package/commands/create/cmds/sync-secrets.js +2 -1
- package/commands/create/libs/output-credentials.js +59 -0
- package/commands/create/libs/parallel-runner.js +204 -0
- package/commands/create/libs/resolve-operator.js +59 -0
- package/commands/create/libs/utilities.js +73 -8
- package/commands/create/libs/vars.js +120 -136
- package/commands/create/services/aws/acm.js +61 -0
- package/commands/create/services/aws/cloudfront.js +51 -45
- package/commands/create/services/aws/ec2.js +675 -48
- package/commands/create/services/aws/elasticache.js +159 -0
- package/commands/create/services/aws/elasticbeanstalk.js +154 -57
- package/commands/create/services/aws/iam.js +402 -82
- package/commands/create/services/aws/index.js +1398 -260
- package/commands/create/services/aws/opensearch.js +155 -0
- package/commands/create/services/aws/rds.js +57 -31
- package/commands/create/services/aws/s3.js +52 -31
- package/commands/create/services/aws/secretsmanager.js +21 -12
- package/commands/create/services/aws/ses.js +195 -0
- package/commands/create/templates/elasticbeanstalk/.ebextensions/laravel/cron.config +45 -0
- package/commands/create/templates/elasticbeanstalk/.ebextensions/laravel/queue-worker.config +29 -0
- package/commands/create/templates/elasticbeanstalk/.ebextensions/laravel/software.config +12 -0
- package/commands/create/templates/elasticbeanstalk/.ebextensions/misc/setvars.config +2 -16
- package/commands/create/templates/elasticbeanstalk/.platform/confighooks/postdeploy/rewrite-flush.sh +1 -1
- package/commands/create/templates/elasticbeanstalk/.platform/confighooks/postdeploy/setvars.sh +19 -0
- package/commands/create/templates/elasticbeanstalk/.platform/hooks/postdeploy/restart_queue.sh +4 -0
- package/commands/create/templates/elasticbeanstalk/.platform/hooks/prebuild/setvars.sh +19 -0
- package/package.json +7 -2
|
@@ -9,25 +9,35 @@ const {
|
|
|
9
9
|
CreateAccessKeyCommand,
|
|
10
10
|
DeleteAccessKeyCommand,
|
|
11
11
|
ListAccessKeysCommand,
|
|
12
|
+
AddUserToGroupCommand,
|
|
12
13
|
GetRoleCommand,
|
|
13
14
|
CreateRoleCommand,
|
|
15
|
+
DeleteRoleCommand,
|
|
14
16
|
AttachRolePolicyCommand,
|
|
17
|
+
DetachRolePolicyCommand,
|
|
18
|
+
ListAttachedRolePoliciesCommand,
|
|
15
19
|
CreateInstanceProfileCommand,
|
|
20
|
+
DeleteInstanceProfileCommand,
|
|
16
21
|
AddRoleToInstanceProfileCommand,
|
|
22
|
+
RemoveRoleFromInstanceProfileCommand,
|
|
17
23
|
PutRolePolicyCommand,
|
|
24
|
+
DeleteRolePolicyCommand,
|
|
25
|
+
ListRolePoliciesCommand,
|
|
26
|
+
CreateServiceLinkedRoleCommand,
|
|
18
27
|
} = require("@aws-sdk/client-iam");
|
|
19
28
|
const { Spinner } = require("../../libs/utilities");
|
|
29
|
+
const { awsClientConfig } = require("../../libs/vars");
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
const client = new IAMClient({});
|
|
31
|
+
const getClient = () => new IAMClient(awsClientConfig);
|
|
23
32
|
|
|
33
|
+
module.exports.createIAMUser = async (UserName, tags = []) => {
|
|
24
34
|
let res;
|
|
25
35
|
|
|
26
36
|
try {
|
|
27
37
|
res = await Spinner.prototype.simple(
|
|
28
38
|
`Creating IAM user ${UserName}`,
|
|
29
39
|
() => {
|
|
30
|
-
return
|
|
40
|
+
return getClient().send(
|
|
31
41
|
new CreateUserCommand({
|
|
32
42
|
UserName,
|
|
33
43
|
Tags: [
|
|
@@ -41,7 +51,7 @@ module.exports.createIAMUser = async (UserName, tags = []) => {
|
|
|
41
51
|
res = await Spinner.prototype.simple(
|
|
42
52
|
`Retrieving the already existing IAM user ${UserName}`,
|
|
43
53
|
() => {
|
|
44
|
-
return
|
|
54
|
+
return getClient().send(new GetUserCommand({ UserName }));
|
|
45
55
|
},
|
|
46
56
|
);
|
|
47
57
|
}
|
|
@@ -59,16 +69,25 @@ module.exports.createFWIAMUser = async (UserName, permissions) => {
|
|
|
59
69
|
return res;
|
|
60
70
|
};
|
|
61
71
|
|
|
62
|
-
module.exports.
|
|
63
|
-
const
|
|
72
|
+
module.exports.addUserToGroup = async (UserName, GroupName, iamClient) => {
|
|
73
|
+
const c = iamClient || getClient();
|
|
74
|
+
|
|
75
|
+
await Spinner.prototype.simple(
|
|
76
|
+
`Adding IAM user ${UserName} to group ${GroupName}`,
|
|
77
|
+
() => {
|
|
78
|
+
return c.send(new AddUserToGroupCommand({ UserName, GroupName }));
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
};
|
|
64
82
|
|
|
83
|
+
module.exports.removeIAMUser = async (UserName) => {
|
|
65
84
|
let res;
|
|
66
85
|
|
|
67
86
|
try {
|
|
68
87
|
await Spinner.prototype.simple(
|
|
69
88
|
`Checking if IAM user ${UserName} exists`,
|
|
70
89
|
() => {
|
|
71
|
-
return
|
|
90
|
+
return getClient().send(new GetUserCommand({ UserName }));
|
|
72
91
|
},
|
|
73
92
|
);
|
|
74
93
|
|
|
@@ -79,7 +98,7 @@ module.exports.removeIAMUser = async (UserName) => {
|
|
|
79
98
|
res = await Spinner.prototype.simple(
|
|
80
99
|
`Removing IAM user ${UserName}`,
|
|
81
100
|
() => {
|
|
82
|
-
return
|
|
101
|
+
return getClient().send(new DeleteUserCommand({ UserName }));
|
|
83
102
|
},
|
|
84
103
|
);
|
|
85
104
|
} catch {
|
|
@@ -90,12 +109,10 @@ module.exports.removeIAMUser = async (UserName) => {
|
|
|
90
109
|
};
|
|
91
110
|
|
|
92
111
|
module.exports.attachIAMPolicy = async (UserName, policy) => {
|
|
93
|
-
const client = new IAMClient({});
|
|
94
|
-
|
|
95
112
|
let res = await Spinner.prototype.simple(
|
|
96
113
|
`Attaching IAM policy ${policy}`,
|
|
97
114
|
() => {
|
|
98
|
-
return
|
|
115
|
+
return getClient().send(
|
|
99
116
|
new AttachUserPolicyCommand({ UserName, PolicyArn: policy }),
|
|
100
117
|
);
|
|
101
118
|
},
|
|
@@ -107,68 +124,83 @@ module.exports.attachIAMPolicy = async (UserName, policy) => {
|
|
|
107
124
|
module.exports.syncFWIAMPolicies = async (
|
|
108
125
|
UserName,
|
|
109
126
|
permissions = ["auto-content", "deploy-static"],
|
|
127
|
+
preserve = [],
|
|
110
128
|
) => {
|
|
111
|
-
await module.exports.
|
|
129
|
+
let res = await module.exports.listIAMPolicies(UserName);
|
|
130
|
+
|
|
131
|
+
// Detach existing policies in parallel
|
|
132
|
+
const toDetach = res.AttachedPolicies.filter(
|
|
133
|
+
(p) => !preserve.includes(p.PolicyArn),
|
|
134
|
+
);
|
|
135
|
+
if (toDetach.length) {
|
|
136
|
+
await Spinner.prototype.simple(
|
|
137
|
+
`Detaching ${toDetach.length} IAM policies`,
|
|
138
|
+
() =>
|
|
139
|
+
Promise.all(
|
|
140
|
+
toDetach.map((p) =>
|
|
141
|
+
getClient().send(
|
|
142
|
+
new DetachUserPolicyCommand({
|
|
143
|
+
UserName,
|
|
144
|
+
PolicyArn: p.PolicyArn,
|
|
145
|
+
}),
|
|
146
|
+
),
|
|
147
|
+
),
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Collect policies to attach
|
|
153
|
+
const policies = [];
|
|
112
154
|
|
|
113
155
|
if (
|
|
114
156
|
permissions.includes("auto-content") ||
|
|
115
157
|
permissions.includes("deploy-static")
|
|
116
158
|
) {
|
|
117
|
-
|
|
118
|
-
UserName,
|
|
119
|
-
"arn:aws:iam::aws:policy/AmazonS3FullAccess",
|
|
120
|
-
);
|
|
159
|
+
policies.push("arn:aws:iam::aws:policy/AmazonS3FullAccess");
|
|
121
160
|
}
|
|
122
161
|
|
|
123
162
|
if (permissions.includes("deploy-static")) {
|
|
124
|
-
|
|
125
|
-
UserName,
|
|
126
|
-
"arn:aws:iam::aws:policy/CloudFrontFullAccess",
|
|
127
|
-
);
|
|
163
|
+
policies.push("arn:aws:iam::aws:policy/CloudFrontFullAccess");
|
|
128
164
|
}
|
|
129
165
|
|
|
130
166
|
if (permissions.includes("deploy-fullstack")) {
|
|
131
|
-
|
|
132
|
-
UserName,
|
|
167
|
+
policies.push(
|
|
133
168
|
"arn:aws:iam::aws:policy/AdministratorAccess-AWSElasticBeanstalk",
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
await module.exports.attachIAMPolicy(
|
|
137
|
-
UserName,
|
|
138
169
|
"arn:aws:iam::aws:policy/AmazonOpenSearchServiceFullAccess",
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
await module.exports.attachIAMPolicy(
|
|
142
|
-
UserName,
|
|
143
170
|
"arn:aws:iam::aws:policy/AmazonRDSFullAccess",
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
await module.exports.attachIAMPolicy(
|
|
147
|
-
UserName,
|
|
148
171
|
"arn:aws:iam::aws:policy/AmazonElastiCacheFullAccess",
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
await module.exports.attachIAMPolicy(
|
|
152
|
-
UserName,
|
|
153
172
|
"arn:aws:iam::aws:policy/SecretsManagerReadWrite",
|
|
154
173
|
);
|
|
155
174
|
}
|
|
156
175
|
|
|
157
176
|
if (permissions.includes("manage-users")) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
177
|
+
policies.push("arn:aws:iam::aws:policy/IAMFullAccess");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Attach all policies in parallel
|
|
181
|
+
if (policies.length) {
|
|
182
|
+
await Spinner.prototype.simple(
|
|
183
|
+
`Attaching ${policies.length} IAM policies`,
|
|
184
|
+
() =>
|
|
185
|
+
Promise.all(
|
|
186
|
+
policies.map((p) =>
|
|
187
|
+
getClient().send(
|
|
188
|
+
new AttachUserPolicyCommand({
|
|
189
|
+
UserName,
|
|
190
|
+
PolicyArn: p,
|
|
191
|
+
}),
|
|
192
|
+
),
|
|
193
|
+
),
|
|
194
|
+
),
|
|
161
195
|
);
|
|
162
196
|
}
|
|
163
197
|
};
|
|
164
198
|
|
|
165
199
|
module.exports.removeIAMPolicy = async (UserName, policy) => {
|
|
166
|
-
const client = new IAMClient({});
|
|
167
|
-
|
|
168
200
|
let res = await Spinner.prototype.simple(
|
|
169
201
|
`Detaching IAM policy ${policy}`,
|
|
170
202
|
() => {
|
|
171
|
-
return
|
|
203
|
+
return getClient().send(
|
|
172
204
|
new DetachUserPolicyCommand({ UserName, PolicyArn: policy }),
|
|
173
205
|
);
|
|
174
206
|
},
|
|
@@ -178,10 +210,10 @@ module.exports.removeIAMPolicy = async (UserName, policy) => {
|
|
|
178
210
|
};
|
|
179
211
|
|
|
180
212
|
module.exports.listIAMPolicies = async (UserName) => {
|
|
181
|
-
const client = new IAMClient({});
|
|
182
|
-
|
|
183
213
|
let res = await Spinner.prototype.simple(`Listing IAM policies`, () => {
|
|
184
|
-
return
|
|
214
|
+
return getClient().send(
|
|
215
|
+
new ListAttachedUserPoliciesCommand({ UserName }),
|
|
216
|
+
);
|
|
185
217
|
});
|
|
186
218
|
|
|
187
219
|
return res;
|
|
@@ -190,10 +222,20 @@ module.exports.listIAMPolicies = async (UserName) => {
|
|
|
190
222
|
module.exports.removeAllIAMPolicies = async (UserName) => {
|
|
191
223
|
let res = await module.exports.listIAMPolicies(UserName);
|
|
192
224
|
|
|
193
|
-
|
|
194
|
-
await
|
|
195
|
-
|
|
196
|
-
|
|
225
|
+
if (res.AttachedPolicies.length) {
|
|
226
|
+
await Spinner.prototype.simple(
|
|
227
|
+
`Detaching ${res.AttachedPolicies.length} IAM policies`,
|
|
228
|
+
() =>
|
|
229
|
+
Promise.all(
|
|
230
|
+
res.AttachedPolicies.map((p) =>
|
|
231
|
+
getClient().send(
|
|
232
|
+
new DetachUserPolicyCommand({
|
|
233
|
+
UserName,
|
|
234
|
+
PolicyArn: p.PolicyArn,
|
|
235
|
+
}),
|
|
236
|
+
),
|
|
237
|
+
),
|
|
238
|
+
),
|
|
197
239
|
);
|
|
198
240
|
}
|
|
199
241
|
|
|
@@ -201,22 +243,18 @@ module.exports.removeAllIAMPolicies = async (UserName) => {
|
|
|
201
243
|
};
|
|
202
244
|
|
|
203
245
|
module.exports.createAccessKey = async (UserName) => {
|
|
204
|
-
const client = new IAMClient({});
|
|
205
|
-
|
|
206
246
|
let res = await Spinner.prototype.simple(`Creating access key`, () => {
|
|
207
|
-
return
|
|
247
|
+
return getClient().send(new CreateAccessKeyCommand({ UserName }));
|
|
208
248
|
});
|
|
209
249
|
|
|
210
250
|
return res;
|
|
211
251
|
};
|
|
212
252
|
|
|
213
253
|
module.exports.removeAccessKey = async (UserName, AccessKeyId) => {
|
|
214
|
-
const client = new IAMClient({});
|
|
215
|
-
|
|
216
254
|
let res = await Spinner.prototype.simple(
|
|
217
255
|
`Removing access key ${AccessKeyId}`,
|
|
218
256
|
() => {
|
|
219
|
-
return
|
|
257
|
+
return getClient().send(
|
|
220
258
|
new DeleteAccessKeyCommand({ AccessKeyId, UserName }),
|
|
221
259
|
);
|
|
222
260
|
},
|
|
@@ -226,10 +264,8 @@ module.exports.removeAccessKey = async (UserName, AccessKeyId) => {
|
|
|
226
264
|
};
|
|
227
265
|
|
|
228
266
|
module.exports.listAccessKeys = async (UserName) => {
|
|
229
|
-
const client = new IAMClient({});
|
|
230
|
-
|
|
231
267
|
let res = await Spinner.prototype.simple(`Listing access keys`, () => {
|
|
232
|
-
return
|
|
268
|
+
return getClient().send(new ListAccessKeysCommand({ UserName }));
|
|
233
269
|
});
|
|
234
270
|
|
|
235
271
|
return res;
|
|
@@ -238,10 +274,20 @@ module.exports.listAccessKeys = async (UserName) => {
|
|
|
238
274
|
module.exports.removeAllAccessKeys = async (UserName) => {
|
|
239
275
|
let res = await module.exports.listAccessKeys(UserName);
|
|
240
276
|
|
|
241
|
-
|
|
242
|
-
await
|
|
243
|
-
|
|
244
|
-
|
|
277
|
+
if (res.AccessKeyMetadata.length) {
|
|
278
|
+
await Spinner.prototype.simple(
|
|
279
|
+
`Removing ${res.AccessKeyMetadata.length} access keys`,
|
|
280
|
+
() =>
|
|
281
|
+
Promise.all(
|
|
282
|
+
res.AccessKeyMetadata.map((k) =>
|
|
283
|
+
getClient().send(
|
|
284
|
+
new DeleteAccessKeyCommand({
|
|
285
|
+
UserName,
|
|
286
|
+
AccessKeyId: k.AccessKeyId,
|
|
287
|
+
}),
|
|
288
|
+
),
|
|
289
|
+
),
|
|
290
|
+
),
|
|
245
291
|
);
|
|
246
292
|
}
|
|
247
293
|
|
|
@@ -258,13 +304,52 @@ module.exports.createAccessKeySafe = async (UserName) => {
|
|
|
258
304
|
return res;
|
|
259
305
|
};
|
|
260
306
|
|
|
261
|
-
module.exports.
|
|
262
|
-
|
|
307
|
+
module.exports.rotateAccessKey = async (UserName) => {
|
|
308
|
+
let res = await module.exports.listAccessKeys(UserName);
|
|
309
|
+
let keys = res.AccessKeyMetadata;
|
|
310
|
+
|
|
311
|
+
if (keys.length === 2) {
|
|
312
|
+
console.log(
|
|
313
|
+
`\n⚠ ${UserName} has 2 existing keys (likely from a failed previous rotation). Removing the older key to make room.\n`,
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
keys.sort((a, b) => new Date(a.CreateDate) - new Date(b.CreateDate));
|
|
317
|
+
|
|
318
|
+
await module.exports.removeAccessKey(UserName, keys[0].AccessKeyId);
|
|
319
|
+
|
|
320
|
+
keys = [keys[1]];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let newKey = await module.exports.createAccessKey(UserName);
|
|
324
|
+
|
|
325
|
+
if (keys.length === 1) {
|
|
326
|
+
await module.exports.removeAccessKey(UserName, keys[0].AccessKeyId);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return newKey;
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
module.exports.rotateFWIAMUser = async (UserName, permissions) => {
|
|
333
|
+
await module.exports.createIAMUser(UserName);
|
|
334
|
+
|
|
335
|
+
let preserve = [];
|
|
336
|
+
|
|
337
|
+
if (permissions && permissions.includes("manage-users")) {
|
|
338
|
+
preserve.push("arn:aws:iam::aws:policy/IAMFullAccess");
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
await module.exports.syncFWIAMPolicies(UserName, permissions, preserve);
|
|
342
|
+
|
|
343
|
+
let res = await module.exports.rotateAccessKey(UserName);
|
|
263
344
|
|
|
345
|
+
return res;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
module.exports.getRole = async (RoleName) => {
|
|
264
349
|
let res = await Spinner.prototype.simple(
|
|
265
350
|
`Retrieving the role ${RoleName}`,
|
|
266
351
|
() => {
|
|
267
|
-
return
|
|
352
|
+
return getClient().send(new GetRoleCommand({ RoleName }));
|
|
268
353
|
},
|
|
269
354
|
);
|
|
270
355
|
|
|
@@ -272,12 +357,10 @@ module.exports.getRole = async (RoleName) => {
|
|
|
272
357
|
};
|
|
273
358
|
|
|
274
359
|
module.exports.createRole = async (RoleName, AssumeRolePolicyDocument) => {
|
|
275
|
-
const client = new IAMClient({});
|
|
276
|
-
|
|
277
360
|
let res = await Spinner.prototype.simple(
|
|
278
361
|
`Creating the role ${RoleName}`,
|
|
279
362
|
() => {
|
|
280
|
-
return
|
|
363
|
+
return getClient().send(
|
|
281
364
|
new CreateRoleCommand({ RoleName, AssumeRolePolicyDocument }),
|
|
282
365
|
);
|
|
283
366
|
},
|
|
@@ -287,12 +370,10 @@ module.exports.createRole = async (RoleName, AssumeRolePolicyDocument) => {
|
|
|
287
370
|
};
|
|
288
371
|
|
|
289
372
|
module.exports.createInstanceProfile = async (InstanceProfileName) => {
|
|
290
|
-
const client = new IAMClient({});
|
|
291
|
-
|
|
292
373
|
let res = await Spinner.prototype.simple(
|
|
293
374
|
`Creating the instance profile ${InstanceProfileName}`,
|
|
294
375
|
() => {
|
|
295
|
-
return
|
|
376
|
+
return getClient().send(
|
|
296
377
|
new CreateInstanceProfileCommand({ InstanceProfileName }),
|
|
297
378
|
);
|
|
298
379
|
},
|
|
@@ -305,12 +386,10 @@ module.exports.attachRoleToInstanceProfile = async (
|
|
|
305
386
|
RoleName,
|
|
306
387
|
InstanceProfileName,
|
|
307
388
|
) => {
|
|
308
|
-
const client = new IAMClient({});
|
|
309
|
-
|
|
310
389
|
let res = await Spinner.prototype.simple(
|
|
311
390
|
`Attaching role ${RoleName} to the instance profile ${InstanceProfileName}`,
|
|
312
391
|
() => {
|
|
313
|
-
return
|
|
392
|
+
return getClient().send(
|
|
314
393
|
new AddRoleToInstanceProfileCommand({
|
|
315
394
|
RoleName,
|
|
316
395
|
InstanceProfileName,
|
|
@@ -323,12 +402,10 @@ module.exports.attachRoleToInstanceProfile = async (
|
|
|
323
402
|
};
|
|
324
403
|
|
|
325
404
|
module.exports.attachRolePolicy = async (RoleName, PolicyArn) => {
|
|
326
|
-
const client = new IAMClient({});
|
|
327
|
-
|
|
328
405
|
let res = await Spinner.prototype.simple(
|
|
329
406
|
`Attaching Role policy ${PolicyArn}`,
|
|
330
407
|
() => {
|
|
331
|
-
return
|
|
408
|
+
return getClient().send(
|
|
332
409
|
new AttachRolePolicyCommand({ RoleName, PolicyArn }),
|
|
333
410
|
);
|
|
334
411
|
},
|
|
@@ -342,12 +419,10 @@ module.exports.attachInlineRolePolicy = async (
|
|
|
342
419
|
PolicyName,
|
|
343
420
|
PolicyDocument,
|
|
344
421
|
) => {
|
|
345
|
-
const client = new IAMClient({});
|
|
346
|
-
|
|
347
422
|
let res = await Spinner.prototype.simple(
|
|
348
423
|
`Attaching Inline Role policy ${PolicyName}`,
|
|
349
424
|
() => {
|
|
350
|
-
return
|
|
425
|
+
return getClient().send(
|
|
351
426
|
new PutRolePolicyCommand({
|
|
352
427
|
RoleName,
|
|
353
428
|
PolicyName,
|
|
@@ -402,6 +477,10 @@ module.exports.ensureEBInstanceProfileExists = async () => {
|
|
|
402
477
|
role,
|
|
403
478
|
"arn:aws:iam::aws:policy/AWSSecretsManagerClientReadOnlyAccess",
|
|
404
479
|
);
|
|
480
|
+
await module.exports.attachRolePolicy(
|
|
481
|
+
role,
|
|
482
|
+
"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
|
|
483
|
+
);
|
|
405
484
|
await module.exports.attachInlineRolePolicy(
|
|
406
485
|
role,
|
|
407
486
|
"lab-env-elasticbeanstalk-describe-env",
|
|
@@ -504,3 +583,244 @@ module.exports.ensureEBManagedUpdateProfileExists = async () => {
|
|
|
504
583
|
}),
|
|
505
584
|
);
|
|
506
585
|
};
|
|
586
|
+
|
|
587
|
+
module.exports.ensureEnvironmentInstanceProfile = async (
|
|
588
|
+
ebSlug,
|
|
589
|
+
s3BucketName,
|
|
590
|
+
opensearchDomainArn,
|
|
591
|
+
) => {
|
|
592
|
+
const role = `${ebSlug}-role`;
|
|
593
|
+
|
|
594
|
+
try {
|
|
595
|
+
await module.exports.getRole(role);
|
|
596
|
+
} catch {
|
|
597
|
+
await module.exports.createRole(
|
|
598
|
+
role,
|
|
599
|
+
JSON.stringify({
|
|
600
|
+
Version: "2012-10-17",
|
|
601
|
+
Statement: [
|
|
602
|
+
{
|
|
603
|
+
Effect: "Allow",
|
|
604
|
+
Action: ["sts:AssumeRole"],
|
|
605
|
+
Principal: {
|
|
606
|
+
Service: ["ec2.amazonaws.com"],
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
],
|
|
610
|
+
}),
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
await module.exports.createInstanceProfile(role);
|
|
614
|
+
await module.exports.attachRoleToInstanceProfile(role, role);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
await module.exports.attachRolePolicy(
|
|
618
|
+
role,
|
|
619
|
+
"arn:aws:iam::aws:policy/AWSElasticBeanstalkWebTier",
|
|
620
|
+
);
|
|
621
|
+
await module.exports.attachRolePolicy(
|
|
622
|
+
role,
|
|
623
|
+
"arn:aws:iam::aws:policy/AWSElasticBeanstalkWorkerTier",
|
|
624
|
+
);
|
|
625
|
+
await module.exports.attachRolePolicy(
|
|
626
|
+
role,
|
|
627
|
+
"arn:aws:iam::aws:policy/AWSElasticBeanstalkMulticontainerDocker",
|
|
628
|
+
);
|
|
629
|
+
await module.exports.attachRolePolicy(
|
|
630
|
+
role,
|
|
631
|
+
"arn:aws:iam::aws:policy/AWSSecretsManagerClientReadOnlyAccess",
|
|
632
|
+
);
|
|
633
|
+
await module.exports.attachRolePolicy(
|
|
634
|
+
role,
|
|
635
|
+
"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
|
|
636
|
+
);
|
|
637
|
+
await module.exports.attachInlineRolePolicy(
|
|
638
|
+
role,
|
|
639
|
+
"lab-env-elasticbeanstalk-describe-env",
|
|
640
|
+
JSON.stringify({
|
|
641
|
+
Version: "2012-10-17",
|
|
642
|
+
Statement: [
|
|
643
|
+
{
|
|
644
|
+
Sid: "VisualEditor0",
|
|
645
|
+
Effect: "Allow",
|
|
646
|
+
Action: [
|
|
647
|
+
"ec2:DescribeTags",
|
|
648
|
+
"autoscaling:DescribeAutoScalingGroups",
|
|
649
|
+
"elasticbeanstalk:DescribeEvents",
|
|
650
|
+
],
|
|
651
|
+
Resource: "*",
|
|
652
|
+
},
|
|
653
|
+
],
|
|
654
|
+
}),
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
if (s3BucketName) {
|
|
658
|
+
await module.exports.attachInlineRolePolicy(
|
|
659
|
+
role,
|
|
660
|
+
"lab-env-s3-bucket-access",
|
|
661
|
+
JSON.stringify({
|
|
662
|
+
Version: "2012-10-17",
|
|
663
|
+
Statement: [
|
|
664
|
+
{
|
|
665
|
+
Sid: "S3BucketAccess",
|
|
666
|
+
Effect: "Allow",
|
|
667
|
+
Action: [
|
|
668
|
+
"s3:GetObject",
|
|
669
|
+
"s3:PutObject",
|
|
670
|
+
"s3:DeleteObject",
|
|
671
|
+
],
|
|
672
|
+
Resource: `arn:aws:s3:::${s3BucketName}/*`,
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
Sid: "S3BucketList",
|
|
676
|
+
Effect: "Allow",
|
|
677
|
+
Action: ["s3:ListBucket", "s3:GetBucketLocation"],
|
|
678
|
+
Resource: `arn:aws:s3:::${s3BucketName}`,
|
|
679
|
+
},
|
|
680
|
+
],
|
|
681
|
+
}),
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (opensearchDomainArn) {
|
|
686
|
+
await module.exports.attachInlineRolePolicy(
|
|
687
|
+
role,
|
|
688
|
+
"lab-env-opensearch-access",
|
|
689
|
+
JSON.stringify({
|
|
690
|
+
Version: "2012-10-17",
|
|
691
|
+
Statement: [
|
|
692
|
+
{
|
|
693
|
+
Sid: "OpenSearchAccess",
|
|
694
|
+
Effect: "Allow",
|
|
695
|
+
Action: [
|
|
696
|
+
"es:ESHttpGet",
|
|
697
|
+
"es:ESHttpPost",
|
|
698
|
+
"es:ESHttpPut",
|
|
699
|
+
"es:ESHttpDelete",
|
|
700
|
+
"es:ESHttpHead",
|
|
701
|
+
"es:ESHttpPatch",
|
|
702
|
+
],
|
|
703
|
+
Resource: `${opensearchDomainArn}/*`,
|
|
704
|
+
},
|
|
705
|
+
],
|
|
706
|
+
}),
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
return role;
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
module.exports.ensureOpenSearchServiceRole = async () => {
|
|
714
|
+
try {
|
|
715
|
+
await Spinner.prototype.simple(
|
|
716
|
+
"Ensuring OpenSearch service-linked role exists",
|
|
717
|
+
() => {
|
|
718
|
+
return getClient().send(
|
|
719
|
+
new CreateServiceLinkedRoleCommand({
|
|
720
|
+
AWSServiceName: "opensearchservice.amazonaws.com",
|
|
721
|
+
}),
|
|
722
|
+
);
|
|
723
|
+
},
|
|
724
|
+
);
|
|
725
|
+
} catch (e) {
|
|
726
|
+
if (
|
|
727
|
+
e.Code !== "InvalidInput" &&
|
|
728
|
+
e.name !== "InvalidInputException" &&
|
|
729
|
+
e.name !== "InvalidInput"
|
|
730
|
+
) {
|
|
731
|
+
throw e;
|
|
732
|
+
}
|
|
733
|
+
// Role already exists — safe to continue
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
module.exports.removeEnvironmentInstanceProfile = async (ebSlug) => {
|
|
738
|
+
const role = `${ebSlug}-role`;
|
|
739
|
+
|
|
740
|
+
try {
|
|
741
|
+
await module.exports.getRole(role);
|
|
742
|
+
} catch {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const attachedPolicies = await Spinner.prototype.simple(
|
|
747
|
+
`Listing attached policies for ${role}`,
|
|
748
|
+
() => {
|
|
749
|
+
return getClient().send(
|
|
750
|
+
new ListAttachedRolePoliciesCommand({ RoleName: role }),
|
|
751
|
+
);
|
|
752
|
+
},
|
|
753
|
+
);
|
|
754
|
+
|
|
755
|
+
for (const policy of attachedPolicies.AttachedPolicies || []) {
|
|
756
|
+
await Spinner.prototype.simple(
|
|
757
|
+
`Detaching policy ${policy.PolicyName}`,
|
|
758
|
+
() => {
|
|
759
|
+
return getClient().send(
|
|
760
|
+
new DetachRolePolicyCommand({
|
|
761
|
+
RoleName: role,
|
|
762
|
+
PolicyArn: policy.PolicyArn,
|
|
763
|
+
}),
|
|
764
|
+
);
|
|
765
|
+
},
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const inlinePolicies = await Spinner.prototype.simple(
|
|
770
|
+
`Listing inline policies for ${role}`,
|
|
771
|
+
() => {
|
|
772
|
+
return getClient().send(
|
|
773
|
+
new ListRolePoliciesCommand({ RoleName: role }),
|
|
774
|
+
);
|
|
775
|
+
},
|
|
776
|
+
);
|
|
777
|
+
|
|
778
|
+
for (const policyName of inlinePolicies.PolicyNames || []) {
|
|
779
|
+
await Spinner.prototype.simple(
|
|
780
|
+
`Deleting inline policy ${policyName}`,
|
|
781
|
+
() => {
|
|
782
|
+
return getClient().send(
|
|
783
|
+
new DeleteRolePolicyCommand({
|
|
784
|
+
RoleName: role,
|
|
785
|
+
PolicyName: policyName,
|
|
786
|
+
}),
|
|
787
|
+
);
|
|
788
|
+
},
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
try {
|
|
793
|
+
await Spinner.prototype.simple(
|
|
794
|
+
`Removing role from instance profile ${role}`,
|
|
795
|
+
() => {
|
|
796
|
+
return getClient().send(
|
|
797
|
+
new RemoveRoleFromInstanceProfileCommand({
|
|
798
|
+
RoleName: role,
|
|
799
|
+
InstanceProfileName: role,
|
|
800
|
+
}),
|
|
801
|
+
);
|
|
802
|
+
},
|
|
803
|
+
);
|
|
804
|
+
} catch {
|
|
805
|
+
/* empty */
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
try {
|
|
809
|
+
await Spinner.prototype.simple(
|
|
810
|
+
`Deleting instance profile ${role}`,
|
|
811
|
+
() => {
|
|
812
|
+
return getClient().send(
|
|
813
|
+
new DeleteInstanceProfileCommand({
|
|
814
|
+
InstanceProfileName: role,
|
|
815
|
+
}),
|
|
816
|
+
);
|
|
817
|
+
},
|
|
818
|
+
);
|
|
819
|
+
} catch {
|
|
820
|
+
/* empty */
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
await Spinner.prototype.simple(`Deleting role ${role}`, () => {
|
|
824
|
+
return getClient().send(new DeleteRoleCommand({ RoleName: role }));
|
|
825
|
+
});
|
|
826
|
+
};
|