@friggframework/devtools 2.0.0--canary.406.78e2685.0 → 2.0.0--canary.398.dd443c7.0

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 (34) hide show
  1. package/frigg-cli/build-command/index.js +4 -2
  2. package/frigg-cli/deploy-command/index.js +5 -2
  3. package/frigg-cli/generate-iam-command.js +115 -0
  4. package/frigg-cli/index.js +11 -1
  5. package/infrastructure/AWS-DISCOVERY-TROUBLESHOOTING.md +245 -0
  6. package/infrastructure/AWS-IAM-CREDENTIAL-NEEDS.md +596 -0
  7. package/infrastructure/DEPLOYMENT-INSTRUCTIONS.md +268 -0
  8. package/infrastructure/GENERATE-IAM-DOCS.md +253 -0
  9. package/infrastructure/IAM-POLICY-TEMPLATES.md +176 -0
  10. package/infrastructure/README-TESTING.md +332 -0
  11. package/infrastructure/README.md +421 -0
  12. package/infrastructure/WEBSOCKET-CONFIGURATION.md +105 -0
  13. package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
  14. package/infrastructure/__tests__/helpers/test-utils.js +277 -0
  15. package/infrastructure/aws-discovery.js +568 -0
  16. package/infrastructure/aws-discovery.test.js +373 -0
  17. package/infrastructure/build-time-discovery.js +206 -0
  18. package/infrastructure/build-time-discovery.test.js +375 -0
  19. package/infrastructure/create-frigg-infrastructure.js +2 -2
  20. package/infrastructure/frigg-deployment-iam-stack.yaml +379 -0
  21. package/infrastructure/iam-generator.js +687 -0
  22. package/infrastructure/iam-generator.test.js +169 -0
  23. package/infrastructure/iam-policy-basic.json +212 -0
  24. package/infrastructure/iam-policy-full.json +282 -0
  25. package/infrastructure/integration.test.js +383 -0
  26. package/infrastructure/run-discovery.js +110 -0
  27. package/infrastructure/serverless-template.js +514 -167
  28. package/infrastructure/serverless-template.test.js +541 -0
  29. package/management-ui/dist/assets/FriggLogo-B7Xx8ZW1.svg +1 -0
  30. package/management-ui/dist/assets/index-BA21WgFa.js +1221 -0
  31. package/management-ui/dist/assets/index-CbM64Oba.js +1221 -0
  32. package/management-ui/dist/assets/index-CkvseXTC.css +1 -0
  33. package/management-ui/dist/index.html +14 -0
  34. package/package.json +9 -5
@@ -0,0 +1,687 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Generate IAM CloudFormation template based on AppDefinition
6
+ * @param {Object} appDefinition - Application definition object
7
+ * @param {Object} options - Generation options
8
+ * @param {string} [options.deploymentUserName='frigg-deployment-user'] - IAM user name
9
+ * @param {string} [options.stackName='frigg-deployment-iam'] - CloudFormation stack name
10
+ * @param {string} [options.mode='auto'] - Policy mode: 'basic', 'full', or 'auto' (auto-detect from appDefinition)
11
+ * @returns {string} CloudFormation YAML template
12
+ */
13
+ function generateIAMCloudFormation(appDefinition, options = {}) {
14
+ const {
15
+ deploymentUserName = 'frigg-deployment-user',
16
+ stackName = 'frigg-deployment-iam',
17
+ mode = 'auto'
18
+ } = options;
19
+
20
+ // Determine which features are enabled based on mode
21
+ let features;
22
+ if (mode === 'basic') {
23
+ features = {
24
+ vpc: false,
25
+ kms: false,
26
+ ssm: false,
27
+ websockets: appDefinition.websockets?.enable === true
28
+ };
29
+ } else if (mode === 'full') {
30
+ features = {
31
+ vpc: true,
32
+ kms: true,
33
+ ssm: true,
34
+ websockets: appDefinition.websockets?.enable === true
35
+ };
36
+ } else { // mode === 'auto'
37
+ features = {
38
+ vpc: appDefinition.vpc?.enable === true,
39
+ kms: appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true,
40
+ ssm: appDefinition.ssm?.enable === true,
41
+ websockets: appDefinition.websockets?.enable === true
42
+ };
43
+ }
44
+
45
+ // Build the CloudFormation template
46
+ const template = {
47
+ AWSTemplateFormatVersion: '2010-09-09',
48
+ Description: `IAM roles and policies for ${appDefinition.name || 'Frigg'} application deployment pipeline`,
49
+
50
+ Parameters: {
51
+ DeploymentUserName: {
52
+ Type: 'String',
53
+ Default: deploymentUserName,
54
+ Description: 'Name for the IAM user that will deploy Frigg applications'
55
+ },
56
+ EnableVPCSupport: {
57
+ Type: 'String',
58
+ Default: features.vpc ? 'true' : 'false',
59
+ AllowedValues: ['true', 'false'],
60
+ Description: 'Enable VPC-related permissions for Frigg applications'
61
+ },
62
+ EnableKMSSupport: {
63
+ Type: 'String',
64
+ Default: features.kms ? 'true' : 'false',
65
+ AllowedValues: ['true', 'false'],
66
+ Description: 'Enable KMS encryption permissions for Frigg applications'
67
+ },
68
+ EnableSSMSupport: {
69
+ Type: 'String',
70
+ Default: features.ssm ? 'true' : 'false',
71
+ AllowedValues: ['true', 'false'],
72
+ Description: 'Enable SSM Parameter Store permissions for Frigg applications'
73
+ }
74
+ },
75
+
76
+ Conditions: {
77
+ CreateVPCPermissions: { 'Fn::Equals': [{ Ref: 'EnableVPCSupport' }, 'true'] },
78
+ CreateKMSPermissions: { 'Fn::Equals': [{ Ref: 'EnableKMSSupport' }, 'true'] },
79
+ CreateSSMPermissions: { 'Fn::Equals': [{ Ref: 'EnableSSMSupport' }, 'true'] }
80
+ },
81
+
82
+ Resources: {}
83
+ };
84
+
85
+ // Add IAM User
86
+ template.Resources.FriggDeploymentUser = {
87
+ Type: 'AWS::IAM::User',
88
+ Properties: {
89
+ UserName: { Ref: 'DeploymentUserName' },
90
+ ManagedPolicyArns: [
91
+ { Ref: 'FriggDiscoveryPolicy' },
92
+ { Ref: 'FriggCoreDeploymentPolicy' }
93
+ ]
94
+ }
95
+ };
96
+
97
+ // Conditionally add feature-specific policies
98
+ if (features.vpc) {
99
+ template.Resources.FriggDeploymentUser.Properties.ManagedPolicyArns.push({
100
+ 'Fn::If': ['CreateVPCPermissions', { Ref: 'FriggVPCPolicy' }, { Ref: 'AWS::NoValue' }]
101
+ });
102
+ }
103
+ if (features.kms) {
104
+ template.Resources.FriggDeploymentUser.Properties.ManagedPolicyArns.push({
105
+ 'Fn::If': ['CreateKMSPermissions', { Ref: 'FriggKMSPolicy' }, { Ref: 'AWS::NoValue' }]
106
+ });
107
+ }
108
+ if (features.ssm) {
109
+ template.Resources.FriggDeploymentUser.Properties.ManagedPolicyArns.push({
110
+ 'Fn::If': ['CreateSSMPermissions', { Ref: 'FriggSSMPolicy' }, { Ref: 'AWS::NoValue' }]
111
+ });
112
+ }
113
+
114
+ // Add Access Key
115
+ template.Resources.FriggDeploymentAccessKey = {
116
+ Type: 'AWS::IAM::AccessKey',
117
+ Properties: {
118
+ UserName: { Ref: 'FriggDeploymentUser' }
119
+ }
120
+ };
121
+
122
+ // Add Discovery Policy (always needed)
123
+ template.Resources.FriggDiscoveryPolicy = {
124
+ Type: 'AWS::IAM::ManagedPolicy',
125
+ Properties: {
126
+ ManagedPolicyName: 'FriggDiscoveryPolicy',
127
+ Description: 'Permissions for AWS resource discovery during Frigg build process',
128
+ PolicyDocument: {
129
+ Version: '2012-10-17',
130
+ Statement: [
131
+ {
132
+ Sid: 'AWSDiscoveryPermissions',
133
+ Effect: 'Allow',
134
+ Action: [
135
+ 'sts:GetCallerIdentity',
136
+ 'ec2:DescribeVpcs',
137
+ 'ec2:DescribeSubnets',
138
+ 'ec2:DescribeSecurityGroups',
139
+ 'ec2:DescribeRouteTables',
140
+ 'ec2:DescribeNatGateways',
141
+ 'ec2:DescribeAddresses',
142
+ 'kms:ListKeys',
143
+ 'kms:DescribeKey'
144
+ ],
145
+ Resource: '*'
146
+ }
147
+ ]
148
+ }
149
+ }
150
+ };
151
+
152
+ // Add Core Deployment Policy (always needed)
153
+ const coreActions = [
154
+ // CloudFormation permissions
155
+ 'cloudformation:CreateStack',
156
+ 'cloudformation:UpdateStack',
157
+ 'cloudformation:DeleteStack',
158
+ 'cloudformation:DescribeStacks',
159
+ 'cloudformation:DescribeStackEvents',
160
+ 'cloudformation:DescribeStackResources',
161
+ 'cloudformation:DescribeStackResource',
162
+ 'cloudformation:ListStackResources',
163
+ 'cloudformation:GetTemplate',
164
+ 'cloudformation:DescribeChangeSet',
165
+ 'cloudformation:CreateChangeSet',
166
+ 'cloudformation:DeleteChangeSet',
167
+ 'cloudformation:ExecuteChangeSet',
168
+ 'cloudformation:ValidateTemplate',
169
+
170
+ // Lambda permissions
171
+ 'lambda:CreateFunction',
172
+ 'lambda:UpdateFunctionCode',
173
+ 'lambda:UpdateFunctionConfiguration',
174
+ 'lambda:DeleteFunction',
175
+ 'lambda:GetFunction',
176
+ 'lambda:ListFunctions',
177
+ 'lambda:PublishVersion',
178
+ 'lambda:CreateAlias',
179
+ 'lambda:UpdateAlias',
180
+ 'lambda:DeleteAlias',
181
+ 'lambda:GetAlias',
182
+ 'lambda:AddPermission',
183
+ 'lambda:RemovePermission',
184
+ 'lambda:GetPolicy',
185
+ 'lambda:PutProvisionedConcurrencyConfig',
186
+ 'lambda:DeleteProvisionedConcurrencyConfig',
187
+ 'lambda:PutConcurrency',
188
+ 'lambda:DeleteConcurrency',
189
+ 'lambda:TagResource',
190
+ 'lambda:UntagResource',
191
+ 'lambda:ListVersionsByFunction',
192
+
193
+ // IAM permissions
194
+ 'iam:CreateRole',
195
+ 'iam:DeleteRole',
196
+ 'iam:GetRole',
197
+ 'iam:PassRole',
198
+ 'iam:PutRolePolicy',
199
+ 'iam:DeleteRolePolicy',
200
+ 'iam:GetRolePolicy',
201
+ 'iam:AttachRolePolicy',
202
+ 'iam:DetachRolePolicy',
203
+ 'iam:TagRole',
204
+ 'iam:UntagRole',
205
+ 'iam:ListPolicyVersions',
206
+
207
+ // S3 permissions
208
+ 's3:CreateBucket',
209
+ 's3:PutObject',
210
+ 's3:GetObject',
211
+ 's3:DeleteObject',
212
+ 's3:PutBucketPolicy',
213
+ 's3:PutBucketVersioning',
214
+ 's3:PutBucketPublicAccessBlock',
215
+ 's3:GetBucketLocation',
216
+ 's3:ListBucket',
217
+
218
+ // SQS permissions
219
+ 'sqs:CreateQueue',
220
+ 'sqs:DeleteQueue',
221
+ 'sqs:GetQueueAttributes',
222
+ 'sqs:SetQueueAttributes',
223
+ 'sqs:GetQueueUrl',
224
+ 'sqs:TagQueue',
225
+ 'sqs:UntagQueue',
226
+
227
+ // SNS permissions
228
+ 'sns:CreateTopic',
229
+ 'sns:DeleteTopic',
230
+ 'sns:GetTopicAttributes',
231
+ 'sns:SetTopicAttributes',
232
+ 'sns:Subscribe',
233
+ 'sns:Unsubscribe',
234
+ 'sns:ListSubscriptionsByTopic',
235
+ 'sns:TagResource',
236
+ 'sns:UntagResource',
237
+
238
+ // CloudWatch and Logs permissions
239
+ 'cloudwatch:PutMetricAlarm',
240
+ 'cloudwatch:DeleteAlarms',
241
+ 'cloudwatch:DescribeAlarms',
242
+ 'logs:CreateLogGroup',
243
+ 'logs:CreateLogStream',
244
+ 'logs:DeleteLogGroup',
245
+ 'logs:DescribeLogGroups',
246
+ 'logs:DescribeLogStreams',
247
+ 'logs:FilterLogEvents',
248
+ 'logs:PutLogEvents',
249
+ 'logs:PutRetentionPolicy',
250
+
251
+ // API Gateway permissions
252
+ 'apigateway:POST',
253
+ 'apigateway:PUT',
254
+ 'apigateway:DELETE',
255
+ 'apigateway:GET',
256
+ 'apigateway:PATCH'
257
+ ];
258
+
259
+ const coreStatements = [
260
+ {
261
+ Sid: 'CloudFormationFriggStacks',
262
+ Effect: 'Allow',
263
+ Action: [
264
+ 'cloudformation:CreateStack',
265
+ 'cloudformation:UpdateStack',
266
+ 'cloudformation:DeleteStack',
267
+ 'cloudformation:DescribeStacks',
268
+ 'cloudformation:DescribeStackEvents',
269
+ 'cloudformation:DescribeStackResources',
270
+ 'cloudformation:DescribeStackResource',
271
+ 'cloudformation:ListStackResources',
272
+ 'cloudformation:GetTemplate',
273
+ 'cloudformation:DescribeChangeSet',
274
+ 'cloudformation:CreateChangeSet',
275
+ 'cloudformation:DeleteChangeSet',
276
+ 'cloudformation:ExecuteChangeSet'
277
+ ],
278
+ Resource: [
279
+ { 'Fn::Sub': 'arn:aws:cloudformation:*:${AWS::AccountId}:stack/*frigg*/*' }
280
+ ]
281
+ },
282
+ {
283
+ Sid: 'CloudFormationValidateTemplate',
284
+ Effect: 'Allow',
285
+ Action: ['cloudformation:ValidateTemplate'],
286
+ Resource: '*'
287
+ },
288
+ {
289
+ Sid: 'S3DeploymentBucket',
290
+ Effect: 'Allow',
291
+ Action: [
292
+ 's3:CreateBucket',
293
+ 's3:PutObject',
294
+ 's3:GetObject',
295
+ 's3:DeleteObject',
296
+ 's3:PutBucketPolicy',
297
+ 's3:PutBucketVersioning',
298
+ 's3:PutBucketPublicAccessBlock',
299
+ 's3:GetBucketLocation',
300
+ 's3:ListBucket'
301
+ ],
302
+ Resource: [
303
+ 'arn:aws:s3:::*serverless*',
304
+ 'arn:aws:s3:::*serverless*/*'
305
+ ]
306
+ },
307
+ {
308
+ Sid: 'LambdaFriggFunctions',
309
+ Effect: 'Allow',
310
+ Action: [
311
+ 'lambda:CreateFunction',
312
+ 'lambda:UpdateFunctionCode',
313
+ 'lambda:UpdateFunctionConfiguration',
314
+ 'lambda:DeleteFunction',
315
+ 'lambda:GetFunction',
316
+ 'lambda:ListFunctions',
317
+ 'lambda:PublishVersion',
318
+ 'lambda:CreateAlias',
319
+ 'lambda:UpdateAlias',
320
+ 'lambda:DeleteAlias',
321
+ 'lambda:GetAlias',
322
+ 'lambda:AddPermission',
323
+ 'lambda:RemovePermission',
324
+ 'lambda:GetPolicy',
325
+ 'lambda:PutProvisionedConcurrencyConfig',
326
+ 'lambda:DeleteProvisionedConcurrencyConfig',
327
+ 'lambda:PutConcurrency',
328
+ 'lambda:DeleteConcurrency',
329
+ 'lambda:TagResource',
330
+ 'lambda:UntagResource',
331
+ 'lambda:ListVersionsByFunction'
332
+ ],
333
+ Resource: [
334
+ { 'Fn::Sub': 'arn:aws:lambda:*:${AWS::AccountId}:function:*frigg*' }
335
+ ]
336
+ },
337
+ {
338
+ Sid: 'IAMRolesForFriggLambda',
339
+ Effect: 'Allow',
340
+ Action: [
341
+ 'iam:CreateRole',
342
+ 'iam:DeleteRole',
343
+ 'iam:GetRole',
344
+ 'iam:PassRole',
345
+ 'iam:PutRolePolicy',
346
+ 'iam:DeleteRolePolicy',
347
+ 'iam:GetRolePolicy',
348
+ 'iam:AttachRolePolicy',
349
+ 'iam:DetachRolePolicy',
350
+ 'iam:TagRole',
351
+ 'iam:UntagRole'
352
+ ],
353
+ Resource: [
354
+ { 'Fn::Sub': 'arn:aws:iam::${AWS::AccountId}:role/*frigg*' },
355
+ { 'Fn::Sub': 'arn:aws:iam::${AWS::AccountId}:role/*frigg*LambdaRole*' }
356
+ ]
357
+ },
358
+ {
359
+ Sid: 'IAMPolicyVersionPermissions',
360
+ Effect: 'Allow',
361
+ Action: ['iam:ListPolicyVersions'],
362
+ Resource: [{ 'Fn::Sub': 'arn:aws:iam::${AWS::AccountId}:policy/*' }]
363
+ },
364
+ {
365
+ Sid: 'FriggMessagingServices',
366
+ Effect: 'Allow',
367
+ Action: [
368
+ 'sqs:CreateQueue',
369
+ 'sqs:DeleteQueue',
370
+ 'sqs:GetQueueAttributes',
371
+ 'sqs:SetQueueAttributes',
372
+ 'sqs:GetQueueUrl',
373
+ 'sqs:TagQueue',
374
+ 'sqs:UntagQueue'
375
+ ],
376
+ Resource: [
377
+ { 'Fn::Sub': 'arn:aws:sqs:*:${AWS::AccountId}:*frigg*' },
378
+ { 'Fn::Sub': 'arn:aws:sqs:*:${AWS::AccountId}:internal-error-queue-*' }
379
+ ]
380
+ },
381
+ {
382
+ Sid: 'FriggSNSTopics',
383
+ Effect: 'Allow',
384
+ Action: [
385
+ 'sns:CreateTopic',
386
+ 'sns:DeleteTopic',
387
+ 'sns:GetTopicAttributes',
388
+ 'sns:SetTopicAttributes',
389
+ 'sns:Subscribe',
390
+ 'sns:Unsubscribe',
391
+ 'sns:ListSubscriptionsByTopic',
392
+ 'sns:TagResource',
393
+ 'sns:UntagResource'
394
+ ],
395
+ Resource: [
396
+ { 'Fn::Sub': 'arn:aws:sns:*:${AWS::AccountId}:*frigg*' }
397
+ ]
398
+ },
399
+ {
400
+ Sid: 'FriggMonitoringAndLogs',
401
+ Effect: 'Allow',
402
+ Action: [
403
+ 'cloudwatch:PutMetricAlarm',
404
+ 'cloudwatch:DeleteAlarms',
405
+ 'cloudwatch:DescribeAlarms',
406
+ 'logs:CreateLogGroup',
407
+ 'logs:CreateLogStream',
408
+ 'logs:DeleteLogGroup',
409
+ 'logs:DescribeLogGroups',
410
+ 'logs:DescribeLogStreams',
411
+ 'logs:FilterLogEvents',
412
+ 'logs:PutLogEvents',
413
+ 'logs:PutRetentionPolicy'
414
+ ],
415
+ Resource: [
416
+ { 'Fn::Sub': 'arn:aws:logs:*:${AWS::AccountId}:log-group:/aws/lambda/*frigg*' },
417
+ { 'Fn::Sub': 'arn:aws:logs:*:${AWS::AccountId}:log-group:/aws/lambda/*frigg*:*' },
418
+ { 'Fn::Sub': 'arn:aws:cloudwatch:*:${AWS::AccountId}:alarm:*frigg*' }
419
+ ]
420
+ },
421
+ {
422
+ Sid: 'FriggAPIGateway',
423
+ Effect: 'Allow',
424
+ Action: [
425
+ 'apigateway:POST',
426
+ 'apigateway:PUT',
427
+ 'apigateway:DELETE',
428
+ 'apigateway:GET',
429
+ 'apigateway:PATCH'
430
+ ],
431
+ Resource: [
432
+ 'arn:aws:apigateway:*::/restapis',
433
+ 'arn:aws:apigateway:*::/restapis/*',
434
+ 'arn:aws:apigateway:*::/domainnames',
435
+ 'arn:aws:apigateway:*::/domainnames/*'
436
+ ]
437
+ }
438
+ ];
439
+
440
+ template.Resources.FriggCoreDeploymentPolicy = {
441
+ Type: 'AWS::IAM::ManagedPolicy',
442
+ Properties: {
443
+ ManagedPolicyName: 'FriggCoreDeploymentPolicy',
444
+ Description: 'Core permissions for deploying Frigg applications',
445
+ PolicyDocument: {
446
+ Version: '2012-10-17',
447
+ Statement: coreStatements
448
+ }
449
+ }
450
+ };
451
+
452
+ // Add feature-specific policies only if needed
453
+ if (features.vpc) {
454
+ template.Resources.FriggVPCPolicy = {
455
+ Type: 'AWS::IAM::ManagedPolicy',
456
+ Condition: 'CreateVPCPermissions',
457
+ Properties: {
458
+ ManagedPolicyName: 'FriggVPCPolicy',
459
+ Description: 'VPC-related permissions for Frigg applications',
460
+ PolicyDocument: {
461
+ Version: '2012-10-17',
462
+ Statement: [
463
+ {
464
+ Sid: 'FriggVPCEndpointManagement',
465
+ Effect: 'Allow',
466
+ Action: [
467
+ 'ec2:CreateVpcEndpoint',
468
+ 'ec2:DeleteVpcEndpoint',
469
+ 'ec2:DescribeVpcEndpoints',
470
+ 'ec2:ModifyVpcEndpoint',
471
+ 'ec2:CreateNatGateway',
472
+ 'ec2:DeleteNatGateway',
473
+ 'ec2:DescribeNatGateways',
474
+ 'ec2:AllocateAddress',
475
+ 'ec2:ReleaseAddress',
476
+ 'ec2:DescribeAddresses',
477
+ 'ec2:CreateRouteTable',
478
+ 'ec2:DeleteRouteTable',
479
+ 'ec2:DescribeRouteTables',
480
+ 'ec2:CreateRoute',
481
+ 'ec2:DeleteRoute',
482
+ 'ec2:AssociateRouteTable',
483
+ 'ec2:DisassociateRouteTable',
484
+ 'ec2:CreateSecurityGroup',
485
+ 'ec2:DeleteSecurityGroup',
486
+ 'ec2:AuthorizeSecurityGroupEgress',
487
+ 'ec2:AuthorizeSecurityGroupIngress',
488
+ 'ec2:RevokeSecurityGroupEgress',
489
+ 'ec2:RevokeSecurityGroupIngress'
490
+ ],
491
+ Resource: '*'
492
+ }
493
+ ]
494
+ }
495
+ }
496
+ };
497
+ }
498
+
499
+ if (features.kms) {
500
+ template.Resources.FriggKMSPolicy = {
501
+ Type: 'AWS::IAM::ManagedPolicy',
502
+ Condition: 'CreateKMSPermissions',
503
+ Properties: {
504
+ ManagedPolicyName: 'FriggKMSPolicy',
505
+ Description: 'KMS encryption permissions for Frigg applications',
506
+ PolicyDocument: {
507
+ Version: '2012-10-17',
508
+ Statement: [
509
+ {
510
+ Sid: 'FriggKMSEncryptionRuntime',
511
+ Effect: 'Allow',
512
+ Action: [
513
+ 'kms:GenerateDataKey',
514
+ 'kms:Decrypt'
515
+ ],
516
+ Resource: [
517
+ { 'Fn::Sub': 'arn:aws:kms:*:${AWS::AccountId}:key/*' }
518
+ ],
519
+ Condition: {
520
+ StringEquals: {
521
+ 'kms:ViaService': [
522
+ 'lambda.*.amazonaws.com',
523
+ 's3.*.amazonaws.com'
524
+ ]
525
+ }
526
+ }
527
+ }
528
+ ]
529
+ }
530
+ }
531
+ };
532
+ }
533
+
534
+ if (features.ssm) {
535
+ template.Resources.FriggSSMPolicy = {
536
+ Type: 'AWS::IAM::ManagedPolicy',
537
+ Condition: 'CreateSSMPermissions',
538
+ Properties: {
539
+ ManagedPolicyName: 'FriggSSMPolicy',
540
+ Description: 'SSM Parameter Store permissions for Frigg applications',
541
+ PolicyDocument: {
542
+ Version: '2012-10-17',
543
+ Statement: [
544
+ {
545
+ Sid: 'FriggSSMParameterAccess',
546
+ Effect: 'Allow',
547
+ Action: [
548
+ 'ssm:GetParameter',
549
+ 'ssm:GetParameters',
550
+ 'ssm:GetParametersByPath'
551
+ ],
552
+ Resource: [
553
+ { 'Fn::Sub': 'arn:aws:ssm:*:${AWS::AccountId}:parameter/*frigg*' },
554
+ { 'Fn::Sub': 'arn:aws:ssm:*:${AWS::AccountId}:parameter/*frigg*/*' }
555
+ ]
556
+ }
557
+ ]
558
+ }
559
+ }
560
+ };
561
+ }
562
+
563
+ // Add Secrets Manager for credentials
564
+ template.Resources.FriggDeploymentCredentials = {
565
+ Type: 'AWS::SecretsManager::Secret',
566
+ Properties: {
567
+ Name: 'frigg-deployment-credentials',
568
+ Description: 'Access credentials for Frigg deployment user',
569
+ SecretString: {
570
+ 'Fn::Sub': JSON.stringify({
571
+ AccessKeyId: '${FriggDeploymentAccessKey}',
572
+ SecretAccessKey: '${FriggDeploymentAccessKey.SecretAccessKey}'
573
+ })
574
+ }
575
+ }
576
+ };
577
+
578
+ // Add Outputs
579
+ template.Outputs = {
580
+ DeploymentUserArn: {
581
+ Description: 'ARN of the Frigg deployment user',
582
+ Value: { 'Fn::GetAtt': ['FriggDeploymentUser', 'Arn'] },
583
+ Export: {
584
+ Name: { 'Fn::Sub': '${AWS::StackName}-UserArn' }
585
+ }
586
+ },
587
+ AccessKeyId: {
588
+ Description: 'Access Key ID for the deployment user',
589
+ Value: { Ref: 'FriggDeploymentAccessKey' },
590
+ Export: {
591
+ Name: { 'Fn::Sub': '${AWS::StackName}-AccessKeyId' }
592
+ }
593
+ },
594
+ SecretAccessKeyCommand: {
595
+ Description: 'Command to retrieve the secret access key',
596
+ Value: {
597
+ 'Fn::Sub': 'aws secretsmanager get-secret-value --secret-id frigg-deployment-credentials --query SecretString --output text | jq -r .SecretAccessKey'
598
+ }
599
+ },
600
+ CredentialsSecretArn: {
601
+ Description: 'ARN of the secret containing deployment credentials',
602
+ Value: { Ref: 'FriggDeploymentCredentials' },
603
+ Export: {
604
+ Name: { 'Fn::Sub': '${AWS::StackName}-CredentialsSecretArn' }
605
+ }
606
+ }
607
+ };
608
+
609
+ // Convert to YAML
610
+ return convertToYAML(template);
611
+ }
612
+
613
+ /**
614
+ * Convert JavaScript object to CloudFormation YAML
615
+ * @param {Object} obj - JavaScript object
616
+ * @returns {string} YAML string
617
+ */
618
+ function convertToYAML(obj) {
619
+ const yaml = require('js-yaml');
620
+ return yaml.dump(obj, {
621
+ indent: 2,
622
+ lineWidth: 120,
623
+ noRefs: true,
624
+ sortKeys: false
625
+ });
626
+ }
627
+
628
+ /**
629
+ * Generate summary of what features will be included in the IAM policy
630
+ * @param {Object} appDefinition - Application definition
631
+ * @returns {Object} Feature summary
632
+ */
633
+ function getFeatureSummary(appDefinition) {
634
+ const features = {
635
+ core: true, // Always enabled
636
+ vpc: appDefinition.vpc?.enable === true,
637
+ kms: appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true,
638
+ ssm: appDefinition.ssm?.enable === true,
639
+ websockets: appDefinition.websockets?.enable === true
640
+ };
641
+
642
+ const integrationCount = appDefinition.integrations?.length || 0;
643
+
644
+ return {
645
+ features,
646
+ integrationCount,
647
+ appName: appDefinition.name || 'Unnamed Frigg App'
648
+ };
649
+ }
650
+
651
+ /**
652
+ * Generate basic IAM policy (JSON format) - Core Frigg permissions only
653
+ * @returns {Object} Basic IAM policy document
654
+ */
655
+ function generateBasicIAMPolicy() {
656
+ const basicPolicyPath = path.join(__dirname, 'iam-policy-basic.json');
657
+ return require(basicPolicyPath);
658
+ }
659
+
660
+ /**
661
+ * Generate full IAM policy (JSON format) - All features enabled
662
+ * @returns {Object} Full IAM policy document
663
+ */
664
+ function generateFullIAMPolicy() {
665
+ const fullPolicyPath = path.join(__dirname, 'iam-policy-full.json');
666
+ return require(fullPolicyPath);
667
+ }
668
+
669
+ /**
670
+ * Generate IAM policy based on mode
671
+ * @param {string} mode - 'basic' or 'full'
672
+ * @returns {Object} IAM policy document
673
+ */
674
+ function generateIAMPolicy(mode = 'basic') {
675
+ if (mode === 'full') {
676
+ return generateFullIAMPolicy();
677
+ }
678
+ return generateBasicIAMPolicy();
679
+ }
680
+
681
+ module.exports = {
682
+ generateIAMCloudFormation,
683
+ getFeatureSummary,
684
+ generateBasicIAMPolicy,
685
+ generateFullIAMPolicy,
686
+ generateIAMPolicy
687
+ };