@fishawack/lab-env 5.7.0-beta.2 → 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.
Files changed (36) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/_Test/provision.js +1 -8
  3. package/_Test/prune.js +8 -5
  4. package/bitbucket-pipelines.yml +2 -2
  5. package/cli.js +1 -0
  6. package/commands/create/cmds/dekey.js +13 -9
  7. package/commands/create/cmds/deprovision.js +29 -0
  8. package/commands/create/cmds/key.js +60 -41
  9. package/commands/create/cmds/provision.js +425 -55
  10. package/commands/create/cmds/rekey.js +218 -0
  11. package/commands/create/libs/output-credentials.js +59 -0
  12. package/commands/create/libs/parallel-runner.js +204 -0
  13. package/commands/create/libs/resolve-operator.js +59 -0
  14. package/commands/create/libs/utilities.js +71 -8
  15. package/commands/create/libs/vars.js +70 -86
  16. package/commands/create/services/aws/acm.js +4 -2
  17. package/commands/create/services/aws/cloudfront.js +51 -45
  18. package/commands/create/services/aws/ec2.js +132 -53
  19. package/commands/create/services/aws/elasticache.js +159 -0
  20. package/commands/create/services/aws/elasticbeanstalk.js +141 -60
  21. package/commands/create/services/aws/iam.js +159 -93
  22. package/commands/create/services/aws/index.js +1209 -466
  23. package/commands/create/services/aws/opensearch.js +25 -17
  24. package/commands/create/services/aws/rds.js +22 -34
  25. package/commands/create/services/aws/s3.js +14 -27
  26. package/commands/create/services/aws/secretsmanager.js +8 -15
  27. package/commands/create/services/aws/ses.js +195 -0
  28. package/commands/create/templates/elasticbeanstalk/.ebextensions/laravel/cron.config +45 -0
  29. package/commands/create/templates/elasticbeanstalk/.ebextensions/laravel/queue-worker.config +29 -0
  30. package/commands/create/templates/elasticbeanstalk/.ebextensions/laravel/software.config +12 -0
  31. package/commands/create/templates/elasticbeanstalk/.ebextensions/misc/setvars.config +2 -16
  32. package/commands/create/templates/elasticbeanstalk/.platform/confighooks/postdeploy/rewrite-flush.sh +1 -1
  33. package/commands/create/templates/elasticbeanstalk/.platform/confighooks/postdeploy/setvars.sh +19 -0
  34. package/commands/create/templates/elasticbeanstalk/.platform/hooks/postdeploy/restart_queue.sh +4 -0
  35. package/commands/create/templates/elasticbeanstalk/.platform/hooks/prebuild/setvars.sh +19 -0
  36. package/package.json +5 -2
@@ -9,6 +9,7 @@ const {
9
9
  CreateAccessKeyCommand,
10
10
  DeleteAccessKeyCommand,
11
11
  ListAccessKeysCommand,
12
+ AddUserToGroupCommand,
12
13
  GetRoleCommand,
13
14
  CreateRoleCommand,
14
15
  DeleteRoleCommand,
@@ -25,17 +26,18 @@ const {
25
26
  CreateServiceLinkedRoleCommand,
26
27
  } = require("@aws-sdk/client-iam");
27
28
  const { Spinner } = require("../../libs/utilities");
29
+ const { awsClientConfig } = require("../../libs/vars");
28
30
 
29
- module.exports.createIAMUser = async (UserName, tags = []) => {
30
- const client = new IAMClient({});
31
+ const getClient = () => new IAMClient(awsClientConfig);
31
32
 
33
+ module.exports.createIAMUser = async (UserName, tags = []) => {
32
34
  let res;
33
35
 
34
36
  try {
35
37
  res = await Spinner.prototype.simple(
36
38
  `Creating IAM user ${UserName}`,
37
39
  () => {
38
- return client.send(
40
+ return getClient().send(
39
41
  new CreateUserCommand({
40
42
  UserName,
41
43
  Tags: [
@@ -49,7 +51,7 @@ module.exports.createIAMUser = async (UserName, tags = []) => {
49
51
  res = await Spinner.prototype.simple(
50
52
  `Retrieving the already existing IAM user ${UserName}`,
51
53
  () => {
52
- return client.send(new GetUserCommand({ UserName }));
54
+ return getClient().send(new GetUserCommand({ UserName }));
53
55
  },
54
56
  );
55
57
  }
@@ -67,16 +69,25 @@ module.exports.createFWIAMUser = async (UserName, permissions) => {
67
69
  return res;
68
70
  };
69
71
 
70
- module.exports.removeIAMUser = async (UserName) => {
71
- const client = new IAMClient({});
72
+ module.exports.addUserToGroup = async (UserName, GroupName, iamClient) => {
73
+ const c = iamClient || getClient();
72
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
+ };
82
+
83
+ module.exports.removeIAMUser = async (UserName) => {
73
84
  let res;
74
85
 
75
86
  try {
76
87
  await Spinner.prototype.simple(
77
88
  `Checking if IAM user ${UserName} exists`,
78
89
  () => {
79
- return client.send(new GetUserCommand({ UserName }));
90
+ return getClient().send(new GetUserCommand({ UserName }));
80
91
  },
81
92
  );
82
93
 
@@ -87,7 +98,7 @@ module.exports.removeIAMUser = async (UserName) => {
87
98
  res = await Spinner.prototype.simple(
88
99
  `Removing IAM user ${UserName}`,
89
100
  () => {
90
- return client.send(new DeleteUserCommand({ UserName }));
101
+ return getClient().send(new DeleteUserCommand({ UserName }));
91
102
  },
92
103
  );
93
104
  } catch {
@@ -98,12 +109,10 @@ module.exports.removeIAMUser = async (UserName) => {
98
109
  };
99
110
 
100
111
  module.exports.attachIAMPolicy = async (UserName, policy) => {
101
- const client = new IAMClient({});
102
-
103
112
  let res = await Spinner.prototype.simple(
104
113
  `Attaching IAM policy ${policy}`,
105
114
  () => {
106
- return client.send(
115
+ return getClient().send(
107
116
  new AttachUserPolicyCommand({ UserName, PolicyArn: policy }),
108
117
  );
109
118
  },
@@ -115,68 +124,83 @@ module.exports.attachIAMPolicy = async (UserName, policy) => {
115
124
  module.exports.syncFWIAMPolicies = async (
116
125
  UserName,
117
126
  permissions = ["auto-content", "deploy-static"],
127
+ preserve = [],
118
128
  ) => {
119
- await module.exports.removeAllIAMPolicies(UserName);
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 = [];
120
154
 
121
155
  if (
122
156
  permissions.includes("auto-content") ||
123
157
  permissions.includes("deploy-static")
124
158
  ) {
125
- await module.exports.attachIAMPolicy(
126
- UserName,
127
- "arn:aws:iam::aws:policy/AmazonS3FullAccess",
128
- );
159
+ policies.push("arn:aws:iam::aws:policy/AmazonS3FullAccess");
129
160
  }
130
161
 
131
162
  if (permissions.includes("deploy-static")) {
132
- await module.exports.attachIAMPolicy(
133
- UserName,
134
- "arn:aws:iam::aws:policy/CloudFrontFullAccess",
135
- );
163
+ policies.push("arn:aws:iam::aws:policy/CloudFrontFullAccess");
136
164
  }
137
165
 
138
166
  if (permissions.includes("deploy-fullstack")) {
139
- await module.exports.attachIAMPolicy(
140
- UserName,
167
+ policies.push(
141
168
  "arn:aws:iam::aws:policy/AdministratorAccess-AWSElasticBeanstalk",
142
- );
143
-
144
- await module.exports.attachIAMPolicy(
145
- UserName,
146
169
  "arn:aws:iam::aws:policy/AmazonOpenSearchServiceFullAccess",
147
- );
148
-
149
- await module.exports.attachIAMPolicy(
150
- UserName,
151
170
  "arn:aws:iam::aws:policy/AmazonRDSFullAccess",
152
- );
153
-
154
- await module.exports.attachIAMPolicy(
155
- UserName,
156
171
  "arn:aws:iam::aws:policy/AmazonElastiCacheFullAccess",
157
- );
158
-
159
- await module.exports.attachIAMPolicy(
160
- UserName,
161
172
  "arn:aws:iam::aws:policy/SecretsManagerReadWrite",
162
173
  );
163
174
  }
164
175
 
165
176
  if (permissions.includes("manage-users")) {
166
- await module.exports.attachIAMPolicy(
167
- UserName,
168
- "arn:aws:iam::aws:policy/IAMFullAccess",
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
+ ),
169
195
  );
170
196
  }
171
197
  };
172
198
 
173
199
  module.exports.removeIAMPolicy = async (UserName, policy) => {
174
- const client = new IAMClient({});
175
-
176
200
  let res = await Spinner.prototype.simple(
177
201
  `Detaching IAM policy ${policy}`,
178
202
  () => {
179
- return client.send(
203
+ return getClient().send(
180
204
  new DetachUserPolicyCommand({ UserName, PolicyArn: policy }),
181
205
  );
182
206
  },
@@ -186,10 +210,10 @@ module.exports.removeIAMPolicy = async (UserName, policy) => {
186
210
  };
187
211
 
188
212
  module.exports.listIAMPolicies = async (UserName) => {
189
- const client = new IAMClient({});
190
-
191
213
  let res = await Spinner.prototype.simple(`Listing IAM policies`, () => {
192
- return client.send(new ListAttachedUserPoliciesCommand({ UserName }));
214
+ return getClient().send(
215
+ new ListAttachedUserPoliciesCommand({ UserName }),
216
+ );
193
217
  });
194
218
 
195
219
  return res;
@@ -198,10 +222,20 @@ module.exports.listIAMPolicies = async (UserName) => {
198
222
  module.exports.removeAllIAMPolicies = async (UserName) => {
199
223
  let res = await module.exports.listIAMPolicies(UserName);
200
224
 
201
- for (let i = 0; i < res.AttachedPolicies.length; i++) {
202
- await module.exports.removeIAMPolicy(
203
- UserName,
204
- res.AttachedPolicies[i].PolicyArn,
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
+ ),
205
239
  );
206
240
  }
207
241
 
@@ -209,22 +243,18 @@ module.exports.removeAllIAMPolicies = async (UserName) => {
209
243
  };
210
244
 
211
245
  module.exports.createAccessKey = async (UserName) => {
212
- const client = new IAMClient({});
213
-
214
246
  let res = await Spinner.prototype.simple(`Creating access key`, () => {
215
- return client.send(new CreateAccessKeyCommand({ UserName }));
247
+ return getClient().send(new CreateAccessKeyCommand({ UserName }));
216
248
  });
217
249
 
218
250
  return res;
219
251
  };
220
252
 
221
253
  module.exports.removeAccessKey = async (UserName, AccessKeyId) => {
222
- const client = new IAMClient({});
223
-
224
254
  let res = await Spinner.prototype.simple(
225
255
  `Removing access key ${AccessKeyId}`,
226
256
  () => {
227
- return client.send(
257
+ return getClient().send(
228
258
  new DeleteAccessKeyCommand({ AccessKeyId, UserName }),
229
259
  );
230
260
  },
@@ -234,10 +264,8 @@ module.exports.removeAccessKey = async (UserName, AccessKeyId) => {
234
264
  };
235
265
 
236
266
  module.exports.listAccessKeys = async (UserName) => {
237
- const client = new IAMClient({});
238
-
239
267
  let res = await Spinner.prototype.simple(`Listing access keys`, () => {
240
- return client.send(new ListAccessKeysCommand({ UserName }));
268
+ return getClient().send(new ListAccessKeysCommand({ UserName }));
241
269
  });
242
270
 
243
271
  return res;
@@ -246,10 +274,20 @@ module.exports.listAccessKeys = async (UserName) => {
246
274
  module.exports.removeAllAccessKeys = async (UserName) => {
247
275
  let res = await module.exports.listAccessKeys(UserName);
248
276
 
249
- for (let i = 0; i < res.AccessKeyMetadata.length; i++) {
250
- await module.exports.removeAccessKey(
251
- UserName,
252
- res.AccessKeyMetadata[i].AccessKeyId,
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
+ ),
253
291
  );
254
292
  }
255
293
 
@@ -266,13 +304,52 @@ module.exports.createAccessKeySafe = async (UserName) => {
266
304
  return res;
267
305
  };
268
306
 
269
- module.exports.getRole = async (RoleName) => {
270
- const client = new IAMClient({});
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);
344
+
345
+ return res;
346
+ };
271
347
 
348
+ module.exports.getRole = async (RoleName) => {
272
349
  let res = await Spinner.prototype.simple(
273
350
  `Retrieving the role ${RoleName}`,
274
351
  () => {
275
- return client.send(new GetRoleCommand({ RoleName }));
352
+ return getClient().send(new GetRoleCommand({ RoleName }));
276
353
  },
277
354
  );
278
355
 
@@ -280,12 +357,10 @@ module.exports.getRole = async (RoleName) => {
280
357
  };
281
358
 
282
359
  module.exports.createRole = async (RoleName, AssumeRolePolicyDocument) => {
283
- const client = new IAMClient({});
284
-
285
360
  let res = await Spinner.prototype.simple(
286
361
  `Creating the role ${RoleName}`,
287
362
  () => {
288
- return client.send(
363
+ return getClient().send(
289
364
  new CreateRoleCommand({ RoleName, AssumeRolePolicyDocument }),
290
365
  );
291
366
  },
@@ -295,12 +370,10 @@ module.exports.createRole = async (RoleName, AssumeRolePolicyDocument) => {
295
370
  };
296
371
 
297
372
  module.exports.createInstanceProfile = async (InstanceProfileName) => {
298
- const client = new IAMClient({});
299
-
300
373
  let res = await Spinner.prototype.simple(
301
374
  `Creating the instance profile ${InstanceProfileName}`,
302
375
  () => {
303
- return client.send(
376
+ return getClient().send(
304
377
  new CreateInstanceProfileCommand({ InstanceProfileName }),
305
378
  );
306
379
  },
@@ -313,12 +386,10 @@ module.exports.attachRoleToInstanceProfile = async (
313
386
  RoleName,
314
387
  InstanceProfileName,
315
388
  ) => {
316
- const client = new IAMClient({});
317
-
318
389
  let res = await Spinner.prototype.simple(
319
390
  `Attaching role ${RoleName} to the instance profile ${InstanceProfileName}`,
320
391
  () => {
321
- return client.send(
392
+ return getClient().send(
322
393
  new AddRoleToInstanceProfileCommand({
323
394
  RoleName,
324
395
  InstanceProfileName,
@@ -331,12 +402,10 @@ module.exports.attachRoleToInstanceProfile = async (
331
402
  };
332
403
 
333
404
  module.exports.attachRolePolicy = async (RoleName, PolicyArn) => {
334
- const client = new IAMClient({});
335
-
336
405
  let res = await Spinner.prototype.simple(
337
406
  `Attaching Role policy ${PolicyArn}`,
338
407
  () => {
339
- return client.send(
408
+ return getClient().send(
340
409
  new AttachRolePolicyCommand({ RoleName, PolicyArn }),
341
410
  );
342
411
  },
@@ -350,12 +419,10 @@ module.exports.attachInlineRolePolicy = async (
350
419
  PolicyName,
351
420
  PolicyDocument,
352
421
  ) => {
353
- const client = new IAMClient({});
354
-
355
422
  let res = await Spinner.prototype.simple(
356
423
  `Attaching Inline Role policy ${PolicyName}`,
357
424
  () => {
358
- return client.send(
425
+ return getClient().send(
359
426
  new PutRolePolicyCommand({
360
427
  RoleName,
361
428
  PolicyName,
@@ -644,13 +711,11 @@ module.exports.ensureEnvironmentInstanceProfile = async (
644
711
  };
645
712
 
646
713
  module.exports.ensureOpenSearchServiceRole = async () => {
647
- const client = new IAMClient({});
648
-
649
714
  try {
650
715
  await Spinner.prototype.simple(
651
716
  "Ensuring OpenSearch service-linked role exists",
652
717
  () => {
653
- return client.send(
718
+ return getClient().send(
654
719
  new CreateServiceLinkedRoleCommand({
655
720
  AWSServiceName: "opensearchservice.amazonaws.com",
656
721
  }),
@@ -670,7 +735,6 @@ module.exports.ensureOpenSearchServiceRole = async () => {
670
735
  };
671
736
 
672
737
  module.exports.removeEnvironmentInstanceProfile = async (ebSlug) => {
673
- const client = new IAMClient({});
674
738
  const role = `${ebSlug}-role`;
675
739
 
676
740
  try {
@@ -682,7 +746,7 @@ module.exports.removeEnvironmentInstanceProfile = async (ebSlug) => {
682
746
  const attachedPolicies = await Spinner.prototype.simple(
683
747
  `Listing attached policies for ${role}`,
684
748
  () => {
685
- return client.send(
749
+ return getClient().send(
686
750
  new ListAttachedRolePoliciesCommand({ RoleName: role }),
687
751
  );
688
752
  },
@@ -692,7 +756,7 @@ module.exports.removeEnvironmentInstanceProfile = async (ebSlug) => {
692
756
  await Spinner.prototype.simple(
693
757
  `Detaching policy ${policy.PolicyName}`,
694
758
  () => {
695
- return client.send(
759
+ return getClient().send(
696
760
  new DetachRolePolicyCommand({
697
761
  RoleName: role,
698
762
  PolicyArn: policy.PolicyArn,
@@ -705,7 +769,9 @@ module.exports.removeEnvironmentInstanceProfile = async (ebSlug) => {
705
769
  const inlinePolicies = await Spinner.prototype.simple(
706
770
  `Listing inline policies for ${role}`,
707
771
  () => {
708
- return client.send(new ListRolePoliciesCommand({ RoleName: role }));
772
+ return getClient().send(
773
+ new ListRolePoliciesCommand({ RoleName: role }),
774
+ );
709
775
  },
710
776
  );
711
777
 
@@ -713,7 +779,7 @@ module.exports.removeEnvironmentInstanceProfile = async (ebSlug) => {
713
779
  await Spinner.prototype.simple(
714
780
  `Deleting inline policy ${policyName}`,
715
781
  () => {
716
- return client.send(
782
+ return getClient().send(
717
783
  new DeleteRolePolicyCommand({
718
784
  RoleName: role,
719
785
  PolicyName: policyName,
@@ -727,7 +793,7 @@ module.exports.removeEnvironmentInstanceProfile = async (ebSlug) => {
727
793
  await Spinner.prototype.simple(
728
794
  `Removing role from instance profile ${role}`,
729
795
  () => {
730
- return client.send(
796
+ return getClient().send(
731
797
  new RemoveRoleFromInstanceProfileCommand({
732
798
  RoleName: role,
733
799
  InstanceProfileName: role,
@@ -743,7 +809,7 @@ module.exports.removeEnvironmentInstanceProfile = async (ebSlug) => {
743
809
  await Spinner.prototype.simple(
744
810
  `Deleting instance profile ${role}`,
745
811
  () => {
746
- return client.send(
812
+ return getClient().send(
747
813
  new DeleteInstanceProfileCommand({
748
814
  InstanceProfileName: role,
749
815
  }),
@@ -755,6 +821,6 @@ module.exports.removeEnvironmentInstanceProfile = async (ebSlug) => {
755
821
  }
756
822
 
757
823
  await Spinner.prototype.simple(`Deleting role ${role}`, () => {
758
- return client.send(new DeleteRoleCommand({ RoleName: role }));
824
+ return getClient().send(new DeleteRoleCommand({ RoleName: role }));
759
825
  });
760
826
  };