aac-cli 0.1.3__tar.gz → 0.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aac_cli-0.1.3 → aac_cli-0.1.4}/PKG-INFO +163 -2
- {aac_cli-0.1.3 → aac_cli-0.1.4}/README.md +161 -1
- {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/__init__.py +1 -1
- {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/cli.py +50 -10
- aac_cli-0.1.4/aac_cli/config_render.py +266 -0
- aac_cli-0.1.4/aac_cli/dev_material.py +405 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/idp_recovery.py +4 -50
- aac_cli-0.1.4/aac_cli/init_cli.py +1392 -0
- aac_cli-0.1.4/aac_cli/secure_files.py +200 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/sso_login.py +85 -16
- aac_cli-0.1.4/aac_cli/workspace_cli.py +525 -0
- aac_cli-0.1.4/aac_cli/workspace_health.py +156 -0
- aac_cli-0.1.4/aac_cli/workspace_layout.py +467 -0
- aac_cli-0.1.4/aac_cli/workspace_manifest.py +144 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli.egg-info/PKG-INFO +163 -2
- {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli.egg-info/SOURCES.txt +15 -1
- {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli.egg-info/requires.txt +1 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/pyproject.toml +7 -1
- aac_cli-0.1.4/tests/test_b241_edge_paths.py +925 -0
- aac_cli-0.1.4/tests/test_config_render.py +177 -0
- aac_cli-0.1.4/tests/test_dev_material.py +127 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_hosted_domains_cli.py +36 -0
- aac_cli-0.1.4/tests/test_init_cli.py +1417 -0
- aac_cli-0.1.4/tests/test_inventory_docs.py +70 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_sso_login_cli.py +121 -1
- aac_cli-0.1.4/tests/test_workspace_cli.py +400 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/LICENSE +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/__main__.py +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/admin_key_pem.py +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/api_key_rotation_state.py +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/config.py +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/profiles.py +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/registration_state.py +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli.egg-info/dependency_links.txt +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli.egg-info/entry_points.txt +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli.egg-info/top_level.txt +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/setup.cfg +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_aac_cli.py +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_admin_key_pem.py +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_api_key_reissue_cli.py +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_api_key_rotation_cli.py +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_api_key_rotation_state.py +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_idp_recovery_cli.py +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_profile_cli.py +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_registration_recovery_cli.py +0 -0
- {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_registration_state.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aac-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
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
|
|
@@ -9,6 +9,7 @@ Description-Content-Type: text/markdown
|
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Dist: httpx>=0.28
|
|
11
11
|
Requires-Dist: cryptography>=42.0
|
|
12
|
+
Requires-Dist: PyYAML>=6.0
|
|
12
13
|
Provides-Extra: test
|
|
13
14
|
Requires-Dist: pytest>=8.0; extra == "test"
|
|
14
15
|
Requires-Dist: pytest-httpx>=0.30; extra == "test"
|
|
@@ -272,7 +273,8 @@ aac sso register-idp --shared --file shared-github.json \
|
|
|
272
273
|
# admin verb (tenant list/describe/update, self-serve register-idp)
|
|
273
274
|
# then rides the session automatically.
|
|
274
275
|
aac sso login --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000
|
|
275
|
-
aac sso login --
|
|
276
|
+
aac sso login --idp github # developer tenant: GitHub or Google sign-in
|
|
277
|
+
aac sso login --issuer https://login.microsoftonline.com/<tid>/v2.0 # enterprise IdP, by issuer URL
|
|
276
278
|
aac sso login --flow pkce --no-browser # print the URL, don't launch
|
|
277
279
|
|
|
278
280
|
# Sessions are REFRESH-LESS by design (Eng Spec §XVII.2): on expiry
|
|
@@ -549,6 +551,165 @@ needed. Against a live stack: bring up the joined topology
|
|
|
549
551
|
(`./bin/run-wedge-a-control-plane-compose.sh --keep-up`) and point the
|
|
550
552
|
flags at localhost.
|
|
551
553
|
|
|
554
|
+
## Guided setup: `aac init` and `aac workspace`
|
|
555
|
+
|
|
556
|
+
`aac init` takes a developer from an installed CLI to a complete, runnable
|
|
557
|
+
local workload in one command. It signs you in, registers a developer tenant
|
|
558
|
+
(or reuses the one your profile is already bound to), takes the AAC-assigned
|
|
559
|
+
hosted trust domain, registers the sample workload, generates development
|
|
560
|
+
keys and certificates, registers the tenant-admin public key together with a
|
|
561
|
+
new tenant, and writes a complete sidecar configuration plus the publisher
|
|
562
|
+
environment and a Compose variable file.
|
|
563
|
+
|
|
564
|
+
```bash
|
|
565
|
+
# Developer tier: sign in with GitHub or Google. The command shows what it
|
|
566
|
+
# will create and asks before registering the tenant.
|
|
567
|
+
aac init --profile stage --admin-url https://api.stage.cascadeauth.dev \
|
|
568
|
+
--data-plane-url https://api.stage.cascadeauth.dev \
|
|
569
|
+
--trust-url https://trust.stage.cascadeauth.dev \
|
|
570
|
+
--display-name 'YOUR TEAM' --contact 'YOUR EMAIL' --idp github
|
|
571
|
+
|
|
572
|
+
# Scripted: every prompt has a flag; --create-tenant acknowledges creating a
|
|
573
|
+
# permanent tenant when stdin is not a terminal.
|
|
574
|
+
aac init --profile stage --admin-url ... --trust-url ... \
|
|
575
|
+
--display-name 'YOUR TEAM' --contact 'YOUR EMAIL' --idp github --create-tenant
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
Progress lines `[1/5]` to `[5/5]` name the five steps: tenant, sign-in, hosted trust
|
|
579
|
+
domain, workload, material and rendering. A rerun prints every step, marking the ones
|
|
580
|
+
already done. The sign-in you chose with `--idp` is remembered in the workspace, so a
|
|
581
|
+
rerun (or `aac sso login --idp github` by hand when a session expires) never asks you
|
|
582
|
+
to pick between GitHub and Google. Workspace commands take `--workspace <name>`; the
|
|
583
|
+
profile is recorded in the workspace, so `--profile` is optional there and only checked.
|
|
584
|
+
|
|
585
|
+
What it creates, and where:
|
|
586
|
+
|
|
587
|
+
* `~/.aac/workspaces/<name>/` — the **workspace** for one workload (default
|
|
588
|
+
name `starter`): `pki/` with what the sidecar container mounts — the
|
|
589
|
+
workload, terminal-attestation and localhost TLS key pairs, this
|
|
590
|
+
workspace's root-signing key and the outbound CA bundle; `pair/` with what
|
|
591
|
+
both containers mount at `/run/secrets` — the pairing secret and the public
|
|
592
|
+
development CA certificate; `ca/` with the development CA private key (kept
|
|
593
|
+
out of both mounts so no container ever sees the key that mints
|
|
594
|
+
identities); `sidecar-config.yaml`; `compose.env` (non-secret variables for
|
|
595
|
+
a Compose consumer); and an internal manifest that lets a rerun resume.
|
|
596
|
+
* `~/.aac/tenants/<tenant-id>/` — **tenant-level** material shared by every
|
|
597
|
+
workspace of that tenant: the tenant-admin signing key, the publisher's
|
|
598
|
+
input directories (`root-keys/`, `spiffe-bundle/` — public halves only) and
|
|
599
|
+
the rendered `publisher.env`.
|
|
600
|
+
* The tenant API key stays where the CLI always kept it,
|
|
601
|
+
`~/.aac/credentials/<tenant-id>`; the rendered configuration references it
|
|
602
|
+
by path.
|
|
603
|
+
|
|
604
|
+
### What each file is for, and what to back up
|
|
605
|
+
|
|
606
|
+
The table below is rendered from the CLI's own layout module and checked by a test, so it
|
|
607
|
+
cannot drift from the code. `<workspace>` is the workspace name (default `starter`) and
|
|
608
|
+
`<tenant-id>` the server-allocated tenant id.
|
|
609
|
+
|
|
610
|
+
<!-- inventory-table:start -->
|
|
611
|
+
| Material | Where | Used for | Lifetime | Back up? |
|
|
612
|
+
|---|---|---|---|---|
|
|
613
|
+
| 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` |
|
|
614
|
+
| tenant_admin_session | `~/.aac/credentials/<tenant-id>.session` | CLI admin calls | hours; `aac sso login` | No. sign in again |
|
|
615
|
+
| tenant_admin_signing_key | `~/.aac/tenants/<tenant-id>/tenant-admin.pem`, `~/.aac/tenants/<tenant-id>/tenant-admin.pub.pem` | the publisher signs trust-material uploads; the public half is registered with AAC | until rotated; `aac tenant rotate-admin-key` | **Recommended.** recommended for developer tenants (loss needs `aac tenant rotate-admin-key`); production custody belongs in an HSM/KMS, not a password manager |
|
|
616
|
+
| root_signing_key | `~/.aac/workspaces/<workspace>/pki/root.pem`, `~/.aac/workspaces/<workspace>/pki/root.pub.pem`, `~/.aac/tenants/<tenant-id>/root-keys/<workspace>-root-v1.pub.pem` | the sidecar mints authority chains; the public half is published under the key id | until you retire its key id (root key ids are fixed per workspace name) | No. never restore: a replacement workspace gets a new name and therefore a new key id, published alongside the old one until the old one is retired |
|
|
617
|
+
| development_ca | `~/.aac/workspaces/<workspace>/ca/dev-ca.key`, `~/.aac/workspaces/<workspace>/pki/dev-ca.crt`, `~/.aac/workspaces/<workspace>/pair/dev-ca.crt`, `~/.aac/tenants/<tenant-id>/spiffe-bundle/<workspace>-dev-ca.ca.pem` | signs the workload, terminal and TLS certificates; peers trust it via the published bundle | 7 days; `aac workspace renew --ca` | No. development only; reissue |
|
|
618
|
+
| workload_svid | `~/.aac/workspaces/<workspace>/pki/workload.key`, `~/.aac/workspaces/<workspace>/pki/workload.crt` | the sidecar's SPIFFE identity: proves on every live request that this workload holds the key behind its certificate | 1 day; `aac workspace renew` | No. development only; reissue |
|
|
619
|
+
| terminal_attestation | `~/.aac/workspaces/<workspace>/pki/terminal.key`, `~/.aac/workspaces/<workspace>/pki/terminal.crt` | signs the receipt (terminal attestation) the sidecar issues when this workload completes a delegated request; auditors verify it offline against your published CA; kept separate from the workload key so neither can forge the other's role | 1 day; `aac workspace renew` | No. development only; reissue |
|
|
620
|
+
| localhost_tls | `~/.aac/workspaces/<workspace>/pki/server.key`, `~/.aac/workspaces/<workspace>/pki/server.crt` | the sidecar's HTTPS listener | 1 day; `aac workspace renew` | No. development only; reissue |
|
|
621
|
+
| outbound_ca_bundle | `~/.aac/workspaces/<workspace>/pki/outbound-ca.pem` | the sidecar verifies TLS to AAC and to itself | rebuilt by renew; `aac workspace renew` | No. derived |
|
|
622
|
+
| pairing_secret | `~/.aac/workspaces/<workspace>/pair/pairing.secret` | the sidecar and its agent authenticate each other (same bytes in both) | until regenerated | No. regenerable; both processes must read the same file |
|
|
623
|
+
| workspace_manifest | `~/.aac/workspaces/<workspace>/manifest.json` | the workspace's identities and progress; every `workspace` verb reads it first | for the life of the workspace | **Recommended.** cannot be recreated by any command; without it start a new workspace under a new name (see the machine-loss procedure) |
|
|
624
|
+
| published_public_inputs | `~/.aac/tenants/<tenant-id>/root-keys`, `~/.aac/tenants/<tenant-id>/spiffe-bundle` | the publisher's input directories: every root public key and CA certificate this tenant has published, across all workspaces | for the life of the tenant | **Recommended.** back up with the tenant directory: a replacement workspace must publish its new root key alongside a still-ACTIVE previous one (the control plane refuses a root-key set that drops every ACTIVE key at once) |
|
|
625
|
+
| rendered_files | `~/.aac/workspaces/<workspace>/sidecar-config.yaml`, `~/.aac/workspaces/<workspace>/compose.env`, `~/.aac/tenants/<tenant-id>/publisher.env` | non-secret configuration derived from the manifest | re-rendered on demand; `aac workspace render --force` | No. derived; `aac workspace render --force` recreates them |
|
|
626
|
+
<!-- inventory-table:end -->
|
|
627
|
+
|
|
628
|
+
#### If you lose this machine
|
|
629
|
+
|
|
630
|
+
Back up two things while the machine is healthy: the tenant API key at
|
|
631
|
+
`~/.aac/credentials/<tenant-id>`, and the whole tenant directory
|
|
632
|
+
`~/.aac/tenants/<tenant-id>/` (the admin signing key plus the `root-keys/` and
|
|
633
|
+
`spiffe-bundle/` public inputs the publisher has already published). The workspace
|
|
634
|
+
manifest is worth keeping too, but a workspace can always be replaced. Then, on the new
|
|
635
|
+
machine:
|
|
636
|
+
|
|
637
|
+
1. Install the CLI and `aac profile create <name> --admin-url … --data-plane-url …`.
|
|
638
|
+
Sign in with `aac sso login --idp github --tenant-id <tenant-id> --profile <name>`
|
|
639
|
+
(or `--issuer <url>` for an enterprise connection): the remaining
|
|
640
|
+
steps register a workload and read the tenant's admin-key state, which need a
|
|
641
|
+
tenant-admin session whatever way the tenant was registered (a ceremony bootstrap
|
|
642
|
+
token only covers registration and hosted-domain assignment).
|
|
643
|
+
2. Restore the tenant API key to `~/.aac/credentials/<tenant-id>` (mode `0600`), or issue a
|
|
644
|
+
new one with `aac tenant reissue-api-key` if it was not backed up. Restore
|
|
645
|
+
`~/.aac/tenants/<tenant-id>/` from the backup (directories `0700`, files `0600`).
|
|
646
|
+
3. Run `aac init --profile <name> --workspace <new-name>` with a **new** workspace name.
|
|
647
|
+
Root key ids are fixed per workspace name and the control plane never accepts new key
|
|
648
|
+
material under an old id, so the replacement workspace publishes a new id
|
|
649
|
+
(`<new-name>-root-v1`) and a new development CA anchor.
|
|
650
|
+
4. Start the publisher from the restored tenant directory. Its root-key set now holds the
|
|
651
|
+
old and the new public keys, which is what the control plane requires: a set that
|
|
652
|
+
drops every currently ACTIVE key at once is refused. Retire the old key later by
|
|
653
|
+
removing its public file once nothing signs with it.
|
|
654
|
+
|
|
655
|
+
Reusing the old workspace name on the new machine is refused on purpose: the restored
|
|
656
|
+
tenant directory still holds that name's published root key, and `init` never generates
|
|
657
|
+
different key material under an existing key id. The development certificates and the
|
|
658
|
+
pairing secret are regenerated by step 3; they are never restored.
|
|
659
|
+
|
|
660
|
+
Every private file is created once at mode `0600` under `0700` directories
|
|
661
|
+
and is never overwritten; before generating anything, `init` checks every
|
|
662
|
+
destination and refuses with the full list if one already exists. Rerunning
|
|
663
|
+
`aac init` resumes an interrupted setup (outcome `resumed`), reports a
|
|
664
|
+
complete workspace (outcome `complete`, exit 0), or names the exact conflict
|
|
665
|
+
(exit 3). Every post-registration step is pinned to the workspace's tenant,
|
|
666
|
+
so a profile that has since been pointed at another tenant is refused rather
|
|
667
|
+
than mixed in. A rerun on a complete workspace validates it offline (binding,
|
|
668
|
+
files, permissions, certificate validity, CA fitness) and exits 3 with the
|
|
669
|
+
next command if it is no longer usable. Missing flags for a new tenant are
|
|
670
|
+
usage errors (exit 2).
|
|
671
|
+
|
|
672
|
+
**`aac init` never installs or replaces a tenant-admin authority for an
|
|
673
|
+
existing tenant.** For a new tenant it generates the admin key first and
|
|
674
|
+
registers the public half together with the tenant, which the control plane
|
|
675
|
+
creates in one transaction, so no competing authority can ever be retired by
|
|
676
|
+
setup. For an existing tenant it only checks: the key on this machine must
|
|
677
|
+
already be the tenant's ACTIVE admin key. If the tenant's ACTIVE key is not on
|
|
678
|
+
this machine, `init` stops and tells you to restore that key to
|
|
679
|
+
`~/.aac/tenants/<tenant-id>/tenant-admin.pem` (or pass
|
|
680
|
+
`--tenant-admin-key-file`); if the tenant has no admin key at all, it tells
|
|
681
|
+
you to install one deliberately with `aac tenant rotate-admin-key`. Replacing
|
|
682
|
+
an authority is always that deliberate command, never a side effect of setup. Managed directories under `~/.aac/workspaces/` and
|
|
683
|
+
`~/.aac/tenants/` must be real directories at mode `0700`; a symlink or a
|
|
684
|
+
looser mode is refused before anything is written.
|
|
685
|
+
A second workspace for the same tenant (`--workspace other`) reuses the
|
|
686
|
+
tenant-admin key and gets its own root key id. Each key has an
|
|
687
|
+
`--<role>-key-file` flag to use existing material instead of generating it.
|
|
688
|
+
|
|
689
|
+
**Everything generated is development material.** The CA lives seven days and
|
|
690
|
+
the leaf certificates one day; nothing here qualifies for production
|
|
691
|
+
regardless of the tenant's hosted domain. Moving to production means
|
|
692
|
+
`aac tenant rotate-admin-key` with a key under production custody, a new root
|
|
693
|
+
key id published by your publisher, and certificates from your approved
|
|
694
|
+
issuer.
|
|
695
|
+
|
|
696
|
+
Inspect and maintain a workspace with the `workspace` noun:
|
|
697
|
+
|
|
698
|
+
```bash
|
|
699
|
+
aac workspace status --workspace starter # what exists, expiry, permissions, next command
|
|
700
|
+
aac workspace status --workspace starter --remote # also: is the public trust material visible?
|
|
701
|
+
aac workspace renew --workspace starter # fresh leaf keys + certificates; old ones archived
|
|
702
|
+
aac workspace render --workspace starter --layout container --force # container path layout
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
`status` is offline by default and exits 3 when material is missing, expired
|
|
706
|
+
or unsafely permissioned. `renew` reissues the one-day certificates with fresh
|
|
707
|
+
keys and reissues the development CA only when it is expired or within a day
|
|
708
|
+
of expiry (or with `--ca`); a new CA is published under the next anchor id and
|
|
709
|
+
needs the publisher restarted. `render` never touches keys and refuses to
|
|
710
|
+
overwrite rendered files unless `--force` is given. Command, flag and path
|
|
711
|
+
names are provisional until the public starter example has consumed them.
|
|
712
|
+
|
|
552
713
|
## Assigned tenant domains (B239)
|
|
553
714
|
|
|
554
715
|
Registration automatically saves the server-assigned `hosted_trust_domain` in
|
|
@@ -256,7 +256,8 @@ aac sso register-idp --shared --file shared-github.json \
|
|
|
256
256
|
# admin verb (tenant list/describe/update, self-serve register-idp)
|
|
257
257
|
# then rides the session automatically.
|
|
258
258
|
aac sso login --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000
|
|
259
|
-
aac sso login --
|
|
259
|
+
aac sso login --idp github # developer tenant: GitHub or Google sign-in
|
|
260
|
+
aac sso login --issuer https://login.microsoftonline.com/<tid>/v2.0 # enterprise IdP, by issuer URL
|
|
260
261
|
aac sso login --flow pkce --no-browser # print the URL, don't launch
|
|
261
262
|
|
|
262
263
|
# Sessions are REFRESH-LESS by design (Eng Spec §XVII.2): on expiry
|
|
@@ -533,6 +534,165 @@ needed. Against a live stack: bring up the joined topology
|
|
|
533
534
|
(`./bin/run-wedge-a-control-plane-compose.sh --keep-up`) and point the
|
|
534
535
|
flags at localhost.
|
|
535
536
|
|
|
537
|
+
## Guided setup: `aac init` and `aac workspace`
|
|
538
|
+
|
|
539
|
+
`aac init` takes a developer from an installed CLI to a complete, runnable
|
|
540
|
+
local workload in one command. It signs you in, registers a developer tenant
|
|
541
|
+
(or reuses the one your profile is already bound to), takes the AAC-assigned
|
|
542
|
+
hosted trust domain, registers the sample workload, generates development
|
|
543
|
+
keys and certificates, registers the tenant-admin public key together with a
|
|
544
|
+
new tenant, and writes a complete sidecar configuration plus the publisher
|
|
545
|
+
environment and a Compose variable file.
|
|
546
|
+
|
|
547
|
+
```bash
|
|
548
|
+
# Developer tier: sign in with GitHub or Google. The command shows what it
|
|
549
|
+
# will create and asks before registering the tenant.
|
|
550
|
+
aac init --profile stage --admin-url https://api.stage.cascadeauth.dev \
|
|
551
|
+
--data-plane-url https://api.stage.cascadeauth.dev \
|
|
552
|
+
--trust-url https://trust.stage.cascadeauth.dev \
|
|
553
|
+
--display-name 'YOUR TEAM' --contact 'YOUR EMAIL' --idp github
|
|
554
|
+
|
|
555
|
+
# Scripted: every prompt has a flag; --create-tenant acknowledges creating a
|
|
556
|
+
# permanent tenant when stdin is not a terminal.
|
|
557
|
+
aac init --profile stage --admin-url ... --trust-url ... \
|
|
558
|
+
--display-name 'YOUR TEAM' --contact 'YOUR EMAIL' --idp github --create-tenant
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
Progress lines `[1/5]` to `[5/5]` name the five steps: tenant, sign-in, hosted trust
|
|
562
|
+
domain, workload, material and rendering. A rerun prints every step, marking the ones
|
|
563
|
+
already done. The sign-in you chose with `--idp` is remembered in the workspace, so a
|
|
564
|
+
rerun (or `aac sso login --idp github` by hand when a session expires) never asks you
|
|
565
|
+
to pick between GitHub and Google. Workspace commands take `--workspace <name>`; the
|
|
566
|
+
profile is recorded in the workspace, so `--profile` is optional there and only checked.
|
|
567
|
+
|
|
568
|
+
What it creates, and where:
|
|
569
|
+
|
|
570
|
+
* `~/.aac/workspaces/<name>/` — the **workspace** for one workload (default
|
|
571
|
+
name `starter`): `pki/` with what the sidecar container mounts — the
|
|
572
|
+
workload, terminal-attestation and localhost TLS key pairs, this
|
|
573
|
+
workspace's root-signing key and the outbound CA bundle; `pair/` with what
|
|
574
|
+
both containers mount at `/run/secrets` — the pairing secret and the public
|
|
575
|
+
development CA certificate; `ca/` with the development CA private key (kept
|
|
576
|
+
out of both mounts so no container ever sees the key that mints
|
|
577
|
+
identities); `sidecar-config.yaml`; `compose.env` (non-secret variables for
|
|
578
|
+
a Compose consumer); and an internal manifest that lets a rerun resume.
|
|
579
|
+
* `~/.aac/tenants/<tenant-id>/` — **tenant-level** material shared by every
|
|
580
|
+
workspace of that tenant: the tenant-admin signing key, the publisher's
|
|
581
|
+
input directories (`root-keys/`, `spiffe-bundle/` — public halves only) and
|
|
582
|
+
the rendered `publisher.env`.
|
|
583
|
+
* The tenant API key stays where the CLI always kept it,
|
|
584
|
+
`~/.aac/credentials/<tenant-id>`; the rendered configuration references it
|
|
585
|
+
by path.
|
|
586
|
+
|
|
587
|
+
### What each file is for, and what to back up
|
|
588
|
+
|
|
589
|
+
The table below is rendered from the CLI's own layout module and checked by a test, so it
|
|
590
|
+
cannot drift from the code. `<workspace>` is the workspace name (default `starter`) and
|
|
591
|
+
`<tenant-id>` the server-allocated tenant id.
|
|
592
|
+
|
|
593
|
+
<!-- inventory-table:start -->
|
|
594
|
+
| Material | Where | Used for | Lifetime | Back up? |
|
|
595
|
+
|---|---|---|---|---|
|
|
596
|
+
| 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` |
|
|
597
|
+
| tenant_admin_session | `~/.aac/credentials/<tenant-id>.session` | CLI admin calls | hours; `aac sso login` | No. sign in again |
|
|
598
|
+
| tenant_admin_signing_key | `~/.aac/tenants/<tenant-id>/tenant-admin.pem`, `~/.aac/tenants/<tenant-id>/tenant-admin.pub.pem` | the publisher signs trust-material uploads; the public half is registered with AAC | until rotated; `aac tenant rotate-admin-key` | **Recommended.** recommended for developer tenants (loss needs `aac tenant rotate-admin-key`); production custody belongs in an HSM/KMS, not a password manager |
|
|
599
|
+
| root_signing_key | `~/.aac/workspaces/<workspace>/pki/root.pem`, `~/.aac/workspaces/<workspace>/pki/root.pub.pem`, `~/.aac/tenants/<tenant-id>/root-keys/<workspace>-root-v1.pub.pem` | the sidecar mints authority chains; the public half is published under the key id | until you retire its key id (root key ids are fixed per workspace name) | No. never restore: a replacement workspace gets a new name and therefore a new key id, published alongside the old one until the old one is retired |
|
|
600
|
+
| development_ca | `~/.aac/workspaces/<workspace>/ca/dev-ca.key`, `~/.aac/workspaces/<workspace>/pki/dev-ca.crt`, `~/.aac/workspaces/<workspace>/pair/dev-ca.crt`, `~/.aac/tenants/<tenant-id>/spiffe-bundle/<workspace>-dev-ca.ca.pem` | signs the workload, terminal and TLS certificates; peers trust it via the published bundle | 7 days; `aac workspace renew --ca` | No. development only; reissue |
|
|
601
|
+
| workload_svid | `~/.aac/workspaces/<workspace>/pki/workload.key`, `~/.aac/workspaces/<workspace>/pki/workload.crt` | the sidecar's SPIFFE identity: proves on every live request that this workload holds the key behind its certificate | 1 day; `aac workspace renew` | No. development only; reissue |
|
|
602
|
+
| terminal_attestation | `~/.aac/workspaces/<workspace>/pki/terminal.key`, `~/.aac/workspaces/<workspace>/pki/terminal.crt` | signs the receipt (terminal attestation) the sidecar issues when this workload completes a delegated request; auditors verify it offline against your published CA; kept separate from the workload key so neither can forge the other's role | 1 day; `aac workspace renew` | No. development only; reissue |
|
|
603
|
+
| localhost_tls | `~/.aac/workspaces/<workspace>/pki/server.key`, `~/.aac/workspaces/<workspace>/pki/server.crt` | the sidecar's HTTPS listener | 1 day; `aac workspace renew` | No. development only; reissue |
|
|
604
|
+
| outbound_ca_bundle | `~/.aac/workspaces/<workspace>/pki/outbound-ca.pem` | the sidecar verifies TLS to AAC and to itself | rebuilt by renew; `aac workspace renew` | No. derived |
|
|
605
|
+
| pairing_secret | `~/.aac/workspaces/<workspace>/pair/pairing.secret` | the sidecar and its agent authenticate each other (same bytes in both) | until regenerated | No. regenerable; both processes must read the same file |
|
|
606
|
+
| workspace_manifest | `~/.aac/workspaces/<workspace>/manifest.json` | the workspace's identities and progress; every `workspace` verb reads it first | for the life of the workspace | **Recommended.** cannot be recreated by any command; without it start a new workspace under a new name (see the machine-loss procedure) |
|
|
607
|
+
| published_public_inputs | `~/.aac/tenants/<tenant-id>/root-keys`, `~/.aac/tenants/<tenant-id>/spiffe-bundle` | the publisher's input directories: every root public key and CA certificate this tenant has published, across all workspaces | for the life of the tenant | **Recommended.** back up with the tenant directory: a replacement workspace must publish its new root key alongside a still-ACTIVE previous one (the control plane refuses a root-key set that drops every ACTIVE key at once) |
|
|
608
|
+
| rendered_files | `~/.aac/workspaces/<workspace>/sidecar-config.yaml`, `~/.aac/workspaces/<workspace>/compose.env`, `~/.aac/tenants/<tenant-id>/publisher.env` | non-secret configuration derived from the manifest | re-rendered on demand; `aac workspace render --force` | No. derived; `aac workspace render --force` recreates them |
|
|
609
|
+
<!-- inventory-table:end -->
|
|
610
|
+
|
|
611
|
+
#### If you lose this machine
|
|
612
|
+
|
|
613
|
+
Back up two things while the machine is healthy: the tenant API key at
|
|
614
|
+
`~/.aac/credentials/<tenant-id>`, and the whole tenant directory
|
|
615
|
+
`~/.aac/tenants/<tenant-id>/` (the admin signing key plus the `root-keys/` and
|
|
616
|
+
`spiffe-bundle/` public inputs the publisher has already published). The workspace
|
|
617
|
+
manifest is worth keeping too, but a workspace can always be replaced. Then, on the new
|
|
618
|
+
machine:
|
|
619
|
+
|
|
620
|
+
1. Install the CLI and `aac profile create <name> --admin-url … --data-plane-url …`.
|
|
621
|
+
Sign in with `aac sso login --idp github --tenant-id <tenant-id> --profile <name>`
|
|
622
|
+
(or `--issuer <url>` for an enterprise connection): the remaining
|
|
623
|
+
steps register a workload and read the tenant's admin-key state, which need a
|
|
624
|
+
tenant-admin session whatever way the tenant was registered (a ceremony bootstrap
|
|
625
|
+
token only covers registration and hosted-domain assignment).
|
|
626
|
+
2. Restore the tenant API key to `~/.aac/credentials/<tenant-id>` (mode `0600`), or issue a
|
|
627
|
+
new one with `aac tenant reissue-api-key` if it was not backed up. Restore
|
|
628
|
+
`~/.aac/tenants/<tenant-id>/` from the backup (directories `0700`, files `0600`).
|
|
629
|
+
3. Run `aac init --profile <name> --workspace <new-name>` with a **new** workspace name.
|
|
630
|
+
Root key ids are fixed per workspace name and the control plane never accepts new key
|
|
631
|
+
material under an old id, so the replacement workspace publishes a new id
|
|
632
|
+
(`<new-name>-root-v1`) and a new development CA anchor.
|
|
633
|
+
4. Start the publisher from the restored tenant directory. Its root-key set now holds the
|
|
634
|
+
old and the new public keys, which is what the control plane requires: a set that
|
|
635
|
+
drops every currently ACTIVE key at once is refused. Retire the old key later by
|
|
636
|
+
removing its public file once nothing signs with it.
|
|
637
|
+
|
|
638
|
+
Reusing the old workspace name on the new machine is refused on purpose: the restored
|
|
639
|
+
tenant directory still holds that name's published root key, and `init` never generates
|
|
640
|
+
different key material under an existing key id. The development certificates and the
|
|
641
|
+
pairing secret are regenerated by step 3; they are never restored.
|
|
642
|
+
|
|
643
|
+
Every private file is created once at mode `0600` under `0700` directories
|
|
644
|
+
and is never overwritten; before generating anything, `init` checks every
|
|
645
|
+
destination and refuses with the full list if one already exists. Rerunning
|
|
646
|
+
`aac init` resumes an interrupted setup (outcome `resumed`), reports a
|
|
647
|
+
complete workspace (outcome `complete`, exit 0), or names the exact conflict
|
|
648
|
+
(exit 3). Every post-registration step is pinned to the workspace's tenant,
|
|
649
|
+
so a profile that has since been pointed at another tenant is refused rather
|
|
650
|
+
than mixed in. A rerun on a complete workspace validates it offline (binding,
|
|
651
|
+
files, permissions, certificate validity, CA fitness) and exits 3 with the
|
|
652
|
+
next command if it is no longer usable. Missing flags for a new tenant are
|
|
653
|
+
usage errors (exit 2).
|
|
654
|
+
|
|
655
|
+
**`aac init` never installs or replaces a tenant-admin authority for an
|
|
656
|
+
existing tenant.** For a new tenant it generates the admin key first and
|
|
657
|
+
registers the public half together with the tenant, which the control plane
|
|
658
|
+
creates in one transaction, so no competing authority can ever be retired by
|
|
659
|
+
setup. For an existing tenant it only checks: the key on this machine must
|
|
660
|
+
already be the tenant's ACTIVE admin key. If the tenant's ACTIVE key is not on
|
|
661
|
+
this machine, `init` stops and tells you to restore that key to
|
|
662
|
+
`~/.aac/tenants/<tenant-id>/tenant-admin.pem` (or pass
|
|
663
|
+
`--tenant-admin-key-file`); if the tenant has no admin key at all, it tells
|
|
664
|
+
you to install one deliberately with `aac tenant rotate-admin-key`. Replacing
|
|
665
|
+
an authority is always that deliberate command, never a side effect of setup. Managed directories under `~/.aac/workspaces/` and
|
|
666
|
+
`~/.aac/tenants/` must be real directories at mode `0700`; a symlink or a
|
|
667
|
+
looser mode is refused before anything is written.
|
|
668
|
+
A second workspace for the same tenant (`--workspace other`) reuses the
|
|
669
|
+
tenant-admin key and gets its own root key id. Each key has an
|
|
670
|
+
`--<role>-key-file` flag to use existing material instead of generating it.
|
|
671
|
+
|
|
672
|
+
**Everything generated is development material.** The CA lives seven days and
|
|
673
|
+
the leaf certificates one day; nothing here qualifies for production
|
|
674
|
+
regardless of the tenant's hosted domain. Moving to production means
|
|
675
|
+
`aac tenant rotate-admin-key` with a key under production custody, a new root
|
|
676
|
+
key id published by your publisher, and certificates from your approved
|
|
677
|
+
issuer.
|
|
678
|
+
|
|
679
|
+
Inspect and maintain a workspace with the `workspace` noun:
|
|
680
|
+
|
|
681
|
+
```bash
|
|
682
|
+
aac workspace status --workspace starter # what exists, expiry, permissions, next command
|
|
683
|
+
aac workspace status --workspace starter --remote # also: is the public trust material visible?
|
|
684
|
+
aac workspace renew --workspace starter # fresh leaf keys + certificates; old ones archived
|
|
685
|
+
aac workspace render --workspace starter --layout container --force # container path layout
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
`status` is offline by default and exits 3 when material is missing, expired
|
|
689
|
+
or unsafely permissioned. `renew` reissues the one-day certificates with fresh
|
|
690
|
+
keys and reissues the development CA only when it is expired or within a day
|
|
691
|
+
of expiry (or with `--ca`); a new CA is published under the next anchor id and
|
|
692
|
+
needs the publisher restarted. `render` never touches keys and refuses to
|
|
693
|
+
overwrite rendered files unless `--force` is given. Command, flag and path
|
|
694
|
+
names are provisional until the public starter example has consumed them.
|
|
695
|
+
|
|
536
696
|
## Assigned tenant domains (B239)
|
|
537
697
|
|
|
538
698
|
Registration automatically saves the server-assigned `hosted_trust_domain` in
|
|
@@ -125,6 +125,7 @@ from aac_cli.idp_recovery import (
|
|
|
125
125
|
from aac_cli.idp_recovery import (
|
|
126
126
|
encode_signature as encode_idp_recovery_signature,
|
|
127
127
|
)
|
|
128
|
+
from aac_cli.init_cli import add_init_parser
|
|
128
129
|
from aac_cli.profiles import (
|
|
129
130
|
cmd_profile_create,
|
|
130
131
|
cmd_profile_delete,
|
|
@@ -159,6 +160,7 @@ from aac_cli.sso_login import (
|
|
|
159
160
|
select_connection,
|
|
160
161
|
select_flow,
|
|
161
162
|
)
|
|
163
|
+
from aac_cli.workspace_cli import add_workspace_parser
|
|
162
164
|
|
|
163
165
|
__all__ = ["build_parser", "entrypoint", "main"]
|
|
164
166
|
|
|
@@ -438,8 +440,8 @@ def _print_api_key_verification_guidance(
|
|
|
438
440
|
" curl --fail-with-body --silent --show-error --config - \\\n"
|
|
439
441
|
f" {shlex.quote(verification_url)}\n\n"
|
|
440
442
|
"A 200 response confirms the new key. Do not place the plaintext "
|
|
441
|
-
"key directly in a shell command. The key is stored at
|
|
442
|
-
"
|
|
443
|
+
f"key directly in a shell command. The key is stored at {credential_path}; "
|
|
444
|
+
"transfer it from that file to the organization's approved "
|
|
443
445
|
"secret store for any other system, never through chat, tickets, or "
|
|
444
446
|
"shell arguments.",
|
|
445
447
|
file=sys.stderr,
|
|
@@ -941,7 +943,8 @@ def _cmd_tenant_register_locked(args: argparse.Namespace, profile: str) -> int:
|
|
|
941
943
|
return 3
|
|
942
944
|
if recovered_domain is not None:
|
|
943
945
|
payload["hosted_trust_domain"] = recovered_domain
|
|
944
|
-
|
|
946
|
+
if not getattr(args, "guided", False):
|
|
947
|
+
_print_hosted_domain_guidance(recovered_domain, profile, output=args.output)
|
|
945
948
|
try:
|
|
946
949
|
read_api_key(returned_tenant_id)
|
|
947
950
|
except CliConfigError:
|
|
@@ -1079,16 +1082,19 @@ def _cmd_tenant_register_locked(args: argparse.Namespace, profile: str) -> int:
|
|
|
1079
1082
|
print(
|
|
1080
1083
|
f"\nNEXT: your {intent.admission.idp} identity is this "
|
|
1081
1084
|
f"tenant's first tenant-admin — sign in with:\n"
|
|
1082
|
-
f" aac sso login --
|
|
1083
|
-
f"--profile {profile}"
|
|
1085
|
+
f" aac sso login --idp {intent.admission.idp} "
|
|
1086
|
+
f"--tenant-id {returned_tenant_id} --profile {shlex.quote(profile)}"
|
|
1084
1087
|
)
|
|
1085
1088
|
else:
|
|
1086
1089
|
print(json.dumps(result, indent=2))
|
|
1087
|
-
|
|
1090
|
+
# `aac init` drives this verb and prints its own step summary: the
|
|
1091
|
+
# human guidance below would interleave with the guided progress.
|
|
1092
|
+
guided = bool(getattr(args, "guided", False))
|
|
1093
|
+
if payload.get("hosted_trust_domain") is not None and not guided:
|
|
1088
1094
|
_print_hosted_domain_guidance(
|
|
1089
1095
|
payload["hosted_trust_domain"]["trust_domain"], profile, output=args.output
|
|
1090
1096
|
)
|
|
1091
|
-
if stored_at is not None:
|
|
1097
|
+
if stored_at is not None and not guided:
|
|
1092
1098
|
_print_api_key_verification_guidance(
|
|
1093
1099
|
tenant_id=returned_tenant_id,
|
|
1094
1100
|
profile=profile,
|
|
@@ -4102,6 +4108,19 @@ def _default_scope_for(connection: LoginConnection) -> str:
|
|
|
4102
4108
|
return "openid profile email"
|
|
4103
4109
|
|
|
4104
4110
|
|
|
4111
|
+
def _login_command_prefix(args: argparse.Namespace, tenant_id: str) -> str:
|
|
4112
|
+
"""`aac sso login …` as the caller invoked it, so printed remedies run unchanged."""
|
|
4113
|
+
prefix = (
|
|
4114
|
+
f"aac sso login --tenant-id {tenant_id} "
|
|
4115
|
+
f"--profile {shlex.quote(select_profile(args.profile).name)}"
|
|
4116
|
+
)
|
|
4117
|
+
if args.admin_url:
|
|
4118
|
+
prefix += f" --admin-url {shlex.quote(args.admin_url)}"
|
|
4119
|
+
if args.data_plane_url:
|
|
4120
|
+
prefix += f" --data-plane-url {shlex.quote(args.data_plane_url)}"
|
|
4121
|
+
return prefix
|
|
4122
|
+
|
|
4123
|
+
|
|
4105
4124
|
def _cmd_sso_login(args: argparse.Namespace) -> int:
|
|
4106
4125
|
"""B141 (§XVII.9): the aws-sso-login UX for AAC.
|
|
4107
4126
|
|
|
@@ -4153,7 +4172,12 @@ def _cmd_sso_login(args: argparse.Namespace) -> int:
|
|
|
4153
4172
|
print(line, file=sys.stderr)
|
|
4154
4173
|
|
|
4155
4174
|
try:
|
|
4156
|
-
connection = select_connection(
|
|
4175
|
+
connection = select_connection(
|
|
4176
|
+
connections,
|
|
4177
|
+
args.issuer,
|
|
4178
|
+
args.idp,
|
|
4179
|
+
command_prefix=_login_command_prefix(args, tenant_id),
|
|
4180
|
+
)
|
|
4157
4181
|
flow = select_flow(connection, args.flow)
|
|
4158
4182
|
scope = args.scope if args.scope is not None else _default_scope_for(connection)
|
|
4159
4183
|
evidence_token = _run_login_flow(
|
|
@@ -4237,6 +4261,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
4237
4261
|
parser.add_argument("--version", action="version", version=f"aac {__version__}")
|
|
4238
4262
|
nouns = parser.add_subparsers(dest="noun", required=True)
|
|
4239
4263
|
|
|
4264
|
+
# `aac init` is a meta command beside `doctor`/`version`; `workspace` is the
|
|
4265
|
+
# noun for what it creates (CLI Config+Profile Spec, B241 amendment §C/§D).
|
|
4266
|
+
add_init_parser(nouns)
|
|
4267
|
+
add_workspace_parser(nouns)
|
|
4268
|
+
|
|
4240
4269
|
profile = nouns.add_parser(
|
|
4241
4270
|
"profile",
|
|
4242
4271
|
help="Manage local profiles (list/show/create/update/delete).",
|
|
@@ -4484,6 +4513,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
4484
4513
|
"federated evidence instead)."
|
|
4485
4514
|
),
|
|
4486
4515
|
)
|
|
4516
|
+
register.add_argument("--guided", action="store_true", help=argparse.SUPPRESS)
|
|
4487
4517
|
register.add_argument(
|
|
4488
4518
|
"--idp",
|
|
4489
4519
|
choices=("github", "google"),
|
|
@@ -5527,10 +5557,20 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
5527
5557
|
default=None,
|
|
5528
5558
|
help="Tenant to sign in TO (default: profile/env tenant).",
|
|
5529
5559
|
)
|
|
5530
|
-
login.
|
|
5560
|
+
which = login.add_mutually_exclusive_group()
|
|
5561
|
+
which.add_argument(
|
|
5531
5562
|
"--issuer",
|
|
5532
5563
|
default=None,
|
|
5533
|
-
help=
|
|
5564
|
+
help=(
|
|
5565
|
+
"Which IdP connection to use, by its full issuer URL (needed when several "
|
|
5566
|
+
"are registered; the error lists the exact commands)."
|
|
5567
|
+
),
|
|
5568
|
+
)
|
|
5569
|
+
which.add_argument(
|
|
5570
|
+
"--idp",
|
|
5571
|
+
choices=("github", "google"),
|
|
5572
|
+
default=None,
|
|
5573
|
+
help="Short form of --issuer for the shared developer sign-ins: github or google.",
|
|
5534
5574
|
)
|
|
5535
5575
|
login.add_argument(
|
|
5536
5576
|
"--flow",
|