terraform-provider-azurerm icon indicating copy to clipboard operation
terraform-provider-azurerm copied to clipboard

Support for enumerating all versions of a keyvault secret

Open ben-childs-docusign opened this issue 1 year ago • 8 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

Community Note

  • Please vote on this issue by adding a :thumbsup: reaction to the original issue to help the community and maintainers prioritize this request
  • Please do not leave comments along the lines of "+1", "me too" or "any updates", they generate extra noise for issue followers and do not help prioritize the request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment and review the contribution guide to help.

Description

A prior issue was filed to support this but it was closed with only a partial implementation: https://github.com/hashicorp/terraform-provider-azurerm/issues/21283

It is possible to pass the version as a parameter to the secret object however it is still not possible to enumerate all versions of a secret which would be useful in the case that you want to e.g. publish the last 2 versions of a secret automatically.

New or Affected Resource(s)/Data Source(s)

azurerm_key_vault_secret

Potential Terraform Configuration

data "azurerm_key_vault_secret_versions" "sourced_secret_versions" {
  name         = secret_name
  key_vault_id = data.azurerm_key_vault.source.id
}

data "azurerm_key_vault_secret" "sourced_secret" {
  name         = secret_name
  version_id   = data.azurerm_key_vault_secret_versions.sourced_secret_versions.first
  key_vault_id = data.azurerm_key_vault.source.id
}

References

https://github.com/hashicorp/terraform-provider-azurerm/issues/21283

ben-childs-docusign avatar Sep 10 '24 22:09 ben-childs-docusign

@ben-childs-docusign what attributes would you expect to be returned by the data source azurerm_key_vault_secret_versions? The API call in question would be Get Secret Versions and the response includes a lot more than just the secret ID for each version.

The resulting list of versions would probably be a list of maps ordered by creation date. Each map would at least have the id key, which would make your code look more like this:

data "azurerm_key_vault_secret_versions" "sourced_secret_versions" {
  name         = secret_name
  key_vault_id = data.azurerm_key_vault.source.id
}

data "azurerm_key_vault_secret" "sourced_secret" {
  name         = secret_name
  version_id   = data.azurerm_key_vault_secret_versions.sourced_secret_versions[0].id
  key_vault_id = data.azurerm_key_vault.source.id
}

What other keys would you expect to find in the map for each secret version? I would think you would want enabled status and creation date. What do you think?

Your special attribute first has some ambiguity to it. Does first refer to the first version of the secret ever created, or does it refer to the most recent version of the secret? Do you think we need the first attribute or would latest be better?

ned1313 avatar Sep 11 '24 13:09 ned1313

Hi Ned, thanks for taking a look,

I just copied this definition from the prior opened issue which was closed without adding support to list versions. So I didn't give the definition much thought.

For our specific use case we want to be able to list the most recent "n" versions of a secret and I agree it should probably return an array.

Ideally each secret version would include a version id, not before, expiry and enabled flags. I suppose we may as well return all these attributes provided by the api

image

ben-childs-docusign avatar Sep 11 '24 15:09 ben-childs-docusign

it might also be useful to have a parameter to control the max number of results (maxresults) e.g. in our case we would likely only be interested in the last 2-3 versions. The use case for us is staged rollout of automatically renewed certificates in which case we need both the current and prior versions.

ben-childs-docusign avatar Sep 11 '24 15:09 ben-childs-docusign

Thanks for the feedback and clarification! The API does support a max version query value, so that should be simple enough. For the data source arguments, it sounds like we'd want:

  • name
  • key_vault_id
  • results (defaulted to 25)

And for the returned list of secret versions, we would have the attributes:

  • secret_id
  • created_on
  • expired_on
  • updated_on
  • not_before
  • enabled

If all that sounds good, I'll start working on the data source.

ned1313 avatar Sep 11 '24 16:09 ned1313

That sound great thanks Ned! Also just to confirm each secret version should include the actual version id, i guess that would be part of secret_id?

ben-childs-docusign avatar Sep 11 '24 16:09 ben-childs-docusign

It will have the id along with the other attributes as part of each version.

ned1313 avatar Sep 12 '24 13:09 ned1313

The returned ID is the full URI, so I added two attributes:

  • id - the short ID that can be used with the azurerm_key_vault_secret data source.
  • uri - the full URI of the secret, just in case.

ned1313 avatar Sep 13 '24 17:09 ned1313

In the mean time I was able to get a version of this working using external data with the following script:

#/bin/bash
set -e
set -x

# Read input parameters
eval "$(jq -r '@sh "VAULT=\(.vault) SECRET=\(.secret)"')"

# Load secret versions using az cli which returns an array of values in json [ { version info } ... ]
# map id property of each value to the key of the dictionary, e.g. { id => { version info } ... }
# and then json encode the values so { id => "\{ version info \}" ...} since terraform wants a map of string => string
az keyvault secret list-versions --name $SECRET --vault-name $VAULT -o json \
  | jq 'map({(.id): .}) | add' \
  | jq 'with_entries(.value |= @json)'

data "external" "secret_versions" {
  program = ["bash", "get-secret-versions.sh"]
  query = {
    vault  = "vault-name"
    secret = "secret-name"
  }
}

locals {
  secret_versions = {
    for key, value in
    { for key, value in data.external.secret_versions.result : one(regex("secrets/([^/]+/[0-9a-f]+)$", key)) => jsondecode(value) } :
    key => merge(value, {
      name    = split("/", key)[0]
      version = split("/", key)[1]
    }) if value.attributes.enabled
  }
}

ben-childs-docusign avatar Oct 03 '24 17:10 ben-childs-docusign