Skip to content

Instantly share code, notes, and snippets.

@ThomasBuchinger
Last active August 29, 2022 18:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ThomasBuchinger/d1bdea179959cfcc89a73224ee79562d to your computer and use it in GitHub Desktop.
Save ThomasBuchinger/d1bdea179959cfcc89a73224ee79562d to your computer and use it in GitHub Desktop.
create-or-update Vault-Secret in Terraform

Secrets

Unfortunately the secret handling in this module is quite complex. The problem is that:

  • The provider cannot update individual values in a secret, it always replaces the whole thing
  • You can use a datasource in terraform to read the current secret and merge it with the new data
  • A datasource cannot be optional. a secret mathing the name MUST exist in vault

Solution:

  • Compute all the paths where secrets should be stored
  • Check if a secret with that name already exists
  • If a secret with that name exists, define a datasource for it
  • Merge the updated keys with the existing keys from the datasource (or an empty map)
  • Replace the entire secret with the updated secret values
variable "vault_namespaces" {
type = list(string)
default = ["ns1", "ns2"]
}
locals {
number_of_vault_secrets = var.use_vault ? length(var.vault_k8s_namespaces) : 0
vault_secret_name = "mysecret"
vault_path_formatstr = "/path/to/%s"
# Compute the vault path for all secrets, with and without the actual secret name
# e.g. /path/to/<ns> and /path/to/<ns>/<secret_name>
desired_paths_without_secret_name = [for ns in var.vault_namespaces : format(local.vault_path_formatstr, ns) ]
desired_paths = [for path in local.desired_paths_without_secret_name : join("/", [path, local.vault_secret_name]) ]
}
# Get all the secrets in a particular namespace
data "vault_kv_secrets_list_v2" "secret_list" {
for_each = local.desired_paths_without_secret_name
mount = "secrets"
name = each.key
}
# Check which paths already have a secret with our name
locals {
existing_paths_without_secret_name = compact([for s in data.vault_kv_secrets_list_v2.secret_list : contains([for n in s.names : basename(n)], local.vault_secret_name) ? s.name : "" ])
}
# compute full name os existing secrets
locals {
existing_paths = [for path in local.existing_paths_without_secret_name : join("/",[path, local.vault_secret_name]) ]
}
# Get data from existing Secrets
data "vault_kv_secret_v2" "existing_secrets"{
for_each = toset(local.existing_paths)
mount = "secrets"
name = each.key
}
# map existing data to the secrets we want to create (or default to emtpy)
locals {
existing_data_or_default = [
for i, path in local.desired_paths :
contains(local.existing_paths, path)
? data.vault_kv_secret_v2.existing_secrets[path].data_json
: "{}"
]
}
# Store client_secret in vault
resource "vault_kv_secret_v2" "client_secret" {
count = local.number_of_vault_secrets
mount = "secrets"
name = "${local.desired_paths[count.index]}"
data_json = jsonencode(
merge(
jsondecode(local.existing_data_or_default[count.index]),
{
"foo" = "bar"
}
)
)
}
locals {
path = "/path/to/namespace"
name = "secret_name"
}
# Get the List of all Secrets in path
data "vault_kv_secrets_list_v2" "secret_list" {
mount = "secrets"
name = local.path
}
# Check if local.name is in secret_list and get the data
locals {
secret_exists = contains([ for n in data.vault_kv_secrets_list_v2.secret_list.names: basename(n)], local.name)
}
data "vault_kv_secret_v2" "existing_secret"{
count = secret_exists ? 1 : 0
mount = "secrets"
name = "${local.path}/${local.name}"
}
# Save existing data
local {
data = secret_exists ? data.vault_kv_secret_v2.existing_secret[0].data_json : "{}"
}
resource "vault_kv_secret_v2" "client_secret" {
mount = "secrets"
name = "${local.path}/${local.name}"
data_json = jsonencode(merge(jsondecode(local.data), {"foo" = "bar"}))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment