AWS Encryption Best Practices for SOC 2 Compliance
The complete guide to AWS encryption controls for SOC 2 — KMS key management, RDS encryption, EBS encryption, Secrets Manager, and exactly what auditors check.
SOC 2 and Encryption Requirements
SOC 2's CC6.7 requires that cryptographic protections are used to protect data in transit and at rest. For AWS environments, this maps to a specific set of configurations across your storage, compute, and secrets infrastructure.
Encryption findings are among the most common in SOC 2 audits — not because teams don't understand encryption, but because AWS services default to unencrypted configurations or because accounts accumulate resources that predate encryption policies.
1. AWS KMS — The Foundation
AWS Key Management Service is the cryptographic foundation for most AWS encryption. For SOC 2, the key decisions are:
Use customer-managed keys (CMKs), not AWS-managed keys, for sensitive data. AWS-managed keys (the ones AWS creates automatically when you enable encryption) work, but they give you no key policy control, no rotation control, and no audit trail beyond basic CloudTrail. CMKs give you explicit key policies, automatic rotation, and every encryption/decryption operation logged to CloudTrail.
Enable automatic key rotation:
aws kms enable-key-rotation --key-id YOUR-KEY-ID
Automatic rotation generates a new key material annually while maintaining the same key ID — existing encrypted data is transparently re-encrypted. For SOC 2, the relevant check: is automatic rotation enabled on customer-managed keys? SecureSpect flags every CMK with rotation disabled.
Restrict key access via key policies. The default key policy is too permissive — it allows the account root to perform any KMS action. Use a least-privilege key policy:
{
"Sid": "AllowRDSEncryption",
"Effect": "Allow",
"Principal": {
"Service": "rds.amazonaws.com"
},
"Action": [
"kms:Decrypt",
"kms:GenerateDataKey",
"kms:DescribeKey"
],
"Resource": "*"
}
2. RDS Encryption
All production RDS instances must be encrypted at rest. RDS encryption is enabled at instance creation and cannot be enabled on an existing unencrypted instance without creating an encrypted snapshot and restoring.
# Create encrypted RDS instance
aws rds create-db-instance \
--db-instance-identifier prod-postgres \
--storage-encrypted \
--kms-key-id arn:aws:kms:REGION:ACCOUNT:key/KEY-ID \
--db-instance-class db.t3.medium \
--engine postgres \
--master-username admin \
--master-user-password "$(openssl rand -base64 32)"
To encrypt an existing unencrypted instance:
# Create encrypted snapshot
aws rds create-db-snapshot \
--db-instance-identifier existing-db \
--db-snapshot-identifier pre-encryption-backup
aws rds copy-db-snapshot \
--source-db-snapshot-identifier pre-encryption-backup \
--target-db-snapshot-identifier encrypted-snapshot \
--kms-key-id arn:aws:kms:REGION:ACCOUNT:key/KEY-ID
# Restore from encrypted snapshot to new instance
aws rds restore-db-instance-from-db-snapshot \
--db-instance-identifier prod-postgres-encrypted \
--db-snapshot-identifier encrypted-snapshot
SecureSpect check: aws.rds.storage_encrypted — flags every RDS instance where StorageEncrypted: false.
3. EBS Volume Encryption
EC2 root volumes and data volumes must be encrypted. Enable account-level default encryption so new volumes are always encrypted:
aws ec2 enable-ebs-encryption-by-default --region YOUR-REGION
Apply this to every region where you run EC2 instances. Verify existing unencrypted volumes:
aws ec2 describe-volumes \
--filters "Name=encrypted,Values=false" \
--query 'Volumes[*].[VolumeId,State,Attachments[0].InstanceId]' \
--output table
Unencrypted volumes require snapshot → encrypted copy → new volume → re-attach. Plan this change for maintenance windows.
4. Secrets Manager — Eliminating Hardcoded Credentials
Hardcoded database passwords, API keys, and other secrets in application code or environment variables are a SOC 2 finding. Secrets Manager provides secure, audited, rotation-capable secret storage:
# Store a database credential
aws secretsmanager create-secret \
--name "prod/postgres/admin" \
--secret-string '{"username":"admin","password":"GENERATE_STRONG_PASSWORD"}' \
--kms-key-id arn:aws:kms:REGION:ACCOUNT:key/KEY-ID
# Enable automatic rotation
aws secretsmanager rotate-secret \
--secret-id "prod/postgres/admin" \
--rotation-rules AutomaticallyAfterDays=30
In your application, retrieve secrets at runtime:
import boto3
import json
def get_secret(secret_name):
client = boto3.client('secretsmanager')
response = client.get_secret_value(SecretId=secret_name)
return json.loads(response['SecretString'])
SecureSpect check: aws.secrets_manager.secrets_encrypted — verifies all secrets are encrypted with a CMK, not the default AWS-managed key.
5. DynamoDB and ElastiCache Encryption
DynamoDB tables and ElastiCache clusters that store customer data must be encrypted at rest.
# Create encrypted DynamoDB table
aws dynamodb create-table \
--table-name users \
--sse-specification Enabled=true,SSEType=KMS,KMSMasterKeyId=KEY-ID \
--attribute-definitions AttributeName=userId,AttributeType=S \
--key-schema AttributeName=userId,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
# Create encrypted ElastiCache Redis cluster
aws elasticache create-replication-group \
--replication-group-id prod-redis \
--at-rest-encryption-enabled \
--transit-encryption-enabled \
--kms-key-id KEY-ID \
--cache-node-type cache.t3.small
Encryption Control Summary for Auditors
| Data store | Encryption mechanism | SecureSpect check |
| S3 | SSE-KMS with CMK | ✓ Automated |
| RDS | Storage encryption with KMS | ✓ Automated |
| EBS | Volume encryption by default | ✓ Automated |
| DynamoDB | KMS SSE | ✓ Automated |
| Secrets | Secrets Manager with CMK | ✓ Automated |
| ElastiCache | At-rest + in-transit encryption | ✓ Automated |
| KMS keys | Automatic rotation enabled | ✓ Automated |
All encryption checks in SecureSpect map to CC6.7 of the SOC 2 Trust Services Criteria — protection of information through encryption.