AAC Docs

Configure the sidecar

AAC Sidecar v0.4.4MarkdownDocs 42797a46037e

Start the AAC journey · All references

This reference is for a deployment or integration you have chosen to configure. For the first authenticated reservation, use the packaged Compose journey above.

One tenant with many production agents

This section explains the production deployment model. The public developer-beta binary license permits evaluation and integration development with stage; it does not grant production or third-party service use.

Register the tenant once, establish its administrator and any enterprise IdP connection/recovery path, and protect the tenant-admin signing key. Publish the tenant's CA certificates and the root public keys of agents that start tasks. The CLI user guide provides the developer and enterprise registration procedures.

For each agent, register its workload, obtain a matching SPIFFE certificate/key from your issuer chaining to the published CA, prepare its sidecar config, private pairing secret and scoped tenant API key, and deploy the pair with restricted ingress and appropriate persistent state. Give a root-signing key only to originators and a receipt-signing key to agents that finish tasks.

For a chain of 100 agents where agent n calls agent n+1, first select the existing tenant's prod profile and active assigned or custom trust domain. Replace YOUR_ACTIVE_TRUST_DOMAIN with that domain, then register the workloads:

AAC_TRUST_DOMAIN='YOUR_ACTIVE_TRUST_DOMAIN'
for n in {1..100}; do
  aac tenant add-workload \
    --profile prod \
    --spiffe-id "spiffe://$AAC_TRUST_DOMAIN/agent-$n" \
    --display-name "Agent $n"
done

Issue the corresponding certificates through your tenant issuer. Configure agents 1–99 with their actual successor's HTTPS endpoint and exact workload audience, and agent 100 to return the terminal receipt. Agent 1 also needs its initial class of action. Forwarding narrows the received authority and signs with the forwarder's identity key. This is a configuration example, not a 100-agent capacity measurement or an automatic deployment command.

Agent 1 (starts the task) Agents 2–100 (receive and forward)
The sidecar yes yes
An identity: a SPIFFE ID with a certificate and key from the tenant's own issuer, chaining to the CA the tenant publishes yes yes
The workload registered with AAC (aac tenant add-workload --spiffe-id …, one per agent, scriptable) yes yes
A root signing key (mints the chain's first authority) yes no — a forwarding sidecar narrows the authority it received and signs its proof with its own identity key
A receipt-signing (terminal attestation) key if it finishes tasks agent 100, which finishes the task
The sidecar config, the pairing secret shared with its agent, the tenant API key its sidecar uses to reach AAC yes yes

Once per tenant: registration/admin key and coordinated public trust publishing. For this chain only agent 1's root public key needs publishing. All 100 agents need identities; receiving and forwarding does not require an originator key.

The CLI's supplied-material configuration workflow can validate and arrange your issuer's certificate/key files. It does not become a production CA or provision remote hosts. For role-specific minimal deployments use the configuration template and key inventory: do not generate unused originator/terminal keys merely to fill a generic example. Protect and deliver private material only to its actual consumer, then verify authenticated calls and receipts before admitting traffic.

Publisher ownership with multiple domains

One root-key writer owns the tenant's root set. One SPIFFE writer owns each active binding. A single process can serve both roles for one domain; another domain requires a separate publisher process with its own public-CA directory.

Setting / authority Publisher A Publisher B
Tenant ID Same tenant Same tenant
SPIFFE trust domain AAC-assigned domain Proven custom domain
SPIFFE bundle directory Assigned-domain public CA anchors Custom-domain public CA anchors
SPIFFE ingest/read URLs Ingest URL and assigned-domain read URL Ingest URL and custom-domain read URL
Root-key role Root public-key directory and ingest URL Both AAC_TAP_ROOT_KEYS_DIR and AAC_TAP_ROOT_KEYS_INGEST_URL unset

Both use authorized tenant-admin signing credentials and discover their own active binding version. Root sequences are tenant-scoped; SPIFFE sequences are binding-episode scoped. Never start competing writers for either scope. The publisher receives public root/CA material and its administration credential, never a CA private key or workload signing private key. Adding a custom domain does not rename existing workload identities; register and issue new identities deliberately while retaining any hosted workloads you still need.

Installation artifacts inventory

Choose one sidecar installation format: the container for Docker/Kubernetes, or a standalone binary for a VM, systemd host or macOS. The remaining artifacts serve tenant administration, public-trust publication and workload integration. They do not all need to be installed on every workload host. For the trust anchor publisher, choose one deployment option as well: its Docker container or Python package. Both run the same daemon. Run one writer for your tenant's root keys and one for each active domain binding, reusing an existing publisher where available: a tenant with a single trust domain needs one process, and a tenant that keeps its assigned domain and adds its own runs two, with root-key publishing enabled in exactly one of them.

The package pages and downloads below are public. Installing these artifacts does not require a GitHub, Docker Hub or PyPI account. Registering and managing your AAC tenant does require the sign-in described in Register your developer tenant below.

Artifact Package or registry location Type When to install
AAC CLI aac-cli on PyPI Command-line administration tool Used by the tenant operator for this guide's onboarding, credentials, public trust and trace commands. The running sidecar does not depend on the CLI.
AAC Sidecar container (default) docker.io/cascadeauth/aac-sidecar Ready-made Linux container image Choose this for Docker/Kubernetes. This and the standalone binary below are alternative installations of the same sidecar.
Trust anchor publisher Docker container ghcr.io/cascadeauth/aac-trust-anchor-publisher Containerized public-trust publisher Choose this for a container host or orchestrator to publish/manage the tenant's public root keys and SPIFFE CA bundle. Docker or the orchestrator manages its lifecycle.
Trust anchor publisher Python package (alternative) aac-trust-anchor-publisher on PyPI Python wheel that installs the publisher daemon command Choose this for installation in a host/VM's Python virtual environment; use systemd on a managed Linux host where applicable. It requires Python, unlike the sidecar's compiled standalone binary.
AAC Sidecar standalone bundle (alternative) oras pull --output ./aac-sidecar-bundle docker.io/cascadeauth/aac-sidecar:v0.4.4-bundle Download containing standalone binaries, guide/template and audit evidence Choose this if you are not using the container. Install ORAS, then follow the standalone installation steps. Container users can download the template directly from this site.
Invoke authentication aac-invoke-auth on PyPI; optional [fastapi] extra Framework-independent Python signing/verification library, with an optional FastAPI/Starlette adapter Install it in a Python workload that uses these helpers. Use [fastapi] for the supplied middleware/dependency integration or the protocol reference's Python example. Other stacks need compatible pairing authentication; they do not need to install this Python package.

Image verification does not require the standalone bundle. The container signature is attached to its registry image and can be checked directly with Cosign, followed by a pull of the verified digest. The bundle adds standalone binaries and the SPDX/provenance/OCI files used by the optional deep audit. ORAS is a command-line tool for downloading files stored in a container registry. The command above saves the bundle's files in ./aac-sidecar-bundle; use a new, empty directory. Use oras pull for these files and docker pull for the runnable sidecar image. ORAS is not needed to run the sidecar.

Installing current releases

Use the current release of each AAC artifact. The sidecar installation commands below use the current published version. The image and standalone bundle use the same version. Exact versions and digests are available in the release record.

The sidecar has no latest tag, so an untagged pull will fail and every sidecar command here names the version. Its companion packages are different: install the current release of each from PyPI without a version pin, and let pip resolve it —

Upgrade an existing companion installation before following this guide. Pin a version only when your own deployment needs a fixed one.

For the advanced verification path, reject a tag that does not resolve to a SHA-256 digest or an artifact whose signature or digest fails.

Downloading, copying, installing, or using the sidecar accepts the included AAC Sidecar Developer Beta Binary License 1.0. The three Python companion artifacts remain independently licensed under Apache-2.0. The bundle and image also carry THIRD_PARTY_NOTICES.md for modules linked into the compiled binary.

AAC stage endpoints

Use https://api.stage.cascadeauth.dev for both CLI admin and data-plane requests. Public trust documents are served from https://trust.stage.cascadeauth.dev. These are AAC stage endpoints, not a production service or an instruction to expose your sidecar's loopback port.

Check HTTPS reachability without credentials or tenant creation:

curl --fail --silent --show-error https://api.stage.cascadeauth.dev/healthz

The response includes status: ok and control_plane_version. This is a liveness check, not proof that your tenant, trust material, or sidecar is ready.

Supported deployments and endpoint allowlist

Surface Supported beta profile
Container Linux amd64/arm64; ready-made distroless image; UID/GID 65532
Standalone Linux amd64/arm64 and macOS amd64/arm64; non-root process
Agent pairing Same network namespace; local /invoke and /a2a/v1 authenticated with the per-pair secret
Native A2A A2A 1.0 unary SendMessage; JSON-RPC 2.0; no streaming or general-purpose A2A method support
Root and terminal signing File-backed Ed25519/P-256, or explicit Azure Key Vault Standard software-protected non-exportable P-256 keys
Workload DPoP Local file-backed key matching the SVID; remote DPoP is unsupported
Replay Basic: explicit memory/basic, process-local and lost on restart. Shared durable: qualified authenticated-TLS Valkey ha-retained-write-safe; no fallback
A2A retry state Retained private bbolt file, one process owner; storage must be qualified for your deployment
Service level Developer evaluation/integration beta; no production SLA or production-rate claim

The sidecar is provider-neutral. AWS/GCP/HSM/PKCS#11 signer adapters, general plugin loading, and tenant-built sidecar images are outside this beta profile. Azure hosting is optional; use the qualification worksheet below if relevant.

Allow only the endpoints your selected installation actually needs:

Caller Destination Purpose and boundary
Installer Docker Hub registry/auth/content endpoints for docker.io/cascadeauth/aac-sidecar Public image and bundle download; no AAC artifact credential
Installer pypi.org, files.pythonhosted.org Public Python companion/sample dependencies
Publisher container installer ghcr.io/cascadeauth/aac-trust-anchor-publisher and GHCR's content endpoints Optional public publisher image
Advanced verifier Sigstore's public verification/transparency services as required by Cosign Validate release identity and transparency evidence
CLI, sidecar projection/telemetry, publisher https://api.stage.cascadeauth.dev:443 Admin/data APIs, STS, signed trust ingest; use the appropriate credential role
Sidecar, publisher public reads https://trust.stage.cascadeauth.dev:443 Public root-key and SPIFFE-bundle polling
CLI/browser Selected GitHub/Google sign-in endpoints and CLI's temporary local callback Interactive identity-provider sign-in; no blanket IdP access needed by the sidecar
Paired agent/client 127.0.0.1:8080; sidecar to 127.0.0.1:8000 Trusted local APIs/callbacks; do not expose outside the shared namespace
Peer sidecars Explicit configured peer HTTPS endpoints, normally port 9443 TLS, AAC chain, DPoP and recipient verification; URLs must be final, without redirects
Sidecar, if Shared durable selected Tenant-local authenticated-TLS Valkey endpoint Shared retained replay claims; no fallback to Basic or central service
Optional Azure signer Exact tenant vault HTTPS hostname and platform managed-identity endpoint Managed identity plus pinned key-version get/sign; no client secret or file fallback

Registry/CDN and identity-provider redirects are operated by those providers; apply their current endpoint policy to installer/browser hosts. They are not a reason to allow general internet egress from the running sidecar. Your DNS, time synchronization and PKI distribution must also work. The local sample uses only loopback peer endpoints and the explicit stage trust/data hosts.

Configuration and workflow state

Keep business progress and reports in your application's storage. Messages waiting for other branches of a workflow are buffered temporarily and can be lost when the sidecar restarts. Replay protection and retry-result storage do not replace an application database.

For work spanning days or weeks, the tenant application must retain its own business evidence and progress, then obtain independently authorized fresh chains at checkpoints. It may explicitly reuse a task_ref for correlation. Stored evidence does not renew expired authority; AAC does not supply a workflow database or automatic long-running orchestration.

You can configure request timeouts in your sidecar YAML when an agent or peer needs more or less time to respond:

Tenant setting Use it to
timeouts.agent_invoke_timeout_seconds Limit how long the sidecar waits for your local agent
timeouts.cross_org_dispatch_timeout_seconds Set the default timeout for a request to another sidecar
destinations.<name>.timeout_ms Override that default for one destination, in milliseconds

Use positive, unquoted numbers. A destination override takes precedence over the dispatch default. For A2A, keep it within the configured overall operation deadline. Leave these settings at their defaults unless your application needs an adjustment.

Set authority duration with valid_for on the class or destination. Business dates in the payload do not extend that authority or the request timeout.

Optional Azure qualification worksheet

Complete this before relying on the Azure adapter or a particular storage class. The local example does not supply these results. Keep identifiers and sanitized receipts in your own tenant record; never send keys to AAC.

Check Record and pass condition
Exact package Image/bundle digest, version, signature identity and guide checksum
Root/terminal keys Separate, exact versioned HTTPS key URIs; Standard software-protected P-256; public keys match configured identity/certificates
Managed identity Selected system/user-assigned identity and least-privilege key get/sign; local workload DPoP stays local
Failures Disabled key, removed grant, auth failure, throttling, timeout, malformed/wrong-key response: no minted artifact and no fallback
Persistent storage Provider/SKU/class/mount options; private ownership; remount and replacement retain bbolt; missing/corrupt/insecure/locked files fail closed
Capacity Allocate storage for your expected traffic and retention period, including saved responses. Confirm records survive replacement and that capacity limits produce a clear, recoverable failure.
A2A requests Check that your normal request sizes and response times fit your configured limits. Confirm retries do not repeat a completed business action.
Connectivity Public trust polling, exact workload projection, central metadata delivery and local terminal evidence
Lifecycle Credential rotation/revocation, upgrade, rollback, rejected-beta handling, local cleanup and retained-state custody
Cost/support Your expected cloud costs, responsible operator and escalation contact

When using signers, configure each purpose with provider: azure-key-vault and an exact versioned key_uri; omit that purpose's file-backed private-key setting. Keep the terminal certificate and workload SVID/key. Set AZURE_CLIENT_ID only when selecting a user-assigned managed identity. Unsupported providers or conflicting file/provider settings fail startup.

Advanced installation guide — container

For optional signature and digest checks, use the artifact verification.

1. Place the sidecar beside the workload

The image already contains the compiled /aac-sidecar entrypoint and runs as non-root 65532:65532. Do not copy the binary into a tenant-built image.

The production security boundary expects the sidecar and its paired agent to share one network namespace. In Kubernetes, put both containers in the same Pod. The agent listens on 127.0.0.1:8000; the sidecar keeps sidecar.agent_invoke_url: http://127.0.0.1:8000/invoke and its loopback API on 127.0.0.1:8080. Expose only the sidecar external port 9443 through the tenant's approved Service/ingress path.

Mount rather than bake:

The starter selects Basic replay protection. Choose Shared durable when replay history must survive restart or coordinate replicas; provision the qualified Valkey service and workload credentials first. Both profiles require the normal identity, trust, TLS and pairing setup. See replay profiles.

A minimal Pod fragment is:

apiVersion: v1
kind: Pod
metadata:
  name: paired-agent
spec:
  securityContext:
    runAsNonRoot: true
    fsGroup: 65532
  containers:
    - name: agent
      image: <tenant-agent-image-by-digest>
      env:
        - name: AAC_INVOKE_AUTH_SECRET_FILE
          value: /etc/aac/invoke-auth/invoke-auth.secret
      volumeMounts:
        - name: invoke-auth
          mountPath: /etc/aac/invoke-auth
          readOnly: true
    - name: aac-sidecar
      # Use the immutable image digest from the published component record.
      image: docker.io/cascadeauth/aac-sidecar@sha256:REPLACE_WITH_SELECTED_DIGEST
      args: ["-config", "/etc/aac/sidecar-config.yaml"]
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop: ["ALL"]
      ports:
        - name: aac-external
          containerPort: 9443
      volumeMounts:
        - name: sidecar-config
          mountPath: /etc/aac
          readOnly: true
        - name: invoke-auth
          mountPath: /etc/aac/invoke-auth
          readOnly: true
        - name: aac-state
          mountPath: /var/lib/aac
  volumes:
    - name: sidecar-config
      secret:
        secretName: aac-sidecar-config
        defaultMode: 0400
    - name: invoke-auth
      secret:
        secretName: aac-invoke-auth
        defaultMode: 0400
    - name: aac-state
      persistentVolumeClaim:
        claimName: aac-sidecar-state

Set your tenant's TLS, trust, replay, signer, resource and ingress settings before deploying. Download sidecar-config.template.yaml, fill every required path and identifier, and verify replay, trust and an authenticated workflow before admitting traffic.

Plain Docker may use --network=container:<agent-container> to share the agent's loopback namespace. Ordinary Docker Compose containers have separate loopback interfaces; widening the sidecar loopback bind is a development-only posture and requires dev_mode: true, so it is not the production recipe.

2. Verify process identity and readiness

docker exec aac-sidecar /aac-sidecar -version
curl --fail --silent http://127.0.0.1:8080/healthz
curl --fail --silent http://127.0.0.1:8080/readyz

Run the two HTTP checks from the shared Pod/network namespace. Do not expose the loopback port. Liveness is not readiness: keep admission closed unless /readyz confirms replay-authority readiness, then independently verify trust material and an authenticated workflow.

Companion developer tools

Install the AAC CLI on the operator's machine in a dedicated virtual environment:

python3 -m venv .aac-tools
. .aac-tools/bin/activate
python -m pip install --upgrade aac-cli

aac --version
aac profile --help
aac tenant register --help
aac tenant api-key --help
aac trust-anchor --help

If this host will operate the tenant's publisher using Python, install it here. Skip this wheel install if you use its container deployment or an existing tenant-operated publisher:

python -m pip install --upgrade aac-trust-anchor-publisher
aac-trust-anchor-publisher --help

Install the following in the Python agent's environment for the protocol reference's optional FastAPI example or your own FastAPI/Starlette integration. It can use the same environment for the local example:

python -m pip install --upgrade 'aac-invoke-auth[fastapi]'
python -m pip show aac-invoke-auth

aac-invoke-auth is a library, not a separate daemon. Its base package supplies framework-independent Python helpers; another Python framework can use those without the [fastapi] extra. Other workload stacks must provide equivalent pairing authentication before trusting sidecar requests. Installing this Python package is conditional; authenticating the paired calls is not.

For deployments with separate ingest and public trust hosts, set AAC_TAP_SPIFFE_BUNDLE_READ_URL to the full public SPIFFE bundle URL supplied for the trust domain. For stage that is https://trust.stage.cascadeauth.dev/.well-known/spiffe-bundle/<trust-domain>. Signed uploads continue to use the supplied API-host ingest URL.

Create a local stage profile after installing the CLI:

aac profile create stage \
  --admin-url https://api.stage.cascadeauth.dev \
  --data-plane-url https://api.stage.cascadeauth.dev

This writes local profile settings only; it does not register a tenant. If a profile named stage already exists, inspect it before changing it. Continue with developer self-service below, or use your existing enterprise tenant and approved credential process. Never paste API keys, recovery keys, signing keys, pairing secrets, private payloads, or complete configuration into support messages.

Register your developer tenant

Prefer aac init for supported tenant registration and agent setup. It prepares the tenant-admin key, assigned domain, workload, certificates and configuration in CLI-managed directories. Start with the CLI user guide for shared GitHub, shared Google and enterprise Microsoft Entra ID onboarding; the enterprise first connection and recovery verifier are prerequisites to its normal init flow. The complete command reference describes the latest supported released CLI.

Save the guide's agent YAML for your workload, then run its aac init command. Generated CA/leaf material is development-only. The CLI also validates supplied certificates and keys from your issuer; that choice alone does not qualify a production deployment. Use the public reservation demo for an executed two-tenant example with no user-written inline Python.

Optional advanced manual registration

The following lower-level route is for operators who deliberately manage the material paths themselves; it is not required by the guided setup or demo. set -euo pipefail is Bash error handling. Here it makes the private-key existence guard stop execution rather than overwrite an existing key.

Choose a new, unbound profile for each tenant. The following example uses the stage profile created above and GitHub sign-in; use --idp google for Google. Your verified identity becomes the first tenant administrator.

Generate a tenant-admin key in a new private directory. The publisher needs this key to sign ingest requests; it is distinct from the workload's root key. Do not rerun key generation over an existing key.

set -euo pipefail
export AAC_PROFILE=stage
export AAC_MATERIAL_DIR="$HOME/aac-material/$AAC_PROFILE"
umask 077
mkdir -p "$AAC_MATERIAL_DIR"
test ! -e "$AAC_MATERIAL_DIR/tenant-admin.pem"
openssl genpkey -algorithm ed25519 -out "$AAC_MATERIAL_DIR/tenant-admin.pem"
openssl pkey -in "$AAC_MATERIAL_DIR/tenant-admin.pem" \
  -pubout -out "$AAC_MATERIAL_DIR/tenant-admin.pub.pem"

aac tenant register --profile "$AAC_PROFILE" \
  --display-name 'YOUR TEAM OR PROJECT' --contact 'YOUR EMAIL' \
  --tenant-admin-pubkey-file "$AAC_MATERIAL_DIR/tenant-admin.pub.pem" \
  --idp github --output table

aac sso login --profile "$AAC_PROFILE"
aac sso whoami --profile "$AAC_PROFILE" --output table
AAC_TENANT_ID=$(aac profile show "$AAC_PROFILE" --field tenant-id)
export AAC_TENANT_ID
aac tenant describe --profile "$AAC_PROFILE" --output table

Follow the CLI's browser/device instructions. AAC assigns the tnt-<uuid> identifier; there is no user-chosen --tenant-id on registration. The CLI shows the API-key value once, stores it with mode 0600 under ~/.aac/credentials/<tenant-id>, and binds the profile. Save a protected copy in your secret manager. A bound profile refuses a second registration; create a different profile for a different tenant. If a registration response is interrupted, use the same profile and follow the CLI's resume instruction; do not create a competing registration to recover the response.

Bind your trust domain and register the workload

AAC assigns your tenant a trust domain, so you need no DNS name and no TXT record. Assigning it and registering a workload are separate operations; run them in this order:

AAC_TRUST_DOMAIN=$(aac tenant assign-hosted-domain --profile "$AAC_PROFILE" --field trust-domain)
export AAC_TRUST_DOMAIN
echo "$AAC_TRUST_DOMAIN"
aac tenant list-trust-domains --profile "$AAC_PROFILE" --output table
aac tenant add-workload --profile "$AAC_PROFILE" \
  --spiffe-id "spiffe://${AAC_TRUST_DOMAIN}/demo/agent" --display-name 'Synthetic demo agent'
aac tenant list-workloads --profile "$AAC_PROFILE" --output table

The assigned domain is built from your tenant identifier and looks like tnt-<uuid>.tenants.stage.cascadeauth.dev. Registration itself usually assigns it already and prints it, so the command above normally reads that same domain back; it is idempotent and safe to rerun. If a previous hosted binding was revoked, reactivate it with aac tenant reactivate-hosted-domain before registering a workload. Workload registration records the identity only; your tenant's PKI/SPIFFE system still issues its certificate and matching private key.

Use a DNS domain of your own instead

Optional, and only if your tenant must be identified by its own name, such as agents.example.com. It needs a DNS name you control and a published TXT record. Use your actual canonical lowercase DNS name:

export AAC_TRUST_DOMAIN=agents.example.com
aac tenant issue-domain-challenge --profile "$AAC_PROFILE" --domain "$AAC_TRUST_DOMAIN"

Publish the exact TXT name/value returned by that command at your DNS provider, wait for propagation, then bind it and register the workload under it. Run these lines rather than the block above, whose first line would replace AAC_TRUST_DOMAIN with the assigned domain:

aac tenant verify-domain --profile "$AAC_PROFILE" --domain "$AAC_TRUST_DOMAIN"
aac tenant bind-trust-domain --profile "$AAC_PROFILE" --trust-domain "$AAC_TRUST_DOMAIN"
aac tenant add-workload --profile "$AAC_PROFILE" \
  --spiffe-id "spiffe://${AAC_TRUST_DOMAIN}/demo/agent" --display-name 'Synthetic demo agent'
aac tenant list-workloads --profile "$AAC_PROFILE" --output table

Issuing a new challenge replaces the prior challenge; retain the current TXT record while the binding needs domain evidence. Existing or previously revoked bindings follow their lifecycle rules, so an ownership/history rejection needs operator resolution. Do not retry it with a bootstrap token or invent a tenant ID.

Keep credential roles separate

Every key, certificate and shared secret this guide creates is described in one place: Keys and certificates. That page says what each item proves, where it lives, who gets a copy and how long it lasts, and it covers both ways an agent gets its certificates — a development authority created for you, or your own issuer's. Read it before you generate anything.

Two rules the rest of this section depends on. Use Ed25519 or P-256 keys and currently valid, matching SPIFFE certificates; your CA bundle must validate the workload and terminal certificates for the bound domain. And keep pairing secrets distinct from every signing key: for a managed deployment, generate one in a new protected location with umask 077; openssl rand -hex 32 > invoke-auth.secret, then install it privately in both processes. The development recipe below generates its own.

Optional development integration fixture

The manual PKI fixture is only for the optional protocol example. Normal setup uses the CLI.

Publish public trust material

For a file-backed demonstration, create a root-signing key and public half in your private onboarding directory, then copy only the public half into the publisher's root-key directory:

export AAC_ROOT_KEY_ID=demo-root-v1
test ! -e "$AAC_MATERIAL_DIR/${AAC_ROOT_KEY_ID}.pem"
openssl genpkey -algorithm ed25519 -out "$AAC_MATERIAL_DIR/${AAC_ROOT_KEY_ID}.pem"
openssl pkey -in "$AAC_MATERIAL_DIR/${AAC_ROOT_KEY_ID}.pem" \
  -pubout -out "$AAC_MATERIAL_DIR/${AAC_ROOT_KEY_ID}.pub.pem"
mkdir -p "$AAC_MATERIAL_DIR/root-public"
cp "$AAC_MATERIAL_DIR/${AAC_ROOT_KEY_ID}.pub.pem" "$AAC_MATERIAL_DIR/root-public/"

export AAC_TAP_TENANT_ID="$AAC_TENANT_ID"
export AAC_TAP_ADMIN_KEY_FILE="$AAC_MATERIAL_DIR/tenant-admin.pem"
export AAC_TAP_ROOT_KEYS_DIR="$AAC_MATERIAL_DIR/root-public"
export AAC_TAP_ROOT_KEYS_INGEST_URL=https://api.stage.cascadeauth.dev/v1/root-keys/ingest
export AAC_TAP_POLL_INTERVAL_SECONDS=60
aac-trust-anchor-publisher

This runs the installed publisher in the foreground. Run one active writer for your tenant's root keys and one per active domain binding, and never two competing writers for the same root set or binding, to avoid uncoordinated ingest sequence changes. The public filename stem is the root key ID; configure tenant.key_id: demo-root-v1 and mount the private half only into the workload. Rotation gets a new key ID. Successfully ingested public material remains stored after the publisher exits; the serving cache TTL is not a key-expiry timer. Run the publisher again when keys or CA bundles change, following their rotation/revocation lifecycle.

To enable SPIFFE-bundle publication, stop the foreground publisher, place public CA certificates only in a separate directory using filenames <anchor_id>.ca.pem, and add these settings in the same shell before restarting that one publisher. The trust-domain binding must already be active.

export AAC_TAP_SPIFFE_BUNDLE_DIR="$AAC_MATERIAL_DIR/spiffe-ca-public"
# Populate this directory with your issuer's public <anchor_id>.ca.pem files.
export AAC_TAP_SPIFFE_TRUST_DOMAIN="$AAC_TRUST_DOMAIN"
export AAC_TAP_SPIFFE_BUNDLE_INGEST_URL=https://api.stage.cascadeauth.dev/v1/spiffe-bundle/ingest
tap_trust_url=https://trust.stage.cascadeauth.dev
export AAC_TAP_SPIFFE_BUNDLE_READ_URL="${tap_trust_url}/.well-known/spiffe-bundle/${AAC_TRUST_DOMAIN}"
aac-trust-anchor-publisher

For the optional integration reference's development PKI, set AAC_TAP_SPIFFE_BUNDLE_DIR to "$AAC_DEMO_DIR/publish-ca" instead. That directory contains only the public development CA certificate. Never point the publisher at pki/.

Check the public bundle at the configured read URL and root keys at https://trust.stage.cascadeauth.dev/.well-known/aac-root-keys/<tenant-id>. Do not send a CA private key or workload private key to the publisher.

In another terminal, confirm ingest and public key visibility:

aac trust-anchor ingest-history --profile "$AAC_PROFILE" --output table
aac trust-anchor list --profile "$AAC_PROFILE" --output table

Only proceed to the runnable example when the expected root key and SPIFFE CA bundle are visible, your workload projection is registered, and all configured key/certificate pairs are valid. A successful registration by itself does not complete this preparation.