Files
harmony/docs/guides/harmony-auth-cli.md

523 lines
18 KiB
Markdown

# 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 access
- `tenant`: 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
```text
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`:
```sh
harmony-auth connection check --context prod
```
Configure the context once:
```sh
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:
```sh
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:
```text
Zitadel connected
OpenBao connected
```
## Identity commands
### List identities
```sh
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:
```text
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
```sh
harmony-auth identity show 241696899342475267
```
Default output includes identity metadata, Harmony assignments, and imported
access:
```text
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:
```sh
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)
```sh
harmony-auth identity grant 241696899342475267 \
--permission tenant-admin \
--tenant devsights
```
Grant is review-only by default:
```text
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:
```sh
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`:
```sh
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:
```sh
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.
```sh
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:
```sh
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:
```sh
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:
```sh
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
```sh
harmony-auth tenant list
```
Tenants and projects are discovered from managed assignments and recognized
OpenBao policy paths:
```text
TENANT PROJECT HUMANS SERVICES
devsights All projects 2 0
devsights folk-timesheet 1 1
detexion harmony-fleet 0 2
```
### Show a tenant
```sh
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:
```text
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:
```json
{
"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 above
- `imported`: objects with `role_name`, `policy_name`, `secret_paths`,
`scopes`, and `effect`
- `roles`: objects with `name`, `subject_id`, `bound_audiences`, and `policies`
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:
```json
{
"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
```text
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-`0600` files; credential refresh and remote
profile synchronization are not implemented.