Skip to content
Registry StackDocsDevelopment (unreleased)

Build and deploy an Evidence Gateway project

For the assertion provider and operator

View as Markdown

Complete Prove an Evidence Gateway project and its review before starting this tutorial. You will turn that editable project into one reviewed production candidate, then bind its secrets and runtime paths on the target host. This tutorial uses released evidence and evidencectl binaries. It does not copy .evidence/dev, generate a key, register a caller, or deploy a service for you.

Outcome
A reviewed Evidence Gateway candidate, one target-host validation ceremony, and one independently verified synthetic assertion.
Time
About 45 minutes after the authoring project is reviewed
Level
Production handoff with synthetic deployment data
Prerequisites
A released Evidence Gateway toolsetA reviewed editable Evidence Gateway projectA provisioned Vault or OpenBao Transit keyAn operator with target-host and secret-manager accessA shell with curl and an editor

You need a released Evidence Gateway toolset on PATH, an editable project that runs locally, and an operator who can provision owner-only secrets and a private listener on the target host. Configure a compatible OpenID Connect (OIDC) issuer for Evidence Gateway access tokens.

A production or evidence-grade target also needs a Vault or OpenBao Transit key before you build, not after. The target’s governance.yaml names one governed public JWK, and for these profiles that JWK is the public half of a non-exportable Transit key, so the key has to exist before the target is complete. Provision it with Configure Transit signing for Evidence Gateway. The build itself never contacts Transit; the workload-local proxy is what the target-host ceremony and the running service reach.

Confirm the released binaries before changing the project:

Terminal window
curl -fsSL "https://github.com/registrystack/registry-stack/releases/download/<released-tag>/evidencectl-<released-tag>-install.sh" | bash
evidence --version
evidencectl --version

These binaries produce the candidate you hand to an operator, so verify the downloaded assets against the signed SHA256SUMS for that tag before you install them, following OpenSSF and release trust. To read the installer before it runs, replace | bash with | less.

Keep all source responses, credentials, tokens, and local .evidence/ state outside the candidate.

Create a synthetic fixture for every question under fixtures/, then add one governance block and stable concept identifiers to each question. The metadata binds the reviewed requirement, frameworks, Evidence Type, validity, observation timezone, fixture, and disclosure families.

answers:
- concept: is_adult
id: urn:example:concepts:is-adult
type: boolean
governance:
requirement: urn:example:requirements:adult-status:v1
kind: criterion
referenceFrameworks:
- urn:example:frameworks:adult-eligibility:v1
evidenceType: urn:example:evidence-types:adult-status:v1
validitySeconds: 300
observationTimezone: UTC
fixtures: fixtures/adult-status.yaml
disclosureFamilies:
- urn:example:disclosure-families:adult-status

Do not invent these URIs during the build. Review them with the institution that owns the requirement and disclosure decision.

Keep reviewed Evidence semantics and complete environment bindings in one protected branch of the deployment repository:

shared/
evidence-project/
environments/
local/
evidence/{governance.yaml,runtime.yaml,public-keys/}
staging/
evidence/{governance.yaml,runtime.yaml,public-keys/}
transit/{proxy-configs/,policies/}
production/
evidence/{governance.yaml,runtime.yaml,public-keys/}
transit/{proxy-configs/,policies/}

Every environment target is complete. Do not use overlays, environment branches, symlinks, or runtime substitutions. Git contains public keys and nonsecret provider configuration, but never private JWKs, HMAC keys, provider tokens, auto-auth credentials, access tokens, live responses, or real identifiers.

governance.yaml provides bundle-owned production values. It contains version 1, assuranceProfile: production, service and issuer, authentication, audit, subject binding, rate limits, signing, optional response formats, and authority profiles. Secret references use secret:file/<name> only. Do not place secret values or absolute secret paths in this file.

runtime.yaml is the ordinary Evidence Gateway runtime document. It binds the final absolute candidate bundle path, private listener, secret root, audit path, and optional private certificate authority files. The build copies its bytes unchanged, so the target host remains the authority for path, ownership, permission, secret, and trust validation. Set bundleDirectory to <new-candidate-directory>/bundle, and keep the listener on a numeric loopback or private address. listener.networkExposure declares which addresses that means. It defaults to private-address, which accepts loopback, RFC 1918 private IPv4, or RFC 4193 unique-local IPv6. A container listener that must bind the wildcard 0.0.0.0 inside an isolated network sets networkExposure: container-private instead, as runtime.docker.yaml in the Registry Stack repository does; that value permits the wildcard bind for an operator-confined container network and authorizes no public or direct-TLS serving. The runtime cannot override the governed service, authentication, authority, source, disclosure, or signing fields.

public-keys/ contains the exact active and published service JWKs named by governance.yaml. Production and evidence-grade targets bind the matching non-exportable provider key through the Transit signer in runtime.yaml. Use Configure Transit signing for Evidence Gateway before building the first strict candidate.

Choose a new output path. The command refuses an existing path and does not modify the editable project:

Terminal window
evidencectl package "<deployment-repository>/shared/evidence-project" \
--target "<deployment-repository>/environments/production/evidence" \
--output "<new-candidate-directory>"

The candidate contains the runtime document and closed bundle:

<new-candidate-directory>/
runtime.yaml
bundle/
evidence.yaml
adapters/
derivations/
schemas/
fixtures/
public-keys/

The build validates the generated bundle through the real evidence binary and every referenced fixture before it publishes the candidate. It asks that binary for its version first and refuses one that is not this evidencectl’s, so the candidate is always the one the matching runtime shaped: evidencectl package takes the binary from EVIDENCE_BIN or the first evidence on PATH, and evidencectl test accepts --evidence-bin for the same handshake. A bundle that declares a publication also has to render the description that advertises it, and a build whose description comes back empty is refused rather than written, because the candidate would carry no catalog.jsonld. The build does not contact an identity provider or a source endpoint. It validates governed public-key semantics without contacting Transit or generating an unrelated signing key. The target-host check performs the provider self-test. Record the printed bundle revision with the approved candidate path.

Transfer the exact candidate. The operator provisions the audit HMAC key, subject-binding HMAC key, and source credentials beneath the runtime secret root. The workload-local Transit proxy holds the provider token and auto-auth state; Evidence Gateway receives only access to the configured Unix socket. Its non-exportable signing key remains in Transit. Make the candidate runtime and bundle non-writable to the Evidence Gateway service identity, for example chmod -R a-w bundle && chmod 444 runtime.yaml. evidencectl test below runs evidence check first, which refuses a writable runtime file, bundle artifact, or CA bundle file, a secret root reachable by group or other, or a writable extract, before it evaluates any fixture.

Start the workload-local Transit proxy. Run the grouped offline ceremony once after the candidate, runtime bindings, trust files, secrets, and proxy socket are in place:

Terminal window
evidencectl doctor --runtime-config "<candidate>/runtime.yaml"
evidencectl test "<candidate>"

Start Evidence Gateway only after both commands pass:

Terminal window
evidence --runtime "<candidate>/runtime.yaml" serve

Route traffic through operator-controlled TLS only after GET /ready succeeds. The listener stays private. Each requirement’s own configuration revision remains the deployed assertion configurationRevision.

Create an owner-only Curl configuration. Put its Authorization: Bearer header in the file through your approved secret-management path, not on a command line or in shell history. Do not commit it.

Terminal window
umask 077
install -m 600 /dev/null "<owner-only-curl-config>"

The file contains this Curl configuration directive, with the actual token supplied outside the command line:

header = "Authorization: Bearer <access-token>"

Send one request for a synthetic deployment test record:

Terminal window
curl --fail --silent --show-error \
--config "<owner-only-curl-config>" \
--header 'Content-Type: application/json' \
--data-binary "@<synthetic-request.json>" \
"https://<evidence-host>/v1/evidence" \
--output "<assertion.jws.json>"

Verify the retained response with an independently prepared production verification policy and trusted public keys. Then drain traffic and stop Evidence Gateway before verifying the audit chain: a running writer holds the active segment, so verify-audit would prove only sealed history and skip the record this request just wrote.

Terminal window
evidence verify \
--jws "<assertion.jws.json>" \
--jwks "<trusted-evidence-jwks.json>" \
--policy "<production-verification-policy.json>"
evidence --runtime "<candidate>/runtime.yaml" verify-audit

The signing policy and secret references are governed bundle content. Rotate a key by publishing a new public JWK whose kid is its RFC 7638 thumbprint, not by replacing a key under an unchanged identifier.

You have a retained signed response that verifies under the independent production policy, an audit chain that passes verification, and a recorded bundle revision for the exact candidate serving the synthetic request.

Remove the temporary Curl configuration when the deployment check is complete. Retain the signed response and audit-verification record according to the operator’s evidence-retention procedure.

Terminal window
rm -f "<owner-only-curl-config>"