Intermediate

AWS Resource Protection

Enable built-in protection mechanisms on every critical AWS resource. Termination protection, deletion protection, Object Lock, stack policies, and versioning — with AWS CLI and Terraform examples for each service.

Why Resource-Level Protection Matters

IAM policies and SCPs prevent unauthorized API calls. Resource-level protection adds a second layer: even if an API call is authorized, the resource itself will refuse to be deleted. This defense-in-depth approach means an AI agent would need to first disable protection (a separate API call you can deny) before it could delete the resource.

EC2 Termination Protection

EC2 termination protection prevents an instance from being terminated via the API, CLI, or console. When enabled, any TerminateInstances call will fail with an error.

Bash — Enable EC2 termination protection
# Enable on an existing instance
aws ec2 modify-instance-attribute \
  --instance-id i-0abc123def456789 \
  --disable-api-termination

# Verify it is enabled
aws ec2 describe-instance-attribute \
  --instance-id i-0abc123def456789 \
  --attribute disableApiTermination

# Enable on launch
aws ec2 run-instances \
  --image-id ami-0abcdef1234567890 \
  --instance-type t3.medium \
  --disable-api-termination \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=production-web},{Key=Lifecycle,Value=production}]'
HCL — Terraform: EC2 with termination protection
resource "aws_instance" "production_web" {
  ami                     = "ami-0abcdef1234567890"
  instance_type           = "t3.medium"
  disable_api_termination = true
  disable_api_stop        = false  # Allow stopping but not terminating

  tags = {
    Name      = "production-web"
    Lifecycle = "production"
    ManagedBy = "terraform"
  }

  lifecycle {
    prevent_destroy = true  # Terraform-level protection
  }
}
💡
Double protection: Use both AWS termination protection (disable_api_termination) and Terraform lifecycle (prevent_destroy). The AWS setting blocks API calls; the Terraform setting prevents terraform destroy from even attempting the API call.

RDS Deletion Protection

RDS deletion protection prevents a database instance or cluster from being deleted. Additionally, you should enforce final snapshots so that even if deletion protection is disabled, a backup is always created.

Bash — Enable RDS deletion protection
# Enable deletion protection on an existing instance
aws rds modify-db-instance \
  --db-instance-identifier production-postgres \
  --deletion-protection \
  --apply-immediately

# Enable on a new instance
aws rds create-db-instance \
  --db-instance-identifier production-postgres \
  --db-instance-class db.t3.medium \
  --engine postgres \
  --engine-version 16.1 \
  --master-username admin \
  --master-user-password 'CHANGE-ME-IMMEDIATELY' \
  --allocated-storage 100 \
  --deletion-protection \
  --backup-retention-period 35 \
  --copy-tags-to-snapshot \
  --tags Key=Lifecycle,Value=production

# Verify deletion protection
aws rds describe-db-instances \
  --db-instance-identifier production-postgres \
  --query 'DBInstances[0].DeletionProtection'
HCL — Terraform: RDS with deletion protection
resource "aws_db_instance" "production_postgres" {
  identifier              = "production-postgres"
  engine                  = "postgres"
  engine_version          = "16.1"
  instance_class          = "db.t3.medium"
  allocated_storage       = 100
  username                = "admin"
  password                = var.db_password
  deletion_protection     = true
  skip_final_snapshot     = false
  final_snapshot_identifier = "production-postgres-final-${formatdate("YYYY-MM-DD-hhmm", timestamp())}"
  backup_retention_period = 35
  copy_tags_to_snapshot   = true

  tags = {
    Name      = "production-postgres"
    Lifecycle = "production"
  }

  lifecycle {
    prevent_destroy = true
  }
}

S3 Bucket Protection

S3 offers multiple layers of protection: bucket policies, versioning, and Object Lock.

S3 Versioning

With versioning enabled, deleting an object only creates a "delete marker" — the previous versions are preserved and can be restored.

Bash — Enable S3 versioning
# Enable versioning
aws s3api put-bucket-versioning \
  --bucket production-assets \
  --versioning-configuration Status=Enabled

# Verify versioning status
aws s3api get-bucket-versioning --bucket production-assets

S3 Bucket Policy Denying Deletion

JSON — S3 bucket policy preventing deletion
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyBucketDeletion",
      "Effect": "Deny",
      "Principal": "*",
      "Action": [
        "s3:DeleteBucket",
        "s3:DeleteBucketPolicy"
      ],
      "Resource": "arn:aws:s3:::production-assets"
    },
    {
      "Sid": "DenyObjectDeletion",
      "Effect": "Deny",
      "Principal": "*",
      "Action": [
        "s3:DeleteObject",
        "s3:DeleteObjectVersion"
      ],
      "Resource": "arn:aws:s3:::production-assets/*",
      "Condition": {
        "StringNotEquals": {
          "aws:PrincipalArn": "arn:aws:iam::123456789012:role/AdminRole"
        }
      }
    }
  ]
}

S3 Object Lock

Object Lock provides WORM (Write Once Read Many) protection. Once locked, objects cannot be deleted or overwritten for the specified retention period.

Bash — Enable S3 Object Lock
# Create a bucket with Object Lock enabled (must be set at creation)
aws s3api create-bucket \
  --bucket production-critical-data \
  --region us-east-1 \
  --object-lock-enabled-for-bucket

# Set default retention (Governance mode allows admin override; Compliance mode is immutable)
aws s3api put-object-lock-configuration \
  --bucket production-critical-data \
  --object-lock-configuration '{
    "ObjectLockEnabled": "Enabled",
    "Rule": {
      "DefaultRetention": {
        "Mode": "GOVERNANCE",
        "Days": 90
      }
    }
  }'
HCL — Terraform: S3 with versioning and Object Lock
resource "aws_s3_bucket" "production_assets" {
  bucket              = "production-assets"
  object_lock_enabled = true

  tags = {
    Name      = "production-assets"
    Lifecycle = "production"
  }
}

resource "aws_s3_bucket_versioning" "production_assets" {
  bucket = aws_s3_bucket.production_assets.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_object_lock_configuration" "production_assets" {
  bucket = aws_s3_bucket.production_assets.id

  rule {
    default_retention {
      mode = "GOVERNANCE"
      days = 90
    }
  }
}

EKS Cluster Deletion Protection

Bash — EKS cluster protection (available since 2024)
# Create cluster with deletion protection
aws eks create-cluster \
  --name production-cluster \
  --role-arn arn:aws:iam::123456789012:role/EKSClusterRole \
  --resources-vpc-config subnetIds=subnet-xxxx,subnet-yyyy \
  --access-config authenticationMode=API_AND_CONFIG_MAP \
  --tags Lifecycle=production

# Note: EKS does not have a native --deletion-protection flag like RDS
# Protect EKS via SCPs (covered in Lesson 3) and Terraform lifecycle rules
HCL — Terraform: EKS with lifecycle protection
resource "aws_eks_cluster" "production" {
  name     = "production-cluster"
  role_arn = aws_iam_role.eks_cluster.arn

  vpc_config {
    subnet_ids = var.subnet_ids
  }

  tags = {
    Name      = "production-cluster"
    Lifecycle = "production"
  }

  lifecycle {
    prevent_destroy = true
  }
}

CloudFormation Stack Protection

CloudFormation offers two levels of protection: termination protection (prevents DeleteStack) and stack policies (prevents updates/replacements of specific resources).

Bash — CloudFormation termination protection
# Enable on an existing stack
aws cloudformation update-termination-protection \
  --stack-name production-infrastructure \
  --enable-termination-protection

# Enable on stack creation
aws cloudformation create-stack \
  --stack-name production-infrastructure \
  --template-body file://template.yaml \
  --enable-termination-protection

CloudFormation Stack Policy

A stack policy controls which resources can be updated or replaced during a stack update. This prevents accidental replacement of databases or other stateful resources:

JSON — CloudFormation stack policy
{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "Update:*",
      "Principal": "*",
      "Resource": "*"
    },
    {
      "Effect": "Deny",
      "Action": [
        "Update:Replace",
        "Update:Delete"
      ],
      "Principal": "*",
      "Resource": "LogicalResourceId/ProductionDatabase"
    },
    {
      "Effect": "Deny",
      "Action": [
        "Update:Replace",
        "Update:Delete"
      ],
      "Principal": "*",
      "Resource": "LogicalResourceId/ProductionVPC"
    }
  ]
}

DynamoDB Deletion Protection

Bash — Enable DynamoDB deletion protection
# Enable deletion protection on existing table
aws dynamodb update-table \
  --table-name production-users \
  --deletion-protection-enabled

# Create table with deletion protection
aws dynamodb create-table \
  --table-name production-orders \
  --attribute-definitions AttributeName=OrderId,AttributeType=S \
  --key-schema AttributeName=OrderId,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST \
  --deletion-protection-enabled \
  --tags Key=Lifecycle,Value=production
HCL — Terraform: DynamoDB with deletion protection
resource "aws_dynamodb_table" "production_users" {
  name                        = "production-users"
  billing_mode                = "PAY_PER_REQUEST"
  hash_key                    = "UserId"
  deletion_protection_enabled = true

  attribute {
    name = "UserId"
    type = "S"
  }

  point_in_time_recovery {
    enabled = true
  }

  tags = {
    Name      = "production-users"
    Lifecycle = "production"
  }

  lifecycle {
    prevent_destroy = true
  }
}

ElastiCache Deletion Protection

Bash — ElastiCache with final snapshot
# Create a Redis replication group with automatic failover
aws elasticache create-replication-group \
  --replication-group-id production-redis \
  --replication-group-description "Production Redis cluster" \
  --engine redis \
  --cache-node-type cache.r6g.large \
  --num-cache-clusters 2 \
  --automatic-failover-enabled \
  --snapshot-retention-limit 35 \
  --tags Key=Lifecycle,Value=production

# When deletion is attempted, always require a final snapshot
# This is enforced via IAM/SCP policies that deny DeleteReplicationGroup
# without FinalSnapshotIdentifier
HCL — Terraform: ElastiCache with protection
resource "aws_elasticache_replication_group" "production_redis" {
  replication_group_id = "production-redis"
  description          = "Production Redis cluster"
  engine               = "redis"
  node_type            = "cache.r6g.large"
  num_cache_clusters   = 2
  automatic_failover_enabled = true
  snapshot_retention_limit   = 35
  final_snapshot_identifier  = "production-redis-final"

  tags = {
    Name      = "production-redis"
    Lifecycle = "production"
  }

  lifecycle {
    prevent_destroy = true
  }
}

Resource Protection Summary

ServiceProtection MechanismAWS CLI FlagTerraform Attribute
EC2Termination protection--disable-api-terminationdisable_api_termination = true
RDSDeletion protection--deletion-protectiondeletion_protection = true
RDSFinal snapshot--no-skip-final-snapshotskip_final_snapshot = false
S3Versioningput-bucket-versioningversioning { status = "Enabled" }
S3Object Lock--object-lock-enabledobject_lock_enabled = true
DynamoDBDeletion protection--deletion-protection-enableddeletion_protection_enabled = true
CloudFormationTermination protection--enable-termination-protectionN/A (use AWS CLI)
ElastiCacheSnapshot on delete--final-snapshot-identifierfinal_snapshot_identifier