LINUXOR.SK ... open source notes ...

Vault 02 - Secret model

category: solutionz · date: 2024-12-31 · updated: 2026-10-02 · author: LALA

Vault Solution · Previous: Use cases and requirements · Next: High-level design

A secrets store is only as usable as its paths. This article describes how customer secrets were laid out in the Key-Value engine, why the first layout was replaced, and what the replacement cost.

One mount, one rule

All customer secrets live in one KV version 2 mount, kv2/. Every secret is an object with a type and a generated identifier, under the customer it belongs to.

output 1 line
kv2/data/<customerExternalId>/<objectType>/<UUID>
PartMeaning
kv2The mount
dataThe KV v2 prefix under which secret content is read and written
<customerExternalId>The customer, by the identifier every system of the platform already uses
<objectType>What kind of secret this is; one of the nine names below
<UUID>The object, generated by whoever writes it
mermaid
flowchart TD
  kv2["kv2/"] --> c1["customer A"]
  kv2 --> c2["customer B"]
  c1 --> t1["cloudAwsCredentials"]
  c1 --> t2["routingAuthKey"]
  c1 --> t3["certificateCustomer"]
  t1 --> o1["UUID 1<br/>accessKey, secretKey, assumeRoleArn"]
  t2 --> o2["UUID 2<br/>authKey"]
  t2 --> o3["UUID 3<br/>authKey"]
  t3 --> o4["UUID 4<br/>certificateKey, certificateKeyPassphrase"]
  c2 --> t4["webGatewayCredentials"]
  t4 --> o5["UUID 5<br/>adminAccount, adminApiKey, adminPassword"]

Object types

The names are generalised for publication; the structure is as built.

Object typeKeysUsed for
webGatewayCredentialsadminAccount, adminApiKey, adminPasswordPartner administrator access to the secure web gateway service
webGatewayVpnManualprimaryGatewayPsk, primaryGatewayFqdn, secondaryGatewayPsk, secondaryGatewayFqdnVPN credentials the customer created at the gateway service
webGatewayVpnAutothe same fourVPN credentials the orchestrator created there itself
cloudAwsCredentialsaccessKey, secretKey, assumeRoleArnDeploying virtual firewalls in AWS
cloudAzureCredentialsclientId, clientSecret, tenantId, subscriptionIdThe same in Azure
cloudGcpCredentialsprojectId, clientId, privateKeyId, privateKey, serviceAccountEmailThe same in GCP
certificateCustomercertificateKey, certificateKeyPassphrasePrivate key of a customer certificate, for TLS inspection
routingAuthKeyauthKeyBGP and OSPF authentication
ipsecPreSharedKeypreSharedKeyIKE pre-shared key of a tunnel to a third party

What the service model carries

The data model of the platform, the one that describes a customer's services and travels between the portal and the orchestrator, holds two attributes for every object with a secret, and nothing else about it.

AttributeTypeMeaning
vault-data-uuidstringThe <UUID> of the object in Vault
vault-data-versionunsigned integerThe KV v2 version of the secret this configuration was built with

The object type follows from the kind of object the two attributes hang on, and the customer from the order. With those four values the orchestrator can build the path by itself.

The version matters as much as the identifier. KV v2 keeps earlier versions of a secret, and a service configuration that records which version it used can be rolled back together with its secret. A customer who replaces a cloud key and breaks a deployment gets the last working pair back, not the old configuration with the new key.

A separate list in the service model says which object types exist and which keys each has. That list, not a Vault policy, is where the shape of a secret is defined.

The first layout

The first version of the system used a KV version 1 mount, kv/, and paths that mirrored the service model.

SecretKV v1 pathKeys
Web gateway admin credentialskv/<customer-external-id>/breakout/web-gateway/admin-credentialspartner-admin-account, partner-admin-api-key, partner-admin-password
VPN credentials, manualkv/<customer-external-id>/breakout/web-gateway/vpn-settings-manual/<settings-id>primary-gateway-psk, …
VPN credentials, automatickv/<customer-external-id>/breakout/web-gateway/vpn-settings-auto/<settings-id>/<site-name>the same
AWS credentialskv/<customer-external-id>/cloud/aws/credentialsaccess-key, secret-key, assume-role-arn
Customer certificatekv/<customer-external-id>/certificate/customer/<certificate-id>certificate-key, certificate-key-passphrase
BGP key, WAN sidekv/<customer-external-id>/routing/wan/bgp/<…>/<…>one key
OSPF key, LAN side, MD5kv/<customer-external-id>/routing/lan/ospf/<…>/<…>/<…>/md5/<key-id>one key

In that version the service model also held, for every secret attribute, the Vault path of the value. A password field did not contain the password; it contained where the password was.

Why it was replaced

Problem with the first layoutAnswer in the second
A path encoded the place of an object in the service model. Renaming a site or moving a setting changed the path of its secret.A path encodes customer, type and a generated identifier. Nothing in it changes when the service model does.
One customer could have one AWS credential, because the path had no identifier.Any number of objects per type.
Paths of different types had different depths, from four segments to nine. Every new type needed new path logic in three systems and a new policy shape.Every path has the same five segments. A new type is one more name.
No history. An updated secret overwrote the old one.KV v2 versions, referenced from the service model.
The service model was full of attributes that held paths.Two attributes per object.

What the migration cost

The move was not a conversion in place. A new mount was enabled beside the old one, new policies and AppRoles were created with a -kv2 suffix, and the consumers switched over one environment at a time. The old mount, policies and roles stayed until nothing used them.

bash
$ vault secrets enable -version=2 -path=kv2 kv
$ vault policy write orchestrator-policy-kv2 /etc/vault.d/policy/orchestrator-policy-kv2_v03.hcl
$ vault write auth/approle/role/orchestrator-kv2 policies=orchestrator-policy-kv2

One control did not survive. The KV v1 policies restricted which keys a consumer could write, with allowed_parameters:

hcl
path "kv/+/breakout/web-gateway/vpn-settings-auto/+/+" {
  capabilities = ["create", "read", "update", "delete", "list"]
  allowed_parameters = {
    "primary-gateway-psk"    = []
    "primary-gateway-fqdn"   = []
    "secondary-gateway-psk"  = []
    "secondary-gateway-fqdn" = []
  }
}

A KV v2 write wraps the keys in a data object, and parameter constraints only see the top level of a request. They cannot express "these keys and no others" for KV v2, then or now. After the migration, Vault enforces who may write an object of a type; that the object has the right keys is left to the writer and the type list in the service model.

The second cost is in the policies themselves. The KV v2 rules were written as kv2/+/+/<objectType>/+, with a wildcard where data stands in the path. That is shorter than one rule per prefix and grants more than it appears to; Authentication and policies takes it apart.

Reading it today

The KV v2 API is unchanged in Vault 2.1: data/, metadata/, delete/, undelete/, destroy/ and subkeys/ under the mount, and the patch capability for partial updates. Parameter constraints are still not supported on KV v2. Namespaces, which would give each customer a mount of their own, are still an Enterprise feature, so path-and-policy separation inside one mount remains the Community-edition answer.

← solutionz