@friggframework/devtools 2.0.0-next.62 → 2.0.0-next.63
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/infrastructure/ARCHITECTURE.md +487 -0
- package/infrastructure/CLAUDE.md +481 -0
- package/infrastructure/HEALTH.md +468 -0
- package/infrastructure/README.md +522 -0
- package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
- package/infrastructure/__tests__/helpers/test-utils.js +277 -0
- package/infrastructure/__tests__/postgres-config.test.js +914 -0
- package/infrastructure/__tests__/template-generation.test.js +687 -0
- package/infrastructure/create-frigg-infrastructure.js +147 -0
- package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
- package/infrastructure/docs/PRE-DEPLOYMENT-HEALTH-CHECK-SPEC.md +1317 -0
- package/infrastructure/docs/WEBSOCKET-CONFIGURATION.md +105 -0
- package/infrastructure/docs/deployment-instructions.md +268 -0
- package/infrastructure/docs/generate-iam-command.md +278 -0
- package/infrastructure/docs/iam-policy-templates.md +193 -0
- package/infrastructure/domains/database/aurora-builder.js +809 -0
- package/infrastructure/domains/database/aurora-builder.test.js +950 -0
- package/infrastructure/domains/database/aurora-discovery.js +87 -0
- package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
- package/infrastructure/domains/database/aurora-resolver.js +210 -0
- package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
- package/infrastructure/domains/database/migration-builder.js +701 -0
- package/infrastructure/domains/database/migration-builder.test.js +321 -0
- package/infrastructure/domains/database/migration-resolver.js +163 -0
- package/infrastructure/domains/database/migration-resolver.test.js +337 -0
- package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
- package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
- package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
- package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
- package/infrastructure/domains/health/application/ports/index.js +26 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
- package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
- package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
- package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
- package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
- package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
- package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
- package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
- package/infrastructure/domains/health/domain/entities/issue.js +299 -0
- package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
- package/infrastructure/domains/health/domain/entities/resource.js +159 -0
- package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
- package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
- package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
- package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
- package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
- package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
- package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
- package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
- package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
- package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
- package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
- package/infrastructure/domains/integration/integration-builder.js +404 -0
- package/infrastructure/domains/integration/integration-builder.test.js +690 -0
- package/infrastructure/domains/integration/integration-resolver.js +170 -0
- package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
- package/infrastructure/domains/integration/websocket-builder.js +69 -0
- package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
- package/infrastructure/domains/networking/vpc-builder.js +2051 -0
- package/infrastructure/domains/networking/vpc-builder.test.js +1960 -0
- package/infrastructure/domains/networking/vpc-discovery.js +177 -0
- package/infrastructure/domains/networking/vpc-discovery.test.js +350 -0
- package/infrastructure/domains/networking/vpc-resolver.js +505 -0
- package/infrastructure/domains/networking/vpc-resolver.test.js +801 -0
- package/infrastructure/domains/parameters/ssm-builder.js +79 -0
- package/infrastructure/domains/parameters/ssm-builder.test.js +189 -0
- package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
- package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
- package/infrastructure/domains/security/iam-generator.js +816 -0
- package/infrastructure/domains/security/iam-generator.test.js +204 -0
- package/infrastructure/domains/security/kms-builder.js +415 -0
- package/infrastructure/domains/security/kms-builder.test.js +392 -0
- package/infrastructure/domains/security/kms-discovery.js +80 -0
- package/infrastructure/domains/security/kms-discovery.test.js +177 -0
- package/infrastructure/domains/security/kms-resolver.js +96 -0
- package/infrastructure/domains/security/kms-resolver.test.js +216 -0
- package/infrastructure/domains/security/templates/frigg-deployment-iam-stack.yaml +401 -0
- package/infrastructure/domains/security/templates/iam-policy-basic.json +218 -0
- package/infrastructure/domains/security/templates/iam-policy-full.json +288 -0
- package/infrastructure/domains/shared/base-builder.js +112 -0
- package/infrastructure/domains/shared/base-resolver.js +186 -0
- package/infrastructure/domains/shared/base-resolver.test.js +305 -0
- package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
- package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
- package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
- package/infrastructure/domains/shared/cloudformation-discovery.js +672 -0
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +985 -0
- package/infrastructure/domains/shared/environment-builder.js +119 -0
- package/infrastructure/domains/shared/environment-builder.test.js +247 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.js +579 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +416 -0
- package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
- package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
- package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
- package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
- package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
- package/infrastructure/domains/shared/resource-discovery.enhanced.test.js +306 -0
- package/infrastructure/domains/shared/resource-discovery.js +233 -0
- package/infrastructure/domains/shared/resource-discovery.test.js +588 -0
- package/infrastructure/domains/shared/types/app-definition.js +205 -0
- package/infrastructure/domains/shared/types/discovery-result.js +106 -0
- package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
- package/infrastructure/domains/shared/types/index.js +46 -0
- package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
- package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +408 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +291 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +159 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +444 -0
- package/infrastructure/domains/shared/validation/env-validator.js +78 -0
- package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
- package/infrastructure/domains/shared/validation/plugin-validator.js +187 -0
- package/infrastructure/domains/shared/validation/plugin-validator.test.js +323 -0
- package/infrastructure/esbuild.config.js +53 -0
- package/infrastructure/index.js +4 -0
- package/infrastructure/infrastructure-composer.js +117 -0
- package/infrastructure/infrastructure-composer.test.js +1895 -0
- package/infrastructure/integration.test.js +383 -0
- package/infrastructure/scripts/build-prisma-layer.js +701 -0
- package/infrastructure/scripts/build-prisma-layer.test.js +170 -0
- package/infrastructure/scripts/build-time-discovery.js +238 -0
- package/infrastructure/scripts/build-time-discovery.test.js +379 -0
- package/infrastructure/scripts/run-discovery.js +110 -0
- package/infrastructure/scripts/verify-prisma-layer.js +72 -0
- package/package.json +8 -7
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
const { generateIAMCloudFormation, getFeatureSummary } = require('./iam-generator');
|
|
2
|
+
|
|
3
|
+
describe('IAM Generator', () => {
|
|
4
|
+
describe('getFeatureSummary', () => {
|
|
5
|
+
it('should detect all features when enabled', () => {
|
|
6
|
+
const appDefinition = {
|
|
7
|
+
name: 'test-app',
|
|
8
|
+
integrations: ['Integration1', 'Integration2'],
|
|
9
|
+
vpc: { enable: true },
|
|
10
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
11
|
+
ssm: { enable: true },
|
|
12
|
+
websockets: { enable: true }
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const summary = getFeatureSummary(appDefinition);
|
|
16
|
+
|
|
17
|
+
expect(summary.appName).toBe('test-app');
|
|
18
|
+
expect(summary.integrationCount).toBe(2);
|
|
19
|
+
expect(summary.features.core).toBe(true);
|
|
20
|
+
expect(summary.features.vpc).toBe(true);
|
|
21
|
+
expect(summary.features.kms).toBe(true);
|
|
22
|
+
expect(summary.features.ssm).toBe(true);
|
|
23
|
+
expect(summary.features.websockets).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should detect minimal features when disabled', () => {
|
|
27
|
+
const appDefinition = {
|
|
28
|
+
integrations: []
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const summary = getFeatureSummary(appDefinition);
|
|
32
|
+
|
|
33
|
+
expect(summary.appName).toBe('Unnamed Frigg App');
|
|
34
|
+
expect(summary.integrationCount).toBe(0);
|
|
35
|
+
expect(summary.features.core).toBe(true);
|
|
36
|
+
expect(summary.features.vpc).toBe(false);
|
|
37
|
+
expect(summary.features.kms).toBe(false);
|
|
38
|
+
expect(summary.features.ssm).toBe(false);
|
|
39
|
+
expect(summary.features.websockets).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('generateIAMCloudFormation', () => {
|
|
44
|
+
it('should generate valid CloudFormation YAML', () => {
|
|
45
|
+
const appDefinition = {
|
|
46
|
+
name: 'test-app',
|
|
47
|
+
integrations: [],
|
|
48
|
+
vpc: { enable: false },
|
|
49
|
+
encryption: { fieldLevelEncryptionMethod: 'aes' },
|
|
50
|
+
ssm: { enable: false },
|
|
51
|
+
websockets: { enable: false }
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const summary = getFeatureSummary(appDefinition);
|
|
55
|
+
const yaml = generateIAMCloudFormation({
|
|
56
|
+
appName: summary.appName,
|
|
57
|
+
features: summary.features
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(yaml).toContain('AWSTemplateFormatVersion');
|
|
61
|
+
expect(yaml).toContain('FriggDeploymentUser');
|
|
62
|
+
expect(yaml).toContain('FriggCoreDeploymentPolicy');
|
|
63
|
+
expect(yaml).toContain('FriggDiscoveryPolicy');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should include VPC policy when VPC is enabled', () => {
|
|
67
|
+
const appDefinition = {
|
|
68
|
+
name: 'test-app',
|
|
69
|
+
integrations: [],
|
|
70
|
+
vpc: { enable: true }
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const summary = getFeatureSummary(appDefinition);
|
|
74
|
+
const yaml = generateIAMCloudFormation({
|
|
75
|
+
appName: summary.appName,
|
|
76
|
+
features: summary.features
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(yaml).toContain('FriggVPCPolicy');
|
|
80
|
+
expect(yaml).toContain('CreateVPCPermissions');
|
|
81
|
+
expect(yaml).toContain('EnableVPCSupport');
|
|
82
|
+
expect(yaml).toContain('ec2:ReplaceRoute');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should include KMS policy when encryption is enabled', () => {
|
|
86
|
+
const appDefinition = {
|
|
87
|
+
name: 'test-app',
|
|
88
|
+
integrations: [],
|
|
89
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' }
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const summary = getFeatureSummary(appDefinition);
|
|
93
|
+
const yaml = generateIAMCloudFormation({
|
|
94
|
+
appName: summary.appName,
|
|
95
|
+
features: summary.features
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(yaml).toContain('FriggKMSPolicy');
|
|
99
|
+
expect(yaml).toContain('CreateKMSPermissions');
|
|
100
|
+
expect(yaml).toContain('EnableKMSSupport');
|
|
101
|
+
expect(yaml).toContain('FriggKMSKeyAlias');
|
|
102
|
+
expect(yaml).toContain('kms:CreateAlias');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should include SSM policy when SSM is enabled', () => {
|
|
106
|
+
const appDefinition = {
|
|
107
|
+
name: 'test-app',
|
|
108
|
+
integrations: [],
|
|
109
|
+
ssm: { enable: true }
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const summary = getFeatureSummary(appDefinition);
|
|
113
|
+
const yaml = generateIAMCloudFormation({
|
|
114
|
+
appName: summary.appName,
|
|
115
|
+
features: summary.features
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(yaml).toContain('FriggSSMPolicy');
|
|
119
|
+
expect(yaml).toContain('CreateSSMPermissions');
|
|
120
|
+
expect(yaml).toContain('EnableSSMSupport');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should set correct default parameter values based on features', () => {
|
|
124
|
+
const appDefinition = {
|
|
125
|
+
name: 'test-app',
|
|
126
|
+
integrations: [],
|
|
127
|
+
vpc: { enable: true },
|
|
128
|
+
encryption: { fieldLevelEncryptionMethod: 'aes' },
|
|
129
|
+
ssm: { enable: true }
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const summary = getFeatureSummary(appDefinition);
|
|
133
|
+
const yaml = generateIAMCloudFormation({
|
|
134
|
+
appName: summary.appName,
|
|
135
|
+
features: summary.features
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Check parameter defaults match the enabled features
|
|
139
|
+
expect(yaml).toContain("Default: 'true'"); // VPC enabled
|
|
140
|
+
expect(yaml).toContain("Default: 'false'"); // KMS disabled
|
|
141
|
+
expect(yaml).toContain('alias/frigg-deployment');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should include all core permissions', () => {
|
|
145
|
+
const appDefinition = {
|
|
146
|
+
name: 'test-app',
|
|
147
|
+
integrations: []
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const summary = getFeatureSummary(appDefinition);
|
|
151
|
+
const yaml = generateIAMCloudFormation({
|
|
152
|
+
appName: summary.appName,
|
|
153
|
+
features: summary.features
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Check for core permissions
|
|
157
|
+
expect(yaml).toContain('cloudformation:CreateStack');
|
|
158
|
+
expect(yaml).toContain('cloudformation:ListStackResources');
|
|
159
|
+
expect(yaml).toContain('lambda:CreateFunction');
|
|
160
|
+
expect(yaml).toContain('iam:CreateRole');
|
|
161
|
+
expect(yaml).toContain('s3:CreateBucket');
|
|
162
|
+
expect(yaml).toContain('sqs:CreateQueue');
|
|
163
|
+
expect(yaml).toContain('sns:CreateTopic');
|
|
164
|
+
expect(yaml).toContain('logs:CreateLogGroup');
|
|
165
|
+
expect(yaml).toContain('apigateway:POST');
|
|
166
|
+
expect(yaml).toContain('lambda:ListVersionsByFunction');
|
|
167
|
+
expect(yaml).toContain('iam:ListPolicyVersions');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should include internal-error-queue pattern in SQS resources', () => {
|
|
171
|
+
const appDefinition = {
|
|
172
|
+
name: 'test-app',
|
|
173
|
+
integrations: []
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const summary = getFeatureSummary(appDefinition);
|
|
177
|
+
const yaml = generateIAMCloudFormation({
|
|
178
|
+
appName: summary.appName,
|
|
179
|
+
features: summary.features
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(yaml).toContain('internal-error-queue-*');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should generate outputs section', () => {
|
|
186
|
+
const appDefinition = {
|
|
187
|
+
name: 'test-app',
|
|
188
|
+
integrations: []
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const summary = getFeatureSummary(appDefinition);
|
|
192
|
+
const yaml = generateIAMCloudFormation({
|
|
193
|
+
appName: summary.appName,
|
|
194
|
+
features: summary.features
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
expect(yaml).toContain('Outputs:');
|
|
198
|
+
expect(yaml).toContain('DeploymentUserArn:');
|
|
199
|
+
expect(yaml).toContain('AccessKeyId:');
|
|
200
|
+
expect(yaml).toContain('SecretAccessKeyCommand:');
|
|
201
|
+
expect(yaml).toContain('CredentialsSecretArn:');
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
});
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KMS (Key Management Service) Builder
|
|
3
|
+
*
|
|
4
|
+
* Domain Layer - Hexagonal Architecture
|
|
5
|
+
*
|
|
6
|
+
* Responsible for:
|
|
7
|
+
* - KMS key creation or discovery
|
|
8
|
+
* - KMS key configuration for field-level encryption
|
|
9
|
+
* - IAM permissions for KMS operations
|
|
10
|
+
* - KMS key policy configuration for Lambda execution role
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { InfrastructureBuilder, ValidationResult } = require('../shared/base-builder');
|
|
14
|
+
const { KmsResourceResolver } = require('./kms-resolver');
|
|
15
|
+
const { createEmptyDiscoveryResult, ResourceOwnership } = require('../shared/types');
|
|
16
|
+
|
|
17
|
+
class KmsBuilder extends InfrastructureBuilder {
|
|
18
|
+
constructor() {
|
|
19
|
+
super();
|
|
20
|
+
this.name = 'KmsBuilder';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
shouldExecute(appDefinition) {
|
|
24
|
+
// Skip KMS in local mode (when FRIGG_SKIP_AWS_DISCOVERY is set)
|
|
25
|
+
// KMS is an AWS-specific service that should only be created in production
|
|
26
|
+
if (process.env.FRIGG_SKIP_AWS_DISCOVERY === 'true') {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
validate(appDefinition) {
|
|
34
|
+
const result = new ValidationResult();
|
|
35
|
+
|
|
36
|
+
if (!appDefinition.encryption) {
|
|
37
|
+
result.addError('Encryption configuration is missing');
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const encryption = appDefinition.encryption;
|
|
42
|
+
|
|
43
|
+
if (encryption.fieldLevelEncryptionMethod !== 'kms') {
|
|
44
|
+
// Not an error - just not applicable
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Validate createResourceIfNoneFound is boolean
|
|
49
|
+
if (encryption.createResourceIfNoneFound !== undefined &&
|
|
50
|
+
typeof encryption.createResourceIfNoneFound !== 'boolean') {
|
|
51
|
+
result.addError('encryption.createResourceIfNoneFound must be a boolean');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Build KMS infrastructure using ownership-based architecture
|
|
59
|
+
*/
|
|
60
|
+
async build(appDefinition, discoveredResources) {
|
|
61
|
+
console.log(`\n[${this.name}] Configuring KMS encryption...`);
|
|
62
|
+
|
|
63
|
+
// Backwards compatibility: Translate old schema to new ownership schema
|
|
64
|
+
appDefinition = this.translateLegacyConfig(appDefinition, discoveredResources);
|
|
65
|
+
|
|
66
|
+
const result = {
|
|
67
|
+
resources: {},
|
|
68
|
+
iamStatements: [],
|
|
69
|
+
environment: {},
|
|
70
|
+
pluginConfig: {},
|
|
71
|
+
plugins: [],
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Get structured discovery result
|
|
75
|
+
const discovery = discoveredResources._structured || this.convertFlatDiscoveryToStructured(discoveredResources, appDefinition);
|
|
76
|
+
|
|
77
|
+
// Use KmsResourceResolver to make ownership decisions
|
|
78
|
+
const resolver = new KmsResourceResolver();
|
|
79
|
+
const decisions = resolver.resolveAll(appDefinition, discovery);
|
|
80
|
+
|
|
81
|
+
// Check if external key exists (for accurate logging)
|
|
82
|
+
const externalKmsKey = discoveredResources?.defaultKmsKeyId ||
|
|
83
|
+
discoveredResources?.kmsKeyArn ||
|
|
84
|
+
discoveredResources?.kmsKeyId;
|
|
85
|
+
const willUseExternal = decisions.key.ownership === ResourceOwnership.STACK &&
|
|
86
|
+
!decisions.key.physicalId &&
|
|
87
|
+
externalKmsKey;
|
|
88
|
+
|
|
89
|
+
console.log('\n 📋 Resource Ownership Decisions:');
|
|
90
|
+
if (willUseExternal) {
|
|
91
|
+
console.log(` Key: external - Found external KMS key (not in stack)`);
|
|
92
|
+
} else {
|
|
93
|
+
console.log(` Key: ${decisions.key.ownership} - ${decisions.key.reason}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Build resources based on ownership decisions
|
|
97
|
+
await this.buildFromDecisions(decisions, appDefinition, discoveredResources, result);
|
|
98
|
+
|
|
99
|
+
// Add IAM permissions for Lambda role
|
|
100
|
+
result.iamStatements.push({
|
|
101
|
+
Effect: 'Allow',
|
|
102
|
+
Action: ['kms:GenerateDataKey', 'kms:Decrypt', 'kms:Encrypt', 'kms:DescribeKey'],
|
|
103
|
+
Resource: result.environment.KMS_KEY_ARN,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
console.log(`[${this.name}] ✅ KMS configuration completed`);
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Convert flat discovery to structured discovery
|
|
112
|
+
* Provides backwards compatibility for tests
|
|
113
|
+
*/
|
|
114
|
+
convertFlatDiscoveryToStructured(flatDiscovery, appDefinition = {}) {
|
|
115
|
+
const discovery = createEmptyDiscoveryResult();
|
|
116
|
+
|
|
117
|
+
if (!flatDiscovery) {
|
|
118
|
+
return discovery;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check if resources are from CloudFormation stack
|
|
122
|
+
const isManagedIsolated = appDefinition.managementMode === 'managed' &&
|
|
123
|
+
(appDefinition.vpcIsolation === 'isolated' || !appDefinition.vpcIsolation);
|
|
124
|
+
const hasExistingStackResources = isManagedIsolated && flatDiscovery.defaultKmsKeyId &&
|
|
125
|
+
typeof flatDiscovery.defaultKmsKeyId === 'string';
|
|
126
|
+
|
|
127
|
+
if (flatDiscovery.fromCloudFormationStack || hasExistingStackResources) {
|
|
128
|
+
discovery.fromCloudFormation = true;
|
|
129
|
+
discovery.stackName = flatDiscovery.stackName || 'assumed-stack';
|
|
130
|
+
|
|
131
|
+
// Add stack-managed resources
|
|
132
|
+
let existingLogicalIds = flatDiscovery.existingLogicalIds || [];
|
|
133
|
+
|
|
134
|
+
// Infer logical IDs from physical IDs if needed
|
|
135
|
+
if (hasExistingStackResources && existingLogicalIds.length === 0) {
|
|
136
|
+
if (flatDiscovery.defaultKmsKeyId) {
|
|
137
|
+
existingLogicalIds.push('FriggKMSKey');
|
|
138
|
+
existingLogicalIds.push('FriggKMSKeyAlias');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
existingLogicalIds.forEach(logicalId => {
|
|
143
|
+
let resourceType = '';
|
|
144
|
+
let physicalId = '';
|
|
145
|
+
|
|
146
|
+
if (logicalId === 'FriggKMSKey') {
|
|
147
|
+
resourceType = 'AWS::KMS::Key';
|
|
148
|
+
physicalId = flatDiscovery.defaultKmsKeyId;
|
|
149
|
+
} else if (logicalId === 'FriggKMSKeyAlias') {
|
|
150
|
+
resourceType = 'AWS::KMS::Alias';
|
|
151
|
+
// Extract alias name from KMS key ARN or use default pattern
|
|
152
|
+
const stackName = flatDiscovery.stackName || 'unknown';
|
|
153
|
+
const stage = appDefinition.stage || 'dev';
|
|
154
|
+
physicalId = `alias/${stackName.replace(`-${stage}`, '')}-${stage}-frigg-kms`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (physicalId && typeof physicalId === 'string') {
|
|
158
|
+
discovery.stackManaged.push({
|
|
159
|
+
logicalId,
|
|
160
|
+
physicalId,
|
|
161
|
+
resourceType
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
} else {
|
|
166
|
+
// Resources discovered from AWS API (external)
|
|
167
|
+
if (flatDiscovery.defaultKmsKeyId && typeof flatDiscovery.defaultKmsKeyId === 'string') {
|
|
168
|
+
discovery.external.push({
|
|
169
|
+
physicalId: flatDiscovery.defaultKmsKeyId,
|
|
170
|
+
resourceType: 'AWS::KMS::Key',
|
|
171
|
+
source: 'aws-discovery'
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return discovery;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Translate legacy configuration to ownership-based configuration
|
|
181
|
+
* Provides backwards compatibility
|
|
182
|
+
*/
|
|
183
|
+
translateLegacyConfig(appDefinition, discoveredResources) {
|
|
184
|
+
// If already using ownership schema, return as-is
|
|
185
|
+
if (appDefinition.encryption?.ownership) {
|
|
186
|
+
return appDefinition;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const translated = JSON.parse(JSON.stringify(appDefinition));
|
|
190
|
+
|
|
191
|
+
// Initialize ownership sections
|
|
192
|
+
if (!translated.encryption) translated.encryption = {};
|
|
193
|
+
if (!translated.encryption.ownership) {
|
|
194
|
+
translated.encryption.ownership = {};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Handle top-level managementMode
|
|
198
|
+
const globalMode = appDefinition.managementMode || 'discover';
|
|
199
|
+
const vpcIsolation = appDefinition.vpcIsolation || 'shared';
|
|
200
|
+
|
|
201
|
+
if (globalMode === 'managed') {
|
|
202
|
+
if (appDefinition.encryption?.createResourceIfNoneFound !== undefined) {
|
|
203
|
+
console.log(` ⚠️ managementMode='managed' ignoring: encryption.createResourceIfNoneFound`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (vpcIsolation === 'isolated') {
|
|
207
|
+
const hasStackKms = discoveredResources?.defaultKmsKeyId &&
|
|
208
|
+
typeof discoveredResources.defaultKmsKeyId === 'string';
|
|
209
|
+
|
|
210
|
+
if (hasStackKms) {
|
|
211
|
+
translated.encryption.ownership.key = 'auto';
|
|
212
|
+
console.log(` managementMode='managed' + vpcIsolation='isolated' → stack has KMS, reusing`);
|
|
213
|
+
} else {
|
|
214
|
+
translated.encryption.ownership.key = 'stack';
|
|
215
|
+
console.log(` managementMode='managed' + vpcIsolation='isolated' → no stack KMS, creating new`);
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
translated.encryption.ownership.key = 'auto';
|
|
219
|
+
console.log(` managementMode='managed' + vpcIsolation='shared' → discovering KMS`);
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
// Handle legacy createResourceIfNoneFound
|
|
223
|
+
const createIfNoneFound = appDefinition.encryption?.createResourceIfNoneFound;
|
|
224
|
+
if (createIfNoneFound === true) {
|
|
225
|
+
translated.encryption.ownership.key = 'stack';
|
|
226
|
+
} else if (createIfNoneFound === false || createIfNoneFound === undefined) {
|
|
227
|
+
// When createResourceIfNoneFound is false or not specified:
|
|
228
|
+
// - If KMS found → use it (auto)
|
|
229
|
+
// - If not found → use environment variable (external)
|
|
230
|
+
// We use 'auto' here; the resolver will decide based on discovery
|
|
231
|
+
// But we need special handling in buildFromDecisions for the env var fallback
|
|
232
|
+
translated.encryption.ownership.key = 'auto';
|
|
233
|
+
translated.encryption._useEnvVarFallback = true; // Flag for env var fallback
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return translated;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Build all KMS resources based on ownership decisions
|
|
242
|
+
*/
|
|
243
|
+
async buildFromDecisions(decisions, appDefinition, discoveredResources, result) {
|
|
244
|
+
// Check for environment variable fallback flag (legacy behavior)
|
|
245
|
+
const useEnvVarFallback = appDefinition.encryption?._useEnvVarFallback;
|
|
246
|
+
|
|
247
|
+
// CRITICAL FIX: Check if KMS key exists OUTSIDE of stack (orphaned resource)
|
|
248
|
+
// If key exists but not in stack, we should use it as EXTERNAL, not try to create it
|
|
249
|
+
const externalKmsKey = discoveredResources?.defaultKmsKeyId ||
|
|
250
|
+
discoveredResources?.kmsKeyArn ||
|
|
251
|
+
discoveredResources?.kmsKeyId;
|
|
252
|
+
|
|
253
|
+
if (decisions.key.ownership === ResourceOwnership.STACK && decisions.key.physicalId) {
|
|
254
|
+
// Key exists in stack - add definitions (CloudFormation idempotency)
|
|
255
|
+
console.log(' → Adding KMS definitions to template (existing in stack)');
|
|
256
|
+
|
|
257
|
+
// CRITICAL: Check if alias exists in stack before trying to create it
|
|
258
|
+
// Matches old serverless-template.js behavior: only create alias if it doesn't exist
|
|
259
|
+
const aliasExistsInStack = discoveredResources?.existingLogicalIds?.includes('FriggKMSKeyAlias');
|
|
260
|
+
if (!aliasExistsInStack) {
|
|
261
|
+
if (appDefinition.encryption?.kmsKeyAlias !== true) {
|
|
262
|
+
// Alias doesn't exist in stack - skip creation unless explicitly enabled
|
|
263
|
+
// This avoids kms:CreateAlias permission errors
|
|
264
|
+
console.log(' ℹ KMS alias not in stack - skipping creation (set kmsKeyAlias: true to force)');
|
|
265
|
+
appDefinition.encryption = appDefinition.encryption || {};
|
|
266
|
+
appDefinition.encryption.kmsKeyAlias = false;
|
|
267
|
+
} else {
|
|
268
|
+
console.log(' → Will create KMS alias (kmsKeyAlias: true explicitly set)');
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
console.log(' ✓ KMS alias found in stack - will keep in template');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
result.resources = this.createKmsKey(appDefinition);
|
|
275
|
+
result.environment.KMS_KEY_ARN = { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] };
|
|
276
|
+
console.log(' ✅ KMS key resources created');
|
|
277
|
+
} else if (decisions.key.ownership === ResourceOwnership.STACK && !decisions.key.physicalId && externalKmsKey) {
|
|
278
|
+
// ORPHANED KEY FIX: Key exists externally but not in stack
|
|
279
|
+
// Use it as external instead of trying to create (would fail with "already exists")
|
|
280
|
+
console.log(` → Using external KMS key: ${externalKmsKey}`);
|
|
281
|
+
|
|
282
|
+
// Format as ARN if it's just a key ID
|
|
283
|
+
const kmsArn = externalKmsKey.startsWith('arn:')
|
|
284
|
+
? externalKmsKey
|
|
285
|
+
: `arn:aws:kms:\${self:provider.region}:\${aws:accountId}:key/${externalKmsKey}`;
|
|
286
|
+
|
|
287
|
+
result.environment.KMS_KEY_ARN = kmsArn;
|
|
288
|
+
} else if (decisions.key.ownership === ResourceOwnership.STACK && !decisions.key.physicalId && !useEnvVarFallback) {
|
|
289
|
+
// Create new KMS key (only if not using env var fallback and no external key found)
|
|
290
|
+
console.log(' → Creating new KMS key in stack');
|
|
291
|
+
|
|
292
|
+
// CRITICAL: Don't create alias by default to avoid kms:CreateAlias permission errors
|
|
293
|
+
// Matches old serverless-template.js behavior: only create alias if explicitly requested
|
|
294
|
+
if (appDefinition.encryption?.kmsKeyAlias !== true) {
|
|
295
|
+
console.log(' ℹ Skipping KMS alias creation by default (set kmsKeyAlias: true to enable)');
|
|
296
|
+
appDefinition.encryption = appDefinition.encryption || {};
|
|
297
|
+
appDefinition.encryption.kmsKeyAlias = false;
|
|
298
|
+
} else {
|
|
299
|
+
console.log(' → Will create KMS alias (kmsKeyAlias: true explicitly set)');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
result.resources = this.createKmsKey(appDefinition);
|
|
303
|
+
result.environment.KMS_KEY_ARN = { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] };
|
|
304
|
+
console.log(' ✅ KMS key resources created');
|
|
305
|
+
} else if (decisions.key.ownership === ResourceOwnership.STACK && !decisions.key.physicalId && useEnvVarFallback) {
|
|
306
|
+
// Legacy behavior: fallback to environment variable when createResourceIfNoneFound=false/undefined
|
|
307
|
+
const createIfNoneFound = discoveredResources.defaultKmsKeyId ? true : appDefinition.encryption?.createResourceIfNoneFound;
|
|
308
|
+
const formatAsArn = createIfNoneFound === undefined; // Format as ARN when not specified
|
|
309
|
+
|
|
310
|
+
if (formatAsArn) {
|
|
311
|
+
console.log(' → Using environment variable for KMS key (formatted as ARN)');
|
|
312
|
+
result.environment.KMS_KEY_ARN = 'arn:aws:kms:${self:provider.region}:${aws:accountId}:key/${env:AWS_DISCOVERY_KMS_KEY_ID}';
|
|
313
|
+
} else {
|
|
314
|
+
console.log(' → Using environment variable for KMS key');
|
|
315
|
+
result.environment.KMS_KEY_ARN = '${env:AWS_DISCOVERY_KMS_KEY_ID}';
|
|
316
|
+
}
|
|
317
|
+
} else if (decisions.key.ownership === ResourceOwnership.EXTERNAL) {
|
|
318
|
+
// Use discovered KMS key
|
|
319
|
+
const kmsKeyId = decisions.key.physicalId || '${env:AWS_DISCOVERY_KMS_KEY_ID}';
|
|
320
|
+
console.log(` → Using ${decisions.key.physicalId ? 'discovered' : 'environment variable'} KMS key`);
|
|
321
|
+
|
|
322
|
+
// Format as ARN if it's just a key ID (for IAM policies)
|
|
323
|
+
const kmsArn = kmsKeyId.startsWith('arn:')
|
|
324
|
+
? kmsKeyId
|
|
325
|
+
: `arn:aws:kms:\${self:provider.region}:\${aws:accountId}:key/${kmsKeyId}`;
|
|
326
|
+
|
|
327
|
+
result.environment.KMS_KEY_ARN = kmsArn;
|
|
328
|
+
} else {
|
|
329
|
+
// Fallback
|
|
330
|
+
console.log(' → Using environment variable for KMS key');
|
|
331
|
+
result.environment.KMS_KEY_ARN = 'arn:aws:kms:${self:provider.region}:${aws:accountId}:key/${env:AWS_DISCOVERY_KMS_KEY_ID}';
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Create KMS key CloudFormation resources
|
|
337
|
+
*/
|
|
338
|
+
createKmsKey(appDefinition) {
|
|
339
|
+
const resources = {
|
|
340
|
+
FriggKMSKey: {
|
|
341
|
+
Type: 'AWS::KMS::Key',
|
|
342
|
+
DeletionPolicy: 'Retain',
|
|
343
|
+
UpdateReplacePolicy: 'Retain',
|
|
344
|
+
Properties: {
|
|
345
|
+
Description: 'Frigg Field-Level Encryption Key for ${self:service}-${self:provider.stage}',
|
|
346
|
+
EnableKeyRotation: true,
|
|
347
|
+
KeyPolicy: {
|
|
348
|
+
Version: '2012-10-17',
|
|
349
|
+
Id: 'key-policy-1',
|
|
350
|
+
Statement: [
|
|
351
|
+
{
|
|
352
|
+
Sid: 'AllowRootAccountAdmin',
|
|
353
|
+
Effect: 'Allow',
|
|
354
|
+
Principal: {
|
|
355
|
+
AWS: {
|
|
356
|
+
'Fn::Sub': 'arn:aws:iam::${AWS::AccountId}:root',
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
Action: 'kms:*',
|
|
360
|
+
Resource: '*',
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
Sid: 'AllowLambdaService',
|
|
364
|
+
Effect: 'Allow',
|
|
365
|
+
Principal: {
|
|
366
|
+
Service: 'lambda.amazonaws.com',
|
|
367
|
+
},
|
|
368
|
+
Action: [
|
|
369
|
+
'kms:Decrypt',
|
|
370
|
+
'kms:GenerateDataKey',
|
|
371
|
+
'kms:CreateGrant',
|
|
372
|
+
],
|
|
373
|
+
Resource: '*',
|
|
374
|
+
Condition: {
|
|
375
|
+
StringEquals: {
|
|
376
|
+
'kms:ViaService': 'lambda.${self:provider.region}.amazonaws.com',
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
// NOTE: We do NOT add a statement referencing IamRoleLambdaExecution here
|
|
381
|
+
// because it creates a circular dependency (KMS Key → IAM Role → KMS Key).
|
|
382
|
+
// Instead, IAM policies grant the Lambda execution role permissions to use KMS.
|
|
383
|
+
],
|
|
384
|
+
},
|
|
385
|
+
Tags: [
|
|
386
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-kms' },
|
|
387
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
388
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
389
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
390
|
+
],
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// Only create alias if explicitly enabled (default: true for backwards compatibility)
|
|
396
|
+
const createAlias = appDefinition.encryption?.kmsKeyAlias !== false;
|
|
397
|
+
if (createAlias) {
|
|
398
|
+
resources.FriggKMSKeyAlias = {
|
|
399
|
+
Type: 'AWS::KMS::Alias',
|
|
400
|
+
DeletionPolicy: 'Retain',
|
|
401
|
+
Properties: {
|
|
402
|
+
AliasName: 'alias/${self:service}-${self:provider.stage}-frigg-kms',
|
|
403
|
+
TargetKeyId: { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] },
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
} else {
|
|
407
|
+
console.log(' ℹ Skipping KMS key alias creation (kmsKeyAlias: false)');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return resources;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
module.exports = { KmsBuilder };
|
|
415
|
+
|