aac-cli 0.1.1__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aac_cli-0.1.1 → aac_cli-0.1.2}/PKG-INFO +228 -6
- {aac_cli-0.1.1 → aac_cli-0.1.2}/README.md +226 -5
- {aac_cli-0.1.1 → aac_cli-0.1.2}/aac_cli/__init__.py +1 -1
- aac_cli-0.1.2/aac_cli/admin_key_pem.py +236 -0
- aac_cli-0.1.2/aac_cli/api_key_rotation_state.py +192 -0
- aac_cli-0.1.2/aac_cli/cli.py +5411 -0
- {aac_cli-0.1.1 → aac_cli-0.1.2}/aac_cli/config.py +27 -37
- aac_cli-0.1.2/aac_cli/idp_recovery.py +489 -0
- {aac_cli-0.1.1 → aac_cli-0.1.2}/aac_cli/profiles.py +16 -14
- {aac_cli-0.1.1 → aac_cli-0.1.2}/aac_cli/registration_state.py +135 -75
- {aac_cli-0.1.1 → aac_cli-0.1.2}/aac_cli/sso_login.py +55 -34
- {aac_cli-0.1.1 → aac_cli-0.1.2}/aac_cli.egg-info/PKG-INFO +228 -6
- {aac_cli-0.1.1 → aac_cli-0.1.2}/aac_cli.egg-info/SOURCES.txt +8 -0
- {aac_cli-0.1.1 → aac_cli-0.1.2}/aac_cli.egg-info/requires.txt +1 -0
- {aac_cli-0.1.1 → aac_cli-0.1.2}/pyproject.toml +5 -2
- aac_cli-0.1.2/tests/test_aac_cli.py +3254 -0
- aac_cli-0.1.2/tests/test_admin_key_pem.py +331 -0
- aac_cli-0.1.2/tests/test_api_key_reissue_cli.py +313 -0
- aac_cli-0.1.2/tests/test_api_key_rotation_cli.py +895 -0
- aac_cli-0.1.2/tests/test_api_key_rotation_state.py +177 -0
- aac_cli-0.1.2/tests/test_idp_recovery_cli.py +1140 -0
- {aac_cli-0.1.1 → aac_cli-0.1.2}/tests/test_profile_cli.py +32 -73
- {aac_cli-0.1.1 → aac_cli-0.1.2}/tests/test_registration_recovery_cli.py +67 -16
- {aac_cli-0.1.1 → aac_cli-0.1.2}/tests/test_registration_state.py +71 -35
- {aac_cli-0.1.1 → aac_cli-0.1.2}/tests/test_sso_login_cli.py +204 -132
- aac_cli-0.1.1/aac_cli/cli.py +0 -2780
- aac_cli-0.1.1/tests/test_aac_cli.py +0 -1734
- {aac_cli-0.1.1 → aac_cli-0.1.2}/LICENSE +0 -0
- {aac_cli-0.1.1 → aac_cli-0.1.2}/aac_cli/__main__.py +0 -0
- {aac_cli-0.1.1 → aac_cli-0.1.2}/aac_cli.egg-info/dependency_links.txt +0 -0
- {aac_cli-0.1.1 → aac_cli-0.1.2}/aac_cli.egg-info/entry_points.txt +0 -0
- {aac_cli-0.1.1 → aac_cli-0.1.2}/aac_cli.egg-info/top_level.txt +0 -0
- {aac_cli-0.1.1 → aac_cli-0.1.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aac-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: The aac platform CLI — headless operator interface to the AAC control plane (tenant registration, chain audit).
|
|
5
5
|
Author: Agent Authority Cloud Project
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -8,6 +8,7 @@ Requires-Python: >=3.10
|
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Dist: httpx>=0.28
|
|
11
|
+
Requires-Dist: cryptography>=42.0
|
|
11
12
|
Provides-Extra: test
|
|
12
13
|
Requires-Dist: pytest>=8.0; extra == "test"
|
|
13
14
|
Requires-Dist: pytest-httpx>=0.30; extra == "test"
|
|
@@ -101,6 +102,48 @@ aac tenant list # which tenant ids exist (no 409
|
|
|
101
102
|
aac tenant describe --tenant-id tnt-<uuid> # workloads + key METADATA
|
|
102
103
|
aac tenant update --tenant-id tnt-<uuid> --display-name "ACME Manufacturing Inc"
|
|
103
104
|
|
|
105
|
+
# B121/B228 — business-domain control proof and bounded claim lifecycle.
|
|
106
|
+
# Registration can capture up to 16 pending rows with repeatable --tenant-domain.
|
|
107
|
+
# The issue/verify/release/revoke commands
|
|
108
|
+
# require the owning tenant's tenant-admin session and default --tenant-id from
|
|
109
|
+
# the profile; the established tenant describe command keeps its explicit id.
|
|
110
|
+
aac tenant register --display-name "ACME" --contact ops@acme.example \
|
|
111
|
+
--tenant-domain acme.example
|
|
112
|
+
aac tenant issue-domain-challenge --domain acme.example
|
|
113
|
+
# Publish the exact record printed above, then:
|
|
114
|
+
aac tenant verify-domain --domain acme.example
|
|
115
|
+
# B182 keeps verification and binding explicit. The binding command consumes
|
|
116
|
+
# current exact-name evidence (and lazily revalidates when due); verify-domain
|
|
117
|
+
# never performs an implicit binding write.
|
|
118
|
+
aac tenant bind-trust-domain --trust-domain acme.example
|
|
119
|
+
aac tenant list-trust-domains --output table
|
|
120
|
+
aac tenant describe --tenant-id tnt-<uuid> --output table # status + TXT
|
|
121
|
+
aac tenant release-domain --domain acme.example --reason "planned transfer"
|
|
122
|
+
aac tenant revoke-domain --domain acme.example --reason "DNS control incident"
|
|
123
|
+
|
|
124
|
+
# If bind-trust-domain reports missing proof, its printed issue/verify/rerun
|
|
125
|
+
# commands pin the selected profile, effective admin URL, tenant id, and exact
|
|
126
|
+
# domain. They never print a session, bootstrap token, or challenge value.
|
|
127
|
+
|
|
128
|
+
# B153 — replace the tenant-admin key. Accepts and transmits the PUBLIC half
|
|
129
|
+
# ONLY. It never generates or writes private-key material, and it refuses a
|
|
130
|
+
# private key without transmitting or logging it — it does read the file you
|
|
131
|
+
# name, because refusing requires inspecting it.
|
|
132
|
+
# Replacement is IMMEDIATE and forward-only: the previous key stops
|
|
133
|
+
# verifying ingest JWSs at once (admin keys get NO grace window) and can
|
|
134
|
+
# never be reinstated.
|
|
135
|
+
openssl genpkey -algorithm ed25519 -out tenant-admin.pem
|
|
136
|
+
openssl pkey -in tenant-admin.pem -pubout -out tenant-admin.public.pem
|
|
137
|
+
aac tenant rotate-admin-key --tenant-admin-pubkey-file tenant-admin.public.pem
|
|
138
|
+
# BEFORE running it in production: stage the new PRIVATE key on the
|
|
139
|
+
# trust-anchor publisher host and be ready to restart it. The restart
|
|
140
|
+
# procedure DIFFERS by deployment path — a Docker publisher's env and key
|
|
141
|
+
# bind-mount are fixed at container creation, so a plain restart comes back
|
|
142
|
+
# on the OLD key. Follow trust_anchor_publisher/INSTALL.md, "Rotating the
|
|
143
|
+
# tenant-admin key". Publication stays paused until it restarts.
|
|
144
|
+
# --bootstrap-token (or $AAC_BOOTSTRAP_TOKEN) lets a ceremony operator
|
|
145
|
+
# rotate for a tenant that has no session.
|
|
146
|
+
|
|
104
147
|
# B172 — post-registration workload lifecycle. --tenant-id is optional
|
|
105
148
|
# here: the selected profile supplies it by default. JSON is default;
|
|
106
149
|
# every command also supports --output table.
|
|
@@ -130,17 +173,90 @@ aac trust-anchor list --tenant-id tnt-<uuid> # both roles, all states
|
|
|
130
173
|
aac trust-anchor list --role tenant-admin # ingest-signing keys only
|
|
131
174
|
aac trust-anchor describe --kid key-1 # lifecycle + public PEM
|
|
132
175
|
aac trust-anchor ingest-history --artifact-class root-keys --limit 20
|
|
176
|
+
# Session-only emergency/environment-retirement operation. Terminal, may
|
|
177
|
+
# revoke the last root, and cannot be authorized by bootstrap token/API key:
|
|
178
|
+
aac trust-anchor revoke --tenant-id tnt-<uuid> --kid key-1 \
|
|
179
|
+
--reason "root signing key suspected compromised"
|
|
180
|
+
|
|
181
|
+
# B144 — before the first tenant IdP, Tenant Ops generates an Ed25519
|
|
182
|
+
# offline recovery keypair. The private PEM stays offline and mode 0600;
|
|
183
|
+
# only the non-secret enrollment JSON crosses to AAC Ops/control plane.
|
|
184
|
+
aac sso generate-idp-recovery-key \
|
|
185
|
+
--private-key-file offline-idp-recovery-private.pem \
|
|
186
|
+
--enrollment-file idp-recovery-enrollment.json
|
|
187
|
+
aac sso enroll-idp-recovery-key \
|
|
188
|
+
--tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
189
|
+
--file idp-recovery-enrollment.json \
|
|
190
|
+
--bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
|
|
133
191
|
|
|
134
192
|
# B140 — register a tenant↔IdP connection (Eng Spec §XVII.4; the sso
|
|
135
193
|
# noun). The connection config rides as a JSON file (nested claims
|
|
136
|
-
# mapping — see --help for an Entra example).
|
|
137
|
-
#
|
|
194
|
+
# mapping — see --help for an Entra example). Initial recovery-key
|
|
195
|
+
# enrollment above happens before first-IdP registration. First-IdP
|
|
196
|
+
# registration is the ops ceremony; later IdPs ride a cached session:
|
|
138
197
|
# (--tenant-id takes the canonical tnt-<uuid> id captured at your
|
|
139
198
|
# tenant's registration — B154 PR 5: a dotted value fails locally.)
|
|
140
199
|
aac sso register-idp --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
141
200
|
--file connection.json \
|
|
142
201
|
--bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
|
|
143
202
|
|
|
203
|
+
# B151 — discover the opaque connection id and current revision without a
|
|
204
|
+
# session. The public response contains only safe issuer metadata, a stable
|
|
205
|
+
# idp_connections.id, and the numeric ETag needed for correction:
|
|
206
|
+
aac sso list-idp --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000
|
|
207
|
+
|
|
208
|
+
# B151 — correct the COMPLETE connection document. Pin the revision you
|
|
209
|
+
# reviewed; the CLI sends it with the cached tenant-admin session and never
|
|
210
|
+
# substitutes a newer revision. Repeating after a lost response is safe:
|
|
211
|
+
aac sso replace-idp --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
212
|
+
--connection-id <id-from-list-idp> \
|
|
213
|
+
--revision <revision-from-list-idp> \
|
|
214
|
+
--file corrected-connection.json
|
|
215
|
+
|
|
216
|
+
# B144 — if the IdP is unavailable, the same B151 replacement is authorized
|
|
217
|
+
# by a three-command two-party ceremony. Tenant Ops signs the exact corrected
|
|
218
|
+
# file and receives a request id plus REPAIR-INTENT fingerprint. AAC Ops
|
|
219
|
+
# compares both through the incident channel, approves that exact intent, and
|
|
220
|
+
# manually notifies Tenant Ops. Tenant Ops then runs replace-idp with the same
|
|
221
|
+
# request/file/revision/key. The CLI internally redeems a ten-minute,
|
|
222
|
+
# single-use capability, holds it only in memory, and never prints or stores it.
|
|
223
|
+
# It grants no login, data-plane access, or general administration.
|
|
224
|
+
aac sso request-idp-repair \
|
|
225
|
+
--tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
226
|
+
--connection-id <id-from-list-idp> \
|
|
227
|
+
--revision <revision-from-list-idp> \
|
|
228
|
+
--file corrected-connection.json \
|
|
229
|
+
--recovery-key-file offline-idp-recovery-private.pem
|
|
230
|
+
aac sso approve-idp-repair \
|
|
231
|
+
--tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
232
|
+
--connection-id <id-from-list-idp> \
|
|
233
|
+
--repair-request-id <tenant-provided-request-id> \
|
|
234
|
+
--repair-intent-fingerprint sha256:<tenant-provided-intent-hash> \
|
|
235
|
+
--bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
|
|
236
|
+
aac sso replace-idp \
|
|
237
|
+
--tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
238
|
+
--connection-id <id-from-list-idp> \
|
|
239
|
+
--revision <same-request-revision> \
|
|
240
|
+
--file corrected-connection.json \
|
|
241
|
+
--repair-request-id <approved-request-id> \
|
|
242
|
+
--recovery-key-file offline-idp-recovery-private.pem
|
|
243
|
+
|
|
244
|
+
# These fingerprints are different: recovery_key_fingerprint identifies the
|
|
245
|
+
# enrolled Ed25519 PUBLIC key; repair_intent_fingerprint identifies one exact
|
|
246
|
+
# tenant/connection/revision/document/key intent. Neither replaces the other.
|
|
247
|
+
|
|
248
|
+
# Recovery-key lifecycle is normal-session administration. Generate a fresh
|
|
249
|
+
# pair before rotation; old material cannot return. Revocation is terminal.
|
|
250
|
+
aac sso list-idp-recovery-keys --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000
|
|
251
|
+
aac sso rotate-idp-recovery-key \
|
|
252
|
+
--tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
253
|
+
--file new-idp-recovery-enrollment.json \
|
|
254
|
+
--replaces-recovery-key-id <current-active-key-id>
|
|
255
|
+
aac sso revoke-idp-recovery-key \
|
|
256
|
+
--tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
257
|
+
--recovery-key-id <current-active-key-id> \
|
|
258
|
+
--reason "offline media lost"
|
|
259
|
+
|
|
144
260
|
# B142 — register a SHARED developer-tier connection (platform
|
|
145
261
|
# operator ceremony ONLY; owned by no tenant). GitHub registers with
|
|
146
262
|
# jwks_static {"keys": []} + explicit endpoints (GitHub is not an OIDC
|
|
@@ -248,6 +364,104 @@ one-time API key. The CLI binds the profile and transitions the state to
|
|
|
248
364
|
`completed_credential_reissue_required`, directing the operator to the
|
|
249
365
|
separate B173 reissue capability; B171 does not implement reissue.
|
|
250
366
|
|
|
367
|
+
### Replace or recover a tenant API key (B173)
|
|
368
|
+
|
|
369
|
+
After establishing an own-tenant `tenant-admin` session, run:
|
|
370
|
+
|
|
371
|
+
```console
|
|
372
|
+
aac tenant reissue-api-key \
|
|
373
|
+
--tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
374
|
+
--profile production
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
This is immediate replacement, not B98/B189 graceful rotation: the server
|
|
378
|
+
revokes every ACTIVE tenant API key and returns one fresh `aac_ak_*` value once.
|
|
379
|
+
The CLI makes one HTTP request per invocation and atomically replaces the
|
|
380
|
+
mode-`0600` credential file only after validating the complete success
|
|
381
|
+
response. A transport interruption leaves the local file untouched and tells
|
|
382
|
+
the administrator to run the command again; the next explicit invocation is a
|
|
383
|
+
fresh attempt that revokes any unknown prior result. There is no automatic
|
|
384
|
+
retry or secret-response journal. A successful B173 replacement also removes
|
|
385
|
+
any superseded non-secret B189 local rotation record. It does not silently
|
|
386
|
+
delete the separate staged key file; that value is now revoked and must be
|
|
387
|
+
removed through the approved secure-file procedure.
|
|
388
|
+
|
|
389
|
+
After success the CLI prints copy/paste-ready `aac trust-anchor list` and curl
|
|
390
|
+
commands for API-key-only verification against `GET /v1/trust-anchors`. The
|
|
391
|
+
curl recipe streams the stored credential through stdin configuration rather
|
|
392
|
+
than writing plaintext into shell history or an OS process argument.
|
|
393
|
+
|
|
394
|
+
Operator-assisted onboarding recovery is exceptional and requires both the
|
|
395
|
+
explicitly opened bootstrap ceremony and the matching completed registration
|
|
396
|
+
locator. Load `AAC_BOOTSTRAP_TOKEN` through the approved secret-handling
|
|
397
|
+
process, then run:
|
|
398
|
+
|
|
399
|
+
```console
|
|
400
|
+
aac tenant reissue-api-key \
|
|
401
|
+
--tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
402
|
+
--registration-request-id treq-550e8400-e29b-41d4-a716-446655440000 \
|
|
403
|
+
--profile onboarding
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
The server admits this only before a tenant login path exists. The `treq-*`
|
|
407
|
+
value is a locator, never a credential. Tenant API keys are separate from
|
|
408
|
+
tenant-admin Ed25519 keys and B144 offline IdP-recovery material. See
|
|
409
|
+
**SOP-010 — Recover or replace a tenant API key** in the AAC design vault's
|
|
410
|
+
`sops/` directory for the tenant-administrator and AAC Ops procedures.
|
|
411
|
+
|
|
412
|
+
### Rotate a tenant API key without downtime (B189)
|
|
413
|
+
|
|
414
|
+
Planned rotation uses a nested command family and an own-tenant
|
|
415
|
+
`tenant-admin` AAC session:
|
|
416
|
+
|
|
417
|
+
```console
|
|
418
|
+
aac tenant api-key list --profile production
|
|
419
|
+
aac tenant api-key issue --profile production
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
`issue` saves a non-secret `akr-*` issuance identity before sending one HTTP
|
|
423
|
+
request. It writes the returned plaintext only to a new mode-`0600` staged
|
|
424
|
+
file (default `~/.aac/credentials/<tenant-id>.next`), refuses existing files
|
|
425
|
+
and symlinks, requires any custom parent directory to exist, preserves that
|
|
426
|
+
directory's permissions, and never replaces the current profile credential. Its success
|
|
427
|
+
output contains metadata and a curl recipe that reads the staged file without
|
|
428
|
+
putting plaintext in shell history or process arguments.
|
|
429
|
+
|
|
430
|
+
Move every client to the staged key through the approved secret store, verify
|
|
431
|
+
client traffic, then list again and select the old key by its exact non-secret
|
|
432
|
+
`key_id`:
|
|
433
|
+
|
|
434
|
+
```console
|
|
435
|
+
aac tenant api-key retire \
|
|
436
|
+
--key-id ak_0123456789abcdef \
|
|
437
|
+
--yes \
|
|
438
|
+
--profile production
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Retirement requires two ACTIVE keys and is terminal. The CLI fetches the
|
|
442
|
+
authoritative set immediately before the request, names the exact key that will
|
|
443
|
+
remain, and never guesses by age or position. It does not silently promote the
|
|
444
|
+
staged file into the current credential location. Do that only through the
|
|
445
|
+
approved local secret-handling procedure after the selected old key retires.
|
|
446
|
+
|
|
447
|
+
If an issue response is ambiguous, rerun the same command. The saved identity
|
|
448
|
+
is reused: a non-committed attempt may return the new plaintext, while a
|
|
449
|
+
committed attempt reports that plaintext is unavailable and identifies the
|
|
450
|
+
unknown new row through `api-key list`. Retire that unknown row, leaving the
|
|
451
|
+
known old credential ACTIVE, then issue again. No automatic retry or plaintext
|
|
452
|
+
journal exists. A profile with unresolved rotation state cannot be deleted.
|
|
453
|
+
See **SOP-011 — Rotate a tenant API key without downtime** in the design vault.
|
|
454
|
+
|
|
455
|
+
`~/.aac/idp-repair-requests/<tenant_id>/<connection_id>.json` is B144's
|
|
456
|
+
separate AAC-managed IdP repair record (directory 0700, file 0600). It is
|
|
457
|
+
written before `request-idp-repair` transmits anything and contains the exact
|
|
458
|
+
connection document, document digest, request id, recovery-key fingerprint,
|
|
459
|
+
and public signature. It contains neither the offline private key nor the
|
|
460
|
+
short-lived AAC repair capability. An identical retry reuses the request id;
|
|
461
|
+
a different live intent is refused locally. Preserve this file with the
|
|
462
|
+
incident until replacement is complete, and do not edit, rename, copy between
|
|
463
|
+
tenants, or delete it to “cancel” a request that may already exist server-side.
|
|
464
|
+
|
|
251
465
|
### `AAC_CLI_HOME` — isolated homes
|
|
252
466
|
|
|
253
467
|
`AAC_CLI_HOME` relocates the whole `~/.aac` tree (config +
|
|
@@ -298,9 +512,14 @@ captures it into the selected profile, and it is immutable, public,
|
|
|
298
512
|
and never reused — like an AWS account ID. `--parent-tenant-id` takes
|
|
299
513
|
the parent org's canonical `tnt-` id — the server resolves handles to
|
|
300
514
|
internal row ids; no database identifier ever crosses the API (B118
|
|
301
|
-
D3/D4).
|
|
302
|
-
|
|
303
|
-
|
|
515
|
+
D3/D4). B121 domain ownership verification is a separate, additive
|
|
516
|
+
control tracked in `tenant_domains`: `_aac-challenge.<domain>.` TXT
|
|
517
|
+
`aac-verify=<token>`, issued/verified through the tenant-admin commands
|
|
518
|
+
above. A verified business domain is evidence only. B182's separate
|
|
519
|
+
`bind-trust-domain` request admits a domain's first SPIFFE binding only from
|
|
520
|
+
B228-current evidence for the exact same name; parent-domain proof does not
|
|
521
|
+
cover subdomains. SPIFFE-valid values outside the DNS proof grammar remain
|
|
522
|
+
ceremony-only, and existing binding history remains B181.
|
|
304
523
|
|
|
305
524
|
## Semantics worth knowing
|
|
306
525
|
|
|
@@ -318,6 +537,9 @@ flow ships in a follow-up increment.
|
|
|
318
537
|
configuration or state (missing profile/credential, malformed INI,
|
|
319
538
|
bad registration state) / 4 transport failure (unreachable or timed
|
|
320
539
|
out — the retryable class).
|
|
540
|
+
* B159 rate limiting: a numeric server `Retry-After` is rendered beside
|
|
541
|
+
the stable 429/503 error. The CLI never automatically replays a
|
|
542
|
+
mutation; wait for the displayed interval and rerun deliberately.
|
|
321
543
|
|
|
322
544
|
## Tests
|
|
323
545
|
|
|
@@ -86,6 +86,48 @@ aac tenant list # which tenant ids exist (no 409
|
|
|
86
86
|
aac tenant describe --tenant-id tnt-<uuid> # workloads + key METADATA
|
|
87
87
|
aac tenant update --tenant-id tnt-<uuid> --display-name "ACME Manufacturing Inc"
|
|
88
88
|
|
|
89
|
+
# B121/B228 — business-domain control proof and bounded claim lifecycle.
|
|
90
|
+
# Registration can capture up to 16 pending rows with repeatable --tenant-domain.
|
|
91
|
+
# The issue/verify/release/revoke commands
|
|
92
|
+
# require the owning tenant's tenant-admin session and default --tenant-id from
|
|
93
|
+
# the profile; the established tenant describe command keeps its explicit id.
|
|
94
|
+
aac tenant register --display-name "ACME" --contact ops@acme.example \
|
|
95
|
+
--tenant-domain acme.example
|
|
96
|
+
aac tenant issue-domain-challenge --domain acme.example
|
|
97
|
+
# Publish the exact record printed above, then:
|
|
98
|
+
aac tenant verify-domain --domain acme.example
|
|
99
|
+
# B182 keeps verification and binding explicit. The binding command consumes
|
|
100
|
+
# current exact-name evidence (and lazily revalidates when due); verify-domain
|
|
101
|
+
# never performs an implicit binding write.
|
|
102
|
+
aac tenant bind-trust-domain --trust-domain acme.example
|
|
103
|
+
aac tenant list-trust-domains --output table
|
|
104
|
+
aac tenant describe --tenant-id tnt-<uuid> --output table # status + TXT
|
|
105
|
+
aac tenant release-domain --domain acme.example --reason "planned transfer"
|
|
106
|
+
aac tenant revoke-domain --domain acme.example --reason "DNS control incident"
|
|
107
|
+
|
|
108
|
+
# If bind-trust-domain reports missing proof, its printed issue/verify/rerun
|
|
109
|
+
# commands pin the selected profile, effective admin URL, tenant id, and exact
|
|
110
|
+
# domain. They never print a session, bootstrap token, or challenge value.
|
|
111
|
+
|
|
112
|
+
# B153 — replace the tenant-admin key. Accepts and transmits the PUBLIC half
|
|
113
|
+
# ONLY. It never generates or writes private-key material, and it refuses a
|
|
114
|
+
# private key without transmitting or logging it — it does read the file you
|
|
115
|
+
# name, because refusing requires inspecting it.
|
|
116
|
+
# Replacement is IMMEDIATE and forward-only: the previous key stops
|
|
117
|
+
# verifying ingest JWSs at once (admin keys get NO grace window) and can
|
|
118
|
+
# never be reinstated.
|
|
119
|
+
openssl genpkey -algorithm ed25519 -out tenant-admin.pem
|
|
120
|
+
openssl pkey -in tenant-admin.pem -pubout -out tenant-admin.public.pem
|
|
121
|
+
aac tenant rotate-admin-key --tenant-admin-pubkey-file tenant-admin.public.pem
|
|
122
|
+
# BEFORE running it in production: stage the new PRIVATE key on the
|
|
123
|
+
# trust-anchor publisher host and be ready to restart it. The restart
|
|
124
|
+
# procedure DIFFERS by deployment path — a Docker publisher's env and key
|
|
125
|
+
# bind-mount are fixed at container creation, so a plain restart comes back
|
|
126
|
+
# on the OLD key. Follow trust_anchor_publisher/INSTALL.md, "Rotating the
|
|
127
|
+
# tenant-admin key". Publication stays paused until it restarts.
|
|
128
|
+
# --bootstrap-token (or $AAC_BOOTSTRAP_TOKEN) lets a ceremony operator
|
|
129
|
+
# rotate for a tenant that has no session.
|
|
130
|
+
|
|
89
131
|
# B172 — post-registration workload lifecycle. --tenant-id is optional
|
|
90
132
|
# here: the selected profile supplies it by default. JSON is default;
|
|
91
133
|
# every command also supports --output table.
|
|
@@ -115,17 +157,90 @@ aac trust-anchor list --tenant-id tnt-<uuid> # both roles, all states
|
|
|
115
157
|
aac trust-anchor list --role tenant-admin # ingest-signing keys only
|
|
116
158
|
aac trust-anchor describe --kid key-1 # lifecycle + public PEM
|
|
117
159
|
aac trust-anchor ingest-history --artifact-class root-keys --limit 20
|
|
160
|
+
# Session-only emergency/environment-retirement operation. Terminal, may
|
|
161
|
+
# revoke the last root, and cannot be authorized by bootstrap token/API key:
|
|
162
|
+
aac trust-anchor revoke --tenant-id tnt-<uuid> --kid key-1 \
|
|
163
|
+
--reason "root signing key suspected compromised"
|
|
164
|
+
|
|
165
|
+
# B144 — before the first tenant IdP, Tenant Ops generates an Ed25519
|
|
166
|
+
# offline recovery keypair. The private PEM stays offline and mode 0600;
|
|
167
|
+
# only the non-secret enrollment JSON crosses to AAC Ops/control plane.
|
|
168
|
+
aac sso generate-idp-recovery-key \
|
|
169
|
+
--private-key-file offline-idp-recovery-private.pem \
|
|
170
|
+
--enrollment-file idp-recovery-enrollment.json
|
|
171
|
+
aac sso enroll-idp-recovery-key \
|
|
172
|
+
--tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
173
|
+
--file idp-recovery-enrollment.json \
|
|
174
|
+
--bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
|
|
118
175
|
|
|
119
176
|
# B140 — register a tenant↔IdP connection (Eng Spec §XVII.4; the sso
|
|
120
177
|
# noun). The connection config rides as a JSON file (nested claims
|
|
121
|
-
# mapping — see --help for an Entra example).
|
|
122
|
-
#
|
|
178
|
+
# mapping — see --help for an Entra example). Initial recovery-key
|
|
179
|
+
# enrollment above happens before first-IdP registration. First-IdP
|
|
180
|
+
# registration is the ops ceremony; later IdPs ride a cached session:
|
|
123
181
|
# (--tenant-id takes the canonical tnt-<uuid> id captured at your
|
|
124
182
|
# tenant's registration — B154 PR 5: a dotted value fails locally.)
|
|
125
183
|
aac sso register-idp --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
126
184
|
--file connection.json \
|
|
127
185
|
--bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
|
|
128
186
|
|
|
187
|
+
# B151 — discover the opaque connection id and current revision without a
|
|
188
|
+
# session. The public response contains only safe issuer metadata, a stable
|
|
189
|
+
# idp_connections.id, and the numeric ETag needed for correction:
|
|
190
|
+
aac sso list-idp --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000
|
|
191
|
+
|
|
192
|
+
# B151 — correct the COMPLETE connection document. Pin the revision you
|
|
193
|
+
# reviewed; the CLI sends it with the cached tenant-admin session and never
|
|
194
|
+
# substitutes a newer revision. Repeating after a lost response is safe:
|
|
195
|
+
aac sso replace-idp --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
196
|
+
--connection-id <id-from-list-idp> \
|
|
197
|
+
--revision <revision-from-list-idp> \
|
|
198
|
+
--file corrected-connection.json
|
|
199
|
+
|
|
200
|
+
# B144 — if the IdP is unavailable, the same B151 replacement is authorized
|
|
201
|
+
# by a three-command two-party ceremony. Tenant Ops signs the exact corrected
|
|
202
|
+
# file and receives a request id plus REPAIR-INTENT fingerprint. AAC Ops
|
|
203
|
+
# compares both through the incident channel, approves that exact intent, and
|
|
204
|
+
# manually notifies Tenant Ops. Tenant Ops then runs replace-idp with the same
|
|
205
|
+
# request/file/revision/key. The CLI internally redeems a ten-minute,
|
|
206
|
+
# single-use capability, holds it only in memory, and never prints or stores it.
|
|
207
|
+
# It grants no login, data-plane access, or general administration.
|
|
208
|
+
aac sso request-idp-repair \
|
|
209
|
+
--tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
210
|
+
--connection-id <id-from-list-idp> \
|
|
211
|
+
--revision <revision-from-list-idp> \
|
|
212
|
+
--file corrected-connection.json \
|
|
213
|
+
--recovery-key-file offline-idp-recovery-private.pem
|
|
214
|
+
aac sso approve-idp-repair \
|
|
215
|
+
--tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
216
|
+
--connection-id <id-from-list-idp> \
|
|
217
|
+
--repair-request-id <tenant-provided-request-id> \
|
|
218
|
+
--repair-intent-fingerprint sha256:<tenant-provided-intent-hash> \
|
|
219
|
+
--bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
|
|
220
|
+
aac sso replace-idp \
|
|
221
|
+
--tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
222
|
+
--connection-id <id-from-list-idp> \
|
|
223
|
+
--revision <same-request-revision> \
|
|
224
|
+
--file corrected-connection.json \
|
|
225
|
+
--repair-request-id <approved-request-id> \
|
|
226
|
+
--recovery-key-file offline-idp-recovery-private.pem
|
|
227
|
+
|
|
228
|
+
# These fingerprints are different: recovery_key_fingerprint identifies the
|
|
229
|
+
# enrolled Ed25519 PUBLIC key; repair_intent_fingerprint identifies one exact
|
|
230
|
+
# tenant/connection/revision/document/key intent. Neither replaces the other.
|
|
231
|
+
|
|
232
|
+
# Recovery-key lifecycle is normal-session administration. Generate a fresh
|
|
233
|
+
# pair before rotation; old material cannot return. Revocation is terminal.
|
|
234
|
+
aac sso list-idp-recovery-keys --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000
|
|
235
|
+
aac sso rotate-idp-recovery-key \
|
|
236
|
+
--tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
237
|
+
--file new-idp-recovery-enrollment.json \
|
|
238
|
+
--replaces-recovery-key-id <current-active-key-id>
|
|
239
|
+
aac sso revoke-idp-recovery-key \
|
|
240
|
+
--tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
241
|
+
--recovery-key-id <current-active-key-id> \
|
|
242
|
+
--reason "offline media lost"
|
|
243
|
+
|
|
129
244
|
# B142 — register a SHARED developer-tier connection (platform
|
|
130
245
|
# operator ceremony ONLY; owned by no tenant). GitHub registers with
|
|
131
246
|
# jwks_static {"keys": []} + explicit endpoints (GitHub is not an OIDC
|
|
@@ -233,6 +348,104 @@ one-time API key. The CLI binds the profile and transitions the state to
|
|
|
233
348
|
`completed_credential_reissue_required`, directing the operator to the
|
|
234
349
|
separate B173 reissue capability; B171 does not implement reissue.
|
|
235
350
|
|
|
351
|
+
### Replace or recover a tenant API key (B173)
|
|
352
|
+
|
|
353
|
+
After establishing an own-tenant `tenant-admin` session, run:
|
|
354
|
+
|
|
355
|
+
```console
|
|
356
|
+
aac tenant reissue-api-key \
|
|
357
|
+
--tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
358
|
+
--profile production
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
This is immediate replacement, not B98/B189 graceful rotation: the server
|
|
362
|
+
revokes every ACTIVE tenant API key and returns one fresh `aac_ak_*` value once.
|
|
363
|
+
The CLI makes one HTTP request per invocation and atomically replaces the
|
|
364
|
+
mode-`0600` credential file only after validating the complete success
|
|
365
|
+
response. A transport interruption leaves the local file untouched and tells
|
|
366
|
+
the administrator to run the command again; the next explicit invocation is a
|
|
367
|
+
fresh attempt that revokes any unknown prior result. There is no automatic
|
|
368
|
+
retry or secret-response journal. A successful B173 replacement also removes
|
|
369
|
+
any superseded non-secret B189 local rotation record. It does not silently
|
|
370
|
+
delete the separate staged key file; that value is now revoked and must be
|
|
371
|
+
removed through the approved secure-file procedure.
|
|
372
|
+
|
|
373
|
+
After success the CLI prints copy/paste-ready `aac trust-anchor list` and curl
|
|
374
|
+
commands for API-key-only verification against `GET /v1/trust-anchors`. The
|
|
375
|
+
curl recipe streams the stored credential through stdin configuration rather
|
|
376
|
+
than writing plaintext into shell history or an OS process argument.
|
|
377
|
+
|
|
378
|
+
Operator-assisted onboarding recovery is exceptional and requires both the
|
|
379
|
+
explicitly opened bootstrap ceremony and the matching completed registration
|
|
380
|
+
locator. Load `AAC_BOOTSTRAP_TOKEN` through the approved secret-handling
|
|
381
|
+
process, then run:
|
|
382
|
+
|
|
383
|
+
```console
|
|
384
|
+
aac tenant reissue-api-key \
|
|
385
|
+
--tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
|
|
386
|
+
--registration-request-id treq-550e8400-e29b-41d4-a716-446655440000 \
|
|
387
|
+
--profile onboarding
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
The server admits this only before a tenant login path exists. The `treq-*`
|
|
391
|
+
value is a locator, never a credential. Tenant API keys are separate from
|
|
392
|
+
tenant-admin Ed25519 keys and B144 offline IdP-recovery material. See
|
|
393
|
+
**SOP-010 — Recover or replace a tenant API key** in the AAC design vault's
|
|
394
|
+
`sops/` directory for the tenant-administrator and AAC Ops procedures.
|
|
395
|
+
|
|
396
|
+
### Rotate a tenant API key without downtime (B189)
|
|
397
|
+
|
|
398
|
+
Planned rotation uses a nested command family and an own-tenant
|
|
399
|
+
`tenant-admin` AAC session:
|
|
400
|
+
|
|
401
|
+
```console
|
|
402
|
+
aac tenant api-key list --profile production
|
|
403
|
+
aac tenant api-key issue --profile production
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
`issue` saves a non-secret `akr-*` issuance identity before sending one HTTP
|
|
407
|
+
request. It writes the returned plaintext only to a new mode-`0600` staged
|
|
408
|
+
file (default `~/.aac/credentials/<tenant-id>.next`), refuses existing files
|
|
409
|
+
and symlinks, requires any custom parent directory to exist, preserves that
|
|
410
|
+
directory's permissions, and never replaces the current profile credential. Its success
|
|
411
|
+
output contains metadata and a curl recipe that reads the staged file without
|
|
412
|
+
putting plaintext in shell history or process arguments.
|
|
413
|
+
|
|
414
|
+
Move every client to the staged key through the approved secret store, verify
|
|
415
|
+
client traffic, then list again and select the old key by its exact non-secret
|
|
416
|
+
`key_id`:
|
|
417
|
+
|
|
418
|
+
```console
|
|
419
|
+
aac tenant api-key retire \
|
|
420
|
+
--key-id ak_0123456789abcdef \
|
|
421
|
+
--yes \
|
|
422
|
+
--profile production
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Retirement requires two ACTIVE keys and is terminal. The CLI fetches the
|
|
426
|
+
authoritative set immediately before the request, names the exact key that will
|
|
427
|
+
remain, and never guesses by age or position. It does not silently promote the
|
|
428
|
+
staged file into the current credential location. Do that only through the
|
|
429
|
+
approved local secret-handling procedure after the selected old key retires.
|
|
430
|
+
|
|
431
|
+
If an issue response is ambiguous, rerun the same command. The saved identity
|
|
432
|
+
is reused: a non-committed attempt may return the new plaintext, while a
|
|
433
|
+
committed attempt reports that plaintext is unavailable and identifies the
|
|
434
|
+
unknown new row through `api-key list`. Retire that unknown row, leaving the
|
|
435
|
+
known old credential ACTIVE, then issue again. No automatic retry or plaintext
|
|
436
|
+
journal exists. A profile with unresolved rotation state cannot be deleted.
|
|
437
|
+
See **SOP-011 — Rotate a tenant API key without downtime** in the design vault.
|
|
438
|
+
|
|
439
|
+
`~/.aac/idp-repair-requests/<tenant_id>/<connection_id>.json` is B144's
|
|
440
|
+
separate AAC-managed IdP repair record (directory 0700, file 0600). It is
|
|
441
|
+
written before `request-idp-repair` transmits anything and contains the exact
|
|
442
|
+
connection document, document digest, request id, recovery-key fingerprint,
|
|
443
|
+
and public signature. It contains neither the offline private key nor the
|
|
444
|
+
short-lived AAC repair capability. An identical retry reuses the request id;
|
|
445
|
+
a different live intent is refused locally. Preserve this file with the
|
|
446
|
+
incident until replacement is complete, and do not edit, rename, copy between
|
|
447
|
+
tenants, or delete it to “cancel” a request that may already exist server-side.
|
|
448
|
+
|
|
236
449
|
### `AAC_CLI_HOME` — isolated homes
|
|
237
450
|
|
|
238
451
|
`AAC_CLI_HOME` relocates the whole `~/.aac` tree (config +
|
|
@@ -283,9 +496,14 @@ captures it into the selected profile, and it is immutable, public,
|
|
|
283
496
|
and never reused — like an AWS account ID. `--parent-tenant-id` takes
|
|
284
497
|
the parent org's canonical `tnt-` id — the server resolves handles to
|
|
285
498
|
internal row ids; no database identifier ever crosses the API (B118
|
|
286
|
-
D3/D4).
|
|
287
|
-
|
|
288
|
-
|
|
499
|
+
D3/D4). B121 domain ownership verification is a separate, additive
|
|
500
|
+
control tracked in `tenant_domains`: `_aac-challenge.<domain>.` TXT
|
|
501
|
+
`aac-verify=<token>`, issued/verified through the tenant-admin commands
|
|
502
|
+
above. A verified business domain is evidence only. B182's separate
|
|
503
|
+
`bind-trust-domain` request admits a domain's first SPIFFE binding only from
|
|
504
|
+
B228-current evidence for the exact same name; parent-domain proof does not
|
|
505
|
+
cover subdomains. SPIFFE-valid values outside the DNS proof grammar remain
|
|
506
|
+
ceremony-only, and existing binding history remains B181.
|
|
289
507
|
|
|
290
508
|
## Semantics worth knowing
|
|
291
509
|
|
|
@@ -303,6 +521,9 @@ flow ships in a follow-up increment.
|
|
|
303
521
|
configuration or state (missing profile/credential, malformed INI,
|
|
304
522
|
bad registration state) / 4 transport failure (unreachable or timed
|
|
305
523
|
out — the retryable class).
|
|
524
|
+
* B159 rate limiting: a numeric server `Retry-After` is rendered beside
|
|
525
|
+
the stable 429/503 error. The CLI never automatically replays a
|
|
526
|
+
mutation; wait for the displayed interval and rerun deliberately.
|
|
306
527
|
|
|
307
528
|
## Tests
|
|
308
529
|
|