From 38eb3b03b1272700056da9ad9cadad56ff6a0ad7 Mon Sep 17 00:00:00 2001 From: sam Date: Mon, 23 Oct 2023 10:47:58 -0700 Subject: [PATCH] upgrade cdk and default encryption for compliance (#473) --- apps/test-app-sst/package.json | 2 +- apps/test-app/package.json | 4 +- apps/test-app/src/app.ts | 2 +- apps/tests/aws-runtime-cdk/eventual.json | 5 + apps/tests/aws-runtime-cdk/package.json | 10 +- .../scripts/report-violations.ts | 32 ++++ apps/tests/aws-runtime-cdk/src/app.mts | 56 +++++-- apps/tests/aws-runtime/package.json | 2 +- package.json | 3 +- packages/@eventual/aws-cdk/package.json | 18 +- .../@eventual/aws-cdk/src/bucket-service.ts | 11 +- .../@eventual/aws-cdk/src/command-service.ts | 8 +- packages/@eventual/aws-cdk/src/compliance.ts | 127 ++++++++++++++ .../src/constructs/event-bridge-pipe.ts | 37 ++--- .../@eventual/aws-cdk/src/entity-service.ts | 14 +- packages/@eventual/aws-cdk/src/index.ts | 1 + .../@eventual/aws-cdk/src/queue-service.ts | 16 +- .../aws-cdk/src/scheduler-service.ts | 24 +-- .../aws-cdk/src/search/base-search-service.ts | 6 +- .../@eventual/aws-cdk/src/secure/bucket.ts | 45 +++++ .../@eventual/aws-cdk/src/secure/function.ts | 21 +++ packages/@eventual/aws-cdk/src/secure/key.ts | 3 + .../@eventual/aws-cdk/src/secure/log-group.ts | 33 ++++ .../@eventual/aws-cdk/src/secure/queue.ts | 31 ++++ .../@eventual/aws-cdk/src/secure/table.ts | 36 ++++ .../@eventual/aws-cdk/src/service-common.ts | 24 +-- .../@eventual/aws-cdk/src/service-function.ts | 8 +- packages/@eventual/aws-cdk/src/service.ts | 41 +++-- .../@eventual/aws-cdk/src/socket-service.ts | 1 + .../@eventual/aws-cdk/src/subscriptions.ts | 11 +- .../@eventual/aws-cdk/src/task-service.ts | 24 +-- packages/@eventual/aws-cdk/src/utils.ts | 13 +- .../@eventual/aws-cdk/src/workflow-service.ts | 52 +++--- packages/create-eventual/package.json | 2 +- .../src/create-new-aws-cdk-project.ts | 14 +- pnpm-lock.yaml | 155 ++++++++++-------- 36 files changed, 662 insertions(+), 230 deletions(-) create mode 100644 apps/tests/aws-runtime-cdk/eventual.json create mode 100644 apps/tests/aws-runtime-cdk/scripts/report-violations.ts create mode 100644 packages/@eventual/aws-cdk/src/compliance.ts create mode 100644 packages/@eventual/aws-cdk/src/secure/bucket.ts create mode 100644 packages/@eventual/aws-cdk/src/secure/function.ts create mode 100644 packages/@eventual/aws-cdk/src/secure/key.ts create mode 100644 packages/@eventual/aws-cdk/src/secure/log-group.ts create mode 100644 packages/@eventual/aws-cdk/src/secure/queue.ts create mode 100644 packages/@eventual/aws-cdk/src/secure/table.ts diff --git a/apps/test-app-sst/package.json b/apps/test-app-sst/package.json index 391e2cd86..e733f8d67 100644 --- a/apps/test-app-sst/package.json +++ b/apps/test-app-sst/package.json @@ -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", diff --git a/apps/test-app/package.json b/apps/test-app/package.json index 1a3b662b5..2e2ea42d9 100644 --- a/apps/test-app/package.json +++ b/apps/test-app/package.json @@ -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:^", diff --git a/apps/test-app/src/app.ts b/apps/test-app/src/app.ts index 8967e8f0e..738d8992b 100644 --- a/apps/test-app/src/app.ts +++ b/apps/test-app/src/app.ts @@ -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 diff --git a/apps/tests/aws-runtime-cdk/eventual.json b/apps/tests/aws-runtime-cdk/eventual.json new file mode 100644 index 000000000..60b2388c0 --- /dev/null +++ b/apps/tests/aws-runtime-cdk/eventual.json @@ -0,0 +1,5 @@ +{ + "projectType": "aws-cdk", + "synth": "npx cdk synth --app \"ts-node --esm ./src/app.mts\"", + "deploy": "../aws-runtime/scripts/deploy" +} diff --git a/apps/tests/aws-runtime-cdk/package.json b/apps/tests/aws-runtime-cdk/package.json index e13a7b1ac..08636be3f 100644 --- a/apps/tests/aws-runtime-cdk/package.json +++ b/apps/tests/aws-runtime-cdk/package.json @@ -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": { @@ -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:^", diff --git a/apps/tests/aws-runtime-cdk/scripts/report-violations.ts b/apps/tests/aws-runtime-cdk/scripts/report-violations.ts new file mode 100644 index 000000000..9e1d050e3 --- /dev/null +++ b/apps/tests/aws-runtime-cdk/scripts/report-violations.ts @@ -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, "")}`; + } +} diff --git a/apps/tests/aws-runtime-cdk/src/app.mts b/apps/tests/aws-runtime-cdk/src/app.mts index a1f0ca817..7a8d6c9f4 100644 --- a/apps/tests/aws-runtime-cdk/src/app.mts +++ b/apps/tests/aws-runtime-cdk/src/app.mts @@ -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, @@ -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); @@ -56,6 +65,9 @@ const testService = new eventual.Service( TEST_QUEUE_URL: testQueue.queueUrl, TEST_TABLE_NAME: testTable.tableName, }, + compliance: { + standards: [ComplianceStandard.HIPAA], + }, system: { workflowService: { logLevel: LogLevel.DEBUG, @@ -79,6 +91,27 @@ const testService = new eventual.Service( } ); +// these run linting rules on the CDK code and should all pass to enforce compliance +enableNagPack(AwsSolutionsChecks); +enableNagPack(HIPAASecurityChecks); +function enableNagPack

( + 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); @@ -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":""}', - }, - 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":""}', }, + name: stack.stackName + "_pipe", + roleArn: pipeRole.roleArn, + source: testQueue.queueArn, + target: asyncWriterFunction.functionArn, }); new ServiceDashboard(stack, "dashboard", { diff --git a/apps/tests/aws-runtime/package.json b/apps/tests/aws-runtime/package.json index ff2761745..bc17314b2 100644 --- a/apps/tests/aws-runtime/package.json +++ b/apps/tests/aws-runtime/package.json @@ -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", diff --git a/package.json b/package.json index 47065e956..413c6a779 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/@eventual/aws-cdk/package.json b/packages/@eventual/aws-cdk/package.json index a3a3bc08c..5db3e9f8b 100644 --- a/packages/@eventual/aws-cdk/package.json +++ b/packages/@eventual/aws-cdk/package.json @@ -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", diff --git a/packages/@eventual/aws-cdk/src/bucket-service.ts b/packages/@eventual/aws-cdk/src/bucket-service.ts index cda0326bd..d0b9b3b52 100644 --- a/packages/@eventual/aws-cdk/src/bucket-service.ts +++ b/packages/@eventual/aws-cdk/src/bucket-service.ts @@ -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 = Partial< ServiceEntityProps< @@ -141,6 +142,12 @@ export class BucketService { ], }) ); + if (this.props.compliancePolicy.isCustomerManagedKeys()) { + // data in the buckets are encrypted with a key that the customer owns + this.props.compliancePolicy.dataEncryptionKey.grantEncryptDecrypt( + grantee + ); + } } private readonly ENV_MAPPINGS = { @@ -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 && @@ -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}`, diff --git a/packages/@eventual/aws-cdk/src/command-service.ts b/packages/@eventual/aws-cdk/src/command-service.ts index 50449e104..9929c6572 100644 --- a/packages/@eventual/aws-cdk/src/command-service.ts +++ b/packages/@eventual/aws-cdk/src/command-service.ts @@ -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; @@ -180,6 +181,7 @@ export class CommandService { commandsSystemScope, "SystemCommandHandler", { + compliancePolicy: props.compliancePolicy, build: this.props.build, bundledFunction: this.props.build.system.eventualService.systemCommandHandler, @@ -242,6 +244,7 @@ export class CommandService { scope, commandNamespaceName(command), { + compliancePolicy: props.compliancePolicy, build: self.props.build, bundledFunction: manifest, functionNameSuffix: commandFunctionNameSuffix(command), @@ -330,13 +333,14 @@ export class CommandService { 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 diff --git a/packages/@eventual/aws-cdk/src/compliance.ts b/packages/@eventual/aws-cdk/src/compliance.ts new file mode 100644 index 000000000..0382bc41d --- /dev/null +++ b/packages/@eventual/aws-cdk/src/compliance.ts @@ -0,0 +1,127 @@ +import { IBucket } from "aws-cdk-lib/aws-s3"; +import { Construct } from "constructs"; +import { SecureKey } from "./secure/key"; +import { aws_iam } from "aws-cdk-lib"; + +export enum ComplianceStandard { + HIPAA = "HIPAA", +} + +export interface CompliancePolicyProps { + /** + * A list of {@link ComplianceStandard}s that this Service must comply with. + */ + standards?: ComplianceStandard[]; + /** + * The Landing Zone's Audit Bucket. + * + * TODO: implement this + * + * @see https://docs.aws.amazon.com/controltower/latest/userguide/planning-your-deployment.html + */ + auditBucket?: IBucket; + /** + * The Landing Zone's Logging Bucket. + * + * * TODO: implement this + * + * @see https://docs.aws.amazon.com/controltower/latest/userguide/planning-your-deployment.html + */ + loggingBucket?: IBucket; +} + +export enum EncryptionKeyOwnership { + CUSTOMER_OWNED = "CUSTOMER_OWNED", + AWS_MANAGED = "AWS_MANAGED", + AWS_OWNED = "AWS_OWNED", +} + +/** + * Centralized control of Security and Compliance features. + * + * E.g. KMS keys + */ +export class Compliance extends Construct { + /** + * A list of {@link ComplianceStandard}s that this Service must comply with. + */ + public readonly standards: ComplianceStandard[]; + /** + * A KMS Key for encrypting logs at test. + */ + public readonly logEncryptionKey: SecureKey | undefined; + /** + * A KMS key for encrypting application data at rest. + */ + public readonly dataEncryptionKey: SecureKey | undefined; + + constructor(scope: Construct, id: string, props?: CompliancePolicyProps) { + super(scope, id); + this.standards = props?.standards ?? []; + + if (this.encryptionKeyOwnership === EncryptionKeyOwnership.CUSTOMER_OWNED) { + this.logEncryptionKey = new SecureKey(this, "LogEncryptionKey", { + enableKeyRotation: true, + }); + this.logEncryptionKey.grantEncryptDecrypt( + new aws_iam.ServicePrincipal( + aws_iam.ServicePrincipal.servicePrincipalName("logs.amazonaws.com") + ) + ); + + this.dataEncryptionKey = new SecureKey(this, "DataEncryptionKey", { + enableKeyRotation: true, + }); + } + } + + /** + * Does the compliant standards mandate that SSL is used when communicating with AWS resources. + * + * TODO: I can't think of a reason to not always enable this. + */ + public get isSSLRequired(): boolean { + return this.standards.includes(ComplianceStandard.HIPAA); + } + + /** + * The lowest level of security - AWS owns the keys and the customer has no visibility into them. + */ + public isAWSOwnedKeys(): this is { + dataEncryptionKey: undefined; + logEncryptionKey: undefined; + } { + return this.encryptionKeyOwnership === EncryptionKeyOwnership.AWS_OWNED; + } + + /** + * The customer owns the keys but AWS manages their lifecycle. + */ + public isAWSManagedKeys(): this is { + dataEncryptionKey: undefined; + logEncryptionKey: undefined; + } { + return this.encryptionKeyOwnership === EncryptionKeyOwnership.AWS_MANAGED; + } + + /** + * The customer owns the keys and manages their lifecycle. + */ + public isCustomerManagedKeys(): this is { + dataEncryptionKey: SecureKey; + logEncryptionKey: SecureKey; + } { + return ( + this.encryptionKeyOwnership === EncryptionKeyOwnership.CUSTOMER_OWNED + ); + } + + public get encryptionKeyOwnership(): EncryptionKeyOwnership { + if (this.standards.includes(ComplianceStandard.HIPAA)) { + // HIPAA requires the customer to have transparency into the key but AWS can manage its lifecycle + // TODO: link to a reference + return EncryptionKeyOwnership.AWS_MANAGED; + } + return EncryptionKeyOwnership.AWS_OWNED; + } +} diff --git a/packages/@eventual/aws-cdk/src/constructs/event-bridge-pipe.ts b/packages/@eventual/aws-cdk/src/constructs/event-bridge-pipe.ts index d595978d3..458662aa4 100644 --- a/packages/@eventual/aws-cdk/src/constructs/event-bridge-pipe.ts +++ b/packages/@eventual/aws-cdk/src/constructs/event-bridge-pipe.ts @@ -1,4 +1,4 @@ -import { CfnResource } from "aws-cdk-lib/core"; +import { Resource } from "aws-cdk-lib/core"; import { IGrantable, IPrincipal, @@ -6,16 +6,18 @@ import { Role, ServicePrincipal, } from "aws-cdk-lib/aws-iam"; +import { CfnPipe, CfnPipeProps } from "aws-cdk-lib/aws-pipes"; import { Construct } from "constructs"; -export interface EventBridgePipeProps { +export interface EventBridgePipeProps extends Omit { role?: IRole; - source: string; - target: string; - sourceParameters: PipeSourceParameters; - targetParameters: PipeTargetParameters; } +export type PipeSourceParameters = Exclude< + CfnPipeProps["sourceParameters"], + undefined +>; + /** * Note: this is an incomplete set of possible arguments for an sqs pipe target. */ @@ -50,13 +52,7 @@ export interface PipeDynamoDBStreamParameters { MaximumBatchingWindowInSeconds: number; } -export interface PipeSourceParameters { - DynamoDBStreamParameters: PipeDynamoDBStreamParameters; - FilterCriteria?: { Filters: { Pattern: string }[] }; - [key: string]: any; -} - -export class EventBridgePipe extends Construct implements IGrantable { +export class EventBridgePipe extends Resource implements IGrantable { public grantPrincipal: IPrincipal; constructor(scope: Construct, id: string, props: EventBridgePipeProps) { @@ -68,15 +64,12 @@ export class EventBridgePipe extends Construct implements IGrantable { this.grantPrincipal = pipeRole; - new CfnResource(scope, "Resource", { - type: "AWS::Pipes::Pipe", - properties: { - RoleArn: pipeRole.roleArn, - Source: props.source, - SourceParameters: props.sourceParameters, - Target: props.target, - TargetParameters: props.targetParameters, - }, + new CfnPipe(scope, "Resource", { + roleArn: pipeRole.roleArn, + source: props.source, + target: props.target, + sourceParameters: props.sourceParameters, + targetParameters: props.targetParameters, }); } } diff --git a/packages/@eventual/aws-cdk/src/entity-service.ts b/packages/@eventual/aws-cdk/src/entity-service.ts index 4b6b368c9..ee3e63eb5 100644 --- a/packages/@eventual/aws-cdk/src/entity-service.ts +++ b/packages/@eventual/aws-cdk/src/entity-service.ts @@ -20,7 +20,6 @@ import { BillingMode, ITable, StreamViewType, - Table, } from "aws-cdk-lib/aws-dynamodb"; import { IGrantable, IPrincipal, PolicyStatement } from "aws-cdk-lib/aws-iam"; import { @@ -43,6 +42,7 @@ import { ServiceFunction } from "./service-function"; import { ServiceEntityProps, serviceTableArn } from "./utils"; import { WorkflowService } from "./workflow-service.js"; import { EventualResource } from "./resource.js"; +import { SecureTable } from "./secure/table.js"; export type ServiceEntities = ServiceEntityProps< Service, @@ -126,6 +126,7 @@ export class EntityService { entityServiceConstruct, "TransactionWorker", { + compliancePolicy: props.compliancePolicy, build: props.build, bundledFunction: props.build.system.entityService.transactionWorker, functionNameSuffix: "transaction-worker", @@ -154,6 +155,13 @@ export class EntityService { } public grantReadWriteEntityTables(grantee: IGrantable) { + if (this.props.compliancePolicy.isCustomerManagedKeys()) { + // the Tables are encrypted with a CMK, so grant the permission to use it + this.props.compliancePolicy.dataEncryptionKey.grantEncryptDecrypt( + grantee + ); + } + // grants the permission to start any task grantee.grantPrincipal.addToPrincipalPolicy( new PolicyStatement({ @@ -226,7 +234,8 @@ class Entity extends Construct { const keyDefinition = props.entity.key; - const table = (this.table = new Table(this, "Table", { + const table = (this.table = new SecureTable(this, "Table", { + compliancePolicy: props.serviceProps.compliancePolicy, tableName: entityServiceTableName( props.serviceProps.serviceName, props.entity.name @@ -344,6 +353,7 @@ export class EntityStream extends Construct implements EventualResource { this.handler = new ServiceFunction(this, "Handler", { build: props.serviceProps.build, + compliancePolicy: props.serviceProps.compliancePolicy, bundledFunction: props.stream, functionNameSuffix: `entity-stream-${entityName}-${streamName}`, serviceName: props.serviceProps.serviceName, diff --git a/packages/@eventual/aws-cdk/src/index.ts b/packages/@eventual/aws-cdk/src/index.ts index 73e5834ce..dffdff1ec 100644 --- a/packages/@eventual/aws-cdk/src/index.ts +++ b/packages/@eventual/aws-cdk/src/index.ts @@ -1,3 +1,4 @@ +export * from "./compliance.js"; export * from "./debug-dashboard.js"; export * from "./service-dashboard.js"; export * from "./service.js"; diff --git a/packages/@eventual/aws-cdk/src/queue-service.ts b/packages/@eventual/aws-cdk/src/queue-service.ts index b6afcba25..d62337f7d 100644 --- a/packages/@eventual/aws-cdk/src/queue-service.ts +++ b/packages/@eventual/aws-cdk/src/queue-service.ts @@ -19,6 +19,7 @@ import { } from "./service-common"; import { ServiceFunction } from "./service-function"; import { ServiceEntityProps, formatQueueArn, serviceQueueArn } from "./utils"; +import { SecureQueue } from "./secure/queue"; export type QueueHandlerFunctionProps = Omit< Partial, @@ -71,6 +72,13 @@ export class QueueService { } public grantSendAndManageMessage(grantee: IGrantable) { + if (this.props.compliancePolicy.isCustomerManagedKeys()) { + // the Queues are encrypted with a CMK, so grant the permission to use it + this.props.compliancePolicy.dataEncryptionKey.grantEncryptDecrypt( + grantee + ); + } + // find any queue names that were provided by the service and not computed const queueNameOverrides = this.props.queueOverrides ? Object.values( @@ -132,7 +140,8 @@ class Queue extends Construct implements IQueue { const { handler, ...overrides } = props.serviceProps.queueOverrides?.[props.queue.name] ?? {}; - this.queue = new sqs.Queue(this, "Queue", { + this.queue = new SecureQueue(this, "Queue", { + compliancePolicy: props.serviceProps.compliancePolicy, contentBasedDeduplication: props.queue.contentBasedDeduplication, deliveryDelay: props.queue.delay ? Duration.seconds(computeDurationSeconds(props.queue.delay)) @@ -148,10 +157,6 @@ class Queue extends Construct implements IQueue { computeDurationSeconds(props.queue.visibilityTimeout) ) : undefined, - // TODO: support customer managed key - encryption: props.queue.encryption - ? sqs.QueueEncryption.SQS_MANAGED - : sqs.QueueEncryption.UNENCRYPTED, ...overrides, }); @@ -180,6 +185,7 @@ export class QueueHandler extends Construct implements EventualResource { const queueName = props.runtimeQueue.name; this.handler = new ServiceFunction(this, "Handler", { + compliancePolicy: props.serviceProps.compliancePolicy, build: props.serviceProps.build, bundledFunction: props.runtimeQueue.handler, functionNameSuffix: `queue-handler-${queueName}`, diff --git a/packages/@eventual/aws-cdk/src/scheduler-service.ts b/packages/@eventual/aws-cdk/src/scheduler-service.ts index c4868f1e4..23b4a7177 100644 --- a/packages/@eventual/aws-cdk/src/scheduler-service.ts +++ b/packages/@eventual/aws-cdk/src/scheduler-service.ts @@ -1,5 +1,4 @@ import { ENV_NAMES } from "@eventual/aws-runtime"; -import { ArnFormat, CfnResource, Resource, Stack } from "aws-cdk-lib/core"; import { IGrantable, IRole, @@ -9,15 +8,18 @@ import { } from "aws-cdk-lib/aws-iam"; import { Function } from "aws-cdk-lib/aws-lambda"; import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources"; +import { CfnScheduleGroup } from "aws-cdk-lib/aws-scheduler"; import { IQueue, Queue } from "aws-cdk-lib/aws-sqs"; +import { ArnFormat, Resource, Stack } from "aws-cdk-lib/core"; import { Construct } from "constructs"; import { grant } from "./grant"; import { LazyInterface } from "./proxy-construct"; +import { SecureQueue } from "./secure/queue"; +import { ServiceConstructProps } from "./service-common"; import { ServiceFunction } from "./service-function"; import type { TaskService } from "./task-service.js"; import { serviceFunctionArn } from "./utils"; import { WorkflowService } from "./workflow-service"; -import { ServiceConstructProps } from "./service-common"; export interface SchedulerProps extends ServiceConstructProps { /** @@ -89,13 +91,18 @@ export class SchedulerService { }), }); - this.dlq = new Queue(schedulerServiceScope, "DeadLetterQueue"); + this.dlq = new SecureQueue(schedulerServiceScope, "DeadLetterQueue", { + compliancePolicy: props.compliancePolicy, + }); this.dlq.grantSendMessages(this.schedulerRole); - this.queue = new Queue(schedulerServiceScope, "Queue"); + this.queue = new SecureQueue(schedulerServiceScope, "Queue", { + compliancePolicy: props.compliancePolicy, + }); // TODO: handle failures to a DLQ - https://github.com/functionless/eventual/issues/40 this.forwarder = new ServiceFunction(schedulerServiceScope, "Forwarder", { + compliancePolicy: props.compliancePolicy, build: props.build, bundledFunction: props.build.system.schedulerService.forwarder, functionNameSuffix: "scheduler-forwarder", @@ -106,6 +113,7 @@ export class SchedulerService { this.forwarder.grantInvoke(this.schedulerRole); this.handler = new ServiceFunction(schedulerServiceScope, "handler", { + compliancePolicy: props.compliancePolicy, build: props.build, bundledFunction: props.build.system.schedulerService.timerHandler, functionNameSuffix: "scheduler-handler", @@ -228,14 +236,10 @@ export class SchedulerService { } class ScheduleGroup extends Resource { - public readonly resource: CfnResource; + public readonly resource: CfnScheduleGroup; constructor(scope: Construct, id: string) { super(scope, id); - - this.resource = new CfnResource(this, "Resource", { - type: "AWS::Scheduler::ScheduleGroup", - properties: {}, - }); + this.resource = new CfnScheduleGroup(this, "Resource"); } public get ref() { diff --git a/packages/@eventual/aws-cdk/src/search/base-search-service.ts b/packages/@eventual/aws-cdk/src/search/base-search-service.ts index ce4291f09..b128689cd 100644 --- a/packages/@eventual/aws-cdk/src/search/base-search-service.ts +++ b/packages/@eventual/aws-cdk/src/search/base-search-service.ts @@ -4,6 +4,7 @@ import type { Function } from "aws-cdk-lib/aws-lambda"; import * as aws_lambda from "aws-cdk-lib/aws-lambda"; import { Duration, Lazy } from "aws-cdk-lib/core"; import { Construct } from "constructs"; +import { SecureFunction } from "../secure/function"; import type { ServiceConstructProps } from "../service-common"; import type { ServiceEntityProps } from "../utils"; import { SearchIndex } from "./search-index"; @@ -57,11 +58,12 @@ export abstract class BaseSearchService super(props.systemScope, "SearchService"); this.indexRoot = new Construct(props.serviceScope, "Indices"); - this.customResourceHandler = new aws_lambda.Function( + this.customResourceHandler = new SecureFunction( this, "CustomResourceHandler", { - runtime: aws_lambda.Runtime.NODEJS_18_X, + compliancePolicy: props.compliancePolicy, + runtime: aws_lambda.Runtime.NODEJS_LATEST, handler: props.build.system.searchService.customResourceHandler.handler ?? "index.default", diff --git a/packages/@eventual/aws-cdk/src/secure/bucket.ts b/packages/@eventual/aws-cdk/src/secure/bucket.ts new file mode 100644 index 000000000..84742954c --- /dev/null +++ b/packages/@eventual/aws-cdk/src/secure/bucket.ts @@ -0,0 +1,45 @@ +import { + BlockPublicAccess, + Bucket, + BucketEncryption, + BucketProps, +} from "aws-cdk-lib/aws-s3"; +import { Construct } from "constructs"; +import type { Compliance } from "../compliance"; + +export interface SecureBucketProps extends BucketProps { + compliancePolicy: Compliance; +} + +export class SecureBucket extends Bucket { + constructor( + scope: Construct, + id: string, + { compliancePolicy, ...props }: SecureBucketProps + ) { + super(scope, id, { + enforceSSL: compliancePolicy.isSSLRequired, + encryption: compliancePolicy.isAWSOwnedKeys() + ? BucketEncryption.S3_MANAGED + : compliancePolicy.isAWSManagedKeys() + ? BucketEncryption.KMS_MANAGED + : BucketEncryption.KMS, + encryptionKey: compliancePolicy.isCustomerManagedKeys() + ? compliancePolicy.dataEncryptionKey + : undefined, + bucketKeyEnabled: + compliancePolicy.isAWSManagedKeys() || + compliancePolicy.isCustomerManagedKeys() + ? true + : undefined, + blockPublicAccess: BlockPublicAccess.BLOCK_ALL, + // TODO: what is the right value here? + // versioned: true, + publicReadAccess: false, + // TODO: is this needed for cloud trail? + serverAccessLogsBucket: undefined, + serverAccessLogsPrefix: undefined, + ...props, + }); + } +} diff --git a/packages/@eventual/aws-cdk/src/secure/function.ts b/packages/@eventual/aws-cdk/src/secure/function.ts new file mode 100644 index 000000000..36ef088fe --- /dev/null +++ b/packages/@eventual/aws-cdk/src/secure/function.ts @@ -0,0 +1,21 @@ +import { Function, FunctionProps } from "aws-cdk-lib/aws-lambda"; +import type { Construct } from "constructs"; +import type { Compliance } from "../compliance"; +import { SecureLogGroup } from "./log-group"; + +export interface SecureFunctionProps extends FunctionProps { + compliancePolicy: Compliance; +} + +export class SecureFunction extends Function { + constructor(scope: Construct, id: string, props: SecureFunctionProps) { + super(scope, id, props); + + if (props.compliancePolicy.isCustomerManagedKeys()) { + new SecureLogGroup(this, "LogGroup", { + compliancePolicy: props.compliancePolicy, + logGroupName: `/aws/lambda/${this.functionName}`, + }); + } + } +} diff --git a/packages/@eventual/aws-cdk/src/secure/key.ts b/packages/@eventual/aws-cdk/src/secure/key.ts new file mode 100644 index 000000000..9afdce4cc --- /dev/null +++ b/packages/@eventual/aws-cdk/src/secure/key.ts @@ -0,0 +1,3 @@ +import { aws_kms } from "aws-cdk-lib"; + +export class SecureKey extends aws_kms.Key {} diff --git a/packages/@eventual/aws-cdk/src/secure/log-group.ts b/packages/@eventual/aws-cdk/src/secure/log-group.ts new file mode 100644 index 000000000..80a924881 --- /dev/null +++ b/packages/@eventual/aws-cdk/src/secure/log-group.ts @@ -0,0 +1,33 @@ +import { LogGroup, LogGroupProps } from "aws-cdk-lib/aws-logs"; +import { Construct } from "constructs"; +import { Compliance } from "../compliance"; + +export interface SecureLogGroupProps extends LogGroupProps { + compliancePolicy: Compliance; +} + +export class SecureLogGroup extends LogGroup { + constructor( + scope: Construct, + id: string, + { compliancePolicy, ...props }: SecureLogGroupProps + ) { + super(scope, id, { + encryptionKey: compliancePolicy.logEncryptionKey, + + // TODO: retention? + // retention: undefined, + + // TODO: configure data protection policy to scan for PII and report findings to CloudTrail + // dataProtectionPolicy: new DataProtectionPolicy({ + // name, + // description, + // identifiers, + // logGroupAuditDestination, + // s3BucketAuditDestination, + // deliveryStreamNameAuditDestination, + // }), + ...props, + }); + } +} diff --git a/packages/@eventual/aws-cdk/src/secure/queue.ts b/packages/@eventual/aws-cdk/src/secure/queue.ts new file mode 100644 index 000000000..269570071 --- /dev/null +++ b/packages/@eventual/aws-cdk/src/secure/queue.ts @@ -0,0 +1,31 @@ +import { Queue, QueueEncryption, QueueProps } from "aws-cdk-lib/aws-sqs"; +import { Duration } from "aws-cdk-lib/core"; +import { Construct } from "constructs"; +import type { Compliance } from "../compliance"; + +export interface SecureQueueProps extends QueueProps { + compliancePolicy: Compliance; +} + +export class SecureQueue extends Queue { + constructor( + scope: Construct, + id: string, + { compliancePolicy, ...props }: SecureQueueProps + ) { + super(scope, id, { + enforceSSL: compliancePolicy.isSSLRequired, + encryption: compliancePolicy.isAWSOwnedKeys() + ? QueueEncryption.SQS_MANAGED + : compliancePolicy.isAWSManagedKeys() + ? QueueEncryption.KMS_MANAGED + : QueueEncryption.KMS, + encryptionMasterKey: compliancePolicy.isCustomerManagedKeys() + ? compliancePolicy.dataEncryptionKey + : undefined, + // set to maximum so we always have time to respond + retentionPeriod: Duration.days(14), + ...props, + }); + } +} diff --git a/packages/@eventual/aws-cdk/src/secure/table.ts b/packages/@eventual/aws-cdk/src/secure/table.ts new file mode 100644 index 000000000..755dfd85b --- /dev/null +++ b/packages/@eventual/aws-cdk/src/secure/table.ts @@ -0,0 +1,36 @@ +import { + BillingMode, + StreamViewType, + Table, + TableEncryption, + TableProps, +} from "aws-cdk-lib/aws-dynamodb"; +import { Construct } from "constructs"; +import { type Compliance } from "../compliance"; + +export interface SecureTableProps extends TableProps { + compliancePolicy: Compliance; +} + +export class SecureTable extends Table { + constructor( + scope: Construct, + id: string, + { compliancePolicy, ...props }: SecureTableProps + ) { + super(scope, id, { + billingMode: BillingMode.PAY_PER_REQUEST, // Or adjust as needed + encryption: compliancePolicy.isAWSOwnedKeys() + ? TableEncryption.DEFAULT + : compliancePolicy.isAWSManagedKeys() + ? TableEncryption.AWS_MANAGED + : TableEncryption.CUSTOMER_MANAGED, + encryptionKey: compliancePolicy.isCustomerManagedKeys() + ? compliancePolicy.dataEncryptionKey + : undefined, + pointInTimeRecovery: true, // Ensure data recovery + stream: StreamViewType.NEW_AND_OLD_IMAGES, // For keeping track of all changes + ...props, + }); + } +} diff --git a/packages/@eventual/aws-cdk/src/service-common.ts b/packages/@eventual/aws-cdk/src/service-common.ts index 07192c32b..8ce0a7074 100644 --- a/packages/@eventual/aws-cdk/src/service-common.ts +++ b/packages/@eventual/aws-cdk/src/service-common.ts @@ -1,14 +1,15 @@ -import { Function } from "aws-cdk-lib/aws-lambda"; -import { Construct } from "constructs"; -import { BucketService } from "./bucket-service"; -import { BuildOutput } from "./build"; -import { CommandService } from "./command-service"; -import { EntityService } from "./entity-service"; -import { LazyInterface } from "./proxy-construct"; -import { QueueService } from "./queue-service"; -import { SearchService } from "./search/search-service"; -import { Service } from "./service"; -import { SocketService } from "./socket-service"; +import type { Function } from "aws-cdk-lib/aws-lambda"; +import type { Construct } from "constructs"; +import type { BucketService } from "./bucket-service"; +import type { BuildOutput } from "./build"; +import type { CommandService } from "./command-service"; +import type { EntityService } from "./entity-service"; +import type { LazyInterface } from "./proxy-construct"; +import type { QueueService } from "./queue-service"; +import type { SearchService } from "./search/search-service"; +import type { Service } from "./service"; +import type { SocketService } from "./socket-service"; +import type { Compliance } from "./compliance"; export interface ServiceConstructProps { /** @@ -26,6 +27,7 @@ export interface ServiceConstructProps { readonly serviceScope: Construct; readonly systemScope: Construct; readonly eventualServiceScope: Construct; + readonly compliancePolicy: Compliance; } /** diff --git a/packages/@eventual/aws-cdk/src/service-function.ts b/packages/@eventual/aws-cdk/src/service-function.ts index 63376df44..3dc48e348 100644 --- a/packages/@eventual/aws-cdk/src/service-function.ts +++ b/packages/@eventual/aws-cdk/src/service-function.ts @@ -4,10 +4,12 @@ import { BundledFunction, computeDurationSeconds, } from "@eventual/core-runtime"; +import { FunctionProps } from "aws-cdk-lib/aws-lambda"; import { Duration, Stack } from "aws-cdk-lib/core"; -import { Function, FunctionProps } from "aws-cdk-lib/aws-lambda"; import { Construct } from "constructs"; import type { BuildOutput } from "./build"; +import type { Compliance } from "./compliance"; +import { SecureFunction } from "./secure/function"; import { baseFnProps } from "./utils"; export interface ServiceFunctionProps { @@ -18,6 +20,7 @@ export interface ServiceFunctionProps { runtimeProps?: FunctionRuntimeProps; serviceName: string; functionNameSuffix: string; + compliancePolicy: Compliance; } /** @@ -29,12 +32,13 @@ export interface ServiceFunctionProps { * * Deep Merged: environment */ -export class ServiceFunction extends Function { +export class ServiceFunction extends SecureFunction { constructor(scope: Construct, id: string, props: ServiceFunctionProps) { super(scope, id, { ...baseFnProps, ...props.defaults, ...props.overrides, + compliancePolicy: props.compliancePolicy, functionName: serviceFunctionName( props.serviceName, props.functionNameSuffix diff --git a/packages/@eventual/aws-cdk/src/service.ts b/packages/@eventual/aws-cdk/src/service.ts index 63b592585..8990ba997 100644 --- a/packages/@eventual/aws-cdk/src/service.ts +++ b/packages/@eventual/aws-cdk/src/service.ts @@ -3,12 +3,7 @@ import { ENV_NAMES } from "@eventual/aws-runtime"; import { Event } from "@eventual/core"; import { MetricsCommon, OrchestratorMetrics } from "@eventual/core-runtime"; import { EventualConfig, discoverEventualConfigSync } from "@eventual/project"; -import { - Metric, - MetricOptions, - Statistic, - Unit, -} from "aws-cdk-lib/aws-cloudwatch"; +import { Metric, MetricOptions, Stats, Unit } from "aws-cdk-lib/aws-cloudwatch"; import aws_events from "aws-cdk-lib/aws-events"; import aws_events_targets from "aws-cdk-lib/aws-events-targets"; import { @@ -69,6 +64,7 @@ import { SchedulerService } from "./scheduler-service"; import { SearchService, SearchServiceOverrides } from "./search/search-service"; import { ServerfulSearchService } from "./search/serverful-search-service"; import { ServerlessSearchService } from "./search/serverless-search-service"; +import { Compliance, CompliancePolicyProps } from "./compliance"; import { ServiceConstructProps, WorkerServiceConstructProps, @@ -184,6 +180,7 @@ export interface ServiceProps { workflowService?: WorkflowServiceOverrides; entityService?: EntityServiceProps["entityServiceOverrides"]; }; + compliance?: CompliancePolicyProps; } export interface ServiceSystem { @@ -296,8 +293,14 @@ export class Service extends Construct { * Enable local mode by setting environment variable EVENTUAL_LOCAL=1 in deployment environment. */ public readonly local?: ServiceLocal; - + /** + * The search service for this service. + */ public readonly searchService: SearchService | undefined; + /** + * Centralized control of Security and Compliance features. + */ + public readonly compliancePolicy: Compliance; constructor(scope: Construct, id: string, props: ServiceProps) { super(scope, id); @@ -318,6 +321,13 @@ export class Service extends Construct { const serviceScope = this; const systemScope = new Construct(this, "System"); + + this.compliancePolicy = new Compliance( + systemScope, + "Compliance", + props.compliance + ); + const eventualServiceScope = new Construct(systemScope, "EventualService"); const accessRole = new Role(eventualServiceScope, "AccessRole", { @@ -366,6 +376,7 @@ export class Service extends Construct { serviceScope, systemScope, eventualServiceScope, + compliancePolicy: this.compliancePolicy, }; const workerConstructProps: WorkerServiceConstructProps = { @@ -737,7 +748,7 @@ export class Service extends Construct { public metricAdvanceExecutionDuration(options?: MetricOptions): Metric { return this.metric({ - statistic: Statistic.AVERAGE, + statistic: Stats.AVERAGE, metricName: OrchestratorMetrics.AdvanceExecutionDuration, unit: Unit.MILLISECONDS, ...options, @@ -746,7 +757,7 @@ export class Service extends Construct { public metricCommandsInvoked(options?: MetricOptions): Metric { return this.metric({ - statistic: Statistic.AVERAGE, + statistic: Stats.AVERAGE, metricName: OrchestratorMetrics.CallsInvoked, unit: Unit.COUNT, ...options, @@ -755,7 +766,7 @@ export class Service extends Construct { public metricInvokeCommandsDuration(options?: MetricOptions): Metric { return this.metric({ - statistic: Statistic.AVERAGE, + statistic: Stats.AVERAGE, metricName: OrchestratorMetrics.InvokeCallsDuration, unit: Unit.MILLISECONDS, ...options, @@ -764,7 +775,7 @@ export class Service extends Construct { public metricLoadHistoryDuration(options?: MetricOptions): Metric { return this.metric({ - statistic: Statistic.AVERAGE, + statistic: Stats.AVERAGE, metricName: OrchestratorMetrics.LoadHistoryDuration, unit: Unit.MILLISECONDS, ...options, @@ -773,7 +784,7 @@ export class Service extends Construct { public metricSaveHistoryDuration(options?: MetricOptions): Metric { return this.metric({ - statistic: Statistic.AVERAGE, + statistic: Stats.AVERAGE, metricName: OrchestratorMetrics.SaveHistoryDuration, unit: Unit.MILLISECONDS, ...options, @@ -784,7 +795,7 @@ export class Service extends Construct { return this.metric({ metricName: OrchestratorMetrics.SavedHistoryBytes, unit: Unit.BYTES, - statistic: Statistic.AVERAGE, + statistic: Stats.AVERAGE, ...options, }); } @@ -793,14 +804,14 @@ export class Service extends Construct { return this.metric({ metricName: OrchestratorMetrics.SavedHistoryEvents, unit: Unit.COUNT, - statistic: Statistic.AVERAGE, + statistic: Stats.AVERAGE, ...options, }); } public metricMaxTaskAge(options?: MetricOptions): Metric { return this.metric({ - statistic: Statistic.AVERAGE, + statistic: Stats.AVERAGE, metricName: OrchestratorMetrics.MaxTaskAge, unit: Unit.MILLISECONDS, ...options, diff --git a/packages/@eventual/aws-cdk/src/socket-service.ts b/packages/@eventual/aws-cdk/src/socket-service.ts index a2774eaf8..1999a2cdc 100644 --- a/packages/@eventual/aws-cdk/src/socket-service.ts +++ b/packages/@eventual/aws-cdk/src/socket-service.ts @@ -127,6 +127,7 @@ class Socket extends Construct implements EventualResource, ISocket { super(scope, socketName); this.handler = new ServiceFunction(this, "DefaultHandler", { + compliancePolicy: props.serviceProps.compliancePolicy, build: props.serviceProps.build, bundledFunction: props.socket, functionNameSuffix: `socket-${socketName}-default`, diff --git a/packages/@eventual/aws-cdk/src/subscriptions.ts b/packages/@eventual/aws-cdk/src/subscriptions.ts index 13e31641d..679f36db7 100644 --- a/packages/@eventual/aws-cdk/src/subscriptions.ts +++ b/packages/@eventual/aws-cdk/src/subscriptions.ts @@ -9,6 +9,9 @@ import { Construct } from "constructs"; import type { BuildOutput } from "./build"; import { DeepCompositePrincipal } from "./deep-composite-principal"; import type { EventService } from "./event-service"; +import { EventualResource } from "./resource"; +import type { Compliance } from "./compliance"; +import { SecureQueue } from "./secure/queue"; import type { ServiceLocal } from "./service"; import { WorkerServiceConstructProps, @@ -16,7 +19,6 @@ import { } from "./service-common"; import { ServiceFunction } from "./service-function"; import type { ServiceEntityProps } from "./utils"; -import { EventualResource } from "./resource"; export type Subscriptions = ServiceEntityProps< Service, @@ -59,6 +61,7 @@ export const Subscriptions: { return [ sub.spec.name, new Subscription(subscriptionsServiceScope, sub.spec.name, { + compliancePolicy: props.compliancePolicy, build: props.build, bus: props.eventService.bus, serviceName: props.serviceName, @@ -95,6 +98,7 @@ export interface SubscriptionProps { environment?: Record; bus: IEventBus; local: ServiceLocal | undefined; + compliancePolicy: Compliance; } export class Subscription extends Construct implements EventualResource { @@ -115,8 +119,11 @@ export class Subscription extends Construct implements EventualResource { super(scope, id); const subscription = props.subscription.spec; - this.deadLetterQueue = new Queue(this, "DeadLetterQueue"); + this.deadLetterQueue = new SecureQueue(this, "DeadLetterQueue", { + compliancePolicy: props.compliancePolicy, + }); this.handler = new ServiceFunction(this, "Handler", { + compliancePolicy: props.compliancePolicy, build: props.build, serviceName: props.serviceName, functionNameSuffix: subscriptionServiceFunctionSuffix(subscription.name), diff --git a/packages/@eventual/aws-cdk/src/task-service.ts b/packages/@eventual/aws-cdk/src/task-service.ts index 305e39740..dc449b845 100644 --- a/packages/@eventual/aws-cdk/src/task-service.ts +++ b/packages/@eventual/aws-cdk/src/task-service.ts @@ -1,21 +1,18 @@ import { ENV_NAMES, taskServiceFunctionSuffix } from "@eventual/aws-runtime"; import type { TaskFunction } from "@eventual/core-runtime"; -import { - AttributeType, - BillingMode, - ITable, - Table, -} from "aws-cdk-lib/aws-dynamodb"; +import { AttributeType, BillingMode, ITable } from "aws-cdk-lib/aws-dynamodb"; import aws_iam, { IGrantable, PolicyStatement } from "aws-cdk-lib/aws-iam"; import { Function, FunctionProps } from "aws-cdk-lib/aws-lambda"; import { LambdaDestination } from "aws-cdk-lib/aws-lambda-destinations"; -import { Duration, RemovalPolicy, Stack } from "aws-cdk-lib/core"; +import { Duration, Stack } from "aws-cdk-lib/core"; import { Construct } from "constructs"; import type { BuildOutput } from "./build"; import { DeepCompositePrincipal } from "./deep-composite-principal"; import { grant } from "./grant"; import type { LazyInterface } from "./proxy-construct"; +import { EventualResource } from "./resource"; import type { SchedulerService } from "./scheduler-service"; +import { SecureTable } from "./secure/table"; import type { ServiceLocal } from "./service"; import { WorkerServiceConstructProps, @@ -24,7 +21,7 @@ import { import { ServiceFunction } from "./service-function"; import { ServiceEntityProps, serviceFunctionArn } from "./utils"; import type { WorkflowService } from "./workflow-service"; -import { EventualResource } from "./resource"; +import type { Compliance } from "./compliance"; export type ServiceTasks = ServiceEntityProps; @@ -61,20 +58,20 @@ export class TaskService { constructor(private props: TasksProps) { const taskServiceScope = new Construct(props.systemScope, "TaskService"); - this.table = new Table(taskServiceScope, "Table", { + this.table = new SecureTable(taskServiceScope, "Table", { + compliancePolicy: props.compliancePolicy, billingMode: BillingMode.PAY_PER_REQUEST, partitionKey: { name: "pk", type: AttributeType.STRING, }, - // TODO: remove after testing - removalPolicy: RemovalPolicy.DESTROY, }); this.fallbackHandler = new ServiceFunction( taskServiceScope, "FallbackHandler", { + compliancePolicy: props.compliancePolicy, bundledFunction: props.build.system.taskService.fallbackHandler, build: props.build, functionNameSuffix: taskServiceFunctionSuffix( @@ -88,6 +85,7 @@ export class TaskService { this.tasks = Object.fromEntries( props.build.tasks.map((t) => { const task = new Task(taskScope, t.spec.name, { + compliancePolicy: props.compliancePolicy, task: t, build: props.build, codeFile: t.entry, @@ -254,6 +252,7 @@ export interface TaskProps { fallbackHandler: Function; overrides?: TaskHandlerProps; local?: ServiceLocal; + compliancePolicy: Compliance; } export class Task extends Construct implements EventualResource { @@ -264,6 +263,7 @@ export class Task extends Construct implements EventualResource { super(scope, id); this.handler = new ServiceFunction(this, "Worker", { + compliancePolicy: props.compliancePolicy, build: props.build, serviceName: props.serviceName, bundledFunction: props.task, @@ -280,6 +280,8 @@ export class Task extends Construct implements EventualResource { overrides: props.overrides, }); + // TODO: Dead Letter Queue? + this.grantPrincipal = props.local ? new DeepCompositePrincipal( props.local.environmentRole, diff --git a/packages/@eventual/aws-cdk/src/utils.ts b/packages/@eventual/aws-cdk/src/utils.ts index 0169f1c75..93a167da7 100644 --- a/packages/@eventual/aws-cdk/src/utils.ts +++ b/packages/@eventual/aws-cdk/src/utils.ts @@ -2,23 +2,14 @@ import { serviceFunctionName, socketServiceSocketName, } from "@eventual/aws-runtime"; +import { Architecture, FunctionProps, Runtime } from "aws-cdk-lib/aws-lambda"; import { ArnFormat, Stack } from "aws-cdk-lib/core"; -import { - Architecture, - FunctionProps, - Runtime, - RuntimeFamily, -} from "aws-cdk-lib/aws-lambda"; - -export const NODE_18_X = new Runtime("nodejs18.x", RuntimeFamily.NODEJS, { - supportsInlineCode: true, -}); export const baseFnProps: Pick< FunctionProps, "runtime" | "architecture" | "environment" > = { - runtime: NODE_18_X, + runtime: Runtime.NODEJS_LATEST, architecture: Architecture.ARM_64, environment: { NODE_OPTIONS: "--enable-source-maps", diff --git a/packages/@eventual/aws-cdk/src/workflow-service.ts b/packages/@eventual/aws-cdk/src/workflow-service.ts index 37534924f..171a008f9 100644 --- a/packages/@eventual/aws-cdk/src/workflow-service.ts +++ b/packages/@eventual/aws-cdk/src/workflow-service.ts @@ -7,18 +7,16 @@ import { ITable, ProjectionType, StreamViewType, - Table, } from "aws-cdk-lib/aws-dynamodb"; import { IGrantable } from "aws-cdk-lib/aws-iam"; import { Function } from "aws-cdk-lib/aws-lambda"; import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources"; import { LogGroup } from "aws-cdk-lib/aws-logs"; -import { Bucket, IBucket } from "aws-cdk-lib/aws-s3"; +import { IBucket } from "aws-cdk-lib/aws-s3"; import { DeduplicationScope, FifoThroughputLimit, IQueue, - Queue, } from "aws-cdk-lib/aws-sqs"; import { RemovalPolicy } from "aws-cdk-lib/core"; import { Construct } from "constructs"; @@ -31,13 +29,17 @@ import { EntityService } from "./entity-service"; import { EventService } from "./event-service"; import { grant } from "./grant"; import { LazyInterface } from "./proxy-construct"; +import { QueueService } from "./queue-service"; import { SchedulerService } from "./scheduler-service"; -import { ServiceFunction } from "./service-function"; -import type { TaskService } from "./task-service.js"; import type { SearchService } from "./search/search-service"; +import { SecureBucket } from "./secure/bucket"; +import { SecureLogGroup } from "./secure/log-group"; +import { SecureQueue } from "./secure/queue"; +import { SecureTable } from "./secure/table"; import { ServiceConstructProps } from "./service-common"; -import { QueueService } from "./queue-service"; +import { ServiceFunction } from "./service-function"; import { SocketService } from "./socket-service"; +import type { TaskService } from "./task-service.js"; export interface WorkflowsProps extends ServiceConstructProps { bucketService: LazyInterface>; @@ -119,22 +121,22 @@ export class WorkflowService { this.logGroup = props.overrides?.logGroup ?? - new LogGroup(props.serviceScope, "WorkflowExecutionLogs", { + new SecureLogGroup(props.serviceScope, "WorkflowExecutionLogs", { + compliancePolicy: props.compliancePolicy, removalPolicy: RemovalPolicy.DESTROY, logGroupName: `${props.serviceName}-execution-logs`, }); - this.history = new Bucket(workflowServiceScope, "HistoryBucket", { - // TODO: remove after testing - removalPolicy: RemovalPolicy.DESTROY, - autoDeleteObjects: true, + this.history = new SecureBucket(workflowServiceScope, "HistoryBucket", { + compliancePolicy: props.compliancePolicy, }); // Table - History, Executions - const executionsTable = (this.executionsTable = new Table( + const executionsTable = (this.executionsTable = new SecureTable( workflowServiceScope, "ExecutionTable", { + compliancePolicy: props.compliancePolicy, partitionKey: { name: "pk", type: AttributeType.STRING }, sortKey: { name: "sk", type: AttributeType.STRING }, billingMode: BillingMode.PAY_PER_REQUEST, @@ -152,10 +154,11 @@ export class WorkflowService { projectionType: ProjectionType.ALL, }); - this.executionHistoryTable = new Table( + this.executionHistoryTable = new SecureTable( workflowServiceScope, "ExecutionHistoryTable", { + compliancePolicy: props.compliancePolicy, partitionKey: { name: "pk", type: AttributeType.STRING }, sortKey: { name: "sk", type: AttributeType.STRING }, billingMode: BillingMode.PAY_PER_REQUEST, @@ -163,7 +166,8 @@ export class WorkflowService { } ); - this.queue = new Queue(workflowServiceScope, "Queue", { + this.queue = new SecureQueue(workflowServiceScope, "Queue", { + compliancePolicy: props.compliancePolicy, fifo: true, fifoThroughputLimit: FifoThroughputLimit.PER_MESSAGE_GROUP_ID, deduplicationScope: DeduplicationScope.MESSAGE_GROUP, @@ -174,6 +178,7 @@ export class WorkflowService { workflowServiceScope, "Orchestrator", { + compliancePolicy: props.compliancePolicy, functionNameSuffix: `orchestrator-handler`, build: props.build, bundledFunction: props.build.system.workflowService.orchestrator, @@ -211,19 +216,20 @@ export class WorkflowService { source: this.executionsTable.tableStreamArn!, sourceProps: { // will retry forever in the case of an SQS outage! - DynamoDBStreamParameters: { + dynamoDbStreamParameters: { // when CREATE/REPLACING a pipe, it can take up to 1 minute to start polling for events. // TRIM_HORIZON will catch any events created during that one minute (and last 24 hours for existing streams) // The assumption is that it is unlikely that the pipe will be replaced on an active service // TODO: check in with the Event Bridge team to see LATEST will work without dropping events // for new streams. - StartingPosition: "TRIM_HORIZON", - MaximumBatchingWindowInSeconds: 1, + startingPosition: "TRIM_HORIZON", + maximumBatchingWindowInSeconds: 1, }, - FilterCriteria: { - Filters: [ + + filterCriteria: { + filters: [ { - Pattern: JSON.stringify({ + pattern: JSON.stringify({ eventName: ["INSERT"], dynamodb: { NewImage: { @@ -253,10 +259,10 @@ export class WorkflowService { sourceParameters: props.sourceProps, target: this.queue.queueArn, targetParameters: { - SqsQueueParameters: { - MessageGroupId: props.executionIdPath, + sqsQueueParameters: { + messageGroupId: props.executionIdPath, }, - InputTemplate: `{"task": { "events": [${props.event}], "executionId": <${props.executionIdPath}> } }`, + inputTemplate: `{"task": { "events": [${props.event}], "executionId": <${props.executionIdPath}> } }`, }, }); diff --git a/packages/create-eventual/package.json b/packages/create-eventual/package.json index 0d08f61a3..ba71f48f5 100644 --- a/packages/create-eventual/package.json +++ b/packages/create-eventual/package.json @@ -14,7 +14,7 @@ "watch": "esbuild src/index.ts --bundle --outfile=lib/cli.js --platform=node --watch --sourcemap=inline" }, "dependencies": { - "aws-cdk": "^2.80.0", + "aws-cdk": "^2.102.0", "create-sst": "latest" }, "devDependencies": { diff --git a/packages/create-eventual/src/create-new-aws-cdk-project.ts b/packages/create-eventual/src/create-new-aws-cdk-project.ts index 19c453c45..7ad63193f 100644 --- a/packages/create-eventual/src/create-new-aws-cdk-project.ts +++ b/packages/create-eventual/src/create-new-aws-cdk-project.ts @@ -160,7 +160,7 @@ ${npm("deploy")} "@tsconfig/node18": "^1", "@types/jest": "^29", "@types/node": "^18", - "aws-cdk": "^2.80.0", + "aws-cdk": "^2.102.0", esbuild: "^0.16.14", typescript: "^5", "ts-node": "^10.9.1", @@ -294,19 +294,19 @@ packages: test: "echo no-op", }, dependencies: { - "@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", "@eventual/aws-cdk": `^${version}`, - "aws-cdk-lib": "^2.80.0", - "aws-cdk": "^2.80.0", + "aws-cdk-lib": "^2.102.0", + "aws-cdk": "^2.102.0", constructs: "^10", esbuild: "^0.16.14", [servicePackageName]: workspaceVersion, }, devDependencies: { "@types/node": "^18", - "aws-cdk": "^2.80.0", + "aws-cdk": "^2.102.0", "ts-node": "^10.9.1", typescript: "^5", }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9db718e2c..674b0a246 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,8 +68,8 @@ importers: specifier: workspace:^ version: link:../../packages/@eventual/aws-cdk aws-cdk-lib: - specifier: 2.80.0 - version: 2.80.0(constructs@10.1.154) + specifier: 2.102.0 + version: 2.102.0(constructs@10.1.154) constructs: specifier: 10.1.154 version: 10.1.154 @@ -84,8 +84,8 @@ importers: specifier: ^18 version: 18.0.0 aws-cdk: - specifier: 2.80.0 - version: 2.80.0 + specifier: 2.102.0 + version: 2.102.0 esbuild: specifier: ^0.17.4 version: 0.17.4 @@ -197,8 +197,8 @@ importers: specifier: ^1.0.1 version: 1.0.1 aws-cdk-lib: - specifier: 2.80.0 - version: 2.80.0(constructs@10.1.154) + specifier: 2.102.0 + version: 2.102.0(constructs@10.1.154) chalk: specifier: ^5.2.0 version: 5.2.0 @@ -277,8 +277,8 @@ importers: specifier: ^8.5.5 version: 8.5.5 aws-cdk: - specifier: ^2.80.0 - version: 2.80.0 + specifier: ^2.102.0 + version: 2.102.0 esbuild: specifier: ^0.17.4 version: 0.17.4 @@ -304,11 +304,14 @@ importers: apps/tests/aws-runtime-cdk: dependencies: '@aws-cdk/aws-apigatewayv2-alpha': - specifier: ^2.80.0-alpha.0 - version: 2.80.0-alpha.0(aws-cdk-lib@2.80.0)(constructs@10.1.154) + specifier: ^2.102.0-alpha.0 + version: 2.102.0-alpha.0(aws-cdk-lib@2.102.0)(constructs@10.1.154) aws-cdk-lib: - specifier: 2.80.0 - version: 2.80.0(constructs@10.1.154) + specifier: 2.102.0 + version: 2.102.0(constructs@10.1.154) + cdk-nag: + specifier: ^2.27.164 + version: 2.27.164(aws-cdk-lib@2.102.0)(constructs@10.1.154) constructs: specifier: 10.1.154 version: 10.1.154 @@ -332,8 +335,8 @@ importers: specifier: ^18 version: 18.0.0 aws-cdk: - specifier: ^2.80.0 - version: 2.80.0 + specifier: ^2.102.0 + version: 2.102.0 esbuild: specifier: ^0.17.4 version: 0.17.4 @@ -381,14 +384,14 @@ importers: version: 1.12.0 devDependencies: '@aws-cdk/aws-apigatewayv2-alpha': - specifier: 2.80.0-alpha.0 - version: 2.80.0-alpha.0(aws-cdk-lib@2.80.0)(constructs@10.1.154) + specifier: 2.102.0-alpha.0 + version: 2.102.0-alpha.0(aws-cdk-lib@2.102.0)(constructs@10.1.154) '@aws-cdk/aws-apigatewayv2-authorizers-alpha': - specifier: 2.80.0-alpha.0 - version: 2.80.0-alpha.0(@aws-cdk/aws-apigatewayv2-alpha@2.80.0-alpha.0)(aws-cdk-lib@2.80.0)(constructs@10.1.154) + specifier: 2.102.0-alpha.0 + version: 2.102.0-alpha.0(@aws-cdk/aws-apigatewayv2-alpha@2.102.0-alpha.0)(aws-cdk-lib@2.102.0)(constructs@10.1.154) '@aws-cdk/aws-apigatewayv2-integrations-alpha': - specifier: 2.80.0-alpha.0 - version: 2.80.0-alpha.0(@aws-cdk/aws-apigatewayv2-alpha@2.80.0-alpha.0)(aws-cdk-lib@2.80.0)(constructs@10.1.154) + specifier: 2.102.0-alpha.0 + version: 2.102.0-alpha.0(@aws-cdk/aws-apigatewayv2-alpha@2.102.0-alpha.0)(aws-cdk-lib@2.102.0)(constructs@10.1.154) '@types/aws-lambda': specifier: 8.10.115 version: 8.10.115 @@ -402,11 +405,11 @@ importers: specifier: ^18 version: 18.0.0 aws-cdk: - specifier: 2.80.0 - version: 2.80.0 + specifier: 2.102.0 + version: 2.102.0 aws-cdk-lib: - specifier: 2.80.0 - version: 2.80.0(constructs@10.1.154) + specifier: 2.102.0 + version: 2.102.0(constructs@10.1.154) constructs: specifier: 10.1.154 version: 10.1.154 @@ -993,11 +996,11 @@ importers: packages/create-eventual: dependencies: aws-cdk: - specifier: ^2.80.0 - version: 2.80.0 + specifier: ^2.102.0 + version: 2.102.0 create-sst: specifier: latest - version: 2.25.2 + version: 2.21.4 devDependencies: '@eventual/project': specifier: workspace:^ @@ -1047,8 +1050,18 @@ packages: /@aws-cdk/asset-kubectl-v20@2.1.2: resolution: {integrity: sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==} - /@aws-cdk/asset-node-proxy-agent-v5@2.0.166: - resolution: {integrity: sha512-j0xnccpUQHXJKPgCwQcGGNu4lRiC1PptYfdxBIH1L4dRK91iBxtSQHESRQX+yB47oGLaF/WfNN/aF3WXwlhikg==} + /@aws-cdk/asset-node-proxy-agent-v6@2.0.1: + resolution: {integrity: sha512-DDt4SLdLOwWCjGtltH4VCST7hpOI5DzieuhGZsBpZ+AgJdSI2GCjklCXm0GCTwJG/SolkL5dtQXyUKgg9luBDg==} + + /@aws-cdk/aws-apigatewayv2-alpha@2.102.0-alpha.0(aws-cdk-lib@2.102.0)(constructs@10.1.154): + resolution: {integrity: sha512-Mkz0j1nn5UxKNO1O+oAtHBfjlNfrTql/1gdpDkaoG+3kDjMABKrF4dvfvPUOaM62IcOhHnpSDSXXknJAiFvk6g==} + engines: {node: '>= 14.15.0'} + peerDependencies: + aws-cdk-lib: ^2.102.0 + constructs: ^10.0.0 + dependencies: + aws-cdk-lib: 2.102.0(constructs@10.1.154) + constructs: 10.1.154 /@aws-cdk/aws-apigatewayv2-alpha@2.50.0-alpha.0(aws-cdk-lib@2.50.0)(constructs@10.1.154): resolution: {integrity: sha512-dttWDqy+nTg/fD9y0egvj7/zdnOVEo0qyGsep1RV+p16R3F4ObMKyPVIg15fz57tK//Gp/i1QgXsZaSqbcWHOg==} @@ -1061,15 +1074,18 @@ packages: constructs: 10.1.154 dev: true - /@aws-cdk/aws-apigatewayv2-alpha@2.80.0-alpha.0(aws-cdk-lib@2.80.0)(constructs@10.1.154): - resolution: {integrity: sha512-XBvDiay46ThYP5hoPcwVfzE9egPiwHMGUpVepg6qJ+HQwCmLesbArwurmG2TXcfRbO06uXrAWmWpAqQmh7nstw==} + /@aws-cdk/aws-apigatewayv2-authorizers-alpha@2.102.0-alpha.0(@aws-cdk/aws-apigatewayv2-alpha@2.102.0-alpha.0)(aws-cdk-lib@2.102.0)(constructs@10.1.154): + resolution: {integrity: sha512-SQT05xWs/L3O8PT/GX6cvQnOHi+z9or5gPDibJQjYeJR3Q3JZm2+gauIriy9xlURxXpzXOWZw7I/elYDPoKo1w==} engines: {node: '>= 14.15.0'} peerDependencies: - aws-cdk-lib: 2.80.0 + '@aws-cdk/aws-apigatewayv2-alpha': 2.102.0-alpha.0 + aws-cdk-lib: ^2.102.0 constructs: ^10.0.0 dependencies: - aws-cdk-lib: 2.80.0(constructs@10.1.154) + '@aws-cdk/aws-apigatewayv2-alpha': 2.102.0-alpha.0(aws-cdk-lib@2.102.0)(constructs@10.1.154) + aws-cdk-lib: 2.102.0(constructs@10.1.154) constructs: 10.1.154 + dev: true /@aws-cdk/aws-apigatewayv2-authorizers-alpha@2.50.0-alpha.0(@aws-cdk/aws-apigatewayv2-alpha@2.50.0-alpha.0)(aws-cdk-lib@2.50.0)(constructs@10.1.154): resolution: {integrity: sha512-lMXnSpUSOYtCxoAxauNkGJZLsKMonHgd9rzlFUK2zxE7aC1lVwb4qYX4X9WJdvIExkFOHSZQzOTKM6SZqusssw==} @@ -1084,16 +1100,16 @@ packages: constructs: 10.1.154 dev: true - /@aws-cdk/aws-apigatewayv2-authorizers-alpha@2.80.0-alpha.0(@aws-cdk/aws-apigatewayv2-alpha@2.80.0-alpha.0)(aws-cdk-lib@2.80.0)(constructs@10.1.154): - resolution: {integrity: sha512-WiQKWOua4XydDGP5t4ffSIi73O/skQQi6XXNoYlF2E+m/x8Hxw6otyw7gjS7NUsbnFnO02u7gLWJBTpzqSDLfw==} + /@aws-cdk/aws-apigatewayv2-integrations-alpha@2.102.0-alpha.0(@aws-cdk/aws-apigatewayv2-alpha@2.102.0-alpha.0)(aws-cdk-lib@2.102.0)(constructs@10.1.154): + resolution: {integrity: sha512-aJa5FbumzLsOVZMd8UcxzMFdv1r9CgtlXBeSN+YYxIhB6a0K3EsBLnsQo8BJgoFbIWUDDPyxGNwPfodPzHXe6w==} engines: {node: '>= 14.15.0'} peerDependencies: - '@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 constructs: ^10.0.0 dependencies: - '@aws-cdk/aws-apigatewayv2-alpha': 2.80.0-alpha.0(aws-cdk-lib@2.80.0)(constructs@10.1.154) - aws-cdk-lib: 2.80.0(constructs@10.1.154) + '@aws-cdk/aws-apigatewayv2-alpha': 2.102.0-alpha.0(aws-cdk-lib@2.102.0)(constructs@10.1.154) + aws-cdk-lib: 2.102.0(constructs@10.1.154) constructs: 10.1.154 dev: true @@ -1110,19 +1126,6 @@ packages: constructs: 10.1.154 dev: true - /@aws-cdk/aws-apigatewayv2-integrations-alpha@2.80.0-alpha.0(@aws-cdk/aws-apigatewayv2-alpha@2.80.0-alpha.0)(aws-cdk-lib@2.80.0)(constructs@10.1.154): - resolution: {integrity: sha512-369z0DEcx/RnRfMawIgGgqittug/kJ1Pyv237v7j5n5YvVQth2rT7CenbNjG29UXlNiqLUTwxOVMLJ/74/1pMQ==} - engines: {node: '>= 14.15.0'} - peerDependencies: - '@aws-cdk/aws-apigatewayv2-alpha': 2.80.0-alpha.0 - aws-cdk-lib: 2.80.0 - constructs: ^10.0.0 - dependencies: - '@aws-cdk/aws-apigatewayv2-alpha': 2.80.0-alpha.0(aws-cdk-lib@2.80.0)(constructs@10.1.154) - aws-cdk-lib: 2.80.0(constructs@10.1.154) - constructs: 10.1.154 - dev: true - /@aws-cdk/aws-appsync-alpha@2.50.0-alpha.0(aws-cdk-lib@2.50.0)(constructs@10.1.154): resolution: {integrity: sha512-ZA5M1z5MKOS+m68MMs5YySVFOjOdzrR6F+22Atx6mrCcAD9E5PypZ7tVSwtWYVYvoUnGMI7Bv5Umc3n4DCnjkg==} engines: {node: '>= 14.15.0'} @@ -6464,23 +6467,26 @@ packages: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} - /aws-cdk-lib@2.50.0(constructs@10.1.154): - resolution: {integrity: sha512-deDbZTI7oyu3rqUyqjwhP6tnUO8MD70lE98yR65xiYty4yXBpsWKbeH3s1wNLpLAWS3hWJYyMtjZ4ZfC35NtVg==} + /aws-cdk-lib@2.102.0(constructs@10.1.154): + resolution: {integrity: sha512-pYcKGlshU2j7n3f8TbJ1CCrwNnLsgGd17G7p/s9njIU8xakU4tIwuNyo4Q9HHQA7aUb3enPI/afAn1A6gp7TrA==} engines: {node: '>= 14.15.0'} peerDependencies: constructs: ^10.0.0 dependencies: + '@aws-cdk/asset-awscli-v1': 2.2.200 + '@aws-cdk/asset-kubectl-v20': 2.1.2 + '@aws-cdk/asset-node-proxy-agent-v6': 2.0.1 '@balena/dockerignore': 1.0.2 case: 1.6.3 constructs: 10.1.154 - fs-extra: 9.1.0 + fs-extra: 11.1.1 ignore: 5.2.4 jsonschema: 1.4.1 minimatch: 3.1.2 punycode: 2.3.0 semver: 7.5.4 + table: 6.8.1 yaml: 1.10.2 - dev: true bundledDependencies: - '@balena/dockerignore' - case @@ -6490,28 +6496,26 @@ packages: - minimatch - punycode - semver + - table - yaml - /aws-cdk-lib@2.80.0(constructs@10.1.154): - resolution: {integrity: sha512-PoqD3Yms5I0ajuTi071nTW/hpkH3XsdyZzn5gYsPv0qD7mqP3h6Qr+6RiGx+yQ1KcVFyxWdX15uK+DsC0KwvcQ==} + /aws-cdk-lib@2.50.0(constructs@10.1.154): + resolution: {integrity: sha512-deDbZTI7oyu3rqUyqjwhP6tnUO8MD70lE98yR65xiYty4yXBpsWKbeH3s1wNLpLAWS3hWJYyMtjZ4ZfC35NtVg==} engines: {node: '>= 14.15.0'} peerDependencies: constructs: ^10.0.0 dependencies: - '@aws-cdk/asset-awscli-v1': 2.2.200 - '@aws-cdk/asset-kubectl-v20': 2.1.2 - '@aws-cdk/asset-node-proxy-agent-v5': 2.0.166 '@balena/dockerignore': 1.0.2 case: 1.6.3 constructs: 10.1.154 - fs-extra: 11.1.1 + fs-extra: 9.1.0 ignore: 5.2.4 jsonschema: 1.4.1 minimatch: 3.1.2 punycode: 2.3.0 semver: 7.5.4 - table: 6.8.1 yaml: 1.10.2 + dev: true bundledDependencies: - '@balena/dockerignore' - case @@ -6521,23 +6525,22 @@ packages: - minimatch - punycode - semver - - table - yaml - /aws-cdk@2.50.0: - resolution: {integrity: sha512-55vmKTf2DZRqioumVfXn+S0H9oAbpRK3HFHY8EjZ5ykR5tq2+XiMWEZkYduX2HJhVAeHJJIS6h+Okk3smZjeqw==} + /aws-cdk@2.102.0: + resolution: {integrity: sha512-q+FQSeX/25QvZ1/Fxjr7GydMY/WR/+iTif2EiaN7rUlEEZx27o0I5k1p9YmTNUGiBl13ZvggIJjwTRmnL7E/lg==} engines: {node: '>= 14.15.0'} hasBin: true optionalDependencies: fsevents: 2.3.2 - dev: true - /aws-cdk@2.80.0: - resolution: {integrity: sha512-SKMZ/sGlNmFV37Lk40HHe4QJ2hJZmD0PrkScBmkr33xzEqjyKhN3jIHC4PYqTUeUK/qYemq3Y5OpXKQuWTCoKA==} + /aws-cdk@2.50.0: + resolution: {integrity: sha512-55vmKTf2DZRqioumVfXn+S0H9oAbpRK3HFHY8EjZ5ykR5tq2+XiMWEZkYduX2HJhVAeHJJIS6h+Okk3smZjeqw==} engines: {node: '>= 14.15.0'} hasBin: true optionalDependencies: fsevents: 2.3.2 + dev: true /aws-embedded-metrics@4.1.0: resolution: {integrity: sha512-Yiscee2EfyiczIy9GFOSR0qzqD6kcD0HjQuBJRyO842SkKoFlxzOo/99OVEmg2odUS5XI8oxiS7HO0WTynkteg==} @@ -6976,6 +6979,16 @@ packages: resolution: {integrity: sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==} engines: {node: '>= 0.8.0'} + /cdk-nag@2.27.164(aws-cdk-lib@2.102.0)(constructs@10.1.154): + resolution: {integrity: sha512-+T5u/diNLWdjDqXKMsRcUen40fquMhSSNIeYaNUBQpE97xztZPyjfK8UBaYobTVL1pvuavMTdIdx5CkNTpvsSA==} + peerDependencies: + aws-cdk-lib: ^2.78.0 + constructs: ^10.0.5 + dependencies: + aws-cdk-lib: 2.102.0(constructs@10.1.154) + constructs: 10.1.154 + dev: false + /chai@4.3.7: resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==} engines: {node: '>=4'} @@ -7438,8 +7451,8 @@ packages: /create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - /create-sst@2.25.2: - resolution: {integrity: sha512-dlguHq0Mo71QLlcoiBo57FdfT/eG+sXhifxYY7wX8m/+WcEr8eWYHVqGhhD+odpEsNyZzRFN4PNMqkb6slISbQ==} + /create-sst@2.21.4: + resolution: {integrity: sha512-FLzaUd4Y34QV7l+Gi6F9bZfQW5mE2EL2UXLZHPcKLh5vPVgceDix3Oa9NfT78GeajJQzc+/dOv2nzsV4NFGmog==} hasBin: true dependencies: cli-spinners: 2.9.0