aac-cli 0.1.6__tar.gz → 0.2.1__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.2.1/PKG-INFO +941 -0
- aac_cli-0.2.1/README.md +924 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/__init__.py +1 -1
- aac_cli-0.2.1/aac_cli/agent_cli.py +683 -0
- aac_cli-0.2.1/aac_cli/agent_config.py +462 -0
- aac_cli-0.1.6/aac_cli/workspace_health.py → aac_cli-0.2.1/aac_cli/agent_health.py +144 -97
- aac_cli-0.1.6/aac_cli/workspace_layout.py → aac_cli-0.2.1/aac_cli/agent_layout.py +198 -125
- aac_cli-0.2.1/aac_cli/agent_record.py +177 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/cli.py +4 -4
- aac_cli-0.2.1/aac_cli/config_render.py +323 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/dev_material.py +290 -13
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/init_cli.py +857 -390
- aac_cli-0.2.1/aac_cli/material_cases.py +175 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/reference.py +41 -12
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/secure_files.py +1 -1
- aac_cli-0.2.1/aac_cli/supplied_material.py +293 -0
- aac_cli-0.2.1/aac_cli.egg-info/PKG-INFO +941 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli.egg-info/SOURCES.txt +7 -4
- {aac_cli-0.1.6 → aac_cli-0.2.1}/pyproject.toml +2 -2
- aac_cli-0.1.6/PKG-INFO +0 -726
- aac_cli-0.1.6/README.md +0 -709
- aac_cli-0.1.6/aac_cli/config_render.py +0 -266
- aac_cli-0.1.6/aac_cli/workspace_cli.py +0 -544
- aac_cli-0.1.6/aac_cli/workspace_manifest.py +0 -144
- aac_cli-0.1.6/aac_cli.egg-info/PKG-INFO +0 -726
- {aac_cli-0.1.6 → aac_cli-0.2.1}/LICENSE +0 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/MANIFEST.in +0 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/__main__.py +0 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/admin_key_pem.py +0 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/api_key_rotation_state.py +0 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/config.py +0 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/idp_recovery.py +0 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/profiles.py +0 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/registration_state.py +0 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/sso_login.py +0 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli.egg-info/dependency_links.txt +0 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli.egg-info/entry_points.txt +0 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli.egg-info/requires.txt +0 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli.egg-info/top_level.txt +0 -0
- {aac_cli-0.1.6 → aac_cli-0.2.1}/setup.cfg +0 -0
aac_cli-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,941 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aac-cli
|
|
3
|
+
Version: 0.2.1
|
|
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
|
+
New to AAC? [What AAC is for](https://cascadeauth.github.io/aac-sidecar-go/#what-aac-is-for)
|
|
28
|
+
explains what delegated authority between agents buys you, and what it does not.
|
|
29
|
+
|
|
30
|
+
The `aac` command is how a tenant's operator does everything that is not
|
|
31
|
+
done by the sidecar itself: register the tenant, sign in, manage keys,
|
|
32
|
+
workloads and domains, and prepare a runnable local workload. Everything
|
|
33
|
+
works from a terminal or a script; there is no web console to click
|
|
34
|
+
through. Output is JSON by default, and every command that reports a
|
|
35
|
+
result also offers `--output table`.
|
|
36
|
+
|
|
37
|
+
Two kinds of tenant use the same tool:
|
|
38
|
+
|
|
39
|
+
* **Developer tenants** register themselves by signing in with GitHub or
|
|
40
|
+
Google. `aac init` takes a developer from an installed CLI to a runnable
|
|
41
|
+
local workload in one command.
|
|
42
|
+
* **Enterprise tenants** are registered through an onboarding ceremony with
|
|
43
|
+
AAC operations and sign in through their own identity provider. Their
|
|
44
|
+
keys, domains and workloads are managed with the `aac tenant` and
|
|
45
|
+
`aac sso` commands described further down.
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install aac-cli
|
|
51
|
+
aac --version
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Python 3.10 or newer is required.
|
|
55
|
+
|
|
56
|
+
> **Mind the name.** `pip install aac` installs an unrelated project that
|
|
57
|
+
> happens to share the acronym. The AAC package is **`aac-cli`**; the
|
|
58
|
+
> command it installs is **`aac`**.
|
|
59
|
+
|
|
60
|
+
## Guided setup for developer tenants: `aac init`
|
|
61
|
+
|
|
62
|
+
`aac init` prepares the AAC side of one of your agents on this machine: it
|
|
63
|
+
registers the agent's identity with AAC and creates the keys, certificates
|
|
64
|
+
and configuration its sidecar needs. The agent itself is yours. The first
|
|
65
|
+
run also creates your profile and registers your tenant; run it again with
|
|
66
|
+
a new `--agent` and its own `--agent-config` file, and the same `--trust-url`
|
|
67
|
+
and `--idp`, for each further agent.
|
|
68
|
+
|
|
69
|
+
Along the way it signs you in, registers a developer tenant (or reuses the
|
|
70
|
+
one your profile is already bound to), takes the trust domain AAC assigns to the
|
|
71
|
+
tenant, registers your workload, generates development keys and
|
|
72
|
+
certificates, and writes a complete sidecar configuration together with the
|
|
73
|
+
files the trust-anchor publisher and Docker Compose need.
|
|
74
|
+
|
|
75
|
+
Point it at the endpoints your AAC environment gives you. The example below
|
|
76
|
+
uses the AAC stage environment. First save the YAML example in “Agent configuration”
|
|
77
|
+
below as `orders.yaml`:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
aac init --profile stage --agent orders --agent-config orders.yaml \
|
|
81
|
+
--admin-url https://api.stage.cascadeauth.dev \
|
|
82
|
+
--data-plane-url https://api.stage.cascadeauth.dev \
|
|
83
|
+
--trust-url https://trust.stage.cascadeauth.dev \
|
|
84
|
+
--display-name 'Example Team' --contact 'dev@example.com' --idp github
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The command shows what it will create and asks before registering the
|
|
88
|
+
tenant, because a tenant is permanent. In a script, where there is no
|
|
89
|
+
terminal to answer the question, pass `--create-tenant`:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
aac init --profile stage --agent orders --agent-config orders.yaml \
|
|
93
|
+
--admin-url https://api.stage.cascadeauth.dev \
|
|
94
|
+
--data-plane-url https://api.stage.cascadeauth.dev \
|
|
95
|
+
--trust-url https://trust.stage.cascadeauth.dev \
|
|
96
|
+
--display-name 'Example Team' --contact 'dev@example.com' --idp github \
|
|
97
|
+
--create-tenant
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`--idp` names how you sign in: `github` or `google`, the two sign-ins AAC
|
|
101
|
+
offers developer tenants. If you need another sign-in provider, contact
|
|
102
|
+
support@cascadeauth.com.
|
|
103
|
+
|
|
104
|
+
`--trust-url` is required when an agent is created: it is the public URL
|
|
105
|
+
where your AAC environment serves trust material, and for AAC stage it is
|
|
106
|
+
`https://trust.stage.cascadeauth.dev`. If you leave it out, the command
|
|
107
|
+
stops before creating anything and names the value your tenant already
|
|
108
|
+
uses, if it has one.
|
|
109
|
+
|
|
110
|
+
Setting up a new developer tenant takes two browser sign-ins: the first
|
|
111
|
+
registers the tenant, the second starts the tenant-admin session that
|
|
112
|
+
registers the workload. The command says so before the first one.
|
|
113
|
+
|
|
114
|
+
Progress lines `[1/5]` to `[5/5]` name the five steps: tenant, sign-in,
|
|
115
|
+
hosted trust domain, workload, and material; the settings files are
|
|
116
|
+
written last. Rerunning the
|
|
117
|
+
command is safe: it resumes an interrupted setup, reports a complete
|
|
118
|
+
agent, or names the exact conflict. It never overwrites private
|
|
119
|
+
material. The sign-in you chose with `--idp` is remembered, so a rerun
|
|
120
|
+
never asks you to pick between GitHub and Google again.
|
|
121
|
+
|
|
122
|
+
### What it creates, and where
|
|
123
|
+
|
|
124
|
+
* `~/.aac/agents/<agent>/` holds the files **one sidecar** needs, in folders named for where each file goes. `sidecar/` is
|
|
125
|
+
what you mount or copy into the sidecar: the workload, receipt and HTTPS
|
|
126
|
+
key pairs, this agent's root-signing key, the CA certificate and the
|
|
127
|
+
outbound CA bundle. `agent/` is what the paired application reads: the
|
|
128
|
+
pairing secret and the CA certificate. `keep/` stays with you — it holds
|
|
129
|
+
the development CA private key, out of both mounts, so no container ever
|
|
130
|
+
sees the key that mints identities; an agent whose certificates your own
|
|
131
|
+
issuer signed has no `keep/` at all. Beside them sit
|
|
132
|
+
`sidecar-config.yaml`, `compose.env`, `state/` and `record.json`, which
|
|
133
|
+
lets a rerun resume.
|
|
134
|
+
* `~/.aac/tenants/<tenant-id>/` is **tenant-level** material shared by every
|
|
135
|
+
agent of that tenant: the tenant-admin signing key, the publisher's input
|
|
136
|
+
directories (`root-keys/` and `spiffe-bundle/`, public halves only) and
|
|
137
|
+
the generated `publisher.env`.
|
|
138
|
+
* `~/.aac/credentials/<tenant-id>` is the tenant API key. The generated
|
|
139
|
+
configuration references it by path.
|
|
140
|
+
|
|
141
|
+
**By default everything `aac init` generates is development material.** The
|
|
142
|
+
development CA lives seven days and the leaf certificates one day. Nothing
|
|
143
|
+
there qualifies for production, whatever the tenant's domain. If your own
|
|
144
|
+
issuer signs your agent's certificates instead, see "Two ways to get your
|
|
145
|
+
agent's certificates" below; moving to production also means a tenant-admin
|
|
146
|
+
key under production custody (`aac tenant rotate-admin-key`) and a root key
|
|
147
|
+
id published by your publisher.
|
|
148
|
+
|
|
149
|
+
### What an agent folder is for, and how to use it
|
|
150
|
+
|
|
151
|
+
An agent folder holds everything one workload needs to run beside an AAC
|
|
152
|
+
sidecar, kept apart from every other workload: its identity in your
|
|
153
|
+
tenant's trust domain, its own root signing key and key id, its
|
|
154
|
+
certificates, the pairing secret its sidecar and application share, and the
|
|
155
|
+
settings files written for them. It also records how far setup got, so
|
|
156
|
+
rerunning `aac init` resumes where it stopped.
|
|
157
|
+
|
|
158
|
+
Use one agent per workload. `aac init --agent <name>` creates an agent
|
|
159
|
+
(with `--agent-config` and `--trust-url`) or resumes it. There is no default agent;
|
|
160
|
+
three commands look after it:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
aac agent status --agent orders # what exists, what is due, and the next step
|
|
164
|
+
aac agent renew --agent orders # replace the short-lived certificates
|
|
165
|
+
aac agent list # every agent on this machine
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`agent list` reports an unreadable or unsupported record in that agent's
|
|
169
|
+
`state` and continues listing the others. `agent status --agent <name>` still
|
|
170
|
+
returns a local-state error for that individual agent.
|
|
171
|
+
|
|
172
|
+
There is no separate command for the settings files. Running setup again —
|
|
173
|
+
with the profile the agent belongs to — writes `sidecar-config.yaml`,
|
|
174
|
+
`compose.env` and the tenant's `publisher.env` from what the agent already
|
|
175
|
+
records, without touching a single key:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
aac init --profile stage --agent orders # write them again
|
|
179
|
+
aac init --profile stage --agent orders --layout container # and change the layout
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
That is how you rebuild a settings file you lost and how you move between
|
|
183
|
+
running the sidecar directly on this machine and running it in a container.
|
|
184
|
+
`--profile` is required because setup picks the profile before it reads the
|
|
185
|
+
agent; `aac agent status` prints the same command with the right profile
|
|
186
|
+
whenever it is the next step.
|
|
187
|
+
|
|
188
|
+
A second workload is a second agent with its own workload path:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
aac init --profile stage --agent worker --agent-config worker.yaml \
|
|
192
|
+
--idp github --trust-url https://trust.stage.cascadeauth.dev
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Pass the same `--trust-url` and `--idp` as for your first agent. Each agent
|
|
196
|
+
records its own, and the trust URL also goes into the tenant's
|
|
197
|
+
`publisher.env`, which every agent of the tenant shares. Writing the
|
|
198
|
+
settings files never changes that file's endpoints, so an agent created with
|
|
199
|
+
a different trust URL stops at its last step and has to be set up again
|
|
200
|
+
under a new name.
|
|
201
|
+
|
|
202
|
+
`--agent` names the local files and must match the file's `agent_name`.
|
|
203
|
+
The file's required `workload_path` supplies the path at the end of the SPIFFE ID.
|
|
204
|
+
An optional `--workload-path` must match the file or saved identity; omission
|
|
205
|
+
on refresh reuses that identity. Missing or conflicting identities are refused. Two agents with the same workload path are the same
|
|
206
|
+
workload holding different keys: that is what a replacement computer wants,
|
|
207
|
+
and what a second workload does not. The agent name is permanent, because
|
|
208
|
+
the root key id is made from it. `aac init` needs the profile the agent was
|
|
209
|
+
created with; the `aac agent` commands read it from the agent itself.
|
|
210
|
+
|
|
211
|
+
### Agent configuration
|
|
212
|
+
|
|
213
|
+
The operator writes one YAML file. `aac init --agent-config PATH` validates and
|
|
214
|
+
explicitly applies it. The profile remains INI; this file describes one agent,
|
|
215
|
+
not its profile or the hosted service. For example, save this as `orders.yaml`:
|
|
216
|
+
|
|
217
|
+
```yaml
|
|
218
|
+
agent_name: orders
|
|
219
|
+
workload_path: fulfilment/orders
|
|
220
|
+
tls_server_names: [orders.internal.example, localhost, 127.0.0.1]
|
|
221
|
+
sidecar:
|
|
222
|
+
agent_invoke_url: http://127.0.0.1:8000/invoke
|
|
223
|
+
external_bind_address: 127.0.0.1
|
|
224
|
+
telemetry:
|
|
225
|
+
sink: orders.jsonl
|
|
226
|
+
central_forward: false
|
|
227
|
+
classes_of_action:
|
|
228
|
+
fulfil_order:
|
|
229
|
+
predicates:
|
|
230
|
+
action: reserve_inventory
|
|
231
|
+
amount_max: 10000
|
|
232
|
+
originator_reference: "PO #4143"
|
|
233
|
+
valid_for: +8m
|
|
234
|
+
destinations: {}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
This declares the tenant's permission; AAC does not invent one. The application
|
|
238
|
+
chooses `fulfil_order` when starting a chain and checks that its business action
|
|
239
|
+
matches the authorized context. Units and business meaning belong to the
|
|
240
|
+
application. Empty classes and destinations are allowed for registration before
|
|
241
|
+
peer identities are known. No self destination is added.
|
|
242
|
+
|
|
243
|
+
**Chain-start boundary:** restrict `/v1/agent/delegations` and
|
|
244
|
+
`/v1/agent/mint-root` to the authorized originating application at the
|
|
245
|
+
network/ingress boundary. The current sidecar does not authenticate these callers;
|
|
246
|
+
sharing a private network with peers is insufficient. Expose only intended
|
|
247
|
+
receive/A2A routes to peers. Development certificates do not require `dev_mode`.
|
|
248
|
+
|
|
249
|
+
The following is the complete input schema. Unknown fields, duplicate mapping
|
|
250
|
+
keys, YAML aliases/merge keys, object tags and files over 64 KiB are refused.
|
|
251
|
+
Null is not an omitted value. Strings must be nonempty and have no surrounding
|
|
252
|
+
whitespace or control characters. Lists allow at most 128 unique values; class
|
|
253
|
+
and destination maps allow at most 128 entries each.
|
|
254
|
+
|
|
255
|
+
| Field | Type and meaning | Default when omitted |
|
|
256
|
+
|---|---|---|
|
|
257
|
+
| `agent_name` | Required local name, 3–15 characters under the same name rules as `--agent`; must match it. | None |
|
|
258
|
+
| `workload_path` | Required concrete SPIFFE path, 3–150 ASCII characters; slash-separated letters/digits/`._~-`, no empty or dot segments, wildcard, query or escaped character. Fixed once the agent exists. | None |
|
|
259
|
+
| `tls_server_names` | Required nonempty list of DNS names/IP addresses callers use for HTTPS, including the originating application. No URLs, wildcards or unspecified bind addresses. | None |
|
|
260
|
+
| `sidecar.dev_mode` | Boolean enabling the runtime's development exceptions; independent of certificate source and replay profile. | `false` |
|
|
261
|
+
| `sidecar.loopback_bind_address` | Must be exactly `127.0.0.1` when `dev_mode: false`; another loopback IP address requires `dev_mode: true`. Non-loopback addresses are not accepted by this input. | `127.0.0.1` |
|
|
262
|
+
| `sidecar.external_bind_address` | IP bind address for the peer listener; choose the reachability your ingress allows. | `127.0.0.1` |
|
|
263
|
+
| `sidecar.loopback_port`, `sidecar.external_port` | Distinct integer ports, 1–65535. | `8080`, `9443` |
|
|
264
|
+
| `sidecar.agent_invoke_url` | Absolute HTTP(S) URL of your application's invoke endpoint. | `http://127.0.0.1:8000/invoke` |
|
|
265
|
+
| `sidecar.log_level` | `debug`, `info`, `warn` or `error`. | `info` |
|
|
266
|
+
| `sidecar.telemetry.sink` | Filename within managed `state/` (letters/digits/`_.-`, starting with a letter/digit), or `stdout` / `none`. `a2a-egress.db` is reserved for managed database state, regardless of case or whether A2A is enabled. Host/container paths are generated. | `telemetry.jsonl` |
|
|
267
|
+
| `sidecar.telemetry.central_forward` | Boolean opt-in for asynchronous metadata-only forwarding. The endpoint and tenant API-key file come from the selected profile/agent binding, never another tenant's file. | `false` |
|
|
268
|
+
| `replay_protection.backend` | `memory` or `valkey`. | `memory` |
|
|
269
|
+
| `replay_protection.deployment_profile` | `basic` with memory; `ha-retained-write-safe` with Valkey. No fallback. | The matching profile |
|
|
270
|
+
| `replay_protection.url` | Required absolute `valkeys://` URL for Valkey, with an optional numeric database path. No query, fragment or embedded credentials. Not valid with memory. | None |
|
|
271
|
+
| `replay_protection.memory_max_entries` | Integer 1–1000000. | `100000` |
|
|
272
|
+
| `replay_protection.connect_timeout_ms` | Integer 1–100. | Runtime default `100` |
|
|
273
|
+
| `replay_protection.operation_timeout_ms` | Integer 1–250. | Runtime default `50` |
|
|
274
|
+
| `replay_protection.max_connections` | Integer 1–4096. | Runtime default `32` |
|
|
275
|
+
| `replay_protection.cold_start_quarantine_seconds` | Integer 120–3600. | Runtime default `120` |
|
|
276
|
+
| `trust_anchors.tenant_ids` | Additional canonical `tnt-<UUID>` tenant IDs whose root signatures you trust. | Empty; own tenant is always included |
|
|
277
|
+
| `spiffe_bundles.trust_domains` | Additional lowercase trust domains whose workload certificates you trust. | Empty; own assigned domain is always included |
|
|
278
|
+
| `https_trust.ca_files` | Paths to public PEM CA certificate files, relative to the input file or absolute; up to 256 KiB each and 1 MiB total. Certificate-only PEM is saved; refresh/renewal do not reread the source files. No private keys. | Empty; own CA is always included |
|
|
279
|
+
| `https_trust.system_roots` | Boolean adding the current machine's system public HTTPS roots, with certifi fallback. With `false`, every outbound TLS service—including the control plane, trust URL, peer and Valkey—must chain to the own CA or a CA listed in `ca_files`. | `true` |
|
|
280
|
+
| `classes_of_action.<name>.predicates` | Mapping of registered predicate names to supported values, described below. The class entry's `<name>` uses letters/digits/`_.-`, starting with a letter/digit, up to 128 characters; predicate names come only from the closed registry. | Empty mapping |
|
|
281
|
+
| `classes_of_action.<name>.valid_for` | Required positive duration, 1 second–24 hours: digits with optional `+`, then `s`, `m`, `h` or `d`. The CLI supplies the registered `audience_self`. | None |
|
|
282
|
+
| `destinations.<name>.url` | Required absolute HTTPS recipient URL, for native receive or A2A. | None |
|
|
283
|
+
| `destinations.<name>.audience_pattern` | Required exact registered SPIFFE identity; no wildcard. | None |
|
|
284
|
+
| `destinations.<name>.predicates`, `.valid_for` | Same value/duration contract as classes. | Empty predicates; duration required |
|
|
285
|
+
| `destinations.<name>.timeout_ms` | Integer 1–2147483647 without A2A; 1–60000 for every destination when A2A is enabled, matching its generated deadline. | `10000` |
|
|
286
|
+
| `a2a.public_base_url`, `a2a.local_handler_url` | Optional A2A block; both required if present. Public URL is HTTPS with no path except optional `/`; the HTTP(S) local handler path must be exactly `/a2a/v1`, with no escaped spelling. Neither URL permits query or fragment markers, even empty ones. Managed A2A state paths and a 60-second deadline are generated. No A2A self-route is added. | A2A disabled |
|
|
287
|
+
|
|
288
|
+
URL hosts are ASCII DNS names or IP addresses; URL fields reject whitespace,
|
|
289
|
+
backslashes, embedded credentials and fragments. Basic replay history
|
|
290
|
+
is process-local: restarting loses it, and replicas do not share it. Selecting
|
|
291
|
+
Shared durable declares that the operator has qualified the Valkey service; the
|
|
292
|
+
CLI does not establish durability. This configuration interface does not accept
|
|
293
|
+
credential-bearing replay URLs.
|
|
294
|
+
|
|
295
|
+
The three kinds of trust above are separate checks. Obtain peer tenant IDs,
|
|
296
|
+
assigned trust domains and exact workload identities from their public status
|
|
297
|
+
or registration output. Exchange public CA certificates only. After registering
|
|
298
|
+
both agents, explicitly apply their peer configurations with the same command:
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
aac init --profile stage --agent orders --agent-config orders.yaml
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
The closed predicate registry accepts `action`, `amount_max`, `amount_min`,
|
|
305
|
+
`amount`, `valid_from`, `beneficiary_account`, `beneficiary_class`, `currency`,
|
|
306
|
+
`data_scope`, `purpose`, `human_authorization_class`, `task_ref`,
|
|
307
|
+
`min_account_age_days`, `max_authority_amount`, `assessed_damage_amount`,
|
|
308
|
+
`assessment_outcome`, `surveyor_findings`, `payout_amount`, `program_reference`,
|
|
309
|
+
`reporting_quarter`, `claim_ref`, `hours`, `quantity_units`, `beneficiary`,
|
|
310
|
+
`unit_price_usd`, `account`, `esg_scope3_co2e_kg`, `conflict_minerals_clear`,
|
|
311
|
+
`originator_reference`, `scope`, `composite_payout_amount`,
|
|
312
|
+
`composite_payout_total`, `composite_units_total`,
|
|
313
|
+
`composite_esg_scope3_co2e_kg_total` and `composite_conflict_minerals_clear`.
|
|
314
|
+
`amount_max`, `amount_min` and `valid_from` accept nonnegative integers or
|
|
315
|
+
canonical decimal strings (`0` or a nonzero digit followed by digits), at most
|
|
316
|
+
9223372036854775807. Enforced numbers are checked before YAML integer conversion:
|
|
317
|
+
spellings such as `010`, `0x10`, `1_000` and `+10` are refused, quoted or unquoted,
|
|
318
|
+
so the loader cannot silently reinterpret a limit. Other values must be strings. Commas are refused because
|
|
319
|
+
they separate wire predicates. The combined mapping has a 7900-byte UTF-8
|
|
320
|
+
budget, leaving room for the generated expiry.
|
|
321
|
+
|
|
322
|
+
`valid_until` is generated authority data, so it is refused in static input:
|
|
323
|
+
use `valid_for`. Registry membership alone does not give business labels new
|
|
324
|
+
monotonic enforcement. The current HTTP mint route uses the selected class's
|
|
325
|
+
static first limits; it accepts no per-request obligations. Your application
|
|
326
|
+
remains responsible for its policy decision.
|
|
327
|
+
|
|
328
|
+
Applying a file rewrites the generated configuration; restart the sidecar to
|
|
329
|
+
load it. Ordinary refresh and certificate renewal use the saved accepted values,
|
|
330
|
+
even if the original file is edited, moved or removed. Public CA file changes
|
|
331
|
+
also require explicit reapplication. The source file is never rewritten. Keep
|
|
332
|
+
the agent directory, credentials and keys; the input file is not their backup.
|
|
333
|
+
Generated files are disposable outputs and should not be hand-edited.
|
|
334
|
+
|
|
335
|
+
HTTPS certificates must authenticate every configured connection name. The CLI
|
|
336
|
+
issues them for those names or checks supplied certificates against them; renewal
|
|
337
|
+
keeps those names. Applying a different name set requires the installed certificate
|
|
338
|
+
to cover it already: supplied issuers can issue a certificate covering both sets
|
|
339
|
+
before the change. A CLI-issued agent needing new names must be created under a
|
|
340
|
+
new name with the intended configuration. No old agent-record format is migrated.
|
|
341
|
+
|
|
342
|
+
Central forwarding retains the local sink and sends only permitted metadata.
|
|
343
|
+
It is asynchronous and best-effort; a local event does not prove central delivery.
|
|
344
|
+
It excludes business predicates, task references, raw tokens/proofs and receipts.
|
|
345
|
+
`AAC_TELEMETRY_SINK` and `AAC_AGENT_STATE_DIR` in `compose.env` identify the local
|
|
346
|
+
file and mount; `AAC_API_KEY_FILE` identifies this tenant's credential. Mount it
|
|
347
|
+
at `/run/secrets/tenant-api-key` in the container layout. Do not read private
|
|
348
|
+
`record.json` from consumers.
|
|
349
|
+
|
|
350
|
+
For a CA change: stop the affected sidecar/application; renew or install the
|
|
351
|
+
issuer's replacement certificates; restart the publisher and verify publication
|
|
352
|
+
with `aac agent status --agent <name> --remote`; exchange the new public CA with
|
|
353
|
+
each intended peer and explicitly reapply that peer's configuration; then restart
|
|
354
|
+
the sidecars/applications and verify traffic in both directions. A peer's files
|
|
355
|
+
are never changed implicitly. Old published CA anchors remain until deliberately
|
|
356
|
+
retired. Leaf-only renewal needs a sidecar restart, without changing peer trust.
|
|
357
|
+
|
|
358
|
+
### Two ways to get your agent's certificates
|
|
359
|
+
|
|
360
|
+
Find your situation here and use the flags it lists. Nothing is inferred:
|
|
361
|
+
which case you are in is decided by whether you pass certificates your own
|
|
362
|
+
issuer signed, and the agent records the answer, so `aac agent status` can
|
|
363
|
+
tell you later and `aac agent renew` behaves accordingly.
|
|
364
|
+
|
|
365
|
+
<!-- material-cases:start -->
|
|
366
|
+
|
|
367
|
+
<!-- Generated from aac_cli/material_cases.py. Do not edit by hand. -->
|
|
368
|
+
|
|
369
|
+
#### The CLI creates a development CA
|
|
370
|
+
|
|
371
|
+
The laptop case. The CLI creates a certificate authority on this machine and signs the agent's identity, receipt and HTTPS certificates with it.
|
|
372
|
+
|
|
373
|
+
**Flags.** No flags are needed. To reuse a development CA across agents, pass `--ca-key-file` and `--ca-cert-file` together; neither one alone.
|
|
374
|
+
|
|
375
|
+
**Keys and signatures.** The CA and the two identity keys are Ed25519; the HTTPS key is EC P-256.
|
|
376
|
+
|
|
377
|
+
**The CLI** creates a 7-day certificate authority and keeps its private key in the agent's keep/ folder; issues the workload, receipt and HTTPS certificates; publishes the CA certificate as a trust anchor named `<agent>-dev-ca`.
|
|
378
|
+
|
|
379
|
+
**The CLI does not** ask you for anything from your own certificate authority.
|
|
380
|
+
|
|
381
|
+
**Renewal.** `aac agent renew --agent <name>` issues fresh certificates from the same CA.
|
|
382
|
+
|
|
383
|
+
#### I bring my own CA
|
|
384
|
+
|
|
385
|
+
Your own issuer has signed the agent's certificates, and your CA private key never reaches this machine. Certificate source alone does not qualify a production deployment.
|
|
386
|
+
|
|
387
|
+
**Flags.** All seven together: `--workload-cert-file`, `--terminal-cert-file` and `--tls-cert-file`, each with its key file (`--workload-key-file`, `--terminal-key-file`, `--tls-key-file`), plus `--ca-cert-file`. Never `--ca-key-file`: the CLI does not want your CA key.
|
|
388
|
+
|
|
389
|
+
**Keys and signatures.** Your CA's key must be Ed25519 or EC P-256, and it must have signed each certificate with Ed25519 or ECDSA-with-SHA-256. The two identity keys may be Ed25519 or EC P-256; the HTTPS key must be EC P-256. RSA is not supported: the sidecar cannot verify against it.
|
|
390
|
+
|
|
391
|
+
**The CLI** checks each certificate against its key, its issuer and its validity window, and HTTPS certificates against every configured connection name; registers the workload and writes the settings files and the pairing secret; publishes your CA certificate as a trust anchor named `<agent>-ca`.
|
|
392
|
+
|
|
393
|
+
**The CLI does not** create a certificate authority; issue any certificate; ask for, read or store your CA private key.
|
|
394
|
+
|
|
395
|
+
**Renewal.** `aac agent renew --agent <name>` cannot reissue what it did not sign: it takes the replacements your issuer produced.
|
|
396
|
+
|
|
397
|
+
<!-- material-cases:end -->
|
|
398
|
+
|
|
399
|
+
The settings files, `sidecar-config.yaml` and `compose.env` in the agent
|
|
400
|
+
folder and `publisher.env` in the tenant directory, follow from what the
|
|
401
|
+
agent records, in both cases. They hold endpoints, identifiers, settings and
|
|
402
|
+
the paths of the key files, never key material. `aac init` writes them as
|
|
403
|
+
its last step and writes them again on any later run. The layout decides the
|
|
404
|
+
paths written into the sidecar configuration: `host` (the default) uses
|
|
405
|
+
paths on this machine, for a sidecar that runs directly on it, and
|
|
406
|
+
`container` uses the paths where the files are mounted inside the
|
|
407
|
+
containers.
|
|
408
|
+
|
|
409
|
+
`publisher.env` belongs to the tenant, and every agent of the tenant writes
|
|
410
|
+
it. A write rewrites its paths and poll interval for this machine (hand
|
|
411
|
+
edits to those are replaced), but refuses to change its tenant ID, trust
|
|
412
|
+
domain or URLs, because that would repoint the publisher for every agent.
|
|
413
|
+
The refusal names the values that differ. If the file is the stale one, for
|
|
414
|
+
example after you deliberately moved the tenant to new endpoints and set up
|
|
415
|
+
its agents again, move it aside (`mv publisher.env publisher.env.old` in the
|
|
416
|
+
tenant directory) and run setup again from an agent with the intended
|
|
417
|
+
endpoints. The admin key and the published public keys stay where they are.
|
|
418
|
+
|
|
419
|
+
### How profiles, agents and tenant data fit together
|
|
420
|
+
|
|
421
|
+
```text
|
|
422
|
+
profile (~/.aac/config) ── bound to ──> tenant
|
|
423
|
+
├── credentials: ~/.aac/credentials/<tenant-id>
|
|
424
|
+
├── tenant directory: ~/.aac/tenants/<tenant-id>/
|
|
425
|
+
└── agents: ~/.aac/agents/<name>/, one per workload
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
* A **profile**, in `~/.aac/config`, names the AAC admin and data-plane
|
|
429
|
+
endpoints you use and, once registration or sign-in has bound it, the one
|
|
430
|
+
tenant it acts for. The trust URL and the sign-in choice are recorded in
|
|
431
|
+
each agent instead. "Profiles and configuration", further down,
|
|
432
|
+
covers profiles in full.
|
|
433
|
+
* The **tenant data** is shared by all of a tenant's agents on this
|
|
434
|
+
machine: the tenant API key and the tenant-admin session in
|
|
435
|
+
`~/.aac/credentials/`, and the tenant directory
|
|
436
|
+
`~/.aac/tenants/<tenant-id>/` with the tenant-admin signing key, the public
|
|
437
|
+
keys and certificates the publisher uploads, and `publisher.env`.
|
|
438
|
+
* An **agent folder**, in `~/.aac/agents/<name>/`, holds one workload's
|
|
439
|
+
material. It records the profile and the tenant it was created for and
|
|
440
|
+
keeps them: one tenant can have many agents, and an agent never
|
|
441
|
+
moves to another tenant.
|
|
442
|
+
|
|
443
|
+
### What each file is for, and what to back up
|
|
444
|
+
|
|
445
|
+
The table lists every file the CLI creates, what it is for, how long it
|
|
446
|
+
lives and whether to back it up. `<agent>` is your explicitly selected agent
|
|
447
|
+
name and `<tenant-id>` the tenant id AAC allocated.
|
|
448
|
+
|
|
449
|
+
For what each of these proves, who else ever gets a copy, what never leaves you
|
|
450
|
+
and how the pieces fit together — including when your own certificate authority
|
|
451
|
+
signs instead of this tool — see
|
|
452
|
+
[Keys and certificates](https://cascadeauth.github.io/aac-sidecar-go/keys-and-certificates.html).
|
|
453
|
+
|
|
454
|
+
<!-- inventory-table:start -->
|
|
455
|
+
| No. | Material | Where | Used for | Lifetime | Back up? |
|
|
456
|
+
|---|---|---|---|---|---|
|
|
457
|
+
| 1 | 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` |
|
|
458
|
+
| 2 | tenant_admin_session | `~/.aac/credentials/<tenant-id>.session` | CLI admin calls | hours; `aac sso login` | No. sign in again |
|
|
459
|
+
| 3 | 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`; a lost public half is derived from the private key); production custody belongs in an HSM/KMS, not a password manager |
|
|
460
|
+
| 4 | root_signing_key | `~/.aac/agents/<agent>/sidecar/root.pem`, `~/.aac/agents/<agent>/sidecar/root.pub.pem`, `~/.aac/tenants/<tenant-id>/root-keys/<agent>-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 agent name) | No. never restore the private key: a replacement agent gets a new name and therefore a new key id, published alongside the old one until the old one is retired; its public copies are recreated from it |
|
|
461
|
+
| 5 | development_ca | `~/.aac/agents/<agent>/keep/ca.key`, `~/.aac/agents/<agent>/sidecar/ca.crt`, `~/.aac/agents/<agent>/agent/ca.crt`, `~/.aac/tenants/<tenant-id>/spiffe-bundle/<agent>-dev-ca.ca.pem` | signs the workload, terminal and TLS certificates; peers trust it via the published bundle | 7 days; `aac agent renew --ca` | No. development only; reissue; a lost copy of the certificate is copied back from `sidecar/ca.crt` |
|
|
462
|
+
| 6 | workload_svid | `~/.aac/agents/<agent>/sidecar/workload.key`, `~/.aac/agents/<agent>/sidecar/workload.crt` | the sidecar's SPIFFE identity: proves on every live request that this workload holds the key behind its certificate | 1 day; `aac agent renew` | No. reissue |
|
|
463
|
+
| 7 | terminal_attestation | `~/.aac/agents/<agent>/sidecar/terminal.key`, `~/.aac/agents/<agent>/sidecar/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; setup gives it its own key so the two signing jobs stay separate, but a receipt is verified against your published CA and the expected agent identity, which the workload certificate also satisfies — treat both keys as one blast radius | 1 day; `aac agent renew` | No. reissue |
|
|
464
|
+
| 8 | https_tls | `~/.aac/agents/<agent>/sidecar/server.key`, `~/.aac/agents/<agent>/sidecar/server.crt` | the sidecar's HTTPS listener | 1 day; `aac agent renew` | No. reissue |
|
|
465
|
+
| 9 | outbound_ca_bundle | `~/.aac/agents/<agent>/sidecar/outbound-ca.pem` | the sidecar verifies TLS to AAC and configured peers | rebuilt by renew; `aac agent renew` | No. derived |
|
|
466
|
+
| 10 | pairing_secret | `~/.aac/agents/<agent>/agent/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 |
|
|
467
|
+
| 11 | agent_record | `~/.aac/agents/<agent>/record.json` | the agent's identities and progress; every `aac agent` verb reads it first | for the life of the agent | **Recommended.** cannot be recreated by any command; without it start a new agent under a new name (see the new-computer setup procedure) |
|
|
468
|
+
| 12 | 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 agents | for the life of the tenant | **Recommended.** back up with the tenant directory: a replacement agent 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) |
|
|
469
|
+
| 13 | settings_files | `~/.aac/agents/<agent>/sidecar-config.yaml`, `~/.aac/agents/<agent>/compose.env`, `~/.aac/tenants/<tenant-id>/publisher.env` | non-secret settings derived from the agent's record | written again on demand; `aac init --profile <profile> --agent <agent>` | No. derived; `aac init --profile <profile> --agent <agent>` writes them again |
|
|
470
|
+
<!-- inventory-table:end -->
|
|
471
|
+
|
|
472
|
+
In short: back up the tenant API key and the whole tenant directory while
|
|
473
|
+
the machine is healthy. Everything else is regenerated.
|
|
474
|
+
|
|
475
|
+
### Inspect and maintain an agent
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
aac agent status --agent orders --output table # what exists, expiry, permissions, next command
|
|
479
|
+
aac agent status --agent orders --remote # also: is the public trust material visible?
|
|
480
|
+
aac agent renew --agent orders # replace the short-lived certificates; old ones archived
|
|
481
|
+
aac agent list # every agent on this machine, with its case
|
|
482
|
+
aac init --profile stage --agent orders --layout container # write the settings again, container paths
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
`status` works offline by default and exits with code 3 when material is
|
|
486
|
+
missing, expired or unsafely permissioned. `renew` reissues the one-day
|
|
487
|
+
certificates with fresh keys, and reissues the development CA only when it
|
|
488
|
+
is expired or within a day of expiry (or when `--ca` is given); a new CA is
|
|
489
|
+
published under the next anchor id and needs the publisher restarted. For an
|
|
490
|
+
agent whose own issuer signed its certificates, `renew` cannot reissue
|
|
491
|
+
anything: it takes the replacements your issuer produced, through the same
|
|
492
|
+
flags `aac init` accepts.
|
|
493
|
+
Re-running `aac init` writes the settings files again and never touches a
|
|
494
|
+
key.
|
|
495
|
+
|
|
496
|
+
`status` also names one next step, `next_command`. When material is
|
|
497
|
+
missing, it is the fix for the first missing item: restore it from your
|
|
498
|
+
backup, recreate a lost public copy from the file it copies, renew, or run
|
|
499
|
+
setup again. Otherwise it is renewal when a certificate is due. After the step,
|
|
500
|
+
run `status` again. Commands it names that change your tenant carry the
|
|
501
|
+
agent's own `--tenant-id` and `--admin-url`, so a value left in
|
|
502
|
+
`AAC_TENANT_ID` or `AAC_ADMIN_URL` cannot aim them at another tenant. The
|
|
503
|
+
file recipes use `openssl`, which must be OpenSSL 1.1.1 or newer: the
|
|
504
|
+
`openssl` that ships with macOS cannot read Ed25519 keys, so install a
|
|
505
|
+
current OpenSSL there, for example with Homebrew.
|
|
506
|
+
|
|
507
|
+
Every private file is created once, readable only by you, and never
|
|
508
|
+
overwritten. Before generating anything, `init` checks every destination
|
|
509
|
+
and refuses with the full list if one already exists. `aac init` never
|
|
510
|
+
installs or replaces a tenant-admin key for an existing tenant: the key on
|
|
511
|
+
this machine must already be the tenant's active admin key, and replacing
|
|
512
|
+
it is always the deliberate `aac tenant rotate-admin-key` command, never a
|
|
513
|
+
side effect of setup. A second agent for the same tenant
|
|
514
|
+
(`--agent other`) reuses the tenant-admin key and gets its own root key
|
|
515
|
+
id.
|
|
516
|
+
|
|
517
|
+
### What to do when keys and certificates expire
|
|
518
|
+
|
|
519
|
+
It is the certificates that expire, and renewing replaces their keys with
|
|
520
|
+
them. `aac agent status` shows when each certificate runs out and flags
|
|
521
|
+
it before it does: six hours ahead for the one-day certificates, a day
|
|
522
|
+
ahead for the development CA.
|
|
523
|
+
|
|
524
|
+
| Material | Lasts | When it runs out | What to do |
|
|
525
|
+
|---|---|---|---|
|
|
526
|
+
| Workload, terminal-attestation and HTTPS certificates | 1 day | the sidecar can no longer prove its identity, sign receipts or serve HTTPS | `aac agent renew --agent <name>`, then restart the sidecar so it loads them |
|
|
527
|
+
| Development CA | 7 days | certificates it signed stop being trusted, and no new ones can be issued | `renew` also replaces the CA when it is expired or within a day of expiry, or when `--ca` is given, and then reissues every certificate under it: restart the publisher so the new CA is published, restart the sidecar and anything else that trusted the old CA, and confirm with `aac agent status --agent <name> --remote` |
|
|
528
|
+
| Tenant-admin session | hours | administration commands ask you to sign in again | `aac sso login` |
|
|
529
|
+
| Tenant API key, tenant-admin signing key, root signing key | do not expire | — | replaced only on purpose: see "Keys" and "Setting up aac-cli on a new computer" |
|
|
530
|
+
|
|
531
|
+
A simple routine: when you start work, run
|
|
532
|
+
`aac agent status --agent <name>`, and when it names renewal, run
|
|
533
|
+
`aac agent renew --agent <name>` and restart the sidecar. About once
|
|
534
|
+
a week that renewal also replaces the development CA (`renew` then reports
|
|
535
|
+
`republication_required: true`): restart the publisher, the sidecar and the
|
|
536
|
+
agent, and anything else that trusted the old CA. Renewing is safe to
|
|
537
|
+
repeat, but it is not a no-op: every run issues fresh keys and moves the old
|
|
538
|
+
files to the agent's `archive/` directory, so whatever reads them must
|
|
539
|
+
reload. Recreate or restart the affected processes with your deployment's
|
|
540
|
+
normal commands after publishing and explicitly refreshing each peer's public
|
|
541
|
+
trust. Restarting a process does not renew its certificates.
|
|
542
|
+
|
|
543
|
+
All of this is development material. In production, certificates come from
|
|
544
|
+
your own issuer and follow its renewal process.
|
|
545
|
+
|
|
546
|
+
## Setting up aac-cli on a new computer
|
|
547
|
+
|
|
548
|
+
This procedure covers a lost machine and a second machine alike. It needs
|
|
549
|
+
the two backups named above: the tenant API key from
|
|
550
|
+
`~/.aac/credentials/<tenant-id>` and the tenant directory
|
|
551
|
+
`~/.aac/tenants/<tenant-id>/`. The tenant id is the `tnt-…` value in your
|
|
552
|
+
profile; `aac profile show` prints it, and the example below uses
|
|
553
|
+
`tnt-550e8400-e29b-41d4-9716-446655440000`.
|
|
554
|
+
|
|
555
|
+
1. Install the CLI, create the profile, and sign in. Signing in needs the
|
|
556
|
+
tenant id, because a fresh profile is not yet bound to a tenant:
|
|
557
|
+
|
|
558
|
+
```bash
|
|
559
|
+
pip install aac-cli
|
|
560
|
+
aac profile create stage \
|
|
561
|
+
--admin-url https://api.stage.cascadeauth.dev \
|
|
562
|
+
--data-plane-url https://api.stage.cascadeauth.dev
|
|
563
|
+
aac sso login --profile stage --idp github \
|
|
564
|
+
--tenant-id tnt-550e8400-e29b-41d4-9716-446655440000
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
An enterprise tenant signs in through its own identity provider instead,
|
|
568
|
+
naming the connection by its URL:
|
|
569
|
+
|
|
570
|
+
```bash
|
|
571
|
+
aac sso login --profile stage \
|
|
572
|
+
--idp-url https://login.microsoftonline.com/11111111-2222-3333-4444-555555555555/v2.0 \
|
|
573
|
+
--tenant-id tnt-550e8400-e29b-41d4-9716-446655440000
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
2. Restore the tenant API key to `~/.aac/credentials/<tenant-id>`, readable
|
|
577
|
+
only by you, or issue a new one if it was not backed up:
|
|
578
|
+
|
|
579
|
+
```bash
|
|
580
|
+
aac tenant reissue-api-key --profile stage
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
Restore `~/.aac/tenants/<tenant-id>/` from the backup, with directories
|
|
584
|
+
readable only by you and files likewise. Its `publisher.env` still names
|
|
585
|
+
the old machine's paths; step 3 rewrites them for this one.
|
|
586
|
+
|
|
587
|
+
If the tenant directory was not backed up, first check whether another
|
|
588
|
+
machine still holds `~/.aac/tenants/<tenant-id>/`: every machine that has
|
|
589
|
+
run `aac init` for this tenant has one. If so, copy it from there. That
|
|
590
|
+
is the backup, and the rest of this step does not apply.
|
|
591
|
+
|
|
592
|
+
If no copy exists anywhere, the tenant-admin key and every root key that
|
|
593
|
+
lived on the lost machine are gone for good. Install a new admin key,
|
|
594
|
+
then account for every root key the tenant has published (the `openssl`
|
|
595
|
+
commands need OpenSSL 1.1.1 or newer, which macOS does not ship):
|
|
596
|
+
|
|
597
|
+
```bash
|
|
598
|
+
openssl genpkey -algorithm ed25519 -out tenant-admin.pem
|
|
599
|
+
openssl pkey -in tenant-admin.pem -pubout -out tenant-admin.public.pem
|
|
600
|
+
aac tenant rotate-admin-key --profile stage \
|
|
601
|
+
--tenant-admin-pubkey-file tenant-admin.public.pem
|
|
602
|
+
aac trust-anchor list --profile stage --role root-signing --output table
|
|
603
|
+
aac trust-anchor revoke --profile stage --kid starter-root-v1 \
|
|
604
|
+
--reason 'machine lost'
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
The list shows each root key with its state. A publisher may drop a key
|
|
608
|
+
from its published set only while another active key stays in the set,
|
|
609
|
+
so a set holding nothing but the new key is accepted only once no other
|
|
610
|
+
key is active. Revoke every active key whose private half was lost, one
|
|
611
|
+
`revoke` per key id, and no other key: a root key that is still in use
|
|
612
|
+
elsewhere must stay active, and its public file must be in the tenant
|
|
613
|
+
directory the publisher runs from. Then run step 3 with
|
|
614
|
+
`--tenant-admin-key-file tenant-admin.pem` added.
|
|
615
|
+
|
|
616
|
+
3. Prepare `laptop-2.yaml` with the new `agent_name` and the original
|
|
617
|
+
workload path, deployment and trust choices. Run setup with that **new agent name**. A developer tenant
|
|
618
|
+
passes its sign-in choice, so an expired session is renewed without a
|
|
619
|
+
prompt:
|
|
620
|
+
|
|
621
|
+
```bash
|
|
622
|
+
aac init --profile stage --agent laptop-2 --agent-config laptop-2.yaml --idp github \
|
|
623
|
+
--trust-url https://trust.stage.cascadeauth.dev
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
An enterprise tenant runs the same command without `--idp`; `--idp`
|
|
627
|
+
selects only the GitHub and Google developer sign-ins. The guided setup
|
|
628
|
+
then uses the session from step 1. If that session has expired, repeat
|
|
629
|
+
the `aac sso login --idp-url …` command from step 1 first:
|
|
630
|
+
|
|
631
|
+
```bash
|
|
632
|
+
aac init --profile stage --agent laptop-2 --agent-config laptop-2.yaml \
|
|
633
|
+
--trust-url https://trust.stage.cascadeauth.dev
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
4. Start the publisher from the tenant directory, restored or written by
|
|
637
|
+
step 3. A restored directory's root-key set holds the old and the new
|
|
638
|
+
public keys, which is exactly what AAC requires: a set that drops every
|
|
639
|
+
currently active key at once is refused. Retire the old key later, by
|
|
640
|
+
removing its public file, once nothing signs with it. A directory written
|
|
641
|
+
fresh after the revocations in step 2 holds only the new key, which is
|
|
642
|
+
accepted because no other key is active any more.
|
|
643
|
+
|
|
644
|
+
### Why the replacement agent needs a new name
|
|
645
|
+
|
|
646
|
+
The agent name is baked into two identifiers that are published for
|
|
647
|
+
the whole tenant: the root key id `<agent>-root-v1` and the CA
|
|
648
|
+
anchor id (`<agent>-dev-ca` for a development CA the CLI created,
|
|
649
|
+
`<agent>-ca` for your own issuer's). Other parties look up your keys and
|
|
650
|
+
certificates by those ids. AAC therefore never accepts new key material
|
|
651
|
+
under an id that has already been published: a key id names one key, for
|
|
652
|
+
good. A new machine has to generate new keys, so it has to publish them
|
|
653
|
+
under new ids, which means a new agent name. The new ids are published
|
|
654
|
+
alongside the old ones until the old ones are retired.
|
|
655
|
+
|
|
656
|
+
Reusing the old agent name on the new machine is refused on purpose:
|
|
657
|
+
the restored tenant directory still holds that name's published root key,
|
|
658
|
+
and `init` never generates different key material under an existing key
|
|
659
|
+
id. The development certificates and the pairing secret are regenerated by
|
|
660
|
+
step 3; they are never restored.
|
|
661
|
+
|
|
662
|
+
## Profiles and configuration
|
|
663
|
+
|
|
664
|
+
A **profile** is a named set of endpoints plus the tenant the profile is
|
|
665
|
+
bound to. Profiles live in `~/.aac/config`, a plain INI file with one
|
|
666
|
+
section per profile:
|
|
667
|
+
|
|
668
|
+
```ini
|
|
669
|
+
[main]
|
|
670
|
+
admin_url = http://127.0.0.1:8000
|
|
671
|
+
data_plane_url = http://127.0.0.1:9000
|
|
672
|
+
tenant_id = tnt-550e8400-e29b-41d4-9716-446655440000
|
|
673
|
+
|
|
674
|
+
[stage]
|
|
675
|
+
admin_url = https://api.stage.cascadeauth.dev
|
|
676
|
+
data_plane_url = https://api.stage.cascadeauth.dev
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
```bash
|
|
680
|
+
aac profile list # every profile, including the built-in main
|
|
681
|
+
aac profile show stage --output table # stored and effective values, with sources
|
|
682
|
+
aac profile create prod --admin-url https://aac-admin.example.com \
|
|
683
|
+
--data-plane-url https://aac-data.example.com
|
|
684
|
+
aac profile update main # edit the built-in baseline
|
|
685
|
+
aac profile delete prod # local only; never touches the server
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
`main` is the reserved baseline profile. It always exists, with localhost
|
|
689
|
+
defaults until `aac profile update main` stores real values, and cannot be
|
|
690
|
+
created or deleted. Every operational command selects its profile in this
|
|
691
|
+
order: `--profile <name>`, then the `AAC_PROFILE` environment variable, then
|
|
692
|
+
`main`. A selected profile that does not exist is an error, never a silent
|
|
693
|
+
fallback. Within the selected profile, a flag beats an environment variable
|
|
694
|
+
(`AAC_ADMIN_URL`, `AAC_DATA_PLANE_URL`, `AAC_TENANT_ID`), which beats the
|
|
695
|
+
stored value.
|
|
696
|
+
|
|
697
|
+
`tenant_id` is managed for you. Profile commands display it but never ask
|
|
698
|
+
for it; only `aac tenant register`, `aac init` and `aac sso login` write it.
|
|
699
|
+
It is your organisation's identifier in the federation, allocated by AAC at
|
|
700
|
+
registration in the form `tnt-<uuid>`, and it never changes.
|
|
701
|
+
|
|
702
|
+
`AAC_CLI_HOME` relocates the whole `~/.aac` tree. Use it for a throwaway
|
|
703
|
+
test home, or to keep separate accounts hard-walled from each other:
|
|
704
|
+
|
|
705
|
+
```bash
|
|
706
|
+
AAC_CLI_HOME=$HOME/.aac-prod aac profile list
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
### Credentials
|
|
710
|
+
|
|
711
|
+
Two credential files can coexist under `~/.aac/credentials/`:
|
|
712
|
+
|
|
713
|
+
* `<tenant-id>` holds the tenant API key, written once by registration and
|
|
714
|
+
printed exactly once. The sidecar and the data-plane commands
|
|
715
|
+
(`aac chain`, `aac trust-anchor`) present it. Only a hash of it exists
|
|
716
|
+
on the server, so a lost key is replaced, never recovered.
|
|
717
|
+
* `<tenant-id>.session` holds the short-lived session that `aac sso login`
|
|
718
|
+
creates. The administration commands (`aac tenant`, `aac sso`) use it
|
|
719
|
+
automatically. Sessions cannot be refreshed: when one expires, any
|
|
720
|
+
administration command tells you to sign in again, and there is no
|
|
721
|
+
refresh token that could be stolen.
|
|
722
|
+
|
|
723
|
+
## Signing in
|
|
724
|
+
|
|
725
|
+
```bash
|
|
726
|
+
aac sso login --profile stage --idp github # developer tenant: GitHub or Google
|
|
727
|
+
aac sso login --profile prod \
|
|
728
|
+
--idp-url https://login.microsoftonline.com/11111111-2222-3333-4444-555555555555/v2.0
|
|
729
|
+
aac sso login --profile prod \
|
|
730
|
+
--idp-url https://login.microsoftonline.com/11111111-2222-3333-4444-555555555555/v2.0 \
|
|
731
|
+
--flow pkce --no-browser # print the URL instead of opening a browser
|
|
732
|
+
aac sso whoami --profile stage --output table # cached session and the tenant's assigned domain
|
|
733
|
+
aac sso logout --profile stage
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
A developer tenant signs in with `--idp github` or `--idp google`; if you
|
|
737
|
+
need another sign-in provider, contact support@cascadeauth.com. An
|
|
738
|
+
enterprise tenant signs in through its Microsoft Entra ID connection, named
|
|
739
|
+
by the connection's exact URL with `--idp-url`; when several connections
|
|
740
|
+
are registered, the error message lists the exact commands to choose one.
|
|
741
|
+
|
|
742
|
+
Sign-in uses the device flow by default, so the browser can be on another
|
|
743
|
+
machine and headless hosts work. Google connections use a loopback flow
|
|
744
|
+
instead, because Google's device flow is scope-restricted; `--flow`
|
|
745
|
+
overrides the choice. `whoami` and `logout` work offline and never contact
|
|
746
|
+
AAC.
|
|
747
|
+
|
|
748
|
+
## Output and exit codes
|
|
749
|
+
|
|
750
|
+
Every command that reports a result prints one JSON document to standard
|
|
751
|
+
output by default, with progress notes and diagnostics on standard error,
|
|
752
|
+
so the JSON stays parseable in scripts; `--output table` prints a readable
|
|
753
|
+
table instead. The three profile writers are the exception: `aac profile
|
|
754
|
+
create`, `update` and `delete` confirm on standard error, leave standard
|
|
755
|
+
output empty and take no `--output`.
|
|
756
|
+
|
|
757
|
+
| Exit code | Meaning |
|
|
758
|
+
|---|---|
|
|
759
|
+
| `0` | Success. |
|
|
760
|
+
| `1` | AAC or the identity provider rejected the request. |
|
|
761
|
+
| `2` | Usage error: an invalid flag, value or flag combination. |
|
|
762
|
+
| `3` | A local configuration or state problem: profile, credential file, cached session or agent. |
|
|
763
|
+
| `4` | Transport failure: an endpoint could not be reached. |
|
|
764
|
+
|
|
765
|
+
If AAC asks you to slow down, the command shows the interval to wait. The
|
|
766
|
+
CLI never replays a change on its own; wait and rerun deliberately.
|
|
767
|
+
|
|
768
|
+
## Enterprise tenants
|
|
769
|
+
|
|
770
|
+
Enterprise tenants are registered through an onboarding ceremony: AAC
|
|
771
|
+
operations opens a registration window and hands the tenant operator a
|
|
772
|
+
bootstrap token, which the commands below read from `--bootstrap-token` or
|
|
773
|
+
the `AAC_BOOTSTRAP_TOKEN` environment variable. The token authorises only
|
|
774
|
+
the onboarding steps shown with it; every other administration command runs
|
|
775
|
+
under a signed-in session.
|
|
776
|
+
|
|
777
|
+
For enterprise sign-in, AAC supports Microsoft Entra ID (also known as
|
|
778
|
+
Azure Active Directory or Azure AD). If your organisation needs another
|
|
779
|
+
identity provider, contact support@cascadeauth.com.
|
|
780
|
+
|
|
781
|
+
### Register the tenant
|
|
782
|
+
|
|
783
|
+
Generate the tenant-admin key pair first. The CLI transmits the public half
|
|
784
|
+
only and refuses a private key:
|
|
785
|
+
|
|
786
|
+
```bash
|
|
787
|
+
openssl genpkey -algorithm ed25519 -out tenant-admin.pem
|
|
788
|
+
openssl pkey -in tenant-admin.pem -pubout -out tenant-admin.public.pem
|
|
789
|
+
aac profile create prod --admin-url https://aac-admin.example.com \
|
|
790
|
+
--data-plane-url https://aac-data.example.com
|
|
791
|
+
aac tenant register --profile prod --display-name 'Example Corporation' \
|
|
792
|
+
--contact ops@example.com \
|
|
793
|
+
--workload-spiffe-id spiffe://example.com/treasury-agent/v1 \
|
|
794
|
+
--tenant-admin-pubkey-file ./tenant-admin.public.pem \
|
|
795
|
+
--bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
AAC allocates the tenant id; you never choose one. The response prints the
|
|
799
|
+
id and the tenant API key exactly once, and the CLI saves both. A profile
|
|
800
|
+
that is already bound to a tenant refuses to register another; use an
|
|
801
|
+
unbound profile. If the response is lost, rerun the same command with no
|
|
802
|
+
changes to the identifying flags: the CLI resumes the frozen request rather
|
|
803
|
+
than registering twice.
|
|
804
|
+
|
|
805
|
+
```bash
|
|
806
|
+
aac tenant describe --profile prod --output table
|
|
807
|
+
aac tenant update --profile prod --tenant-id tnt-550e8400-e29b-41d4-9716-446655440000 \
|
|
808
|
+
--display-name 'Example Corporation Ltd'
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
### Connect your identity provider
|
|
812
|
+
|
|
813
|
+
An identity-provider connection is described by a JSON file
|
|
814
|
+
(`aac sso register-idp --help` shows the fields, with an example for a
|
|
815
|
+
Microsoft Entra ID tenant). Before the first
|
|
816
|
+
connection, generate an offline recovery key so the connection can be
|
|
817
|
+
repaired if the provider is ever unavailable; only the enrollment file
|
|
818
|
+
leaves the machine:
|
|
819
|
+
|
|
820
|
+
```bash
|
|
821
|
+
aac sso generate-idp-recovery-key \
|
|
822
|
+
--private-key-file offline-idp-recovery-private.pem \
|
|
823
|
+
--enrollment-file idp-recovery-enrollment.json
|
|
824
|
+
aac sso enroll-idp-recovery-key --profile prod \
|
|
825
|
+
--file idp-recovery-enrollment.json --bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
|
|
826
|
+
aac sso register-idp --profile prod --file connection.json \
|
|
827
|
+
--bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
|
|
828
|
+
aac sso list-idp --profile prod
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
Later connections, corrections (`aac sso replace-idp`) and the two-party
|
|
832
|
+
repair ceremony (`aac sso request-idp-repair`, `aac sso approve-idp-repair`)
|
|
833
|
+
are described by their `--help`.
|
|
834
|
+
|
|
835
|
+
### Keys
|
|
836
|
+
|
|
837
|
+
Replacing the tenant-admin key is immediate and forward-only: the previous
|
|
838
|
+
key stops verifying at once and can never be reinstated. The trust-anchor
|
|
839
|
+
publisher signs its uploads with this key and loads it once, at start, so
|
|
840
|
+
sequence the change: stop the publisher, register the new public half, put
|
|
841
|
+
the new private key where the publisher's configuration points, start the
|
|
842
|
+
publisher, and confirm that its first upload is accepted under the new key.
|
|
843
|
+
A publisher installed as a system service reads the key path from its
|
|
844
|
+
environment file, which you can point at the new file. A Docker publisher's
|
|
845
|
+
environment and key mount are fixed when the container is created, so
|
|
846
|
+
overwrite the key at the mounted path and recreate the container; a plain
|
|
847
|
+
restart would come back on the old key.
|
|
848
|
+
|
|
849
|
+
```bash
|
|
850
|
+
aac tenant rotate-admin-key --profile prod \
|
|
851
|
+
--tenant-admin-pubkey-file tenant-admin.public.pem
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
A developer tenant set up with `aac init` keeps its tenant-admin key in its
|
|
855
|
+
tenant directory, where the publisher's configuration points. To replace a
|
|
856
|
+
lost key there, write the new pair in place, readable only by you, sign in,
|
|
857
|
+
register the new public half, and then recreate every publisher container.
|
|
858
|
+
Pass the tenant id and admin URL explicitly, as `aac agent status`
|
|
859
|
+
does, so the change cannot land on another tenant. The `openssl` commands
|
|
860
|
+
need OpenSSL 1.1.1 or newer, which macOS does not ship:
|
|
861
|
+
|
|
862
|
+
```bash
|
|
863
|
+
cd ~/.aac/tenants/tnt-550e8400-e29b-41d4-9716-446655440000
|
|
864
|
+
(umask 077 && openssl genpkey -algorithm ed25519 -out tenant-admin.pem.tmp && mv tenant-admin.pem.tmp tenant-admin.pem)
|
|
865
|
+
(umask 077 && openssl pkey -in tenant-admin.pem -pubout -out tenant-admin.pub.pem.tmp && mv tenant-admin.pub.pem.tmp tenant-admin.pub.pem)
|
|
866
|
+
aac sso login --profile stage --idp github \
|
|
867
|
+
--tenant-id tnt-550e8400-e29b-41d4-9716-446655440000 \
|
|
868
|
+
--admin-url https://api.stage.cascadeauth.dev
|
|
869
|
+
aac tenant rotate-admin-key --profile stage \
|
|
870
|
+
--tenant-id tnt-550e8400-e29b-41d4-9716-446655440000 \
|
|
871
|
+
--admin-url https://api.stage.cascadeauth.dev \
|
|
872
|
+
--tenant-admin-pubkey-file tenant-admin.pub.pem
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
The tenant API key can be rotated without downtime. `issue` stages a second
|
|
876
|
+
active key in a new file and never replaces the current one; move every
|
|
877
|
+
client to it, then retire the old key by its exact id:
|
|
878
|
+
|
|
879
|
+
```bash
|
|
880
|
+
aac tenant api-key list --profile prod
|
|
881
|
+
aac tenant api-key issue --profile prod
|
|
882
|
+
aac tenant api-key retire --profile prod --key-id ak_0123456789abcdef --yes
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
A lost or suspected-compromised key is replaced instead: every active key is
|
|
886
|
+
revoked and one fresh key is printed once.
|
|
887
|
+
|
|
888
|
+
```bash
|
|
889
|
+
aac tenant reissue-api-key --profile prod
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
### Domains and workloads
|
|
893
|
+
|
|
894
|
+
Every tenant gets a hosted trust domain from AAC; registration saves it in
|
|
895
|
+
the profile, and `aac tenant assign-hosted-domain` assigns one to a tenant
|
|
896
|
+
that has none yet. To use your own DNS domain as a trust domain instead,
|
|
897
|
+
prove control of it with a DNS TXT record, then bind it:
|
|
898
|
+
|
|
899
|
+
```bash
|
|
900
|
+
aac tenant issue-domain-challenge --profile prod --domain example.com
|
|
901
|
+
# Publish the TXT record the command prints, then:
|
|
902
|
+
aac tenant verify-domain --profile prod --domain example.com
|
|
903
|
+
aac tenant bind-trust-domain --profile prod --trust-domain example.com
|
|
904
|
+
aac tenant list-trust-domains --profile prod --output table
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
Workloads are the identities your sidecars run under:
|
|
908
|
+
|
|
909
|
+
```bash
|
|
910
|
+
aac tenant add-workload --profile prod --spiffe-id spiffe://example.com/payroll/v1 \
|
|
911
|
+
--display-name Payroll
|
|
912
|
+
aac tenant list-workloads --profile prod
|
|
913
|
+
aac tenant deactivate-workload --profile prod --workload-id wl-2f9c1e --reason 'service retired'
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
Deactivation is terminal, and a deactivated SPIFFE id stays reserved.
|
|
917
|
+
|
|
918
|
+
### Inspect trust material and audit a chain
|
|
919
|
+
|
|
920
|
+
```bash
|
|
921
|
+
aac trust-anchor list --profile prod --output table # every published key, every lifecycle state
|
|
922
|
+
aac trust-anchor describe --profile prod --kid starter-root-v1
|
|
923
|
+
aac chain show --profile prod \
|
|
924
|
+
--token-id c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9 --output table
|
|
925
|
+
aac chain show --profile prod \
|
|
926
|
+
--token-id c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9 \
|
|
927
|
+
--render --render-out ./chain.html
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
`chain show` accepts any hop's token id or the chain root id and shows the
|
|
931
|
+
chain to any tenant that took part in it. The timeline carries metadata
|
|
932
|
+
only; the business content of each step stays in your own audit stream.
|
|
933
|
+
|
|
934
|
+
## Getting help
|
|
935
|
+
|
|
936
|
+
`aac --help` lists the command groups and `aac <command> --help` shows every
|
|
937
|
+
flag with its default. Errors name the exact command to run next.
|
|
938
|
+
|
|
939
|
+
## License
|
|
940
|
+
|
|
941
|
+
Apache-2.0.
|