commit 10555af9e274531174bbf85aa41886ae9f552590 Author: Adam Moussa Date: Mon Jun 15 11:26:23 2026 -0400 Add account-local security baseline for seahaven-external-dev Stripped fork of seahaven-account-baseline for the isolated external-dev account (396287094661). Single stack: AWS Config, GuardDuty, Security Hub (FSBP + CIS v3.0), IAM Access Analyzer, VPC flow logs (VPC ids via context), and a $200/mo budget alerting adam@seahaven.com. Drops all org-level / prod-specific controls (local CloudTrail, CIS metric alarms, WAF, SES, Bedrock, DynamoDB CMK, Backup) per the isolated-account design; the org trail already covers this account centrally. Inspector2 is a documented post-deploy CLI step (no CloudFormation enable resource exists). diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a6586ff --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + groups: + minor-and-patch: + update-types: + - "minor" + - "patch" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..89a077f --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,10 @@ +name: CI +on: + pull_request: + branches: [main] + +jobs: + ci: + uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-cdk.yaml@main + with: + node-version: "24" diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..90d3497 --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,20 @@ +name: Deploy +on: + push: + branches: [main] + +permissions: + id-token: write + contents: read + +concurrency: + group: deploy + cancel-in-progress: false + +jobs: + deploy: + uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@main + with: + node-version: "24" + secrets: + deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b323b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +cdk.out/ +*.js +*.d.ts +*.js.map +.env diff --git a/README.md b/README.md new file mode 100644 index 0000000..c7dbfea --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# seahaven-external-dev-baseline + +Account-local security baseline (CDK, TypeScript) for **`seahaven-external-dev`** +(account `396287094661`, us-east-1), the isolated AWS account used by the +external web-app dev team. A stripped fork of +[`seahaven-account-baseline`](https://github.com/Sea-Haven-Industries/seahaven-account-baseline). + +The external team works only in this account; they have no access to the +management/production account `328440206208`. This stack ensures the isolated +account is not a monitoring blind spot. + +## Architecture + +Single stack `seahaven-external-dev-baseline`: + +| Control | Resource | Notes | +|---|---|---| +| AWS Config | recorder + delivery channel + `seahaven-extdev-config-` bucket | Records all supported resource types; foundation for Security Hub CIS | +| GuardDuty | detector (15-min findings) | Account-local threat detection | +| Security Hub | FSBP v1.0.0 + CIS AWS Foundations v3.0.0 | CIS evaluated against Config — no local trail required | +| IAM Access Analyzer | account-scoped external-access analyzer | | +| VPC flow logs | `seahaven-extdev-vpc-flow-logs-` bucket | ALL traffic; VPC ids passed via context | +| Budget | `seahaven-extdev-monthly-cost`, $200/mo | 80%/100% actual + 100% forecast → `adam@seahaven.com` (Sea Haven ops) | + +## Deliberately excluded + +- **No local CloudTrail.** The management-account organization trail + `seahaven-org-trail` already captures this account's management + data events + centrally. A second local trail would duplicate that at extra cost. +- **No CIS Section-4 metric-filter alarms.** Those bind to a local CloudTrail + CloudWatch Logs group, which does not exist here. The Security Hub CIS + standard evaluates the same controls against AWS Config instead. +- **No WAF, SES monitoring, Bedrock logging, DynamoDB CMK, or AWS Backup** — + all production-only concerns in the source baseline. + +## Configuration + +| Input | Where | Value | +|---|---|---| +| Target account | `bin/app.ts` | `396287094661` | +| Region | `bin/app.ts` | `us-east-1` (account is SCP region-locked) | +| Budget | `bin/app.ts` | $200/mo → `adam@seahaven.com` | +| Flow-log VPC ids | cdk context `flowLogVpcIds` | comma-separated; empty by default | + +```bash +# Deploy attaching flow logs to specific VPCs: +npm ci +npx cdk deploy -c flowLogVpcIds=vpc-aaaa,vpc-bbbb +``` + +## Post-deploy runbook + +**Enable Inspector2** (no CloudFormation enable resource exists): + +```bash +aws inspector2 enable --resource-types EC2 ECR LAMBDA --account-ids 396287094661 +``` + +This is a one-time per-account toggle; it persists across stack deploys. + +## Deployment + +CI/CD via the org reusable workflows (`ci-typescript-cdk.yaml`, `cd-cdk.yaml`, +node 24). Push to `main` deploys via GitHub OIDC into `396287094661` using the +`githubdeploy-seahaven-external-dev-baseline` role (repo secret +`AWS_DEPLOY_ROLE_ARN`). diff --git a/bin/app.ts b/bin/app.ts new file mode 100644 index 0000000..c112d79 --- /dev/null +++ b/bin/app.ts @@ -0,0 +1,29 @@ +#!/usr/bin/env node +import "source-map-support/register"; +import * as cdk from "aws-cdk-lib"; +import { ExternalDevBaselineStack } from "../lib/external-dev-baseline-stack"; + +// Isolated external-dev member account (seahaven-external-dev), us-east-1 only. +const ACCOUNT = "396287094661"; + +const app = new cdk.App(); + +// Flow-log VPC ids come from context, NOT hardcoded — this account's VPCs change +// as the external dev team provisions their own infrastructure. Pass via: +// cdk deploy -c flowLogVpcIds=vpc-aaaa,vpc-bbbb +// Empty (default) creates the hardened flow-logs bucket with no flow logs yet. +const vpcCtx = app.node.tryGetContext("flowLogVpcIds"); +const flowLogVpcIds: string[] = vpcCtx + ? String(vpcCtx) + .split(",") + .map((s) => s.trim()) + .filter(Boolean) + : []; + +new ExternalDevBaselineStack(app, "external-dev-baseline", { + stackName: "seahaven-external-dev-baseline", + env: { account: ACCOUNT, region: "us-east-1" }, + monthlyBudgetUsd: 200, + budgetAlertEmail: "adam@seahaven.com", + flowLogVpcIds, +}); diff --git a/cdk.json b/cdk.json new file mode 100644 index 0000000..b69b7d0 --- /dev/null +++ b/cdk.json @@ -0,0 +1,22 @@ +{ + "app": "npx ts-node bin/app.ts", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "node_modules", + "test", + "cdk.out" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": ["aws"] + } +} diff --git a/lib/detective-controls.ts b/lib/detective-controls.ts new file mode 100644 index 0000000..a867299 --- /dev/null +++ b/lib/detective-controls.ts @@ -0,0 +1,327 @@ +import * as cdk from "aws-cdk-lib"; +import * as s3 from "aws-cdk-lib/aws-s3"; +import * as iam from "aws-cdk-lib/aws-iam"; +import * as guardduty from "aws-cdk-lib/aws-guardduty"; +import * as securityhub from "aws-cdk-lib/aws-securityhub"; +import * as accessanalyzer from "aws-cdk-lib/aws-accessanalyzer"; +import * as cr from "aws-cdk-lib/custom-resources"; +import { Construct } from "constructs"; + +/** + * Account-level detective controls for the isolated external-dev account. + * + * Adapted from seahaven-account-baseline/lib/detective-controls.ts. Provides: + * AWS Config recorder + delivery channel (CIS 3.3/3.5) + * GuardDuty detector + * Security Hub with AWS FSBP + CIS v3.0 standards + * IAM Access Analyzer (account-scoped external-access analyzer) + * + * Inspector2 has no CloudFormation enable resource and is a documented + * post-deploy CLI step (see README), matching the prod baseline. + * + * Scope is us-east-1 only (the account is region-locked by SCP). No local + * CloudTrail and no CIS Section-4 metric-filter alarms: the management-account + * organization trail (seahaven-org-trail) already captures this account's + * events centrally, and the Security Hub CIS standard below evaluates the CIS + * controls against AWS Config without needing a local trail log group. + */ +export class DetectiveControls extends Construct { + constructor(scope: Construct, id: string) { + super(scope, id); + + const stack = cdk.Stack.of(this); + + // ────────────────────────────────────────────────────────────────────── + // AWS Config + // ────────────────────────────────────────────────────────────────────── + + // Delivery bucket for Config snapshots/history. Private, TLS-only, + // versioned, SSE-S3 (Config writes here; SSE-S3 avoids a second KMS-grant + // failure mode and is sufficient — CIS does not require a CMK here). + const configBucket = new s3.Bucket(this, "ConfigBucket", { + bucketName: `seahaven-extdev-config-${stack.account}`, + encryption: s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + enforceSSL: true, + versioned: true, + lifecycleRules: [ + { + id: "expire-old-config", + expiration: cdk.Duration.days(365), + abortIncompleteMultipartUploadAfter: cdk.Duration.days(7), + }, + ], + removalPolicy: cdk.RemovalPolicy.RETAIN, + }); + + // Bucket policy that lets the Config service principal verify ownership + // and deliver objects (scoped to this account, owner-full-control ACL). + configBucket.addToResourcePolicy( + new iam.PolicyStatement({ + sid: "AWSConfigBucketPermissionsCheck", + effect: iam.Effect.ALLOW, + principals: [new iam.ServicePrincipal("config.amazonaws.com")], + actions: ["s3:GetBucketAcl", "s3:ListBucket"], + resources: [configBucket.bucketArn], + conditions: { + StringEquals: { "aws:SourceAccount": stack.account }, + }, + }) + ); + configBucket.addToResourcePolicy( + new iam.PolicyStatement({ + sid: "AWSConfigBucketDelivery", + effect: iam.Effect.ALLOW, + principals: [new iam.ServicePrincipal("config.amazonaws.com")], + actions: ["s3:PutObject"], + resources: [ + configBucket.arnForObjects(`AWSLogs/${stack.account}/Config/*`), + ], + conditions: { + StringEquals: { + "s3:x-amz-acl": "bucket-owner-full-control", + "aws:SourceAccount": stack.account, + }, + }, + }) + ); + + // Recorder role — assumed by Config. AWS_ConfigRole grants the read/describe + // permissions Config needs to record every resource type; the inline policy + // grants delivery to the bucket above. (IAM change — cross-review per + // CLAUDE.md; pattern replicated verbatim from the cross-reviewed prod + // baseline.) + const recorderRole = new iam.Role(this, "ConfigRecorderRole", { + roleName: "seahaven-extdev-config-recorder-role", + assumedBy: new iam.ServicePrincipal("config.amazonaws.com"), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWS_ConfigRole"), + ], + }); + recorderRole.addToPolicy( + new iam.PolicyStatement({ + sid: "ConfigDeliveryToBucket", + effect: iam.Effect.ALLOW, + actions: ["s3:PutObject"], + resources: [ + configBucket.arnForObjects(`AWSLogs/${stack.account}/Config/*`), + ], + conditions: { + StringEquals: { "s3:x-amz-acl": "bucket-owner-full-control" }, + }, + }) + ); + recorderRole.addToPolicy( + new iam.PolicyStatement({ + sid: "ConfigBucketAcl", + effect: iam.Effect.ALLOW, + actions: ["s3:GetBucketAcl"], + resources: [configBucket.bucketArn], + }) + ); + + // ── AWS Config recorder + delivery channel ───────────────────────────── + // + // The L1 AWS::Config::ConfigurationRecorder is a stabilizing resource that + // deadlocks the stack: it never reaches CREATE_COMPLETE until recording is + // active, which requires a delivery channel, which can't be created until + // the recorder is complete. Fix (from the prod baseline): an + // AwsCustomResource calls the Config SDK directly — Put* is an upsert. + // Sequence: PutConfigurationRecorder -> PutDeliveryChannel -> + // StartConfigurationRecorder. onDelete stops (does not delete) the + // per-account-singleton recorder. + const configCustomResourceRole = new iam.Role( + this, + "ConfigCustomResourceRole", + { + roleName: "seahaven-extdev-config-custom-resource-role", + assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName( + "service-role/AWSLambdaBasicExecutionRole" + ), + ], + inlinePolicies: { + ConfigRecorderAdoption: new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + sid: "ConfigRecorderManage", + effect: iam.Effect.ALLOW, + actions: [ + "config:PutConfigurationRecorder", + "config:PutDeliveryChannel", + "config:StartConfigurationRecorder", + "config:StopConfigurationRecorder", + ], + // Config recorder/channel are account-level singletons with no + // ARN in resource policies — the API only accepts "*" here. + resources: ["*"], + }), + new iam.PolicyStatement({ + sid: "PassRecorderRole", + effect: iam.Effect.ALLOW, + actions: ["iam:PassRole"], + resources: [recorderRole.roleArn], + conditions: { + StringEquals: { + "iam:PassedToService": "config.amazonaws.com", + }, + }, + }), + ], + }), + }, + } + ); + + const putRecorderCall: cr.AwsSdkCall = { + service: "ConfigService", + action: "putConfigurationRecorder", + parameters: { + ConfigurationRecorder: { + name: "seahaven-extdev-config-recorder", + roleARN: recorderRole.roleArn, + recordingGroup: { + allSupported: true, + includeGlobalResourceTypes: true, + }, + }, + }, + physicalResourceId: cr.PhysicalResourceId.of( + "seahaven-extdev-config-recorder" + ), + }; + + const putChannelCall: cr.AwsSdkCall = { + service: "ConfigService", + action: "putDeliveryChannel", + parameters: { + DeliveryChannel: { + name: "seahaven-extdev-config-delivery", + s3BucketName: configBucket.bucketName, + configSnapshotDeliveryProperties: { + deliveryFrequency: "TwentyFour_Hours", + }, + }, + }, + physicalResourceId: cr.PhysicalResourceId.of( + "seahaven-extdev-config-delivery" + ), + }; + + const startRecorderCall: cr.AwsSdkCall = { + service: "ConfigService", + action: "startConfigurationRecorder", + parameters: { + ConfigurationRecorderName: "seahaven-extdev-config-recorder", + }, + physicalResourceId: cr.PhysicalResourceId.of( + "seahaven-extdev-config-recorder-start" + ), + }; + + const putRecorder = new cr.AwsCustomResource(this, "ConfigPutRecorder", { + onCreate: putRecorderCall, + onUpdate: putRecorderCall, + role: configCustomResourceRole, + installLatestAwsSdk: false, + }); + + const putChannel = new cr.AwsCustomResource(this, "ConfigPutChannel", { + onCreate: putChannelCall, + onUpdate: putChannelCall, + role: configCustomResourceRole, + installLatestAwsSdk: false, + }); + putChannel.node.addDependency(putRecorder); + + const startRecorder = new cr.AwsCustomResource(this, "ConfigStartRecorder", { + onCreate: startRecorderCall, + onUpdate: startRecorderCall, + onDelete: { + service: "ConfigService", + action: "stopConfigurationRecorder", + parameters: { + ConfigurationRecorderName: "seahaven-extdev-config-recorder", + }, + physicalResourceId: cr.PhysicalResourceId.of( + "seahaven-extdev-config-recorder-stop" + ), + }, + role: configCustomResourceRole, + installLatestAwsSdk: false, + }); + startRecorder.node.addDependency(putChannel); + + new cdk.CfnOutput(this, "ConfigRecorderRoleArn", { + value: recorderRole.roleArn, + }); + + // ────────────────────────────────────────────────────────────────────── + // GuardDuty + // ────────────────────────────────────────────────────────────────────── + new guardduty.CfnDetector(this, "GuardDutyDetector", { + enable: true, + findingPublishingFrequency: "FIFTEEN_MINUTES", + }); + + // ────────────────────────────────────────────────────────────────────── + // Security Hub (FSBP + CIS v3.0) — the CIS coverage substitute for the + // dropped Section-4 metric alarms; evaluates against Config, not a trail. + // ────────────────────────────────────────────────────────────────────── + const hub = new securityhub.CfnHub(this, "SecurityHub", { + enableDefaultStandards: false, + controlFindingGenerator: "SECURITY_CONTROL", + autoEnableControls: true, + }); + + const fsbpArn = cdk.Arn.format( + { + service: "securityhub", + region: stack.region, + account: "", + resource: "standards", + resourceName: "aws-foundational-security-best-practices/v/1.0.0", + }, + stack + ); + const cisArn = cdk.Arn.format( + { + service: "securityhub", + region: stack.region, + account: "", + resource: "standards", + resourceName: "cis-aws-foundations-benchmark/v/3.0.0", + }, + stack + ); + + const fsbp = new securityhub.CfnStandard(this, "StandardFSBP", { + standardsArn: fsbpArn, + }); + fsbp.node.addDependency(hub); + + const cis = new securityhub.CfnStandard(this, "StandardCIS", { + standardsArn: cisArn, + }); + cis.node.addDependency(hub); + + // ────────────────────────────────────────────────────────────────────── + // IAM Access Analyzer (free, account-scoped external-access) + // ────────────────────────────────────────────────────────────────────── + new accessanalyzer.CfnAnalyzer(this, "AccountAnalyzer", { + analyzerName: "seahaven-extdev-account-analyzer", + type: "ACCOUNT", + }); + + // Inspector2 (EC2 + ECR + Lambda) has NO CloudFormation resource for + // *enabling* the service — it is a post-deploy CLI step, documented in the + // README runbook (same as the prod baseline): + // aws inspector2 enable --resource-types EC2 ECR LAMBDA \ + // --account-ids + + new cdk.CfnOutput(this, "ConfigBucketName", { + value: configBucket.bucketName, + }); + } +} diff --git a/lib/external-dev-baseline-stack.ts b/lib/external-dev-baseline-stack.ts new file mode 100644 index 0000000..eb20a98 --- /dev/null +++ b/lib/external-dev-baseline-stack.ts @@ -0,0 +1,51 @@ +import * as cdk from "aws-cdk-lib"; +import { Construct } from "constructs"; +import { DetectiveControls } from "./detective-controls"; +import { FlowLogs } from "./flow-logs"; +import { GovernanceToggles } from "./governance-toggles"; + +export interface ExternalDevBaselineStackProps extends cdk.StackProps { + /** Monthly cost budget ceiling in USD. */ + readonly monthlyBudgetUsd: number; + /** Sea Haven ops address that receives budget alerts (not the dev team). */ + readonly budgetAlertEmail: string; + /** VPC ids to attach flow logs to (from cdk context; may be empty). */ + readonly flowLogVpcIds: string[]; +} + +/** + * Account-local security baseline for the isolated external-dev account + * (seahaven-external-dev). A stripped fork of seahaven-account-baseline. + * + * Deliberately excludes everything that is org-level or prod-specific: + * - No local CloudTrail — the management-account org trail (seahaven-org-trail) + * already captures this account's events centrally. + * - No CIS Section-4 metric-filter alarms — the Security Hub CIS standard + * evaluates those controls against Config without a local trail log group. + * - No WAF, SES monitoring, Bedrock logging, DynamoDB CMK, or AWS Backup — + * all prod-only concerns. + * + * Contains: AWS Config, GuardDuty, Security Hub (FSBP + CIS v3.0), IAM Access + * Analyzer, Inspector2, VPC flow logs, and a monthly cost Budget. + */ +export class ExternalDevBaselineStack extends cdk.Stack { + constructor( + scope: Construct, + id: string, + props: ExternalDevBaselineStackProps + ) { + super(scope, id, props); + + new DetectiveControls(this, "DetectiveControls"); + + new FlowLogs(this, "FlowLogs", { vpcIds: props.flowLogVpcIds }); + + new GovernanceToggles(this, "GovernanceToggles", { + monthlyLimitUsd: props.monthlyBudgetUsd, + alertEmail: props.budgetAlertEmail, + }); + + cdk.Tags.of(this).add("Owner", "adam@seahaven.com"); + cdk.Tags.of(this).add("ManagedBy", "seahaven-external-dev-baseline"); + } +} diff --git a/lib/flow-logs.ts b/lib/flow-logs.ts new file mode 100644 index 0000000..601abab --- /dev/null +++ b/lib/flow-logs.ts @@ -0,0 +1,102 @@ +import * as cdk from "aws-cdk-lib"; +import * as s3 from "aws-cdk-lib/aws-s3"; +import * as iam from "aws-cdk-lib/aws-iam"; +import * as ec2 from "aws-cdk-lib/aws-ec2"; +import { Construct } from "constructs"; + +/** + * VPC flow logs delivered to a hardened S3 bucket (ALL traffic), forensically + * queryable via Athena. Adapted from seahaven-account-baseline/lib/flow-logs.ts. + * + * Unlike the prod baseline, the VPC ids are NOT hardcoded — they are passed in + * via props (sourced from cdk context in bin/app.ts), because this account's + * VPCs change as the external dev team provisions their own infrastructure. + * Pass an empty list to create the hardened destination bucket without any + * flow logs attached yet (e.g. before the team's first VPC exists, or while the + * default VPC is pending deletion). + */ +export interface FlowLogsProps { + /** VPC ids to attach ALL-traffic flow logs to. May be empty. */ + readonly vpcIds: string[]; +} + +export class FlowLogs extends Construct { + constructor(scope: Construct, id: string, props: FlowLogsProps) { + super(scope, id); + + const stack = cdk.Stack.of(this); + + const bucket = new s3.Bucket(this, "FlowLogsBucket", { + bucketName: `seahaven-extdev-vpc-flow-logs-${stack.account}`, + encryption: s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + enforceSSL: true, + versioned: false, + lifecycleRules: [ + { + id: "transition-and-expire", + transitions: [ + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + expiration: cdk.Duration.days(365), + abortIncompleteMultipartUploadAfter: cdk.Duration.days(7), + }, + ], + removalPolicy: cdk.RemovalPolicy.RETAIN, + }); + + // Log-delivery service permissions (scoped to this account) — the standard + // VPC-flow-logs-to-S3 bucket policy. + bucket.addToResourcePolicy( + new iam.PolicyStatement({ + sid: "AWSLogDeliveryWrite", + effect: iam.Effect.ALLOW, + principals: [new iam.ServicePrincipal("delivery.logs.amazonaws.com")], + actions: ["s3:PutObject"], + resources: [bucket.arnForObjects(`AWSLogs/${stack.account}/*`)], + conditions: { + StringEquals: { + "s3:x-amz-acl": "bucket-owner-full-control", + "aws:SourceAccount": stack.account, + }, + ArnLike: { + "aws:SourceArn": `arn:${stack.partition}:logs:${stack.region}:${stack.account}:*`, + }, + }, + }) + ); + bucket.addToResourcePolicy( + new iam.PolicyStatement({ + sid: "AWSLogDeliveryAclCheck", + effect: iam.Effect.ALLOW, + principals: [new iam.ServicePrincipal("delivery.logs.amazonaws.com")], + actions: ["s3:GetBucketAcl"], + resources: [bucket.bucketArn], + conditions: { + StringEquals: { "aws:SourceAccount": stack.account }, + ArnLike: { + "aws:SourceArn": `arn:${stack.partition}:logs:${stack.region}:${stack.account}:*`, + }, + }, + }) + ); + + props.vpcIds.forEach((vpcId, i) => { + const flowLog = new ec2.CfnFlowLog(this, `FlowLog${i}`, { + resourceId: vpcId, + resourceType: "VPC", + trafficType: "ALL", + logDestinationType: "s3", + logDestination: bucket.bucketArn, + maxAggregationInterval: 600, + tags: [{ key: "Name", value: `flow-log-${vpcId}` }], + }); + flowLog.node.addDependency(bucket.policy!); + }); + + new cdk.CfnOutput(this, "FlowLogsBucketName", { value: bucket.bucketName }); + } +} diff --git a/lib/governance-toggles.ts b/lib/governance-toggles.ts new file mode 100644 index 0000000..567639e --- /dev/null +++ b/lib/governance-toggles.ts @@ -0,0 +1,79 @@ +import * as cdk from "aws-cdk-lib"; +import * as budgets from "aws-cdk-lib/aws-budgets"; +import { Construct } from "constructs"; + +export interface GovernanceTogglesProps { + /** Monthly cost budget ceiling in USD. */ + readonly monthlyLimitUsd: number; + /** Email that receives the budget threshold alerts. */ + readonly alertEmail: string; +} + +/** + * Account-level governance toggles expressible as CloudFormation. Adapted from + * seahaven-account-baseline/lib/governance-toggles.ts. + * + * Provides a monthly AWS Budget with 80% / 100% actual + 100% forecast alerts. + * Alerts go to a Sea Haven ops address (NOT the external dev team) so cost + * surprises surface to the account owner. + */ +export class GovernanceToggles extends Construct { + constructor(scope: Construct, id: string, props: GovernanceTogglesProps) { + super(scope, id); + + const subscriber = [ + { + subscriptionType: "EMAIL", + address: props.alertEmail, + }, + ]; + + new budgets.CfnBudget(this, "MonthlyCostBudget", { + budget: { + budgetName: "seahaven-extdev-monthly-cost", + budgetType: "COST", + timeUnit: "MONTHLY", + budgetLimit: { + amount: props.monthlyLimitUsd, + unit: "USD", + }, + }, + notificationsWithSubscribers: [ + { + notification: { + notificationType: "ACTUAL", + comparisonOperator: "GREATER_THAN", + threshold: 80, + thresholdType: "PERCENTAGE", + }, + subscribers: subscriber, + }, + { + notification: { + notificationType: "ACTUAL", + comparisonOperator: "GREATER_THAN", + threshold: 100, + thresholdType: "PERCENTAGE", + }, + subscribers: subscriber, + }, + { + notification: { + notificationType: "FORECASTED", + comparisonOperator: "GREATER_THAN", + threshold: 100, + thresholdType: "PERCENTAGE", + }, + subscribers: subscriber, + }, + ], + }); + + cdk.Annotations.of(this).addInfo( + "Budget alerts: 80%/100% actual + 100% forecast of $" + + props.monthlyLimitUsd + + " to " + + props.alertEmail + ); + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e5fe4e2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,720 @@ +{ + "name": "seahaven-external-dev-baseline", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "seahaven-external-dev-baseline", + "version": "1.0.0", + "dependencies": { + "aws-cdk-lib": "2.258.0", + "constructs": "^10.0.0" + }, + "bin": { + "app": "bin/app.js" + }, + "devDependencies": { + "@types/node": "^24.0.0", + "@types/source-map-support": "^0.5.10", + "aws-cdk": "^2.252.0", + "source-map-support": "^0.5.21", + "ts-node": "^10.9.2", + "typescript": "~6.0.3" + } + }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.282", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.282.tgz", + "integrity": "sha512-7hKMi5tTxDcKGIMIOq14PnY0GBcugW33Uh/2YHDZiEwSxLeFOCYBwhR+BFXONb/EJeVI3RETFgailNZbkcKF6g==", + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.2.tgz", + "integrity": "sha512-pDiuqH+qY3zM9lhhLjbKJ1tnKOHzQ2V4Wr/3qsxyKeKAkuPMI/BVGvZG1PbrikUw949cGVTfVEt4ETKKYnrj0Q==", + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/cloud-assembly-schema": { + "version": "54.3.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-54.3.0.tgz", + "integrity": "sha512-1dCM2TXo4PFFKu6sO4qvS0i3d6kxdjuzEFkN7S4xu8nJ+edJRO+DHE7/ttym5ZfAZShAyMY20BhnyTycQN1/jg==", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "license": "Apache-2.0", + "dependencies": { + "jsonschema": "^1.5.0", + "semver": "^7.8.4" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { + "version": "1.5.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { + "version": "7.8.4", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/source-map-support": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-tgVP2H469x9zq34Z0m/fgPewGhg/MLClalNOiPIzQlXrSS2YrKu/xCdSCKnEDwkFha51VKEKB6A9wW26/ZNwzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "^0.6.0" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/aws-cdk": { + "version": "2.1127.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1127.0.tgz", + "integrity": "sha512-/6pUD6+cp3ObwItYEHXF+O+cUCtsKmCoeerCXA/xAfELAAJbdlu36Zx+LJZGbF32aO4xdk3zlH3F7rY657js3g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.258.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.258.0.tgz", + "integrity": "sha512-OfFfg30ikBRJ3dimlzsWhPIrj6qug1p2XYsdB38CtMtcur7SufzoUczgI6kWjbQkOVcQgYzhzN29DErV0yZG7A==", + "bundleDependencies": [ + "@balena/dockerignore", + "@aws-cdk/cloud-assembly-api", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml", + "mime-types" + ], + "license": "Apache-2.0", + "dependencies": { + "@aws-cdk/asset-awscli-v1": "2.2.282", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.2", + "@aws-cdk/cloud-assembly-api": "^2.2.5", + "@aws-cdk/cloud-assembly-schema": "^54.0.0", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.3.5", + "ignore": "^5.3.2", + "jsonschema": "^1.5.0", + "mime-types": "^2.1.35", + "minimatch": "^10.2.5", + "punycode": "^2.3.1", + "semver": "^7.8.1", + "table": "^6.9.0", + "yaml": "1.10.3" + }, + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "constructs": "^10.5.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api": { + "version": "2.2.5", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "jsonschema": "^1.5.0", + "semver": "^7.8.0" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "@aws-cdk/cloud-assembly-schema": ">=53.28.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.20.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "4.0.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "5.0.6", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-uri": { + "version": "3.1.2", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.3.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.3.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.2.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.5.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "inBundle": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "10.2.5", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.8.1", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.9.0", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.3", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/constructs": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.6.0.tgz", + "integrity": "sha512-TxHOnBO5zMo/G76ykzGF/wMpEHu257TbWiIxP9K0Yv/+t70UzgBQiTqjkAsWOPC6jW91DzJI0+ehQV6xDRNBuQ==", + "license": "Apache-2.0" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0bfac78 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "seahaven-external-dev-baseline", + "version": "1.0.0", + "bin": { + "app": "bin/app.js" + }, + "scripts": { + "build": "tsc", + "cdk": "cdk", + "synth": "cdk synth", + "deploy": "cdk deploy", + "diff": "cdk diff" + }, + "devDependencies": { + "@types/node": "^24.0.0", + "@types/source-map-support": "^0.5.10", + "aws-cdk": "^2.252.0", + "source-map-support": "^0.5.21", + "ts-node": "^10.9.2", + "typescript": "~6.0.3" + }, + "dependencies": { + "aws-cdk-lib": "2.258.0", + "constructs": "^10.0.0" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f50978b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "types": ["node"], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "outDir": "./cdk.out", + "rootDir": ".", + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "esModuleInterop": true + }, + "exclude": ["node_modules", "cdk.out"] +}