Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

upgrade cdk and default encryption for compliance #473

Merged
merged 12 commits into from
Oct 23, 2023
2 changes: 1 addition & 1 deletion apps/test-app-sst/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@serverless-stack/core": "^1.18.4",
"@serverless-stack/resources": "^1.18.4",
"@tsconfig/node18": "^1.0.1",
"aws-cdk-lib": "2.80.0",
"aws-cdk-lib": "2.102.0",
"chalk": "^5.2.0",
"fs-extra": "^11.1.0",
"typescript": "^5",
Expand Down
4 changes: 2 additions & 2 deletions apps/test-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
},
"dependencies": {
"@eventual/aws-cdk": "workspace:^",
"aws-cdk-lib": "2.80.0",
"aws-cdk-lib": "2.102.0",
"constructs": "10.1.154"
},
"devDependencies": {
"@eventual/cli": "workspace:^",
"@types/jest": "^29.5.1",
"@types/node": "^18",
"aws-cdk": "2.80.0",
"aws-cdk": "2.102.0",
"esbuild": "^0.17.4",
"jest": "^29",
"test-app-runtime": "workspace:^",
Expand Down
2 changes: 1 addition & 1 deletion apps/test-app/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ new ServiceDashboard(stack, "BenchmarkDashboard", {
const bench = new NodejsFunction(stack, "BenchmarkFunc", {
entry: require.resolve("test-app-runtime/lib/bench.js"),
handler: "handle",
runtime: Runtime.NODEJS_16_X,
runtime: Runtime.NODEJS_LATEST,
architecture: Architecture.ARM_64,
bundling: {
// https://github.com/aws/aws-cdk/issues/21329#issuecomment-1212336356
Expand Down
5 changes: 5 additions & 0 deletions apps/tests/aws-runtime-cdk/eventual.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"projectType": "aws-cdk",
"synth": "npx cdk synth --app \"ts-node --esm ./src/app.mts\"",
"deploy": "../aws-runtime/scripts/deploy"
}
10 changes: 6 additions & 4 deletions apps/tests/aws-runtime-cdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
"version": "0.0.0",
"main": "lib/index.js",
"scripts": {
"cdk": "cdk"
"cdk": "cdk",
"nag": "ts-node-esm ./scripts/report-violations.ts"
},
"dependencies": {
"@aws-cdk/aws-apigatewayv2-alpha": "^2.80.0-alpha.0",
"aws-cdk-lib": "2.80.0",
"@aws-cdk/aws-apigatewayv2-alpha": "^2.102.0-alpha.0",
"aws-cdk-lib": "2.102.0",
"cdk-nag": "^2.27.164",
"constructs": "10.1.154"
},
"devDependencies": {
Expand All @@ -19,7 +21,7 @@
"@eventual/core": "workspace:^",
"@types/jest": "^29.5.1",
"@types/node": "^18",
"aws-cdk": "^2.80.0",
"aws-cdk": "^2.102.0",
"esbuild": "^0.17.4",
"jest": "^29",
"tests-runtime": "workspace:^",
Expand Down
32 changes: 32 additions & 0 deletions apps/tests/aws-runtime-cdk/scripts/report-violations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import hipaa from "../cdk.out/HIPAA.Security-eventual-tests-NagReport.json" assert { type: "json" };
import awsSolutions from "../cdk.out/HIPAA.Security-eventual-tests-NagReport.json" assert { type: "json" };

type Report = typeof hipaa | typeof awsSolutions;

report([hipaa]);

function report(report: Report[]) {
const errors = report.flatMap((report) => report.lines);

const nonCompliant = errors.filter(
(line) => line.compliance === "Non-Compliant"
);
const compliant = errors.filter((line) => line.compliance === "Compliant");
type Violation = (typeof nonCompliant)[number];

console.log("# Non-compliant");
printErrors(nonCompliant);
console.log("# Compliant");
printErrors(compliant);

function printErrors(error: Violation[]) {
const uniqueErrors = Array.from(
new Set(error.map((line) => format(line.ruleInfo)))
);
console.log(uniqueErrors.sort().join("\n"));
}

function format(line: string) {
return `- [ ] ${line.replace(/- \(Control.*/g, "")}`;
}
}
56 changes: 43 additions & 13 deletions apps/tests/aws-runtime-cdk/src/app.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
import * as eventual from "@eventual/aws-cdk";
import { DebugDashboard, ServiceDashboard } from "@eventual/aws-cdk";
import { LogLevel } from "@eventual/core";
import { App, CfnOutput, CfnResource, Stack } from "aws-cdk-lib";
import { App, CfnOutput, Stack } from "aws-cdk-lib";
import { AttributeType, BillingMode, Table } from "aws-cdk-lib/aws-dynamodb";
import {
ArnPrincipal,
Expand All @@ -13,11 +13,20 @@ import {
} from "aws-cdk-lib/aws-iam";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
import { Queue } from "aws-cdk-lib/aws-sqs";
import { Duration } from "aws-cdk-lib/core";
import { Aspects, Duration } from "aws-cdk-lib/core";
import {
AwsSolutionsChecks,
HIPAASecurityChecks,
NagPack,
NagPackProps,
NagReportFormat,
} from "cdk-nag";
import { createRequire as topLevelCreateRequire } from "module";
import path from "path";
import { ChaosExtension } from "./chaos-extension.js";

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

const require = topLevelCreateRequire(import.meta.url);
Expand Down Expand Up @@ -56,6 +65,9 @@ const testService = new eventual.Service<typeof testServiceRuntime>(
TEST_QUEUE_URL: testQueue.queueUrl,
TEST_TABLE_NAME: testTable.tableName,
},
compliance: {
standards: [ComplianceStandard.HIPAA],
},
system: {
workflowService: {
logLevel: LogLevel.DEBUG,
Expand All @@ -79,6 +91,27 @@ const testService = new eventual.Service<typeof testServiceRuntime>(
}
);

// these run linting rules on the CDK code and should all pass to enforce compliance
enableNagPack(AwsSolutionsChecks);
enableNagPack(HIPAASecurityChecks);
function enableNagPack<P extends NagPackProps>(
Pack: new (props: P) => NagPack,
props?: P
) {
// TODO: enable once we comply with all policies and tests pass in deployment
const nag = false;
if (nag) {
Aspects.of(testService).add(
new Pack({
reports: true,
reportFormats: [NagReportFormat.CSV, NagReportFormat.JSON],
verbose: true,
...props,
} as P)
);
}
}

testService.grantInvokeHttpServiceApi(role);
testService.system.accessRole.grantAssumeRole(role);
eventual.Service.grantDescribeParameters(stack, role);
Expand Down Expand Up @@ -128,18 +161,15 @@ asyncWriterFunction.grantInvoke(pipeRole);
testService.grantInvokeHttpServiceApi(asyncWriterFunction);

// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-pipes-pipe.html
new CfnResource(stack, "pipe", {
type: "AWS::Pipes::Pipe",
properties: {
TargetParameters: {
InputTemplate:
'{"token": "<$.body.token>","type":"<$.body.type>","ingestionTime":"<aws.pipes.event.ingestion-time>"}',
},
Name: stack.stackName + "_pipe",
RoleArn: pipeRole.roleArn,
Source: testQueue.queueArn,
Target: asyncWriterFunction.functionArn,
new CfnPipe(stack, "pipe", {
targetParameters: {
inputTemplate:
'{"token": "<$.body.token>","type":"<$.body.type>","ingestionTime":"<aws.pipes.event.ingestion-time>"}',
},
name: stack.stackName + "_pipe",
roleArn: pipeRole.roleArn,
source: testQueue.queueArn,
target: asyncWriterFunction.functionArn,
});

new ServiceDashboard(stack, "dashboard", {
Expand Down
2 changes: 1 addition & 1 deletion apps/tests/aws-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"@types/jest": "^29.5.1",
"@types/node": "^18",
"@types/ws": "^8.5.5",
"aws-cdk": "^2.80.0",
"aws-cdk": "^2.102.0",
"esbuild": "^0.17.4",
"jest": "^29",
"node-fetch": "^3.3.0",
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"test:local": "pnpm -r --filter tests-runtime run test:local-start",
"typecheck": "tsc -b",
"watch": "tsc -b -w",
"export": "turbo run export"
"export": "turbo run export",
"nag": "pnpm --filter tests-cdk run nag"
},
"devDependencies": {
"@types/jest": "^29.5.1",
Expand Down
18 changes: 9 additions & 9 deletions packages/@eventual/aws-cdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,23 @@
"aws4": "^1.12.0"
},
"peerDependencies": {
"@aws-cdk/aws-apigatewayv2-alpha": "^2.80.0-alpha.0",
"@aws-cdk/aws-apigatewayv2-authorizers-alpha": "^2.80.0-alpha.0",
"@aws-cdk/aws-apigatewayv2-integrations-alpha": "^2.80.0-alpha.0",
"aws-cdk-lib": "^2.80.0",
"@aws-cdk/aws-apigatewayv2-alpha": "^2.102.0-alpha.0",
"@aws-cdk/aws-apigatewayv2-authorizers-alpha": "^2.102.0-alpha.0",
"@aws-cdk/aws-apigatewayv2-integrations-alpha": "^2.102.0-alpha.0",
"aws-cdk-lib": "^2.102.0",
"constructs": "^10.0.0",
"esbuild": ">=0.16.x <1.0.0"
},
"devDependencies": {
"@aws-cdk/aws-apigatewayv2-alpha": "2.80.0-alpha.0",
"@aws-cdk/aws-apigatewayv2-authorizers-alpha": "2.80.0-alpha.0",
"@aws-cdk/aws-apigatewayv2-integrations-alpha": "2.80.0-alpha.0",
"@aws-cdk/aws-apigatewayv2-alpha": "2.102.0-alpha.0",
"@aws-cdk/aws-apigatewayv2-authorizers-alpha": "2.102.0-alpha.0",
"@aws-cdk/aws-apigatewayv2-integrations-alpha": "2.102.0-alpha.0",
"@types/aws-lambda": "8.10.115",
"@types/aws4": "^1.11.2",
"@types/jest": "^29.5.1",
"@types/node": "^18",
"aws-cdk": "2.80.0",
"aws-cdk-lib": "2.80.0",
"aws-cdk": "2.102.0",
"aws-cdk-lib": "2.102.0",
"constructs": "10.1.154",
"esbuild": "^0.17.4",
"jest": "^29",
Expand Down
11 changes: 10 additions & 1 deletion packages/@eventual/aws-cdk/src/bucket-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { ServiceFunction } from "./service-function";
import { formatBucketArn, serviceBucketArn, ServiceEntityProps } from "./utils";
import { EventualResource } from "./resource";
import { SecureBucket } from "./secure/bucket";

export type BucketOverrides<Service> = Partial<
ServiceEntityProps<
Expand Down Expand Up @@ -141,6 +142,12 @@ export class BucketService<Service> {
],
})
);
if (this.props.compliancePolicy.isCustomerManagedKeys()) {
// data in the buckets are encrypted with a key that the customer owns
this.props.compliancePolicy.dataEncryptionKey.grantEncryptDecrypt(
grantee
);
}
Comment on lines +145 to +150
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I need to do this for AWS-managed keys. I don't have to for AWS-owned because there's zero visibility into that, but AWS-managed creates a key with an alias, like aws/s3 and I'm hoping I don't need to grant permissions but I may.

}

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

this.bucket = new s3.Bucket(this, "Bucket", {
this.bucket = new SecureBucket(this, "Bucket", {
compliancePolicy: props.serviceProps.compliancePolicy,
...bucketOverrides,
cors:
props.serviceProps.cors &&
Expand Down Expand Up @@ -264,6 +272,7 @@ export class BucketNotificationHandler
const bucketName = props.handler.spec.bucketName;

this.handler = new ServiceFunction(this, "Handler", {
compliancePolicy: props.serviceProps.compliancePolicy,
build: props.serviceProps.build,
bundledFunction: props.handler,
functionNameSuffix: `bucket-handler-${bucketName}-${handlerName}`,
Expand Down
8 changes: 6 additions & 2 deletions packages/@eventual/aws-cdk/src/command-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { ServiceFunction } from "./service-function.js";
import type { TaskService } from "./task-service";
import { ServiceEntityProps, serviceFunctionArn } from "./utils";
import type { WorkflowService } from "./workflow-service";
import { SecureFunction } from "./secure/function.js";

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

Expand Down Expand Up @@ -180,6 +181,7 @@ export class CommandService<Service = any> {
commandsSystemScope,
"SystemCommandHandler",
{
compliancePolicy: props.compliancePolicy,
build: this.props.build,
bundledFunction:
this.props.build.system.eventualService.systemCommandHandler,
Expand Down Expand Up @@ -242,6 +244,7 @@ export class CommandService<Service = any> {
scope,
commandNamespaceName(command),
{
compliancePolicy: props.compliancePolicy,
build: self.props.build,
bundledFunction: manifest,
functionNameSuffix: commandFunctionNameSuffix(command),
Expand Down Expand Up @@ -330,13 +333,14 @@ export class CommandService<Service = any> {

let optionsFunction: Function | undefined;
if (props.cors && !props.cors.disableOptionsEndpoint) {
optionsFunction = new Function(commandsSystemScope, "Options", {
optionsFunction = new SecureFunction(commandsSystemScope, "Options", {
compliancePolicy: props.compliancePolicy,
functionName: serviceFunctionName(
props.serviceName,
"options-command"
),
handler: "index.handler",
runtime: Runtime.NODEJS_18_X,
runtime: Runtime.NODEJS_LATEST,
architecture: Architecture.ARM_64,
memorySize: 512,
// the headers will be replaced with the correct headers based on the cors configuration
Expand Down
Loading