DEV Community

se-piyush
se-piyush

Posted on

Creating Cross-Account DynamoDB Backups with Terraform

In my previous post, Creating Secure Backups for DynamoDB Tables with Terraform, I discussed how to create DynamoDB table backups within the same AWS account. In this post, I will explain how to create cross-account DynamoDB backups, where the tables are in one AWS account and the backups are created in another AWS account for enhanced security and limited access.

Steps to Configure Cross-Account Backups

To achieve cross-account DynamoDB backups, follow these steps:

Step 1: Create Destination Vault in Backup Account

First, set up the backup vault in the AWS account where you want to store the backups. This includes configuring a KMS key for encryption and setting up the vault policy to allow cross-account access.

resource "aws_backup_vault" "destination_backup_vault" {
  name        = "destination-backup-vault"
  kms_key_arn = aws_kms_key.backup_vault_key.arn
}

resource "aws_kms_key" "backup_vault_key" {
  description         = "KMS key for backup vault encryption"
  enable_key_rotation = true
}

resource "aws_backup_vault_policy" "destination_backup_vault_policy" {
  backup_vault_name = aws_backup_vault.destination_backup_vault.name
  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Sid : "AllowCrossAccountAccess",
        Effect = "Allow",
        Principal = {
          AWS = "arn:aws:iam::[YOUR SOURCE ACCOUNT ID]:root"
        },
        Action = [
          "backup:CopyIntoBackupVault"
        ],
        Resource = "*"
      }
    ]
  })
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Create Source Vault in Source Account

Next, create the backup vault in the source account where the DynamoDB tables reside.

resource "aws_backup_vault" "source_backup_vault" {
  name        = "source-backup-vault"
  kms_key_arn = aws_kms_key.backup_vault_key.arn
}

resource "aws_kms_key" "backup_vault_key" {
  description         = "KMS Key for Backup"
  enable_key_rotation = true
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Create IAM Role with Relevant Permissions

Create an IAM role in the source account with the necessary permissions to perform backups, including the AWSBackupServiceRolePolicyForBackup policy.

data "aws_iam_policy_document" "assume_role" {
  statement {
    effect = "Allow"

    principals {
      type        = "Service"
      identifiers = ["backup.amazonaws.com"]
    }

    actions = ["sts:AssumeRole"]
  }
}

resource "aws_iam_role" "source_backup_role" {
  name               = "source-backup-role"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json

  inline_policy {
    name = "backup-policy"

    policy = jsonencode({
      Version = "2012-10-17",
      Statement = [
        {
          Effect = "Allow",
          Action = [
            "dynamodb:CreateBackup",
            "dynamodb:DeleteBackup",
            "dynamodb:DescribeBackup",
            "dynamodb:ListBackups",
            "dynamodb:ListTables",
            "dynamodb:RestoreTableFromBackup",
            "dynamodb:ListTagsOfResource",
            "dynamodb:StartAwsBackupJob",
            "dynamodb:RestoreTableFromAwsBackup"
          ],
          Resource = "*"
        },
        {
          Effect = "Allow",
          Action = [
            "backup:StartBackupJob",
            "backup:StopBackupJob",
            "backup:TagResource",
            "backup:UntagResource"
          ],
          Resource = "*"
        }
      ]
    })
  }
}

resource "aws_iam_role_policy_attachment" "service_backup_policy" {
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup"
  role       = aws_iam_role.source_backup_role.name
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Create a Backup Plan

Configure a backup plan to periodically back up the DynamoDB tables and copy them to the destination vault.

resource "aws_backup_plan" "cross_account_cross_region_copy" {
  name = "cross-account-cross-region-copy"

  rule {
    rule_name         = "copy-to-destination"
    target_vault_name = aws_backup_vault.source_backup_vault.name
    schedule          = "cron(38 13 * * ? *)"

    copy_action {
      destination_vault_arn = var.destination_backup_vault_arn
      lifecycle {
        delete_after = 30 # Retain for 30 days
      }
    }
  }
}

resource "aws_backup_selection" "dynamodb_cross_account_copy_selection" {
  plan_id      = aws_backup_plan.cross_account_cross_region_copy.id
  name         = "copy-backup-selection"
  iam_role_arn = aws_iam_role.source_backup_role.arn

  resources = [
    //table arns
  ]
}

resource "aws_backup_vault_policy" "source_backup_vault_policy" {
  backup_vault_name = aws_backup_vault.source_backup_vault.name
  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Sid : "AllowCrossAccountAccess",
        Effect = "Allow",
        Principal = {
          AWS = "arn:aws:iam::[YOUR BACKUP ACCOUNT ID]:root"
        },
        Action = [
          "backup:CopyFromBackupVault",
          "backup:CopyIntoBackupVault"
        ],
        Resource = "*"
      }
    ]
  })
}
Enter fullscreen mode Exit fullscreen mode

So your complete destination terraform file would look something like this

resource "aws_backup_vault" "source_backup_vault" {
  name        = "source-backup-vault"
  kms_key_arn = aws_kms_key.backup_vault_key.arn
}

resource "aws_kms_key" "backup_vault_key" {
  description         = "KMS Key for Backup"
  enable_key_rotation = true
}

resource "aws_backup_vault_policy" "source_backup_vault_policy" {
  backup_vault_name = aws_backup_vault.source_backup_vault.name
  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Sid : "AllowCrossAccountAccess",
        Effect = "Allow",
        Principal = {
          AWS = "arn:aws:iam::[YOUR BACKUP ACCOUNT ID]:root"
        },
        Action = [
          "backup:CopyFromBackupVault",
          "backup:CopyIntoBackupVault"
        ],
        Resource = "*"
      }
    ]
  })
}

data "aws_iam_policy_document" "assume_role" {
  statement {
    effect = "Allow"

    principals {
      type        = "Service"
      identifiers = ["backup.amazonaws.com"]
    }

    actions = ["sts:AssumeRole"]
  }
}

resource "aws_iam_role" "source_backup_role" {
  name               = "source-backup-role"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json

  inline_policy {
    name = "backup-policy"

    policy = jsonencode({
      Version = "2012-10-17",
      Statement = [
        {
          Effect = "Allow",
          Action = [
            "dynamodb:CreateBackup",
            "dynamodb:DeleteBackup",
            "dynamodb:DescribeBackup",
            "dynamodb:ListBackups",
            "dynamodb:ListTables",
            "dynamodb:RestoreTableFromBackup",
            "dynamodb:ListTagsOfResource",
            "dynamodb:StartAwsBackupJob",
            "dynamodb:RestoreTableFromAwsBackup"
          ],
          Resource = "*"
        },
        {
          Effect = "Allow",
          Action = [
            "backup:StartBackupJob",
            "backup:StopBackupJob",
            "backup:TagResource",
            "backup:UntagResource"
          ],
          Resource = "*"
        }
      ]
    })
  }
}

resource "aws_iam_role_policy_attachment" "service_backup_policy" {
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup"
  role       = aws_iam_role.source_backup_role.name
}

resource "aws_backup_plan" "cross_account_cross_region_copy" {
  name = "cross-account-cross-region-copy"

  rule {
    rule_name         = "copy-to-destination"
    target_vault_name = aws_backup_vault.source_backup_vault.name
    schedule          = "cron(38 13 * * ? *)"

    copy_action {
      destination_vault_arn = var.destination_backup_vault_arn
      lifecycle {
        delete_after = 30 # Retain for 30 days
      }
    }
  }
}

resource "aws_backup_selection" "dynamodb_cross_account_copy_selection" {
  plan_id      = aws_backup_plan.cross_account_cross_region_copy.id
  name         = "copy-backup-selection"
  iam_role_arn = aws_iam_role.source_backup_role.arn

  resources = [
    //table arns
  ]
}

Enter fullscreen mode Exit fullscreen mode

Understanding the Cron Schedule

The schedule parameter in the backup plan uses a cron expression to determine when the backup should run. Here is an explanation of the cron expression used in this configuration:

schedule = "cron(38 13 * * ? *)"
Enter fullscreen mode Exit fullscreen mode
  • 38: Minute (38th minute of the hour).
  • 13: Hour (1 PM UTC).
  • *: Day of the month (any day).
  • *: Month (any month).
  • ?: Day of the week (any day of the week).
  • *: Year (optional, any year).

This configuration schedules the backup job to run daily at 1:38 PM UTC.

Conclusion

Creating cross-account backups for DynamoDB tables enhances the security and availability of your data. By following the steps outlined in this guide, you can use Terraform to automate the process of setting up secure backups across different AWS accounts. This approach ensures that your backups are stored securely in a separate account, providing an additional layer of protection against data loss or unauthorized access.

If you have any suggestions or improvements, please feel free to comment. Happy Terraforming!

Top comments (0)