Skip to content

4. Create a client and narrow grant

Control decides which client may reach which declared operation. Auth enforces the resulting signed policy locally. A client record alone has no key or access. If you used Chapter 2’s tenant-admin pre-publication sequence, steps 1–4 are already complete: reuse that same client and private key, and continue with the exact-target activation check in step 5.

Previous: Install and activate Auth. Next: Send and audit a request.

Select the same environment, fleet and exact Auth Site in Client Vault. Require the declared operation from chapter 2. For the pre-publication sequence, the target need not be active yet; its registered public key and the verified site must be in place. The current console order is:

  1. Add client: enter a display name. Review its generated site-scoped Client ID and Subject. This creates an identity only.

  2. Add client policy: select the verified protected resource and the narrow Read only or Read & write access mode. Read the persisted policy reconciliation, resolved operations, materialized grants and asynchronous publication status. Use Protected Surfaces if no eligible operation appears.

  3. Generate a persistent client key locally. In a private working directory with the checksum-verified released CLI, run this once. It refuses either existing output file, validates the CLI’s Ed25519 fields, creates both files with owner-only permissions, and prints only the public key:

    Terminal window
    umask 077
    python3 - <<'PY'
    import base64
    import json
    import os
    import re
    import subprocess
    from pathlib import Path
    pair = Path("client-keypair.json")
    private = Path("client-private-key")
    if pair.exists() or private.exists() or pair.is_symlink() or private.is_symlink():
    raise SystemExit("Key files already exist; reuse them. No key was generated.")
    raw = subprocess.run(["./trustplane", "gen-key"], capture_output=True,
    text=True, check=True).stdout
    key = json.loads(raw)
    def decoded(value, length):
    if not isinstance(value, str) or not re.fullmatch(r"[A-Za-z0-9_-]+", value):
    raise SystemExit("CLI returned an invalid key field")
    result = base64.b64decode(value + "=" * (-len(value) % 4),
    altchars=b"-_", validate=True)
    if len(result) != length:
    raise SystemExit("CLI returned an unexpected Ed25519 key length")
    return result
    if key.get("algorithm") != "Ed25519" or key.get("key_binding") != "software":
    raise SystemExit("CLI returned an unexpected key type")
    public = key["public_key_b64"]
    secret = key["private_key_b64"]
    decoded(public, 32)
    decoded(secret, 64)
    created = []
    try:
    for path, content in ((pair, raw), (private, secret + "\n")):
    fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
    created.append(path)
    with os.fdopen(fd, "w") as stream:
    stream.write(content)
    stream.flush()
    os.fsync(stream.fileno())
    except BaseException:
    for path in created:
    path.unlink()
    raise
    print("Public key to register:", public)
    PY

    The public value is base64url Ed25519; the private file is base64url and must remain 0600. client-keypair.json also contains the private key. Treat both files as workload credentials, keep them out of screenshots, logs and source control, and reuse the same private file on every request and restart. If either file exists, stop and recover that identity instead of rerunning generation. The released CLI v0.2.5 supports gen-key; use the versioned download and SHA-256 verification and check the release notes before running it.

  4. Add key to client: paste only the public value into Client Vault. Check Control’s derived Key ID, KID and public-key fingerprint. The key inherits the client-level policy and triggers propagation; it does not become usable merely because the form succeeded.

  5. After Auth starts, wait for the key-containing publication, signing profile and the exact target’s active acknowledgement. Client Vault should show an active canonical route rather than No active route or No active signing profile. Inspect the profile issuer, subject, KID, audience, route and Auth authority before making a request.

Open this walkthrough on YouTube

This client key signs requests. It is separate from the Auth Site distribution target key and the Control bundle signer. See the Client Vault reference for the current UI and propagation readbacks.

In the 14 October 2026 first-run verification, a site-scoped caller received only the GET/HEAD /tutorial/echo read grant. Control showed the key, grant, active target, and active signing profiles before the signed request. The private echo is a sample upstream, not a customer service.

Next: Run the exact signed request and find its Control Audit decision.