Skip to content

feat(edge): no Origin Access Control (OAC) L2 — private S3 origins are hard-blocked on legacy OAI #162

Description

@so0k

Problem

There is no L2 construct for CloudFront Origin Access Control (OAC) anywhere in src/. The only origin-access primitive is the legacy Origin Access Identity (OAI) at src/aws/storage/origin-access-identity.ts.

This is not merely a missing convenience — the private-S3-origin path is hard-blocked on OAI. S3BucketOrigin throws in its constructor when the bucket has no OAI (src/aws/edge/origin.ts:311-317):

if (!this.bucket.bucketOutputs.originAccessIdentity) {
  throw new Error(
    "The bucket must have an origin access identity to be used as a CloudFront origin. " +
      "If the bucket is imported, you must include the originaccess identity in the import options.",
  );
}

and renderS3OriginConfig() can only emit originAccessIdentity (origin.ts:320-326). So there is no way to express "private bucket + CloudFront + OAC" with L2s, even by opting out.

Verified at c2bd9c7 (current main):

$ grep -rniI "originaccesscontrol|origin_access_control" src/ --include="*.ts" -l
src/aws/storage/origin-access-identity.ts   # legacy OAI only — no OAC anywhere

Why it matters

OAI is AWS-legacy; OAC is the documented, recommended mechanism and is required, not optional, for several common cases:

  • SSE-KMS encrypted origin buckets — OAI cannot sign KMS requests; OAC can.
  • Regions launched after December 2022 (opt-in regions), which are SigV4-only.
  • PUT/POST to the origin, and dynamic-request signing generally.

New stacks that follow current AWS guidance therefore cannot be expressed in TerraConstructs today without dropping to L1.

Concrete downstream consumer

KiroCrew's artifact-publishing stack (kirodotdev/KiroCrew, src/kiro_crew/deploy/) provisions private S3 + CloudFront + OAC with a bucket policy pinned via AWS:SourceArn, in both of its code paths (skills/artifact-deploy/templates/base-stack.yaml, and engine.py:212-410). Porting that to TerraConstructs is currently blocked on this gap. It is also the same stack that motivated #99 / #50 (thanks for the functionAssociations fix in #159 — that half is now unblocked on main).

Workaround in use

Drop to the L1 cloudfrontOriginAccessControl resource from @cdktn/provider-aws (present as of v24.8.0 / AWS provider 6.52+), wire renderS3OriginConfig manually via an escape hatch, and hand-write the bucket policy AWS:SourceArn condition against the distribution ARN. The BucketPolicy L2 and iam.PolicyStatement are fine for that last part — it's only the OAC resource and the origin wiring that need L1.

Suggested shape

  1. An OriginAccessControl L2 (likely src/aws/edge/), covering at minimum originAccessControlOriginType: "s3", signingBehavior: "always", signingProtocol: "sigv4".
  2. Teach S3BucketOrigin to accept either an OAI or an OAC, emitting origin_access_control_id on the distribution origin rather than the s3_origin_config.origin_access_identity block. The current unconditional throw becomes "must have one of OAI or OAC".
  3. Optionally a S3BucketOrigin.withOriginAccessControl(bucket) static that creates the OAC and attaches the AWS:SourceArn-scoped bucket policy, mirroring aws-cdk's S3BucketOrigin.withOriginAccessControl.

Two related gaps noticed while looking, mentioned here only so they aren't lost — happy to split into separate issues if preferred:

  • The Bucket L2 wires public_access_block / ownership_controls only on the public: true path; a private bucket gets neither, so an explicitly-hardened origin bucket needs L1 s3BucketPublicAccessBlock / s3BucketOwnershipControls alongside it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions