Hashicorp Vault is a great resource for keeping secrets secret. However version 1 of the KV secrets engine lacked the ability to patch a particular endpoint which made modifying existing values fraught with danger. We mitigate this by utilising the ever wonderful jq alongside a little used Vault flag to perform inline modifcations before similuating writes.

Let’s write some test data and read it back again in sequentially more useful ways:

vault write /secret/test/smchm testkey=testvalue
Success! Data written to: secret/test/smchm

vault read /secret/test/smchm
Key                 Value
---                 -----
refresh_interval    768h
testkey             testvalue

vault read --format=json /secret/test/smchm | jq .
{
  "request_id": "087490c9-ccad-e225-cd17-9f88df12abd5",
  "lease_id": "",
  "lease_duration": 2764800,
  "renewable": false,
  "data": {
    "testkey": "testvalue"
  },
  "warnings": null
}

vault read --format=json /secret/test/smchm | jq .data
{
  "testkey": "testvalue"
}

Now lets patch the data with jq, the resultant patched data is not yet stored in Vault:

vault read --format=json /secret/test/smchm | jq '.data += { "testkey": "newvalue" }' | jq .data
{
  "testkey": "newvalue"
}

We can also add values:

vault read --format=json /secret/test/smchm | jq '.data += { "testkey": "newvalue", "testkey2": "testvalue2" }' | jq .data
{
  "testkey": "newvalue",
  "testkey2": "testvalue2"
}

Now we have the values we want in our JSON object we can write them back to our Vault endpoint. However first we can simulate the write by performing a dry-run using the Vault flag –output-curl-string to make sure everything is as we expect:

vault read --format=json /secret/test/smchm | jq '.data += { "testkey": "newvalue", "testkey2": "testvalue2" }' | jq .data | vault write -output-curl-string /secret/test/smchm - 
curl -X PUT -H "X-Vault-Token: $(vault print token)" -d '{"testkey":"newvalue","testkey2":"testvalue2"}' https://ourvaulthost:8200/v1/secret/test/smchm

which can be a little difficult to grok for endpoints with many values, so lets parse the output with sed and jq:

vault read --format=json /secret/test/smchm | jq '.data += { "testkey": "newvalue", "testkey2": "testvalue2" }' | jq .data | vault write -output-curl-string /secret/test/smchm - | sed 's/^.*{/{/' | sed 's/}.*/}/' | jq .
{
  "testkey": "newvalue",
  "testkey2": "testvalue2"
}

looks good, so finally lets stream our values back to Vault to patch our endpoint and validate the results:

vault read --format=json /secret/test/smchm | jq '.data += { "testkey": "newvalue", "testkey2": "testvalue2" }' | jq .data | vault write /secret/test/smchm -
Success! Data written to: secret/test/smchm

vault read --format=json /secret/test/smchm | jq .data
{
  "testkey": "newvalue",
  "testkey2": "testvalue2"
}

Of course this doesn’t prevent us writing to the wrong endpoint!