瀏覽代碼

Enabled Creation of a DLM Policy for Cross-Region AMI Backups

Since there is no built-in terraform support, this module does something new. It uses a local provisioner to call an AWS CLI script to create the DLM policy.

There is also an external data script to get the current ID of the DLM policy, if any. While this turned out not to be needed (I thought I'd have to trigger the null resource to only be created if one didn't exist), it still seems like it will be useful in the future if we want a script to modify rather than replace the existing policy.

I think this is straightforward enough to make sense to future engineers. LMK if there are questions.

Also:
* updates the IAM policy to allow for the additional permissions required.
* updates the ebs key policy to allow access

To be tagged v2.1.0. Seems like it's time, and a new resource type is a good enough reason for me.
Fred Damstra [afs macbook] 4 年之前
父節點
當前提交
facb4892e7

+ 31 - 0
base/account_standards/ami_backups.tf

@@ -0,0 +1,31 @@
+# At this time, terraform does not support DLM AMI policies, only snapshots.
+# So we do it ourselves
+# 
+# NOTE: This will not update an existing policy, but will create one if it's missing.
+
+
+# Grab the current policy name. This turned out to be unnecessary for my purposes, but
+# will be useful if in the future we decide to implement a 'modify' resource.
+#
+# WARNING: External data sources are run before the apply, and even before any decision
+#          is made whether or not to apply, so do not make changes in such a script.
+data "external" "get_dlm_policies" {
+  program = ["bin/get_current_dlm_policies"]
+}
+
+# In rare cases, you may need/want to manually recreate this. To do so, run
+#    terragrunt taint null_resource.create_dlm_policy
+resource "null_resource" "create_dlm_policy" {
+  #count = data.external.get_dlm_policies.result["PolicyId"] == "null" ? 1 : 0
+  #count = data.external.get_dlm_policies.result["PolicyId"] == "policy-02af49210b5b375d5" ? 1 : 0
+
+  # Could maybe find some sort of trigger here, in case the DLM is deleted?
+
+  provisioner "local-exec" {
+    command = "bin/create_dlm_policy ${var.aws_partition} ${var.aws_region} ${var.aws_account_id} ${var.account_name}"
+  }
+
+  depends_on = [
+    aws_iam_role.dlm_lifecycle_role
+  ]
+}

+ 111 - 0
base/account_standards/bin/create_dlm_policy

@@ -0,0 +1,111 @@
+#! /bin/bash
+#
+# Creates the XDR DLM Policy to backup AMIs daily and copy them cross-region.
+#
+# NOTE: If you create a new policy, the old policy will remain. Use the modify
+# script instead. And even if you delete the old policy, the images created by
+# it will remain and continue to incur charges.
+PARTITION=$1
+REGION=$2
+ACCOUNT=$3
+ACCOUNT_NAME=$4
+
+# Fix for some accounts having -gov already appended and some not.
+# Accounts in gov will get it appended.
+ACCOUNT_NAME=${ACCOUNT_NAME%%-gov}
+
+if [[ ${REGION} == "us-gov-east-1" ]]; then
+  PROFILE=${ACCOUNT_NAME}-gov
+  TARGET_REGION="us-gov-west-1"
+elif [[ ${REGION} == "us-gov-west-1" ]]; then
+  PROFILE=${ACCOUNT_NAME}-gov
+  TARGET_REGION="us-gov-east-1"
+elif [[ ${REGION} == "us-east-1" ]]; then
+  PROFILE=${ACCOUNT_NAME}
+  TARGET_REGION="us-west-1"
+elif [[ ${REGION} == "us-west-1" ]]; then
+  PROFILE=${ACCOUNT_NAME}
+  TARGET_REGION="us-east-1"
+else
+  >&2 echo ERROR: Could not determine target region from source region \"${REGION}\"
+  exit -1
+fi
+
+# Fix the accounts that we foolish prepended 'afs-' to.
+PROFILE=${PROFILE##afs-}
+
+# Find the target region key ARN, since we can't use aliases here
+KMS_KEY_ID=$(aws --profile ${PROFILE} --region ${TARGET_REGION} kms list-aliases | jq -r '.Aliases[] | select(.AliasName=="alias/aws/ebs") | .TargetKeyId')
+KMS_ARN=$(aws --profile ${PROFILE} --region ${TARGET_REGION} kms describe-key --key-id ${KMS_KEY_ID} | jq -r '.KeyMetadata.Arn')
+
+tmpfile=$(mktemp /tmp/create_dlm_policy.XXXXXXX)
+cat > ${tmpfile} <<EOF
+{
+    "PolicyType": "IMAGE_MANAGEMENT",
+    "ResourceTypes": [
+        "INSTANCE"
+    ],
+    "TargetTags": [
+        {
+            "Key": "Snapshot",
+            "Value": "Daily"
+        }
+    ],
+    "Schedules": [
+        {
+            "Name": "XDR AMI Backups with Cross Region Replication",
+            "CopyTags": true,
+            "TagsToAdd": [
+                {
+                    "Key": "SnapshotPolicy",
+                    "Value": "Daily"
+                },
+                {
+                    "Key": "SnapshotCreator",
+                    "Value": "XDR AMI Backups with Cross Region Replication"
+                }
+            ],
+            "VariableTags": [
+                {
+                    "Key": "instance-id",
+                    "Value": "\$(instance-id)"
+                }
+            ],
+            "CreateRule": {
+                "Interval": 24,
+                "IntervalUnit": "HOURS",
+                "Times": [
+                    "03:30"
+                ]
+            },
+            "RetainRule": {
+                "Count": 2
+            },
+            "CrossRegionCopyRules": [
+                {
+                    "TargetRegion": "${TARGET_REGION}",
+                    "Encrypted": true,
+                    "CmkArn": "${KMS_ARN}",
+                    "CopyTags": true,
+                    "RetainRule": {
+                        "Interval": 2,
+                        "IntervalUnit": "DAYS"
+                    }
+                }
+            ]
+        }
+    ],
+    "Parameters": {
+        "NoReboot": true
+    }
+}
+EOF
+
+aws --profile ${PROFILE} --region ${REGION} dlm create-lifecycle-policy \
+  --execution-role-arn arn:${PARTITION}:iam::${ACCOUNT}:role/dlm-lifecycle-role \
+  --description "XDR AMI Backups with Cross Region Replication" \
+  --state ENABLED \
+  --tags '{ "Name": "XDR-AMI-XRegion", "SnapshotPolicy": "Daily" }' \
+  --policy-details file://${tmpfile}
+
+rm $tmpfile

+ 18 - 0
base/account_standards/bin/get_current_dlm_policies

@@ -0,0 +1,18 @@
+#! /bin/bash
+# Gets the current dlm policies, if any.
+#
+# WARNING: THIS IS RUN DURRING A 'PLAN' STEP. Do not make changes. Read-only in this script.
+
+# TODO: Pass these in
+PROFILE=mdr-test-c2-gov
+REGION=us-gov-east-1
+ACCOUNT=738800754746
+
+POLICIES=$(aws --profile ${PROFILE} --region ${REGION} dlm get-lifecycle-policies)
+
+# Extracts the policy IDs of IMAGE_MANAGEMENT policye
+POLICY_IDS=$(echo $POLICIES | jq -r '[.Policies[] | select(.PolicyType=="IMAGE_MANAGEMENT") | select(.Tags.SnapshotPolicy=="Daily")] | first | .PolicyId')
+
+# jq ensures a safe json output
+# Will return 'null' if no matching policy
+jq -n --arg policy $POLICY_IDS '{ "PolicyId": $policy }'

+ 35 - 1
base/account_standards/iam.tf

@@ -175,7 +175,10 @@ resource "aws_iam_role_policy" "dlm_lifecycle" {
             "ec2:CreateSnapshot",
             "ec2:DeleteSnapshot",
             "ec2:DescribeVolumes",
-            "ec2:DescribeSnapshots"
+            "ec2:DescribeSnapshots",
+            "ec2:DescribeImages",
+            "ec2:DescribeInstances",
+            "ec2:DescribeImageAttribute"
          ],
          "Resource": "*"
       },
@@ -184,7 +187,38 @@ resource "aws_iam_role_policy" "dlm_lifecycle" {
          "Action": [
             "ec2:CreateTags"
          ],
+         "Resource": [
+           "arn:${var.aws_partition}:ec2:*::snapshot/*",
+           "arn:${var.aws_partition}:ec2:*::image/*"
+         ]
+      },
+      {
+         "Effect": "Allow",
+         "Action": "ec2:DeleteSnapshot",
          "Resource": "arn:${var.aws_partition}:ec2:*::snapshot/*"
+      },
+      {
+         "Effect": "Allow",
+         "Action": [
+            "ec2:ResetImageAttribute",
+            "ec2:DeregisterImage",
+            "ec2:CreateImage",
+            "ec2:CopyImage",
+            "ec2:ModifyImageAttribute"
+         ],
+         "Resource": "*"
+      },
+      {
+         "Effect": "Allow",
+         "Action": [
+            "kms:ReEncrypt*",
+            "kms:GenerateDataKey*",
+            "kms:Encrypt",
+            "kms:DescribeKey",
+            "kms:Decrypt",
+            "kms:Create*"
+         ],
+         "Resource": "*"
       }
    ]
 }

+ 1 - 0
base/account_standards/vars.tf

@@ -36,6 +36,7 @@ variable "log_group_name" {
 # ----------------------------------
 # Below this line are variables inherited from higher levels, so they
 # do not need to be explicitly passed to this module.
+variable "account_name" { type = string }
 variable "binaries_bucket" { type = string}
 variable "binaries_key" { type = string}
 variable "is_legacy" { type = bool }

+ 4 - 2
submodules/kms/ebs-key/main.tf

@@ -77,7 +77,8 @@ data "aws_iam_policy_document" "kms_policy" {
         identifiers = concat(
           var.key_user_arns, 
           [ "arn:${var.aws_partition}:iam::${var.aws_account_id}:role/user/mdr_terraformer",
-            "arn:${var.aws_partition}:iam::${var.aws_account_id}:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling"
+            "arn:${var.aws_partition}:iam::${var.aws_account_id}:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling",
+            "arn:${var.aws_partition}:iam::${var.aws_account_id}:role/dlm-lifecycle-role"
           ] 
         )
       }
@@ -100,7 +101,8 @@ data "aws_iam_policy_document" "kms_policy" {
           var.key_attacher_arns, 
           [  
             "arn:${var.aws_partition}:iam::${var.aws_account_id}:role/user/mdr_terraformer",
-            "arn:${var.aws_partition}:iam::${var.aws_account_id}:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling"
+            "arn:${var.aws_partition}:iam::${var.aws_account_id}:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling",
+            "arn:${var.aws_partition}:iam::${var.aws_account_id}:role/dlm-lifecycle-role"
           ]
         )
       }