slide

Nonsensitive function fails in terraform

Ned Bellavance
3 min read

Cover

When I was trying to work with a module in Terraform, I came across an interesting issue. The module in question created an Azure AD service principal and optionally a secret for the service principal.

I wanted to print the service principal application id and secret to the screen so I could use it for testing. Naturally, the output for the service principal secret (client_secret) had been marked as sensitive, so in order to print it to the terminal window I would need to use the nonsensitive function.

output "client_secret" {
  value = nonsensitive(module.sp.client_secret)
}

But it didn’t work! I got the following error message:

│ Error: Output refers to sensitive values
│   on main.tf line 33:
│   33: output "client_secret" {
│ To reduce the risk of accidentally exporting sensitive data that was intended to be only internal, Terraform requires that any root module output containing
│ sensitive data be explicitly marked as sensitive, to confirm your intent.
│ If you do intend to export this data, annotate the output value as sensitive by adding the following argument:
│     sensitive = true

Super weird right? A quick peek into the module showed me what was happening. The creation of the service principal secret is optional. You might want to create a certificate instead. The code creating the secret looks like this, note the conditional expression for the count meta-argument:

resource "azuread_service_principal_password" "main" {
  count                = var.enable_service_principal_certificate == false ? 1 : 0
  service_principal_id = azuread_service_principal.main.object_id
  rotate_when_changed = {
    rotation = time_rotating.main.id
  }
}

The count meta-argument means the resulting data structure will be a list of azuread_service_principal_password instances, and the address of the secret value would be azuread_service_principal_password.main[0].value.

Looking at the actual output for client_secret that’s not what we see:

output "client_secret" {
  description = "Password for service principal."
  value       = azuread_service_principal_password.main.*.value
  sensitive   = true
}

The expression azuread_service_principal_password.main.*.value is going to return a list of the values in the value attribute for all instances of the azuread_service_principal_password.main resource.

The splat expression above is actually shorthand for this: [ for k in azuread_service_principal_password.main : k.value ]

In our case, that will be a list with a single element, the client secret. For example, if the client secret is 1234567890, the expression will evaluate to ["1234567890"]. It’s a subtle but critical difference.

By setting the output’s sensitive argument to true, the list is set as sensitive. The client secret value inside the list is already set as sensitive. When I use the nonsensitive function to remove the sensitivity marker from the list, the sensitivity marker on the value inside the list remains. And thus I got the confusing error.

To prove that was the issue, I updated the value for my output to be the following:

output "client_secret" {
  value = nonsensitive(module.sp.client_secret[0])
}

And sure enough the error went away! The nonsensitive function was now being applied to the value inside the list and not the list as a whole.

A similar situation can arise when you’re dealing with maps. Check out this excellent post by Chad Quinlan that explains the necessary workaround.

If you’re in a similar situation, I hope this post helped you out!