import * as cdk from "aws-cdk-lib"; import * as s3 from "aws-cdk-lib/aws-s3"; import { Construct } from "constructs"; import { EnvName, prefix } from "../config"; /** * The per-env S3 artifact bucket (`open-swe--assets`) the box pulls its * release from (T7). CI builds the SPA + packages the app source and uploads * `app.tar.gz` / `spa.tar.gz` under `releases//` + `releases/latest/` * (`build-artifacts.yml`, via the `githubdeploy-open-swe-app-` OIDC role); * the box pulls `releases/latest/*` at boot / on deploy via its instance role. * * The bucket holds ONLY build artifacts — no secrets (those live in Secrets * Manager + SSM), no durable runtime state (the langgraph store is in-memory and * rebuilt every boot). It is therefore safe to treat as reproducible-from-CI, but * we RETAIN it on stack delete so an accidental `cdk destroy` cannot strand the * box with no artifact to pull on its next replacement. * * Security posture (locked, reviewed in T7): * - `BLOCK_ALL` public access (this is an internal artifact store; ALB/nginx is * the only public surface — never S3 directly). * - SSE-S3 encryption at rest + `enforceSSL` (deny any non-TLS request). * - versioned, so a bad release can be rolled back to the previous object * version (the last-good-artifact story in T19); a lifecycle rule expires * NONcurrent versions after 30 days so history does not grow unbounded. * - aborts incomplete multipart uploads after 7 days (cost hygiene). * * The name is the load-bearing contract: `instance-role.ts` (read), the app * deploy role in `github-deploy-roles.ts` (write), and `user-data.sh` / * `deploy.sh` (`@@ASSETS_BUCKET@@`) all reference `open-swe--assets` by * literal name, so it is set explicitly here rather than auto-generated. */ export class AssetsBucket extends Construct { public readonly bucket: s3.Bucket; constructor(scope: Construct, id: string, envName: EnvName) { super(scope, id); const p = prefix(envName); this.bucket = new s3.Bucket(this, "Bucket", { bucketName: `${p}-assets`, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, encryption: s3.BucketEncryption.S3_MANAGED, enforceSSL: true, versioned: true, // Artifacts are reproducible from CI, but RETAIN protects against an // accidental stack delete leaving the box with nothing to pull (see above). removalPolicy: cdk.RemovalPolicy.RETAIN, lifecycleRules: [ { id: "expire-noncurrent-artifact-versions", noncurrentVersionExpiration: cdk.Duration.days(30), abortIncompleteMultipartUploadAfter: cdk.Duration.days(7), }, ], }); } }