18 KiB
Harmony Auth CLI
Status: read-only inspection and ADR-027 tenant identity provisioning are implemented. Identity grant and revoke commands remain pending.
harmony-auth inspects access and provisions tenants across Zitadel, OpenBao,
and Kubernetes. 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
├── context configure
├── connection check
├── identity list
├── identity show <subject-id>
├── tenant list
├── tenant create <tenant>
├── tenant deployer create <tenant> <account>
└── tenant show <tenant>
There are no flat aliases. harmony-auth list is not valid.
Context and credentials
Every command selects one named environment through --context or
HARMONY_CONTEXT:
harmony-auth connection check --context prod
Configure the context once:
harmony-auth context configure --context prod
The guided setup collects the complete context in one session:
| Field | Meaning |
|---|---|
zitadel_url |
Zitadel issuer and Management API URL |
zitadel_pat |
Zitadel administrator PAT |
zitadel_project |
Existing Zitadel project that contains tenant roles |
openbao_url |
OpenBao API URL |
openbao_token |
OpenBao administrator token |
kubeconfig |
Administrator kubeconfig path |
kube_context |
Exact administrator kube context |
Harmony Config stores the profile at
<config-dir>/contexts/<context>/HarmonyAuthContext.json. The file is mode
0600; PAT and token prompts are masked. Each answer is saved immediately, so
rerunning context configure after an interruption resumes at the first missing
field. Commands either load the complete profile or report the context as
unconfigured. They do not fail through a sequence of missing provider
environment variables.
HARMONY_CONTEXT can set a shell's default context:
export HARMONY_CONTEXT=prod
harmony-auth connection check
Secret values never appear in help output, normal output, JSON, or logs.
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
Create a tenant
tenant create configures owner access, secret access, resource limits,
network isolation, Harbor push credentials, and namespace-scoped deployment
credentials. It runs TenantScore and TenantCredentialScore against an
explicit administrator kube context.
harmony-auth tenant create acme \
--context prod \
--owner acme-admin
Missing resource limits are prompted with defaults. Each answer is saved
immediately in the selected context's tenant draft, so an interrupted or
plan-only run resumes without repeating completed prompts. Later plans print
the saved limits. Applied values are stored in the authoritative tenant
definition under harmony_auth/data/tenants/<tenant>. Use flags such as
--cpu-limit-cores for unattended use.
Interactive applied runs request any missing Harbor push and pull credentials.
For unattended use, set HARBOR_USERNAME, HARBOR_TOKEN,
HARBOR_PULL_USERNAME, and HARBOR_PULL_TOKEN; tokens have no command-line
flags because process arguments are not secret-safe. Supplying either half of
one credential pair replaces that pair and requires both values. Harmony stores them at
<OPENBAO_KV_MOUNT>/data/<tenant>/RegistryCredentials and
<OPENBAO_KV_MOUNT>/data/<tenant>/RegistryPullCredentials. K8sAnywhere
Application deploys use the pull credentials to reconcile the context's
Kubernetes image pull Secret. Harbor must enforce the robot's pull-only scope.
Harbor SSO will replace the stored push credentials when registry federation is
available.
The selected Harmony context supplies the default kubeconfig path and kube context. Command flags override those defaults for the current invocation; local paths are not stored in the tenant definition. Owner usernames are bound to the namespace deployer Role until OKD group claims are available.
For an OKD OpenID provider that maps Zitadel's preferred_username to the
OpenShift username, values passed through --owner must be OpenShift usernames
such as acme-admin, not email addresses unless the username itself is an
email.
The command only prints a plan unless --apply is set. Owner additions are
additive. Omitting an existing owner does not revoke access; identity revocation
remains a separate pending command.
Tenant creation does not grant viewer access. The tenant's secret subtree also contains namespace deployment credentials, so viewer access remains disabled until those credentials are separated.
The shared identity and secret-login baseline must exist before tenant creation. Operators can override its defaults through hidden environment configuration:
| Environment | Default | Meaning |
|---|---|---|
ZITADEL_ORG_ID |
PAT organization | Zitadel organization containing the project |
HARMONY_GROUPS_ACTION |
harmonyGroupsClaim |
Action that adds tenant roles to tokens |
OPENBAO_KV_MOUNT |
secret |
Tenant secret mount |
OPENBAO_JWT_AUTH_MOUNT |
jwt |
Zitadel-backed OpenBao auth mount |
OPENBAO_JWT_ROLE |
auto-discovered | Shared login role; set only when more than one compatible role exists |
Use --step-by-step with --apply to pause after each completed component:
harmony-auth tenant create acme \
--context prod \
--owner acme-admin \
--apply \
--step-by-step
The command pauses after baseline validation, tenant permissions, owner access,
secret access, stored tenant state, each Harbor credential pair, and between
the Kubernetes tenant and credential Scores. Each prompt names the operation
it will run next, including the target Zitadel project, OpenBao path, namespace,
and kube context where applicable. All completed operations are logged at
INFO, including detail between checkpoints. The administrator can test from
another terminal before approving the next operation. Declining stops safely;
rerunning the same command continues through idempotent operations. Interactive
mode cannot be combined with --json and requires a terminal.
Create a CI deployer
CI deployer creation is separate from tenant creation:
harmony-auth tenant deployer create acme acme-ci \
--context prod \
--credentials ./acme-ci-key.json
This prints a plan and verifies that the tenant exists, the shared baseline is
valid, and the account name is unused. Add --apply to create the account,
grant tenant deployer access, and write its one-time credentials:
harmony-auth tenant deployer create acme acme-ci \
--context prod \
--credentials ./acme-ci-key.json \
--apply
The credentials file is created with mode 0600 and is never overwritten. The
command fails without changing access if the account already exists. If writing
the credentials fails after account creation, it deletes the new account; the
error reports if that cleanup also fails.
The deployer can read tenant inputs under
<OPENBAO_KV_MOUNT>/data/<tenant>/*. Harmony-generated durable state is kept
separately under
<OPENBAO_KV_MOUNT>/data/<tenant>/harmony-state/*, where the deployer can
create, read, and update values. It cannot write input credentials or other
tenant secrets.
Before deploying an updated Harmony application into an existing tenant, rerun
tenant create --apply with the updated harmony-auth binary. Policy
reconciliation must grant the state subpath before the application migrates any
legacy generated values from the tenant root.
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 |
|---|---|
context configure |
context, path |
connection check |
zitadel: { connected }, openbao: { connected } |
identity list |
identities: [{ identity, access }] |
identity show |
identity, access |
tenant list |
tenants: [{ scope, humans, services }] |
tenant create |
tenant, applied |
tenant deployer create |
tenant, account, credentials, applied |
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
- named Harmony context profile loading
- 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.
- Tenant deployer creation can create a Zitadel machine identity; general identity creation is not implemented.
- The CLI does not provide a generic OpenBao policy editor.
- OKD group-to-RBAC mapping, read-only viewer RBAC, short-lived Kubernetes credentials, and WireGuard identity linking remain deferred.
- Tenant administrators are not yet authenticated as constrained actors; the supplied OpenBao token determines backend authority.
- Context profiles are local mode-
0600files; credential refresh and remote profile synchronization are not implemented.