@friggframework/devtools 2.0.0-next.44 → 2.0.0-next.46
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/HEALTH.md +468 -0
- package/infrastructure/README.md +51 -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 +1 -1
- package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
- package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
- package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
- 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 +633 -0
- package/infrastructure/domains/database/migration-builder.test.js +294 -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 +397 -0
- package/infrastructure/domains/integration/integration-builder.test.js +593 -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 +1829 -0
- package/infrastructure/domains/networking/vpc-builder.test.js +1262 -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 +324 -0
- package/infrastructure/domains/networking/vpc-resolver.test.js +501 -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/{iam-generator.js → domains/security/iam-generator.js} +2 -2
- package/infrastructure/domains/security/kms-builder.js +366 -0
- package/infrastructure/domains/security/kms-builder.test.js +374 -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/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 +375 -0
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +590 -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 +544 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +377 -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.js +192 -0
- package/infrastructure/domains/shared/resource-discovery.test.js +552 -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 +380 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -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 +55 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +138 -0
- package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +2 -1
- package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
- package/infrastructure/esbuild.config.js +53 -0
- package/infrastructure/infrastructure-composer.js +87 -0
- package/infrastructure/{serverless-template.test.js → infrastructure-composer.test.js} +115 -24
- package/infrastructure/scripts/build-prisma-layer.js +553 -0
- package/infrastructure/scripts/build-prisma-layer.test.js +102 -0
- package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +80 -48
- package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
- package/layers/prisma/nodejs/package.json +8 -0
- package/management-ui/server/utils/cliIntegration.js +1 -1
- package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
- package/package.json +11 -11
- package/frigg-cli/.eslintrc.js +0 -141
- package/frigg-cli/__tests__/unit/commands/build.test.js +0 -251
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +0 -548
- package/frigg-cli/__tests__/unit/commands/install.test.js +0 -400
- package/frigg-cli/__tests__/unit/commands/ui.test.js +0 -346
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +0 -366
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +0 -304
- package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +0 -486
- package/frigg-cli/__tests__/utils/mock-factory.js +0 -270
- package/frigg-cli/__tests__/utils/prisma-mock.js +0 -194
- package/frigg-cli/__tests__/utils/test-fixtures.js +0 -463
- package/frigg-cli/__tests__/utils/test-setup.js +0 -287
- package/frigg-cli/build-command/index.js +0 -65
- package/frigg-cli/db-setup-command/index.js +0 -193
- package/frigg-cli/deploy-command/index.js +0 -175
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +0 -301
- package/frigg-cli/generate-command/azure-generator.js +0 -43
- package/frigg-cli/generate-command/gcp-generator.js +0 -47
- package/frigg-cli/generate-command/index.js +0 -332
- package/frigg-cli/generate-command/terraform-generator.js +0 -555
- package/frigg-cli/generate-iam-command.js +0 -118
- package/frigg-cli/index.js +0 -75
- package/frigg-cli/index.test.js +0 -158
- package/frigg-cli/init-command/backend-first-handler.js +0 -756
- package/frigg-cli/init-command/index.js +0 -93
- package/frigg-cli/init-command/template-handler.js +0 -143
- package/frigg-cli/install-command/backend-js.js +0 -33
- package/frigg-cli/install-command/commit-changes.js +0 -16
- package/frigg-cli/install-command/environment-variables.js +0 -127
- package/frigg-cli/install-command/environment-variables.test.js +0 -136
- package/frigg-cli/install-command/index.js +0 -54
- package/frigg-cli/install-command/install-package.js +0 -13
- package/frigg-cli/install-command/integration-file.js +0 -30
- package/frigg-cli/install-command/logger.js +0 -12
- package/frigg-cli/install-command/template.js +0 -90
- package/frigg-cli/install-command/validate-package.js +0 -75
- package/frigg-cli/jest.config.js +0 -124
- package/frigg-cli/package.json +0 -54
- package/frigg-cli/start-command/index.js +0 -149
- package/frigg-cli/start-command/start-command.test.js +0 -297
- package/frigg-cli/test/init-command.test.js +0 -180
- package/frigg-cli/test/npm-registry.test.js +0 -319
- package/frigg-cli/ui-command/index.js +0 -154
- package/frigg-cli/utils/app-resolver.js +0 -319
- package/frigg-cli/utils/backend-path.js +0 -25
- package/frigg-cli/utils/database-validator.js +0 -161
- package/frigg-cli/utils/error-messages.js +0 -257
- package/frigg-cli/utils/npm-registry.js +0 -167
- package/frigg-cli/utils/prisma-runner.js +0 -280
- package/frigg-cli/utils/process-manager.js +0 -199
- package/frigg-cli/utils/repo-detection.js +0 -405
- package/infrastructure/aws-discovery.js +0 -1176
- package/infrastructure/aws-discovery.test.js +0 -1220
- package/infrastructure/serverless-template.js +0 -2074
- /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
- /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
- /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
- /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
- /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
- /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
- /package/infrastructure/{run-discovery.js → scripts/run-discovery.js} +0 -0
|
@@ -0,0 +1,366 @@
|
|
|
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
|
+
console.log('\n 📋 Resource Ownership Decisions:');
|
|
82
|
+
console.log(` Key: ${decisions.key.ownership} - ${decisions.key.reason}`);
|
|
83
|
+
|
|
84
|
+
// Build resources based on ownership decisions
|
|
85
|
+
await this.buildFromDecisions(decisions, appDefinition, discoveredResources, result);
|
|
86
|
+
|
|
87
|
+
// Add IAM permissions for Lambda role
|
|
88
|
+
result.iamStatements.push({
|
|
89
|
+
Effect: 'Allow',
|
|
90
|
+
Action: ['kms:GenerateDataKey', 'kms:Decrypt', 'kms:Encrypt', 'kms:DescribeKey'],
|
|
91
|
+
Resource: result.environment.KMS_KEY_ARN,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
console.log(`[${this.name}] ✅ KMS configuration completed`);
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Convert flat discovery to structured discovery
|
|
100
|
+
* Provides backwards compatibility for tests
|
|
101
|
+
*/
|
|
102
|
+
convertFlatDiscoveryToStructured(flatDiscovery, appDefinition = {}) {
|
|
103
|
+
const discovery = createEmptyDiscoveryResult();
|
|
104
|
+
|
|
105
|
+
if (!flatDiscovery) {
|
|
106
|
+
return discovery;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check if resources are from CloudFormation stack
|
|
110
|
+
const isManagedIsolated = appDefinition.managementMode === 'managed' &&
|
|
111
|
+
(appDefinition.vpcIsolation === 'isolated' || !appDefinition.vpcIsolation);
|
|
112
|
+
const hasExistingStackResources = isManagedIsolated && flatDiscovery.defaultKmsKeyId &&
|
|
113
|
+
typeof flatDiscovery.defaultKmsKeyId === 'string';
|
|
114
|
+
|
|
115
|
+
if (flatDiscovery.fromCloudFormationStack || hasExistingStackResources) {
|
|
116
|
+
discovery.fromCloudFormation = true;
|
|
117
|
+
discovery.stackName = flatDiscovery.stackName || 'assumed-stack';
|
|
118
|
+
|
|
119
|
+
// Add stack-managed resources
|
|
120
|
+
let existingLogicalIds = flatDiscovery.existingLogicalIds || [];
|
|
121
|
+
|
|
122
|
+
// Infer logical IDs from physical IDs if needed
|
|
123
|
+
if (hasExistingStackResources && existingLogicalIds.length === 0) {
|
|
124
|
+
if (flatDiscovery.defaultKmsKeyId) {
|
|
125
|
+
existingLogicalIds.push('FriggKMSKey');
|
|
126
|
+
existingLogicalIds.push('FriggKMSKeyAlias');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
existingLogicalIds.forEach(logicalId => {
|
|
131
|
+
let resourceType = '';
|
|
132
|
+
let physicalId = '';
|
|
133
|
+
|
|
134
|
+
if (logicalId === 'FriggKMSKey') {
|
|
135
|
+
resourceType = 'AWS::KMS::Key';
|
|
136
|
+
physicalId = flatDiscovery.defaultKmsKeyId;
|
|
137
|
+
} else if (logicalId === 'FriggKMSKeyAlias') {
|
|
138
|
+
resourceType = 'AWS::KMS::Alias';
|
|
139
|
+
// Extract alias name from KMS key ARN or use default pattern
|
|
140
|
+
const stackName = flatDiscovery.stackName || 'unknown';
|
|
141
|
+
const stage = appDefinition.stage || 'dev';
|
|
142
|
+
physicalId = `alias/${stackName.replace(`-${stage}`, '')}-${stage}-frigg-kms`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (physicalId && typeof physicalId === 'string') {
|
|
146
|
+
discovery.stackManaged.push({
|
|
147
|
+
logicalId,
|
|
148
|
+
physicalId,
|
|
149
|
+
resourceType
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
} else {
|
|
154
|
+
// Resources discovered from AWS API (external)
|
|
155
|
+
if (flatDiscovery.defaultKmsKeyId && typeof flatDiscovery.defaultKmsKeyId === 'string') {
|
|
156
|
+
discovery.external.push({
|
|
157
|
+
physicalId: flatDiscovery.defaultKmsKeyId,
|
|
158
|
+
resourceType: 'AWS::KMS::Key',
|
|
159
|
+
source: 'aws-discovery'
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return discovery;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Translate legacy configuration to ownership-based configuration
|
|
169
|
+
* Provides backwards compatibility
|
|
170
|
+
*/
|
|
171
|
+
translateLegacyConfig(appDefinition, discoveredResources) {
|
|
172
|
+
// If already using ownership schema, return as-is
|
|
173
|
+
if (appDefinition.encryption?.ownership) {
|
|
174
|
+
return appDefinition;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const translated = JSON.parse(JSON.stringify(appDefinition));
|
|
178
|
+
|
|
179
|
+
// Initialize ownership sections
|
|
180
|
+
if (!translated.encryption) translated.encryption = {};
|
|
181
|
+
if (!translated.encryption.ownership) {
|
|
182
|
+
translated.encryption.ownership = {};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Handle top-level managementMode
|
|
186
|
+
const globalMode = appDefinition.managementMode || 'discover';
|
|
187
|
+
const vpcIsolation = appDefinition.vpcIsolation || 'shared';
|
|
188
|
+
|
|
189
|
+
if (globalMode === 'managed') {
|
|
190
|
+
if (appDefinition.encryption?.createResourceIfNoneFound !== undefined) {
|
|
191
|
+
console.log(` ⚠️ managementMode='managed' ignoring: encryption.createResourceIfNoneFound`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (vpcIsolation === 'isolated') {
|
|
195
|
+
const hasStackKms = discoveredResources?.defaultKmsKeyId &&
|
|
196
|
+
typeof discoveredResources.defaultKmsKeyId === 'string';
|
|
197
|
+
|
|
198
|
+
if (hasStackKms) {
|
|
199
|
+
translated.encryption.ownership.key = 'auto';
|
|
200
|
+
console.log(` managementMode='managed' + vpcIsolation='isolated' → stack has KMS, reusing`);
|
|
201
|
+
} else {
|
|
202
|
+
translated.encryption.ownership.key = 'stack';
|
|
203
|
+
console.log(` managementMode='managed' + vpcIsolation='isolated' → no stack KMS, creating new`);
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
translated.encryption.ownership.key = 'auto';
|
|
207
|
+
console.log(` managementMode='managed' + vpcIsolation='shared' → discovering KMS`);
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
// Handle legacy createResourceIfNoneFound
|
|
211
|
+
const createIfNoneFound = appDefinition.encryption?.createResourceIfNoneFound;
|
|
212
|
+
if (createIfNoneFound === true) {
|
|
213
|
+
translated.encryption.ownership.key = 'stack';
|
|
214
|
+
} else if (createIfNoneFound === false || createIfNoneFound === undefined) {
|
|
215
|
+
// When createResourceIfNoneFound is false or not specified:
|
|
216
|
+
// - If KMS found → use it (auto)
|
|
217
|
+
// - If not found → use environment variable (external)
|
|
218
|
+
// We use 'auto' here; the resolver will decide based on discovery
|
|
219
|
+
// But we need special handling in buildFromDecisions for the env var fallback
|
|
220
|
+
translated.encryption.ownership.key = 'auto';
|
|
221
|
+
translated.encryption._useEnvVarFallback = true; // Flag for env var fallback
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return translated;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Build all KMS resources based on ownership decisions
|
|
230
|
+
*/
|
|
231
|
+
async buildFromDecisions(decisions, appDefinition, discoveredResources, result) {
|
|
232
|
+
// Check for environment variable fallback flag (legacy behavior)
|
|
233
|
+
const useEnvVarFallback = appDefinition.encryption?._useEnvVarFallback;
|
|
234
|
+
|
|
235
|
+
// CRITICAL FIX: Check if KMS key exists OUTSIDE of stack (orphaned resource)
|
|
236
|
+
// If key exists but not in stack, we should use it as EXTERNAL, not try to create it
|
|
237
|
+
const externalKmsKey = discoveredResources?.defaultKmsKeyId ||
|
|
238
|
+
discoveredResources?.kmsKeyArn ||
|
|
239
|
+
discoveredResources?.kmsKeyId;
|
|
240
|
+
|
|
241
|
+
if (decisions.key.ownership === ResourceOwnership.STACK && decisions.key.physicalId) {
|
|
242
|
+
// Key exists in stack - add definitions (CloudFormation idempotency)
|
|
243
|
+
console.log(' → Adding KMS definitions to template (existing in stack)');
|
|
244
|
+
result.resources = this.createKmsKey(appDefinition);
|
|
245
|
+
result.environment.KMS_KEY_ARN = { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] };
|
|
246
|
+
console.log(' ✅ KMS key resources created');
|
|
247
|
+
} else if (decisions.key.ownership === ResourceOwnership.STACK && !decisions.key.physicalId && externalKmsKey) {
|
|
248
|
+
// ORPHANED KEY FIX: Key exists externally but not in stack
|
|
249
|
+
// Use it as external instead of trying to create (would fail with "already exists")
|
|
250
|
+
console.log(' ⚠️ KMS key exists externally but not in stack - using as external resource');
|
|
251
|
+
console.log(` → Using external KMS key: ${externalKmsKey}`);
|
|
252
|
+
|
|
253
|
+
// Format as ARN if it's just a key ID
|
|
254
|
+
const kmsArn = externalKmsKey.startsWith('arn:')
|
|
255
|
+
? externalKmsKey
|
|
256
|
+
: `arn:aws:kms:\${self:provider.region}:\${aws:accountId}:key/${externalKmsKey}`;
|
|
257
|
+
|
|
258
|
+
result.environment.KMS_KEY_ARN = kmsArn;
|
|
259
|
+
} else if (decisions.key.ownership === ResourceOwnership.STACK && !decisions.key.physicalId && !useEnvVarFallback) {
|
|
260
|
+
// Create new KMS key (only if not using env var fallback and no external key found)
|
|
261
|
+
console.log(' → Creating new KMS key in stack');
|
|
262
|
+
result.resources = this.createKmsKey(appDefinition);
|
|
263
|
+
result.environment.KMS_KEY_ARN = { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] };
|
|
264
|
+
console.log(' ✅ KMS key resources created');
|
|
265
|
+
} else if (decisions.key.ownership === ResourceOwnership.STACK && !decisions.key.physicalId && useEnvVarFallback) {
|
|
266
|
+
// Legacy behavior: fallback to environment variable when createResourceIfNoneFound=false/undefined
|
|
267
|
+
const createIfNoneFound = discoveredResources.defaultKmsKeyId ? true : appDefinition.encryption?.createResourceIfNoneFound;
|
|
268
|
+
const formatAsArn = createIfNoneFound === undefined; // Format as ARN when not specified
|
|
269
|
+
|
|
270
|
+
if (formatAsArn) {
|
|
271
|
+
console.log(' → Using environment variable for KMS key (formatted as ARN)');
|
|
272
|
+
result.environment.KMS_KEY_ARN = 'arn:aws:kms:${self:provider.region}:${aws:accountId}:key/${env:AWS_DISCOVERY_KMS_KEY_ID}';
|
|
273
|
+
} else {
|
|
274
|
+
console.log(' → Using environment variable for KMS key');
|
|
275
|
+
result.environment.KMS_KEY_ARN = '${env:AWS_DISCOVERY_KMS_KEY_ID}';
|
|
276
|
+
}
|
|
277
|
+
} else if (decisions.key.ownership === ResourceOwnership.EXTERNAL) {
|
|
278
|
+
// Use discovered KMS key
|
|
279
|
+
const kmsKeyId = decisions.key.physicalId || '${env:AWS_DISCOVERY_KMS_KEY_ID}';
|
|
280
|
+
console.log(` → Using ${decisions.key.physicalId ? 'discovered' : 'environment variable'} KMS key`);
|
|
281
|
+
|
|
282
|
+
// Format as ARN if it's just a key ID (for IAM policies)
|
|
283
|
+
const kmsArn = kmsKeyId.startsWith('arn:')
|
|
284
|
+
? kmsKeyId
|
|
285
|
+
: `arn:aws:kms:\${self:provider.region}:\${aws:accountId}:key/${kmsKeyId}`;
|
|
286
|
+
|
|
287
|
+
result.environment.KMS_KEY_ARN = kmsArn;
|
|
288
|
+
} else {
|
|
289
|
+
// Fallback
|
|
290
|
+
console.log(' → Using environment variable for KMS key');
|
|
291
|
+
result.environment.KMS_KEY_ARN = 'arn:aws:kms:${self:provider.region}:${aws:accountId}:key/${env:AWS_DISCOVERY_KMS_KEY_ID}';
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Create KMS key CloudFormation resources
|
|
297
|
+
*/
|
|
298
|
+
createKmsKey(appDefinition) {
|
|
299
|
+
return {
|
|
300
|
+
FriggKMSKey: {
|
|
301
|
+
Type: 'AWS::KMS::Key',
|
|
302
|
+
DeletionPolicy: 'Retain',
|
|
303
|
+
UpdateReplacePolicy: 'Retain',
|
|
304
|
+
Properties: {
|
|
305
|
+
Description: 'Frigg Field-Level Encryption Key for ${self:service}-${self:provider.stage}',
|
|
306
|
+
EnableKeyRotation: true,
|
|
307
|
+
KeyPolicy: {
|
|
308
|
+
Version: '2012-10-17',
|
|
309
|
+
Id: 'key-policy-1',
|
|
310
|
+
Statement: [
|
|
311
|
+
{
|
|
312
|
+
Sid: 'AllowRootAccountAdmin',
|
|
313
|
+
Effect: 'Allow',
|
|
314
|
+
Principal: {
|
|
315
|
+
AWS: {
|
|
316
|
+
'Fn::Sub': 'arn:aws:iam::${AWS::AccountId}:root',
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
Action: 'kms:*',
|
|
320
|
+
Resource: '*',
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
Sid: 'AllowLambdaService',
|
|
324
|
+
Effect: 'Allow',
|
|
325
|
+
Principal: {
|
|
326
|
+
Service: 'lambda.amazonaws.com',
|
|
327
|
+
},
|
|
328
|
+
Action: [
|
|
329
|
+
'kms:Decrypt',
|
|
330
|
+
'kms:GenerateDataKey',
|
|
331
|
+
'kms:CreateGrant',
|
|
332
|
+
],
|
|
333
|
+
Resource: '*',
|
|
334
|
+
Condition: {
|
|
335
|
+
StringEquals: {
|
|
336
|
+
'kms:ViaService': 'lambda.${self:provider.region}.amazonaws.com',
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
// NOTE: We do NOT add a statement referencing IamRoleLambdaExecution here
|
|
341
|
+
// because it creates a circular dependency (KMS Key → IAM Role → KMS Key).
|
|
342
|
+
// Instead, IAM policies grant the Lambda execution role permissions to use KMS.
|
|
343
|
+
],
|
|
344
|
+
},
|
|
345
|
+
Tags: [
|
|
346
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-kms' },
|
|
347
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
348
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
349
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
350
|
+
],
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
FriggKMSKeyAlias: {
|
|
354
|
+
Type: 'AWS::KMS::Alias',
|
|
355
|
+
DeletionPolicy: 'Retain',
|
|
356
|
+
Properties: {
|
|
357
|
+
AliasName: 'alias/${self:service}-${self:provider.stage}-frigg-kms',
|
|
358
|
+
TargetKeyId: { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] },
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
module.exports = { KmsBuilder };
|
|
366
|
+
|