Skip to content

Commit 38eb3b0

Browse files
authored
upgrade cdk and default encryption for compliance (#473)
1 parent 12adb34 commit 38eb3b0

36 files changed

+662
-230
lines changed

apps/test-app-sst/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"@serverless-stack/core": "^1.18.4",
1818
"@serverless-stack/resources": "^1.18.4",
1919
"@tsconfig/node18": "^1.0.1",
20-
"aws-cdk-lib": "2.80.0",
20+
"aws-cdk-lib": "2.102.0",
2121
"chalk": "^5.2.0",
2222
"fs-extra": "^11.1.0",
2323
"typescript": "^5",

apps/test-app/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
},
1515
"dependencies": {
1616
"@eventual/aws-cdk": "workspace:^",
17-
"aws-cdk-lib": "2.80.0",
17+
"aws-cdk-lib": "2.102.0",
1818
"constructs": "10.1.154"
1919
},
2020
"devDependencies": {
2121
"@eventual/cli": "workspace:^",
2222
"@types/jest": "^29.5.1",
2323
"@types/node": "^18",
24-
"aws-cdk": "2.80.0",
24+
"aws-cdk": "2.102.0",
2525
"esbuild": "^0.17.4",
2626
"jest": "^29",
2727
"test-app-runtime": "workspace:^",

apps/test-app/src/app.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ new ServiceDashboard(stack, "BenchmarkDashboard", {
2626
const bench = new NodejsFunction(stack, "BenchmarkFunc", {
2727
entry: require.resolve("test-app-runtime/lib/bench.js"),
2828
handler: "handle",
29-
runtime: Runtime.NODEJS_16_X,
29+
runtime: Runtime.NODEJS_LATEST,
3030
architecture: Architecture.ARM_64,
3131
bundling: {
3232
// https://github.com/aws/aws-cdk/issues/21329#issuecomment-1212336356
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"projectType": "aws-cdk",
3+
"synth": "npx cdk synth --app \"ts-node --esm ./src/app.mts\"",
4+
"deploy": "../aws-runtime/scripts/deploy"
5+
}

apps/tests/aws-runtime-cdk/package.json

+6-4
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
"version": "0.0.0",
66
"main": "lib/index.js",
77
"scripts": {
8-
"cdk": "cdk"
8+
"cdk": "cdk",
9+
"nag": "ts-node-esm ./scripts/report-violations.ts"
910
},
1011
"dependencies": {
11-
"@aws-cdk/aws-apigatewayv2-alpha": "^2.80.0-alpha.0",
12-
"aws-cdk-lib": "2.80.0",
12+
"@aws-cdk/aws-apigatewayv2-alpha": "^2.102.0-alpha.0",
13+
"aws-cdk-lib": "2.102.0",
14+
"cdk-nag": "^2.27.164",
1315
"constructs": "10.1.154"
1416
},
1517
"devDependencies": {
@@ -19,7 +21,7 @@
1921
"@eventual/core": "workspace:^",
2022
"@types/jest": "^29.5.1",
2123
"@types/node": "^18",
22-
"aws-cdk": "^2.80.0",
24+
"aws-cdk": "^2.102.0",
2325
"esbuild": "^0.17.4",
2426
"jest": "^29",
2527
"tests-runtime": "workspace:^",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import hipaa from "../cdk.out/HIPAA.Security-eventual-tests-NagReport.json" assert { type: "json" };
2+
import awsSolutions from "../cdk.out/HIPAA.Security-eventual-tests-NagReport.json" assert { type: "json" };
3+
4+
type Report = typeof hipaa | typeof awsSolutions;
5+
6+
report([hipaa]);
7+
8+
function report(report: Report[]) {
9+
const errors = report.flatMap((report) => report.lines);
10+
11+
const nonCompliant = errors.filter(
12+
(line) => line.compliance === "Non-Compliant"
13+
);
14+
const compliant = errors.filter((line) => line.compliance === "Compliant");
15+
type Violation = (typeof nonCompliant)[number];
16+
17+
console.log("# Non-compliant");
18+
printErrors(nonCompliant);
19+
console.log("# Compliant");
20+
printErrors(compliant);
21+
22+
function printErrors(error: Violation[]) {
23+
const uniqueErrors = Array.from(
24+
new Set(error.map((line) => format(line.ruleInfo)))
25+
);
26+
console.log(uniqueErrors.sort().join("\n"));
27+
}
28+
29+
function format(line: string) {
30+
return `- [ ] ${line.replace(/- \(Control.*/g, "")}`;
31+
}
32+
}

apps/tests/aws-runtime-cdk/src/app.mts

+43-13
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
33
import * as eventual from "@eventual/aws-cdk";
44
import { DebugDashboard, ServiceDashboard } from "@eventual/aws-cdk";
55
import { LogLevel } from "@eventual/core";
6-
import { App, CfnOutput, CfnResource, Stack } from "aws-cdk-lib";
6+
import { App, CfnOutput, Stack } from "aws-cdk-lib";
77
import { AttributeType, BillingMode, Table } from "aws-cdk-lib/aws-dynamodb";
88
import {
99
ArnPrincipal,
@@ -13,11 +13,20 @@ import {
1313
} from "aws-cdk-lib/aws-iam";
1414
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
1515
import { Queue } from "aws-cdk-lib/aws-sqs";
16-
import { Duration } from "aws-cdk-lib/core";
16+
import { Aspects, Duration } from "aws-cdk-lib/core";
17+
import {
18+
AwsSolutionsChecks,
19+
HIPAASecurityChecks,
20+
NagPack,
21+
NagPackProps,
22+
NagReportFormat,
23+
} from "cdk-nag";
1724
import { createRequire as topLevelCreateRequire } from "module";
1825
import path from "path";
1926
import { ChaosExtension } from "./chaos-extension.js";
2027

28+
import { ComplianceStandard } from "@eventual/aws-cdk";
29+
import { CfnPipe } from "aws-cdk-lib/aws-pipes";
2130
import type * as testServiceRuntime from "tests-runtime";
2231

2332
const require = topLevelCreateRequire(import.meta.url);
@@ -56,6 +65,9 @@ const testService = new eventual.Service<typeof testServiceRuntime>(
5665
TEST_QUEUE_URL: testQueue.queueUrl,
5766
TEST_TABLE_NAME: testTable.tableName,
5867
},
68+
compliance: {
69+
standards: [ComplianceStandard.HIPAA],
70+
},
5971
system: {
6072
workflowService: {
6173
logLevel: LogLevel.DEBUG,
@@ -79,6 +91,27 @@ const testService = new eventual.Service<typeof testServiceRuntime>(
7991
}
8092
);
8193

94+
// these run linting rules on the CDK code and should all pass to enforce compliance
95+
enableNagPack(AwsSolutionsChecks);
96+
enableNagPack(HIPAASecurityChecks);
97+
function enableNagPack<P extends NagPackProps>(
98+
Pack: new (props: P) => NagPack,
99+
props?: P
100+
) {
101+
// TODO: enable once we comply with all policies and tests pass in deployment
102+
const nag = false;
103+
if (nag) {
104+
Aspects.of(testService).add(
105+
new Pack({
106+
reports: true,
107+
reportFormats: [NagReportFormat.CSV, NagReportFormat.JSON],
108+
verbose: true,
109+
...props,
110+
} as P)
111+
);
112+
}
113+
}
114+
82115
testService.grantInvokeHttpServiceApi(role);
83116
testService.system.accessRole.grantAssumeRole(role);
84117
eventual.Service.grantDescribeParameters(stack, role);
@@ -128,18 +161,15 @@ asyncWriterFunction.grantInvoke(pipeRole);
128161
testService.grantInvokeHttpServiceApi(asyncWriterFunction);
129162

130163
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-pipes-pipe.html
131-
new CfnResource(stack, "pipe", {
132-
type: "AWS::Pipes::Pipe",
133-
properties: {
134-
TargetParameters: {
135-
InputTemplate:
136-
'{"token": "<$.body.token>","type":"<$.body.type>","ingestionTime":"<aws.pipes.event.ingestion-time>"}',
137-
},
138-
Name: stack.stackName + "_pipe",
139-
RoleArn: pipeRole.roleArn,
140-
Source: testQueue.queueArn,
141-
Target: asyncWriterFunction.functionArn,
164+
new CfnPipe(stack, "pipe", {
165+
targetParameters: {
166+
inputTemplate:
167+
'{"token": "<$.body.token>","type":"<$.body.type>","ingestionTime":"<aws.pipes.event.ingestion-time>"}',
142168
},
169+
name: stack.stackName + "_pipe",
170+
roleArn: pipeRole.roleArn,
171+
source: testQueue.queueArn,
172+
target: asyncWriterFunction.functionArn,
143173
});
144174

145175
new ServiceDashboard(stack, "dashboard", {

apps/tests/aws-runtime/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"@types/jest": "^29.5.1",
3333
"@types/node": "^18",
3434
"@types/ws": "^8.5.5",
35-
"aws-cdk": "^2.80.0",
35+
"aws-cdk": "^2.102.0",
3636
"esbuild": "^0.17.4",
3737
"jest": "^29",
3838
"node-fetch": "^3.3.0",

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"test:local": "pnpm -r --filter tests-runtime run test:local-start",
2626
"typecheck": "tsc -b",
2727
"watch": "tsc -b -w",
28-
"export": "turbo run export"
28+
"export": "turbo run export",
29+
"nag": "pnpm --filter tests-cdk run nag"
2930
},
3031
"devDependencies": {
3132
"@types/jest": "^29.5.1",

packages/@eventual/aws-cdk/package.json

+9-9
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,23 @@
1717
"aws4": "^1.12.0"
1818
},
1919
"peerDependencies": {
20-
"@aws-cdk/aws-apigatewayv2-alpha": "^2.80.0-alpha.0",
21-
"@aws-cdk/aws-apigatewayv2-authorizers-alpha": "^2.80.0-alpha.0",
22-
"@aws-cdk/aws-apigatewayv2-integrations-alpha": "^2.80.0-alpha.0",
23-
"aws-cdk-lib": "^2.80.0",
20+
"@aws-cdk/aws-apigatewayv2-alpha": "^2.102.0-alpha.0",
21+
"@aws-cdk/aws-apigatewayv2-authorizers-alpha": "^2.102.0-alpha.0",
22+
"@aws-cdk/aws-apigatewayv2-integrations-alpha": "^2.102.0-alpha.0",
23+
"aws-cdk-lib": "^2.102.0",
2424
"constructs": "^10.0.0",
2525
"esbuild": ">=0.16.x <1.0.0"
2626
},
2727
"devDependencies": {
28-
"@aws-cdk/aws-apigatewayv2-alpha": "2.80.0-alpha.0",
29-
"@aws-cdk/aws-apigatewayv2-authorizers-alpha": "2.80.0-alpha.0",
30-
"@aws-cdk/aws-apigatewayv2-integrations-alpha": "2.80.0-alpha.0",
28+
"@aws-cdk/aws-apigatewayv2-alpha": "2.102.0-alpha.0",
29+
"@aws-cdk/aws-apigatewayv2-authorizers-alpha": "2.102.0-alpha.0",
30+
"@aws-cdk/aws-apigatewayv2-integrations-alpha": "2.102.0-alpha.0",
3131
"@types/aws-lambda": "8.10.115",
3232
"@types/aws4": "^1.11.2",
3333
"@types/jest": "^29.5.1",
3434
"@types/node": "^18",
35-
"aws-cdk": "2.80.0",
36-
"aws-cdk-lib": "2.80.0",
35+
"aws-cdk": "2.102.0",
36+
"aws-cdk-lib": "2.102.0",
3737
"constructs": "10.1.154",
3838
"esbuild": "^0.17.4",
3939
"jest": "^29",

packages/@eventual/aws-cdk/src/bucket-service.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
import { ServiceFunction } from "./service-function";
2424
import { formatBucketArn, serviceBucketArn, ServiceEntityProps } from "./utils";
2525
import { EventualResource } from "./resource";
26+
import { SecureBucket } from "./secure/bucket";
2627

2728
export type BucketOverrides<Service> = Partial<
2829
ServiceEntityProps<
@@ -141,6 +142,12 @@ export class BucketService<Service> {
141142
],
142143
})
143144
);
145+
if (this.props.compliancePolicy.isCustomerManagedKeys()) {
146+
// data in the buckets are encrypted with a key that the customer owns
147+
this.props.compliancePolicy.dataEncryptionKey.grantEncryptDecrypt(
148+
grantee
149+
);
150+
}
144151
}
145152

146153
private readonly ENV_MAPPINGS = {
@@ -179,7 +186,8 @@ class Bucket extends Construct implements IBucket {
179186
const bucketOverrides =
180187
props.serviceProps.bucketOverrides?.[props.bucket.name];
181188

182-
this.bucket = new s3.Bucket(this, "Bucket", {
189+
this.bucket = new SecureBucket(this, "Bucket", {
190+
compliancePolicy: props.serviceProps.compliancePolicy,
183191
...bucketOverrides,
184192
cors:
185193
props.serviceProps.cors &&
@@ -264,6 +272,7 @@ export class BucketNotificationHandler
264272
const bucketName = props.handler.spec.bucketName;
265273

266274
this.handler = new ServiceFunction(this, "Handler", {
275+
compliancePolicy: props.serviceProps.compliancePolicy,
267276
build: props.serviceProps.build,
268277
bundledFunction: props.handler,
269278
functionNameSuffix: `bucket-handler-${bucketName}-${handlerName}`,

packages/@eventual/aws-cdk/src/command-service.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { ServiceFunction } from "./service-function.js";
4747
import type { TaskService } from "./task-service";
4848
import { ServiceEntityProps, serviceFunctionArn } from "./utils";
4949
import type { WorkflowService } from "./workflow-service";
50+
import { SecureFunction } from "./secure/function.js";
5051

5152
export type ApiOverrides = Omit<SpecHttpApiProps, "apiDefinition">;
5253

@@ -180,6 +181,7 @@ export class CommandService<Service = any> {
180181
commandsSystemScope,
181182
"SystemCommandHandler",
182183
{
184+
compliancePolicy: props.compliancePolicy,
183185
build: this.props.build,
184186
bundledFunction:
185187
this.props.build.system.eventualService.systemCommandHandler,
@@ -242,6 +244,7 @@ export class CommandService<Service = any> {
242244
scope,
243245
commandNamespaceName(command),
244246
{
247+
compliancePolicy: props.compliancePolicy,
245248
build: self.props.build,
246249
bundledFunction: manifest,
247250
functionNameSuffix: commandFunctionNameSuffix(command),
@@ -330,13 +333,14 @@ export class CommandService<Service = any> {
330333

331334
let optionsFunction: Function | undefined;
332335
if (props.cors && !props.cors.disableOptionsEndpoint) {
333-
optionsFunction = new Function(commandsSystemScope, "Options", {
336+
optionsFunction = new SecureFunction(commandsSystemScope, "Options", {
337+
compliancePolicy: props.compliancePolicy,
334338
functionName: serviceFunctionName(
335339
props.serviceName,
336340
"options-command"
337341
),
338342
handler: "index.handler",
339-
runtime: Runtime.NODEJS_18_X,
343+
runtime: Runtime.NODEJS_LATEST,
340344
architecture: Architecture.ARM_64,
341345
memorySize: 512,
342346
// the headers will be replaced with the correct headers based on the cors configuration

0 commit comments

Comments
 (0)