Data Layer: Encryption and Access Controls
The data layer protects your information even when other layers fail. If an attacker gets past your network controls and obtains some level of IAM access, encryption ensures they cannot read your data without also having access to the encryption keys. This layer covers encryption at rest, encryption in transit, and access controls on data stores.
Encryption at Rest​
Encryption at rest means data is encrypted when stored on disk. On AWS, this is handled through the Key Management Service (KMS) and service-level encryption settings.
KMS Key Types​
| Key Type | Managed By | Rotation | Key Policy Control | Cost |
|---|---|---|---|---|
| AWS owned keys | AWS | Automatic | No customer control | Free |
| AWS managed keys | AWS (per service) | Automatic (yearly) | View only, cannot modify | Free (pay per API call) |
| Customer managed keys (CMK) | You | Configurable (yearly auto or on-demand) | Full control | $1/month + API calls |
When to use each:
- AWS owned keys: Default encryption where you need no control over the key. Good enough for many workloads.
- AWS managed keys: When you want to see key usage in CloudTrail but do not need custom key policies. The key ARN format is
aws/service-name. - Customer managed keys: When you need key policies (restrict who can use the key), custom rotation schedules, cross-account access, or the ability to disable/delete the key. Required for compliance frameworks that mandate key management controls.
Service-Level Encryption​
Most AWS storage services support encryption at rest:
| Service | Encryption Setting | Default |
|---|---|---|
| S3 | SSE-S3, SSE-KMS, or SSE-C | SSE-S3 enabled by default |
| EBS | KMS encryption | Must be enabled (can set account-level default) |
| RDS | KMS encryption | Must be enabled at creation (cannot enable later) |
| DynamoDB | AWS owned key or CMK | AWS owned key by default |
| EFS | KMS encryption | Must be enabled at creation |
Important: For RDS, encryption must be enabled when you create the instance. You cannot encrypt an existing unencrypted RDS instance in place. You must create an encrypted snapshot copy and restore from it.
Encryption in Transit​
Encryption in transit protects data as it moves between systems. On AWS, this means TLS.
ACM and ALB HTTPS​
AWS Certificate Manager (ACM) provides free TLS certificates for use with AWS services. The standard pattern:
- Request a certificate in ACM for your domain.
- Attach it to an ALB HTTPS listener.
- The ALB terminates TLS and forwards traffic to your backend.
- Configure the ALB to redirect HTTP (port 80) to HTTPS (port 443).
Enforcing TLS​
For S3, you can enforce TLS with a bucket policy condition:
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
],
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
This denies any request that does not use TLS, ensuring all S3 access is encrypted in transit.
S3 Bucket Policies and Block Public Access​
S3 is the most commonly misconfigured service for data exposure. Two mechanisms protect against accidental public access:
S3 Block Public Access​
Block Public Access is a set of four account-level and bucket-level settings that override any policy or ACL that would make data public:
| Setting | What It Blocks |
|---|---|
BlockPublicAcls | Prevents new public ACLs from being applied |
IgnorePublicAcls | Ignores existing public ACLs |
BlockPublicPolicy | Prevents new public bucket policies |
RestrictPublicBuckets | Limits public bucket access to authorized AWS services |
Best practice: Enable all four settings at the account level. Override at the bucket level only for buckets that genuinely need public access (static website hosting).
Bucket Policies​
Bucket policies are resource-based policies that control access to the bucket and its objects. Use them to:
- Enforce encryption in transit (
aws:SecureTransport) - Enforce server-side encryption on uploads (
s3:x-amz-server-side-encryption) - Restrict access to specific VPC endpoints or IP ranges
- Grant cross-account access
Terraform: S3 Bucket with Encryption and Access Controls​
- Terraform
- CDK (TypeScript)
- CDK (Python)
- CloudFormation
# Secure S3 bucket with encryption, versioning,
# block public access, and enforced TLS
resource "aws_s3_bucket" "data" {
bucket = "cloudbuckle-app-data-${data.aws_caller_identity.current.account_id}"
tags = {
Application = "data-platform"
SecurityLayer = "data"
ManagedBy = "terraform"
}
}
# Enable versioning for data recovery and audit trail
resource "aws_s3_bucket_versioning" "data" {
bucket = aws_s3_bucket.data.id
versioning_configuration {
status = "Enabled"
}
}
# Encrypt all objects with a customer managed KMS key
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
bucket = aws_s3_bucket.data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.data_encryption.arn
}
bucket_key_enabled = true # Reduces KMS API calls and cost
}
}
# Block all public access
resource "aws_s3_bucket_public_access_block" "data" {
bucket = aws_s3_bucket.data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Bucket policy: enforce TLS and KMS encryption on uploads
resource "aws_s3_bucket_policy" "data" {
bucket = aws_s3_bucket.data.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "DenyInsecureTransport"
Effect = "Deny"
Principal = "*"
Action = "s3:*"
Resource = [
aws_s3_bucket.data.arn,
"${aws_s3_bucket.data.arn}/*"
]
Condition = {
Bool = {
"aws:SecureTransport" = "false"
}
}
},
{
Sid = "DenyUnencryptedUploads"
Effect = "Deny"
Principal = "*"
Action = "s3:PutObject"
Resource = "${aws_s3_bucket.data.arn}/*"
Condition = {
StringNotEquals = {
"s3:x-amz-server-side-encryption" = "aws:kms"
}
}
}
]
})
depends_on = [aws_s3_bucket_public_access_block.data]
}
data "aws_caller_identity" "current" {}
CDK & CloudFormation Templates
Get this implementation in AWS CDK (TypeScript/Python) and CloudFormation YAML.
CDK & CloudFormation Templates
Get this implementation in AWS CDK (TypeScript/Python) and CloudFormation YAML.
CDK & CloudFormation Templates
Get this implementation in AWS CDK (TypeScript/Python) and CloudFormation YAML.
Terraform: KMS Key with Rotation​
- Terraform
- CDK (TypeScript)
- CDK (Python)
- CloudFormation
# Customer managed KMS key with automatic rotation
# and a key policy that enforces separation of duties
data "aws_caller_identity" "current" {}
resource "aws_kms_key" "data_encryption" {
description = "Encryption key for application data (S3, EBS, RDS)"
deletion_window_in_days = 30
enable_key_rotation = true # Rotates key material annually
rotation_period_in_days = 365
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "EnableRootAccountFullAccess"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
}
Action = "kms:*"
Resource = "*"
},
{
Sid = "AllowKeyAdministration"
Effect = "Allow"
Principal = {
AWS = var.key_admin_role_arn
}
Action = [
"kms:Create*",
"kms:Describe*",
"kms:Enable*",
"kms:List*",
"kms:Put*",
"kms:Update*",
"kms:Revoke*",
"kms:Disable*",
"kms:Get*",
"kms:Delete*",
"kms:TagResource",
"kms:UntagResource",
"kms:ScheduleKeyDeletion",
"kms:CancelKeyDeletion"
]
Resource = "*"
},
{
Sid = "AllowKeyUsage"
Effect = "Allow"
Principal = {
AWS = var.key_user_role_arns
}
Action = [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
]
Resource = "*"
},
{
Sid = "AllowServiceIntegration"
Effect = "Allow"
Principal = {
AWS = var.key_user_role_arns
}
Action = [
"kms:CreateGrant",
"kms:ListGrants",
"kms:RevokeGrant"
]
Resource = "*"
Condition = {
Bool = {
"kms:GrantIsForAWSResource" = "true"
}
}
}
]
})
tags = {
Application = "data-platform"
SecurityLayer = "data"
ManagedBy = "terraform"
}
}
resource "aws_kms_alias" "data_encryption" {
name = "alias/app-data-encryption"
target_key_id = aws_kms_key.data_encryption.key_id
}
variable "key_admin_role_arn" {
type = string
description = "ARN of the IAM role that can administer this key (but not use it for encryption)"
}
variable "key_user_role_arns" {
type = list(string)
description = "ARNs of IAM roles that can use this key for encryption and decryption"
}
CDK & CloudFormation Templates
Get this implementation in AWS CDK (TypeScript/Python) and CloudFormation YAML.
CDK & CloudFormation Templates
Get this implementation in AWS CDK (TypeScript/Python) and CloudFormation YAML.
CDK & CloudFormation Templates
Get this implementation in AWS CDK (TypeScript/Python) and CloudFormation YAML.
This KMS key configuration demonstrates separation of duties: the key administrator can manage the key but cannot use it for encryption, while key users can encrypt and decrypt but cannot change the key policy or delete the key.
Flashcards​
What are the three KMS key types and when should you use each?
Click to revealAWS owned keys (no control, free), AWS managed keys (visible in CloudTrail, free), and Customer managed keys (full key policy control, $1/month). Use CMKs when you need key policies, custom rotation, or compliance requirements.