Security Services
IAM decides who can call AWS APIs. This chapter covers how data is protected, how threats are detected, and how compliance is measured. KMS encrypts secrets and storage; Secrets Manager rotates credentials without redeploys; WAF and Shield defend the edge; GuardDuty and Inspector find what humans miss; Security Hub and Config turn scattered findings into a single compliance picture. Production security is encryption everywhere, least privilege, and automated response — not a quarterly audit panic.
KMS & envelope encryption
AWS Key Management Service (KMS) is the cryptographic control plane for AWS. You never send plaintext secrets to KMS for bulk storage — you use envelope encryption: KMS protects a data key; the data key encrypts your payload locally. Master keys stay in KMS hardware security modules (HSMs); your app only handles encrypted data keys and ciphertext.
CMK types
| Key type | Who manages | Use case | Rotation |
|---|---|---|---|
| AWS managed CMK | AWS (per service) | Default encryption for S3, EBS, RDS, Secrets Manager — zero setup | Automatic every ~3 years; you cannot disable |
| AWS owned CMK | AWS (shared across accounts) | Some multi-tenant services (e.g. S3 default SSE-S3 uses AWS-owned keys) | Not visible or manageable in your account |
| Customer managed CMK | You | Cross-service encryption, audit trails, key policies, grants, multi-region keys | Optional annual automatic rotation (symmetric keys only) |
| Imported key material | You bring key; AWS hosts HSM | Regulatory requirements for on-prem key generation | Manual — you re-import; no AWS auto-rotation |
Envelope encryption flow
When your Spring service writes a PII record to DynamoDB with a customer-managed CMK:
- App calls kms:GenerateDataKey — KMS returns a plaintext data key + encrypted copy
- App encrypts the record locally with AES-256 using the plaintext data key
- App stores ciphertext + encrypted data key in DynamoDB; wipes plaintext key from memory
- On read: app calls kms:Decrypt on the encrypted data key, decrypts payload locally
sequenceDiagram participant App as Application participant KMS as AWS KMS participant Store as S3 / RDS / DynamoDB App->>KMS: GenerateDataKey(KeyId=CMK) KMS-->>App: Plaintext DEK + Encrypted DEK App->>App: Encrypt payload with Plaintext DEK App->>Store: Store ciphertext + Encrypted DEK Note over App: Zero plaintext DEK in memory App->>Store: Read ciphertext + Encrypted DEK App->>KMS: Decrypt(Encrypted DEK) KMS-->>App: Plaintext DEK App->>App: Decrypt payload locally
Key rotation
Automatic rotation (customer-managed symmetric CMKs): KMS generates new backing material annually; old versions decrypt existing ciphertext transparently. Rotation does not re-encrypt data — it rotates the CMK material used for new GenerateDataKey calls. Enable with one checkbox or EnableKeyRotation API.
Manual rotation: create a new CMK, re-encrypt data, update aliases and key policies, deprecate old key. Required for asymmetric keys and imported key material.
Grants vs key policies
| Mechanism | Scope | When to use |
|---|---|---|
| Key policy | Resource-based policy on the CMK — required for every CMK | Account admins, cross-account access, service principals (e.g. s3.amazonaws.com) |
| IAM policy | Identity-based on user/role | Grant kms:Decrypt to an ECS task role — key policy must also allow the principal |
| Grant | Temporary, fine-grained delegation on a CMK | Cross-account without editing key policy repeatedly; Lambda concurrent encrypt with constraints |
Multi-Region Keys (MRKs)
A multi-region primary key in eu-west-1 has replica keys in us-east-1 with the same key ID and key material. Encrypt in one region, decrypt in another without cross-region KMS API calls. Use for global apps, DR failover, and DynamoDB global tables with client-side encryption. MRKs are not the same as replicating encrypted snapshots — the key ID travels with the data.
Create a customer-managed CMK with alias
# Create symmetric CMK with rotation
KEY_ID=$(aws kms create-key \
--description "App data encryption key" \
--key-usage ENCRYPT_DECRYPT \
--query 'KeyMetadata.KeyId' --output text)
aws kms enable-key-rotation --key-id "$KEY_ID"
aws kms create-alias \
--alias-name alias/app-data-prod \
--target-key-id "$KEY_ID"
# Grant ECS task role decrypt (key policy + IAM policy both required)
aws kms create-grant \
--key-id "$KEY_ID" \
--grantee-principal arn:aws:iam::123456789012:role/order-service-task \
--operations Decrypt GenerateDataKey
resource "aws_kms_key" "app_data" {
description = "App data encryption key"
enable_key_rotation = true
deletion_window_in_days = 30
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "EnableRootAdmin"
Effect = "Allow"
Principal = { AWS = "arn:aws:iam::123456789012:root" }
Action = "kms:*"
Resource = "*"
},
{
Sid = "AllowECSRoleUse"
Effect = "Allow"
Principal = { AWS = aws_iam_role.ecs_task.arn }
Action = ["kms:Decrypt", "kms:GenerateDataKey"]
Resource = "*"
}
]
})
}
resource "aws_kms_alias" "app_data" {
name = "alias/app-data-prod"
target_key_id = aws_kms_key.app_data.key_id
}
import * as kms from 'aws-cdk-lib/aws-kms';
const appKey = new kms.Key(this, 'AppDataKey', {
alias: 'app-data-prod',
enableKeyRotation: true,
description: 'Customer-managed CMK for application data',
});
appKey.grantDecrypt(taskRole);
appKey.grant(taskRole, 'kms:GenerateDataKey');
KMS never exports raw CMK material. GenerateDataKey happens inside the HSM; only the encrypted data key leaves KMS. KMS API calls are logged to CloudTrail — every decrypt is auditable. Latency is ~5–15 ms per call; batch envelope encryption at the app layer to avoid per-row KMS calls in hot paths.
Key policy + IAM policy = AND for customer-managed CMKs — both must allow the action. AWS managed CMKs: IAM alone is enough. Cross-account KMS: update key policy on the CMK account; identity policy on the caller. MRKs share key ID — exam loves "encrypt in Region A, decrypt in Region B without re-encrypting."
Schedule CMK deletion with a 7–30 day waiting period — accidental deletes are recoverable during the window. Use kms:ViaService condition keys to restrict decrypt to specific services. Deny kms:DisableKey and kms:ScheduleKeyDeletion via SCP for production keys.
Secrets Manager
Secrets Manager stores credentials, API keys, and connection strings with encryption at rest (KMS), fine-grained IAM access, and optional automatic rotation without redeploying your ECS tasks or Lambda functions. Your app fetches the current version at runtime — no secrets in environment variables committed to Git.
How secrets are versioned
Each secret has immutable versions identified by UUIDs. Two staging labels point to versions:
- AWSCURRENT — what apps should read in production
- AWSPENDING — used during rotation; rotation Lambda writes here first, then swaps labels
Rotation is atomic at the label swap: apps reading AWSCURRENT get the new password on the next GetSecretValue call. Cache secrets in memory with TTL (5–15 min), not forever.
Automatic rotation
Secrets Manager invokes a rotation Lambda on a schedule (default 30 days, configurable). Built-in rotation templates exist for:
- RDS (single-user and multi-user)
- Redshift
- DocumentDB
- Amazon MQ
- Custom rotation via your own Lambda (any database or API key)
Secrets Manager vs SSM Parameter Store
| Feature | Secrets Manager | SSM Parameter Store |
|---|---|---|
| Automatic rotation | Built-in with Lambda templates | Manual — you build rotation yourself |
| Cross-region replication | Native replicate to DR region | No native replication |
| Version staging labels | AWSCURRENT / AWSPENDING | Version numbers only (1, 2, 3…) |
| Encryption | KMS CMK (required) | SecureString with KMS, or plain String |
| Cost | ~$0.40/secret/month + API calls | Standard parameters free; Advanced $0.05/parameter/month |
| Best for | DB passwords, API keys needing rotation | Config flags, AMI IDs, non-rotating app settings |
| ECS/EKS integration | Native secrets injection in task definitions | Parameter Store references in task definitions |
| Audit | CloudTrail per GetSecretValue | CloudTrail per GetParameter |
Create a secret with RDS rotation
aws secretsmanager create-secret \
--name prod/order-service/rds \
--description "Order service PostgreSQL credentials" \
--kms-key-id alias/app-data-prod \
--secret-string '{"username":"orders_app","password":"GENERATE_STRONG","host":"orders.xxxx.eu-west-1.rds.amazonaws.com","port":5432,"dbname":"orders"}'
# Attach managed rotation (single-user RDS PostgreSQL)
aws secretsmanager rotate-secret \
--secret-id prod/order-service/rds \
--rotation-lambda-arn arn:aws:lambda:eu-west-1:123456789012:function:SecretsManagerRDSRotation \
--rotation-rules AutomaticallyAfterDays=30
resource "aws_secretsmanager_secret" "rds" {
name = "prod/order-service/rds"
kms_key_id = aws_kms_key.app_data.arn
}
resource "aws_secretsmanager_secret_version" "rds" {
secret_id = aws_secretsmanager_secret.rds.id
secret_string = jsonencode({
username = "orders_app"
password = random_password.db.result
host = aws_db_instance.orders.address
port = 5432
dbname = "orders"
})
}
resource "aws_secretsmanager_secret_rotation" "rds" {
secret_id = aws_secretsmanager_secret.rds.id
rotation_lambda_arn = aws_serverlessapplicationrepository_cloudformation_stack.rds_rotation.outputs["RotationLambdaARN"]
rotation_rules {
automatically_after_days = 30
}
}
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
import * as rds from 'aws-cdk-lib/aws-rds';
const dbSecret = new rds.DatabaseSecret(this, 'OrderDbSecret', {
username: 'orders_app',
secretName: 'prod/order-service/rds',
encryptionKey: appKey,
});
const instance = new rds.DatabaseInstance(this, 'OrdersDb', {
engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_16 }),
credentials: rds.Credentials.fromSecret(dbSecret),
// ...
});
dbSecret.addRotationSchedule('Rotation', {
automaticallyAfter: cdk.Duration.days(30),
});
Storing the secret value in ECS task definition environment variables — visible in the console and CloudFormation templates. Pass the ARN and fetch at runtime, or use ECS native secrets block which injects without exposing in plain task JSON to operators with limited IAM.
Structure secrets as JSON with consistent keys (username, password, host) so one rotation Lambda template works across services. Use hierarchical names: prod/order-service/rds — IAM resource policies can scope by path prefix.
Secrets Manager vs Parameter Store: 50 non-rotating config params in Parameter Store costs $0; 50 secrets in Secrets Manager costs ~$20/month. Use Parameter Store for feature flags and AMI IDs; Secrets Manager only where rotation or cross-region replication justifies the premium.
WAF & Shield
AWS WAF inspects HTTP/S requests at the edge — CloudFront, ALB, API Gateway, AppSync — and blocks malicious traffic before it reaches your containers. Shield protects against DDoS at layers 3, 4, and (with Advanced) layer 7. WAF is your application firewall; Shield is your volumetric attack absorber.
WAF rule types
| Rule type | What it matches | Example |
|---|---|---|
| Regular rule | Statements on URI, headers, body, method, IP, geo | Block requests where Authorization header is missing on /api/* |
| Rate-based rule | Request count per IP (or forwarded IP) in a sliding window | Block IP exceeding 2000 requests / 5 min — stops brute force and scraping |
| Managed rule group | AWS or marketplace-maintained rule sets | AWSManagedRulesCommonRuleSet — SQLi, XSS, size restrictions |
| Rule group (custom) | Reusable collection of your own rules | Shared bot-detection rules across 10 CloudFront distributions |
Managed rule groups (production starters)
Start every public ALB with AWSManagedRulesCommonRuleSet (OWASP baseline), add KnownBadInputs (Log4j/Spring4Shell), SQLi for data-heavy APIs, and IpReputationList for known-bad IPs. Bot Control adds per-request cost — enable when scraper abuse is real.
Shield Standard vs Advanced
| Capability | Shield Standard | Shield Advanced |
|---|---|---|
| Cost | Free — automatic on all AWS customers | ~$3,000/month + data transfer fees (enterprise contracts vary) |
| Layer 3/4 DDoS | Always-on detection and mitigation | Enhanced detection, proactive engagement |
| Layer 7 DDoS | Not included | Specialized support + WAF rule tuning during attacks |
| Cost protection | None | Credits for scaling costs during documented DDoS events |
| DRT access | None | 24×7 AWS DDoS Response Team |
| Protected resources | CloudFront, Route 53 (implicit) | Must register ELB, CloudFront, Route 53, Global Accelerator |
Web ACL on an ALB
# Create Web ACL (REGIONAL scope for ALB)
WEBACL_ARN=$(aws wafv2 create-web-acl \
--name order-api-waf \
--scope REGIONAL \
--default-action Allow={} \
--rules '[
{
"Name": "RateLimit",
"Priority": 1,
"Statement": {
"RateBasedStatement": {
"Limit": 2000,
"AggregateKeyType": "IP"
}
},
"Action": { "Block": {} },
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "RateLimit"
}
},
{
"Name": "AWS-AWSManagedRulesCommonRuleSet",
"Priority": 2,
"Statement": {
"ManagedRuleGroupStatement": {
"VendorName": "AWS",
"Name": "AWSManagedRulesCommonRuleSet"
}
},
"OverrideAction": { "None": {} },
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "CommonRuleSet"
}
}
]' \
--visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=order-api-waf \
--query 'Summary.ARN' --output text)
# Associate with ALB
aws wafv2 associate-web-acl \
--web-acl-arn "$WEBACL_ARN" \
--resource-arn arn:aws:elasticloadbalancing:eu-west-1:123456789012:loadbalancer/app/order-api/abcdef1234567890
resource "aws_wafv2_web_acl" "order_api" {
name = "order-api-waf"
scope = "REGIONAL"
default_action { allow {} }
rule {
name = "RateLimit"
priority = 1
action { block {} }
statement {
rate_based_statement {
limit = 2000
aggregate_key_type = "IP"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "RateLimit"
sampled_requests_enabled = true
}
}
rule {
name = "AWSManagedRulesCommonRuleSet"
priority = 2
override_action { none {} }
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "CommonRuleSet"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "order-api-waf"
sampled_requests_enabled = true
}
}
resource "aws_wafv2_web_acl_association" "alb" {
resource_arn = aws_lb.order_api.arn
web_acl_arn = aws_wafv2_web_acl.order_api.arn
}
import * as wafv2 from 'aws-cdk-lib/aws-wafv2';
const vis = { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true };
const webAcl = new wafv2.CfnWebACL(this, 'OrderApiWaf', {
name: 'order-api-waf', scope: 'REGIONAL', defaultAction: { allow: {} },
visibilityConfig: { ...vis, metricName: 'order-api-waf' },
rules: [
{ name: 'RateLimit', priority: 1, action: { block: {} },
statement: { rateBasedStatement: { limit: 2000, aggregateKeyType: 'IP' } },
visibilityConfig: { ...vis, metricName: 'RateLimit' } },
{ name: 'CommonRuleSet', priority: 2, overrideAction: { none: {} },
statement: { managedRuleGroupStatement: { vendorName: 'AWS', name: 'AWSManagedRulesCommonRuleSet' } },
visibilityConfig: { ...vis, metricName: 'CommonRuleSet' } },
],
});
new wafv2.CfnWebACLAssociation(this, 'AlbWafAssoc', {
resourceArn: alb.loadBalancerArn, webAclArn: webAcl.attrArn,
});
WAF charges per Web ACL, per rule, and per million requests inspected. Bot Control adds per-request fees. Start with one Web ACL per ALB, two rules (rate limit + CommonRuleSet), and enable WAF logging to S3 only when debugging — log storage adds up. Shield Advanced is for revenue-critical properties with DDoS history.
Scope matters: ALB and API Gateway use REGIONAL Web ACLs; CloudFront uses CLOUDFRONT scope (must create in us-east-1). Shield Standard is automatic and free. For "block SQL injection at the edge," answer WAF managed rule group, not Security Groups.
GuardDuty & Inspector
GuardDuty is continuous threat detection — it watches CloudTrail, VPC Flow Logs, DNS logs, and EKS audit logs for malicious patterns without you deploying agents. Inspector is vulnerability management — it scans EC2 instances, container images in ECR, and Lambda functions for CVEs. Detection plus scanning closes the loop from "something looks wrong" to "this image has a known exploit."
What GuardDuty detects
Credential exfiltration (unusual geographies, Tor), crypto mining, IAM reconnaissance at volume, malware on EBS/EKS, anomalous S3 data events, and privileged EKS container launches — all without deploying agents on EC2.
Findings workflow
- Enable GuardDuty in the security/admin account; delegate admin for org-wide enrollment
- Severity — Low (4.0–4.9), Medium (5.0–6.9), High (7.0–8.9), Critical (9.0+) on a 0–10 scale
- Aggregate — findings flow to Security Hub as ASFF (AWS Security Finding Format)
- Triage — EventBridge rule on severity ≥ 7 → SNS/PagerDuty/Slack
- Investigate — pivot on resource ARN, principal, and affected region in CloudTrail
- Remediate — isolate SG, rotate credentials, quarantine EC2 snapshot; mark finding resolved in GuardDuty
- Suppress — false positives get suppression rules (by finding type + resource tag) — document why
Inspector vulnerability scanning
| Scan target | What it checks | Trigger |
|---|---|---|
| EC2 instances | OS packages on running instances with SSM agent | Continuous — daily rescan |
| ECR images | CVEs in container layers on push and periodic rescan | CI gate: block deploy if Critical CVEs |
| Lambda | Known vulnerabilities in function dependencies | On deploy and weekly |
| Lambda code scanning | Code patterns (optional Code Security feature) | Integrated with Inspector v2 |
Inspector scores findings with CVSS and EPSS. Gate CI on Critical CVEs via ECR enhanced scanning before promoting image tags. Inspector finds the CVE; GuardDuty tells you if exploitation is happening in your VPC.
Coinbase and other fintechs run GuardDuty org-wide with automated isolation — high-severity EC2 findings trigger Lambda that attaches a quarantine security group and opens a PagerDuty incident. Inspector ECR gates block deploy pipelines when Critical CVEs appear in base images — shift-left on supply chain.
GuardDuty uses AWS-curated threat intel feeds plus ML on your own telemetry — it does not require agents on EC2. VPC Flow Logs must reach GuardDuty (enabled by default when you turn on the service). Malware Protection for EBS snapshots instances on demand — adds scan cost per GB. Inspector v2 replaced the old Inspector Classic agent model entirely.
Security Hub & AWS Config
Individual services generate findings in different formats. Security Hub normalizes them into one scorecard. AWS Config records configuration changes over time and evaluates rules — "is this S3 bucket public?", "does this SG allow 0.0.0.0/0 on port 22?" — continuously, not just at deploy time.
Security Hub
Enable Security Hub in the security account; delegate administrator for the org. It ingests findings from:
- GuardDuty, Inspector, IAM Access Analyzer, Macie, Firewall Manager
- Third-party integrations (Prowler, Checkov, Prisma) via ASFF import
- Config rules mapped to Security Hub controls
Security standards — enable AWS Foundational Security Best Practices (FSBP) and CIS AWS Foundations Benchmark v1.4.0. Each control maps to a pass/fail per account/region. The security score is passed controls ÷ total enabled controls. Failed critical controls (e.g. root MFA disabled, public S3) should page on-call.
AWS Config rules
| Rule type | How it works | Example |
|---|---|---|
| Managed rule | AWS-maintained; evaluates on config change + periodic | s3-bucket-public-read-prohibited |
| Custom rule | Lambda evaluates compliance; you write the logic | All RDS instances must have backup-retention ≥ 7 days |
| Conformance pack | Bundle of managed rules as a deployable unit | Operational Best Practices for PCI DSS |
| Remediation | SSM Automation document triggered on NON_COMPLIANT | Auto-enable S3 Block Public Access when rule fails |
Compliance dashboards
Security Hub Insights filter by control failure type; export findings to S3/Athena for audit reports. Config Aggregator unifies multi-account inventory — essential for "every SG allowing SSH from 0.0.0.0/0" queries.
Detective controls (Config + Hub) flag drift after it happens — good for audit and auto-remediation. Preventive controls (SCPs, IAM denies) block the API call before change is recorded. Use both: SCPs deny public S3 at org level; Config catches anything that slips through.
Enabling Config in every region without an aggregator — auditors get 16 regional consoles and incomplete picture. Enable Config only in used regions; use an aggregator in the security account. Config charges per configuration item recorded — high-churn resources (Lambda versions, ECS tasks) inflate cost; scope recording if needed.
Security Hub standards vs custom Config rules: FSBP gives 200+ controls out of the box but includes checks irrelevant to your stack (e.g. Redshift rules when you use RDS). Start with FSBP, disable inapplicable controls, add custom rules for app-specific requirements (tag enforcement, approved AMIs).
Encryption everywhere
Encryption at rest protects data on disk if someone steals a snapshot or dumps an S3 bucket. Encryption in transit protects data on the wire if someone taps a VPC path or runs a MITM on a public endpoint. AWS makes both easy to enable — the hard part is making it mandatory and verifying nothing slipped through.
Encrypt at rest — service by service
| Service | Default | Production setting | Key choice |
|---|---|---|---|
| EBS volumes | Encrypted by default in most regions (since 2023) | Account-level ec2:EncryptedVolumes Config rule; enforce CMK for prod | Customer-managed CMK per environment |
| RDS / Aurora | Encryption at create time only — cannot enable later | storage_encrypted = true in Terraform; snapshot copies stay encrypted | CMK for regulated data; AWS managed for dev |
| S3 | SSE-S3 (AWS-managed) default since 2023 | SSE-KMS with CMK; bucket policy denies unencrypted PUTs | CMK with key policy allowing S3 service |
| Lambda env vars | Encrypted at rest with AWS-managed key | Use Secrets Manager or SSM SecureString instead of env for secrets; CMK for env encryption | CMK via kms:Encrypt on function |
S3 bucket policy — deny unencrypted uploads
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyUnencryptedObjectUploads",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-app-artifacts/*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "aws:kms"
}
}
},
{
"Sid": "DenyInsecureTransport",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-app-artifacts",
"arn:aws:s3:::my-app-artifacts/*"
],
"Condition": {
"Bool": { "aws:SecureTransport": "false" }
}
}
]
}
TLS in transit checklist
-
ALB / CloudFront
ACM certificate on HTTPS listener; redirect HTTP → HTTPS; TLS 1.2+ policy (ELBSecurityPolicy-TLS13-1-2-2021-06).
-
Service-to-service
RDS/Aurora: require SSL in connection string (sslmode=require). ElastiCache in-transit encryption enabled.
-
VPC endpoints
Interface endpoints for S3, DynamoDB, Secrets Manager — traffic stays on AWS backbone, not public internet.
-
API Gateway
Custom domain with ACM cert; mutual TLS for B2B partners where required.
-
IAM enforcement
aws:SecureTransport = true in policies for S3, SNS, SQS, ECR.
-
Verify continuously
Config rules: alb-listener-https, rds-storage-encrypted, s3-default-encryption-kms.
RDS encryption is immutable — create encrypted from day one or restore encrypted snapshot to new instance. Lambda environment variables are visible to anyone with lambda:GetFunctionConfiguration — store secrets in Secrets Manager, pass ARNs only. Enable S3 Block Public Access at account level before creating any bucket.
Run aws s3api get-bucket-encryption and aws rds describe-db-instances --query 'DBInstances[?StorageEncrypted==`false`]' in a weekly CI job. Config catches drift; proactive scans catch resources created before Config was enabled.
"Encrypt existing unencrypted RDS" → snapshot → copy with encryption → restore to new instance — you cannot flip the flag in place. "Encrypt S3 objects already stored" → batch copy in place with new SSE-KMS default, or S3 Batch Operations. ACM certs are free for AWS-integrated services; you cannot export the private key.