Skip to main content

Encryption

TSC mapping: CC6.1 (Logical access security — data at rest), CC6.7 (Data transmission and removal controls)

Auditors require evidence that customer data is encrypted at rest and in transit, that encryption keys are managed with proper access controls, and that secrets are not stored in plaintext.


1. AWS KMS — Key Management

Customer-managed KMS keys (CMKs) give you full control over key policies, rotation, and audit trails. Use CMKs instead of AWS-managed keys for any data requiring strong access segregation.

Create a CMK with automatic rotation

# Create a symmetric CMK
aws kms create-key \
--description "SOC2 data encryption key" \
--key-usage ENCRYPT_DECRYPT \
--tags TagKey=compliance,TagValue=soc2

# Enable automatic annual rotation
aws kms enable-key-rotation --key-id <key-id>

# Verify rotation is enabled
aws kms get-key-rotation-status --key-id <key-id>

Restrict key access with a key policy

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowKeyAdmins",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<account>:role/KeyAdminRole"
},
"Action": [
"kms:Create*", "kms:Describe*", "kms:Enable*",
"kms:List*", "kms:Put*", "kms:Update*", "kms:Revoke*",
"kms:Disable*", "kms:Get*", "kms:Delete*", "kms:ScheduleKeyDeletion",
"kms:CancelKeyDeletion"
],
"Resource": "*"
},
{
"Sid": "AllowKeyUsage",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<account>:role/AppRole"
},
"Action": [
"kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*",
"kms:GenerateDataKey*", "kms:DescribeKey"
],
"Resource": "*"
},
{
"Sid": "DenyKeyDeletion",
"Effect": "Deny",
"Principal": "*",
"Action": ["kms:ScheduleKeyDeletion", "kms:DeleteImportedKeyMaterial"],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:PrincipalArn": "arn:aws:iam::<account>:role/KeyAdminRole"
}
}
}
]
}

AWS Config rule:

RuleWhat it checks
kms-cmk-not-scheduled-for-deletionNo CMK is pending deletion
cmk-backing-key-rotation-enabledCMK automatic rotation is enabled

Reference: AWS KMS documentation →


2. S3 — Encryption at Rest

Enable default bucket encryption (SSE-KMS)

aws s3api put-bucket-encryption \
--bucket my-sensitive-bucket \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:<region>:<account>:key/<key-id>"
},
"BucketKeyEnabled": true
}]
}'

Block all public access at the account level

aws s3control put-public-access-block \
--account-id <account-id> \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

Enforce HTTPS-only access

aws s3api put-bucket-policy \
--bucket my-sensitive-bucket \
--policy '{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyHTTP",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-sensitive-bucket",
"arn:aws:s3:::my-sensitive-bucket/*"
],
"Condition": {
"Bool": {"aws:SecureTransport": "false"}
}
}]
}'

Enable S3 access logging

aws s3api put-bucket-logging \
--bucket my-sensitive-bucket \
--bucket-logging-status '{
"LoggingEnabled": {
"TargetBucket": "my-access-logs-bucket",
"TargetPrefix": "s3-access-logs/my-sensitive-bucket/"
}
}'

AWS Config rules:

RuleWhat it checks
s3-bucket-server-side-encryption-enabledDefault encryption on all buckets
s3-bucket-public-access-prohibitedAccount-level public access block
s3-bucket-logging-enabledAccess logging enabled
s3-bucket-ssl-requests-onlyBucket policy denies HTTP

Reference: S3 server-side encryption →


3. EBS — Encryption at Rest

Enable default EBS encryption in every region so all new volumes are encrypted automatically, regardless of how they are created.

# Enable per region (run for every region in use)
aws ec2 enable-ebs-encryption-by-default --region us-east-1
aws ec2 enable-ebs-encryption-by-default --region eu-west-1

# Verify
aws ec2 get-ebs-encryption-by-default --region us-east-1

Verify existing volumes:

aws ec2 describe-volumes \
--query 'Volumes[?Encrypted==`false`].[VolumeId,AvailabilityZone,Size]' \
--output table

AWS Config rule:

RuleWhat it checks
ec2-ebs-encryption-by-defaultDefault EBS encryption is enabled per region
encrypted-volumesAll attached EBS volumes are encrypted

4. RDS — Encryption at Rest

RDS encryption must be enabled at creation time — it cannot be added to an existing unencrypted instance.

# Create an encrypted RDS instance
aws rds create-db-instance \
--db-instance-identifier prod-db \
--db-instance-class db.t3.medium \
--engine postgres \
--engine-version "15.4" \
--master-username admin \
--master-user-password <from-secrets-manager> \
--storage-encrypted \
--kms-key-id arn:aws:kms:<region>:<account>:key/<key-id> \
--backup-retention-period 7 \
--deletion-protection \
--no-publicly-accessible

Audit unencrypted RDS instances:

aws rds describe-db-instances \
--query 'DBInstances[?StorageEncrypted==`false`].[DBInstanceIdentifier,DBInstanceClass,Engine]' \
--output table

AWS Config rules:

RuleWhat it checks
rds-storage-encryptedAll RDS instances have storage encryption enabled
rds-instance-public-access-checkNo RDS instance is publicly accessible
rds-automatic-minor-version-upgrade-enabledAuto minor version upgrades are on
rds-backup-enabledAutomated backups enabled with retention ≥ 7 days

Reference: RDS encryption at rest →


5. AWS Secrets Manager — Secret Storage and Rotation

Store all credentials, API keys, and connection strings in Secrets Manager. Never store them in environment variables, code, or SSM Parameter Store as plain text.

# Store a database credential
aws secretsmanager create-secret \
--name prod/app/database \
--description "Production database credentials" \
--kms-key-id arn:aws:kms:<region>:<account>:key/<key-id> \
--secret-string '{"username":"app_user","password":"<password>","host":"prod-db.cluster.region.rds.amazonaws.com","port":5432}'

# Enable automatic rotation (e.g., every 30 days for RDS)
aws secretsmanager rotate-secret \
--secret-id prod/app/database \
--rotation-lambda-arn arn:aws:lambda:<region>:<account>:function:SecretsManagerRDSRotation \
--rotation-rules AutomaticallyAfterDays=30

Retrieve a secret in application code (do not log the value):

aws secretsmanager get-secret-value \
--secret-id prod/app/database \
--query SecretString \
--output text

AWS Config rule:

RuleWhat it checks
secretsmanager-rotation-enabled-checkAll secrets have rotation configured
secretsmanager-scheduled-rotation-success-checkLast rotation succeeded
secretsmanager-secret-unusedSecrets not accessed in N days are flagged

Reference: AWS Secrets Manager documentation →


6. Other Services — Encryption Checklist

ServiceControlCommand to verify
ElastiCache (Redis)Enable in-transit and at-rest encryptionaws elasticache describe-replication-groups --query 'ReplicationGroups[*].[ReplicationGroupId,AtRestEncryptionEnabled,TransitEncryptionEnabled]'
DynamoDBEnable table encryption with CMKaws dynamodb describe-table --table-name <table> --query 'Table.SSEDescription'
SQSEnable SSE-KMS on all queuesaws sqs get-queue-attributes --attribute-names KmsMasterKeyId
SNSEnable server-side encryptionaws sns get-topic-attributes --topic-arn <arn> --query 'Attributes.KmsMasterKeyId'
EFSEnable encryption at rest and in transitaws efs describe-file-systems --query 'FileSystems[*].[FileSystemId,Encrypted]'
KinesisEnable SSE on data streamsaws kinesis describe-stream-summary --stream-name <name>

SOC 2 Evidence for Encryption

Evidence itemHow to collect
KMS key list with rotation statusaws kms list-keys + get-key-rotation-status per key
S3 bucket encryption configurationaws s3api get-bucket-encryption --bucket <name>
S3 public access block statusaws s3control get-public-access-block --account-id <id>
EBS default encryption statusaws ec2 get-ebs-encryption-by-default per region
RDS encryption statusaws rds describe-db-instances
Secrets Manager rotation statusaws secretsmanager list-secrets
Config rule compliance (encryption rules)AWS Config console → Compliance by rule