diff --git a/README.md b/README.md index e1b270c..48eff72 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The data volume is not created by Terraform. Set `data_volume_id` on the workspa Workspace `file-share-prod` is manual apply. Auto-apply stays off until the share has soaked. `user_data_replace_on_change` is false, so an AMI or user-data change does not replace the instance by itself. Snapshot the data volume and confirm before any apply that would replace the instance. -The nightly DLM policy targets volumes tagged `file-share-backup=true` and keeps 30 snapshots. +The nightly DLM policy targets volumes tagged `file-share-backup=true` and keeps 30 snapshots. The policy runs as `/tf-managed/file-share-dlm`. ## Secrets diff --git a/terraform/dlm.tf b/terraform/dlm.tf index 8161899..09482c8 100644 --- a/terraform/dlm.tf +++ b/terraform/dlm.tf @@ -1,6 +1,6 @@ resource "aws_dlm_lifecycle_policy" "nightly" { description = "Nightly EBS snapshots for file share" - execution_role_arn = local.dlm_service_role_arn + execution_role_arn = aws_iam_role.dlm.arn state = "ENABLED" policy_details { diff --git a/terraform/hcp_iam.tf b/terraform/hcp_iam.tf index b59f6b4..3d7b9c8 100644 --- a/terraform/hcp_iam.tf +++ b/terraform/hcp_iam.tf @@ -156,10 +156,10 @@ data "aws_iam_policy_document" "hcptf_scoped_iam" { } statement { - sid = "PassDlmServiceRole" + sid = "PassDlmRole" effect = "Allow" actions = ["iam:PassRole"] - resources = [local.dlm_service_role_arn] + resources = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/tf-managed/${local.dlm_role_name}"] condition { test = "StringEquals" @@ -168,21 +168,6 @@ data "aws_iam_policy_document" "hcptf_scoped_iam" { } } - statement { - sid = "CreateDlmServiceLinkedRole" - effect = "Allow" - actions = [ - "iam:CreateServiceLinkedRole", - ] - resources = [local.dlm_service_role_arn] - - condition { - test = "StringEquals" - variable = "iam:AWSServiceName" - values = ["dlm.amazonaws.com"] - } - } - statement { sid = "InstanceProfiles" effect = "Allow" diff --git a/terraform/iam.tf b/terraform/iam.tf index 2a1783a..71dde07 100644 --- a/terraform/iam.tf +++ b/terraform/iam.tf @@ -165,3 +165,87 @@ resource "aws_iam_instance_profile" "this" { path = "/tf-managed/" role = aws_iam_role.instance.name } + +# DLM permissions boundary. +# Intersection with AWSDataLifecycleManagerServiceRole is the snapshot API set. +# Creating this policy needs the hcptf-bootstrap window. + +data "aws_iam_policy_document" "dlm_boundary" { + # checkov:skip=CKV_AWS_111: DLM snapshot and describe APIs require Resource=*. EventBridge rules are ARN-scoped. + statement { + sid = "SnapshotLifecycle" + effect = "Allow" + actions = [ + "ec2:CopySnapshot", + "ec2:CreateSnapshot", + "ec2:CreateSnapshots", + "ec2:CreateTags", + "ec2:DeleteSnapshot", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeFastSnapshotRestores", + "ec2:DescribeInstances", + "ec2:DescribeSnapshotAttribute", + "ec2:DescribeSnapshots", + "ec2:DescribeSnapshotTierStatus", + "ec2:DescribeVolumes", + "ec2:DisableFastSnapshotRestores", + "ec2:EnableFastSnapshotRestores", + "ec2:ModifySnapshotAttribute", + "ec2:ModifySnapshotTier", + ] + resources = ["*"] + } + + statement { + sid = "SnapshotRules" + effect = "Allow" + actions = [ + "events:DeleteRule", + "events:DescribeRule", + "events:DisableRule", + "events:EnableRule", + "events:ListTargetsByRule", + "events:PutRule", + "events:PutTargets", + "events:RemoveTargets", + ] + resources = ["arn:aws:events:*:*:rule/AwsDataLifecycleRule.managed-cwe.*"] + } +} + +resource "aws_iam_policy" "dlm_boundary" { + # checkov:skip=CKV_AWS_111: DLM snapshot and describe APIs require Resource=*. EventBridge rules are ARN-scoped. + name = local.dlm_boundary_name + path = "/tf-managed/" + description = "Permissions boundary for the file-share DLM role." + policy = data.aws_iam_policy_document.dlm_boundary.json +} + +data "aws_iam_policy_document" "dlm_assume" { + statement { + sid = "DlmAssume" + effect = "Allow" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["dlm.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "dlm" { + name = local.dlm_role_name + path = "/tf-managed/" + assume_role_policy = data.aws_iam_policy_document.dlm_assume.json + permissions_boundary = aws_iam_policy.dlm_boundary.arn + + tags = { + Name = local.dlm_role_name + } +} + +resource "aws_iam_role_policy_attachment" "dlm" { + role = aws_iam_role.dlm.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSDataLifecycleManagerServiceRole" +} diff --git a/terraform/locals.tf b/terraform/locals.tf index 35ebfe5..63ca20f 100644 --- a/terraform/locals.tf +++ b/terraform/locals.tf @@ -12,6 +12,8 @@ locals { instance_role_name = "file-share-role" instance_profile_name = "file-share-profile" boundary_name = "file-share-instance-boundary" + dlm_role_name = "file-share-dlm" + dlm_boundary_name = "file-share-dlm-boundary" office_lan_cidrs = ["10.10.0.0/16", "10.30.0.0/16"] subnet_cidr = "10.40.20.0/24" @@ -19,8 +21,6 @@ locals { create_instance = var.data_volume_id != "" - dlm_service_role_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/aws-service-role/dlm.amazonaws.com/AWSServiceRoleForDataLifecycleManager" - user_data = templatefile("${path.module}/user_data.sh.tftpl", { aws_region = var.aws_region filebrowser_version = var.filebrowser_version