Setting up AWS Backup with Terraform

A comprehensive guide to configuring AWS Backup for centralized data protection using Terraform Infrastructure as Code

Setting up AWS Backup with Terraform

AWS Backup is a fully managed backup service that makes it easy to centralize and automate data protection across AWS services. This guide shows how to set up AWS Backup using Terraform.

Prerequisites

  • AWS CLI configured
  • Terraform installed
  • Understanding of backup requirements
  • Basic knowledge of AWS services

Project Structure

aws-backup-terraform/
├── main.tf
├── variables.tf
├── outputs.tf
└── terraform.tfvars

Basic AWS Backup Configuration

# main.tf
provider "aws" {
  region = var.aws_region
}

# AWS Backup Vault
resource "aws_backup_vault" "main" {
  name        = "${var.project_name}-vault"
  kms_key_arn = aws_kms_key.backup.arn

  tags = {
    Environment = var.environment
  }
}

# KMS Key for Encryption
resource "aws_kms_key" "backup" {
  description             = "KMS key for AWS Backup encryption"
  deletion_window_in_days = 7
  enable_key_rotation     = true

  tags = {
    Environment = var.environment
  }
}

# IAM Role for AWS Backup
resource "aws_iam_role" "backup" {
  name = "${var.project_name}-backup-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "backup.amazonaws.com"
        }
      }
    ]
  })
}

# IAM Policy for AWS Backup
resource "aws_iam_role_policy_attachment" "backup" {
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup"
  role       = aws_iam_role.backup.name
}

# Backup Plan
resource "aws_backup_plan" "main" {
  name = "${var.project_name}-backup-plan"

  rule {
    rule_name         = "daily-backup"
    target_vault_name = aws_backup_vault.main.name
    schedule          = "cron(0 5 ? * * *)"

    lifecycle {
      delete_after = 30
    }

    copy_action {
      destination_vault_arn = aws_backup_vault.secondary.arn
    }
  }

  rule {
    rule_name         = "weekly-backup"
    target_vault_name = aws_backup_vault.main.name
    schedule          = "cron(0 5 ? * SAT *)"

    lifecycle {
      cold_storage_after = 30
      delete_after       = 90
    }
  }

  rule {
    rule_name         = "monthly-backup"
    target_vault_name = aws_backup_vault.main.name
    schedule          = "cron(0 5 1 * ? *)"

    lifecycle {
      cold_storage_after = 90
      delete_after       = 365
    }
  }

  advanced_backup_setting {
    backup_options = {
      WindowsVSS = "enabled"
    }
    resource_type = "EC2"
  }

  tags = {
    Environment = var.environment
  }
}

# Secondary Backup Vault (for cross-region copies)
resource "aws_backup_vault" "secondary" {
  provider    = aws.secondary
  name        = "${var.project_name}-vault-secondary"
  kms_key_arn = aws_kms_key.backup_secondary.arn

  tags = {
    Environment = var.environment
  }
}

# Secondary KMS Key
resource "aws_kms_key" "backup_secondary" {
  provider                = aws.secondary
  description             = "KMS key for secondary AWS Backup encryption"
  deletion_window_in_days = 7
  enable_key_rotation     = true

  tags = {
    Environment = var.environment
  }
}

# Backup Selection
resource "aws_backup_selection" "main" {
  name         = "${var.project_name}-backup-selection"
  iam_role_arn = aws_iam_role.backup.arn
  plan_id      = aws_backup_plan.main.id

  selection_tag {
    type  = "STRINGEQUALS"
    key   = "Backup"
    value = "true"
  }

  condition {
    string_equals {
      key   = "aws:ResourceTag/Environment"
      value = var.environment
    }
  }

  resources = var.resource_arns
}

Variables Configuration

# variables.tf
variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "us-west-2"
}

variable "project_name" {
  description = "Project name"
  type        = string
}

variable "environment" {
  description = "Environment name"
  type        = string
  default     = "dev"
}

variable "resource_arns" {
  description = "List of resource ARNs to backup"
  type        = list(string)
  default     = []
}

variable "secondary_region" {
  description = "Secondary region for backup copies"
  type        = string
  default     = "us-east-1"
}

Best Practices

  1. Backup Management

    • Define retention policies
    • Implement cross-region copies
    • Configure notifications
    • Regular backup testing
  2. Security

    • Enable encryption
    • Use proper IAM roles
    • Implement access policies
    • Regular security reviews
  3. Cost Optimization

    • Monitor storage usage
    • Use lifecycle policies
    • Clean up old backups
    • Regular cost reviews
  4. Performance

    • Schedule during off-peak
    • Monitor backup windows
    • Use incremental backups
    • Regular performance reviews

Cross-Account Backup

# Cross-Account Backup Vault Policy
resource "aws_backup_vault_policy" "cross_account" {
  backup_vault_name = aws_backup_vault.main.name

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "AllowCrossAccountBackup"
        Effect = "Allow"
        Principal = {
          AWS = var.cross_account_arns
        }
        Action = [
          "backup:CopyIntoBackupVault"
        ]
        Resource = "*"
      }
    ]
  })
}

# Cross-Account IAM Role
resource "aws_iam_role" "cross_account_backup" {
  provider = aws.source
  name     = "${var.project_name}-cross-account-backup"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          AWS = var.target_account_arn
        }
      }
    ]
  })
}

Notification Configuration

# SNS Topic for Notifications
resource "aws_sns_topic" "backup_notifications" {
  name = "${var.project_name}-backup-notifications"

  tags = {
    Environment = var.environment
  }
}

# SNS Topic Policy
resource "aws_sns_topic_policy" "backup_notifications" {
  arn = aws_sns_topic.backup_notifications.arn

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "AllowBackupPublish"
        Effect = "Allow"
        Principal = {
          Service = "backup.amazonaws.com"
        }
        Action   = "SNS:Publish"
        Resource = aws_sns_topic.backup_notifications.arn
      }
    ]
  })
}

# EventBridge Rule for Backup Events
resource "aws_cloudwatch_event_rule" "backup_events" {
  name        = "${var.project_name}-backup-events"
  description = "Capture AWS Backup events"

  event_pattern = jsonencode({
    source      = ["aws.backup"]
    detail-type = ["AWS Backup Job State Change"]
  })
}

# EventBridge Target
resource "aws_cloudwatch_event_target" "sns" {
  rule      = aws_cloudwatch_event_rule.backup_events.name
  target_id = "SendToSNS"
  arn       = aws_sns_topic.backup_notifications.arn

  input_transformer {
    input_paths = {
      status      = "$.detail.state"
      resource    = "$.detail.resourceArn"
      backup_job  = "$.detail.backupJobId"
    }
    input_template = "\"Backup job <backup_job> for resource <resource> changed status to <status>\""
  }
}

Monitoring Configuration

# CloudWatch Dashboard
resource "aws_cloudwatch_dashboard" "backup" {
  dashboard_name = "${var.project_name}-backup-dashboard"

  dashboard_body = jsonencode({
    widgets = [
      {
        type   = "metric"
        x      = 0
        y      = 0
        width  = 12
        height = 6

        properties = {
          metrics = [
            ["AWS/Backup", "NumberOfBackupJobsCompleted", "BackupVaultName", aws_backup_vault.main.name],
            ["AWS/Backup", "NumberOfBackupJobsFailed", "BackupVaultName", aws_backup_vault.main.name]
          ]
          period = 300
          stat   = "Sum"
          region = var.aws_region
          title  = "Backup Job Status"
        }
      }
    ]
  })
}

# CloudWatch Alarms
resource "aws_cloudwatch_metric_alarm" "backup_failures" {
  alarm_name          = "${var.project_name}-backup-failures"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = "1"
  metric_name         = "NumberOfBackupJobsFailed"
  namespace           = "AWS/Backup"
  period             = "300"
  statistic          = "Sum"
  threshold          = "0"
  alarm_description  = "This metric monitors backup job failures"
  alarm_actions      = [aws_sns_topic.backup_notifications.arn]

  dimensions = {
    BackupVaultName = aws_backup_vault.main.name
  }
}

Common Use Cases

  1. Database Backup
resource "aws_backup_selection" "databases" {
  name         = "${var.project_name}-database-backup"
  iam_role_arn = aws_iam_role.backup.arn
  plan_id      = aws_backup_plan.main.id

  selection_tag {
    type  = "STRINGEQUALS"
    key   = "Type"
    value = "Database"
  }

  resources = [
    aws_db_instance.main.arn,
    aws_rds_cluster.aurora.arn
  ]
}
  1. EC2 Instance Backup
resource "aws_backup_selection" "ec2" {
  name         = "${var.project_name}-ec2-backup"
  iam_role_arn = aws_iam_role.backup.arn
  plan_id      = aws_backup_plan.main.id

  selection_tag {
    type  = "STRINGEQUALS"
    key   = "Backup"
    value = "true"
  }

  condition {
    string_equals {
      key   = "aws:ResourceTag/Environment"
      value = var.environment
    }
  }

  resources = [
    "arn:aws:ec2:${var.aws_region}:${data.aws_caller_identity.current.account_id}:instance/*"
  ]
}

Advanced Features

# Continuous Backup
resource "aws_backup_plan" "continuous" {
  name = "${var.project_name}-continuous-backup"

  rule {
    rule_name         = "continuous-backup"
    target_vault_name = aws_backup_vault.main.name
    schedule          = "cron(0/15 * ? * * *)"

    lifecycle {
      delete_after = 7
    }

    enable_continuous_backup = true
  }

  tags = {
    Environment = var.environment
  }
}

# Backup Report Plan
resource "aws_backup_report_plan" "main" {
  name        = "${var.project_name}-backup-report"
  description = "Daily backup reports"

  report_delivery_channel {
    formats = ["CSV"]
    s3_bucket_name = aws_s3_bucket.backup_reports.id
  }

  report_setting {
    report_template = "BACKUP_JOB_REPORT"
    framework_arns  = [aws_backup_framework.main.arn]
  }

  tags = {
    Environment = var.environment
  }
}

Conclusion

This setup provides a comprehensive foundation for deploying AWS Backup using Terraform. Remember to:

  • Plan your backup strategy carefully
  • Implement proper retention policies
  • Monitor backup status and costs
  • Keep your configurations versioned
  • Test backup restoration regularly

The complete code can be customized based on your specific requirements and use cases.