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