Skip to main content
emnode / learn
Compliance

Enable S3 access and object-level logging

One capability across S3 server access logs and CloudTrail data events: make every bucket holding sensitive data keep a record of who read, wrote, and deleted which object, so a leak shows up in the audit trail instead of nowhere.

14 min·10 sections·AWS

Last reviewed

Remediates AWS Security Hub: S3.9S3.22S3.23

S3 access logging: the basics

Why your bucket records who configured it but not who used it

S3 logs who configures a bucket by default, but not who uses it. There are two complementary ways to close that object-level gap. S3 server access logging is a per-bucket setting that makes S3 write a plain-text line for every request hitting the bucket (every GET, PUT, DELETE, LIST) into a target bucket you nominate, free of ingestion charges. CloudTrail data events are the newer path: every object-level GetObject, PutObject, and DeleteObject call is captured into a CloudTrail trail with full IAM context, queryable in Athena and routable to EventBridge, billed per event. Both record what server access logging and management-event-only CloudTrail miss: who actually touched the data.

AWS Security Hub turns each into its own control, which is why a single estate can fail several object-logging checks at once. S3.9 fails any bucket with no LoggingEnabled block in its BucketLogging config (server access logging off). S3.22 fails an account with no multi-Region CloudTrail trail logging write data events for S3, and S3.23 fails when no such trail logs read data events. They look like separate problems on the report, but they are one capability: make sure the buckets holding sensitive data keep a durable record of object-level access, so the question "what was actually accessed?" has an answer.

It is flagged because reads and writes are the half of object access that maps directly to data exfiltration and tampering. A leaked credential, an over-broad IAM role, or an over-shared presigned URL usually reads your data and walks out, or overwrites it, rather than touching the bucket's configuration. Without object-level logging, the most security-relevant question after an incident has no answer in your logs, and most breach-notification laws read "we cannot rule it out" as "notify everyone."

In this lesson you will learn how S3 server access logging differs from CloudTrail data events, how to find every bucket and account that is not keeping an object-level access trail, and how to enable both without setting fire to the storage or CloudTrail bill. The Controls this lesson covers section lists every Security Hub control in this capability, each linking to a deep page with the exact check and a copy-and-paste fix.

Fun fact

The Capital One bucket was logs nobody read

When the 2019 Capital One breach was disclosed, one detail in the post-mortem was that the attacker's requests had been faithfully captured in S3 server access logs the whole time. The logs existed; nobody was tailing them. The forensics team reconstructed the entire intrusion timeline in 48 hours from those text files alone. The opposite failure is just as common: incident-response firms report year after year that a large share of cloud data-exposure cases involve buckets where object-level data-event logging was never enabled, so investigators can confirm a credential had GetObject on a bucket of customer records but cannot prove which objects were actually downloaded. Under most breach-notification laws, that is the difference between notifying 4,000 affected customers and notifying all 4 million.

Finding buckets with no access trail

Marco is reviewing the morning Security Hub digest at a fintech preparing for its annual SOC 2 review. He sees 47 instances of S3.9 across the production account, plus S3.22 and S3.23 failing at the account level: almost no bucket keeps a server access log, and CloudTrail records no object reads or writes at all.

Rather than bulk-fix everything blind, he confirms the exact check Security Hub runs and picks one bucket to validate the fix before scripting it across the fleet.

Run the audit query Security Hub uses for S3.9. An empty object is the failing case: no LoggingEnabled block means no server access logging.

$ aws s3api get-bucket-logging --bucket fintech-customer-uploads
{
}
# Empty response = no LoggingEnabled config = S3.9 FAIL.
# CloudTrail data events are also off, so S3.22 (write) and S3.23 (read) fail too.

The exact check Security Hub runs. Buckets holding sensitive data come first, since those are the ones whose access you would most need to scope.

How S3 records object-level accessdeep dive

There are two largely independent paths. Server access logs (S3.9) are the legacy mechanism: S3 batches request records server-side and writes plain-text files into a target bucket on a best-effort schedule (usually 1-5 minutes), free of ingestion charges. The control evaluates per bucket and fails when get-bucket-logging returns an empty object. CloudTrail data events (S3.22 for writes, S3.23 for reads) are the newer path: every object-level API call is captured into a CloudTrail trail with full IAM context and JSON schema, billed per event. These controls evaluate at the account level (resource type AWS::::Account) and ask whether the account has a multi-Region trail logging the relevant data events across S3; the multi-Region requirement is the detail people miss, because a single-Region trail does not satisfy them.

The destination plumbing for server access logging has a chicken-and-egg trap: the target bucket needs its own logging configuration or Security Hub flags it too, so the standard pattern is a central log-archive bucket that logs to itself with a separate prefix. The target bucket also needs a policy granting s3:PutObject to the logging.s3.amazonaws.com service principal (with an aws:SourceAccount condition to prevent confused-deputy abuse), which for newer accounts with ACLs disabled is the only mechanism that works. Use SSE-S3 rather than SSE-KMS on the target bucket, because KMS requires the logging principal to hold kms:GenerateDataKey on the key and gets messy across accounts.

For data events, you scope with advanced event selectors so the cost tracks the risk: log read and write data events for a sensitive prefix only, and leave high-traffic public buckets out where logging would be pure cost with no audit value. The same selector can cover read and write, so in practice you configure both S3.22 and S3.23 at once. Server access logs are best-effort and idiosyncratically formatted but cover the audit case cheaply; CloudTrail data events are the right complement when you need guaranteed, structured, IAM-attributed capture or real-time alerting. Most teams use server access logs broadly and data events scoped to the buckets that hold regulated data.

What is the impact of running buckets without access logging?

The direct impact is forensic blindness. S3 buckets are the long-term resting place for the most sensitive data in most organisations: backups, exports, customer uploads, training data. The bucket policy controls who can access objects; access and data-event logging are the only things that record who did. Without them, breach disclosure has to assume worst-case, because there is no way to scope down to what an attacker actually read or deleted. The cost difference between "we know it was these 14 objects" and "we must notify every customer in the bucket" can be millions of dollars in disclosure, legal, and customer-credit costs.

The regulatory impact is just as direct. SOC 2, ISO 27001, HIPAA, and PCI DSS requirement 10 all require logging of access to systems holding sensitive data, and "S3 buckets without access logging" is the textbook audit finding. For organisations with continuous-compliance attestations, an open S3.9, S3.22, or S3.23 finding is itself a control failure regardless of whether anything was breached, and it gets cited the next year if it is not fixed.

The operational impact is that incident-response time blows up. Without logs, an investigation that should take an afternoon ("did this leaked key touch our PII bucket, and what did it do?") becomes a multi-week effort of inference from billing data, CloudFront logs, and customer-side artefacts. Every additional hour of investigation has both direct labour cost and reputational cost as the breach window stays open.

How do you enable S3 access logging safely?

Work the capability as one loop rather than chasing individual findings. The order matters: stand up the destination before sources write to it, scope data events before you turn them on, and set retention before logs pile up.

1. Inventory which buckets and accounts keep an access trail

Sweep every bucket with get-bucket-logging to find the S3.9 failures, and check each account's CloudTrail trails for S3 read and write data-event selectors to find the S3.22 and S3.23 gaps. Rank buckets by data sensitivity (customer PII, payment records, and regulated exports first; build artefacts and public assets last), because that ranking decides both where logging is mandatory and where data events would be pure cost.

2. Stand up a central log-archive bucket first

Create one log-archive bucket per account (or one in a dedicated logging account), pointing its own logging at itself with a separate prefix so it does not fail S3.9 too. Use SSE-S3 for encryption, enforce TLS, and block public access. Grant s3:PutObject to logging.s3.amazonaws.com with an aws:SourceAccount condition so the logging service can write to it; for ACL-disabled accounts this bucket-policy grant is the only mechanism that works.

3. Enable server access logging broadly and data events scoped

Bulk-enable server access logging across source buckets with put-bucket-logging and a per-source prefix; the call is idempotent and S3.9 is change-triggered, so it can pass within minutes. For S3.22 and S3.23, add an advanced event selector to a multi-Region trail that logs read and write data events, scoped to sensitive prefixes only so the per-event CloudTrail cost tracks the risk rather than the whole estate's traffic.

4. Cap retention and prevent recurrence

Lifecycle the log-archive bucket aggressively (Standard for 30 days, then Glacier tiers, expiring at the compliance horizon: 12 months for SOC 2 and PCI, 6 years for HIPAA) and set retention on any CloudWatch destinations, because server access logs accumulate fast on busy buckets. Then make logging a provisioning default backed by a Config rule, so every new bucket holding sensitive data is born compliant rather than remediated after the fact.

# Enable server access logging on a flagged bucket, pointing at a central archive bucket.
aws s3api put-bucket-logging \
  --bucket fintech-customer-uploads \
  --bucket-logging-status '{"LoggingEnabled":{"TargetBucket":"fintech-s3-access-logs","TargetPrefix":"customer-uploads/"}}'

# Log object-level read and write data events, scoped to the sensitive prefix only,
# on a multi-Region trail (which is what S3.22 and S3.23 require).
aws cloudtrail put-event-selectors \
  --trail-name acme-management-trail \
  --advanced-event-selectors '[{"Name":"Log read+write data events for customer-records","FieldSelectors":[{"Field":"eventCategory","Equals":["Data"]},{"Field":"resources.type","Equals":["AWS::S3::Object"]},{"Field":"resources.ARN","StartsWith":["arn:aws:s3:::acme-customer-records/"]}]}]'

Quick quiz

Question 1 of 5

Security Hub shows S3.9 failing on many buckets and S3.22 and S3.23 failing at the account level. What is the most efficient way to think about them?

You can now treat S3 access logging as one capability rather than a scatter of findings: inventory which buckets and accounts keep an object-level trail, stand up a central log-archive bucket without tripping the same finding on itself, enable server access logging broadly and CloudTrail data events scoped to sensitive prefixes, and lifecycle the logs so storage stays bounded. The Controls this lesson covers section below links every control in this group to its deep page and fix.

Back to the library

Controls this lesson covers

One capability, many AWS Security Hub controls. This lesson is the shared playbook; each control below keeps its own deep page with the exact check, severity and a copy-and-paste fix.