terraform-provider-vault
terraform-provider-vault copied to clipboard
The `vault_kv_secret_v2` resource's `cas` field does not work
Terraform Version
Terraform v1.3.2
on darwin_arm64
+ provider registry.terraform.io/hashicorp/vault v3.12.0
Affected Resource(s)
Terraform Configuration Files
terraform {
required_providers {
vault = {
source = "hashicorp/vault"
version = "3.12.0"
}
}
}
provider "vault" {
address = "http://localhost:8200"
}
resource "vault_kv_secret_backend_v2" "cas_required" {
mount = "/secret"
cas_required = true
}
# Creating this secret fails unexepectedly, despite the `cas` field being set.
resource "vault_kv_secret_v2" "example" {
mount = vault_kv_secret_backend_v2.cas_required.mount
name = "my_secret"
cas = 0
data_json = jsonencode({ my_key = "my_value" })
}
Expected Behavior
When creating a secret in an engine with cas_required set to true, I expected a vault_kv_secret_v2 resource with the cas field set to be created successfully.
Actual Behavior
The vault_kv_secret_v2 resource's creation fails with an error saying the check-and-set parameter is missing.
Steps to Reproduce
-
Deploy a fresh Vault instance:
docker run \ --cap-add=IPC_LOCK \ --detach \ --publish=8200:8200 \ --name=vault \ hashicorp/vault:1.12.2 -
Get the root auth token from startup logs for future use:
# At startup, Vault logs the following line: # Root Token: hvs.0123456789abcdefghijklmno vault_token="$(docker logs vault 2>/dev/null | awk '/Root Token:/ { print $3 }')" -
Write the code above to
main.tf. -
Run Terraform:
terraform init VAULT_TOKEN="$vault_token" terraform applyThis fails with an error saying that the check-and-set field is unset:
╷ │ Error: error writing secret data to /secret/data/my_secret, err=Error making API request. │ │ URL: PUT http://localhost:8200/v1/secret/data/my_secret │ Code: 400. Errors: │ │ * check-and-set parameter required for this call │ │ with vault_kv_secret_v2.example, │ on main.tf line 20, in resource "vault_kv_secret_v2" "example": │ 20: resource "vault_kv_secret_v2" "example" { │ ╵
Investigation
Setting TF_LOG=debug and TERRAFORM_VAULT_LOG_REQUEST_BODY=true shows that the provider sends the following payload to Vault:
{
"cas": 0,
"data": {
"my_key": "my_value"
},
"options": {}
}
The API docs are ambiguous about the position of the cas field. It turns out that putting the cas field inside the options map works:
{
"data": {
"my_key": "my_value"
},
"options": {
"cas": 0
}
}
This can be accomplished by using the vault_kv_secret_v2 resource's options field instead of the cas field:
resource "vault_kv_secret_v2" "example" {
mount = vault_kv_secret_backend_v2.cas_required.mount
name = "my_secret"
options = { cas = 0 }
data_json = jsonencode({ my_key = "my_value" })
}
With this code, the provider sends the following payload:
{
"cas": 0,
"data": {
"my_key": "my_value"
},
"options": {
"cas": 0
}
}
This works as expected.
The code responsible
Digging into the provider's implementation, I found where it sets the cas and options fields: right here.
The provider assumes that the cas and options fields are neighbors, and not that the cas field is inside the options map. My hypothesis is that this mistake is due to the documentation's ambiguity, as detailed in this issue.
References
- https://github.com/hashicorp/vault/issues/18724