Hedronite · Ops Lesson · TF track · Sun 2026-10-11 · BACKFILL

Terraform AWS S3 Block Public Access at account and bucket, and a Rust census

Four switches, two levels, stricter wins flag by flag.

Lesson Class: Ops (T2 · phase A)
Referent: S3 Block Public Access · account + bucket
Not a repeat: not 10-02 CW · 10-05 Azure · 10-08 GCP KMS
Paired Cert: Terraform Associate moved blocks
Name the Four
Two for ACLs, two for policies.
Stricter Wins
Effective = account OR bucket.
Belt on Both Levels
Pin buckets that matter.
A bucket with no config inherits the account's gap.

Four switches, two levels, and the stricter one wins flag by flag. Turn all four on at the account, keep them on each bucket, and count the buckets that could still go public.

§I. Frame

S3 Block Public Access (BPA) is four boolean settings. They exist at the account level and at the bucket level (access points too). AWS documents that S3 applies the most restrictive combination of the account and bucket settings. New buckets get all four on by default, but older buckets and accounts often carry partial configs.

The task: set all four at the account in Terraform, pin them on a bucket that matters, and write a cargo script that reports every bucket whose effective setting is incomplete.

§II. Three techniques

Name the Four (named technique).

SettingStops
BlockPublicAclsnew public ACLs being set
IgnorePublicAclsexisting public ACLs from taking effect
BlockPublicPolicynew bucket policies that grant public access
RestrictPublicBucketspublic and cross-account access through an existing public policy

Two cover ACLs, two cover policies. A bucket with only the ACL pair on can still be opened by a policy.

Stricter Wins, Flag by Flag (named technique). The effective value of each flag is account OR bucket. Account BlockPublicPolicy=false plus bucket BlockPublicPolicy=true gives true. The census computes exactly that OR.

Belt on Both Levels (named technique). Set the account block and still set the bucket block on buckets you care about. If someone relaxes the account setting later, those buckets stay closed.

§III. Terraform

s3-bpa.tf (validated, not applied):

resource "aws_s3_account_public_access_block" "this" {
  block_public_acls       = true
  ignore_public_acls      = true
  block_public_policy     = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_public_access_block" "tmp_uploads" {
  bucket                  = aws_s3_bucket.tmp_uploads.id
  block_public_acls       = true
  ignore_public_acls      = true
  block_public_policy     = true
  restrict_public_buckets = true
}

terraform init -backend=false installed hashicorp/aws v6.68.0; terraform validate -json returned "valid": true with 0 diagnostics.

§IV. The census

s3-bpa-census.rs is a cargo script (cargo +nightly -Zscript, manifest in the frontmatter) with serde. It reads the JSON that aws s3control get-public-access-block and per-bucket aws s3api get-public-access-block return (a missing bucket config becomes null), ORs account and bucket per flag, and exits 2 when any bucket needs attention. Same pattern as 10-08: serde over CLI JSON rather than aws-sdk-s3, to keep compile time inside the budget.

fn or(self, o: Bpa) -> Bpa {
    Bpa {
        block_public_acls: self.block_public_acls || o.block_public_acls,
        ignore_public_acls: self.ignore_public_acls || o.ignore_public_acls,
        block_public_policy: self.block_public_policy || o.block_public_policy,
        restrict_public_buckets: self.restrict_public_buckets || o.restrict_public_buckets,
    }
}

Real output over fixture/s3-bpa.json (synthetic: account has only the ACL pair on):

account 111122223333 block-public-access: INCOMPLETE
ok         logs-archive
ATTENTION  static-site  missing=BlockPublicPolicy|RestrictPublicBuckets  (bucket config partial)
ATTENTION  tmp-uploads  missing=BlockPublicPolicy|RestrictPublicBuckets  (no bucket config)
3 bucket(s), 2 need attention

Exit code 2, 0 warnings. tmp-uploads has no bucket config at all, so it inherits the account's gap. Applying the Terraform above closes both: the account block fixes the OR for every bucket.

s3-bpa-census.nix is a writeShellApplication that pins awscli2 and jq and builds the input file from a live account. Written, not built this fire.

§V. Drills

  1. Account has all four on. A bucket has none. Can a bucket policy make it public? (No: the account settings apply.)
  2. Which two settings matter for a bucket policy that grants "Principal": "*"? (BlockPublicPolicy, RestrictPublicBuckets.)
  3. Why keep bucket-level blocks when the account block is on? (Belt on Both Levels: a later account change does not open them.)
  4. The census exits 2 in CI. What does the pipeline do with that? (Fail the job and print the ATTENTION lines.)

Close

Name the Four, Stricter Wins Flag by Flag, Belt on Both Levels. Set the account block in Terraform, pin the buckets that matter, and let the census count what is still open.

Related