AWS S3 Security Best Practices for SOC 2 Compliance
A technical deep-dive into securing Amazon S3 for SOC 2 — covering public access blocks, bucket policies, encryption, versioning, object lock, and access logging.
Why S3 Is a Top SOC 2 Finding Source
S3 buckets are among the most commonly misconfigured AWS resources — and among the most damaging when exposed. Public S3 buckets have leaked customer data at companies across every industry. For SOC 2 auditors, S3 configuration is one of the first things they check. A single publicly accessible bucket with unencrypted customer data is enough to produce a material finding in your report.
The good news: S3 security is entirely configuration-driven. No code changes required. The controls below can be applied in an afternoon and monitored automatically from that point forward.
1. Enable S3 Account-Level Public Access Block (Do This First)
The most important single S3 control. At the AWS account level, enable all four settings:
- Block public access to buckets and objects granted through new access control lists (ACLs)
- Block public access to buckets and objects granted through any access control lists (ACLs)
- Block public access to buckets and objects granted through new public bucket or access point policies
- Block public and cross-account access to buckets and objects through any public bucket or access point policies
aws s3control put-public-access-block \
--account-id YOUR_ACCOUNT_ID \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
This prevents any new or existing bucket from becoming public regardless of individual bucket policies or ACLs — a single account-level setting that overrides everything beneath it.
SecureSpect check: aws.s3.account_public_access_block — fails if any of the four settings is disabled at the account level.
2. Enforce SSL-Only Bucket Policies
Data in transit must be encrypted. Enforce TLS on every bucket by attaching a bucket policy that denies unencrypted HTTP requests:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyNonSSL",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::YOUR-BUCKET",
"arn:aws:s3:::YOUR-BUCKET/*"
],
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
]
}
Apply this policy to every bucket that stores customer data. For new buckets, include it as part of your Terraform or CDK module so it's applied automatically.
SecureSpect check: aws.s3.bucket_ssl_policy — verifies the ssl-only deny policy is present on in-scope buckets.
3. Enable Default Encryption with SSE-KMS
All buckets storing customer data must be encrypted at rest. Configure default encryption using AWS KMS:
aws s3api put-bucket-encryption \
--bucket YOUR-BUCKET \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:REGION:ACCOUNT:key/KEY-ID"
},
"BucketKeyEnabled": true
}]
}'
Use a customer-managed KMS key (not the default AWS-managed S3 key) for buckets storing sensitive data. Customer-managed keys give you key rotation control, access policy control, and a CloudTrail audit trail for every encryption/decryption operation.
SecureSpect check: aws.s3.bucket_encryption — flags buckets with SSE-S3 instead of SSE-KMS, and buckets with no encryption.
4. Enable Versioning on Critical Buckets
Versioning protects against accidental deletion and ransomware. For SOC 2, it supports the Availability criterion — you can recover data to a prior point.
Enable versioning on all buckets that store customer data, audit logs, or compliance evidence:
aws s3api put-bucket-versioning \
--bucket YOUR-BUCKET \
--versioning-configuration Status=Enabled
Combine versioning with Object Lock for CloudTrail log buckets to prevent deletion:
aws s3api put-object-lock-configuration \
--bucket YOUR-CLOUDTRAIL-BUCKET \
--object-lock-configuration '{
"ObjectLockEnabled": "Enabled",
"Rule": {
"DefaultRetention": {
"Mode": "GOVERNANCE",
"Days": 365
}
}
}'
5. Enable S3 Server Access Logging
For audit trails, enable server access logging on all in-scope buckets. Access logs capture every request made to a bucket — who accessed what, when, from where.
aws s3api put-bucket-logging \
--bucket YOUR-BUCKET \
--bucket-logging-status '{
"LoggingEnabled": {
"TargetBucket": "YOUR-ACCESS-LOGS-BUCKET",
"TargetPrefix": "s3-access-logs/YOUR-BUCKET/"
}
}'
Store access logs in a separate, locked logging bucket — not in the same bucket being logged.
6. Cross-Account and Cross-Region Access Controls
If you use S3 replication for disaster recovery, ensure replication is configured with encryption and that destination bucket policies restrict access. Cross-account bucket access should use explicit IAM resource policies rather than public access — and should be documented for auditors.
Audit-Ready S3 Control Summary
| Control | Mechanism | SecureSpect Check |
| No public buckets | Account-level Block Public Access | ✓ Automated |
| TLS-only access | SSL-enforcing bucket policy | ✓ Automated |
| Encryption at rest | SSE-KMS default encryption | ✓ Automated |
| Log integrity | Object Lock on log buckets | ✓ Automated |
| Data recovery | Versioning on data buckets | ✓ Automated |
| Access audit trail | Server access logging | Manual review |
Every S3 control SecureSpect checks maps to CC6.7 (logical access over encryption) or CC7.1 (detection of security events) in the SOC 2 Trust Services Criteria.