Back to S3 & Storage
Q2Question 2 of 5

Securing an S3 Bucket and Why Public Buckets Cause Breaches

Key Concepts
  • S3 Block Public Access at the account level is the most important control, overriding any policy or ACL that would make objects public.
  • Bucket policies and IAM policies grant least-privilege access to specific principals for specific actions on specific resources.
  • SSE-KMS encryption at rest provides key rotation, fine-grained key policies, and a CloudTrail audit of every key use.
  • A deny condition for non-TLS requests forces encryption in transit for all S3 operations.
  • A VPC gateway endpoint keeps S3 traffic private on the AWS network, never traversing the public internet.
  • Access logging and CloudTrail data events provide a full audit trail of who accessed which object, when, and from where.
  • Public buckets cause breaches because one misconfigured permission exposes all objects to the internet, and automated scanners constantly crawl for open buckets.

Definition

Secure S3 with layered controls -- block public access, least-privilege policies, encryption at rest, private VPC endpoints, and logging -- because one wrong permission on a public bucket exposes everything to the whole internet.
  • Block Public Access – a set of four account-level and bucket-level settings that override any policy or ACL that would make objects public.
  • Bucket policies and IAM policies – JSON-based access rules that grant least-privilege permissions to specific principals for specific actions on specific resources.
  • SSE-S3 and SSE-KMS – server-side encryption at rest using S3-managed keys (SSE-S3) or AWS KMS-managed keys (SSE-KMS) with full audit capability.
  • VPC gateway endpoint – a private connection from a VPC to S3 that keeps traffic on the AWS network, never traversing the public internet.
  • Access logging and CloudTrail – audit trail of who accessed which object, when, and from which IP address.

Think of an S3 bucket as a vault at National Bank of India. Block Public Access is the master rule that the vault can never be left open to the street, no matter what any junior clerk does. Bucket policies and IAM are the list of exactly which employees may open which drawers. Encryption at rest is keeping documents in a locked safe inside the vault. A VPC endpoint is a private internal corridor so documents never go out onto the public street. Access logging is the CCTV recording every entry. A public bucket is like leaving the vault door open onto a busy public road -- anyone walking past can take everything.

Understanding the Concepts

What is S3 Block Public Access?

S3 Block Public Access is a set of four settings at the account and bucket level that override any policy or ACL that would make objects public. The four settings block new public ACLs, block all public ACLs, block new public bucket policies, and restrict all public access. For a bank, this should be enabled at the account level so no team can accidentally expose a bucket, even if someone writes a bad policy.

BASH
# Enable Block Public Access at the account level
aws s3control put-public-access-block \
  --account-id 111122223333 \
  --public-access-block-configuration \
  BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
JAVA
package com.bank.security;

public class BlockPublicAccessExplainer {
    public static void printSettings() {
        System.out.println("BlockPublicAcls: prevents new public ACLs from being set");
        System.out.println("IgnorePublicAcls: ignores all existing public ACLs");
        System.out.println("BlockPublicPolicy: rejects bucket policies that grant public access");
        System.out.println("RestrictPublicBuckets: restricts access to only AWS principals");
        System.out.println();
        System.out.println("Set at ACCOUNT level to protect every bucket as a backstop.");
    }
}

How do bucket policies and IAM policies work together?

IAM identity policies grant permissions to users and roles, while bucket policies are attached to the bucket and can grant or deny access to specific principals. The effective permission is the union of all applicable policies, with explicit denies always winning. You write them with least privilege: allow only the exact actions, the exact principal, and the exact resource.

JAVA
// A least-privilege bucket policy
package com.bank.security;

public class BucketPolicyBuilder {
    public static String buildStatementServicePolicy() {
        return """
            {
              "Version": "2012-10-17",
              "Statement": [{
                "Effect": "Allow",
                "Principal": {"AWS": "arn:aws:iam::111122223333:role/statement-service"},
                "Action": ["s3:GetObject"],
                "Resource": "arn:aws:s3:::nbi-statements/*"
              },{
                "Effect": "Deny",
                "Principal": "*",
                "Action": "s3:*",
                "Resource": "arn:aws:s3:::nbi-statements/*",
                "Condition": {"Bool": {"aws:SecureTransport": "false"}}
              }]
            }
            """;
    }
}

The second statement above denies any request not over TLS, forcing encryption in transit.

What is encryption at rest with SSE-S3 and SSE-KMS?

SSE-S3 uses keys that S3 manages for you. It is simple and free, applied transparently on every upload. SSE-KMS uses AWS Key Management Service keys, which gives you key rotation, fine-grained key policies that control who can use the key, and an audit trail in CloudTrail of every key use. For regulated banking data, SSE-KMS is the right choice. You can require encryption by rejecting unencrypted uploads via a bucket policy condition.

JAVA
package com.bank.security;

public class EncryptionPolicyBuilder {
    public static String requireKmsEncryption() {
        return """
            {
              "Version": "2012-10-17",
              "Statement": [{
                "Effect": "Deny",
                "Principal": "*",
                "Action": "s3:PutObject",
                "Resource": "arn:aws:s3:::nbi-kyc/*",
                "Condition": {
                  "StringNotEquals": {
                    "s3:x-amz-server-side-encryption": "aws:kms"
                  }
                }
              }]
            }
            """;
    }
}

What is a VPC gateway endpoint for S3?

A VPC gateway endpoint keeps S3 traffic private. Without it, an EC2 instance in a private subnet reaches S3 over the public internet via a NAT gateway. With a gateway endpoint, the traffic stays on the AWS network and never touches the internet. You can even restrict the bucket to only accept requests coming through that endpoint using a bucket policy condition.

JAVA
package com.bank.security;

public class VpcEndpointPolicyBuilder {
    public static String restrictToVpcEndpoint(String vpceId) {
        return """
            {
              "Version": "2012-10-17",
              "Statement": [{
                "Effect": "Deny",
                "Principal": "*",
                "Action": "s3:*",
                "Resource": ["arn:aws:s3:::nbi-ledger", "arn:aws:s3:::nbi-ledger/*"],
                "Condition": {
                  "StringNotEquals": {
                    "aws:sourceVpce": "%s"
                  }
                }
              }]
            }
            """.formatted(vpceId);
    }
}

Why are public buckets a common breach source?

S3 is used to store huge amounts of sensitive data, and making a bucket public requires only one wrong setting or ACL. Once public, every object is reachable by anyone on the internet with just the URL, no credentials needed, and automated scanners constantly crawl for open buckets. The window between a mistake and a breach is tiny, and the blast radius is the entire bucket.

JAVA
package com.bank.security;

public class PublicBucketRisk {
    public static void demonstrateRisk() {
        System.out.println("Without Block Public Access:");
        System.out.println("  1. Developer adds a policy with Principal: *");
        System.out.println("  2. Every object becomes publicly accessible");
        System.out.println("  3. Automated scanners find it within hours");
        System.out.println("  4. All customer data is exposed");
        System.out.println();
        System.out.println("With Block Public Access at account level:");
        System.out.println("  1. Developer adds a policy with Principal: *");
        System.out.println("  2. Block Public Access OVERRIDES the policy");
        System.out.println("  3. Data remains private");
    }
}

What is access logging and how does CloudTrail help?

S3 access logging writes a record for every request to a separate logging bucket. CloudTrail data events provide a richer, structured audit trail of S3 API calls including the caller identity, source IP, and time. For a bank, both are essential for compliance and incident investigation.

JAVA
package com.bank.security;

public class AuditTrailSetup {
    public static void explain() {
        System.out.println("S3 Access Logging:");
        System.out.println("  - Logs every request (GET, PUT, DELETE)");
        System.out.println("  - Written to a separate bucket");
        System.out.println("  - Best-effort delivery");
        System.out.println();
        System.out.println("CloudTrail Data Events:");
        System.out.println("  - Structured API call records");
        System.out.println("  - Caller identity, source IP, time");
        System.out.println("  - Guaranteed delivery to S3 or CloudWatch Logs");
        System.out.println("  - Required for compliance audits at NBI");
    }
}

What is defense in depth and why does it matter?

Defense in depth means layering multiple controls so that no single misconfiguration can expose data. Block Public Access is the backstop, but even with it you still use least-privilege policies, encryption, VPC endpoints, and logging. Each layer catches what another might miss.

JAVA
package com.bank.security;

import java.util.List;

public class DefenseInDepthChecklist {
    public static List<String> layers() {
        return List.of(
            "1. Block Public Access at account level (backstop)",
            "2. Least-privilege bucket policy (specific principals, actions, resources)",
            "3. TLS-only condition (deny non-HTTPS requests)",
            "4. SSE-KMS encryption at rest (audit trail on key use)",
            "5. VPC gateway endpoint (private traffic path)",
            "6. S3 access logging + CloudTrail data events (audit)",
            "7. Versioning + Object Lock (recovery and WORM compliance)"
        );
    }
}

What should a senior engineer know?

The single most important control is account-level Block Public Access because it cannot be overridden by a careless bucket policy. A senior engineer also knows that S3 ACLs are legacy and should be disabled in favor of bucket policies, that bucket ownership controls (BucketOwnerEnforced) simplify cross-account uploads, and that S3 Object Ownership settings can disable ACLs entirely. For cross-account access, the recommended pattern is a bucket policy that grants the external account's role access, combined with the external role's IAM policy, rather than ACLs.

Visual Overview

DIAGRAM
               S3 Security Layers (Defense in Depth)

  ┌────────────────────────────────────────────────────────┐
  │              Block Public Access (Account)             │
  │        Overrides any public policy or ACL              │
  │  ┌──────────────────────────────────────────────────┐  │
  │  │           Bucket Policy (Least Privilege)        │  │
  │  │     Specific principals, actions, resources      │  │
  │  │  ┌────────────────────────────────────────────┐  │  │
  │  │  │         TLS-Only Condition                 │  │  │
  │  │  │    Deny requests not over HTTPS            │  │  │
  │  │  │  ┌──────────────────────────────────────┐  │  │  │
  │  │  │  │    SSE-KMS Encryption at Rest        │  │  │  │
  │  │  │  │  Key rotation, audit trail           │  │  │  │
  │  │  │  │  ┌────────────────────────────────┐  │  │  │  │
  │  │  │  │  │  VPC Gateway Endpoint          │  │  │  │  │
  │  │  │  │  │  Private traffic, no internet  │  │  │  │  │
  │  │  │  │  │  ┌──────────────────────────┐  │  │  │  │  │
  │  │  │  │  │  │  Access Logging +        │  │  │  │  │  │
  │  │  │  │  │  │  CloudTrail Data Events  │  │  │  │  │  │
  │  │  │  │  │  │  Full audit trail        │  │  │  │  │  │
  │  │  │  │  │  └──────────────────────────┘  │  │  │  │  │
  │  │  │  │  └────────────────────────────────┘  │  │  │  │
  │  │  │  └──────────────────────────────────────┘  │  │  │
  │  │  └────────────────────────────────────────────┘  │  │
  │  └──────────────────────────────────────────────────┘  │
  └────────────────────────────────────────────────────────┘

Key Takeaway

I secure an S3 bucket in layers, because any single control can be misconfigured and a bank cannot afford one mistake to expose customer data. The first and most important layer is S3 Block Public Access, which I enable at the account level so that no bucket can be made public even if someone writes a careless policy or ACL. It acts as a backstop that overrides everything beneath it.

The next layers are access control and encryption. I use least-privilege bucket policies that grant only the exact principals the exact actions they need, and I add a deny condition for non-TLS requests to force encryption in transit. For encryption at rest I use SSE-KMS because it gives me key rotation, fine-grained key policies, and a CloudTrail audit of every key use. To keep traffic private I use a VPC gateway endpoint so services in private subnets reach S3 over the AWS network and never over the public internet. Finally, I turn on access logging and CloudTrail data events for a full audit trail.

Public buckets are a common breach source because S3 holds enormous amounts of sensitive data and making a bucket public takes only one wrong setting. Once public, every object is reachable by anyone with the URL and no credentials, and automated scanners constantly crawl the internet looking for open buckets, so the window between mistake and breach is tiny.

At National Bank of India a developer once added a policy that would have made the entire statements bucket public, but because I had enabled Block Public Access at the account level the data was never actually exposed -- the backstop overrode the bad policy. On top of that I ran least-privilege bucket policies, SSE-KMS encryption, a TLS-only condition, a VPC gateway endpoint, and full access logging.

Remember one sentence: enable S3 Block Public Access at the account level as the non-overridable backstop, then layer least-privilege policies, SSE-KMS encryption, VPC endpoints, and access logging so no single misconfiguration can expose customer data.

---

Share
Use ← → arrow keys to navigate