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.
Files changed (46) hide show
  1. {aac_cli-0.1.3 → aac_cli-0.1.4}/PKG-INFO +163 -2
  2. {aac_cli-0.1.3 → aac_cli-0.1.4}/README.md +161 -1
  3. {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/__init__.py +1 -1
  4. {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/cli.py +50 -10
  5. aac_cli-0.1.4/aac_cli/config_render.py +266 -0
  6. aac_cli-0.1.4/aac_cli/dev_material.py +405 -0
  7. {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/idp_recovery.py +4 -50
  8. aac_cli-0.1.4/aac_cli/init_cli.py +1392 -0
  9. aac_cli-0.1.4/aac_cli/secure_files.py +200 -0
  10. {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/sso_login.py +85 -16
  11. aac_cli-0.1.4/aac_cli/workspace_cli.py +525 -0
  12. aac_cli-0.1.4/aac_cli/workspace_health.py +156 -0
  13. aac_cli-0.1.4/aac_cli/workspace_layout.py +467 -0
  14. aac_cli-0.1.4/aac_cli/workspace_manifest.py +144 -0
  15. {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli.egg-info/PKG-INFO +163 -2
  16. {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli.egg-info/SOURCES.txt +15 -1
  17. {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli.egg-info/requires.txt +1 -0
  18. {aac_cli-0.1.3 → aac_cli-0.1.4}/pyproject.toml +7 -1
  19. aac_cli-0.1.4/tests/test_b241_edge_paths.py +925 -0
  20. aac_cli-0.1.4/tests/test_config_render.py +177 -0
  21. aac_cli-0.1.4/tests/test_dev_material.py +127 -0
  22. {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_hosted_domains_cli.py +36 -0
  23. aac_cli-0.1.4/tests/test_init_cli.py +1417 -0
  24. aac_cli-0.1.4/tests/test_inventory_docs.py +70 -0
  25. {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_sso_login_cli.py +121 -1
  26. aac_cli-0.1.4/tests/test_workspace_cli.py +400 -0
  27. {aac_cli-0.1.3 → aac_cli-0.1.4}/LICENSE +0 -0
  28. {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/__main__.py +0 -0
  29. {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/admin_key_pem.py +0 -0
  30. {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/api_key_rotation_state.py +0 -0
  31. {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/config.py +0 -0
  32. {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/profiles.py +0 -0
  33. {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli/registration_state.py +0 -0
  34. {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli.egg-info/dependency_links.txt +0 -0
  35. {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli.egg-info/entry_points.txt +0 -0
  36. {aac_cli-0.1.3 → aac_cli-0.1.4}/aac_cli.egg-info/top_level.txt +0 -0
  37. {aac_cli-0.1.3 → aac_cli-0.1.4}/setup.cfg +0 -0
  38. {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_aac_cli.py +0 -0
  39. {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_admin_key_pem.py +0 -0
  40. {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_api_key_reissue_cli.py +0 -0
  41. {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_api_key_rotation_cli.py +0 -0
  42. {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_api_key_rotation_state.py +0 -0
  43. {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_idp_recovery_cli.py +0 -0
  44. {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_profile_cli.py +0 -0
  45. {aac_cli-0.1.3 → aac_cli-0.1.4}/tests/test_registration_recovery_cli.py +0 -0
  46. {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
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 --issuer https://login.microsoftonline.com/<tid>/v2.0 # >1 IdP
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 --issuer https://login.microsoftonline.com/<tid>/v2.0 # >1 IdP
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
@@ -10,4 +10,4 @@ entrypoint (`aac_cli.cli:entrypoint`).
10
10
  # test in bin/tests/test_cli_version_parity.py. A literal keeps the
11
11
  # shipped code free of import-time metadata lookups (and their
12
12
  # not-installed failure mode).
13
- __version__ = "0.1.3"
13
+ __version__ = "0.1.4"
@@ -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 the printed "
442
- "path; transfer it from that file to the organization's approved "
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
- _print_hosted_domain_guidance(recovered_domain, profile, output=args.output)
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 --tenant-id {returned_tenant_id} "
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
- if payload.get("hosted_trust_domain") is not None:
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(connections, args.issuer)
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.add_argument(
5560
+ which = login.add_mutually_exclusive_group()
5561
+ which.add_argument(
5531
5562
  "--issuer",
5532
5563
  default=None,
5533
- help="Which IdP connection to use (required when several are registered).",
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",