11 KiB
Harmony Auth CLI
Status: read-only commands implemented. Mutation commands follow the ADR-027 group migration.
harmony-auth inspects and manages the relationship between Zitadel
identities and OpenBao access. It presents tenants, identities, and Harmony
permissions first. JWT roles, policy names, subject claims, and HCL remain
available through advanced output.
The CLI is the preferred interface while the web UI matures. Both interfaces
use the same harmony_auth operations and return the same effective access.
Mental model
The CLI has two main views:
identity: who an identity is and what it can accesstenant: who can access a tenant or project
Harmony permissions are the primary authorization vocabulary:
| Permission | Intended identity | Effect |
|---|---|---|
tenant-admin |
Human | Read, create, change, and delete secrets in a tenant or project |
cd-deployer |
Service account | Read deployment secrets in one project |
read-only |
Human or service account | Read secrets in a tenant or project |
Existing OpenBao policies that do not correspond to a Harmony assignment are shown as imported access. The CLI does not rename, rewrite, or hide them.
Command tree
harmony-auth
├── connection check
├── identity list
├── identity show <subject-id>
├── tenant list
└── tenant show <tenant>
There are no flat aliases. harmony-auth list is not valid.
Connection and credentials
Every command except --help and --version requires:
| Flag | Environment | Meaning |
|---|---|---|
--zitadel-url |
ZITADEL_URL |
Zitadel base URL |
--openbao-url |
OPENBAO_URL |
OpenBao base URL |
| none | ZITADEL_PAT |
Zitadel service-account PAT |
| none | OPENBAO_TOKEN |
Temporary OpenBao administrator token |
Secrets are environment-only because command-line arguments remain in shell history and may be visible in the process list. Secret values never appear in help output, normal output, JSON, or logs.
Example:
export ZITADEL_URL=https://sso.example.com
export ZITADEL_PAT=...
export OPENBAO_URL=https://secrets.example.com
export OPENBAO_TOKEN=...
harmony-auth connection check
The CLI does not persist profiles or credentials. Browser profile storage and session credential refresh remain web UI concerns.
connection check attempts both backends even when one fails. It reports each
status without printing provider response bodies:
Zitadel connected
OpenBao connected
Identity commands
List identities
harmony-auth identity list
harmony-auth identity list --search folk
harmony-auth identity list --kind human
harmony-auth identity list --tenant devsights
harmony-auth identity list --tenant devsights --kind service
Filters combine with AND semantics. --search matches display name, login, or
email. --kind accepts human or service. Tenant matching uses parsed
assignment and imported-policy scopes, not string-prefix matching.
Imported scopes are recognized only from wildcard roots:
<mount>/data/<tenant>/*is tenant-wide.<mount>/data/<tenant>/<project>/*is project-specific.- Exact secret paths and wildcard paths below a project are custom access. They remain visible but do not affect tenant filters or summaries.
Tenant and project components must pass the same slug validation as managed
Harmony scopes. harmony_auth returns both the raw paths and parsed scopes;
frontends never infer scopes themselves.
Human output keeps the tenant visible:
ACTIVE HUMAN Alice Example alice@example.com
subject 241696899342475267
devsights Tenant Admin
ACTIVE SERVICE Folk CD folk-cd
subject 241697058442100739
devsights/folk-timesheet CD Deployer
An identity without recognized access is still listed with No access.
Show an identity
harmony-auth identity show 241696899342475267
Default output includes identity metadata, Harmony assignments, and imported access:
Alice Example
Subject: 241696899342475267
Login: alice@example.com
Kind: Human
Status: Active
Harmony permissions
019b... Tenant Admin devsights
Imported OpenBao access
legacy-folk-reader devsights/folk-timesheet Read secrets
Use --advanced to inspect implementation details:
harmony-auth identity show 241696899342475267 --advanced
Advanced output adds matching JWT roles, bound subject, audiences, attached
policy names, and the exact HCL returned by OpenBao. A built-in or inaccessible
policy remains listed with Policy body unavailable.
Planned group-based mutations
The first release does not grant or revoke access. Existing per-subject JWT roles are discovery input, not a writable authorization model.
ADR-027 makes Zitadel roles named <tenant>:owner, <tenant>:deployer, and
<tenant>:viewer authoritative. One shared OpenBao JWT role reads the groups
claim, and OpenBao external groups attach policies. Mutation commands ship only
after harmony_auth implements that model end to end.
Grant a permission (planned)
harmony-auth identity grant 241696899342475267 \
--permission tenant-admin \
--tenant devsights
Grant is review-only by default:
Plan
Identity: Alice Example (241696899342475267)
Permission: Tenant Admin
Scope: devsights
Effect: Read, create, change, and delete secrets
No changes applied. Re-run with --apply to continue.
Apply the reviewed request explicitly:
harmony-auth identity grant 241696899342475267 \
--permission tenant-admin \
--tenant devsights \
--apply
cd-deployer requires --project. Other permissions accept an optional
project. Permission applicability and scope validation come from
harmony_auth; the CLI does not duplicate those rules.
Applying a grant changes the Zitadel role assignment. It does not create or
edit a per-subject OpenBao JWT role. Applying the same grant twice must report
changed: false.
Revoke a permission (planned)
The assignment ID comes from identity show:
harmony-auth identity revoke 241696899342475267 019b...
Revoke is also review-only by default. It shows identity, permission, scope,
and the warning that already-issued OpenBao tokens remain valid until expiry or
revocation. --apply performs the removal:
harmony-auth identity revoke 241696899342475267 019b... --apply
Imported per-subject access cannot be revoked through this command because it is outside the ADR-027 group model. Its OpenBao policy name remains visible for manual migration.
Tenant commands
List tenants
harmony-auth tenant list
Tenants and projects are discovered from managed assignments and recognized OpenBao policy paths:
TENANT PROJECT HUMANS SERVICES
devsights All projects 2 0
devsights folk-timesheet 1 1
detexion harmony-fleet 0 2
Show a tenant
harmony-auth tenant show devsights
harmony-auth tenant show devsights --project folk-timesheet
Output lists matching scopes and identities with their access. With
--project, tenant-wide access and access to that exact project are included;
other projects are excluded:
devsights/folk-timesheet
Alice Example Human Read-only
Folk CD Service CD Deployer
This is an authorization view, not a secret-value browser. Secret listing, creation, update, and reveal are outside the first release.
JSON output
Every implemented command accepts --json. JSON is written to stdout;
diagnostics and logs are written to stderr. The envelope is versioned:
{
"schema_version": 1,
"command": "identity.list",
"result": {}
}
The first release uses these result shapes:
| Command | result fields |
|---|---|
connection check |
zitadel: { connected }, openbao: { connected } |
identity list |
identities: [{ identity, access }] |
identity show |
identity, access |
tenant list |
tenants: [{ scope, humans, services }] |
tenant show |
tenant, project, identities: [{ identity, access }] |
identity contains subject_id, kind, display_name, login_name,
email, and active. Identity kinds are human and service; email is a
string or null.
scope contains tenant and project, where project is a string or null.
An assignment contains id, subject_id, permission, scope,
policy_name, and created_at. IDs are UUID strings, timestamps are RFC 3339,
and permissions are tenant_admin, cd_deployer, or read_only.
access contains:
assignments: assignment objects as defined aboveimported: objects withrole_name,policy_name,secret_paths,scopes, andeffectroles: objects withname,subject_id,bound_audiences, andpolicies
Each role policy contains name and body. Policy bodies are strings only for
identity show --advanced; otherwise body is null. Imported
secret_paths, audiences, and scopes are arrays. effect is the
plain-language interpretation returned by harmony_auth.
connection check always returns both status objects and exits 1 when either
connected value is false. A top-level error is used only when the command
cannot produce those statuses.
Domain and backend errors use stdout when --json is active:
{
"schema_version": 1,
"command": "identity.show",
"error": {
"kind": "not_found",
"message": "identity not found"
}
}
Error kinds are not_found, invalid, and backend. Clap usage errors remain
on stderr because command parsing fails before JSON dispatch.
Planned grant and revoke results will add applied and changed when those
commands are implemented. Their JSON schema is not frozen by this release.
Exit and error behavior
| Exit | Meaning |
|---|---|
0 |
Query completed, plan produced, or mutation applied/no-op |
2 |
Invalid command, missing connection value, not found, or invalid input |
1 |
Zitadel or OpenBao request failed |
Errors name the failed operation and backend but do not print credentials,
provider response bodies, or stack traces. RUST_LOG=info enables operation
logs on stderr.
Architecture boundary
harmony_auth_ui ─┐
├──> harmony_auth ──> Zitadel + OpenBao
harmony_auth_cli ─┘
harmony_auth owns:
- identities, scopes, permissions, assignments, roles, policies, and plans
- Zitadel identity discovery
- OpenBao role and policy discovery
- tenant and identity access queries
- permission applicability and scope validation
- assignment discovery and current legacy assignment operations used by the UI
- provider request construction and response interpretation
harmony_auth_cli owns:
- Clap arguments and environment mapping
- terminal and JSON rendering
- binary exit codes and logging setup
harmony_auth_ui owns HTTP routes, browser sessions, cookies, forms, HTML, CSS,
and browser JavaScript. Neither frontend may parse OpenBao policies, infer
tenants, reconcile JWT roles, or implement permission rules.
First-release limits
- Authorization discovers existing per-subject JWT roles as imported access.
- Grant and revoke wait for Zitadel role and OpenBao external-group operations.
- The CLI does not create Zitadel identities.
- The CLI does not provide a generic OpenBao policy editor.
- Tenant administrators are not yet authenticated as constrained actors; the supplied OpenBao token determines backend authority.
- The CLI does not store profiles or credentials.