Terraform AWS IAM Error — “MalformedPolicyDocument: Policy document should not specify a principal”

Siddharth Deshpande
3 min readSep 30, 2020

--

I’m still a bit lost when it comes to understanding the nitty-gritty of IAM and I was a little stumped when I encountered this error trying to deploy a CodeBuild project with Terraform:

aws_iam_policy.codebuild: Modifying... [id=arn:aws:iam::<AWS Account ID>:policy/bot-dev-CodeBuild-policy]Error: Error updating IAM policy arn:aws:iam::<AWS Account ID>:policy/bot-dev-CodeBuild-policy: MalformedPolicyDocument: Policy document should not specify a principal.
status code: 400, request id: ...

Here is the relevant fragment of the policy document (ref: Terraform doc) I was using:

data "aws_iam_policy_document" "codebuild" {  statement {
sid = "EC2NICperms"
effect = "Allow"
actions = [
"ec2:CreateNetworkInterfacePermission"
]
resources = ["arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:network-interface/*"] condition {
test = "StringEquals"
variable = "ec2:Subnet"
values = [
var.pubnet1,
var.pubnet2
]
}
principals {
type = "Service"
identifiers = ["codebuild.amazonaws.com"]
}
}

This brings us to a fundamental (mis)understanding of two different types of IAM policies:

  • Identity Based: attached to an IAM user, group, or role.
  • Resource based: attached to AWS resources like S3 buckets, SQS queues etc.

From the AWS doc on principals:

The Principal element specifies the user, account, service, or other entity that is allowed or denied access to a resource.

In simple terms, if access control defines Who has access to What, then the Principal is the Who, and the Resource is the What.

In our case, the principal desired is exactly what is specified in the policy document, i.e. the AWS CodeBuild service.

An IAM role consists of a set of rules to allow or deny access to specified resources, i.e. an IAM policy and who is allowed to invoke the permissions listed in that IAM policy, i.e., a Trust relationship.

When you log into the IAM console and view a role, you typically see something like this:

AWS IAM Console Role Details View

A working IAM policy would look something like this:

As you can see, there is no “Principal” here. From this doc:

You cannot use the Principal element in an IAM identity-based policy. You can use it in the trust policies for IAM roles and in resource-based policies.

A role is an IAM identity, therefore we cannot use “Principal” in its policy. So how do we specify that only AWS CodeBuild service has access to the action and resource specified in the above policy? As the second quoted sentence above says, through a “Trust Policy” (seen in the console under “Trust relationships”). This is the Terraform version:

data “aws_iam_policy_document” “sts_codebuild” {
statement {
sid = “STSassumeRole”
effect = “Allow”
actions = [“sts:AssumeRole”]
principals {
type = “Service”
identifiers = [“codebuild.amazonaws.com”]
}
}
}

STS is Amazon’s Simple Token Service, which generates temporary credentials that allow the specified principals to “Assume Role”, which means take on the permissions mentioned in the IAM policies attached to this role. It is somewhat like Linux’s sudo mechanism, where you have users who are able to temporarily elevate privileges, and what privileges they elevate to are also defined in the sudoers file.

We can generate a role starting with the trust policy:

resource “aws_iam_role” “codebuild” {
name = “custom-codebuild-role”
assume_role_policy = data.aws_iam_policy_document.sts_codebuild.json
}

And then attach the IAM policies to it:

resource “aws_iam_policy” “codebuild” {
name = “custom-CodeBuild-policy”
policy = data.aws_iam_policy_document.codebuild.json
}
resource “aws_iam_role_policy_attachment” “codebuild” {
role = aws_iam_role.codebuild.name
policy_arn = aws_iam_policy.codebuild.arn
}

This fixes the “MalformedPolicyDocument” error and allows Terraform apply to run successfully, generating an IAM policy and trust relationship and an IAM role associated with them. This role can then be invoked by the principal specified (CodeBuild) in this case, to do whatever it needs to do, as long as you have included the appropriate permissions in the IAM policy document.

--

--