Your SaaS stores customer files in S3. One AWS Key Management Service (KMS) key encrypts every bucket because "encryption is on." An enterprise customer asks for customer-managed keys (CMKs) and key rotation evidence. You discover twelve services share one key policy with Principal: "*".
KMS is not checkbox encryption. It is access control for ciphertext. SaaS teams must decide what to centralize, what to isolate per tenant, and how to keep key policies readable as the product grows.
In this tutorial, you will learn how to choose a KMS key strategy for SaaS teams: key types, tenancy models, rotation, cross-account patterns, and verification.
Who this is for: SaaS platform engineers, security architects, and DevSecOps engineers on AWS.
Prerequisites:
- AWS account with S3, RDS, or similar encrypted services
- Basic understanding of IAM and resource policies
TL;DR
- Use AWS managed keys for low-friction defaults; customer managed keys (CMKs) when you need policy control, rotation evidence, or customer bring-your-own-key (BYOK).
- Prefer one CMK per data class (app secrets, customer blobs, backups), not one key for everything, not one key per customer unless contracts require it.
- Multi-tenant isolation is usually logical (tenant ID in app layer) plus per-environment keys, dedicated keys per tenant only for regulated tiers.
- Key policies must name specific roles, avoid account-root wildcards.
- Verify: only intended roles can
kms:Decrypt; CloudTrail showsDecryptattribution.
Why One Default Key Fails for SaaS
| Approach | Works when | Breaks when |
|---|---|---|
| Single CMK for all data | Early MVP | Customer demands key isolation or audit separation |
| AWS managed keys only | Standard tier, no BYOK | You cannot customize key policy or cross-account grant |
| Per-tenant CMK from day one | Regulated enterprise SKU | Key policy sprawl, cost, and ops load at scale |
Key idea: Match key granularity to contract and blast radius, not to idealized zero-trust diagrams on day one.
Step 1: Pick Key Types
| Key type | Control | Typical SaaS use |
|---|---|---|
AWS managed (aws/s3, aws/rds) |
AWS owns rotation and policy | Non-sensitive dev sandboxes |
| Customer managed CMK | You define key policy, rotation, aliases | Production data, customer-facing storage |
| AWS CloudHSM / External key store | Hardware or external HSM | Strict compliance, BYOK contracts |
Start production on CMKs. Use AWS managed keys only where you accept the AWS-default policy.
Step 2: Choose a Tenancy Model
Model A, Shared platform key (default tier)
One CMK per environment encrypts all tenant data in S3/RDS. Tenant isolation is application-enforced (tenant ID column, prefix, or row-level security).
s3://app-data-prod/tenant-a/*
s3://app-data-prod/tenant-b/*
↓
CMK: alias/app-data-prod
Model B, Key per data class
Separate CMK for:
alias/app-customer-contentalias/app-internal-secretsalias/app-backups
Blast radius shrinks: backup operator cannot decrypt live customer content if IAM is scoped.
Model C, Dedicated key per enterprise tenant
For customers who contract for CMK isolation or BYOK:
Tenant Acme → CMK alias/tenant-acme-prod + bucket policy binding
Automate provisioning with Terraform; do not hand-craft key policies per customer without a pipeline.
Step 3: Write Minimal Key Policies
CMK policy allowing app role and deny everyone else by default:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowRootAccountAdmin",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::ACCOUNT_ID:root"
},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "AllowAppRoleUseKey",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::ACCOUNT_ID:role/app-api-prod"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey",
"kms:DescribeKey"
],
"Resource": "*"
}
]
}
Pair with an S3 bucket policy that requires encryption with this CMK:
{
"Sid": "DenyIncorrectEncryptionHeader",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::app-data-prod/*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption-aws-kms-key-id": "arn:aws:kms:REGION:ACCOUNT_ID:key/KEY_ID"
}
}
}
Step 4, Plan Rotation and Grants
- Enable automatic annual rotation on CMKs where supported.
- Document manual rotation for keys that cannot auto-rotate (asymmetric use cases).
- Prefer IAM role access over long-lived KMS grants; use grants only for cross-account microservice patterns with expiry.
aws kms enable-key-rotation --key-id alias/app-customer-content
Step 5, Cross-Account and BYOK Patterns
| Scenario | Pattern |
|---|---|
| Central logging account | CMK in log account; S3 bucket policy + key policy trust logging role |
| Customer owns key in their account | Cross-account key policy + IAM role in your account with kms:Decrypt on their CMK |
| Disaster recovery copy | Re-encrypt to DR region CMK during replication; do not copy ciphertext without a key strategy |
How to Verify This Works
- Positive: app role reads encrypted S3 object successfully.
-
Negative: developer role without key policy gets
AccessDeniedonkms:Decrypt. - Wrong key test: upload object with a different CMK; bucket policy denies.
-
CloudTrail:
Decryptevents show role ARN, not scattered IAM users.
aws kms describe-key --key-id alias/app-customer-content \
--query 'KeyMetadata.{Enabled:Enabled,Rotation:KeyRotationEnabled}'
When This Breaks Down
- Key policy size limits: too many tenant principals on one policy, move to Model C automation or ABAC patterns.
- Lambda cold start + KMS latency: excessive decrypt calls; use data key caching carefully within security review.
- Terraform state contains key ARNs only: good; never plaintext data keys in state.
- Regulatory checkbox without tenant isolation: shared key with "encrypted" label fails customer security review.
Conclusion
In this tutorial, you learned how to choose a KMS strategy for SaaS: key types, shared vs per-class vs per-tenant CMKs, minimal policies, rotation, and verification.
Pick Model B for your next production environment, enable rotation, and run one negative decrypt test this sprint.

Top comments (0)