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.

devops architect Regional services Shield Standard free

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:

  1. App calls kms:GenerateDataKey — KMS returns a plaintext data key + encrypted copy
  2. App encrypts the record locally with AES-256 using the plaintext data key
  3. App stores ciphertext + encrypted data key in DynamoDB; wipes plaintext key from memory
  4. 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

saved globally
bash
# 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
hcl
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
}
typescript
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');
🔬 Under the Hood

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.

🎯 Exam Tip

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

🔒 Security

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

saved globally
bash
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
hcl
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
  }
}
typescript
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),
});
⚠️ Pitfall

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.

💡 Pro Tip

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.

⚖️ Trade-off

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

saved globally
bash
# 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
hcl
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
}
typescript
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,
});
💰 Cost

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.

🎯 Exam Tip

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

  1. Enable GuardDuty in the security/admin account; delegate admin for org-wide enrollment
  2. Severity — Low (4.0–4.9), Medium (5.0–6.9), High (7.0–8.9), Critical (9.0+) on a 0–10 scale
  3. Aggregate — findings flow to Security Hub as ASFF (AWS Security Finding Format)
  4. Triage — EventBridge rule on severity ≥ 7 → SNS/PagerDuty/Slack
  5. Investigate — pivot on resource ARN, principal, and affected region in CloudTrail
  6. Remediate — isolate SG, rotate credentials, quarantine EC2 snapshot; mark finding resolved in GuardDuty
  7. 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.

📦 Real World

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.

🔬 Under the Hood

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.

⚠️ Pitfall

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.

⚖️ Trade-off

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

json
{
  "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.

🔒 Security

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.

💡 Pro Tip

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.

🎯 Exam Tip

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