377 lines
11 KiB
Markdown
377 lines
11 KiB
Markdown
# 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 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
|
|
├── 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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```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
|
|
|
|
### 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 |
|
|
|---|---|
|
|
| `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 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
|
|
- 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.
|