Run commands with credentials from OpenBao, plus credential helpers for kubectl, git and AWS
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Mikhail Lukianchenko ed8b684298
All checks were successful
Release / changelog (push) Successful in 3s
Release / test (push) Successful in 1m10s
Release / ci (push) Successful in 0s
Release / plan (push) Successful in 4s
Release / image (push) Successful in 1m13s
Release / packages (push) Successful in 1m13s
Release / release (push) Successful in 3s
baox token: print the OpenBao token, renewed when due
The agents' bao shell wrapper renewed the token file by mtime every seven
days; with this it takes the token from baox, which renews by the TTL
OpenBao reports.
2026-10-31 15:07:03 +01:00
.forgejo/workflows Name the registry integration audience in the workflow 2026-10-31 14:17:08 +01:00
internal baox token: print the OpenBao token, renewed when due 2026-10-31 15:07:03 +01:00
packaging Skeleton: config file, OpenBao login, CI and image 2026-10-31 13:58:38 +01:00
.gitignore Ignore agent worktrees 2026-10-31 13:59:57 +01:00
.ko.yaml Skeleton: config file, OpenBao login, CI and image 2026-10-31 13:58:38 +01:00
CHANGELOG.md baox token: print the OpenBao token, renewed when due 2026-10-31 15:07:03 +01:00
go.mod baox aws: credential_process credentials via STS web identity 2026-10-31 14:04:06 +01:00
go.sum baox aws: credential_process credentials via STS web identity 2026-10-31 14:04:06 +01:00
LICENSE Skeleton: config file, OpenBao login, CI and image 2026-10-31 13:58:38 +01:00
main.go Skeleton: config file, OpenBao login, CI and image 2026-10-31 13:58:38 +01:00
README.md baox token: print the OpenBao token, renewed when due 2026-10-31 15:07:03 +01:00

baox

Run commands with credentials from OpenBao in their environment, act as a credential helper for kubectl, git, AWS and an MCP headersHelper, and seal archives with transit keys.

Install

brew install mikluko/tap/baox
go install sarak.sh/mikluko/baox@latest

Releases also ship a container image, sarak.sh/mikluko/baox, and .deb packages for amd64 and arm64 in the sarak.sh Debian registry (distribution trixie, component main).

OpenBao login

Every command gets its token the same way, first match wins:

  1. BAO_TOKEN in the environment.
  2. openbao.token_file: a token renewed when less than half its period (or creation TTL) remains.
  3. openbao.kubernetes: login through the kubernetes auth method with the pod's service account token.

The address is BAO_ADDR, else openbao.address. The other BAO_* client variables (BAO_CACERT and so on) apply.

baox token prints that token, renewed first when due, for the bao CLI and anything else that wants one:

BAO_TOKEN="$(baox token)" bao kv list -mount=kv k8s

Config file

--config, else $BAOX_CONFIG, else $XDG_CONFIG_HOME/baox/config.yaml or ~/.config/baox/config.yaml. A missing file is an empty config. Unknown keys are errors.

openbao:
  address: https://secrets.sarak.sh
  token_file: ~/.config/openbao/agent-token
  # kubernetes:
  #   mount: kubernetes/saraksh   # default kubernetes
  #   role: agent
  #   jwt_file: ...               # default the projected service account token

accounts:
  mikluko:
    kind: github
    host: github.com              # default github.com
    owners: [mikluko]
    mount: github
    permission_set: agent-mikluko # token from <mount>/token/<permission_set>
  sarak:
    kind: forgejo
    host: sarak.sh
    owners: [mikluko]
    role: forgejo                 # identity/oidc/token/<role>
  prod:
    kind: aws
    account_id: "123456789012"
    role: aws                     # identity token role
    region: eu-central-1          # default us-east-1

clusters:
  saraksh:
    mount: k8s-saraksh
    namespace: openbao
    cluster_role_binding: true
    ttl: 1h

seal:
  mount: transit                  # default transit
  encryption_key: agent-sessions
  signing_key: agent-sessions-sign

github and forgejo accounts are picked by host and owner; a host and owner matching no account, or several, is an error.

Commands

run

baox run --env-file .env -- ./server

The child inherits baox's environment, overridden by each --env-file in turn. Any value, inherited or from a file, of the form bao://<mount>/<path>#<key> is replaced by that field of the KV v2 secret. baox prints no value and exits with the child's status. Env files take KEY=VALUE lines, # comments, an optional export prefix, and single or double quotes.

kube

A kubeconfig user:

users:
  - name: saraksh-view
    user:
      exec:
        apiVersion: client.authentication.k8s.io/v1
        command: baox
        args: [kube, saraksh, view]
        interactiveMode: Never

baox kube <cluster> <role> writes <mount>/creds/<role> and prints an ExecCredential whose expiry falls 10% of the lease (at most 5 minutes) before the lease ends. Each call creates a new lease.

github and forgejo

git config --global credential.https://github.com.helper '!baox github git-credential'
git config --global credential.https://github.com.useHttpPath true
git config --global credential.https://sarak.sh.helper '!baox forgejo git-credential'
git config --global credential.https://sarak.sh.useHttpPath true

run sets GH_TOKEN (github) or FORGEJO_TOKEN (forgejo) for the child, choosing the account by --account, else by the origin remote of the current directory:

baox github run -- gh pr list
baox forgejo run --account sarak -- fj issue list

The forgejo credential is an OpenBao identity token that Forgejo accepts through an Authorized Integration. baox forgejo header <account> prints {"Authorization":"Bearer <token>"} for Claude Code's headersHelper:

{
  "mcpServers": {
    "forgejo": {
      "type": "http",
      "url": "https://example/mcp",
      "headersHelper": "baox forgejo header sarak"
    }
  }
}

aws

[profile prod-admin]
credential_process = baox aws prod admin

baox aws <account> <role> exchanges an identity token for credentials of arn:aws:iam::<account_id>:role/<role> through STS AssumeRoleWithWebIdentity.

seal and unseal

tar -c work | baox seal -o work.tar.sealed
baox unseal work.tar.sealed | tar -x

seal encrypts with a data key from transit and signs with an ed25519 transit key; the format is described in the internal/seal package doc. unseal takes a file, not a pipe: it verifies the signature over the whole file before transit decrypts the data key, and writes nothing if verification fails. Both stream in constant memory.

Prior art

  • 1Password CLI: op run resolves op:// references in the environment and env files the same way baox run resolves bao:// ones.

Development

go test ./... runs a dev-mode OpenBao from the bao binary on PATH; without one those tests skip, unless BAOX_REQUIRE_BAO is set.

Publishing the image and packages exchanges the workflow's OIDC token through an Authorized Integration of the owner's with scope write:package; REGISTRY_AUDIENCE in release.yaml holds that integration's audience.