有没有一种方法可以使用terraform将IAM存储桶策略声明附加到现有声明中?

时间:2020-06-30 13:10:06

标签: terraform

我目前在通过多个其他子帐户访问的中央帐户中有一个集中式S3存储桶。我想通过采用现有的IAM存储桶策略来动态更新每个子帐户的存储桶策略,并附加一条新语句,以允许调用帐户中的IAM角色访问存储桶。

我试图结合使用aws_iam_policy_document和source_json和新语句。但是,所有这些操作将覆盖现有语句。

我想要实现的是采用现有策略json并合并或追加到新语句。任何想法如何实现这一目标?

有人能管理跨帐户s3存储桶策略并动态更新该策略以允许子帐户中的角色进行访问吗?

2 个答案:

答案 0 :(得分:2)

aws_iam_policy_document数据源的source_json参数通过使用策略语句的语句ID(“ sid”)值合并策略语句来工作,因此为了使来自先前JSON的语句出现在结果中,{{ 1}}新语句中的参数必须与源文档中的参数不同。

另一个选择是在Terraform语言本身中更手动地进行转换。这要求直接使用原始语句数据结构,因此,您有责任确保稳健地处理输入并生成有效的IAM策略数据结构。

例如:

sid

然后,您可以使用locals { policy_a = jsondecode(file("${path.module}/policy_a.json")) policy_b = jsondecode(file("${path.module}/policy_b.json")) policy_c = { Version: local.policy_a.Version, Statement: concat( local.policy_a.Statement, local.policy_b.Statement, ), } } 在模块的其他位置生成policy_c的JSON版本。

因为这使用的是jsonencode(local.policy_c),所以结果实际上是将两个语句列表串联在一起,因此您需要确保结果对您自己是明智的:不会通过语句id或任何类似的规范化。

答案 1 :(得分:0)

我遇到了类似的情况,并且解决方法很复杂,涉及外部脚本和null资源。在我的情况下,我使用了多个terraform工作区,每个工作区都有自己的Cloudfront分布,但是所有人都使用S3存储桶作为其中一个起源。对于每个工作区,我需要将工作区的Cloudfront原始访问身份附加到存储桶的策略中,而不替换或破坏该策略中已经存在的条目。

该存储桶是手动创建的(在terraform外部),并将其作为数据资源导入。

我有3个外部脚本(尽管它可能是一个更复杂的脚本)-一个用于创建具有新标识的策略,一个用于创建不带标识的策略(用于destroy),以及一个实际应用创建的策略(由null资源使用,直接调用aws命令)。

resource "aws_cloudfront_origin_access_identity" "s3_identity" {
  comment = terraform.workspace
}

data "external" "append_bucket_policy" {
  program = ["scripts/append_bucket_policy.sh", data.aws_s3_bucket.bucket.id, aws_cloudfront_origin_access_identity.s3_identity.iam_arn]
}                                                   
     
data "external" "cleanup_bucket_policy" {
  program = ["scripts/cleanup_bucket_policy.sh", data.aws_s3_bucket.bucket.id, aws_cloudfront_origin_access_identity.s3_identity.id]
}

locals {
  bucket_identifiers      = compact(split(",", data.external.append_bucket_policy.result["principals"]))
  cleanup_policy_identifiers = compact(split(",", data.external.cleanup_bucket_policy.result["principals"]))
}

data "aws_s3_bucket" "bucket" {
  bucket   = "bucketname"       
}

data "aws_iam_policy_document" "append_policy" {
  statement {
    actions   = ["s3:GetObject"]
    resources = ["${data.aws_s3_bucket.bucket.arn}/*"]

    principals {
      type        = "AWS"
      identifiers = local.bucket_identifiers
    }
  }

  statement {
    actions   = ["s3:ListBucket"]
    resources = [data.aws_s3_bucket.bucket.arn]

    principals {
      type        = "AWS"
      identifiers = local.bucket_identifiers
    }
  }
}


data "aws_iam_policy_document" "cleanup_policy" {
  statement {
    actions   = ["s3:GetObject"]
    resources = ["${data.aws_s3_bucket.bucket.arn}/*"]

    principals {
      type        = "AWS"
      identifiers = local.cleanup_policy_identifiers
    }
  }

  statement {
    actions   = ["s3:ListBucket"]
    resources = [data.aws_s3_bucket.bucket.arn]

    principals {
      type        = "AWS"
      identifiers = local.cleanup_policy_identifiers
    }
  }
}

resource "null_resource" "apply_policy" {
  depends_on = [data.external.append_bucket_policy, data.external.append_bucket_policy, data.aws_iam_policy_document.append_policy, data.external.cleanup_bucket_policy, data.aws_iam_policy_document.cleanup_policy, aws_cloudfront_origin_access_identity.s3_identity]

  provisioner "local-exec" {
    when    = create
    command = "scripts/apply_bucket_policy.sh ${data.aws_s3_bucket.bucket.id} '${data.aws_iam_policy_document.append_policy.json}'"
  }

  provisioner "local-exec" {
    when    = destroy
    command = "scripts/apply_bucket_policy.sh ${data.aws_s3_bucket.bucket.id} '${data.aws_iam_policy_document.cleanup_policy.json}'"
  }
}

append_bucket_policy.sh

#!/bin/bash

set -eo pipefail

# To be called as an external data resource program by terraform
# Usage: append_bucket_policy.sh <bucket name> <principal to append>
#
# Retrieves the current principals in bucket policy for <bucket name>, appends <principal to append>, uniqs the output to remove any duplicates, concats the principals into a comma seperated string, and outputs a json blob that terraform can understand
# In terraform, the variable is split on commas to create a list, which is used in the "identifiers" of an iam policy document resource, and re-applied to the bucket

IFS='
'

# this likely won't work with more complex bucket policies, but it does the job for cloudfront origin access identities
CURRENT=$(aws --region us-west-2 s3api get-bucket-policy --bucket "${1}" | jq --raw-output '.Policy' | jq '.Statement[].Principal.AWS[]')

TEMP=$(for i in ${CURRENT} ; do
  printf "%s\n" "${i},"
done
printf "\"%s\"" "${2},")

NEW=$(echo "${TEMP}" | sort | uniq | tr -d '\n')

jq -n --arg principals "${NEW}" '.principals = $principals' | sed 's/\\\"//g'

cleanup_bucket_policy.sh

#!/bin/bash

set -eo pipefail

IFS='
'

CURRENT=$(aws --region us-west-2 s3api get-bucket-policy --bucket "${1}" | jq --raw-output '.Policy' | jq '.Statement[].Principal.AWS[]')

TEMP=$(for i in ${CURRENT} ; do
  printf "%s\n" "${i},"
done | egrep -v "${2}")

NEW=$(echo "${TEMP}" | sort | uniq | tr -d '\n')

jq -n --arg principals "${NEW}" '.principals = $principals' | sed 's/\\\"//g'

apply_bucket_policy.sh

#!/bin/bash

# apply bucket policy generated by append_bucket_policy.sh

set -eo pipefail

# may fail to apply if the origin access identity was just created, wait a few seconds
sleep 10

RESULT=$(aws --region us-west-2 s3api put-bucket-policy --bucket "${1}" --policy "${2}")

jq -n --arg result "${RESULT}" '.result = $result' | sed 's/\\\"//g'

我为此感到非常抱歉,也许不是100%合适,但过去它对我有用,并可能为您激发一些想法。我也欢迎一种更好的方法!

相关问题