AWS KMS & Secrets Manager for System Architects
Table of Contents
- What Problems KMS & Secrets Manager Solve
- AWS KMS Fundamentals
- AWS Secrets Manager Fundamentals
- Secrets Manager vs Parameter Store
- Envelope Encryption
- Key Policies and Grants
- Automatic Secret Rotation
- Cross-Account Access
- Integration with AWS Services
- Cost Optimization Strategies
- Performance and Scalability
- Security Best Practices
- Observability and Monitoring
- Common Pitfalls
- Key Takeaways
What Problems KMS & Secrets Manager Solve
Without Centralized Key and Secret Management
Security Challenges:
- Database passwords hardcoded in application code (committed to Git)
- Encryption keys stored in plaintext configuration files
- No audit trail for who accessed which secrets when
- Manual secret rotation requires deploying updated code
- Secrets shared via email, Slack, sticky notes
- No separation of duties (developers have production secrets)
- Compliance violations (HIPAA, PCI-DSS require encrypted data at rest)
Real-World Impact:
- Hardcoded RDS password in GitHub repo; repo made public; database compromised within 3 hours
- API keys for payment gateway stolen; $100K fraudulent charges before detection
- No secret rotation for 3 years; ex-employee still has access to production database
- Compliance audit failure: Customer data stored unencrypted in S3; $500K fine
- Developer laptop stolen with plaintext AWS access keys; attacker launches EC2 instances for cryptocurrency mining
- Manual secret rotation takes 6 hours; requires coordinated deployment across 50 services; done once per year due to complexity
With KMS & Secrets Manager
Automated Encryption and Secret Management:
AWS KMS (Key Management Service):
- Centralized key management: Create, rotate, disable, audit encryption keys
- Envelope encryption: Encrypt data keys with master keys; never expose master keys
- Integrated with AWS services: S3, EBS, RDS, DynamoDB automatically use KMS
- Hardware security modules (HSMs): FIPS 140-2 Level 2 validated (Level 3 for CloudHSM)
- Audit trail: CloudTrail logs every key usage
AWS Secrets Manager:
- Centralized secret storage: Database passwords, API keys, OAuth tokens
- Automatic rotation: Lambda function rotates secrets on schedule (30/60/90 days)
- Versioning: Track secret changes over time; rollback if needed
- Fine-grained access control: IAM policies control who can retrieve secrets
- Encrypted at rest: All secrets encrypted with KMS keys
Problem-Solution Mapping:
| Problem | KMS Solution | Secrets Manager Solution |
|---|---|---|
| Hardcoded passwords | N/A | Store secrets in Secrets Manager; retrieve at runtime |
| Encryption keys in plaintext | Store keys in KMS; keys never leave HSM | N/A |
| No audit trail for key/secret access | CloudTrail logs all KMS API calls | CloudTrail logs all secret retrievals |
| Manual secret rotation | Automatic key rotation (365 days) | Automatic secret rotation (30-365 days) with Lambda |
| Secrets shared insecurely | N/A | IAM-based access control; secrets never exposed |
| Ex-employee retains access | Disable key; all encrypted data inaccessible | Rotate secret; old credentials invalid |
| Unencrypted data (compliance violation) | Enable encryption with KMS for S3, RDS, EBS | N/A |
AWS KMS Fundamentals
What is AWS KMS?
AWS Key Management Service (KMS) is a managed service for creating and controlling encryption keys.
Core Concept: KMS creates and stores Customer Master Keys (CMKs); you use CMKs to encrypt/decrypt data; CMKs never leave AWS HSMs unencrypted.
Key Types
1. AWS Managed Keys
- Created automatically by AWS services (S3, RDS, EBS)
- Key alias:
aws/s3,aws/rds,aws/ebs - Free (no monthly charge)
- Automatic rotation: Every 3 years (1,095 days)
- Use case: Default encryption for AWS services
2. Customer Managed Keys (CMKs)
- You create and manage
- Full control: Key policies, grants, rotation schedule, enable/disable
- Cost: $1/month per key + $0.03 per 10,000 requests
- Rotation: Optional automatic rotation (365 days) or manual
- Use case: Custom encryption, cross-account access, compliance requirements
3. AWS Owned Keys
- Owned by AWS (not in your account)
- No visibility or control
- Free
- Use case: DynamoDB default encryption, S3 default encryption (SSE-S3)
Key Type Comparison:
| Feature | AWS Managed | Customer Managed | AWS Owned |
|---|---|---|---|
| Who creates | AWS service | You | AWS |
| Visibility | View in KMS console | Full control | No visibility |
| Cost | Free | $1/month + usage | Free |
| Rotation | Automatic (3 years) | Optional (1 year) | N/A |
| Key policy | AWS-managed | You control | N/A |
| Cross-account | No | Yes | No |
CMK Components
Customer Master Key (CMK) contains:
- Key ID: Unique identifier (e.g.,
12345678-1234-1234-1234-123456789012) - Key ARN:
arn:aws:kms:us-east-1:123456789012:key/12345678-... - Alias: Human-friendly name (e.g.,
alias/database-encryption-key) - Key material: Actual encryption key (stored in HSM, never exposed)
- Key policy: JSON document defining who can use the key
- Key state: Enabled, Disabled, PendingDeletion, PendingImport
Create CMK:
aws kms create-key \
--description "Production database encryption key" \
--key-usage ENCRYPT_DECRYPT \
--origin AWS_KMS
Create Alias:
aws kms create-alias \
--alias-name alias/prod-db-key \
--target-key-id 12345678-1234-1234-1234-123456789012
Key Rotation
Automatic Rotation (Customer Managed Keys):
- Enable via console or API
- Rotates key material every 365 days
- Old key material retained for decryption (transparent to applications)
- Cost: No additional charge
Enable Rotation:
aws kms enable-key-rotation \
--key-id 12345678-1234-1234-1234-123456789012
How Rotation Works:
Year 1: CMK uses key material version 1
Year 2: CMK automatically generates key material version 2
- New encryptions use version 2
- Old encrypted data still decryptable with version 1
- Application code unchanged (KMS handles version selection)
Manual Rotation:
- Create new CMK
- Update application to use new key
- Re-encrypt data with new key
- Delete old key after retention period
AWS Secrets Manager Fundamentals
What is Secrets Manager?
AWS Secrets Manager is a managed service for storing, retrieving, and rotating secrets (passwords, API keys, credentials).
Core Concept: Store secrets in Secrets Manager; applications retrieve secrets at runtime via API; secrets automatically rotated on schedule.
Secret Types
1. Database Credentials
- RDS, Aurora, Redshift, DocumentDB
- Automatic rotation with Lambda function
- Secrets Manager updates database password and secret simultaneously
2. API Keys and Tokens
- Third-party API keys (Stripe, Twilio, SendGrid)
- OAuth tokens
- Manual rotation or custom Lambda function
3. SSH Keys
- Private SSH keys for EC2 access
- Manual rotation
4. Custom Secrets
- Any JSON document (up to 65,536 bytes)
- Custom rotation logic via Lambda
Secret Structure
Secret JSON:
{
"username": "admin",
"password": "SuperSecret123!",
"engine": "mysql",
"host": "mydb.us-east-1.rds.amazonaws.com",
"port": 3306,
"dbname": "production"
}
Create Secret:
aws secretsmanager create-secret \
--name production/database/credentials \
--description "Production RDS MySQL credentials" \
--secret-string '{
"username":"admin",
"password":"SuperSecret123!",
"host":"mydb.us-east-1.rds.amazonaws.com"
}'
Retrieving Secrets
Retrieve Secret (Application Code):
import boto3
import json
def get_secret():
client = boto3.client('secretsmanager', region_name='us-east-1')
response = client.get_secret_value(SecretId='production/database/credentials')
secret = json.loads(response['SecretString'])
return {
'username': secret['username'],
'password': secret['password'],
'host': secret['host']
}
# Use in database connection
secret = get_secret()
conn = mysql.connector.connect(
host=secret['host'],
user=secret['username'],
password=secret['password']
)
Best Practice: Retrieve secret once at startup; cache for application lifetime (not per-request).
Secret Versioning
Secrets Manager maintains versions:
AWSCURRENT: Current active secretAWSPENDING: New secret being rotated (not yet active)AWSPREVIOUS: Previous secret (after rotation completes)
Version Stages:
Day 0: Secret created
AWSCURRENT → Version 1
Day 30: Rotation begins
AWSCURRENT → Version 1
AWSPENDING → Version 2 (rotation Lambda testing new password)
Day 30 + 5 minutes: Rotation completes
AWSCURRENT → Version 2
AWSPREVIOUS → Version 1
Retrieve Specific Version:
response = client.get_secret_value(
SecretId='production/database/credentials',
VersionStage='AWSPREVIOUS' # Get previous version
)
Secrets Manager vs Parameter Store
Service Comparison
| Feature | Secrets Manager | Parameter Store (Standard) | Parameter Store (Advanced) |
|---|---|---|---|
| Purpose | Secrets storage + rotation | Configuration management | Configuration + secrets |
| Automatic Rotation | Yes (built-in Lambda for RDS) | No | No |
| Pricing | $0.40 per secret per month + $0.05 per 10K API calls | Free | $0.05 per advanced parameter per month |
| Secret Size | Up to 65,536 bytes | Up to 4 KB | Up to 8 KB |
| Versioning | Yes (AWSCURRENT, AWSPENDING, AWSPREVIOUS) | Yes (up to 100 versions) | Yes (up to 100 versions) |
| Cross-Account | Yes (resource policy) | No (use cross-account IAM roles) | No |
| Encryption | KMS (required) | KMS (optional) | KMS (optional) |
| Parameter Policies | No | No | Yes (expiration, change notification) |
When to Use Secrets Manager
✅ Use Secrets Manager when:
- Need automatic secret rotation (RDS, Aurora, Redshift)
- Storing database credentials
- Require cross-account secret access
- Compliance requires automatic rotation (PCI-DSS, HIPAA)
- Need secret versioning with staging labels
Examples:
- RDS database password (automatic rotation every 30 days)
- OAuth tokens requiring rotation
- Shared secrets across multiple AWS accounts
When to Use Parameter Store
✅ Use Parameter Store when:
- Storing configuration (not secrets requiring rotation)
- Cost-sensitive (free tier available)
- Secrets don’t need automatic rotation
- Application configuration parameters (feature flags, environment variables)
Examples:
- Application configuration (API endpoint URLs, feature flags)
- Static secrets (API keys changed manually)
- Non-sensitive configuration data
Hybrid Approach
Store configuration in Parameter Store; secrets in Secrets Manager:
import boto3
ssm = boto3.client('ssm')
secrets = boto3.client('secretsmanager')
# Configuration from Parameter Store (free)
config = ssm.get_parameter(Name='/app/config/api_endpoint')
api_url = config['Parameter']['Value']
# Secret from Secrets Manager (automatic rotation)
secret = secrets.get_secret_value(SecretId='production/api/key')
api_key = json.loads(secret['SecretString'])['api_key']
# Use both
response = requests.get(api_url, headers={'Authorization': f'Bearer {api_key}'})
Envelope Encryption
What is Envelope Encryption?
Envelope Encryption: Encrypt data with a data key; encrypt the data key with a master key.
Why: Master key never leaves KMS; only encrypted data keys transmitted; improves performance (encrypt large data locally, not via network).
Architecture:
1. Request data key from KMS (GenerateDataKey API)
2. KMS returns:
- Plaintext data key (use to encrypt data)
- Encrypted data key (store with encrypted data)
3. Encrypt data with plaintext data key
4. Discard plaintext data key from memory
5. Store encrypted data + encrypted data key
Decryption:
1. Send encrypted data key to KMS (Decrypt API)
2. KMS returns plaintext data key
3. Decrypt data with plaintext data key
4. Discard plaintext data key from memory
Example: Envelope Encryption
Encrypt File:
import boto3
from cryptography.fernet import Fernet
kms = boto3.client('kms')
# 1. Generate data key from KMS
response = kms.generate_data_key(
KeyId='alias/prod-encryption-key',
KeySpec='AES_256'
)
plaintext_data_key = response['Plaintext']
encrypted_data_key = response['CiphertextBlob']
# 2. Encrypt data with plaintext data key
file_data = open('sensitive_data.txt', 'rb').read()
cipher = Fernet(plaintext_data_key)
encrypted_data = cipher.encrypt(file_data)
# 3. Store encrypted data + encrypted data key
with open('sensitive_data.txt.encrypted', 'wb') as f:
f.write(encrypted_data_key) # First, store encrypted data key
f.write(encrypted_data) # Then, store encrypted data
# 4. Discard plaintext data key
del plaintext_data_key
Decrypt File:
# 1. Read encrypted data key and encrypted data
with open('sensitive_data.txt.encrypted', 'rb') as f:
encrypted_data_key = f.read(512) # Encrypted data key is fixed size
encrypted_data = f.read()
# 2. Decrypt data key with KMS
response = kms.decrypt(CiphertextBlob=encrypted_data_key)
plaintext_data_key = response['Plaintext']
# 3. Decrypt data with plaintext data key
cipher = Fernet(plaintext_data_key)
decrypted_data = cipher.decrypt(encrypted_data)
# 4. Discard plaintext data key
del plaintext_data_key
Benefits:
- Master key never exposed
- Encrypt/decrypt large files without network latency (only data key sent to KMS)
- Performance: Local encryption with data key faster than KMS API calls
Key Policies and Grants
Key Policies
Key Policy: JSON document controlling access to CMK.
Default Key Policy (Created by Root User):
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "Enable IAM policies",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": "kms:*",
"Resource": "*"
}]
}
Custom Key Policy (Principle of Least Privilege):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Allow administrators to manage key",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/KeyAdministrator"
},
"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": "Allow use of key for encryption",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/ApplicationRole"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "*"
}
]
}
Key Policy vs IAM Policy:
- Key Policy: Attached to CMK; controls access to specific key
- IAM Policy: Attached to IAM principal; controls what keys principal can use
Both Required: Key policy must allow principal; IAM policy must allow action.
KMS Grants
Grant: Programmatic, temporary permission to use CMK.
Use Case: Allow AWS service (Lambda, S3) to use your CMK without modifying key policy.
Create Grant:
aws kms create-grant \
--key-id 12345678-1234-1234-1234-123456789012 \
--grantee-principal arn:aws:iam::123456789012:role/LambdaExecutionRole \
--operations Encrypt Decrypt GenerateDataKey
Grant vs Key Policy:
- Grant: Temporary, programmatic, can be revoked
- Key Policy: Permanent (until changed), declarative
Automatic Secret Rotation
Rotation for RDS/Aurora
Secrets Manager Automatic Rotation:
Setup:
aws secretsmanager rotate-secret \
--secret-id production/database/credentials \
--rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:SecretsManagerRDSRotation \
--rotation-rules AutomaticallyAfterDays=30
Rotation Lambda (AWS-Provided):
createSecret: Generate new passwordsetSecret: Update RDS database passwordtestSecret: Test new password worksfinishSecret: Mark secret asAWSCURRENT
Rotation Workflow:
Day 0: Current password = "OldPassword123"
Version 1 (AWSCURRENT)
Day 30: Rotation begins
1. Lambda creates new password: "NewPassword456" (Version 2, AWSPENDING)
2. Lambda updates RDS: ALTER USER admin IDENTIFIED BY 'NewPassword456'
3. Lambda tests connection with new password
4. Lambda marks Version 2 as AWSCURRENT
5. Version 1 becomes AWSPREVIOUS
Application behavior:
- Applications always retrieve AWSCURRENT
- No downtime (new password tested before activation)
- Rollback possible (revert to AWSPREVIOUS if needed)
Custom Rotation (API Keys)
Custom Lambda Function:
import boto3
import requests
def lambda_handler(event, context):
secret_id = event['SecretId']
token = event['ClientRequestToken']
step = event['Step']
secrets = boto3.client('secretsmanager')
if step == 'createSecret':
# Generate new API key from third-party service
response = requests.post('https://api.example.com/keys/rotate')
new_api_key = response.json()['api_key']
# Store new key as AWSPENDING
secrets.put_secret_value(
SecretId=secret_id,
ClientRequestToken=token,
SecretString=new_api_key,
VersionStages=['AWSPENDING']
)
elif step == 'setSecret':
# No action needed (API already updated)
pass
elif step == 'testSecret':
# Test new API key works
secret = secrets.get_secret_value(
SecretId=secret_id,
VersionStage='AWSPENDING'
)
api_key = secret['SecretString']
# Test API call
response = requests.get(
'https://api.example.com/test',
headers={'Authorization': f'Bearer {api_key}'}
)
if response.status_code != 200:
raise Exception('API key test failed')
elif step == 'finishSecret':
# Mark new version as AWSCURRENT
secrets.update_secret_version_stage(
SecretId=secret_id,
VersionStage='AWSCURRENT',
MoveToVersionId=token,
RemoveFromVersionId=<previous_version_id>
)
Configure Rotation:
aws secretsmanager rotate-secret \
--secret-id production/api/key \
--rotation-lambda-arn arn:aws:lambda:...:function:CustomAPIRotation \
--rotation-rules AutomaticallyAfterDays=60
Cross-Account Access
Cross-Account Secret Access
Use Case: Central security account stores secrets; application accounts retrieve them.
Setup:
1. Secret in Account A (111111111111):
aws secretsmanager put-resource-policy \
--secret-id production/database/credentials \
--resource-policy '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222222222:role/ApplicationRole"
},
"Action": "secretsmanager:GetSecretValue",
"Resource": "*"
}]
}'
2. IAM Role in Account B (222222222222):
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": "arn:aws:secretsmanager:us-east-1:111111111111:secret:production/database/credentials-*"
}]
}
3. Retrieve Secret from Account B:
# Application in Account B
secrets = boto3.client('secretsmanager')
response = secrets.get_secret_value(
SecretId='arn:aws:secretsmanager:us-east-1:111111111111:secret:production/database/credentials-abc123'
)
Cross-Account KMS Access
CMK in Account A; used by Account B:
1. Key Policy in Account A:
{
"Sid": "Allow Account B to use key",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222222222:root"
},
"Action": [
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": "*"
}
2. IAM Policy in Account B:
{
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": "arn:aws:kms:us-east-1:111111111111:key/12345678-1234-1234-1234-123456789012"
}
Integration with AWS Services
S3 + KMS
Server-Side Encryption with KMS (SSE-KMS):
# Enable default encryption with CMK
aws s3api put-bucket-encryption \
--bucket my-bucket \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:us-east-1:123456789012:key/12345678-..."
}
}]
}'
# Upload object (automatically encrypted)
aws s3 cp sensitive_data.txt s3://my-bucket/
Benefit: All objects encrypted at rest; key managed by KMS; audit trail in CloudTrail.
RDS + KMS
Enable Encryption at Rest:
aws rds create-db-instance \
--db-instance-identifier production-db \
--storage-encrypted \
--kms-key-id arn:aws:kms:us-east-1:123456789012:key/12345678-...
Encrypted Snapshots:
- Manual snapshots of encrypted DB encrypted with same key
- Automated backups encrypted with same key
Lambda + Secrets Manager
Retrieve Secret in Lambda:
import boto3
import json
import os
secrets_client = boto3.client('secretsmanager')
def lambda_handler(event, context):
# Retrieve secret
secret_arn = os.environ['DB_SECRET_ARN']
secret = secrets_client.get_secret_value(SecretId=secret_arn)
credentials = json.loads(secret['SecretString'])
# Use credentials
db_password = credentials['password']
Best Practice: Cache secret for Lambda container lifetime (not per-invocation):
# Cache secret outside handler
secret = None
def get_secret():
global secret
if secret is None:
response = secrets_client.get_secret_value(SecretId=os.environ['DB_SECRET_ARN'])
secret = json.loads(response['SecretString'])
return secret
def lambda_handler(event, context):
credentials = get_secret() # Cached after first invocation
Cost Optimization Strategies
KMS Pricing (us-east-1, 2025)
Customer Managed Keys:
- $1.00 per CMK per month
- $0.03 per 10,000 requests (Encrypt, Decrypt, GenerateDataKey)
AWS Managed Keys:
- Free (no monthly charge)
- $0.03 per 10,000 requests
Free Tier:
- 20,000 requests per month free (across all keys)
Secrets Manager Pricing
- $0.40 per secret per month
- $0.05 per 10,000 API calls
Parameter Store Pricing
Standard Parameters:
- Free (up to 10,000 parameters)
- Free API calls
Advanced Parameters:
- $0.05 per parameter per month
- Free API calls
1. Use Parameter Store for Non-Rotated Secrets
Problem: Storing 100 static API keys in Secrets Manager.
Cost (Secrets Manager):
100 secrets × $0.40 = $40/month
Cost (Parameter Store):
100 standard parameters = $0/month
Savings: $40/month (100%)
When to Use Secrets Manager: Only for secrets requiring automatic rotation.
2. Reuse CMKs Across Services
Problem: Creating separate CMK for each service.
Without Reuse:
CMK for S3: $1/month
CMK for RDS: $1/month
CMK for EBS: $1/month
Total: $3/month
With Reuse (Single CMK):
CMK for all services: $1/month
Savings: $2/month (67%)
Best Practice: One CMK per environment (e.g., prod-encryption-key) used across all services.
3. Cache Secrets in Application
Problem: Application retrieves secret on every request.
Without Caching:
1M requests/day
1M Secrets Manager API calls/day = 30M/month
Cost: 30M × $0.05/10K = $150/month
With Caching (Retrieve Once per Hour):
24 retrievals/day = 720/month
Cost: 720 × $0.05/10K = $0.004/month
Savings: $150/month (99.9%)
Best Practice: Cache secret for container/process lifetime; refresh every 1-24 hours.
4. Use AWS Managed Keys for Default Encryption
Problem: Creating CMK for S3 default encryption when AWS managed key sufficient.
Customer Managed Key:
CMK cost: $1/month
Use case: No cross-account access, no custom key policy needed
AWS Managed Key (aws/s3):
Cost: $0/month (free)
Same functionality for single-account S3 encryption
Savings: $1/month
Use CMK Only When: Need cross-account access, custom key policy, or compliance requires customer-managed keys.
Cost Example: Production Application
Scenario:
- 10 secrets in Secrets Manager (RDS passwords, API keys)
- 1 CMK for encryption
- 1M secret retrievals/month (cached)
- 100K KMS requests/month
Secrets Manager:
Secrets: 10 × $0.40 = $4.00/month
API calls: (1M / 10K) × $0.05 = $5.00/month
Total: $9.00/month
KMS:
CMK: $1.00/month
Requests: (100K - 20K free) / 10K × $0.03 = $0.24/month
Total: $1.24/month
Total Cost: $10.24/month for secure secret management
ROI: Prevents hardcoded secrets; automatic rotation; compliance; far lower cost than security breach.
Performance and Scalability
KMS Throughput
Request Limits:
- Shared quota: 5,500 requests/sec (us-east-1, us-west-2)
- Other regions: 1,200 requests/sec
- Request limit increase: Available via AWS Support
Latency:
- GenerateDataKey: 10-50 ms
- Decrypt: 10-50 ms
- Encrypt: 10-50 ms
Best Practice: Use envelope encryption (encrypt locally with data key, not KMS API per-record).
Secrets Manager Throughput
No Hard Limits:
- Scales automatically
- Typical latency: 50-200 ms
Best Practice: Cache secrets; don’t retrieve on every request.
Security Best Practices
1. Enable Key Rotation
aws kms enable-key-rotation --key-id <key-id>
Benefit: Automatic annual rotation; reduces risk of key compromise.
2. Use Separate Keys per Environment
Development: dev-encryption-key
Staging: staging-encryption-key
Production: prod-encryption-key
Benefit: Compromise of dev key doesn’t affect production.
3. Principle of Least Privilege
Key Policy:
{
"Sid": "Allow decrypt only",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/ReadOnlyRole"
},
"Action": "kms:Decrypt",
"Resource": "*"
}
Deny Encrypt: Read-only applications should only decrypt, not encrypt.
4. Monitor Key Usage
CloudWatch Alarm (Unusual Key Usage):
Metric: KMS API Calls (custom metric from CloudTrail)
Threshold: >10,000 requests in 5 minutes
Action: Alert security team
5. Use VPC Endpoints for KMS
Keep traffic private:
Application in VPC → VPC Endpoint → KMS (private connection)
Benefit: No internet gateway; reduced attack surface.
Observability and Monitoring
CloudTrail Logging
All KMS API calls logged:
{
"eventName": "Decrypt",
"userIdentity": {
"arn": "arn:aws:iam::123456789012:role/ApplicationRole"
},
"requestParameters": {
"encryptionContext": {
"application": "payment-processor",
"environment": "production"
}
},
"responseElements": null,
"resources": [{
"ARN": "arn:aws:kms:us-east-1:123456789012:key/12345678-..."
}]
}
Audit Questions:
- Who decrypted production database encryption key?
- How many times was API key retrieved today?
- Which keys were deleted?
CloudWatch Metrics
Custom Metrics (from CloudTrail):
| Metric | Description | Alert Threshold |
|---|---|---|
| KMS Decrypt Calls | Decrypt operations | Spike indicates unusual access |
| Secrets Retrieved | GetSecretValue calls | Compare to baseline |
| Key Disabled | DisableKey API calls | >0 (unauthorized key disable) |
Common Pitfalls
Pitfall 1: Not Caching Secrets
Problem: Retrieving secret on every request; 1M requests = $150/month API costs.
Solution: Cache secret for application lifetime; refresh hourly.
Cost Impact: 99.9% savings with caching.
Pitfall 2: Hardcoding Key IDs
Problem: Application code contains key-id=12345678-...; key rotation breaks application.
Solution: Use key alias (alias/prod-key); update alias to point to new key during rotation.
Cost Impact: Application downtime during key rotation.
Pitfall 3: Not Enabling Key Rotation
Problem: CMK used for 5 years without rotation; compliance violation.
Solution: Enable automatic rotation for all customer managed keys.
Cost Impact: Compliance audit failure; regulatory fines.
Pitfall 4: Using Secrets Manager for Static Configuration
Problem: 100 feature flags stored in Secrets Manager; $40/month cost.
Solution: Use Parameter Store (free) for non-secret configuration.
Cost Impact: $40/month wasted on non-secret storage.
Pitfall 5: Over-Permissive Key Policy
Problem: Key policy allows kms:* for all principals.
Solution: Principle of least privilege; grant only required actions.
Cost Impact: Unauthorized key usage; potential data breach.
Pitfall 6: No Cross-Account Key Policy for Shared CMK
Problem: Application in Account B can’t decrypt data encrypted in Account A.
Solution: Update key policy to allow Account B principals.
Cost Impact: Application failures; deployment delays.
Key Takeaways
-
KMS manages encryption keys; Secrets Manager manages secrets. KMS creates/rotates keys for encrypting data. Secrets Manager stores/rotates passwords and API keys.
-
Use Customer Managed Keys for cross-account access and custom policies. AWS managed keys are free but limited. CMKs cost $1/month but provide full control.
-
Envelope encryption improves performance and security. Encrypt data locally with data key; encrypt data key with master key. Master key never leaves HSM.
-
Secrets Manager provides automatic rotation for RDS/Aurora. Built-in Lambda rotates database passwords every 30-365 days without downtime.
-
Parameter Store is free for standard parameters. Use for configuration and static secrets. Use Secrets Manager only for secrets requiring rotation.
-
Cache secrets to reduce API costs 99%. Retrieve secret once per hour instead of per-request. Reduces Secrets Manager costs from $150/month to $0.15/month.
-
Enable automatic key rotation for compliance. CMKs rotate annually; old key material retained for decryption. Application code unchanged.
-
Key policies and IAM policies both required for access. Key policy must allow principal; IAM policy must allow action. Both evaluated together.
-
All KMS and Secrets Manager API calls logged to CloudTrail. Provides audit trail for compliance (who accessed which key/secret when).
-
Use separate keys per environment (dev, staging, prod). Prevents compromise of dev key from affecting production data.
-
Secret versioning enables zero-downtime rotation. AWSCURRENT, AWSPENDING, AWSPREVIOUS stages; applications always retrieve AWSCURRENT.
-
Cross-account access requires resource policy + IAM policy. Secret/key policy allows Account B; IAM policy in Account B allows GetSecretValue/Decrypt.
-
KMS request limits: 5,500 req/sec (us-east-1). Use envelope encryption to reduce KMS API calls. Encrypt locally with data key.
-
Reuse CMKs across services to reduce costs. One CMK for S3, RDS, EBS saves $2/month per eliminated key.
-
Use VPC endpoints for KMS/Secrets Manager to keep traffic private. Prevents secrets from traversing internet; reduces attack surface.
AWS KMS and Secrets Manager provide enterprise-grade encryption and secret management, enabling automatic rotation, fine-grained access control, and complete audit trails for compliance and security. KMS handles encryption keys while Secrets Manager handles passwords, API keys, and credentials—both essential for protecting sensitive data in production.
Found this guide helpful? Share it with your team:
Share on LinkedIn