Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ installing Node.js, and a one-line bootstrap that wires both into any repo.
| [`phi-check`](src/phi-check) | A linter-style command that refuses files holding PHI (US SSNs, Medicare Beneficiary Identifiers, X12, HL7 v2, FHIR Patient data, and a record id or date of birth next to a diagnosis or a name label): `phi-check <path>...`, never printing the matched text, failing closed on binary and missing files, knowing nothing of git (the repository wires it into a hook or CI); the patterns are one importable module for other guards. Python 3 standard library only, nothing downloaded; Python comes through `dependsOn`. |
| [`common`](src/common) | The shared scripts: `dcf-persist`, behind every Feature's setup script (make a state directory usable and say what backs it), for a repo's own scripts too; and `dcf-curl`, which every installer's downloads go through so a TLS reset or a cut connection is retried. |
| [`coder-cli`](src/coder-cli) | [Coder](https://coder.com) CLI from GitHub releases, verified against the release checksums. |
| [`openbao`](src/openbao) | [OpenBao](https://openbao.org) CLI (`bao`), verified against the release checksums, its login (`~/.vault-token`) kept in a volume it owns (`OPENBAO_STATE_DIR`), and `dcf-secret` (get, put, status; fixed exit codes), the one command the collection's Features use to ask for a secret. |
| [`openbao`](src/openbao) | [OpenBao](https://openbao.org) CLI (`bao`), verified against the release checksums, its login (`~/.vault-token`) kept in a volume it owns (`OPENBAO_STATE_DIR`), and `dcf-secret` (get, put, status; fixed exit codes), the one command the collection's Features use to ask for a secret. `mode: server` also runs a local server (raft on the volume, sealed until the Operator unseals it; `openbao:init`, `:unseal`, `:seal`, userpass login, loopback-bound AppRoles). |
| [`velero`](src/velero) | [Velero](https://velero.io) CLI, verified against the release `CHECKSUM`. |
| [`kustomize`](src/kustomize) | Standalone [kustomize](https://kustomize.io), verified against the release checksums. |
| [`kubeconform`](src/kubeconform) | [kubeconform](https://github.com/yannh/kubeconform) manifest validator, verified against the release `CHECKSUMS`. |
Expand Down
3 changes: 2 additions & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,13 @@ tasks:
- bash test/ssh-agent/unit/passphrase_test.sh

test:openbao-unit:
desc: 'Docker-free tests of the openbao Feature''s scripts. dcf-secret against a stub bao that answers from environment variables and logs every call (dcf_secret_test.sh): get (the value alone on stdout, the mount, the field and the name checks, a multi-line value untouched), each exit code with its one-line fix (10 unreachable, 11 sealed or not initialised, 12 no token, 13 denied, 14 no such secret, and 1 and 2 for a put bao cannot explain and a wrong call) worked out from bao status, the JSON field initialized and the capabilities bao token capabilities reports, put (the value on stdin and in no argument, kept byte for byte; a merge by default with kv patch, a create with -cas=0 when the secret is absent, one retry as a patch when it appeared in between, --replace with kv put, a token without the patch capability refused with 13 and nothing written; nothing on stdout, a terminal refused), status in every state, DCF_SECRET_AS refused, no bao on PATH, and no fallback to a file in the state folder or a variable when the server is sealed, unreachable or the secret is missing. openbao-setup over temp folders (setup_test.sh) with the real dcf-persist: the state folder made and reported, a token left in ~/.vault-token moved into it once (only with the token helper in effect, never over one already there, mode 600), the token helper reported in effect, unset or another file, an unwritable folder reported, the mode (client, an unknown one reported, run as client), BAO_ADDR, BAO_TOKEN and a saved login reported without their text, bao missing, never fatal. The token helper bao calls (token_helper_test.sh): get, store and erase through `sh -c`, the token at OPENBAO_STATE_DIR/token with mode 600, written atomically, a missing token read as nothing, erase of a missing one no error, a failing store non-zero. `task openbao:logout` (logout_test.sh): revoke then erase, the erase when the revoke fails, nothing stored. That the real bao gives the signals the stub plays is the container test test/openbao/dev_server.sh (CI). Part of lint. Needs bash, grep and task (script(1) for one check, skipped without it).'
desc: 'Docker-free tests of the openbao Feature''s scripts. dcf-secret against a stub bao that answers from environment variables and logs every call (dcf_secret_test.sh): get (the value alone on stdout, the mount, the field and the name checks, a multi-line value untouched), each exit code with its one-line fix (10 unreachable, 11 sealed or not initialised, 12 no token, 13 denied, 14 no such secret, and 1 and 2 for a put bao cannot explain and a wrong call) worked out from bao status, the JSON field initialized and the capabilities bao token capabilities reports, put (the value on stdin and in no argument, kept byte for byte; a merge by default with kv patch, a create with -cas=0 when the secret is absent, one retry as a patch when it appeared in between, --replace with kv put, a token without the patch capability refused with 13 and nothing written; nothing on stdout, a terminal refused), status in every state, DCF_SECRET_AS refused, no bao on PATH, and no fallback to a file in the state folder or a variable when the server is sealed, unreachable or the secret is missing. openbao-setup over temp folders (setup_test.sh) with the real dcf-persist: the state folder made and reported, a token left in ~/.vault-token moved into it once (only with the token helper in effect, never over one already there, mode 600), the token helper reported in effect, unset or another file, an unwritable folder reported, the mode (client, server, an unknown one reported, run as client), BAO_ADDR and server mode''s default for it, BAO_TOKEN and a saved login reported without their text, bao missing, never fatal; and the server-mode start hook (`openbao-setup start`) against a stub bao that plays `bao server` and `bao status`: the config written (raft on the state folder, one loopback listener without TLS, the file audit device, no disable_mlock), the server started once and reused, a dead pid not reused, not initialised, sealed and unsealed said, a server that does not answer reported with its log, a config without the marker line left alone, client mode and a missing bao doing nothing, never fatal. The token helper bao calls (token_helper_test.sh): get, store and erase through `sh -c`, the token at OPENBAO_STATE_DIR/token with mode 600, written atomically, a missing token read as nothing, erase of a missing one no error, a failing store non-zero. `task openbao:logout` (logout_test.sh): revoke then erase, the erase when the revoke fails, nothing stored. Server mode''s tasks (server_test.sh, openbao-server against a stub bao with a small state): the refusal inside an agent''s job for every task that handles a share or a token (init, unseal, seal), by DEVHOST_JOB_CHILD, CLAUDECODE and a variable named in OPENBAO_AGENT_JOB_VARS, with and without a terminal and through the Taskfile, never a call to bao, never a prompt; the refusal without a terminal and in client mode; init under a pseudo-terminal (the backup choice asked for, recorded and asked again when empty or invalid, the Operator''s password asked twice and kept off every argument, 3 shares and threshold 2 printed to the terminal only and written nowhere, the unseal, the configuration in order with the login lifetime of 4 hours and at most 8, the root token used only for it and revoked, also after a failed step, only -pgp-keys accepted, never twice); unseal, seal and status in every state with the standing no-backup warning; the AppRole tasks (the role''s one-secret policy, the loopback binding, 5 minute tokens, one valid secret id written mode 600 and never printed, ALL=1, revoke) and their exit codes. That the real bao gives the signals the stubs play is the container tests test/openbao/dev_server.sh and test/openbao/server_mode.sh (CI). Part of lint. Needs bash, grep and task (script(1) for the terminal checks, skipped without it).'
cmds:
- bash test/openbao/unit/dcf_secret_test.sh
- bash test/openbao/unit/setup_test.sh
- bash test/openbao/unit/token_helper_test.sh
- bash test/openbao/unit/logout_test.sh
- bash test/openbao/unit/server_test.sh

test:ssh-key-import:
desc: 'Docker-free bash tests of the host task devcontainer:ssh-key-import in the bash twin (src/devcontainer-cli/devcontainer-cli.sh): the key file reaches `devcontainer exec` on stdin and never in an argument, the .pub follows, the key load runs last, the refusals (no file, no running container, key mode off in the container), and the host-side warnings (default name, a public key also in authorized_keys or another ~/.ssh/*.pub, never printing the key); stub devcontainer and docker record what they get; part of lint. The PowerShell twin is in test:bootstrap.'
Expand Down
17 changes: 17 additions & 0 deletions src/openbao/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@ here; `task lint` fails when the entry is missing.

Entries from 1.0.0 to 1.0.3 were generated from the commit history (`.github/scripts/changelog.py backfill`) and the "Changes in x.y.z" sections that NOTES.md used to hold; most list commit subjects as written then; the newest of them is in wording for users.

## [1.2.0] - 2026-11-03

### Added

- **Server mode: a local OpenBao server in the container.** Set the `mode` option to `server` (or `OPENBAO_SETUP_MODE=server` in `containerEnv`, no rebuild; needs OpenBao 2.4.0 or newer, and an older pinned `version` fails the build with that said). The post-start hook starts one server with raft storage on the Feature's volume and a loopback-only listener (`127.0.0.1:8200`, no TLS), and it comes up **sealed**: nothing can read a secret until you unseal it. The hook never waits and never prompts; it prints whether the server is not initialised, sealed or unsealed. `mode: client` stays the default and behaves as before. **You must act once, in a plain terminal in the container:** `task openbao:init`, then after every container restart `task openbao:unseal`.
- `task openbao:init`: asks for a **backup destination or the words `no backup`** (never defaulted, and the answer is recorded in the volume), asks for the Operator's password, makes **3 unseal shares with a threshold of 2** and prints them once, only to the terminal (keep one in a password manager, one on paper offline, one with a second person or on a hardware key; losing two loses every secret), has you enter two of them to prove you stored them, sets up the `dcf` KV mount, `userpass` and `approle`, and revokes the root token. `-pgp-keys=...` after `--` encrypts the shares.
- `task openbao:unseal` and `task openbao:seal`. `task openbao:server:status` shows the state, the storage, the audit log, whether your login is valid and the backup choice, with a standing warning while there is none. Your login is `task openbao:login -- -method=userpass username=operator`: it lasts **4 hours, at most 8**, and a renewal past that is refused.
- `task openbao:approle:issue ROLE=<name> POLICY_PATH=<mount/path>` makes a role for a machine identity (for the devhost's backs, later) that may read that one secret, only from inside the container (loopback-bound secret ids and tokens), with 5 minute tokens; `ROLE=<name>` alone or `ALL=1` reissues, leaving exactly one valid secret id per role, written to the volume and never printed. `task openbao:approle:revoke ROLE=<name>` removes a role. **Making a role and deleting one ask for two unseal shares** (typed at a hidden prompt, they make a root token, `bao operator generate-root`, used for that step only and revoked straight after, also on failure); reissuing (`ROLE=<name>` alone, `ALL=1`) uses your login and asks for none. Your login's policy cannot write a policy or make, change or delete a role, so a token of yours can never give a role `snapshot-force`.
- A file audit device in the volume (`audit.log`), declared in the server's config.
- **Not included yet:** `openbao:backup` and restore. A backup destination is recorded but not used, so until a later version a lost machine loses what is in the server's volume (this is why `init` asks and `server:status` warns); take a snapshot by hand with `bao operator raft snapshot save`.

### Changed

- **`init`, `unseal`, `seal` and the two steps that ask for shares (making and deleting a role) refuse inside an agent's job** (when `DEVHOST_JOB_CHILD` or `CLAUDECODE` is set, or a variable you name in `OPENBAO_AGENT_JOB_VARS`), and `init`, `unseal` and those two steps refuse without a terminal: the shares and the token must never land in a transcript. Run them yourself in a plain terminal.
- `dcf-secret`'s failure lines now name the tasks: sealed points to `task openbao:unseal`, not initialised to `task openbao:init`, unreachable to `task openbao:server:status`, each adding what to do against a server elsewhere. The exit codes are unchanged.
- In server mode `BAO_ADDR` defaults to the local server for `dcf-secret` and every task when you have not set it (a bare `bao` still needs `BAO_ADDR`). The tasks `status`, `login`, `logout`, `kv:get` and `setup` run the same `bao` call through `openbao-run`, which adds only that default; client mode is unchanged. `task openbao:setup` now also runs the start hook.

## [1.1.1] - 2026-11-03

### Changed
Expand Down
Loading
Loading