aac-cli 0.2.11__tar.gz → 0.2.13__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.11/aac_cli.egg-info → aac_cli-0.2.13}/PKG-INFO +29 -25
- {aac_cli-0.2.11 → aac_cli-0.2.13}/README.md +28 -24
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/__init__.py +1 -1
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/cli.py +5 -2
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/data/renderer-source.json +7 -7
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/from_sidecar.py +4 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/inputs.py +2 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/model.py +97 -6
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/provenance.py +22 -6
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/visualizer.py +51 -8
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/agent_cli.py +4 -3
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/agent_health.py +5 -1
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/agent_layout.py +6 -4
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/dev_material.py +47 -8
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/init_cli.py +5 -1
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/material_cases.py +3 -2
- {aac_cli-0.2.11 → aac_cli-0.2.13/aac_cli.egg-info}/PKG-INFO +29 -25
- {aac_cli-0.2.11 → aac_cli-0.2.13}/pyproject.toml +1 -1
- {aac_cli-0.2.11 → aac_cli-0.2.13}/LICENSE +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/MANIFEST.in +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/NOTICE +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/THIRD_PARTY_NOTICES.md +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/__main__.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/__init__.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/data/action-taken-v1.json +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/data/predicates-v1.json +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/narrative.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/schemas.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/temporal.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/theme.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/timeline_source.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/admin_key_pem.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/aeg_cli.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/aeg_list.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/agent_config.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/agent_record.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/api_key_rotation_state.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/chain_list.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/cli.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/config.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/config_render.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/idp_recovery.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/interrupts.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/profiles.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/reference.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/registration_state.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/safe_output.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/secure_files.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/sso_login.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/supplied_material.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/trace_query.py +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli.egg-info/SOURCES.txt +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli.egg-info/dependency_links.txt +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli.egg-info/requires.txt +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli.egg-info/top_level.txt +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/launchers/aac +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/launchers/aeg +0 -0
- {aac_cli-0.2.11 → aac_cli-0.2.13}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aac-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.13
|
|
4
4
|
Summary: The aac platform CLI — headless operator interface to the AAC control plane (tenant registration, chain audit).
|
|
5
5
|
Author: Agent Authority Cloud Project
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -28,9 +28,9 @@ Dynamic: license-file
|
|
|
28
28
|
# aac-cli — the `aac` command
|
|
29
29
|
|
|
30
30
|
`aac` is the command-line tool for tenants of Agent Authority Cloud (AAC).
|
|
31
|
-
AAC lets an
|
|
32
|
-
which tenant they belong to and what they were
|
|
33
|
-
**tenant** is one
|
|
31
|
+
AAC lets an organization's AI agents and services prove, on every request,
|
|
32
|
+
which tenant they belong to and what they were authorized to do. A
|
|
33
|
+
**tenant** is one organization registered with AAC. Each of its workloads
|
|
34
34
|
runs beside an AAC sidecar that signs outgoing requests and verifies
|
|
35
35
|
incoming ones against public trust material the tenant publishes.
|
|
36
36
|
|
|
@@ -69,7 +69,8 @@ pip install aac-cli
|
|
|
69
69
|
aac --version
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
Supported platforms are macOS, Linux and Linux Docker containers. Python 3.11
|
|
73
|
+
to 3.14 is supported. Release checks test all four versions.
|
|
73
74
|
|
|
74
75
|
> **Mind the name.** `pip install aac` installs an unrelated project that
|
|
75
76
|
> happens to share the acronym. The AAC package is **`aac-cli`**; the
|
|
@@ -183,7 +184,7 @@ never asks you to pick between GitHub and Google again.
|
|
|
183
184
|
configuration references it by path.
|
|
184
185
|
|
|
185
186
|
**By default everything `aac init` generates is development material.** The
|
|
186
|
-
development CA lives
|
|
187
|
+
development CA lives 365 days and the leaf certificates 90 days. Nothing
|
|
187
188
|
there qualifies for production, whatever the tenant's domain. If your own
|
|
188
189
|
issuer signs your agent's certificates instead, see "Two ways to get your
|
|
189
190
|
agent's certificates" below; moving to production also means a tenant-admin
|
|
@@ -205,7 +206,7 @@ three commands look after it:
|
|
|
205
206
|
|
|
206
207
|
```bash
|
|
207
208
|
aac agent status --agent orders # what exists, what is due, and the next step
|
|
208
|
-
aac agent renew --agent orders # replace the
|
|
209
|
+
aac agent renew --agent orders # replace the certificates
|
|
209
210
|
aac agent list # every agent on this machine
|
|
210
211
|
```
|
|
211
212
|
|
|
@@ -429,11 +430,11 @@ tell you later and `aac agent renew` behaves accordingly.
|
|
|
429
430
|
|
|
430
431
|
The laptop case. The CLI creates a certificate authority on this machine and signs the agent's identity, receipt and HTTPS certificates with it.
|
|
431
432
|
|
|
432
|
-
**Flags.** No flags are needed. To reuse a development CA across agents, pass `--ca-key-file` and `--ca-cert-file` together; neither one alone.
|
|
433
|
+
**Flags.** No flags are needed. To reuse a development CA across agents, pass `--ca-key-file` and `--ca-cert-file` together; neither one alone. A CA that expires before the certificates it would sign is refused.
|
|
433
434
|
|
|
434
435
|
**Keys and signatures.** The CA and the two identity keys are Ed25519; the HTTPS key is EC P-256.
|
|
435
436
|
|
|
436
|
-
**The CLI** creates a
|
|
437
|
+
**The CLI** creates a 365-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`.
|
|
437
438
|
|
|
438
439
|
**The CLI does not** ask you for anything from your own certificate authority.
|
|
439
440
|
|
|
@@ -517,10 +518,10 @@ signs instead of this tool — see
|
|
|
517
518
|
| 2 | tenant_admin_session | `~/.aac/credentials/<tenant-id>.session` | CLI admin calls | hours; `aac sso login` | No. sign in again |
|
|
518
519
|
| 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 |
|
|
519
520
|
| 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 |
|
|
520
|
-
| 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 |
|
|
521
|
-
| 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 |
|
|
522
|
-
| 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 |
|
|
523
|
-
| 8 | https_tls | `~/.aac/agents/<agent>/sidecar/server.key`, `~/.aac/agents/<agent>/sidecar/server.crt` | the sidecar's HTTPS listener |
|
|
521
|
+
| 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 | 365 days; `aac agent renew --ca` | No. development only; reissue; a lost copy of the certificate is copied back from `sidecar/ca.crt` |
|
|
522
|
+
| 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 | 90 days; `aac agent renew` | No. reissue |
|
|
523
|
+
| 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 | 90 days; `aac agent renew` | No. reissue |
|
|
524
|
+
| 8 | https_tls | `~/.aac/agents/<agent>/sidecar/server.key`, `~/.aac/agents/<agent>/sidecar/server.crt` | the sidecar's HTTPS listener | 90 days; `aac agent renew` | No. reissue |
|
|
524
525
|
| 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 |
|
|
525
526
|
| 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 |
|
|
526
527
|
| 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) |
|
|
@@ -536,15 +537,15 @@ the machine is healthy. Everything else is regenerated.
|
|
|
536
537
|
```bash
|
|
537
538
|
aac agent status --agent orders --output table # what exists, expiry, permissions, next command
|
|
538
539
|
aac agent status --agent orders --remote # also: is the public trust material visible?
|
|
539
|
-
aac agent renew --agent orders # replace the
|
|
540
|
+
aac agent renew --agent orders # replace the certificates; old ones archived
|
|
540
541
|
aac agent list # every agent on this machine, with its case
|
|
541
542
|
aac init --profile stage --agent orders --layout container # write the settings again, container paths
|
|
542
543
|
```
|
|
543
544
|
|
|
544
545
|
`status` works offline by default and exits with code 3 when material is
|
|
545
|
-
missing, expired or unsafely permissioned. `renew` reissues the
|
|
546
|
+
missing, expired or unsafely permissioned. `renew` reissues the 90-day
|
|
546
547
|
certificates with fresh keys, and reissues the development CA only when it
|
|
547
|
-
is expired or within
|
|
548
|
+
is expired or within 90 days of expiry (or when `--ca` is given); a new CA is
|
|
548
549
|
published under the next anchor id and needs the publisher restarted. For an
|
|
549
550
|
agent whose own issuer signed its certificates, `renew` cannot reissue
|
|
550
551
|
anything: it takes the replacements your issuer produced, through the same
|
|
@@ -577,20 +578,23 @@ id.
|
|
|
577
578
|
|
|
578
579
|
It is the certificates that expire, and renewing replaces their keys with
|
|
579
580
|
them. `aac agent status` shows when each certificate runs out and flags
|
|
580
|
-
it before it does:
|
|
581
|
-
ahead for the development CA.
|
|
581
|
+
it before it does: seven days ahead for the 90-day certificates, 90 days
|
|
582
|
+
ahead for the development CA. That CA window covers a whole leaf lifetime, so
|
|
583
|
+
a renewal never issues a certificate that would outlive its CA. Certificates
|
|
584
|
+
your own issuer signed get the seven-day heads-up for the CA as well.
|
|
582
585
|
|
|
583
586
|
| Material | Lasts | When it runs out | What to do |
|
|
584
587
|
|---|---|---|---|
|
|
585
|
-
| Workload, terminal-attestation and HTTPS certificates |
|
|
586
|
-
| Development CA |
|
|
588
|
+
| Workload, terminal-attestation and HTTPS certificates | 90 days | 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 |
|
|
589
|
+
| Development CA | 365 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 90 days 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` |
|
|
587
590
|
| Tenant-admin session | hours | administration commands ask you to sign in again | `aac sso login` |
|
|
588
591
|
| 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" |
|
|
589
592
|
|
|
590
593
|
A simple routine: when you start work, run
|
|
591
594
|
`aac agent status --agent <name>`, and when it names renewal, run
|
|
592
|
-
`aac agent renew --agent <name>` and restart the sidecar. About
|
|
593
|
-
|
|
595
|
+
`aac agent renew --agent <name>` and restart the sidecar. About every nine
|
|
596
|
+
months, once the CA is within 90 days of expiry, that renewal also replaces
|
|
597
|
+
the development CA (`renew` then reports
|
|
594
598
|
`republication_required: true`): restart the publisher, the sidecar and the
|
|
595
599
|
agent, and anything else that trusted the old CA. Renewing is safe to
|
|
596
600
|
repeat, but it is not a no-op: every run issues fresh keys and moves the old
|
|
@@ -756,7 +760,7 @@ stored value.
|
|
|
756
760
|
|
|
757
761
|
`tenant_id` is managed for you. Profile commands display it but never ask
|
|
758
762
|
for it; only `aac tenant register`, `aac init` and `aac sso login` write it.
|
|
759
|
-
It is your
|
|
763
|
+
It is your organization's identifier in the federation, allocated by AAC at
|
|
760
764
|
registration in the form `tnt-<uuid>`, and it never changes.
|
|
761
765
|
A binding makes that tenant the profile's stored default; it does not grant
|
|
762
766
|
membership or configure an identity provider. Successful sign-in to an existing
|
|
@@ -953,12 +957,12 @@ code.
|
|
|
953
957
|
Enterprise tenants are registered through an onboarding ceremony: AAC
|
|
954
958
|
operations opens a registration window and hands the tenant operator a
|
|
955
959
|
bootstrap token, which the commands below read from `--bootstrap-token` or
|
|
956
|
-
the `AAC_BOOTSTRAP_TOKEN` environment variable. The token
|
|
960
|
+
the `AAC_BOOTSTRAP_TOKEN` environment variable. The token authorizes only
|
|
957
961
|
the onboarding steps shown with it; every other administration command runs
|
|
958
962
|
under a signed-in session.
|
|
959
963
|
|
|
960
964
|
For enterprise sign-in, AAC supports Microsoft Entra ID (also known as
|
|
961
|
-
Azure Active Directory or Azure AD). If your
|
|
965
|
+
Azure Active Directory or Azure AD). If your organization needs another
|
|
962
966
|
identity provider, contact support@cascadeauth.com.
|
|
963
967
|
|
|
964
968
|
### Register the tenant
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# aac-cli — the `aac` command
|
|
2
2
|
|
|
3
3
|
`aac` is the command-line tool for tenants of Agent Authority Cloud (AAC).
|
|
4
|
-
AAC lets an
|
|
5
|
-
which tenant they belong to and what they were
|
|
6
|
-
**tenant** is one
|
|
4
|
+
AAC lets an organization's AI agents and services prove, on every request,
|
|
5
|
+
which tenant they belong to and what they were authorized to do. A
|
|
6
|
+
**tenant** is one organization registered with AAC. Each of its workloads
|
|
7
7
|
runs beside an AAC sidecar that signs outgoing requests and verifies
|
|
8
8
|
incoming ones against public trust material the tenant publishes.
|
|
9
9
|
|
|
@@ -42,7 +42,8 @@ pip install aac-cli
|
|
|
42
42
|
aac --version
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
Supported platforms are macOS, Linux and Linux Docker containers. Python 3.11
|
|
46
|
+
to 3.14 is supported. Release checks test all four versions.
|
|
46
47
|
|
|
47
48
|
> **Mind the name.** `pip install aac` installs an unrelated project that
|
|
48
49
|
> happens to share the acronym. The AAC package is **`aac-cli`**; the
|
|
@@ -156,7 +157,7 @@ never asks you to pick between GitHub and Google again.
|
|
|
156
157
|
configuration references it by path.
|
|
157
158
|
|
|
158
159
|
**By default everything `aac init` generates is development material.** The
|
|
159
|
-
development CA lives
|
|
160
|
+
development CA lives 365 days and the leaf certificates 90 days. Nothing
|
|
160
161
|
there qualifies for production, whatever the tenant's domain. If your own
|
|
161
162
|
issuer signs your agent's certificates instead, see "Two ways to get your
|
|
162
163
|
agent's certificates" below; moving to production also means a tenant-admin
|
|
@@ -178,7 +179,7 @@ three commands look after it:
|
|
|
178
179
|
|
|
179
180
|
```bash
|
|
180
181
|
aac agent status --agent orders # what exists, what is due, and the next step
|
|
181
|
-
aac agent renew --agent orders # replace the
|
|
182
|
+
aac agent renew --agent orders # replace the certificates
|
|
182
183
|
aac agent list # every agent on this machine
|
|
183
184
|
```
|
|
184
185
|
|
|
@@ -402,11 +403,11 @@ tell you later and `aac agent renew` behaves accordingly.
|
|
|
402
403
|
|
|
403
404
|
The laptop case. The CLI creates a certificate authority on this machine and signs the agent's identity, receipt and HTTPS certificates with it.
|
|
404
405
|
|
|
405
|
-
**Flags.** No flags are needed. To reuse a development CA across agents, pass `--ca-key-file` and `--ca-cert-file` together; neither one alone.
|
|
406
|
+
**Flags.** No flags are needed. To reuse a development CA across agents, pass `--ca-key-file` and `--ca-cert-file` together; neither one alone. A CA that expires before the certificates it would sign is refused.
|
|
406
407
|
|
|
407
408
|
**Keys and signatures.** The CA and the two identity keys are Ed25519; the HTTPS key is EC P-256.
|
|
408
409
|
|
|
409
|
-
**The CLI** creates a
|
|
410
|
+
**The CLI** creates a 365-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`.
|
|
410
411
|
|
|
411
412
|
**The CLI does not** ask you for anything from your own certificate authority.
|
|
412
413
|
|
|
@@ -490,10 +491,10 @@ signs instead of this tool — see
|
|
|
490
491
|
| 2 | tenant_admin_session | `~/.aac/credentials/<tenant-id>.session` | CLI admin calls | hours; `aac sso login` | No. sign in again |
|
|
491
492
|
| 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 |
|
|
492
493
|
| 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 |
|
|
493
|
-
| 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 |
|
|
494
|
-
| 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 |
|
|
495
|
-
| 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 |
|
|
496
|
-
| 8 | https_tls | `~/.aac/agents/<agent>/sidecar/server.key`, `~/.aac/agents/<agent>/sidecar/server.crt` | the sidecar's HTTPS listener |
|
|
494
|
+
| 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 | 365 days; `aac agent renew --ca` | No. development only; reissue; a lost copy of the certificate is copied back from `sidecar/ca.crt` |
|
|
495
|
+
| 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 | 90 days; `aac agent renew` | No. reissue |
|
|
496
|
+
| 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 | 90 days; `aac agent renew` | No. reissue |
|
|
497
|
+
| 8 | https_tls | `~/.aac/agents/<agent>/sidecar/server.key`, `~/.aac/agents/<agent>/sidecar/server.crt` | the sidecar's HTTPS listener | 90 days; `aac agent renew` | No. reissue |
|
|
497
498
|
| 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 |
|
|
498
499
|
| 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 |
|
|
499
500
|
| 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) |
|
|
@@ -509,15 +510,15 @@ the machine is healthy. Everything else is regenerated.
|
|
|
509
510
|
```bash
|
|
510
511
|
aac agent status --agent orders --output table # what exists, expiry, permissions, next command
|
|
511
512
|
aac agent status --agent orders --remote # also: is the public trust material visible?
|
|
512
|
-
aac agent renew --agent orders # replace the
|
|
513
|
+
aac agent renew --agent orders # replace the certificates; old ones archived
|
|
513
514
|
aac agent list # every agent on this machine, with its case
|
|
514
515
|
aac init --profile stage --agent orders --layout container # write the settings again, container paths
|
|
515
516
|
```
|
|
516
517
|
|
|
517
518
|
`status` works offline by default and exits with code 3 when material is
|
|
518
|
-
missing, expired or unsafely permissioned. `renew` reissues the
|
|
519
|
+
missing, expired or unsafely permissioned. `renew` reissues the 90-day
|
|
519
520
|
certificates with fresh keys, and reissues the development CA only when it
|
|
520
|
-
is expired or within
|
|
521
|
+
is expired or within 90 days of expiry (or when `--ca` is given); a new CA is
|
|
521
522
|
published under the next anchor id and needs the publisher restarted. For an
|
|
522
523
|
agent whose own issuer signed its certificates, `renew` cannot reissue
|
|
523
524
|
anything: it takes the replacements your issuer produced, through the same
|
|
@@ -550,20 +551,23 @@ id.
|
|
|
550
551
|
|
|
551
552
|
It is the certificates that expire, and renewing replaces their keys with
|
|
552
553
|
them. `aac agent status` shows when each certificate runs out and flags
|
|
553
|
-
it before it does:
|
|
554
|
-
ahead for the development CA.
|
|
554
|
+
it before it does: seven days ahead for the 90-day certificates, 90 days
|
|
555
|
+
ahead for the development CA. That CA window covers a whole leaf lifetime, so
|
|
556
|
+
a renewal never issues a certificate that would outlive its CA. Certificates
|
|
557
|
+
your own issuer signed get the seven-day heads-up for the CA as well.
|
|
555
558
|
|
|
556
559
|
| Material | Lasts | When it runs out | What to do |
|
|
557
560
|
|---|---|---|---|
|
|
558
|
-
| Workload, terminal-attestation and HTTPS certificates |
|
|
559
|
-
| Development CA |
|
|
561
|
+
| Workload, terminal-attestation and HTTPS certificates | 90 days | 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 |
|
|
562
|
+
| Development CA | 365 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 90 days 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` |
|
|
560
563
|
| Tenant-admin session | hours | administration commands ask you to sign in again | `aac sso login` |
|
|
561
564
|
| 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" |
|
|
562
565
|
|
|
563
566
|
A simple routine: when you start work, run
|
|
564
567
|
`aac agent status --agent <name>`, and when it names renewal, run
|
|
565
|
-
`aac agent renew --agent <name>` and restart the sidecar. About
|
|
566
|
-
|
|
568
|
+
`aac agent renew --agent <name>` and restart the sidecar. About every nine
|
|
569
|
+
months, once the CA is within 90 days of expiry, that renewal also replaces
|
|
570
|
+
the development CA (`renew` then reports
|
|
567
571
|
`republication_required: true`): restart the publisher, the sidecar and the
|
|
568
572
|
agent, and anything else that trusted the old CA. Renewing is safe to
|
|
569
573
|
repeat, but it is not a no-op: every run issues fresh keys and moves the old
|
|
@@ -729,7 +733,7 @@ stored value.
|
|
|
729
733
|
|
|
730
734
|
`tenant_id` is managed for you. Profile commands display it but never ask
|
|
731
735
|
for it; only `aac tenant register`, `aac init` and `aac sso login` write it.
|
|
732
|
-
It is your
|
|
736
|
+
It is your organization's identifier in the federation, allocated by AAC at
|
|
733
737
|
registration in the form `tnt-<uuid>`, and it never changes.
|
|
734
738
|
A binding makes that tenant the profile's stored default; it does not grant
|
|
735
739
|
membership or configure an identity provider. Successful sign-in to an existing
|
|
@@ -926,12 +930,12 @@ code.
|
|
|
926
930
|
Enterprise tenants are registered through an onboarding ceremony: AAC
|
|
927
931
|
operations opens a registration window and hands the tenant operator a
|
|
928
932
|
bootstrap token, which the commands below read from `--bootstrap-token` or
|
|
929
|
-
the `AAC_BOOTSTRAP_TOKEN` environment variable. The token
|
|
933
|
+
the `AAC_BOOTSTRAP_TOKEN` environment variable. The token authorizes only
|
|
930
934
|
the onboarding steps shown with it; every other administration command runs
|
|
931
935
|
under a signed-in session.
|
|
932
936
|
|
|
933
937
|
For enterprise sign-in, AAC supports Microsoft Entra ID (also known as
|
|
934
|
-
Azure Active Directory or Azure AD). If your
|
|
938
|
+
Azure Active Directory or Azure AD). If your organization needs another
|
|
935
939
|
identity provider, contact support@cascadeauth.com.
|
|
936
940
|
|
|
937
941
|
### Register the tenant
|
|
@@ -204,15 +204,18 @@ def _render(args, events, query: Query, sources: list[SourceInput]) -> int:
|
|
|
204
204
|
if args.output.resolve() in {path.resolve() for path in protected}:
|
|
205
205
|
raise InputError("--output must not overwrite an evidence input")
|
|
206
206
|
visual = build_visual_aeg(graph)
|
|
207
|
-
visual.provenance = summarize_sources(
|
|
207
|
+
visual.provenance = summarize_sources(
|
|
208
|
+
sources, selected, central_status=central_status, evidence_complete=graph.evidence_complete
|
|
209
|
+
)
|
|
208
210
|
with tempfile.TemporaryDirectory(prefix="aac-aeg-") as directory:
|
|
209
211
|
rendered = write_html_aeg(visual, root, out_dir=Path(directory))
|
|
210
212
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
211
213
|
args.output.write_text(rendered.read_text(encoding="utf-8"), encoding="utf-8")
|
|
212
214
|
for diagnostic in diagnostics:
|
|
213
215
|
print(diagnostic, file=sys.stderr)
|
|
216
|
+
coverage = "" if graph.evidence_complete else "; partial evidence"
|
|
214
217
|
print(
|
|
215
|
-
f"AEG written to {args.output} ({len(graph.tokens)} observed/referenced tokens
|
|
218
|
+
f"AEG written to {args.output} ({len(graph.tokens)} observed/referenced tokens{coverage})",
|
|
216
219
|
file=sys.stderr,
|
|
217
220
|
)
|
|
218
221
|
return 4 if query_failed else 0
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
|
-
"content_sha256": "
|
|
2
|
+
"content_sha256": "f45099fcfa1d1ceca5f31244dfa526ea096094ced6cfc7faf27cc1babe482163",
|
|
3
3
|
"files": {
|
|
4
4
|
"__init__.py": "80d80a1ba35353d271cddf02532801c60b57f5ec06fb268ae823747742ce8979",
|
|
5
|
-
"cli.py": "
|
|
5
|
+
"cli.py": "a180a171b4d1f819fd96d0821394eee19b45fafddddbd50c74778af6ac3d5fb0",
|
|
6
6
|
"data/action-taken-v1.json": "d863343afe7f314646a73cf063480a7e650f93857d2ed5ec3a7b1f9838671fdf",
|
|
7
7
|
"data/predicates-v1.json": "35e83a29feada10e8cfbef7009245146a9717d4f4fd58d74124e0ec91ae922c8",
|
|
8
|
-
"from_sidecar.py": "
|
|
9
|
-
"inputs.py": "
|
|
10
|
-
"model.py": "
|
|
8
|
+
"from_sidecar.py": "8671c3e0efe9bfe9ecf9be057b70e245ca8d0d645b6bad8819f6fb1b882e255e",
|
|
9
|
+
"inputs.py": "047a6b7dd788e04c49e10f090de9336ed3870e754b547c469371369d27b55b8b",
|
|
10
|
+
"model.py": "715d0e92c3aafd9e0a194a74b4df7354bd24d1bf815767d590cf245da3d60018",
|
|
11
11
|
"narrative.py": "8ac4b12add84cc8be907cff5f25e58731ab1144888aec8d376e286de461a6179",
|
|
12
|
-
"provenance.py": "
|
|
12
|
+
"provenance.py": "3f611a8c129a2856e2cf76561bde29dc1884719083dfdddea72810e026946b90",
|
|
13
13
|
"schemas.py": "c47e0138fb8bfeaf1db1725a00d4a37ff4a136a9e8f5907356b1006f301a9ded",
|
|
14
14
|
"temporal.py": "b69081e9280d578dc1261a276da3178e979a02efaf1137f720b4520ed543a36d",
|
|
15
15
|
"theme.py": "ded47be13f5c591da95a1bb91ee1b665c1570c929d842bdaa1a41325d0b40caa",
|
|
16
16
|
"timeline_source.py": "dc875458d69c6135b3ffe0db9c35e511da603b5d52409f780f3e1b74dc464520",
|
|
17
|
-
"visualizer.py": "
|
|
17
|
+
"visualizer.py": "3213a1cbfe901f320c687b5eafcc09bed46bd2270c6cd427a7f74472c80a3bfe"
|
|
18
18
|
},
|
|
19
19
|
"legal": {
|
|
20
20
|
"LICENSE": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594",
|
|
@@ -463,6 +463,9 @@ def _translate_mint(
|
|
|
463
463
|
"tenant_id": sidecar_event.get("tenant_id"),
|
|
464
464
|
"presenter_spiffe_id": sidecar_event.get("actor_spiffe_id"),
|
|
465
465
|
"originator_human_id_hash": None,
|
|
466
|
+
# The sidecar records the sign-in issuer, never the person's identity;
|
|
467
|
+
# the originator node shows it when no identity hash is available.
|
|
468
|
+
"originator_issuer": sidecar_event.get("originator_issuer"),
|
|
466
469
|
# T0 has no parent and no parent_token_ids_list (not a composite).
|
|
467
470
|
}
|
|
468
471
|
t0_event = UnifiedEvent(
|
|
@@ -624,6 +627,7 @@ def _translate_composite_mint(
|
|
|
624
627
|
"parent_root_token_ids": sidecar_event.get("parent_root_token_ids") or [],
|
|
625
628
|
"parent_tenant_ids": sidecar_event.get("parent_tenant_ids") or [],
|
|
626
629
|
"originator_human_id_hash": sidecar_event.get("originator_human_id_hash"),
|
|
630
|
+
"originator_issuer": sidecar_event.get("originator_issuer"),
|
|
627
631
|
"task_ref": sidecar_event.get("task_ref"),
|
|
628
632
|
}
|
|
629
633
|
t_comp_0_event = UnifiedEvent(
|
|
@@ -100,6 +100,7 @@ def _sidecar_types(row: dict, label: str) -> None:
|
|
|
100
100
|
"agent_decision_action",
|
|
101
101
|
"destination",
|
|
102
102
|
"terminal_attestation_verification",
|
|
103
|
+
"originator_issuer",
|
|
103
104
|
):
|
|
104
105
|
if row.get(key) is not None and not isinstance(row[key], str):
|
|
105
106
|
raise InputError(f"{label}: {key} must be a string or null")
|
|
@@ -232,6 +233,7 @@ def read_trace(body, source: str) -> tuple[str, list[UnifiedEvent]]:
|
|
|
232
233
|
"terminal_attestation_verification",
|
|
233
234
|
"destination",
|
|
234
235
|
"agent_decision_action",
|
|
236
|
+
"originator_issuer",
|
|
235
237
|
):
|
|
236
238
|
if raw.get(key) is not None and not isinstance(raw[key], str):
|
|
237
239
|
raise InputError(f"{label}: {key} must be a string or null")
|
|
@@ -215,6 +215,11 @@ class TerminalNode:
|
|
|
215
215
|
action_payload: dict = field(default_factory=dict)
|
|
216
216
|
action_taken_by_spiffe_id: str | None = None
|
|
217
217
|
action_taken_timestamp: str | None = None
|
|
218
|
+
# True only when an action record's actor is the workload the audience
|
|
219
|
+
# names (exact match, or a match of the audience pattern). Any other
|
|
220
|
+
# token-correlated action stays an observation and never proves that the
|
|
221
|
+
# addressed recipient acted.
|
|
222
|
+
recipient_acted: bool = False
|
|
218
223
|
# Canonical associations observed for this leaf and exact receiver workload.
|
|
219
224
|
# A trust-domain match, another token, or a wildcard is not such evidence.
|
|
220
225
|
observed_tenant_ids: tuple[str, ...] = ()
|
|
@@ -274,6 +279,21 @@ class TokenRelationGraph:
|
|
|
274
279
|
# become unknown. Receiving/reporting observations cannot supply this map.
|
|
275
280
|
creator_tenant_claims: dict[str, tuple[str, ...]] = field(default_factory=dict)
|
|
276
281
|
|
|
282
|
+
@property
|
|
283
|
+
def evidence_complete(self) -> bool:
|
|
284
|
+
"""True when the supplied records leave no structural gap.
|
|
285
|
+
|
|
286
|
+
Every referenced token was observed (no placeholder stands in for a
|
|
287
|
+
token only named by another), and each recipient's own records show
|
|
288
|
+
its action. A complete set is still a set of supplied observations,
|
|
289
|
+
not independently verified business truth.
|
|
290
|
+
"""
|
|
291
|
+
if not self.tokens or not self.terminals:
|
|
292
|
+
return False
|
|
293
|
+
if any(token.placeholder for token in self.tokens.values()):
|
|
294
|
+
return False
|
|
295
|
+
return all(terminal.recipient_acted for terminal in self.terminals)
|
|
296
|
+
|
|
277
297
|
|
|
278
298
|
# ---------------------------------------------------------------------------
|
|
279
299
|
# Extraction — events → TokenRelationGraph
|
|
@@ -729,14 +749,49 @@ def _extract_originators(
|
|
|
729
749
|
if e.source.startswith(TENANT_SOURCE_PREFIX) and e.event_type == "human_authenticated"
|
|
730
750
|
}
|
|
731
751
|
|
|
732
|
-
|
|
752
|
+
# Every observation of the same root (local sidecar record, central
|
|
753
|
+
# metadata, a second file) describes ONE originator. Merge them per root so
|
|
754
|
+
# the canvas label and the details are built from the same facts: a SIEM
|
|
755
|
+
# match on any observed hash wins, otherwise the first issuer any
|
|
756
|
+
# observation carries.
|
|
757
|
+
by_root: dict[str, list[UnifiedEvent]] = {}
|
|
733
758
|
for mint in mint_root_events:
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
759
|
+
by_root.setdefault(mint.root_token_id, []).append(mint)
|
|
760
|
+
|
|
761
|
+
out: list[OriginatorNode] = []
|
|
762
|
+
for root_tid, observations in by_root.items():
|
|
763
|
+
h_hash = next(
|
|
764
|
+
(
|
|
765
|
+
str(o.raw_event.get("originator_human_id_hash"))
|
|
766
|
+
for o in observations
|
|
767
|
+
if o.raw_event.get("originator_human_id_hash")
|
|
768
|
+
),
|
|
769
|
+
"",
|
|
770
|
+
)
|
|
771
|
+
issuer = next(
|
|
772
|
+
(
|
|
773
|
+
o.raw_event.get("originator_issuer")
|
|
774
|
+
for o in observations
|
|
775
|
+
if isinstance(o.raw_event.get("originator_issuer"), str)
|
|
776
|
+
and o.raw_event.get("originator_issuer")
|
|
777
|
+
),
|
|
778
|
+
None,
|
|
779
|
+
)
|
|
780
|
+
token = next((tokens[o.token_id] for o in observations if o.token_id in tokens), None)
|
|
781
|
+
tenant_id = (
|
|
782
|
+
token.tenant_id
|
|
783
|
+
if token
|
|
784
|
+
else next(
|
|
785
|
+
(
|
|
786
|
+
str(o.raw_event.get("tenant_id"))
|
|
787
|
+
for o in observations
|
|
788
|
+
if o.raw_event.get("tenant_id")
|
|
789
|
+
),
|
|
790
|
+
"",
|
|
791
|
+
)
|
|
792
|
+
)
|
|
738
793
|
# Optional enrichment from tenant SIEM.
|
|
739
|
-
siem = siem_human_events.get(h_hash)
|
|
794
|
+
siem = siem_human_events.get(h_hash) if h_hash else None
|
|
740
795
|
if siem:
|
|
741
796
|
raw = siem.raw_event
|
|
742
797
|
out.append(
|
|
@@ -757,6 +812,9 @@ def _extract_originators(
|
|
|
757
812
|
human_id_hash=h_hash,
|
|
758
813
|
root_token_id=root_tid,
|
|
759
814
|
tenant_id=tenant_id,
|
|
815
|
+
# The sidecar's mint record names the sign-in issuer even
|
|
816
|
+
# when no identity hash reaches these records.
|
|
817
|
+
human_iss=issuer,
|
|
760
818
|
)
|
|
761
819
|
)
|
|
762
820
|
return out
|
|
@@ -812,6 +870,35 @@ def _extract_auth_summary(
|
|
|
812
870
|
# ---------------------------------------------------------------------------
|
|
813
871
|
|
|
814
872
|
|
|
873
|
+
# The AAC audience grammar (AAC Design, "SPIFFE-ID pattern matching for
|
|
874
|
+
# audience"), as the verifier implements it: a concrete SPIFFE ID, or a trailing
|
|
875
|
+
# `/*` that stands for exactly one non-empty path segment. Trust domains match
|
|
876
|
+
# exactly; a SPIFFE ID itself never contains a wildcard.
|
|
877
|
+
_AUDIENCE_PATTERN = re.compile(r"^spiffe://[A-Za-z0-9.\-_]+(?:/[^/*]+)*(?:/\*)?$")
|
|
878
|
+
_CONCRETE_SPIFFE_ID = re.compile(r"^spiffe://[A-Za-z0-9.\-_]+(?:/[^/*]+)+$")
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
def _actor_matches_audience(actor: object, audience: str) -> bool:
|
|
882
|
+
"""Whether an action's actor is the workload the audience constraint names.
|
|
883
|
+
|
|
884
|
+
Only a concrete SPIFFE ID can be the addressed recipient, and only under
|
|
885
|
+
the audience grammar above. Anything else (an opaque identity, a pattern
|
|
886
|
+
used as an actor, an unsupported audience shape) is kept as an observation
|
|
887
|
+
without claiming a match. A shared token or tenant never counts.
|
|
888
|
+
"""
|
|
889
|
+
if not isinstance(actor, str) or not _CONCRETE_SPIFFE_ID.fullmatch(actor):
|
|
890
|
+
return False
|
|
891
|
+
if any(character.isspace() for character in actor) or not _AUDIENCE_PATTERN.fullmatch(audience):
|
|
892
|
+
return False
|
|
893
|
+
if audience.endswith("/*"):
|
|
894
|
+
prefix = audience[:-2]
|
|
895
|
+
if not actor.startswith(prefix + "/"):
|
|
896
|
+
return False
|
|
897
|
+
tail = actor[len(prefix) + 1 :]
|
|
898
|
+
return bool(tail) and "/" not in tail
|
|
899
|
+
return actor == audience
|
|
900
|
+
|
|
901
|
+
|
|
815
902
|
def _extract_terminals(
|
|
816
903
|
events: list[UnifiedEvent],
|
|
817
904
|
tokens: dict[str, TokenNode],
|
|
@@ -882,6 +969,9 @@ def _extract_terminals(
|
|
|
882
969
|
action_by_token.get(tid, []),
|
|
883
970
|
key=lambda e: (e.timestamp_iso, json.dumps(e.raw_event, sort_keys=True)),
|
|
884
971
|
)
|
|
972
|
+
recipient_acted = any(
|
|
973
|
+
_actor_matches_audience(e.raw_event.get("actor_spiffe_id"), audience) for e in actions
|
|
974
|
+
)
|
|
885
975
|
if actions:
|
|
886
976
|
action = actions[0]
|
|
887
977
|
raw: dict = (
|
|
@@ -911,6 +1001,7 @@ def _extract_terminals(
|
|
|
911
1001
|
# alias is needed.
|
|
912
1002
|
action_taken_by_spiffe_id=raw.get("actor_spiffe_id"),
|
|
913
1003
|
action_taken_timestamp=action.timestamp_iso,
|
|
1004
|
+
recipient_acted=recipient_acted,
|
|
914
1005
|
)
|
|
915
1006
|
)
|
|
916
1007
|
else:
|
|
@@ -50,10 +50,16 @@ class SourceSummary:
|
|
|
50
50
|
class Provenance:
|
|
51
51
|
sources: tuple[SourceSummary, ...]
|
|
52
52
|
central_status: str
|
|
53
|
+
# Structural completeness of the supplied records (TokenRelationGraph.evidence_complete).
|
|
54
|
+
evidence_complete: bool = False
|
|
53
55
|
|
|
54
56
|
|
|
55
57
|
def summarize_sources(
|
|
56
|
-
sources: list[SourceInput],
|
|
58
|
+
sources: list[SourceInput],
|
|
59
|
+
selected: list[UnifiedEvent],
|
|
60
|
+
*,
|
|
61
|
+
central_status: str,
|
|
62
|
+
evidence_complete: bool = False,
|
|
57
63
|
) -> Provenance:
|
|
58
64
|
# Selection retains the original event objects. Track membership by identity,
|
|
59
65
|
# not by a filename, timestamp or caller-controlled event field that could
|
|
@@ -81,7 +87,7 @@ def summarize_sources(
|
|
|
81
87
|
times[-1] if times else None,
|
|
82
88
|
)
|
|
83
89
|
)
|
|
84
|
-
return Provenance(tuple(summaries), central_status)
|
|
90
|
+
return Provenance(tuple(summaries), central_status, evidence_complete)
|
|
85
91
|
|
|
86
92
|
|
|
87
93
|
def source_panel(provenance: Provenance | None, diagnostics: list[str]) -> str:
|
|
@@ -143,11 +149,21 @@ def source_panel(provenance: Provenance | None, diagnostics: list[str]) -> str:
|
|
|
143
149
|
"observations for hosted data, not unique business actions. Timestamp spans describe "
|
|
144
150
|
"available records, not execution start/end or continuous coverage.</p>"
|
|
145
151
|
)
|
|
152
|
+
if provenance and provenance.evidence_complete:
|
|
153
|
+
coverage = (
|
|
154
|
+
"<li>Every referenced token was observed and each recipient's own records show its "
|
|
155
|
+
"action. Records are supplied, correlated observations, not independently verified "
|
|
156
|
+
"business truth.</li>"
|
|
157
|
+
)
|
|
158
|
+
else:
|
|
159
|
+
coverage = (
|
|
160
|
+
"<li>Partial evidence: only supplied, correlated observations are shown. Missing "
|
|
161
|
+
"records do not establish success or failure.</li>"
|
|
162
|
+
)
|
|
146
163
|
parts.append(
|
|
147
|
-
"<h2>Limitations for these inputs</h2><ul
|
|
148
|
-
"
|
|
149
|
-
"
|
|
150
|
-
"does not prove delivery or completion. Mint/transport success does not prove a business result.</li>"
|
|
164
|
+
"<h2>Limitations for these inputs</h2><ul>" + coverage + "<li>A recipient is named by an "
|
|
165
|
+
'audience constraint ("addressed to"); that constraint alone does not prove delivery or '
|
|
166
|
+
"completion. Mint/transport success does not prove a business result.</li>"
|
|
151
167
|
)
|
|
152
168
|
if "Control-plane trace data" in used:
|
|
153
169
|
parts.append(
|