Add vctl and vctx Vault token CLIs
Introduces the vault-tools monorepo: two Go CLIs that share a config file (~/.config/vault) and token cache (~/.cache/vault) for working with multiple Vault instances (contexts). - add shared/ library: config parsing (vctl.yaml/config.yaml, per-context overrides, slash contexts), token cache (0600/0700, atomic writes, path- traversal guards), and a small hand-rolled Vault HTTP client (login/renew) - add vctl: login/renew (single or --all), list, --method/--user overrides, no-echo password/token prompts, dynamic context completion - add vctx: resolve a context, set VAULT_ADDR/VAULT_TOKEN/VAULT_NAMESPACE and exec the vault CLI, passing remaining args through untouched - add unit tests across shared/, vctl and vctx command layers (config resolution, cache paths, vault client, --all iteration + error aggregation, vctx arg pass-through and env construction via fakeable exec/prompt seams) - add Makefile (build/test/completions/rpm, patch|minor|major version bumps), nfpm RPM packaging bundling bash/zsh/fish completions for both binaries - add Woodpecker pipelines: build/test/pre-commit on PRs, and a tag release that cross-compiles, builds+uploads the RPM to artifactapi, and cuts a Gitea release (serviceAccountName default, k8s resources on every step) - add README, per-command docs (docs/vctl.md, docs/vctx.md), AGENTS.md and an example config Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
This commit is contained in:
+140
@@ -0,0 +1,140 @@
|
||||
# vctl
|
||||
|
||||
`vctl` manages Vault tokens for multiple vault instances ("contexts"). It logs
|
||||
in to (or renews tokens for) one or all configured contexts and caches each
|
||||
resulting token under `~/.cache/vault/<context>` for use by
|
||||
[`vctx`](vctx.md) and other tooling.
|
||||
|
||||
## Synopsis
|
||||
|
||||
```
|
||||
vctl login <context> # log in to one context
|
||||
vctl login --all # log in to every configured context
|
||||
vctl renew <context> # renew one context's cached token
|
||||
vctl renew --all # renew every context that has a cached token
|
||||
vctl list # show contexts + cached-token status
|
||||
vctl version
|
||||
```
|
||||
|
||||
## Flags
|
||||
|
||||
Both `login` and `renew` accept:
|
||||
|
||||
| Flag | Default | Description |
|
||||
| -------------- | -------------------------------- | -------------------------------------------------- |
|
||||
| `--all` | `false` | Operate on every configured context. |
|
||||
| `--method <m>` | context/config, else `ldap` | Override the auth method (`ldap`, `userpass`, `okta`, `radius`, `token`). |
|
||||
| `--user <u>` | context/config, else `$USER` | Override the login username. |
|
||||
|
||||
A context name or `--all` is required (but not both).
|
||||
|
||||
## Configuration
|
||||
|
||||
`vctl` reads the first of these files that exists:
|
||||
|
||||
1. `~/.config/vault/vctl.yaml`
|
||||
2. `~/.config/vault/config.yaml`
|
||||
|
||||
(`$XDG_CONFIG_HOME` is honoured in place of `~/.config`.)
|
||||
|
||||
```yaml
|
||||
# ~/.config/vault/vctl.yaml
|
||||
defaults:
|
||||
method: ldap # applied to any context that doesn't set its own
|
||||
user: ben
|
||||
namespace: ""
|
||||
|
||||
contexts:
|
||||
sydney:
|
||||
address: https://vault.syd1.au.unkin.net
|
||||
|
||||
staging/sydney: # slashes are allowed; they nest on disk
|
||||
address: https://vault-staging.syd1.au.unkin.net
|
||||
namespace: staging
|
||||
user: svc-ben # per-context override
|
||||
|
||||
legacy:
|
||||
address: https://vault-legacy.example.net
|
||||
method: userpass
|
||||
path: userpass2 # override the auth mount path (default = method)
|
||||
```
|
||||
|
||||
### Field resolution
|
||||
|
||||
For each context, every field is resolved with the chain
|
||||
**context value → file `defaults` → built-in default**:
|
||||
|
||||
- `method` — built-in default `ldap`.
|
||||
- `user` — built-in default `$USER`.
|
||||
- `namespace` — no built-in default (unset means the root namespace).
|
||||
- `path` — the auth mount path; defaults to the resolved `method`.
|
||||
|
||||
`--method` / `--user` on the command line override the resolved values. When
|
||||
`--method` changes the method and the context did not pin an explicit `path`,
|
||||
the auth path follows the new method.
|
||||
|
||||
## Auth methods
|
||||
|
||||
- **Password methods** (`ldap`, `userpass`, `okta`, `radius`): `vctl` prompts
|
||||
for a password (no echo) and POSTs to `auth/<path>/login/<user>`.
|
||||
- **`token`**: `vctl` prompts for a raw Vault token (no echo), verifies it with
|
||||
`auth/token/lookup-self`, and caches its details.
|
||||
|
||||
## Token cache
|
||||
|
||||
Tokens are written to `~/.cache/vault/<context>` (honouring `$XDG_CACHE_HOME`)
|
||||
as JSON. Slash contexts nest: `staging/sydney` →
|
||||
`~/.cache/vault/staging/sydney`. Parent directories are created `0700` and token
|
||||
files are written `0600`.
|
||||
|
||||
Each file stores enough to inspect and later revoke the token:
|
||||
|
||||
```json
|
||||
{
|
||||
"context": "staging/sydney",
|
||||
"address": "https://vault-staging.syd1.au.unkin.net",
|
||||
"namespace": "staging",
|
||||
"token": "s....",
|
||||
"accessor": "hmac-...",
|
||||
"policies": ["default", "kv-read"],
|
||||
"renewable": true,
|
||||
"lease_duration_seconds": 3600,
|
||||
"issued_at": "2026-07-26T12:00:00Z",
|
||||
"expires_at": "2026-07-26T13:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
The `accessor` lets you revoke the token later without exposing the secret
|
||||
(`vault token revoke -accessor <accessor>`).
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Log in to one context (prompts for password)
|
||||
vctl login sydney
|
||||
|
||||
# Log in everywhere at once
|
||||
vctl login --all
|
||||
|
||||
# Log in to a context overriding the method + user
|
||||
vctl login sydney --method okta --user someone.else
|
||||
|
||||
# Renew a slash-named context
|
||||
vctl renew staging/sydney
|
||||
|
||||
# Renew everything that currently has a cached token
|
||||
vctl renew --all
|
||||
|
||||
# See what is configured and which tokens are still valid
|
||||
vctl list
|
||||
```
|
||||
|
||||
## Shell completion
|
||||
|
||||
```bash
|
||||
vctl completion bash > /etc/bash_completion.d/vctl
|
||||
vctl completion zsh > ~/.zsh/completions/_vctl
|
||||
```
|
||||
|
||||
Context names complete dynamically from the config file (the RPM installs these
|
||||
completions automatically).
|
||||
@@ -0,0 +1,82 @@
|
||||
# vctx
|
||||
|
||||
`vctx` is a thin, context-aware wrapper around the real `vault` CLI. It resolves
|
||||
a context (using the same config file and token cache as [`vctl`](vctl.md)),
|
||||
sets `VAULT_ADDR`, `VAULT_TOKEN` and `VAULT_NAMESPACE` for that single
|
||||
invocation only, and execs `vault` with the remaining arguments.
|
||||
|
||||
## Synopsis
|
||||
|
||||
```
|
||||
vctx --context <context> <any vault args...>
|
||||
vctx version
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
```
|
||||
vctx --context sydney kv put kv/foo/bar secret=baz
|
||||
```
|
||||
|
||||
1. Resolves the `sydney` context from `~/.config/vault/vctl.yaml` (or
|
||||
`config.yaml`) — the exact file `vctl` uses.
|
||||
2. Loads the cached token from `~/.cache/vault/sydney` (errors with a hint to
|
||||
run `vctl login sydney` if none exists).
|
||||
3. Sets, for this process only:
|
||||
- `VAULT_ADDR` = the context's address
|
||||
- `VAULT_TOKEN` = the cached token
|
||||
- `VAULT_NAMESPACE` = the context's namespace (only if non-empty; falls back
|
||||
to the namespace recorded in the cached token)
|
||||
4. `exec`s the `vault` binary (found on `PATH`) with everything after the
|
||||
context. Because it replaces the process, `vault`'s exit status, signals and
|
||||
TTY behaviour pass straight through.
|
||||
|
||||
The ambient `VAULT_ADDR` / `VAULT_TOKEN` in your shell are ignored for the call
|
||||
— `vctx` always targets the chosen context.
|
||||
|
||||
## The `--context` flag
|
||||
|
||||
`--context` is the only flag `vctx` consumes; it must come **before** the vault
|
||||
command. Everything from the first non-flag argument onward is handed to `vault`
|
||||
untouched, so vault's own flags work normally:
|
||||
|
||||
```bash
|
||||
vctx --context sydney kv get -field=password secret/db
|
||||
vctx --context staging/sydney token lookup
|
||||
vctx --context sydney -help # 'vctx --context X' then vault sees -help
|
||||
```
|
||||
|
||||
Context names support slashes (e.g. `staging/sydney`), matching the config and
|
||||
the on-disk token cache.
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Write a secret to the sydney vault
|
||||
vctx --context sydney kv put kv/foo/bar secret=baz
|
||||
|
||||
# Read a single field
|
||||
vctx --context sydney kv get -field=secret kv/foo/bar
|
||||
|
||||
# Operate against a namespaced, slash-named context
|
||||
vctx --context staging/sydney kv list kv/
|
||||
|
||||
# Inspect the token vctx would use
|
||||
vctx --context sydney token lookup
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- The `vault` CLI must be installed and on `PATH`.
|
||||
- A token must already be cached for the context (`vctl login <context>`).
|
||||
|
||||
## Shell completion
|
||||
|
||||
```bash
|
||||
vctx completion bash > /etc/bash_completion.d/vctx
|
||||
vctx completion zsh > ~/.zsh/completions/_vctx
|
||||
```
|
||||
|
||||
The `--context` value completes dynamically from the configured context names,
|
||||
so `vctx --context <TAB>` lists your vaults (the RPM installs these completions
|
||||
automatically).
|
||||
Reference in New Issue
Block a user