Search code examples
kubernetesterraformhashicorp-vaultinfrastructure

Terraform: update resources only when Vault secret data has changed


This should be fairly easy, or I might doing something wrong, but after a while digging into it I couldn't find a solution.

I have a Terraform configuration that contains a Kubernetes Secret resource which data comes from Vault. The resource configuration looks like this:

resource "kubernetes_secret" "external-api-token" {
  metadata {
    name      = "external-api-token"
    namespace = local.platform_namespace
    annotations = { 
      "vault.security.banzaicloud.io/vault-addr" = var.vault_addr
      "vault.security.banzaicloud.io/vault-path" = "kubernetes/${var.name}"
      "vault.security.banzaicloud.io/vault-role" = "reader"
    }   
  }

  data = { 
    "EXTERNAL_API_TOKEN" = "vault:secret/gcp/${var.env}/micro-service#EXTERNAL_API_TOKEN"
  }
}

Everything is working fine so far, but every time I do terraform plan or terraform apply, it marks that resource as "changed" and updates it, even when I didn't touch the resource or other resources related to it. E.g.:

... (other actions to be applied, unrelated to the offending resource) ...


  # kubernetes_secret.external-api-token will be updated in-place
  ~ resource "kubernetes_secret" "external-api-token" {
      ~ data = (sensitive value)
        id   = "platform/external-api-token"
        type = "Opaque"

        metadata {
            annotations      = {
                "vault.security.banzaicloud.io/vault-addr" = "https://vault.infra.megacorp.io:8200"
                "vault.security.banzaicloud.io/vault-path" = "kubernetes/gke-pipe-stg-2"
                "vault.security.banzaicloud.io/vault-role" = "reader"
            }
            generation       = 0
            labels           = {}
            name             = "external-api-token"
            namespace        = "platform"
            resource_version = "160541784"
            self_link        = "/api/v1/namespaces/platform/secrets/external-api-token"
            uid              = "40e93d16-e8ef-47f5-92ac-6d859dfee123"
        }
    }

Plan: 3 to add, 1 to change, 0 to destroy.

It is saying that the data for this resource has been changed. However the data in Vault remains the same, nothing has been modified there. This update happens every single time now.

I was thinking on to use the ignore_changes lifecycle feature, but I assume this will make any changes done in Vault secret to be ignored by Terraform, which I also don't want. I would like the resource to be updated only when the secret in Vault was changed.

Is there a way to do this? What am I missing or doing wrong?


Solution

  • You need to add in the Terraform Lifecycle ignore changes meta argument to your code. For data with API token values but also annotations for some reason Terraform seems to assume that, that data changes every time a plan or apply or even destroy has been run. I had a similar issue with Azure KeyVault.

    Here is the code with the lifecycle ignore changes meta argument included:

    resource "kubernetes_secret" "external-api-token" {
      metadata {
        name      = "external-api-token"
        namespace = local.platform_namespace
        annotations = { 
          "vault.security.banzaicloud.io/vault-addr" = var.vault_addr
          "vault.security.banzaicloud.io/vault-path" = "kubernetes/${var.name}"
          "vault.security.banzaicloud.io/vault-role" = "reader"
        }   
      }
    
      data = { 
        "EXTERNAL_API_TOKEN" = "vault:secret/gcp/${var.env}/micro-service#EXTERNAL_API_TOKEN"
      }
    
    lifecycle {
        ignore_changes = [
          # Ignore changes to data, and annotations e.g. because a management agent
          # updates these based on some ruleset managed elsewhere.
          data,annotations,
        ]
      }
    }

    link to meta arguments with lifecycle:

    https://www.terraform.io/language/meta-arguments/lifecycle