aac-cli 0.1.4__tar.gz → 0.1.5__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.
Files changed (51) hide show
  1. aac_cli-0.1.5/MANIFEST.in +5 -0
  2. aac_cli-0.1.5/PKG-INFO +537 -0
  3. aac_cli-0.1.5/README.md +520 -0
  4. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/__init__.py +1 -1
  5. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/cli.py +101 -75
  6. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/config.py +22 -26
  7. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/idp_recovery.py +2 -2
  8. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/init_cli.py +16 -1
  9. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/profiles.py +6 -9
  10. aac_cli-0.1.5/aac_cli/reference.py +480 -0
  11. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/registration_state.py +6 -7
  12. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/sso_login.py +13 -13
  13. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/workspace_layout.py +1 -1
  14. aac_cli-0.1.5/aac_cli.egg-info/PKG-INFO +537 -0
  15. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli.egg-info/SOURCES.txt +3 -18
  16. {aac_cli-0.1.4 → aac_cli-0.1.5}/pyproject.toml +1 -1
  17. aac_cli-0.1.4/PKG-INFO +0 -747
  18. aac_cli-0.1.4/README.md +0 -730
  19. aac_cli-0.1.4/aac_cli.egg-info/PKG-INFO +0 -747
  20. aac_cli-0.1.4/tests/test_aac_cli.py +0 -3254
  21. aac_cli-0.1.4/tests/test_admin_key_pem.py +0 -331
  22. aac_cli-0.1.4/tests/test_api_key_reissue_cli.py +0 -313
  23. aac_cli-0.1.4/tests/test_api_key_rotation_cli.py +0 -895
  24. aac_cli-0.1.4/tests/test_api_key_rotation_state.py +0 -177
  25. aac_cli-0.1.4/tests/test_b241_edge_paths.py +0 -925
  26. aac_cli-0.1.4/tests/test_config_render.py +0 -177
  27. aac_cli-0.1.4/tests/test_dev_material.py +0 -127
  28. aac_cli-0.1.4/tests/test_hosted_domains_cli.py +0 -569
  29. aac_cli-0.1.4/tests/test_idp_recovery_cli.py +0 -1140
  30. aac_cli-0.1.4/tests/test_init_cli.py +0 -1417
  31. aac_cli-0.1.4/tests/test_inventory_docs.py +0 -70
  32. aac_cli-0.1.4/tests/test_profile_cli.py +0 -841
  33. aac_cli-0.1.4/tests/test_registration_recovery_cli.py +0 -315
  34. aac_cli-0.1.4/tests/test_registration_state.py +0 -712
  35. aac_cli-0.1.4/tests/test_sso_login_cli.py +0 -1362
  36. aac_cli-0.1.4/tests/test_workspace_cli.py +0 -400
  37. {aac_cli-0.1.4 → aac_cli-0.1.5}/LICENSE +0 -0
  38. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/__main__.py +0 -0
  39. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/admin_key_pem.py +0 -0
  40. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/api_key_rotation_state.py +0 -0
  41. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/config_render.py +0 -0
  42. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/dev_material.py +0 -0
  43. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/secure_files.py +0 -0
  44. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/workspace_cli.py +0 -0
  45. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/workspace_health.py +0 -0
  46. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli/workspace_manifest.py +0 -0
  47. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli.egg-info/dependency_links.txt +0 -0
  48. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli.egg-info/entry_points.txt +0 -0
  49. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli.egg-info/requires.txt +0 -0
  50. {aac_cli-0.1.4 → aac_cli-0.1.5}/aac_cli.egg-info/top_level.txt +0 -0
  51. {aac_cli-0.1.4 → aac_cli-0.1.5}/setup.cfg +0 -0
@@ -0,0 +1,5 @@
1
+ # The sdist ships the package, its metadata and the public page only.
2
+ # setuptools would otherwise add tests/test_*.py by default; the tests are
3
+ # repository material (they cannot even run without the helper modules the
4
+ # default rule leaves out) and they carry internal project vocabulary.
5
+ prune tests
aac_cli-0.1.5/PKG-INFO ADDED
@@ -0,0 +1,537 @@
1
+ Metadata-Version: 2.4
2
+ Name: aac-cli
3
+ Version: 0.1.5
4
+ Summary: The aac platform CLI — headless operator interface to the AAC control plane (tenant registration, chain audit).
5
+ Author: Agent Authority Cloud Project
6
+ License-Expression: Apache-2.0
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: httpx>=0.28
11
+ Requires-Dist: cryptography>=42.0
12
+ Requires-Dist: PyYAML>=6.0
13
+ Provides-Extra: test
14
+ Requires-Dist: pytest>=8.0; extra == "test"
15
+ Requires-Dist: pytest-httpx>=0.30; extra == "test"
16
+ Dynamic: license-file
17
+
18
+ # aac-cli — the `aac` command
19
+
20
+ `aac` is the command-line tool for tenants of Agent Authority Cloud (AAC).
21
+ AAC lets an organisation's AI agents and services prove, on every request,
22
+ which tenant they belong to and what they were authorised to do. A
23
+ **tenant** is one organisation registered with AAC. Each of its workloads
24
+ runs beside an AAC sidecar that signs outgoing requests and verifies
25
+ incoming ones against public trust material the tenant publishes.
26
+
27
+ The `aac` command is how a tenant's operator does everything that is not
28
+ done by the sidecar itself: register the tenant, sign in, manage keys,
29
+ workloads and domains, and prepare a runnable local workload. Everything
30
+ works from a terminal or a script; there is no web console to click
31
+ through. Output is JSON by default, and every command that reports a
32
+ result also offers `--output table`.
33
+
34
+ Two kinds of tenant use the same tool:
35
+
36
+ * **Developer tenants** register themselves by signing in with GitHub or
37
+ Google. `aac init` takes a developer from an installed CLI to a runnable
38
+ local workload in one command.
39
+ * **Enterprise tenants** are registered through an onboarding ceremony with
40
+ AAC operations and sign in through their own identity provider. Their
41
+ keys, domains and workloads are managed with the `aac tenant` and
42
+ `aac sso` commands described further down.
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ pip install aac-cli
48
+ aac --version
49
+ ```
50
+
51
+ Python 3.10 or newer is required.
52
+
53
+ > **Mind the name.** `pip install aac` installs an unrelated project that
54
+ > happens to share the acronym. The AAC package is **`aac-cli`**; the
55
+ > command it installs is **`aac`**.
56
+
57
+ ## Guided setup for developer tenants: `aac init`
58
+
59
+ `aac init` signs you in, registers a developer tenant (or reuses the one
60
+ your profile is already bound to), takes the trust domain AAC assigns to the
61
+ tenant, registers a sample workload, generates development keys and
62
+ certificates, and writes a complete sidecar configuration together with the
63
+ files the trust-anchor publisher and Docker Compose need.
64
+
65
+ Point it at the endpoints your AAC environment gives you. The example below
66
+ uses the AAC stage environment:
67
+
68
+ ```bash
69
+ aac init --profile stage \
70
+ --admin-url https://api.stage.cascadeauth.dev \
71
+ --data-plane-url https://api.stage.cascadeauth.dev \
72
+ --trust-url https://trust.stage.cascadeauth.dev \
73
+ --display-name 'Example Team' --contact 'dev@example.com' --idp github
74
+ ```
75
+
76
+ The command shows what it will create and asks before registering the
77
+ tenant, because a tenant is permanent. In a script, where there is no
78
+ terminal to answer the question, pass `--create-tenant`:
79
+
80
+ ```bash
81
+ aac init --profile stage \
82
+ --admin-url https://api.stage.cascadeauth.dev \
83
+ --data-plane-url https://api.stage.cascadeauth.dev \
84
+ --trust-url https://trust.stage.cascadeauth.dev \
85
+ --display-name 'Example Team' --contact 'dev@example.com' --idp github \
86
+ --create-tenant
87
+ ```
88
+
89
+ Progress lines `[1/5]` to `[5/5]` name the five steps: tenant, sign-in,
90
+ hosted trust domain, workload, and material; the configuration files are
91
+ rendered last. Rerunning the
92
+ command is safe: it resumes an interrupted setup, reports a complete
93
+ workspace, or names the exact conflict. It never overwrites private
94
+ material. The sign-in you chose with `--idp` is remembered, so a rerun
95
+ never asks you to pick between GitHub and Google again.
96
+
97
+ ### What it creates, and where
98
+
99
+ * `~/.aac/workspaces/<workspace>/` is the **workspace** for one workload
100
+ (default name `starter`). `pki/` holds what the sidecar container mounts:
101
+ the workload, terminal-attestation and localhost TLS key pairs, this
102
+ workspace's root-signing key and the outbound CA bundle. `pair/` holds
103
+ what both containers mount as secrets: the pairing secret and the public
104
+ development CA certificate. `ca/` holds the development CA private key,
105
+ kept out of both mounts so no container ever sees the key that mints
106
+ identities. Beside them sit `sidecar-config.yaml`, `compose.env` and a
107
+ manifest that lets a rerun resume.
108
+ * `~/.aac/tenants/<tenant-id>/` is **tenant-level** material shared by every
109
+ workspace of that tenant: the tenant-admin signing key, the publisher's
110
+ input directories (`root-keys/` and `spiffe-bundle/`, public halves only)
111
+ and the rendered `publisher.env`.
112
+ * `~/.aac/credentials/<tenant-id>` is the tenant API key. The rendered
113
+ configuration references it by path.
114
+
115
+ **Everything `aac init` generates is development material.** The
116
+ development CA lives seven days and the leaf certificates one day. Nothing
117
+ here qualifies for production, whatever the tenant's domain. Moving to
118
+ production means a tenant-admin key under production custody
119
+ (`aac tenant rotate-admin-key`), a new root key id published by your
120
+ publisher, and certificates from your approved issuer.
121
+
122
+ ### What each file is for, and what to back up
123
+
124
+ The table lists every file the CLI creates, what it is for, how long it
125
+ lives and whether to back it up. `<workspace>` is the workspace name
126
+ (default `starter`) and `<tenant-id>` the tenant id AAC allocated.
127
+
128
+ <!-- inventory-table:start -->
129
+ | Material | Where | Used for | Lifetime | Back up? |
130
+ |---|---|---|---|---|
131
+ | tenant_api_key | `~/.aac/credentials/<tenant-id>` | CLI data-plane calls; the sidecar's workload projection | until retired or rotated; `aac tenant reissue-api-key` | **Yes.** save a protected copy in your secret manager; loss needs `aac tenant reissue-api-key` |
132
+ | tenant_admin_session | `~/.aac/credentials/<tenant-id>.session` | CLI admin calls | hours; `aac sso login` | No. sign in again |
133
+ | tenant_admin_signing_key | `~/.aac/tenants/<tenant-id>/tenant-admin.pem`, `~/.aac/tenants/<tenant-id>/tenant-admin.pub.pem` | the publisher signs trust-material uploads; the public half is registered with AAC | until rotated; `aac tenant rotate-admin-key` | **Recommended.** recommended for developer tenants (loss needs `aac tenant rotate-admin-key`); production custody belongs in an HSM/KMS, not a password manager |
134
+ | root_signing_key | `~/.aac/workspaces/<workspace>/pki/root.pem`, `~/.aac/workspaces/<workspace>/pki/root.pub.pem`, `~/.aac/tenants/<tenant-id>/root-keys/<workspace>-root-v1.pub.pem` | the sidecar mints authority chains; the public half is published under the key id | until you retire its key id (root key ids are fixed per workspace name) | No. never restore: a replacement workspace gets a new name and therefore a new key id, published alongside the old one until the old one is retired |
135
+ | development_ca | `~/.aac/workspaces/<workspace>/ca/dev-ca.key`, `~/.aac/workspaces/<workspace>/pki/dev-ca.crt`, `~/.aac/workspaces/<workspace>/pair/dev-ca.crt`, `~/.aac/tenants/<tenant-id>/spiffe-bundle/<workspace>-dev-ca.ca.pem` | signs the workload, terminal and TLS certificates; peers trust it via the published bundle | 7 days; `aac workspace renew --ca` | No. development only; reissue |
136
+ | workload_svid | `~/.aac/workspaces/<workspace>/pki/workload.key`, `~/.aac/workspaces/<workspace>/pki/workload.crt` | the sidecar's SPIFFE identity: proves on every live request that this workload holds the key behind its certificate | 1 day; `aac workspace renew` | No. development only; reissue |
137
+ | terminal_attestation | `~/.aac/workspaces/<workspace>/pki/terminal.key`, `~/.aac/workspaces/<workspace>/pki/terminal.crt` | signs the receipt (terminal attestation) the sidecar issues when this workload completes a delegated request; auditors verify it offline against your published CA; kept separate from the workload key so neither can forge the other's role | 1 day; `aac workspace renew` | No. development only; reissue |
138
+ | localhost_tls | `~/.aac/workspaces/<workspace>/pki/server.key`, `~/.aac/workspaces/<workspace>/pki/server.crt` | the sidecar's HTTPS listener | 1 day; `aac workspace renew` | No. development only; reissue |
139
+ | outbound_ca_bundle | `~/.aac/workspaces/<workspace>/pki/outbound-ca.pem` | the sidecar verifies TLS to AAC and to itself | rebuilt by renew; `aac workspace renew` | No. derived |
140
+ | pairing_secret | `~/.aac/workspaces/<workspace>/pair/pairing.secret` | the sidecar and its agent authenticate each other (same bytes in both) | until regenerated | No. regenerable; both processes must read the same file |
141
+ | workspace_manifest | `~/.aac/workspaces/<workspace>/manifest.json` | the workspace's identities and progress; every `workspace` verb reads it first | for the life of the workspace | **Recommended.** cannot be recreated by any command; without it start a new workspace under a new name (see the new-computer setup procedure) |
142
+ | published_public_inputs | `~/.aac/tenants/<tenant-id>/root-keys`, `~/.aac/tenants/<tenant-id>/spiffe-bundle` | the publisher's input directories: every root public key and CA certificate this tenant has published, across all workspaces | for the life of the tenant | **Recommended.** back up with the tenant directory: a replacement workspace must publish its new root key alongside a still-ACTIVE previous one (the control plane refuses a root-key set that drops every ACTIVE key at once) |
143
+ | rendered_files | `~/.aac/workspaces/<workspace>/sidecar-config.yaml`, `~/.aac/workspaces/<workspace>/compose.env`, `~/.aac/tenants/<tenant-id>/publisher.env` | non-secret configuration derived from the manifest | re-rendered on demand; `aac workspace render --force` | No. derived; `aac workspace render --force` recreates them |
144
+ <!-- inventory-table:end -->
145
+
146
+ In short: back up the tenant API key and the whole tenant directory while
147
+ the machine is healthy. Everything else is regenerated.
148
+
149
+ ### Inspect and maintain a workspace
150
+
151
+ ```bash
152
+ aac workspace status --workspace starter --output table # what exists, expiry, permissions, next command
153
+ aac workspace status --workspace starter --remote # also: is the public trust material visible?
154
+ aac workspace renew --workspace starter # fresh leaf keys and certificates; old ones archived
155
+ aac workspace render --workspace starter --layout container --force # container path layout
156
+ ```
157
+
158
+ `status` works offline by default and exits with code 3 when material is
159
+ missing, expired or unsafely permissioned. `renew` reissues the one-day
160
+ certificates with fresh keys, and reissues the development CA only when it
161
+ is expired or within a day of expiry (or when `--ca` is given); a new CA is
162
+ published under the next anchor id and needs the publisher restarted.
163
+ `render` never touches keys and refuses to overwrite rendered files unless
164
+ `--force` is given.
165
+
166
+ Every private file is created once, readable only by you, and never
167
+ overwritten. Before generating anything, `init` checks every destination
168
+ and refuses with the full list if one already exists. `aac init` never
169
+ installs or replaces a tenant-admin key for an existing tenant: the key on
170
+ this machine must already be the tenant's active admin key, and replacing
171
+ it is always the deliberate `aac tenant rotate-admin-key` command, never a
172
+ side effect of setup. A second workspace for the same tenant
173
+ (`--workspace other`) reuses the tenant-admin key and gets its own root key
174
+ id.
175
+
176
+ ## Setting up aac-cli on a new computer
177
+
178
+ This procedure covers a lost machine and a second machine alike. It needs
179
+ the two backups named above: the tenant API key from
180
+ `~/.aac/credentials/<tenant-id>` and the tenant directory
181
+ `~/.aac/tenants/<tenant-id>/`. The tenant id is the `tnt-…` value in your
182
+ profile; `aac profile show` prints it, and the example below uses
183
+ `tnt-550e8400-e29b-41d4-9716-446655440000`.
184
+
185
+ 1. Install the CLI, create the profile, and sign in. Signing in needs the
186
+ tenant id, because a fresh profile is not yet bound to a tenant:
187
+
188
+ ```bash
189
+ pip install aac-cli
190
+ aac profile create stage \
191
+ --admin-url https://api.stage.cascadeauth.dev \
192
+ --data-plane-url https://api.stage.cascadeauth.dev
193
+ aac sso login --profile stage --idp github \
194
+ --tenant-id tnt-550e8400-e29b-41d4-9716-446655440000
195
+ ```
196
+
197
+ An enterprise tenant signs in through its own identity provider instead,
198
+ naming the connection by its URL:
199
+
200
+ ```bash
201
+ aac sso login --profile stage \
202
+ --idp-url https://login.microsoftonline.com/11111111-2222-3333-4444-555555555555/v2.0 \
203
+ --tenant-id tnt-550e8400-e29b-41d4-9716-446655440000
204
+ ```
205
+
206
+ 2. Restore the tenant API key to `~/.aac/credentials/<tenant-id>`, readable
207
+ only by you, or issue a new one if it was not backed up:
208
+
209
+ ```bash
210
+ aac tenant reissue-api-key --profile stage
211
+ ```
212
+
213
+ Restore `~/.aac/tenants/<tenant-id>/` from the backup, with directories
214
+ readable only by you and files likewise.
215
+
216
+ If the tenant directory was not backed up, first check whether another
217
+ machine still holds `~/.aac/tenants/<tenant-id>/`: every machine that has
218
+ run `aac init` for this tenant has one. If so, copy it from there. That
219
+ is the backup, and the rest of this step does not apply.
220
+
221
+ If no copy exists anywhere, the tenant-admin key and every root key that
222
+ lived on the lost machine are gone for good. Install a new admin key,
223
+ then account for every root key the tenant has published:
224
+
225
+ ```bash
226
+ openssl genpkey -algorithm ed25519 -out tenant-admin.pem
227
+ openssl pkey -in tenant-admin.pem -pubout -out tenant-admin.public.pem
228
+ aac tenant rotate-admin-key --profile stage \
229
+ --tenant-admin-pubkey-file tenant-admin.public.pem
230
+ aac trust-anchor list --profile stage --role root-signing --output table
231
+ aac trust-anchor revoke --profile stage --kid starter-root-v1 \
232
+ --reason 'machine lost'
233
+ ```
234
+
235
+ The list shows each root key with its state. A publisher may drop a key
236
+ from its published set only while another active key stays in the set,
237
+ so a set holding nothing but the new key is accepted only once no other
238
+ key is active. Revoke every active key whose private half was lost, one
239
+ `revoke` per key id, and no other key: a root key that is still in use
240
+ elsewhere must stay active, and its public file must be in the tenant
241
+ directory the publisher runs from. Then run step 3 with
242
+ `--tenant-admin-key-file tenant-admin.pem` added.
243
+
244
+ 3. Run the guided setup with a **new workspace name**. A developer tenant
245
+ passes its sign-in choice, so an expired session is renewed without a
246
+ prompt:
247
+
248
+ ```bash
249
+ aac init --profile stage --workspace laptop-2 --idp github \
250
+ --trust-url https://trust.stage.cascadeauth.dev
251
+ ```
252
+
253
+ An enterprise tenant runs the same command without `--idp`; `--idp`
254
+ selects only the GitHub and Google developer sign-ins. The guided setup
255
+ then uses the session from step 1. If that session has expired, repeat
256
+ the `aac sso login --idp-url …` command from step 1 first:
257
+
258
+ ```bash
259
+ aac init --profile stage --workspace laptop-2 \
260
+ --trust-url https://trust.stage.cascadeauth.dev
261
+ ```
262
+
263
+ 4. Start the publisher from the tenant directory, restored or written by
264
+ step 3. A restored directory's root-key set holds the old and the new
265
+ public keys, which is exactly what AAC requires: a set that drops every
266
+ currently active key at once is refused. Retire the old key later, by
267
+ removing its public file, once nothing signs with it. A directory written
268
+ fresh after the revocations in step 2 holds only the new key, which is
269
+ accepted because no other key is active any more.
270
+
271
+ ### Why the replacement workspace needs a new name
272
+
273
+ The workspace name is baked into two identifiers that are published for
274
+ the whole tenant: the root key id `<workspace>-root-v1` and the development
275
+ CA anchor id `<workspace>-dev-ca`. Other parties look up your keys and
276
+ certificates by those ids. AAC therefore never accepts new key material
277
+ under an id that has already been published: a key id names one key, for
278
+ good. A new machine has to generate new keys, so it has to publish them
279
+ under new ids, which means a new workspace name. The new ids are published
280
+ alongside the old ones until the old ones are retired.
281
+
282
+ Reusing the old workspace name on the new machine is refused on purpose:
283
+ the restored tenant directory still holds that name's published root key,
284
+ and `init` never generates different key material under an existing key
285
+ id. The development certificates and the pairing secret are regenerated by
286
+ step 3; they are never restored.
287
+
288
+ ## Profiles and configuration
289
+
290
+ A **profile** is a named set of endpoints plus the tenant the profile is
291
+ bound to. Profiles live in `~/.aac/config`, a plain INI file with one
292
+ section per profile:
293
+
294
+ ```ini
295
+ [main]
296
+ admin_url = http://127.0.0.1:8000
297
+ data_plane_url = http://127.0.0.1:9000
298
+ tenant_id = tnt-550e8400-e29b-41d4-9716-446655440000
299
+
300
+ [stage]
301
+ admin_url = https://api.stage.cascadeauth.dev
302
+ data_plane_url = https://api.stage.cascadeauth.dev
303
+ ```
304
+
305
+ ```bash
306
+ aac profile list # every profile, including the built-in main
307
+ aac profile show stage --output table # stored and effective values, with sources
308
+ aac profile create prod --admin-url https://aac-admin.example.com \
309
+ --data-plane-url https://aac-data.example.com
310
+ aac profile update main # edit the built-in baseline
311
+ aac profile delete prod # local only; never touches the server
312
+ ```
313
+
314
+ `main` is the reserved baseline profile. It always exists, with localhost
315
+ defaults until `aac profile update main` stores real values, and cannot be
316
+ created or deleted. Every operational command selects its profile in this
317
+ order: `--profile <name>`, then the `AAC_PROFILE` environment variable, then
318
+ `main`. A selected profile that does not exist is an error, never a silent
319
+ fallback. Within the selected profile, a flag beats an environment variable
320
+ (`AAC_ADMIN_URL`, `AAC_DATA_PLANE_URL`, `AAC_TENANT_ID`), which beats the
321
+ stored value.
322
+
323
+ `tenant_id` is managed for you. Profile commands display it but never ask
324
+ for it; only `aac tenant register`, `aac init` and `aac sso login` write it.
325
+ It is your organisation's identifier in the federation, allocated by AAC at
326
+ registration in the form `tnt-<uuid>`, and it never changes.
327
+
328
+ `AAC_CLI_HOME` relocates the whole `~/.aac` tree. Use it for a throwaway
329
+ test home, or to keep separate accounts hard-walled from each other:
330
+
331
+ ```bash
332
+ AAC_CLI_HOME=$HOME/.aac-prod aac profile list
333
+ ```
334
+
335
+ ### Credentials
336
+
337
+ Two credential files can coexist under `~/.aac/credentials/`:
338
+
339
+ * `<tenant-id>` holds the tenant API key, written once by registration and
340
+ printed exactly once. The sidecar and the data-plane commands
341
+ (`aac chain`, `aac trust-anchor`) present it. Only a hash of it exists
342
+ on the server, so a lost key is replaced, never recovered.
343
+ * `<tenant-id>.session` holds the short-lived session that `aac sso login`
344
+ creates. The administration commands (`aac tenant`, `aac sso`) use it
345
+ automatically. Sessions cannot be refreshed: when one expires, any
346
+ administration command tells you to sign in again, and there is no
347
+ refresh token that could be stolen.
348
+
349
+ ## Signing in
350
+
351
+ ```bash
352
+ aac sso login --profile stage --idp github # developer tenant: GitHub or Google
353
+ aac sso login --profile prod \
354
+ --idp-url https://login.microsoftonline.com/11111111-2222-3333-4444-555555555555/v2.0
355
+ aac sso login --profile prod \
356
+ --idp-url https://login.microsoftonline.com/11111111-2222-3333-4444-555555555555/v2.0 \
357
+ --flow pkce --no-browser # print the URL instead of opening a browser
358
+ aac sso whoami --profile stage --output table # cached session and the tenant's assigned domain
359
+ aac sso logout --profile stage
360
+ ```
361
+
362
+ Sign-in uses the device flow by default, so the browser can be on another
363
+ machine and headless hosts work. Google connections use a loopback flow
364
+ instead, because Google's device flow is scope-restricted; `--flow`
365
+ overrides the choice. An enterprise tenant names its identity-provider
366
+ connection by the connection's exact URL with `--idp-url`; when several
367
+ connections are registered, the error message lists the exact commands to
368
+ choose one. `whoami` and `logout` work offline and never contact AAC.
369
+
370
+ ## Output and exit codes
371
+
372
+ Every command that reports a result prints one JSON document to standard
373
+ output by default, with progress notes and diagnostics on standard error,
374
+ so the JSON stays parseable in scripts; `--output table` prints a readable
375
+ table instead. The three profile writers are the exception: `aac profile
376
+ create`, `update` and `delete` confirm on standard error, leave standard
377
+ output empty and take no `--output`.
378
+
379
+ | Exit code | Meaning |
380
+ |---|---|
381
+ | `0` | Success. |
382
+ | `1` | AAC or the identity provider rejected the request. |
383
+ | `2` | Usage error: an invalid flag, value or flag combination. |
384
+ | `3` | A local configuration or state problem: profile, credential file, cached session or workspace. |
385
+ | `4` | Transport failure: an endpoint could not be reached. |
386
+
387
+ If AAC asks you to slow down, the command shows the interval to wait. The
388
+ CLI never replays a change on its own; wait and rerun deliberately.
389
+
390
+ ## Enterprise tenants
391
+
392
+ Enterprise tenants are registered through an onboarding ceremony: AAC
393
+ operations opens a registration window and hands the tenant operator a
394
+ bootstrap token, which the commands below read from `--bootstrap-token` or
395
+ the `AAC_BOOTSTRAP_TOKEN` environment variable. The token authorises only
396
+ the onboarding steps shown with it; every other administration command runs
397
+ under a signed-in session.
398
+
399
+ ### Register the tenant
400
+
401
+ Generate the tenant-admin key pair first. The CLI transmits the public half
402
+ only and refuses a private key:
403
+
404
+ ```bash
405
+ openssl genpkey -algorithm ed25519 -out tenant-admin.pem
406
+ openssl pkey -in tenant-admin.pem -pubout -out tenant-admin.public.pem
407
+ aac profile create prod --admin-url https://aac-admin.example.com \
408
+ --data-plane-url https://aac-data.example.com
409
+ aac tenant register --profile prod --display-name 'Example Corporation' \
410
+ --contact ops@example.com \
411
+ --workload-spiffe-id spiffe://example.com/treasury-agent/v1 \
412
+ --tenant-admin-pubkey-file ./tenant-admin.public.pem \
413
+ --bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
414
+ ```
415
+
416
+ AAC allocates the tenant id; you never choose one. The response prints the
417
+ id and the tenant API key exactly once, and the CLI saves both. A profile
418
+ that is already bound to a tenant refuses to register another; use an
419
+ unbound profile. If the response is lost, rerun the same command with no
420
+ changes to the identifying flags: the CLI resumes the frozen request rather
421
+ than registering twice.
422
+
423
+ ```bash
424
+ aac tenant describe --profile prod --output table
425
+ aac tenant update --profile prod --tenant-id tnt-550e8400-e29b-41d4-9716-446655440000 \
426
+ --display-name 'Example Corporation Ltd'
427
+ ```
428
+
429
+ ### Connect your identity provider
430
+
431
+ An identity-provider connection is described by a JSON file
432
+ (`aac sso register-idp --help` shows the fields). Before the first
433
+ connection, generate an offline recovery key so the connection can be
434
+ repaired if the provider is ever unavailable; only the enrollment file
435
+ leaves the machine:
436
+
437
+ ```bash
438
+ aac sso generate-idp-recovery-key \
439
+ --private-key-file offline-idp-recovery-private.pem \
440
+ --enrollment-file idp-recovery-enrollment.json
441
+ aac sso enroll-idp-recovery-key --profile prod \
442
+ --file idp-recovery-enrollment.json --bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
443
+ aac sso register-idp --profile prod --file connection.json \
444
+ --bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
445
+ aac sso list-idp --profile prod
446
+ ```
447
+
448
+ Later connections, corrections (`aac sso replace-idp`) and the two-party
449
+ repair ceremony (`aac sso request-idp-repair`, `aac sso approve-idp-repair`)
450
+ are described by their `--help`.
451
+
452
+ ### Keys
453
+
454
+ Replacing the tenant-admin key is immediate and forward-only: the previous
455
+ key stops verifying at once and can never be reinstated. The trust-anchor
456
+ publisher signs its uploads with this key and loads it once, at start, so
457
+ sequence the change: stop the publisher, register the new public half, put
458
+ the new private key where the publisher's configuration points, start the
459
+ publisher, and confirm that its first upload is accepted under the new key.
460
+ A publisher installed as a system service reads the key path from its
461
+ environment file, which you can point at the new file. A Docker publisher's
462
+ environment and key mount are fixed when the container is created, so
463
+ overwrite the key at the mounted path and recreate the container; a plain
464
+ restart would come back on the old key.
465
+
466
+ ```bash
467
+ aac tenant rotate-admin-key --profile prod \
468
+ --tenant-admin-pubkey-file tenant-admin.public.pem
469
+ ```
470
+
471
+ The tenant API key can be rotated without downtime. `issue` stages a second
472
+ active key in a new file and never replaces the current one; move every
473
+ client to it, then retire the old key by its exact id:
474
+
475
+ ```bash
476
+ aac tenant api-key list --profile prod
477
+ aac tenant api-key issue --profile prod
478
+ aac tenant api-key retire --profile prod --key-id ak_0123456789abcdef --yes
479
+ ```
480
+
481
+ A lost or suspected-compromised key is replaced instead: every active key is
482
+ revoked and one fresh key is printed once.
483
+
484
+ ```bash
485
+ aac tenant reissue-api-key --profile prod
486
+ ```
487
+
488
+ ### Domains and workloads
489
+
490
+ Every tenant gets a hosted trust domain from AAC; registration saves it in
491
+ the profile, and `aac tenant assign-hosted-domain` assigns one to a tenant
492
+ that has none yet. To use your own DNS domain as a trust domain instead,
493
+ prove control of it with a DNS TXT record, then bind it:
494
+
495
+ ```bash
496
+ aac tenant issue-domain-challenge --profile prod --domain example.com
497
+ # Publish the TXT record the command prints, then:
498
+ aac tenant verify-domain --profile prod --domain example.com
499
+ aac tenant bind-trust-domain --profile prod --trust-domain example.com
500
+ aac tenant list-trust-domains --profile prod --output table
501
+ ```
502
+
503
+ Workloads are the identities your sidecars run under:
504
+
505
+ ```bash
506
+ aac tenant add-workload --profile prod --spiffe-id spiffe://example.com/payroll/v1 \
507
+ --display-name Payroll
508
+ aac tenant list-workloads --profile prod
509
+ aac tenant deactivate-workload --profile prod --workload-id wl-2f9c1e --reason 'service retired'
510
+ ```
511
+
512
+ Deactivation is terminal, and a deactivated SPIFFE id stays reserved.
513
+
514
+ ### Inspect trust material and audit a chain
515
+
516
+ ```bash
517
+ aac trust-anchor list --profile prod --output table # every published key, every lifecycle state
518
+ aac trust-anchor describe --profile prod --kid starter-root-v1
519
+ aac chain show --profile prod \
520
+ --token-id c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9 --output table
521
+ aac chain show --profile prod \
522
+ --token-id c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9 \
523
+ --render --render-out ./chain.html
524
+ ```
525
+
526
+ `chain show` accepts any hop's token id or the chain root id and shows the
527
+ chain to any tenant that took part in it. The timeline carries metadata
528
+ only; the business content of each step stays in your own audit stream.
529
+
530
+ ## Getting help
531
+
532
+ `aac --help` lists the command groups and `aac <command> --help` shows every
533
+ flag with its default. Errors name the exact command to run next.
534
+
535
+ ## License
536
+
537
+ Apache-2.0.