Skip to main content

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 TypeManaged ByRotationKey Policy ControlCost
AWS owned keysAWSAutomaticNo customer controlFree
AWS managed keysAWS (per service)Automatic (yearly)View only, cannot modifyFree (pay per API call)
Customer managed keys (CMK)YouConfigurable (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:

ServiceEncryption SettingDefault
S3SSE-S3, SSE-KMS, or SSE-CSSE-S3 enabled by default
EBSKMS encryptionMust be enabled (can set account-level default)
RDSKMS encryptionMust be enabled at creation (cannot enable later)
DynamoDBAWS owned key or CMKAWS owned key by default
EFSKMS encryptionMust 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:

  1. Request a certificate in ACM for your domain.
  2. Attach it to an ALB HTTPS listener.
  3. The ALB terminates TLS and forwards traffic to your backend.
  4. 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:

SettingWhat It Blocks
BlockPublicAclsPrevents new public ACLs from being applied
IgnorePublicAclsIgnores existing public ACLs
BlockPublicPolicyPrevents new public bucket policies
RestrictPublicBucketsLimits 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​

# 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" {}

Terraform: KMS Key with Rotation​

# 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"
}

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​

1 / 7
Question

What are the three KMS key types and when should you use each?

Click to reveal
Answer

AWS 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.