Skip to content

3. Install and activate the Auth adapter

Auth sits in front of a private API and verifies every request locally. Control publishes a signed trust and policy bundle for this site’s registered distribution target. This chapter builds one EKS, arm64, single-replica example and checks both its private upstream and its Control-signed activation. A Ready pod by itself does not establish either property.

Previous: Target identity and trust. Next: Create or confirm the client and grant.

This example requires reviewed access to an EKS context with Linux arm64 EC2 nodes, a NetworkPolicy-enforcing CNI, AWS Load Balancer Controller with the alb IngressClass, an encrypted retained gp3 StorageClass backed by EBS CSI, an ACM certificate for the exact Auth hostname, and write access to its authoritative Route 53 zone. The VPC CNI’s network policy feature is not enabled by default; follow the AWS configuration guide and verify enforcement before relying on this example. The ALB controller and EBS CSI StorageClass must already be installed. Review the cost of a dedicated public ALB and retained PVC. For another architecture, CNI, ingress controller or storage provider, adapt and test the manifests for that platform before deployment.

Set these values from the same selected Auth Site in Control and the reviewed cluster; never copy an ID, hostname, key or certificate from another site.

Input Source
Environment, fleet, site, distribution target, API, audience, route and trust domain Selected Control site, protected surface, target registration and bootstrap handoff.
Control API URL and release descriptor The selected site’s ready Bootstrap handoff, not the browser UI URL.
Auth hostname The exact hostname whose DNS ownership was verified in Chapter 1.
Target private key and Control signer public key Chapter 2’s owner-held file and verified site-scoped public-key download.
Kubernetes context, encrypted retained class, certificate ARN and DNS zone Your cluster and DNS inventory.
Control egress CIDR The reviewed reachable Control API destination for your CNI; do not use the example CIDR.

The three downloadable manifests are reviewed templates, not live-site values:

Save them in an owner-controlled directory and inspect every line before applying:

Terminal window
umask 077
curl -fsSLo echo.eks.yaml https://docs.auth.trustplane.dev/examples/managed-auth-site/echo.eks.template.yaml
curl -fsSLo auth.values.yaml https://docs.auth.trustplane.dev/examples/managed-auth-site/values.eks.template.yaml
curl -fsSLo ingress.eks.yaml https://docs.auth.trustplane.dev/examples/managed-auth-site/ingress.eks.template.yaml

Replace every REPLACE_ value, example.invalid hostname, documentation-only CIDR, certificate ARN, namespace and Service name with reviewed values. Set controlDistribution.adapterVersion: v0.2.5 explicitly: chart 0.2.2 otherwise defaults the agent’s adapter version to 0.2.2 even with image v0.2.5. Keep both image references on the approved digest. The render-only values example is a schema aid; it is not a deployment recipe.

Before creating anything, inspect the selected context and collisions:

Terminal window
kubectl config current-context
kubectl get nodes -o wide
kubectl get ingressclass alb
kubectl get storageclass
kubectl get namespace trustplane-auth-tutorial
kubectl get ingress -A
helm list --namespace trustplane-auth-tutorial

The get namespace command should report NotFound for a new disposable namespace. Review the selected class’s provisioner, encryption and reclaim policy; require Retain for this exercise. Confirm that the reviewed CNI enforces NetworkPolicy, and that no existing Ingress or public Service routes directly to the echo. Stop on name or ownership collisions.

The inspected Helm chart is 0.2.2 (package SHA-256 b40987d453af1bbc979011bfce0184a45e8a9e568a26cd4ebe5bdb47a1c7f33b); the inspected adapter image is v0.2.5@sha256:7851520f47ca5d612fc8095dbb6a8861c2072f13b2dbc0b14fc7d78aec6f8d44. Recheck the release ledger and selected-site descriptor before use.

Terminal window
helm pull oci://ghcr.io/trustplane-dev/charts/trustplane-auth --version 0.2.2
printf '%s %s\n' b40987d453af1bbc979011bfce0184a45e8a9e568a26cd4ebe5bdb47a1c7f33b trustplane-auth-0.2.2.tgz | shasum -a 256 -c -
helm template tutorial-auth ./trustplane-auth-0.2.2.tgz \
--namespace trustplane-auth-tutorial --values ./auth.values.yaml > ./auth.rendered.yaml

Check the rendered image digest, --adapter-version=v0.2.5, exact target binding, Secret names and data keys, private upstream, retained PVC, durable replay and NetworkPolicy peers. Review the entire render before applying it. The chart creates two internal Services and no public TLS ingress. In particular, the signer Secret contains only Control’s public key; the target Secret contains the Auth distribution private key. Never place either private key in Helm values, the render, terminal output or documentation.

After the context, template and name review, create the new namespace and private echo. The echo Service must remain ClusterIP; its NetworkPolicy allows ingress only from the adapter pod labels and denies echo egress.

Terminal window
kubectl create namespace trustplane-auth-tutorial
kubectl -n trustplane-auth-tutorial apply -f ./echo.eks.yaml
kubectl -n trustplane-auth-tutorial rollout status deployment/tutorial-echo --timeout=5m
kubectl -n trustplane-auth-tutorial get service tutorial-echo -o wide

Create the two site-specific Secrets from the Chapter 2 files. These create commands fail if either name already exists; investigate that collision instead of replacing a key.

Terminal window
kubectl -n trustplane-auth-tutorial create secret generic tutorial-auth-target-key \
--from-file=target-private-key=./target-private-key
kubectl -n trustplane-auth-tutorial create secret generic tutorial-auth-control-signer \
--from-file=control-signing-public-key=./control-signing-public-key
helm upgrade --install tutorial-auth ./trustplane-auth-0.2.2.tgz \
--namespace trustplane-auth-tutorial --values ./auth.values.yaml \
--atomic --timeout 15m
kubectl -n trustplane-auth-tutorial get statefulset,pods,pvc,service,networkpolicy

Use --kube-context or --context on every command if your shell might switch contexts; the commands above assume the reviewed current context remains selected. Require the PVC to be Bound to the retained encrypted class, the Auth StatefulSet pod healthy, and only internal Services.

Review ingress.eks.yaml after substitutions. Its exact host and /tutorial/echo path must route to the adapter Service only. Never route to tutorial-echo, add a public echo Service or attach this Ingress to a shared serving site’s ALB group. The template creates a separate internet-facing ALB and redirects HTTP to HTTPS.

Terminal window
kubectl -n trustplane-auth-tutorial apply -f ./ingress.eks.yaml
kubectl -n trustplane-auth-tutorial get ingress tutorial-auth -o wide
kubectl -n trustplane-auth-tutorial describe ingress tutorial-auth

Wait for the Ingress ADDRESS, then create a CNAME from the subdomain Auth hostname to that ALB DNS name in the authoritative Route 53 zone. Review the exact hostname, zone, target and TTL before submitting a DNS change. This CNAME is separate from Chapter 1’s TXT ownership proof; if your registrar delegates the zone elsewhere, edit the authoritative provider instead. For a Route 53 hosted zone, set these from your reviewed records and generate a change file without shell interpolation into JSON:

Terminal window
export TP_HOST='auth-tutorial.example.invalid'
export TP_ALB_DNS='<Ingress ADDRESS without a trailing dot>'
export TP_ZONE_ID='<authoritative Route 53 hosted zone ID>'
python3 - <<'PY'
import json
import os
from pathlib import Path
host = os.environ["TP_HOST"].rstrip(".")
target = os.environ["TP_ALB_DNS"].rstrip(".")
if host.endswith("example.invalid") or not host or not target:
raise SystemExit("Replace the example hostname and ALB address")
change = {"Comment": "Dedicated tutorial Auth edge",
"Changes": [{"Action": "CREATE", "ResourceRecordSet": {
"Name": host + ".", "Type": "CNAME", "TTL": 300,
"ResourceRecords": [{"Value": target + "."}]}}]}
Path("auth-dns-change.json").write_text(json.dumps(change, indent=2) + "\n")
PY
cat auth-dns-change.json
aws route53 change-resource-record-sets \
--hosted-zone-id "$TP_ZONE_ID" --change-batch file://auth-dns-change.json

Only submit the last command after comparing the generated record to the selected site’s hostname and the dedicated Ingress ADDRESS. CREATE fails if a record with that name and type already exists; inspect it instead of overwriting another tenant’s DNS. For a DNS provider other than Route 53, use its equivalent CNAME workflow. Confirm both DNS and the certificate hostname:

Terminal window
dig +short CNAME "$TP_HOST"
curl -sS -o /dev/null -w 'HTTP %{http_code}; TLS verify %{ssl_verify_result}\n' \
"https://$TP_HOST/tutorial/echo"

The sample hostname above is deliberately invalid: substitute the verified Auth hostname. An unsigned GET /tutorial/echo returned HTTP 401 with valid TLS in the isolated exercise. That checks edge reachability and fail-closed behavior, not client authorization.

Verify the private boundary and signed activation

Section titled “Verify the private boundary and signed activation”

Use temporary probes in the same namespace. The allowed pod carries the adapter labels selected by the echo NetworkPolicy; the second does not. The first must receive the echo response. The second must fail with a connection timeout, not a DNS or image-pull error. --rm removes the temporary pods when each command exits:

Terminal window
kubectl -n trustplane-auth-tutorial run echo-allowed-probe --rm -i --restart=Never \
--labels=app.kubernetes.io/instance=tutorial-auth,app.kubernetes.io/component=adapter \
--image=docker.io/library/busybox:1.36.1@sha256:bd44eb136a95dcc8dc58995e43abc40a413f2e8e3d4a2aae6bccbe94686acb05 \
--command -- wget -qO- -T 3 http://tutorial-echo:8080/tutorial/echo
kubectl -n trustplane-auth-tutorial run echo-denied-probe --rm -i --restart=Never \
--labels=app=tutorial-probe \
--image=docker.io/library/busybox:1.36.1@sha256:bd44eb136a95dcc8dc58995e43abc40a413f2e8e3d4a2aae6bccbe94686acb05 \
--command -- wget -qO- -T 3 http://tutorial-echo:8080/tutorial/echo
kubectl -n trustplane-auth-tutorial get service tutorial-echo -o wide
kubectl get ingress,service -A

Confirm the Service stays ClusterIP, the reviewed CNI enforces policy, and no Ingress, Gateway or public Service exposes the echo. A timeout alone is not proof of that boundary.

In Control Auth Site Detail → Readiness, refresh the site and compare the promoted target configuration, bundle/trust/policy digests and instance acknowledgement with the agent’s restricted local status. Require the loaded signed generation, active instance quorum, a future offline lease and the bound retained PVC. If you used pre-publication, the package already existed before the agent started; now require its ACK. In the recorded empty-policy-first path, the waiting agent advertised paired capability and Control then published/promoted a signed deny-all generation. Compare the site’s bootstrap public key byte-for-byte with the pinned startup key. An old browser view may show a stale lease; refresh before comparing against the agent’s current lease.

Then follow Chapter 4 and Chapter 5 to prove a signed, granted GET returns HTTP 200 and its JTI appears in Control Audit. Also send an unsigned request and an ungranted operation and require denial without an upstream echo response. These are separate from the network probes: they test Auth’s policy enforcement and audit trail.

Open this walkthrough on YouTube

Next: Create or confirm the caller and narrow grant.