@fjall/components-infrastructure 2.22.1 → 2.23.1
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/dist/lib/patterns/aws/compute.js +10 -2
- package/dist/lib/patterns/aws/computeEcs.js +8 -2
- package/dist/lib/resources/aws/compute/ecsTaskDefinition.js +18 -2
- package/dist/lib/resources/aws/compute/ecsValidation.d.ts +7 -0
- package/dist/lib/resources/aws/compute/ecsValidation.js +16 -3
- package/dist/lib/resources/aws/compute/lambda.js +5 -1
- package/package.json +4 -4
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Token } from "aws-cdk-lib";
|
|
2
2
|
import { Runtime } from "aws-cdk-lib/aws-lambda";
|
|
3
|
+
import { buildParameterPath } from "@fjall/util";
|
|
3
4
|
import { isCompute, isEcsCompute, isLambdaCompute, isEc2Compute } from "./interfaces/compute.js";
|
|
4
5
|
import { warnIfPropertiesIgnored } from "../../utils/validationLogger.js";
|
|
5
6
|
import { applyInferredPublicBuildArgs } from "./computeBuildArgInference.js";
|
|
@@ -205,7 +206,11 @@ export class ComputeFactory {
|
|
|
205
206
|
// Include ssmSecretsPath (explicit or derived)
|
|
206
207
|
manifestService.ssmSecretsPath =
|
|
207
208
|
service.ssmSecretsPath ??
|
|
208
|
-
|
|
209
|
+
buildParameterPath({
|
|
210
|
+
app: appName,
|
|
211
|
+
cluster: clusterName,
|
|
212
|
+
service: service.name
|
|
213
|
+
});
|
|
209
214
|
collector.addService(manifestService);
|
|
210
215
|
}
|
|
211
216
|
return new EcsCompute(scope, id, computeProps);
|
|
@@ -223,7 +228,10 @@ export class ComputeFactory {
|
|
|
223
228
|
const manifestLambda = {
|
|
224
229
|
name: id,
|
|
225
230
|
ssmSecretsPath: lambdaComputeProps.ssmSecretsPath ||
|
|
226
|
-
|
|
231
|
+
buildParameterPath({
|
|
232
|
+
app: lambdaComputeProps.appName,
|
|
233
|
+
lambda: id
|
|
234
|
+
})
|
|
227
235
|
};
|
|
228
236
|
if (lambdaComputeProps.secrets &&
|
|
229
237
|
lambdaComputeProps.secrets.length > 0) {
|
|
@@ -23,7 +23,8 @@ import { toPascalCase } from "../../utils/capitaliseString.js";
|
|
|
23
23
|
import { FjallLogger } from "../../utils/validationLogger.js";
|
|
24
24
|
import { VALIDATION_PATTERNS } from "@fjall/generator";
|
|
25
25
|
import { evaluateBakeGuard } from "@fjall/util/docker";
|
|
26
|
-
import { toKebab } from "@fjall/util";
|
|
26
|
+
import { toKebab, buildParameterPath } from "@fjall/util";
|
|
27
|
+
import { validateSecretName } from "../../resources/aws/compute/ecsValidation.js";
|
|
27
28
|
import { COMPUTE_DEFAULTS, collectImportedSecretNames } from "./compute.js";
|
|
28
29
|
import { isHookMigrations } from "./computeEcsTypes.js";
|
|
29
30
|
export { ScalingType } from "./computeEcsTypes.js";
|
|
@@ -1228,9 +1229,14 @@ export class EcsCompute extends Construct {
|
|
|
1228
1229
|
`is set and EcsComputeProps.appName is missing — set one to enable ` +
|
|
1229
1230
|
`SSM path derivation (/<appName>/<clusterName>/<serviceName>).`);
|
|
1230
1231
|
}
|
|
1231
|
-
ssmPath =
|
|
1232
|
+
ssmPath = buildParameterPath({
|
|
1233
|
+
app: this.appName,
|
|
1234
|
+
cluster: this.clusterId,
|
|
1235
|
+
service: svcConfig.name
|
|
1236
|
+
});
|
|
1232
1237
|
}
|
|
1233
1238
|
for (const secretName of migrations.secrets) {
|
|
1239
|
+
validateSecretName(secretName, `migrations of service '${svcConfig.name}'`);
|
|
1234
1240
|
const param = StringParameter.fromSecureStringParameterAttributes(this, `${idPrefix}${secretName}SsmParam`, { parameterName: `${ssmPath}/${secretName}` });
|
|
1235
1241
|
secretsResolved[secretName] = EcsSecret.fromSsmParameter(param);
|
|
1236
1242
|
}
|
|
@@ -2,8 +2,9 @@ import { AwsLogDriver, ContainerDependencyCondition, FargateTaskDefinition, Ec2T
|
|
|
2
2
|
import { Duration, RemovalPolicy } from "aws-cdk-lib";
|
|
3
3
|
import { Secret as EcsSecret } from "aws-cdk-lib/aws-ecs";
|
|
4
4
|
import { StringParameter } from "aws-cdk-lib/aws-ssm";
|
|
5
|
+
import { buildParameterPath } from "@fjall/util";
|
|
5
6
|
import { resolveOrgId } from "../../../utils/cdkContext.js";
|
|
6
|
-
import { validateSsmPathComponent } from "./ecsValidation.js";
|
|
7
|
+
import { validateSsmPathComponent, validateSecretName } from "./ecsValidation.js";
|
|
7
8
|
import { DEFAULT_LOG_RETENTION, DEFAULT_FARGATE_CPU, DEFAULT_FARGATE_MEMORY_MIB, DEFAULT_EC2_CONTAINER_MEMORY_MIB } from "./ecsConstants.js";
|
|
8
9
|
import { LogGroup } from "../logging/logGroup.js";
|
|
9
10
|
import { getContainerImage } from "./ecsImages.js";
|
|
@@ -46,7 +47,21 @@ export function deriveSsmSecretsPath(props, serviceName, explicitPath) {
|
|
|
46
47
|
validateSsmPathComponent(appName, "appName");
|
|
47
48
|
validateSsmPathComponent(props.clusterName, "clusterName");
|
|
48
49
|
validateSsmPathComponent(serviceName, "serviceName");
|
|
49
|
-
|
|
50
|
+
// "lambda" is the lambda-scope path marker: /<app>/lambda/<x> decodes as a
|
|
51
|
+
// lambda namespace everywhere (SecretNamespaceSchema reserves the name), so
|
|
52
|
+
// a cluster called "lambda" would derive paths the secrets tooling cannot
|
|
53
|
+
// address as ECS scopes.
|
|
54
|
+
if (props.clusterName === "lambda") {
|
|
55
|
+
throw new Error(`Cluster name "lambda" is reserved for lambda-scoped parameter paths. ` +
|
|
56
|
+
`The derived secrets path /${appName}/lambda/${serviceName} would be ` +
|
|
57
|
+
`read as a lambda namespace. Rename the cluster, or set ssmSecretsPath ` +
|
|
58
|
+
`explicitly on service '${serviceName}'.`);
|
|
59
|
+
}
|
|
60
|
+
return buildParameterPath({
|
|
61
|
+
app: appName,
|
|
62
|
+
cluster: props.clusterName,
|
|
63
|
+
service: serviceName
|
|
64
|
+
});
|
|
50
65
|
}
|
|
51
66
|
export function createTaskDefinition(ctx, serviceName, serviceProps, executionRole, taskRole) {
|
|
52
67
|
const cpu = serviceProps.cpu ?? DEFAULT_FARGATE_CPU;
|
|
@@ -155,6 +170,7 @@ export function addContainersToTask(ctx, serviceName, serviceProps, taskDefiniti
|
|
|
155
170
|
}
|
|
156
171
|
const ssmSecretsPath = deriveSsmSecretsPath(ctx.props, serviceName, serviceProps.ssmSecretsPath);
|
|
157
172
|
for (const secretName of containerConfig.secrets) {
|
|
173
|
+
validateSecretName(secretName, `container '${containerConfig.name}' of service '${serviceName}'`);
|
|
158
174
|
const paramPath = `${ssmSecretsPath}/${secretName}`;
|
|
159
175
|
const param = StringParameter.fromSecureStringParameterAttributes(ctx.scope, `${ctx.props.clusterName}${serviceName}${containerConfig.name}${secretName}SsmParam`, { parameterName: paramPath });
|
|
160
176
|
secrets[secretName] = EcsSecret.fromSsmParameter(param);
|
|
@@ -32,3 +32,10 @@ export declare function validateEcsClusterProps(props: EcsClusterProps): void;
|
|
|
32
32
|
* @throws Error if the component is invalid
|
|
33
33
|
*/
|
|
34
34
|
export declare function validateSsmPathComponent(component: string, fieldName: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* Validates a secret name wired into a container or migration task.
|
|
37
|
+
* The construct is the last validation line for direct-CDK consumers: a name
|
|
38
|
+
* failing SECRET_NAME_PATTERN (slash, digit-leading) synthesises a parameter
|
|
39
|
+
* reference the fjall secrets tooling can never write.
|
|
40
|
+
*/
|
|
41
|
+
export declare function validateSecretName(name: string, context: string): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NetworkMode } from "aws-cdk-lib/aws-ecs";
|
|
2
2
|
import { evaluateBakeGuard } from "@fjall/util/docker";
|
|
3
|
-
import { toKebab } from "@fjall/util";
|
|
3
|
+
import { toKebab, SSM_COMPONENT_PATTERN, SSM_COMPONENT_ERROR, SECRET_NAME_PATTERN, SECRET_NAME_ERROR } from "@fjall/util";
|
|
4
4
|
import { ScalingType } from "./ecsTypes.js";
|
|
5
5
|
/**
|
|
6
6
|
* Validates ECS cluster props before construction.
|
|
@@ -185,8 +185,10 @@ export function validateSsmPathComponent(component, fieldName) {
|
|
|
185
185
|
if (!component || component.trim() === "") {
|
|
186
186
|
throw new Error(`${fieldName} cannot be empty for SSM path derivation`);
|
|
187
187
|
}
|
|
188
|
-
if (
|
|
189
|
-
throw new Error(`${fieldName}
|
|
188
|
+
if (!SSM_COMPONENT_PATTERN.test(component)) {
|
|
189
|
+
throw new Error(`${fieldName} is not a valid SSM path component. ${SSM_COMPONENT_ERROR}. ` +
|
|
190
|
+
`Invalid value: "${component}". A path derived from it could not be ` +
|
|
191
|
+
`managed through fjall secrets — rename it, or set ssmSecretsPath explicitly.`);
|
|
190
192
|
}
|
|
191
193
|
// SSM parameter name hierarchy labels have a max length of 2048, but we use a more
|
|
192
194
|
// reasonable limit since each component is just one part of the path
|
|
@@ -194,3 +196,14 @@ export function validateSsmPathComponent(component, fieldName) {
|
|
|
194
196
|
throw new Error(`${fieldName} exceeds maximum length (128 characters).`);
|
|
195
197
|
}
|
|
196
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Validates a secret name wired into a container or migration task.
|
|
201
|
+
* The construct is the last validation line for direct-CDK consumers: a name
|
|
202
|
+
* failing SECRET_NAME_PATTERN (slash, digit-leading) synthesises a parameter
|
|
203
|
+
* reference the fjall secrets tooling can never write.
|
|
204
|
+
*/
|
|
205
|
+
export function validateSecretName(name, context) {
|
|
206
|
+
if (!SECRET_NAME_PATTERN.test(name)) {
|
|
207
|
+
throw new Error(`Secret name "${name}" in ${context} is invalid. ${SECRET_NAME_ERROR}.`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -10,7 +10,9 @@ import { PolicyStatement, Effect } from "aws-cdk-lib/aws-iam";
|
|
|
10
10
|
import { RetentionDays } from "aws-cdk-lib/aws-logs";
|
|
11
11
|
import { LogGroup } from "../logging/logGroup.js";
|
|
12
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
import { buildParameterPath } from "@fjall/util";
|
|
13
14
|
import { resolveImportedSecret } from "../secrets/index.js";
|
|
15
|
+
import { validateSsmPathComponent } from "./ecsValidation.js";
|
|
14
16
|
import { toPascalCase } from "../../../utils/capitaliseString.js";
|
|
15
17
|
import { resolvePrivateSubnetType } from "../../../utils/vpcUtils.js";
|
|
16
18
|
import { createLambdaAlarms } from "../monitoring/index.js";
|
|
@@ -369,6 +371,8 @@ export class LambdaFunction extends Function {
|
|
|
369
371
|
"and appName/functionName are not available. " +
|
|
370
372
|
"Either set ssmSecretsPath explicitly, or ensure App.getName() returns a value.");
|
|
371
373
|
}
|
|
372
|
-
|
|
374
|
+
validateSsmPathComponent(appName, "appName");
|
|
375
|
+
validateSsmPathComponent(functionName, "functionName");
|
|
376
|
+
return buildParameterPath({ app: appName, lambda: functionName });
|
|
373
377
|
}
|
|
374
378
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/components-infrastructure",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.23.1",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@aws-sdk/client-organizations": "^3.1038.0",
|
|
66
|
-
"@fjall/generator": "^2.
|
|
67
|
-
"@fjall/util": "^2.
|
|
66
|
+
"@fjall/generator": "^2.23.1",
|
|
67
|
+
"@fjall/util": "^2.23.1",
|
|
68
68
|
"constructs": "^10.6.0"
|
|
69
69
|
},
|
|
70
70
|
"overrides": {
|
|
@@ -78,5 +78,5 @@
|
|
|
78
78
|
"engines": {
|
|
79
79
|
"node": ">=18.0.0"
|
|
80
80
|
},
|
|
81
|
-
"gitHead": "
|
|
81
|
+
"gitHead": "749133c6c5cfe24a09a401869f2193c7d177a324"
|
|
82
82
|
}
|