The idea of this plugin was born, when I was trying to solve quite specific problem. Long story short, there was the following architecture: CI/CD solution installed in AWS along with Vault cluster (hereafter, central Vault), which stored infrastructural secrets. Aside from them, there was kubernetes cluster, where was deployed dedicated Vault cluster (hereafter, k8s Vault), which provided secrets for workloads deployed in k8s. CI/CD instance could authenticate in central Vault using AWS auth, so there were no problems to run jobs which apply configuration updates to central Vault itself. However, I couldn't find any suitable way how to authenticate CI/CD runner in k8s Vault without exposing any kind of information like role IDs, secret IDs, JWT tokens etc.
That's how I come to the idea of cross-vault authentication:
- k8s Vault cluster was configured to use central Vault's transit engine for unsealing. It was deployed with vault agent sidecar and had been already authenticated in central Vault using kubernetes auth without exposing any credentials or sensitive information ✅
- CI/CD runner could authenticate in central Vault without exposing any credentials or sensitive information ✅
Considering all of the above, I'd decided to prove the concept, that I could use token issued by one Vault cluster to authenticate in another Vault cluster. Well, honestly, I can't. The real workflow is the following:
- Authenticate in central Vault;
- Pass wrapped token/accessor to the central Vault;
- k8s Vault sends unwrap request to central Vault;
- k8s Vault sends lookup request to central Vault for unwrapped token/accessor;
- k8s Vault compares response data with defined parameters (for now it is the entity_id and entity metadata);
- k8s Vault issues token with defined policies, ttl and whatnot;
- Use issued token to log in to k8s Vault;
- PROFIT!
Simply run server in dev mode and pass the -dev-plugin-dir
arg:
vault server -dev -dev-root-token-id=root -dev-plugin-dir=/path/to/dir/with/plugin
vault login root
vault auth enable -path=some-path cva-plugin
# Success! Enabled cva-plugin auth method at: cva/
- Add
plugin_directory
entry to vault server configuration - Reload config if needed or stop/start the server (keep in mind, in case server will be stopped unsealing will be required)
- Register plugin
vault plugin register -sha256=... auth cva-plugin
# Success! Registered plugin: cva-plugin
-
auth/{mount}/config
Available operations:read
,write -f
write -f
parameters:cluster
(string) [Mandatory]namespace
(string) [Enterprise only; default: root]ca_cert
(string)insecure_skip_verify
(bool) [Default: false]
-
auth/{mount}/role
Available operations:list
-
auth/{mount}/role/{name}
Available operations:read
,write
write
parameters:entity_id
(string) [Mandatory]entity_meta
(comma-separated "key"="value")strict_meta_verify
(bool) [Default: false]token_ttl
(go parsable duration: 5s, 10m, 1h etc)token_policies
(comma-separated strings)
-
auth/{mount}/login
Available operations:write
write
parameters:role
(string) [Mandatory]secret
(string) [Mandatory]method
(string) [Values: token-full, token-only, accessor-only]
Falling back to "Why it was created" section, I assume that the Vault cluster, where the
plugin will be enabled, has already been authenticated in the "upstream" Vault cluster. So the VAULT_TOKEN
environment variable should be already set.
To be able to authenticate in target Vault cluster using cross-vault-auth plugin, the least required data for plugin and role configuration:
- API endpoint of the "upstream" cluster, which will be the issuer of the initial token (CA certificate if needed);
- Entity ID which issued token refers to;
❗ Pay attention: secret provided for login must be wrapped ❗
Plugin backend expects, that the secret, provided for login is one of three options:
a. wrapped full token data, got by using response wrapping feature via -wrap-ttl=...
option;
b. token itself or token accessor stored in cubbyhole with the key name equals to secret
and wrapped on read;
So there are three values for mandatory method
parameter: token-full
, token-only
and accessor-only
vault auth enable -path=cva cva-plugin
# Success! Enabled cva-plugin auth method at: cva/
vault write -f auth/cva/config cluster="http://vault.target.example.local" insecure_skip_verify=true
# Success! Data written to: auth/cva/config
vault write auth/cva/role/sample \
entity_id=11111111-2222-3333-4444-555566667777 \
strict_meta_verify=false \
token_ttl=5m
token_policies=sample-policy
# Success! Data written to: auth/cva/role/sample
vault write auth/cva/login role=sample secret={TOKEN} method=token-full
# Key Value
# --- -----
# token hvs.CAESIIkOvtZiU70VBDbAG3EDyK3nbGyuiXFMPo1GEKu62ZqnGh4KHGh2cy53VUJ0OUJuMDM4ZU80cWlHN0RTY1N0Tzk
# token_accessor 4abjOqsQYJv77Az5DZ1ZWacZ
# token_duration 5m
# token_renewable true
# token_policies ["sample-policy" "default"]
# identity_policies []
# policies ["sample-policy" "default"]
# token_meta_mapped_entity_id 11111111-2222-3333-4444-555566667777
# token_meta_role sample
Now issued token can be used to log in to cluster.