Checking IAM policy length using Terraform
Terraform, apart from being an incredible tool overall, is very good for managing IAM policies: it's straightforward, easy to maintain, and very flexible. I've seen a lot of guides on using Terraform for managing infrastructure in general and IAM specifically, but I haven't seen any implementing an important but not very common feature — validating IAM policy length. So I decided to create one.
The problem¶
I'm sure 90% of IAM policies look like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3Access",
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": "*"
}
]
}
Which may be totally fine based on your needs. This policy, however, is not very close to the Well-Architected Framework's "least privilege" design principle1. Keeping with the tone of WAF, the policy should be narrowed to only necessary actions on necessary resources, e.g.:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3Access",
"Effect": "Allow",
"Action": [
"s3:Describe*",
"s3:Get*",
"s3:List*",
],
"Resource": [
"arn:aws:s3:::my-cool-bucket",
"arn:aws:s3:::my-cool-bucket/*"
]
}
]
}
Narrowing this policy reduced its functionality but not its size. If we further fine-tune the functionality, the size of the policy will increase. In medium and small-sized projects, this may not be an issue, but in large projects with many users, resources, accounts, and restrictions, this may become problematic. The reason is AWS's limit for the size of policies: 6144 non-whitespace characters. Splitting one large policy into several smaller ones may work until you reach the hard limit of 20 policies per role2.
Monitoring the size of the policy¶
But this post is not about how to deal with bloated policies (splitting them up, using wildcards, etc.); it's about the ability to monitor the size of a single policy using Terraform.
Terraform has a function length
3, which, when provided with a string, will
calculate the number of characters in it. So, how do we get a policy in the
form of a string? This can be achieved by using the aws_iam_policy_document
data source:
data "aws_iam_policy_document" "example" {
statement {
sid = "AllowS3Access"
effect = "Allow"
actions = [
"s3:Describe*",
"s3:Get*",
"s3:List*"
]
resources = [
"arn:aws:s3:::my-cool-bucket",
"arn:aws:s3:::my-cool-bucket/*"
]
}
}
This policy can be referenced in the aws_iam_policy
resource:
resource "aws_iam_policy" "example" {
name = "example-allow-s3-access"
policy = data.aws_iam_policy_document.example.json
}
And finally, the cherry on top: validating if the policy actually fits:
resource "aws_iam_policy" "example" {
name = "example-allow-s3-access"
policy = data.aws_iam_policy_document.example.json
lifecycle {
precondition {
condition = length(replace(replace(data.aws_iam_policy_document.example.json, " ", ""), "\n", "")) < 6144
error_message = "Length of the policy is more than 6144 symbols, current length is: ${length(replace(replace(data.aws_iam_policy_document.example.json, " ", ""), "\n", ""))}"
}
}
}
Let me elaborate a bit on what's happening here:
- The
lifecycle
block defines the behavior of the resource. In this case, we defineprecondition
, a condition that will be evaluated before Terraform attempts to create the resource. - In
condition
, we calculate the length of the policy string (data.aws_iam_policy_document.example.json
). First, we remove newline (\n
) characters using thereplace
4 function (inner), then we remove whitespace characters (outerreplace
function), and then we count the number of characters in the remaining string using thelength
function. - If the amount is greater than the provided limit (6144), we show an error message.
╷
│ Error: Resource precondition failed
│
│ on main.tf line 123, in resource "aws_iam_policy" "example":
│ 123: condition = length(replace(replace(data.aws_iam_policy_document.example.json, " ", ""), "\n", "")) < 6144
│ ├────────────────
│ │ data.aws_iam_policy_document.example.json is "[REDACTED]"
│ │ var.policy_length_limit is 6144
│
│ Length of the policy is more than 6144 symbols, current length is: 6150
╵
This way, we can catch the situation when the policy exceeds the allowed length during the plan stage instead of during the apply stage.