Dino
Dino

Reputation: 1457

Terraform throwing error: creating IAM Role MalformedPolicyDocument, prohibited field Resource

I have the following trust policy:

data "aws_iam_policy_document" "lambda_role_policy" {
  statement {
    sid       = "LightsailFullAccess"
    effect    = "Allow"
    resources = ["arn:aws:lightsail:::*"]
    not_actions = [
      "lightsail:GetInstanceAccessDetails",
      "lightsail:GetRelationalDatabaseMasterUserPassword"
    ]
  }

  statement {
    sid       = "LogsFullAccess"
    effect    = "Allow"
    resources = ["arn:aws:logs:::*"]
    actions   = ["logs:*"]
  }
}

and then I am using a module (https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/modules/iam-assumable-role):

module "iam_assumable_role_custom_trust_policy" {
  source = "git::https://github.com/terraform-aws-modules/terraform-aws-iam//modules/iam-assumable-role?ref=e63dff8de9f6aee7e9ebfbe8c676fa980f5233c0"
  #version = "5.33.0"

  create_role                     = true
  trusted_role_actions            = ["sts:AssumeRole"]
  role_name                       = "LambdaModifyLightsailSnapshotsRole"
  role_description                = "Policy to Create / Delete Lightsail Snapshots"
  create_custom_role_trust_policy = true
  custom_role_trust_policy        = data.aws_iam_policy_document.lambda_role_policy.json
}

When I execute the terraform I get the following error:

Error: creating IAM Role (LambdaModifyLightsailSnapshotsRole): MalformedPolicyDocument: Has prohibited field Resource

Any help resolving would be appreciated. Thanks


As requested please find the plan output below:

  # module.create_instance_snapshot_lambda.data.aws_iam_policy_document.logs[0] will be read during apply
  # (config refers to values not yet known)
 <= data "aws_iam_policy_document" "logs" {
      + id   = (known after apply)
      + json = (known after apply)

      + statement {
          + actions   = [
              + "logs:CreateLogGroup",
              + "logs:CreateLogStream",
              + "logs:PutLogEvents",
            ]
          + effect    = "Allow"
          + resources = (known after apply)
        }
    }
      # module.prune_instance_snapshot_lambda.data.aws_iam_policy_document.logs[0] will be read during apply
  # (config refers to values not yet known)
 <= data "aws_iam_policy_document" "logs" {
      + id   = (known after apply)
      + json = (known after apply)

      + statement {
          + actions   = [
              + "logs:CreateLogGroup",
              + "logs:CreateLogStream",
              + "logs:PutLogEvents",
            ]
          + effect    = "Allow"
          + resources = (known after apply)
        }
    }
     # module.sns.data.aws_iam_policy_document.this[0] will be read during apply
  # (config refers to values not yet known)
 <= data "aws_iam_policy_document" "this" {
      + id                        = (known after apply)
      + json                      = (known after apply)
      + override_policy_documents = []
      + source_policy_documents   = []

      + statement {
          + actions   = [
              + "sns:AddPermission",
              + "sns:DeleteTopic",
              + "sns:GetTopicAttributes",
              + "sns:ListSubscriptionsByTopic",
              + "sns:Publish",
              + "sns:RemovePermission",
              + "sns:SetTopicAttributes",
              + "sns:Subscribe",
            ]
          + effect    = "Allow"
          + resources = [
              + (known after apply),
            ]
          + sid       = "__default_statement_ID"

          + condition {
              + test     = "StringEquals"
              + values   = [
                  + "074217812019",
                ]
              + variable = "AWS:SourceOwner"
            }

          + principals {
              + identifiers = [
                  + "*",
                ]
              + type        = "AWS"
            }
        }
    }
Roles

# module.create_instance_snapshot_lambda.aws_iam_role.lambda[0] will be created
  + resource "aws_iam_role" "lambda" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRole"
                      + Effect    = "Allow"
                      + Principal = {
                          + Service = "lambda.amazonaws.com"
                        }
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + create_date           = (known after apply)
      + force_detach_policies = true
      + id                    = (known after apply)
      + managed_policy_arns   = (known after apply)
      + max_session_duration  = 3600
      + name                  = "createLightSailSnapshots"
      + name_prefix           = (known after apply)
      + path                  = "/"
      + tags_all              = (known after apply)
      + unique_id             = (known after apply)
    }
      # module.create_instance_snapshot_lambda.aws_iam_role_policy_attachment.logs[0] will be created
  + resource "aws_iam_role_policy_attachment" "logs" {
      + id         = (known after apply)
      + policy_arn = (known after apply)
      + role       = "createLightSailSnapshots"
    }
    # module.eventbridge.aws_iam_role.eventbridge[0] will be created
  + resource "aws_iam_role" "eventbridge" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRole"
                      + Effect    = "Allow"
                      + Principal = {
                          + Service = "events.amazonaws.com"
                        }
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + create_date           = (known after apply)
      + force_detach_policies = true
      + id                    = (known after apply)
      + managed_policy_arns   = (known after apply)
      + max_session_duration  = 3600
      + name                  = "default"
      + name_prefix           = (known after apply)
      + path                  = "/"
      + tags                  = {
          + "Name" = "default"
        }
      + tags_all              = {
          + "Name" = "default"
        }
      + unique_id             = (known after apply)
    }
    # module.iam_assumable_role_custom_trust_policy.aws_iam_role.this[0] will be created
  + resource "aws_iam_role" "this" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Effect    = "Allow"
                      + NotAction = [
                          + "lightsail:GetRelationalDatabaseMasterUserPassword",
                          + "lightsail:GetInstanceAccessDetails",
                        ]
                      + Resource  = "arn:aws:lightsail:::*"
                      + Sid       = "LightsailFullAccess"
                    },
                  + {
                      + Action   = "logs:*"
                      + Effect   = "Allow"
                      + Resource = "arn:aws:logs:::*"
                      + Sid      = "LogsFullAccess"
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + create_date           = (known after apply)
      + description           = "Policy to Create / Delete Lightsail Snapshots"
      + force_detach_policies = false
      + id                    = (known after apply)
      + managed_policy_arns   = (known after apply)
      + max_session_duration  = 3600
      + name                  = "LambdaModifyLightsailSnapshotsRole"
      + name_prefix           = (known after apply)
      + path                  = "/"
      + tags_all              = (known after apply)
      + unique_id             = (known after apply)
    }
    # module.prune_instance_snapshot_lambda.aws_iam_role.lambda[0] will be created
  + resource "aws_iam_role" "lambda" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRole"
                      + Effect    = "Allow"
                      + Principal = {
                          + Service = "lambda.amazonaws.com"
                        }
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + create_date           = (known after apply)
      + force_detach_policies = true
      + id                    = (known after apply)
      + managed_policy_arns   = (known after apply)
      + max_session_duration  = 3600
      + name                  = "pruneLightSailSnapshots"
      + name_prefix           = (known after apply)
      + path                  = "/"
      + tags_all              = (known after apply)
      + unique_id             = (known after apply)
    }
    # module.prune_instance_snapshot_lambda.aws_iam_role_policy_attachment.logs[0] will be created
  + resource "aws_iam_role_policy_attachment" "logs" {
      + id         = (known after apply)
      + policy_arn = (known after apply)
      + role       = "pruneLightSailSnapshots"
    }

Upvotes: 0

Views: 160

Answers (1)

Martin Atkins
Martin Atkins

Reputation: 74499

Broadly speaking, AWS IAM has two main different kinds of policy: Identity-based policies, and Resource-based policies.

These two kinds of policy differ in what they are attached to and, conversely, in what they may specify:

  • An identity-based policy attaches to an IAM principal -- e.g. a user or a role -- and each of its statements specifies the resources that the access rules apply to.
  • A resource-based policy attaches to an AWS resource, which is a broad category that contains all of the non-principal objects across all AWS services. Each of its statements specifies the principals that are being granted access to the resource that the policy is attached to.

Syntactically then, the main difference is that identity-based policies have Resource/NotResource elements, while resource-based policies have Principal/NotPrincipal elements. It isn't valid to include a Resource element in a resource-based policy, because the resource is implied to be whatever object the policy was attached to.

An assume-role policy is an awkward special case in this taxonomy, because it's a resource-based policy where the resource is actually a principal! Specifically, it's an IAM role. When assuming a role, the principal is the user or role that is trying to assume the role, and the resource is the role they are trying to assume. An assume role policy is attached to the target role, and confers access to the principal trying to assume it, and so it must be written as a resource-based policy where the Principal elements describe who may assume the role.

The assume role policy you've assigned to module.iam_assumable_role_custom_trust_policy.aws_iam_role.this[0] is invalid in two ways:

  • It's describing access rules for actions lightsail:GetRelationalDatabaseMasterUserPassword, lightsail:GetInstanceAccessDetails, and logs:*, which are not actions that can be taken against an IAM role. The only actions that make sense in this context are the "assume role" family of actions, such as sts:AssumeRole, or sts:AssumeRoleWithWebIdentity.
  • It specifies Resource instead of Principal, and so it isn't stating which principals are allowed to perform the action. This is what the error message is referring to.

There are some missing parts in the information you've shared in your question which mean that I cannot give a full working example, but the shape of the solution here will be:

  • Change all of your assume_role_policy arguments to be valid assume role policies, which means that they must all be resource-based policies with Principal elements describing who can perform actions like sts:AssumeRole against the role.
  • Attach a separate identity-based policy to each of the roles, using either aws_iam_role_policy_attachment (to attach an existing named policy object) or iam_role_policy (to specify an "inline policy" which lives directly on the role and isn't attachable anywhere else). This is the policy that should include Resource elements and describe what actions can be taken once the role has already been assumed. The access rules about lightsail:GetRelationalDatabaseMasterUserPassword etc belong here.

The main takeaways to keep in mind are:

  • Assume-role policies are resource-based policies that describe which principals are allowed to assume the role that the policy belongs to. The only actions that make sense here are the assume-role actions, like sts:AssumeRole.
  • Each role should also have one or more identity-based policies that describe a set of actions that can be taken on some specified resources after someone has successfully assumed the role.

Some of my previous answers to other questions have more context on this topic:

Upvotes: 0

Related Questions