3

I have the following resource:

resource "aws_iam_user_policy" "ses_send_policy" {
  count       = var.enabled ? 1 : 0
  name_prefix = var.user_policy_name_prefix
  user        = aws_iam_user.ses_smtp_user[0].name

  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ses:SendEmail",
                "ses:SendRawEmail"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "ses:FromAddress": [
                        "${var.user_email_address}"
                    ]
                }
            }
        }
    ]
}
EOF
}

I'd like to ask how to make the "Condition" block in the policy optional and based on a bool type variable? I want to have it only if var.condition = true.

Jacek
  • 53
  • 2
  • 2
  • 4

2 Answers2

4

In the documentation for aws_iam_user_policy at the time of this answer the main usage example shows setting policy like this:

  # Terraform's "jsonencode" function converts a
  # Terraform expression result to valid JSON syntax.
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "ec2:Describe*",
        ]
        Effect   = "Allow"
        Resource = "*"
      },
    ]
  })

Notice that it recommends using the jsonencode function to produce the entire value, rather than trying to construct JSON from parts via template concatenation, since that ensures that the result will always be valid JSON syntax.

It also has the benefit that you can use any expressions you need to make dynamic decisions about the data structure. In your case you already have a dynamic reference to the user's email address from a variable, so let's start by translating what you have into a form more like the example in the documentation:

  policy = jsonencode({
    Version = 2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "ses:SendEmail",
          "ses:SendRawEmail",
        ]
        Resource = "*"
        Condition = {
          StringEquals = {
            "ses:FromAddress" = [
              var.user_email_address,
            ]
          }
        }
      }
    ]
  })

Notice that the value is now written in Terraform's own expression syntax, rather than JSON syntax. Terraform will construct the valid JSON syntax itself as part of evaluating the jsonencode function call.

Your new requirement is to omit Condition entirely in certain cases. To describe that requirement as a Terraform expression requires merging the object which describes the parts that are always present (Effect, Action, and Resource) with another expression which describes the optional parts.

For example:

  policy = jsonencode({
    Version = 2012-10-17"
    Statement = [
      merge(
        {
          Effect = "Allow"
          Action = [
            "ses:SendEmail",
            "ses:SendRawEmail",
          ]
          Resource = "*"
        },
        coalesce(var.condition ? {
          Condition = {
            StringEquals = {
              "ses:FromAddress" = [
                var.user_email_address,
              ]
            }
          }
        } : null, {}),
      )
    ]
  })

What I've changed here is a bit subtle and perhaps hard to see with all of the other content that didn't change, so here's a cut down version with some of the previous elements replaced by comments just to make the new content more prominent:

  policy = jsonencode({
    Version = 2012-10-17"
    Statement = [
      merge(
        {
          # (common attributes here)
        },
        coalesce(var.condition ? {
          # (conditional attributes here)
        } : null, {}),
      )
    ]
  })

The merge function takes multiple objects and returns a single object containing the elements from all of them taken together. In this case, the second argument to merge is a more complex expression which produces either an object with a Condition attribute or an empty object depending on the condition value. Merging an empty object into an object changes nothing about the result, and so in this case the condition controls whether that second argument will contribute any attributes at all.

Martin Atkins
  • 2,508
  • 20
  • 20
  • Great explanation, thank you. – Jacek Feb 01 '22 at 20:55
  • One question, the code works but the policy it in reverted order, why is that? – Jacek Feb 02 '22 at 07:41
  • I'm not sure what you mean by "reverted order", but perhaps you've noticed that `jsonencode` will always sort the object attributes by their names because an object in is an _unordered_ collection of attributes, and so `jsonencode` has to decide some order itself. – Martin Atkins Feb 02 '22 at 16:27
1

You can use a aws_iam_policy_document resource, and make condition block dynamic.

Eric Viera
  • 11
  • 2