AWS SecurityAWSS3SOC 2EncryptionBucket PolicyCompliance

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.

SecureSpect Team··9 min read

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

ControlMechanismSecureSpect Check
|---|---|---|
No public bucketsAccount-level Block Public Access✓ Automated
TLS-only accessSSL-enforcing bucket policy✓ Automated
Encryption at restSSE-KMS default encryption✓ Automated
Log integrityObject Lock on log buckets✓ Automated
Data recoveryVersioning on data buckets✓ Automated
Access audit trailServer access loggingManual 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.

Automate your SOC 2 evidence collection

Connect your AWS and GitHub environments and start collecting audit-ready evidence today. Free to start.

Start Free →More Articles