Terraform jsonencode: Fixing List Interpolation Errors in IAM Policies
Terraform list variables can't be interpolated directly into JSON string templates — the type mismatch causes 'string required' errors. jsonencode() converts HCL tuples to valid JSON arrays. The better solution is aws_iam_policy_document, which handles type conversions automatically and produces cleaner, validated HCL.
The problem: list variables in JSON string templates
When defining an AWS IAM Role with an OIDC provider in Terraform, interpolating a list variable directly into a JSON heredoc policy causes a type error:
Cannot include the given value in a string template: string required.
The ${...} interpolation syntax expects a string. local.test = ["123"] is a tuple (Terraform's term for a list). Terraform can't automatically convert complex types into strings in JSON contexts.
Even if the types matched, the output would be wrong: "${local.test}" renders as HCL-style ["123"] rather than JSON-encoded ["123"]. Structurally identical to the eye, but when embedded in a heredoc string, the surrounding quotes corrupt it into an invalid JSON string.
Heredoc JSON policies can't safely interpolate HCL complex types — use jsonencode or aws_iam_policy_document
ConceptTerraformHeredoc strings in Terraform (<<EOF...EOF) are raw string templates. HCL interpolation inside them converts values to strings using HCL's toString representation, not JSON encoding. Lists, maps, and objects stringify differently in HCL vs JSON. jsonencode() produces JSON-safe output; aws_iam_policy_document generates the entire policy and handles all type conversions.
Prerequisites
- Terraform HCL types
- IAM policy JSON format
- OIDC identity provider
Key Points
- jsonencode(list) converts HCL tuple ["a", "b"] to JSON array ["a","b"].
- Do not wrap jsonencode() in quotes inside a heredoc — it produces a stringified array instead of a JSON array.
- aws_iam_policy_document is the preferred approach — it validates policy structure and handles types natively.
- terraform validate catches type errors before apply, but not JSON semantic errors in heredoc policies.
Fix 1: Use jsonencode
condition {
StringLike = {
"oidc.circleci.com/org/${var.circleci_org_id}:sub" = jsonencode(local.test)
}
}
jsonencode(local.test) converts ["123"] (HCL tuple) to the JSON array string ["123"].
Do not wrap it in quotes:
# Correct: renders as ["123"]
"sub": ${jsonencode(local.test)}
# Wrong: renders as "[\"123\"]" — a stringified array, not a JSON array
"sub": "${jsonencode(local.test)}"
Full corrected example:
resource "aws_iam_role" "oidc_role" {
name = "${var.environment}-${var.project}-circleci-oidc-role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::${var.aws_account_id}:oidc-provider/oidc.circleci.com/org/${var.circleci_org_id}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.circleci.com/org/${var.circleci_org_id}:aud": "${var.circleci_project_id}"
},
"StringLike": {
"oidc.circleci.com/org/${var.circleci_org_id}:sub": ${jsonencode(local.test)}
}
}
}]
}
EOF
}
Fix 2: Use aws_iam_policy_document (preferred)
For complex policies, aws_iam_policy_document handles type conversions automatically and is less error-prone than heredoc JSON:
data "aws_iam_policy_document" "oidc_assume" {
statement {
effect = "Allow"
principals {
type = "Federated"
identifiers = ["arn:aws:iam::${var.aws_account_id}:oidc-provider/oidc.circleci.com/org/${var.circleci_org_id}"]
}
actions = ["sts:AssumeRoleWithWebIdentity"]
condition {
test = "StringEquals"
variable = "oidc.circleci.com/org/${var.circleci_org_id}:aud"
values = [var.circleci_project_id]
}
condition {
test = "StringLike"
variable = "oidc.circleci.com/org/${var.circleci_org_id}:sub"
values = local.test # list variable works directly — no jsonencode needed
}
}
}
resource "aws_iam_role" "oidc_role" {
name = "${var.environment}-${var.project}-circleci-oidc-role"
assume_role_policy = data.aws_iam_policy_document.oidc_assume.json
}
aws_iam_policy_document accepts HCL lists natively in values. It also catches invalid condition operator names and missing required fields at plan time — heredoc JSON doesn't.