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.
# 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}]'
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
}
}
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.
# 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'
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.
# 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
{
"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.
# 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
}
}
}'
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
# 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
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).
# 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:
{
"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
# 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
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
# 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
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
| Service | Protection Mechanism | AWS CLI Flag | Terraform Attribute |
|---|---|---|---|
| EC2 | Termination protection | --disable-api-termination | disable_api_termination = true |
| RDS | Deletion protection | --deletion-protection | deletion_protection = true |
| RDS | Final snapshot | --no-skip-final-snapshot | skip_final_snapshot = false |
| S3 | Versioning | put-bucket-versioning | versioning { status = "Enabled" } |
| S3 | Object Lock | --object-lock-enabled | object_lock_enabled = true |
| DynamoDB | Deletion protection | --deletion-protection-enabled | deletion_protection_enabled = true |
| CloudFormation | Termination protection | --enable-termination-protection | N/A (use AWS CLI) |
| ElastiCache | Snapshot on delete | --final-snapshot-identifier | final_snapshot_identifier |
Lilly Tech Systems