Improve Vault External Secret Syntax
Background
The Vault external secret configuration options are great, but we’ve found that the secret path syntax is incredibly confusing.
The <path>/<subkey>@<engine>
formatting goes against a lot of Vault principals and is really confusing for users to understand. No other Vault library or client formats requests like this and it frankly doesn’t make a whole lot of sense:
- Using
/
as a delimiter between path and subkey is suboptimal because paths can obviously contain slashes. This makes your path look like a folder and subkey like a secret. - Appending the engine at the end violates the hierarchy of Vault’s architecture and the @ is something that is used in other clients to specify a version or subkey, not an engine.
- There is no way, documented at least, to retrieve a specific version of a secret. It may be possible to append
?version=
to the path and have Gitlab pass that through as part of the URL, but that makes the format even messier:path/to/secret/subkey?version=2@secret
.
Current syntax
secrets:
DATABASE_PASSWORD:
vault: production/db/password@ops # translates to secret `ops/data/production/db`, field `password`
Proposal
Gitlab should follow the example set by Hashicorp in the vault kv
subcommand and break out each field. This is a pretty common pattern that is also used in the Kubernetes ExternalSecrets
operator as well.
# vault kv get -mount=ops -field=password production/db
# would look something like:
secrets:
DATABASE_PASSWORD:
vault:
mount: ops
path: production/db
field: password
# This would also make requesting a specific version easy as well:
secrets:
DATABASE_PASSWORD:
vault:
mount: ops
path: production/db
field: password
version: 2
There are a lot of reasons this is useful. First, the code has to make zero assumptions about what path to read in Vault. We can very easily make a GET
request to v1/{mount}/data/{path}(?version={version})
and grab data.{field}
from the response. We also aren't inventing any kind of unique string syntax that users have to learn. Finally, it is backwards compatible; you could easily check if secrets.{name}.vault
is a string or object and react appropriately from there.