@intentius/chant-lexicon-aws 0.18.12 → 0.18.13
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/composites/agentcore-agent.d.ts +74 -0
- package/dist/composites/agentcore-agent.d.ts.map +1 -0
- package/dist/composites/agentcore-trust-policy.d.ts +11 -0
- package/dist/composites/agentcore-trust-policy.d.ts.map +1 -0
- package/dist/composites/index.d.ts +4 -0
- package/dist/composites/index.d.ts.map +1 -1
- package/dist/composites/microvm-app.d.ts +116 -0
- package/dist/composites/microvm-app.d.ts.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +22 -4
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/cf-refs.d.ts +14 -0
- package/dist/lint/post-synth/cf-refs.d.ts.map +1 -1
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/waw038.d.ts +12 -0
- package/dist/lint/post-synth/waw038.d.ts.map +1 -0
- package/dist/lint/post-synth/waw039.d.ts +11 -0
- package/dist/lint/post-synth/waw039.d.ts.map +1 -0
- package/dist/lint/post-synth/waw040.d.ts +13 -0
- package/dist/lint/post-synth/waw040.d.ts.map +1 -0
- package/dist/lint/post-synth/waw041.d.ts +10 -0
- package/dist/lint/post-synth/waw041.d.ts.map +1 -0
- package/dist/lint/post-synth/waw042.d.ts +12 -0
- package/dist/lint/post-synth/waw042.d.ts.map +1 -0
- package/dist/lint/post-synth/waw043.d.ts +11 -0
- package/dist/lint/post-synth/waw043.d.ts.map +1 -0
- package/dist/lint/post-synth/waw044.d.ts +18 -0
- package/dist/lint/post-synth/waw044.d.ts.map +1 -0
- package/dist/lint/post-synth/waw045.d.ts +11 -0
- package/dist/lint/post-synth/waw045.d.ts.map +1 -0
- package/dist/lint/post-synth/waw046.d.ts +12 -0
- package/dist/lint/post-synth/waw046.d.ts.map +1 -0
- package/dist/lint/post-synth/waw047.d.ts +11 -0
- package/dist/lint/post-synth/waw047.d.ts.map +1 -0
- package/dist/lint/post-synth/waw048.d.ts +11 -0
- package/dist/lint/post-synth/waw048.d.ts.map +1 -0
- package/dist/lint/post-synth/waw049.d.ts +21 -0
- package/dist/lint/post-synth/waw049.d.ts.map +1 -0
- package/dist/lint/post-synth/waw050.d.ts +11 -0
- package/dist/lint/post-synth/waw050.d.ts.map +1 -0
- package/dist/lint/post-synth/waw051.d.ts +11 -0
- package/dist/lint/post-synth/waw051.d.ts.map +1 -0
- package/dist/lint/post-synth/waw052.d.ts +12 -0
- package/dist/lint/post-synth/waw052.d.ts.map +1 -0
- package/dist/lint/post-synth/waw053.d.ts +10 -0
- package/dist/lint/post-synth/waw053.d.ts.map +1 -0
- package/dist/lint/post-synth/waw054.d.ts +11 -0
- package/dist/lint/post-synth/waw054.d.ts.map +1 -0
- package/dist/lint/post-synth/waw055.d.ts +11 -0
- package/dist/lint/post-synth/waw055.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/op/activities/floci.d.ts +6 -0
- package/dist/op/activities/floci.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/rules/cf-refs.ts +23 -0
- package/dist/rules/waw038.ts +50 -0
- package/dist/rules/waw039.ts +51 -0
- package/dist/rules/waw040.ts +54 -0
- package/dist/rules/waw041.ts +48 -0
- package/dist/rules/waw042.ts +87 -0
- package/dist/rules/waw043.ts +57 -0
- package/dist/rules/waw044.ts +68 -0
- package/dist/rules/waw045.ts +58 -0
- package/dist/rules/waw046.ts +59 -0
- package/dist/rules/waw047.ts +50 -0
- package/dist/rules/waw048.ts +47 -0
- package/dist/rules/waw049.ts +100 -0
- package/dist/rules/waw050.ts +54 -0
- package/dist/rules/waw051.ts +49 -0
- package/dist/rules/waw052.ts +51 -0
- package/dist/rules/waw053.ts +53 -0
- package/dist/rules/waw054.ts +49 -0
- package/dist/rules/waw055.ts +49 -0
- package/dist/types/index.d.ts +11 -9
- package/package.json +2 -2
- package/src/composites/agentcore-agent.test.ts +188 -0
- package/src/composites/agentcore-agent.ts +195 -0
- package/src/composites/agentcore-trust-policy.ts +13 -0
- package/src/composites/index.ts +4 -0
- package/src/composites/microvm-app.test.ts +234 -0
- package/src/composites/microvm-app.ts +436 -0
- package/src/generated/index.d.ts +11 -9
- package/src/index.ts +2 -0
- package/src/lint/audit-catalog.ts +20 -0
- package/src/lint/post-synth/cf-refs.ts +23 -0
- package/src/lint/post-synth/index.ts +36 -0
- package/src/lint/post-synth/waw038.test.ts +78 -0
- package/src/lint/post-synth/waw038.ts +50 -0
- package/src/lint/post-synth/waw039.test.ts +72 -0
- package/src/lint/post-synth/waw039.ts +51 -0
- package/src/lint/post-synth/waw040.test.ts +71 -0
- package/src/lint/post-synth/waw040.ts +54 -0
- package/src/lint/post-synth/waw041.test.ts +72 -0
- package/src/lint/post-synth/waw041.ts +48 -0
- package/src/lint/post-synth/waw042.test.ts +138 -0
- package/src/lint/post-synth/waw042.ts +87 -0
- package/src/lint/post-synth/waw043.test.ts +84 -0
- package/src/lint/post-synth/waw043.ts +57 -0
- package/src/lint/post-synth/waw044.test.ts +112 -0
- package/src/lint/post-synth/waw044.ts +68 -0
- package/src/lint/post-synth/waw045.test.ts +101 -0
- package/src/lint/post-synth/waw045.ts +58 -0
- package/src/lint/post-synth/waw046.test.ts +112 -0
- package/src/lint/post-synth/waw046.ts +59 -0
- package/src/lint/post-synth/waw047.test.ts +64 -0
- package/src/lint/post-synth/waw047.ts +50 -0
- package/src/lint/post-synth/waw048.test.ts +77 -0
- package/src/lint/post-synth/waw048.ts +47 -0
- package/src/lint/post-synth/waw049.test.ts +139 -0
- package/src/lint/post-synth/waw049.ts +100 -0
- package/src/lint/post-synth/waw050.test.ts +75 -0
- package/src/lint/post-synth/waw050.ts +54 -0
- package/src/lint/post-synth/waw051.test.ts +78 -0
- package/src/lint/post-synth/waw051.ts +49 -0
- package/src/lint/post-synth/waw052.test.ts +54 -0
- package/src/lint/post-synth/waw052.ts +51 -0
- package/src/lint/post-synth/waw053.test.ts +59 -0
- package/src/lint/post-synth/waw053.ts +53 -0
- package/src/lint/post-synth/waw054.test.ts +49 -0
- package/src/lint/post-synth/waw054.ts +49 -0
- package/src/lint/post-synth/waw055.test.ts +43 -0
- package/src/lint/post-synth/waw055.ts +49 -0
- package/src/op/activities/floci.test.ts +26 -0
- package/src/op/activities/floci.ts +25 -9
- package/src/plugin.ts +3 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MicrovmApp — composite-first delivery of AWS Lambda MicroVM support (#879).
|
|
3
|
+
*
|
|
4
|
+
* Emits `AWS::Lambda::MicrovmImage` (a preview CFN resource type — see the
|
|
5
|
+
* issue's "Preview-API gate") plus the least-privilege build/execution IAM
|
|
6
|
+
* roles the upstream `ran-isenberg/lambda-microvm-cdk-python` construct
|
|
7
|
+
* creates, and — only when build-time VPC egress is requested — an
|
|
8
|
+
* `AWS::Lambda::NetworkConnector`, its deny-all-egress security group, and its
|
|
9
|
+
* ENI operator role.
|
|
10
|
+
*
|
|
11
|
+
* Base path only (#879 scope): `chant build` serializes this composite to a
|
|
12
|
+
* CloudFormation template; `cfn-deploy` + `wait-for-stack` deploy it and wait
|
|
13
|
+
* for the image snapshot to build, with no bespoke verb. The
|
|
14
|
+
* `microvm-image-build` capability (build-once / promote-by-ARN) is deferred
|
|
15
|
+
* — see the issue's "Settled decisions".
|
|
16
|
+
*
|
|
17
|
+
* The IAM shapes below mirror `lambda_microvm_cdk._impl.security` (build role
|
|
18
|
+
* scoped to the exact S3 code-artifact object + the image's own log group;
|
|
19
|
+
* execution role scoped to runtime logs only; connector operator role scoped
|
|
20
|
+
* to the AWS-documented `ec2:CreateNetworkInterface`/`CreateTags` grant), and
|
|
21
|
+
* the deny-all security group reproduces `aws-cdk-lib`'s own
|
|
22
|
+
* `MATCH_NO_TRAFFIC` placeholder rule (`aws-ec2/lib/security-group.ts`) —
|
|
23
|
+
* spot-checked against both upstream sources so the shapes match what the
|
|
24
|
+
* MicroVM service actually expects, not a guess.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
28
|
+
import {
|
|
29
|
+
MicrovmImage,
|
|
30
|
+
MicrovmImage_CloudWatchLogging,
|
|
31
|
+
MicrovmImage_CodeArtifact,
|
|
32
|
+
MicrovmImage_CpuConfiguration,
|
|
33
|
+
MicrovmImage_EnvironmentVariable,
|
|
34
|
+
MicrovmImage_Hooks,
|
|
35
|
+
MicrovmImage_Logging,
|
|
36
|
+
MicrovmImage_Resources,
|
|
37
|
+
NetworkConnector,
|
|
38
|
+
NetworkConnector_Config,
|
|
39
|
+
NetworkConnector_VpcEgressConfiguration,
|
|
40
|
+
Role,
|
|
41
|
+
Role_Policy,
|
|
42
|
+
SecurityGroup,
|
|
43
|
+
SecurityGroup_Egress,
|
|
44
|
+
} from "../generated";
|
|
45
|
+
import { Sub } from "../intrinsics";
|
|
46
|
+
|
|
47
|
+
const MICROVM_SERVICE_PRINCIPAL = "lambda.amazonaws.com";
|
|
48
|
+
const CONNECTOR_MANAGED_RESOURCE_OPERATOR = "network-connectors.lambda.amazonaws.com";
|
|
49
|
+
|
|
50
|
+
/** Documented baseline memory tiers (MiB) — the CFN schema types it as an open int, but the service accepts only these five; vCPU auto-scales with the tier. */
|
|
51
|
+
const VALID_MEMORY_MIB = [512, 1024, 2048, 4096, 8192] as const;
|
|
52
|
+
type MicrovmMemoryMiB = (typeof VALID_MEMORY_MIB)[number];
|
|
53
|
+
|
|
54
|
+
const NAME_PATTERN = /^[a-zA-Z0-9-_]+$/;
|
|
55
|
+
const MAX_NAME_LENGTH = 64;
|
|
56
|
+
const RESERVED_ENV_KEYS = new Set(["AWS_REGION"]);
|
|
57
|
+
const MAX_ENVIRONMENT_VARIABLES = 50;
|
|
58
|
+
const MAX_EGRESS_CONNECTORS = 10;
|
|
59
|
+
const MIN_CONNECTOR_SUBNETS = 1;
|
|
60
|
+
const MAX_CONNECTOR_SUBNETS = 16;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* `sts:AssumeRole` + `sts:TagSession` trust for `lambda.amazonaws.com` — the
|
|
64
|
+
* MicroVM service assumes the build/execution roles with session tags
|
|
65
|
+
* (`lambda_microvm_cdk._impl.security._microvm_service_trust`).
|
|
66
|
+
*/
|
|
67
|
+
const microvmServiceTrustPolicy = {
|
|
68
|
+
Version: "2012-10-17" as const,
|
|
69
|
+
Statement: [
|
|
70
|
+
{ Effect: "Allow" as const, Principal: { Service: MICROVM_SERVICE_PRINCIPAL }, Action: "sts:AssumeRole" },
|
|
71
|
+
{ Effect: "Allow" as const, Principal: { Service: MICROVM_SERVICE_PRINCIPAL }, Action: "sts:TagSession" },
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** Plain `sts:AssumeRole` trust — the connector operator role's documented trust (no session tags). */
|
|
76
|
+
const lambdaTrustPolicy = {
|
|
77
|
+
Version: "2012-10-17" as const,
|
|
78
|
+
Statement: [
|
|
79
|
+
{ Effect: "Allow" as const, Principal: { Service: MICROVM_SERVICE_PRINCIPAL }, Action: "sts:AssumeRole" },
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* CDK's synthesized "deny all outbound" placeholder rule. A security group
|
|
85
|
+
* with no egress rules gets CloudFormation's allow-all-outbound default, so
|
|
86
|
+
* this rule (which matches no real traffic — `255.255.255.255/32` is
|
|
87
|
+
* unassignable) is added explicitly to override that default. Verbatim
|
|
88
|
+
* `MATCH_NO_TRAFFIC` from `aws-cdk-lib/aws-ec2/lib/security-group.ts`.
|
|
89
|
+
*/
|
|
90
|
+
const DENY_ALL_EGRESS = new SecurityGroup_Egress({
|
|
91
|
+
CidrIp: "255.255.255.255/32",
|
|
92
|
+
Description: "Disallow all traffic",
|
|
93
|
+
IpProtocol: "icmp",
|
|
94
|
+
FromPort: 252,
|
|
95
|
+
ToPort: 86,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
export interface MicrovmAppBuildConnectorProps {
|
|
99
|
+
/** VPC the connector's ENIs attach to. */
|
|
100
|
+
vpcId: string;
|
|
101
|
+
/** 1-16 subnet ids, all in `vpcId` — where Lambda provisions the connector's ENIs. */
|
|
102
|
+
subnetIds: string[];
|
|
103
|
+
/** `"IPv4"` (default) or `"DualStack"`. */
|
|
104
|
+
networkProtocol?: "IPv4" | "DualStack";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface MicrovmAppProps {
|
|
108
|
+
/** Unique image name (`^[a-zA-Z0-9-_]+$`, ≤64 chars). Create-only — changing it forces replacement. */
|
|
109
|
+
name: string;
|
|
110
|
+
/** Human-readable description. CFN requires the property; `""` is valid. */
|
|
111
|
+
description?: string;
|
|
112
|
+
/**
|
|
113
|
+
* The `CodeArtifact.Uri` value CloudFormation wants — e.g. the `uri` a
|
|
114
|
+
* `publish-artifact` step produced (`"@Publish.uri"`, wired in by the
|
|
115
|
+
* driver). Kept distinct from {@link codeArtifactObjectArn}: this is the
|
|
116
|
+
* value the MicroVM service resolves the artifact from, while that one is
|
|
117
|
+
* the resource ARN the build role's IAM grant is scoped to — the two only
|
|
118
|
+
* coincide by convention, not by type.
|
|
119
|
+
*/
|
|
120
|
+
codeArtifactUri: string;
|
|
121
|
+
/**
|
|
122
|
+
* Exact S3 object ARN of the code artifact, for the build role's
|
|
123
|
+
* `s3:GetObject` grant. Scoped to the precise object (never `bucket/*`),
|
|
124
|
+
* matching the upstream construct's `artifact_object_arn`. Pass the
|
|
125
|
+
* caller-resolved ARN rather than have this composite parse one out of an
|
|
126
|
+
* arbitrary URI — the same "wired reference, already resolved by the
|
|
127
|
+
* driver" convention `cfn-deploy`'s `imageRef` uses.
|
|
128
|
+
*/
|
|
129
|
+
codeArtifactObjectArn: string;
|
|
130
|
+
/** AWS-managed base image alias (e.g. `"al2023-1"`) or a full base-image ARN. */
|
|
131
|
+
baseImage: string;
|
|
132
|
+
/**
|
|
133
|
+
* Base image version to pin. Default: `"0"` — the fallback the upstream
|
|
134
|
+
* construct itself uses when a synth-time boto3 lookup isn't available.
|
|
135
|
+
* Chant's synth is deterministic/offline, so this composite never performs
|
|
136
|
+
* a live lookup; pin this explicitly for a specific managed version.
|
|
137
|
+
*/
|
|
138
|
+
baseImageVersion?: string;
|
|
139
|
+
/** Baseline memory tier in MiB — one of the five documented tiers; vCPU auto-scales with it. Default: `2048`. */
|
|
140
|
+
memoryMiB?: MicrovmMemoryMiB;
|
|
141
|
+
/** Env vars baked into the image at build time (max 50) — snapshotted and shared by every VM booted from this image. Never put secrets here. */
|
|
142
|
+
environment?: Record<string, string>;
|
|
143
|
+
/** Opt into `"ALL"` additional OS capabilities — a documented privilege escalation. Default: none (least privilege). */
|
|
144
|
+
additionalOsCapabilities?: "ALL"[];
|
|
145
|
+
/** Disable CloudWatch build/runtime logging entirely. Default: `false` (logging enabled, pinned to `/aws/lambda/microvms/<name>`). */
|
|
146
|
+
disableLogging?: boolean;
|
|
147
|
+
/** Lifecycle hooks — all default to disabled (traffic flows immediately). */
|
|
148
|
+
hooks?: ConstructorParameters<typeof MicrovmImage_Hooks>[0];
|
|
149
|
+
/**
|
|
150
|
+
* Build-time egress: attach a `NetworkConnector` (+ its deny-all-egress
|
|
151
|
+
* security group + least-privilege ENI operator role) so the image build
|
|
152
|
+
* can reach a VPC (private mirrors, an internal registry). Omit for the
|
|
153
|
+
* default public-internet build egress. The connector's security-group
|
|
154
|
+
* rules ARE its egress policy — deny-all by default; open it via
|
|
155
|
+
* `defaults.connectorSecurityGroup`.
|
|
156
|
+
*/
|
|
157
|
+
buildConnector?: MicrovmAppBuildConnectorProps;
|
|
158
|
+
/** Extra egress connector ARNs to bake in alongside the one this composite creates (e.g. an AWS-managed `INTERNET_EGRESS` alias, or a pre-existing shared connector). Max 10 total. */
|
|
159
|
+
additionalEgressConnectors?: string[];
|
|
160
|
+
defaults?: {
|
|
161
|
+
buildRole?: Partial<ConstructorParameters<typeof Role>[0]>;
|
|
162
|
+
executionRole?: Partial<ConstructorParameters<typeof Role>[0]>;
|
|
163
|
+
connectorOperatorRole?: Partial<ConstructorParameters<typeof Role>[0]>;
|
|
164
|
+
connectorSecurityGroup?: Partial<ConstructorParameters<typeof SecurityGroup>[0]>;
|
|
165
|
+
connector?: Partial<ConstructorParameters<typeof NetworkConnector>[0]>;
|
|
166
|
+
image?: Partial<ConstructorParameters<typeof MicrovmImage>[0]>;
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export type MicrovmAppResult = {
|
|
171
|
+
/** Least-privilege role the MicroVM service assumes to build the image — `s3:GetObject` on the exact code-artifact object, `logs:*` on the image's own log group. */
|
|
172
|
+
buildRole: InstanceType<typeof Role>;
|
|
173
|
+
/** Least-privilege role assumed by the running MicroVM — runtime-logs only; add workload permissions (e.g. Bedrock) on top. */
|
|
174
|
+
executionRole: InstanceType<typeof Role>;
|
|
175
|
+
/** The `AWS::Lambda::MicrovmImage` resource. */
|
|
176
|
+
image: InstanceType<typeof MicrovmImage>;
|
|
177
|
+
/** The build-egress connector's deny-all security group (only when `buildConnector` is set). */
|
|
178
|
+
connectorSecurityGroup?: InstanceType<typeof SecurityGroup>;
|
|
179
|
+
/** The connector's least-privilege ENI operator role (only when `buildConnector` is set). */
|
|
180
|
+
connectorOperatorRole?: InstanceType<typeof Role>;
|
|
181
|
+
/** The `AWS::Lambda::NetworkConnector` resource (only when `buildConnector` is set). */
|
|
182
|
+
connector?: InstanceType<typeof NetworkConnector>;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export const MicrovmApp = Composite<MicrovmAppProps, MicrovmAppResult>((props) => {
|
|
186
|
+
if (!NAME_PATTERN.test(props.name) || props.name.length > MAX_NAME_LENGTH) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
`MicrovmApp name must match ${NAME_PATTERN} and be ≤${MAX_NAME_LENGTH} chars, got: ${JSON.stringify(props.name)}`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const memoryMiB = props.memoryMiB ?? 2048;
|
|
193
|
+
if (!(VALID_MEMORY_MIB as readonly number[]).includes(memoryMiB)) {
|
|
194
|
+
throw new Error(`MicrovmApp memoryMiB must be one of ${VALID_MEMORY_MIB.join(", ")}, got: ${memoryMiB}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const environment = props.environment ?? {};
|
|
198
|
+
const environmentKeys = Object.keys(environment);
|
|
199
|
+
if (environmentKeys.length > MAX_ENVIRONMENT_VARIABLES) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`MicrovmApp environment supports at most ${MAX_ENVIRONMENT_VARIABLES} variables, got ${environmentKeys.length}`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
for (const key of environmentKeys) {
|
|
205
|
+
if (RESERVED_ENV_KEYS.has(key)) {
|
|
206
|
+
throw new Error(`MicrovmApp environment key "${key}" is reserved — the MicroVM runtime injects it`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const additionalEgressConnectors = props.additionalEgressConnectors ?? [];
|
|
211
|
+
const totalConnectors = additionalEgressConnectors.length + (props.buildConnector ? 1 : 0);
|
|
212
|
+
if (totalConnectors > MAX_EGRESS_CONNECTORS) {
|
|
213
|
+
throw new Error(`MicrovmApp supports at most ${MAX_EGRESS_CONNECTORS} egress connectors total, got ${totalConnectors}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (props.buildConnector) {
|
|
217
|
+
const subnetCount = props.buildConnector.subnetIds.length;
|
|
218
|
+
if (subnetCount < MIN_CONNECTOR_SUBNETS || subnetCount > MAX_CONNECTOR_SUBNETS) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`MicrovmApp buildConnector needs ${MIN_CONNECTOR_SUBNETS}-${MAX_CONNECTOR_SUBNETS} subnets, got ${subnetCount}`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const { defaults } = props;
|
|
226
|
+
|
|
227
|
+
// Service-owned log group the build + execution roles are scoped to
|
|
228
|
+
// (`lambda_microvm_cdk._impl.security.log_group_arn`) — the MicroVM service
|
|
229
|
+
// creates and owns this group; this composite only references its ARN for
|
|
230
|
+
// the IAM grants, never declares the group itself.
|
|
231
|
+
const logGroupName = `/aws/lambda/microvms/${props.name}`;
|
|
232
|
+
const logGroupArn = Sub`arn:\${AWS::Partition}:logs:\${AWS::Region}:\${AWS::AccountId}:log-group:${logGroupName}`;
|
|
233
|
+
const logGroupArnWildcard = Sub`arn:\${AWS::Partition}:logs:\${AWS::Region}:\${AWS::AccountId}:log-group:${logGroupName}:*`;
|
|
234
|
+
|
|
235
|
+
// ── Build role ── s3:GetObject on the exact code artifact object, logs:* on the image's own log group.
|
|
236
|
+
const buildRole = new Role(
|
|
237
|
+
mergeDefaults(
|
|
238
|
+
{
|
|
239
|
+
AssumeRolePolicyDocument: microvmServiceTrustPolicy,
|
|
240
|
+
Description: "Least-privilege build role for the Lambda MicroVM image build",
|
|
241
|
+
Policies: [
|
|
242
|
+
new Role_Policy({
|
|
243
|
+
PolicyName: "MicrovmImageBuild",
|
|
244
|
+
PolicyDocument: {
|
|
245
|
+
Version: "2012-10-17",
|
|
246
|
+
Statement: [
|
|
247
|
+
{
|
|
248
|
+
Sid: "ReadExactCodeArtifact",
|
|
249
|
+
Effect: "Allow",
|
|
250
|
+
Action: "s3:GetObject",
|
|
251
|
+
Resource: props.codeArtifactObjectArn,
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
Sid: "WriteBuildLogs",
|
|
255
|
+
Effect: "Allow",
|
|
256
|
+
Action: ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
|
|
257
|
+
Resource: [logGroupArn, logGroupArnWildcard],
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
},
|
|
261
|
+
}),
|
|
262
|
+
],
|
|
263
|
+
},
|
|
264
|
+
defaults?.buildRole,
|
|
265
|
+
),
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// ── Execution role ── runtime-logs only; the consumer adds workload permissions on top.
|
|
269
|
+
const executionRole = new Role(
|
|
270
|
+
mergeDefaults(
|
|
271
|
+
{
|
|
272
|
+
AssumeRolePolicyDocument: microvmServiceTrustPolicy,
|
|
273
|
+
Description: "Least-privilege execution role assumed by the running MicroVM",
|
|
274
|
+
Policies: [
|
|
275
|
+
new Role_Policy({
|
|
276
|
+
PolicyName: "MicrovmRuntimeLogs",
|
|
277
|
+
PolicyDocument: {
|
|
278
|
+
Version: "2012-10-17",
|
|
279
|
+
Statement: [
|
|
280
|
+
{
|
|
281
|
+
Sid: "WriteRuntimeLogs",
|
|
282
|
+
Effect: "Allow",
|
|
283
|
+
Action: ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
|
|
284
|
+
Resource: [logGroupArn, logGroupArnWildcard],
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
},
|
|
288
|
+
}),
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
defaults?.executionRole,
|
|
292
|
+
),
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
// ── Optional build-time egress connector ──────────────────────────────────
|
|
296
|
+
let connectorSecurityGroup: InstanceType<typeof SecurityGroup> | undefined;
|
|
297
|
+
let connectorOperatorRole: InstanceType<typeof Role> | undefined;
|
|
298
|
+
let connector: InstanceType<typeof NetworkConnector> | undefined;
|
|
299
|
+
const egressConnectorArns: unknown[] = [...additionalEgressConnectors];
|
|
300
|
+
|
|
301
|
+
if (props.buildConnector) {
|
|
302
|
+
// Deny-all by default — the security group's rules ARE the egress policy;
|
|
303
|
+
// open specific traffic via `defaults.connectorSecurityGroup`.
|
|
304
|
+
connectorSecurityGroup = new SecurityGroup(
|
|
305
|
+
mergeDefaults(
|
|
306
|
+
{
|
|
307
|
+
GroupDescription: "MicroVM egress connector security group (rules ARE the egress policy)",
|
|
308
|
+
VpcId: props.buildConnector.vpcId,
|
|
309
|
+
SecurityGroupEgress: [DENY_ALL_EGRESS],
|
|
310
|
+
},
|
|
311
|
+
defaults?.connectorSecurityGroup,
|
|
312
|
+
),
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
// Reproduces verbatim the AWS-documented `NetworkConnector` operator-role
|
|
316
|
+
// policy: `ec2:CreateNetworkInterface` on the ENI/subnet/security-group
|
|
317
|
+
// resource types (region/account wildcarded — Lambda creates the ENIs in
|
|
318
|
+
// its own context, so pinning them would deny the call and the connector
|
|
319
|
+
// would never become ACTIVE), plus `ec2:CreateTags` on ENIs conditioned on
|
|
320
|
+
// the connector's own managed-resource operator so the grant can't tag
|
|
321
|
+
// arbitrary interfaces.
|
|
322
|
+
connectorOperatorRole = new Role(
|
|
323
|
+
mergeDefaults(
|
|
324
|
+
{
|
|
325
|
+
AssumeRolePolicyDocument: lambdaTrustPolicy,
|
|
326
|
+
Description: "Least-privilege operator role Lambda assumes to manage the MicroVM connector ENIs",
|
|
327
|
+
Policies: [
|
|
328
|
+
new Role_Policy({
|
|
329
|
+
PolicyName: "MicrovmConnectorEnis",
|
|
330
|
+
PolicyDocument: {
|
|
331
|
+
Version: "2012-10-17",
|
|
332
|
+
Statement: [
|
|
333
|
+
{
|
|
334
|
+
Sid: "CreateENI",
|
|
335
|
+
Effect: "Allow",
|
|
336
|
+
Action: "ec2:CreateNetworkInterface",
|
|
337
|
+
Resource: [
|
|
338
|
+
Sub`arn:\${AWS::Partition}:ec2:*:*:network-interface/*`,
|
|
339
|
+
Sub`arn:\${AWS::Partition}:ec2:*:*:subnet/*`,
|
|
340
|
+
Sub`arn:\${AWS::Partition}:ec2:*:*:security-group/*`,
|
|
341
|
+
],
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
Sid: "TagENI",
|
|
345
|
+
Effect: "Allow",
|
|
346
|
+
Action: "ec2:CreateTags",
|
|
347
|
+
Resource: Sub`arn:\${AWS::Partition}:ec2:*:*:network-interface/*`,
|
|
348
|
+
Condition: {
|
|
349
|
+
StringEquals: { "ec2:ManagedResourceOperator": CONNECTOR_MANAGED_RESOURCE_OPERATOR },
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
},
|
|
354
|
+
}),
|
|
355
|
+
],
|
|
356
|
+
},
|
|
357
|
+
defaults?.connectorOperatorRole,
|
|
358
|
+
),
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
connector = new NetworkConnector(
|
|
362
|
+
mergeDefaults(
|
|
363
|
+
{
|
|
364
|
+
OperatorRole: connectorOperatorRole.Arn,
|
|
365
|
+
Configuration: new NetworkConnector_Config({
|
|
366
|
+
VpcEgressConfiguration: new NetworkConnector_VpcEgressConfiguration({
|
|
367
|
+
AssociatedComputeResourceTypes: ["MicroVm"],
|
|
368
|
+
SubnetIds: props.buildConnector.subnetIds,
|
|
369
|
+
SecurityGroupIds: [connectorSecurityGroup.GroupId],
|
|
370
|
+
NetworkProtocol: props.buildConnector.networkProtocol ?? "IPv4",
|
|
371
|
+
}),
|
|
372
|
+
}),
|
|
373
|
+
},
|
|
374
|
+
defaults?.connector,
|
|
375
|
+
),
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
egressConnectorArns.push(connector.Arn);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ── The image itself ───────────────────────────────────────────────────────
|
|
382
|
+
const logging = props.disableLogging
|
|
383
|
+
? new MicrovmImage_Logging({ Disabled: true })
|
|
384
|
+
: new MicrovmImage_Logging({ CloudWatch: new MicrovmImage_CloudWatchLogging({ LogGroup: logGroupName }) });
|
|
385
|
+
|
|
386
|
+
const environmentVariables = environmentKeys.map(
|
|
387
|
+
(key) => new MicrovmImage_EnvironmentVariable({ Key: key, Value: environment[key] }),
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
// `Hooks` is required by the CFN schema but every nested field is optional —
|
|
391
|
+
// "no hooks configured" (all lifecycle hooks disabled) is a real, valid `{}`.
|
|
392
|
+
// The serializer drops a wrapped property object down to `undefined` when its
|
|
393
|
+
// `.props` has zero keys (so an *optional* empty nested object vanishes
|
|
394
|
+
// rather than serializing as `{}`), which would silently drop this
|
|
395
|
+
// *required* property from the template. Emit a plain object literal instead
|
|
396
|
+
// of the `MicrovmImage_Hooks` wrapper so an empty `Hooks` still serializes
|
|
397
|
+
// as `{}`, not nothing.
|
|
398
|
+
const hasHooks = props.hooks !== undefined && Object.keys(props.hooks).length > 0;
|
|
399
|
+
const hooks = hasHooks ? new MicrovmImage_Hooks(props.hooks!) : {};
|
|
400
|
+
|
|
401
|
+
// Short alias (e.g. "al2023-1") -> the AWS-managed base-image ARN; full ARNs pass through.
|
|
402
|
+
const baseImageArn = props.baseImage.startsWith("arn:")
|
|
403
|
+
? props.baseImage
|
|
404
|
+
: Sub`arn:\${AWS::Partition}:lambda:\${AWS::Region}:aws:microvm-image:${props.baseImage}`;
|
|
405
|
+
|
|
406
|
+
const image = new MicrovmImage(
|
|
407
|
+
mergeDefaults(
|
|
408
|
+
{
|
|
409
|
+
Name: props.name,
|
|
410
|
+
Description: props.description ?? "",
|
|
411
|
+
BaseImageArn: baseImageArn,
|
|
412
|
+
BaseImageVersion: props.baseImageVersion ?? "0",
|
|
413
|
+
BuildRoleArn: buildRole.Arn,
|
|
414
|
+
CodeArtifact: new MicrovmImage_CodeArtifact({ Uri: props.codeArtifactUri }),
|
|
415
|
+
// ARM_64 is the only architecture the service accepts today.
|
|
416
|
+
CpuConfigurations: [new MicrovmImage_CpuConfiguration({ Architecture: "ARM_64" })],
|
|
417
|
+
Resources: [new MicrovmImage_Resources({ MinimumMemoryInMiB: memoryMiB })],
|
|
418
|
+
AdditionalOsCapabilities: props.additionalOsCapabilities ?? [],
|
|
419
|
+
EgressNetworkConnectors: egressConnectorArns,
|
|
420
|
+
EnvironmentVariables: environmentVariables,
|
|
421
|
+
Hooks: hooks,
|
|
422
|
+
Logging: logging,
|
|
423
|
+
},
|
|
424
|
+
defaults?.image,
|
|
425
|
+
),
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
buildRole,
|
|
430
|
+
executionRole,
|
|
431
|
+
image,
|
|
432
|
+
...(connectorSecurityGroup ? { connectorSecurityGroup } : {}),
|
|
433
|
+
...(connectorOperatorRole ? { connectorOperatorRole } : {}),
|
|
434
|
+
...(connector ? { connector } : {}),
|
|
435
|
+
};
|
|
436
|
+
}, "MicrovmApp");
|
package/src/generated/index.d.ts
CHANGED
|
@@ -50316,7 +50316,7 @@ export declare class CfDistribution_CustomOriginConfig {
|
|
|
50316
50316
|
HTTPSPort?: number;
|
|
50317
50317
|
/** Specifies which IP protocol CloudFront uses when connecting to your origin. If your origin uses both IPv4 and IPv6 protocols, you can choose ``dualstack`` to help optimize reliability. */
|
|
50318
50318
|
IpAddressType?: "dualstack" | "ipv4" | "ipv6";
|
|
50319
|
-
/** Specifies how long, in seconds, CloudFront persists its connection to the origin. The minimum timeout is 1 second, the maximum is
|
|
50319
|
+
/** Specifies how long, in seconds, CloudFront persists its connection to the origin. The minimum timeout is 1 second, the maximum is 300 seconds, and the default (if you don't specify otherwise) is 5 seconds.
|
|
50320
50320
|
For more information, see [Keep-alive timeout (custom origins only)](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistValuesOrigin.html#DownloadDistValuesOriginKeepaliveTimeout) in the *Amazon CloudFront Developer Guide*. */
|
|
50321
50321
|
OriginKeepaliveTimeout?: number;
|
|
50322
50322
|
/** Configures mutual TLS authentication between CloudFront and your origin server. */
|
|
@@ -50795,7 +50795,7 @@ export declare class CfDistribution_VpcOriginConfig {
|
|
|
50795
50795
|
constructor(props: {
|
|
50796
50796
|
/** The VPC origin ID. */
|
|
50797
50797
|
VpcOriginId: string;
|
|
50798
|
-
/** Specifies how long, in seconds, CloudFront persists its connection to the origin. The minimum timeout is 1 second, the maximum is
|
|
50798
|
+
/** Specifies how long, in seconds, CloudFront persists its connection to the origin. The minimum timeout is 1 second, the maximum is 300 seconds, and the default (if you don't specify otherwise) is 5 seconds.
|
|
50799
50799
|
For more information, see [Keep-alive timeout (custom origins only)](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistValuesOrigin.html#DownloadDistValuesOriginKeepaliveTimeout) in the *Amazon CloudFront Developer Guide*. */
|
|
50800
50800
|
OriginKeepaliveTimeout?: number;
|
|
50801
50801
|
/** Specifies how long, in seconds, CloudFront waits for a response from the origin. This is also known as the *origin response timeout*. The minimum timeout is 1 second, the maximum is 120 seconds, and the default (if you don't specify otherwise) is 30 seconds.
|
|
@@ -50808,7 +50808,7 @@ export declare class CfDistribution_VpcOriginConfig {
|
|
|
50808
50808
|
|
|
50809
50809
|
export declare class CfFunction_FunctionConfig {
|
|
50810
50810
|
constructor(props: {
|
|
50811
|
-
/** A comment to describe the function. */
|
|
50811
|
+
/** A comment to describe the function. The comment cannot be longer than 128 characters. */
|
|
50812
50812
|
Comment: string;
|
|
50813
50813
|
/** The function's runtime environment version. */
|
|
50814
50814
|
Runtime: string;
|
|
@@ -54524,7 +54524,7 @@ export declare class ConnectionDrainingPolicy {
|
|
|
54524
54524
|
|
|
54525
54525
|
export declare class ConnectionFunction_ConnectionFunctionConfig {
|
|
54526
54526
|
constructor(props: {
|
|
54527
|
-
/** A comment to describe the function. */
|
|
54527
|
+
/** A comment to describe the function. The comment cannot be longer than 128 characters. */
|
|
54528
54528
|
Comment: string;
|
|
54529
54529
|
/** The function's runtime environment version. */
|
|
54530
54530
|
Runtime: "cloudfront-js-2.0";
|
|
@@ -54560,7 +54560,7 @@ export declare class ConnectionFunctionAssociation {
|
|
|
54560
54560
|
|
|
54561
54561
|
export declare class ConnectionFunctionConfig {
|
|
54562
54562
|
constructor(props: {
|
|
54563
|
-
/** A comment to describe the function. */
|
|
54563
|
+
/** A comment to describe the function. The comment cannot be longer than 128 characters. */
|
|
54564
54564
|
Comment: string;
|
|
54565
54565
|
/** The function's runtime environment version. */
|
|
54566
54566
|
Runtime: "cloudfront-js-2.0";
|
|
@@ -57733,7 +57733,7 @@ export declare class CustomOriginConfig {
|
|
|
57733
57733
|
HTTPSPort?: number;
|
|
57734
57734
|
/** Specifies which IP protocol CloudFront uses when connecting to your origin. If your origin uses both IPv4 and IPv6 protocols, you can choose ``dualstack`` to help optimize reliability. */
|
|
57735
57735
|
IpAddressType?: "dualstack" | "ipv4" | "ipv6";
|
|
57736
|
-
/** Specifies how long, in seconds, CloudFront persists its connection to the origin. The minimum timeout is 1 second, the maximum is
|
|
57736
|
+
/** Specifies how long, in seconds, CloudFront persists its connection to the origin. The minimum timeout is 1 second, the maximum is 300 seconds, and the default (if you don't specify otherwise) is 5 seconds.
|
|
57737
57737
|
For more information, see [Keep-alive timeout (custom origins only)](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistValuesOrigin.html#DownloadDistValuesOriginKeepaliveTimeout) in the *Amazon CloudFront Developer Guide*. */
|
|
57738
57738
|
OriginKeepaliveTimeout?: number;
|
|
57739
57739
|
/** Configures mutual TLS authentication between CloudFront and your origin server. */
|
|
@@ -72661,7 +72661,7 @@ export declare class FunctionAssociation {
|
|
|
72661
72661
|
|
|
72662
72662
|
export declare class FunctionConfig {
|
|
72663
72663
|
constructor(props: {
|
|
72664
|
-
/** A comment to describe the function. */
|
|
72664
|
+
/** A comment to describe the function. The comment cannot be longer than 128 characters. */
|
|
72665
72665
|
Comment: string;
|
|
72666
72666
|
/** The function's runtime environment version. */
|
|
72667
72667
|
Runtime: string;
|
|
@@ -121683,6 +121683,7 @@ export declare class VpcOrigin_VpcOriginEndpointConfig {
|
|
|
121683
121683
|
HTTPPort?: number;
|
|
121684
121684
|
/** The HTTPS port of the CloudFront VPC origin endpoint configuration. The default value is ``443``. */
|
|
121685
121685
|
HTTPSPort?: number;
|
|
121686
|
+
IpAddressType?: "dualstack" | "ipv4";
|
|
121686
121687
|
/** The origin protocol policy for the CloudFront VPC origin endpoint configuration. */
|
|
121687
121688
|
OriginProtocolPolicy?: string;
|
|
121688
121689
|
/** Specifies the minimum SSL/TLS protocol that CloudFront uses when connecting to your origin over HTTPS. Valid values include ``SSLv3``, ``TLSv1``, ``TLSv1.1``, and ``TLSv1.2``.
|
|
@@ -121695,7 +121696,7 @@ export declare class VpcOriginConfig {
|
|
|
121695
121696
|
constructor(props: {
|
|
121696
121697
|
/** The VPC origin ID. */
|
|
121697
121698
|
VpcOriginId: string;
|
|
121698
|
-
/** Specifies how long, in seconds, CloudFront persists its connection to the origin. The minimum timeout is 1 second, the maximum is
|
|
121699
|
+
/** Specifies how long, in seconds, CloudFront persists its connection to the origin. The minimum timeout is 1 second, the maximum is 300 seconds, and the default (if you don't specify otherwise) is 5 seconds.
|
|
121699
121700
|
For more information, see [Keep-alive timeout (custom origins only)](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistValuesOrigin.html#DownloadDistValuesOriginKeepaliveTimeout) in the *Amazon CloudFront Developer Guide*. */
|
|
121700
121701
|
OriginKeepaliveTimeout?: number;
|
|
121701
121702
|
/** Specifies how long, in seconds, CloudFront waits for a response from the origin. This is also known as the *origin response timeout*. The minimum timeout is 1 second, the maximum is 120 seconds, and the default (if you don't specify otherwise) is 30 seconds.
|
|
@@ -121716,6 +121717,7 @@ export declare class VpcOriginEndpointConfig {
|
|
|
121716
121717
|
HTTPPort?: number;
|
|
121717
121718
|
/** The HTTPS port of the CloudFront VPC origin endpoint configuration. The default value is ``443``. */
|
|
121718
121719
|
HTTPSPort?: number;
|
|
121720
|
+
IpAddressType?: "dualstack" | "ipv4";
|
|
121719
121721
|
/** The origin protocol policy for the CloudFront VPC origin endpoint configuration. */
|
|
121720
121722
|
OriginProtocolPolicy?: string;
|
|
121721
121723
|
/** Specifies the minimum SSL/TLS protocol that CloudFront uses when connecting to your origin over HTTPS. Valid values include ``SSLv3``, ``TLSv1``, ``TLSv1.1``, and ``TLSv1.2``.
|
|
@@ -127344,7 +127346,7 @@ export type EmailContact_EmailContactStatus = "active" | "inactive";
|
|
|
127344
127346
|
|
|
127345
127347
|
export type EMRServerlessApplication_Architecture = "ARM64" | "X86_64";
|
|
127346
127348
|
|
|
127347
|
-
export type EnforcedGuardrailConfiguration_ConfigOwner = "ACCOUNT";
|
|
127349
|
+
export type EnforcedGuardrailConfiguration_ConfigOwner = "ACCOUNT" | "ORGANIZATION";
|
|
127348
127350
|
|
|
127349
127351
|
export type EnvironmentAccountConnection_Status = "CONNECTED" | "PENDING" | "REJECTED";
|
|
127350
127352
|
|
package/src/index.ts
CHANGED
|
@@ -80,6 +80,7 @@ export {
|
|
|
80
80
|
VpcDefault, FargateAlb, AlbShared, FargateService, RdsInstance, RdsPostgres,
|
|
81
81
|
EfsWithAccessPoint,
|
|
82
82
|
Ec2InstanceRole, MinimalVpc,
|
|
83
|
+
AgentCoreAgent,
|
|
83
84
|
} from "./composites/index";
|
|
84
85
|
export type {
|
|
85
86
|
LambdaFunctionProps, LambdaApiProps, ScheduledLambdaProps,
|
|
@@ -87,6 +88,7 @@ export type {
|
|
|
87
88
|
VpcDefaultProps, FargateAlbProps, AlbSharedProps, FargateServiceProps, RdsInstanceProps, RdsPostgresProps,
|
|
88
89
|
EfsWithAccessPointProps,
|
|
89
90
|
Ec2InstanceRoleProps, MinimalVpcProps,
|
|
91
|
+
AgentCoreAgentProps, AgentCoreAgentResult,
|
|
90
92
|
} from "./composites/index";
|
|
91
93
|
|
|
92
94
|
// Code generation pipeline
|
|
@@ -42,4 +42,24 @@ export const awsAuditCatalog: Record<string, RuleMeta> = {
|
|
|
42
42
|
WAW035: auditRule("WAW035", "report-only", "guidance", "Solr container missing nofile ulimit", "Set a nofile ulimit >= 65535.", { category: "best-practice" }),
|
|
43
43
|
WAW036: auditRule("WAW036", "merge-worthy", "guidance", "Non-ASCII characters in resource properties", "Remove non-ASCII characters rejected at changeset time.", { category: "correctness" }),
|
|
44
44
|
WAW037: auditRule("WAW037", "merge-worthy", "guidance", "Null values in resource properties", "Fix the invalid AttrRef producing null property values.", { category: "correctness" }),
|
|
45
|
+
|
|
46
|
+
// #894 — production-hardening rules (RDS/S3/KMS/ALB/ECS/SG/Cognito/ECR/Logs).
|
|
47
|
+
WAW038: auditRule("WAW038", "merge-worthy", "guidance", "RDS instance is publicly accessible", "Set PubliclyAccessible: false and reach the database through the VPC.", { authority: [AWS_SEC] }),
|
|
48
|
+
WAW039: auditRule("WAW039", "merge-worthy", "guidance", "RDS automated backups disabled", "Set a positive BackupRetentionPeriod.", { category: "best-practice" }),
|
|
49
|
+
WAW040: auditRule("WAW040", "merge-worthy", "guidance", "RDS deletion protection disabled (full tier)", "Set DeletionProtection: true on the full/production tier.", { category: "best-practice" }),
|
|
50
|
+
WAW041: auditRule("WAW041", "merge-worthy", "guidance", "RDS Proxy does not require TLS", "Set RequireTLS: true on the DB proxy.", { authority: [AWS_SEC] }),
|
|
51
|
+
WAW042: auditRule("WAW042", "merge-worthy", "guidance", "S3 bucket missing a TLS-only bucket policy", "Add a Deny statement keyed on aws:SecureTransport = false.", { authority: [AWS_SEC] }),
|
|
52
|
+
WAW043: auditRule("WAW043", "merge-worthy", "guidance", "KMS key rotation disabled", "Set EnableKeyRotation: true on the customer-managed key.", { authority: [AWS_SEC] }),
|
|
53
|
+
WAW044: auditRule("WAW044", "merge-worthy", "guidance", "ALB HTTP listener does not redirect to HTTPS (full tier)", "Add a redirect DefaultAction (Protocol: HTTPS) to the HTTP listener on the full/production tier.", { authority: [AWS_SEC] }),
|
|
54
|
+
WAW045: auditRule("WAW045", "merge-worthy", "guidance", "ALB listener uses a weak or missing TLS policy", "Set SslPolicy to a TLS 1.2+ predefined policy.", { authority: [AWS_SEC] }),
|
|
55
|
+
WAW046: auditRule("WAW046", "merge-worthy", "guidance", "ECS container passes a secret via plaintext Environment", "Move the value to Secrets (Secrets Manager/SSM Parameter Store).", { authority: [AWS_SEC] }),
|
|
56
|
+
WAW047: auditRule("WAW047", "merge-worthy", "guidance", "ECS container runs privileged", "Remove Privileged: true from the container definition.", { authority: [AWS_SEC] }),
|
|
57
|
+
WAW048: auditRule("WAW048", "report-only", "guidance", "ECS container missing log configuration", "Add a LogConfiguration (e.g. awslogs) to the container.", { category: "best-practice" }),
|
|
58
|
+
WAW049: auditRule("WAW049", "merge-worthy", "guidance", "Security group allows unrestricted ingress on a non-ALB port", "Restrict the CIDR; only ALB:80/443 is exempt from this rule.", { authority: [AWS_SEC] }),
|
|
59
|
+
WAW050: auditRule("WAW050", "merge-worthy", "guidance", "Cognito advanced security disabled", "Set UserPoolAddOns.AdvancedSecurityMode to AUDIT or ENFORCED.", { authority: [AWS_SEC] }),
|
|
60
|
+
WAW051: auditRule("WAW051", "merge-worthy", "guidance", "Cognito UserPoolClient allows the implicit OAuth grant", "Drop \"implicit\" from AllowedOAuthFlows; use the code grant.", { authority: [AWS_SEC] }),
|
|
61
|
+
WAW052: auditRule("WAW052", "merge-worthy", "guidance", "Cognito MFA not required (full tier)", "Set MfaConfiguration: ON on the full/production tier.", { authority: [AWS_SEC] }),
|
|
62
|
+
WAW053: auditRule("WAW053", "merge-worthy", "guidance", "ECR repository does not scan images on push", "Set ImageScanningConfiguration.ScanOnPush: true.", { authority: [AWS_SEC] }),
|
|
63
|
+
WAW054: auditRule("WAW054", "merge-worthy", "guidance", "ECR repository allows mutable image tags", "Set ImageTagMutability: IMMUTABLE.", { authority: [AWS_SEC] }),
|
|
64
|
+
WAW055: auditRule("WAW055", "report-only", "guidance", "CloudWatch Logs log group has no retention period", "Set an explicit RetentionInDays.", { category: "best-practice" }),
|
|
45
65
|
};
|
|
@@ -124,6 +124,29 @@ export function getSecurityGroupIngress(
|
|
|
124
124
|
return rules;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Extract an ECS TaskDefinition's container definitions (shared by the
|
|
129
|
+
* WAW046/047/048 ECS checks).
|
|
130
|
+
*/
|
|
131
|
+
export function getContainerDefinitions(resource: CFResource): Array<Record<string, unknown>> {
|
|
132
|
+
const props = resource.Properties ?? {};
|
|
133
|
+
const defs = props.ContainerDefinitions;
|
|
134
|
+
if (!Array.isArray(defs)) return [];
|
|
135
|
+
return defs.filter((d): d is Record<string, unknown> => typeof d === "object" && d !== null);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Whether a build's `env` (from `--env` or the project's `ownership.env`, see
|
|
140
|
+
* PostSynthContext#env and #201) should be treated as the strict "full"/
|
|
141
|
+
* production tier for tier-gated checks, vs a relaxed "light" tier acceptable
|
|
142
|
+
* for local/Floci-style stacks. Reuses the existing `ctx.env` seam — no new
|
|
143
|
+
* tier field. An undefined or unrecognized env is treated as non-strict so a
|
|
144
|
+
* project that never sets `--env`/`ownership.env` isn't unexpectedly hard-failed.
|
|
145
|
+
*/
|
|
146
|
+
export function isFullTierEnv(env: string | undefined): boolean {
|
|
147
|
+
return env === "prod" || env === "production" || env === "full";
|
|
148
|
+
}
|
|
149
|
+
|
|
127
150
|
/**
|
|
128
151
|
* Check if a port range [fromPort, toPort] contains any of the sensitive ports.
|
|
129
152
|
*/
|
|
@@ -29,6 +29,24 @@ import { waw034 } from "./waw034";
|
|
|
29
29
|
import { waw035 } from "./waw035";
|
|
30
30
|
import { waw036 } from "./waw036";
|
|
31
31
|
import { waw037 } from "./waw037";
|
|
32
|
+
import { waw038 } from "./waw038";
|
|
33
|
+
import { waw039 } from "./waw039";
|
|
34
|
+
import { waw040 } from "./waw040";
|
|
35
|
+
import { waw041 } from "./waw041";
|
|
36
|
+
import { waw042 } from "./waw042";
|
|
37
|
+
import { waw043 } from "./waw043";
|
|
38
|
+
import { waw044 } from "./waw044";
|
|
39
|
+
import { waw045 } from "./waw045";
|
|
40
|
+
import { waw046 } from "./waw046";
|
|
41
|
+
import { waw047 } from "./waw047";
|
|
42
|
+
import { waw048 } from "./waw048";
|
|
43
|
+
import { waw049 } from "./waw049";
|
|
44
|
+
import { waw050 } from "./waw050";
|
|
45
|
+
import { waw051 } from "./waw051";
|
|
46
|
+
import { waw052 } from "./waw052";
|
|
47
|
+
import { waw053 } from "./waw053";
|
|
48
|
+
import { waw054 } from "./waw054";
|
|
49
|
+
import { waw055 } from "./waw055";
|
|
32
50
|
|
|
33
51
|
export const postSynthChecks: PostSynthCheck[] = [
|
|
34
52
|
cor020,
|
|
@@ -60,4 +78,22 @@ export const postSynthChecks: PostSynthCheck[] = [
|
|
|
60
78
|
waw035,
|
|
61
79
|
waw036,
|
|
62
80
|
waw037,
|
|
81
|
+
waw038,
|
|
82
|
+
waw039,
|
|
83
|
+
waw040,
|
|
84
|
+
waw041,
|
|
85
|
+
waw042,
|
|
86
|
+
waw043,
|
|
87
|
+
waw044,
|
|
88
|
+
waw045,
|
|
89
|
+
waw046,
|
|
90
|
+
waw047,
|
|
91
|
+
waw048,
|
|
92
|
+
waw049,
|
|
93
|
+
waw050,
|
|
94
|
+
waw051,
|
|
95
|
+
waw052,
|
|
96
|
+
waw053,
|
|
97
|
+
waw054,
|
|
98
|
+
waw055,
|
|
63
99
|
];
|