Terraform AWS S3 Block Public Access at account and bucket, and a Rust census
Four switches, two levels, stricter wins flag by flag.
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).
| Setting | Stops |
|---|---|
BlockPublicAcls | new public ACLs being set |
IgnorePublicAcls | existing public ACLs from taking effect |
BlockPublicPolicy | new bucket policies that grant public access |
RestrictPublicBuckets | public 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
- Account has all four on. A bucket has none. Can a bucket policy make it public? (No: the account settings apply.)
- Which two settings matter for a bucket policy that grants
"Principal": "*"? (BlockPublicPolicy,RestrictPublicBuckets.) - Why keep bucket-level blocks when the account block is on? (Belt on Both Levels: a later account change does not open them.)
- 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
- Tome: Brikman ch5 (referenced)
- Web: S3 Block Public Access guide · aws_s3_account_public_access_block · aws_s3_bucket_public_access_block · cargo -Zscript
- Paired Cert: Terraform Associate moved blocks (2026-10-11)