Skip to Content
InstallationCredentials and secrets

Credentials and secrets

TruStacks never holds production credentials. The Runner needs a tightly scoped set of secrets to read your application repositories, talk to your LLM provider, and open pull requests against your platform repository. Everything else (cluster credentials, cloud keys, production secrets) stays in your environment and never crosses the data-plane boundary.

This page covers the four secrets the Runner needs to do useful work, how each one gets provisioned, and the trust boundary around them.

The four Runner secrets

SecretWhat it isHow it is provisioned
LLM provider keyAnthropic or OpenRouter API keyUI (Settings → LLM Provider) or .env + make secrets for fresh-cluster bootstrap
Per-repo SSH deploy keysed25519 keypair per connected repoGenerated by the Connect-a-repo wizard or by scripts/setup_runner_access.py in the local-dev flow
Git API tokenGitea token (local) or GitHub App installation token (real)Connect-a-repo wizard (auto-provisioned) or scripts/setup_runner_access.py (local-dev)
Signed policy bundleThe signed constitution OCI artifactPushed to your Zot (local) or the TruStacks registry (production) by make policy-bundle or the standard release pipeline

The Runner’s init container materializes each Secret at a stable path before the main container starts. The agent runtime never sees the raw Secret contents; it reads only the materialized paths.

LLM provider key

The Runner talks to Claude through one of two providers. Both providers route to Claude Sonnet 4.5+; provider choice is a billing relationship, not a model selection (see the architecture reference).

Where the Secret lives

  • K8s Secret · trustacks-runner-llm in the trustacks-system namespace.
  • Fields · ANTHROPIC_API_KEY, OPENROUTER_API_KEY, LLM_PROVIDER, OPENROUTER_MODEL. All entries are optional: true so the chart installs cleanly into a fresh cluster before you have saved a config.

How it gets provisioned

Two paths, depending on whether the Control Plane is up.

UI (preferred) · open http://ui.localtest.me:8080/#/settings/llm, paste your key, confirm, save. The Control Plane runs a live ping at the provider, rewrites the Secret, and rolls the Runner.

.env + make secrets (fresh-cluster bootstrap) · used before the Control Plane has booted. Set ANTHROPIC_API_KEY=<key> in .env, run make secrets. The Control Plane lifts the legacy Secret into the new flow on first boot.

Rotating the key

UI flow: paste the new key in Settings → LLM Provider and save. The Runner pod is rolled automatically.

.env flow: update ANTHROPIC_API_KEY and run make secrets.

Per-repo SSH deploy keys

The Runner clones connected repositories over SSH using ed25519 deploy keys. Each repo gets its own keypair. The Runner discovers keys dynamically at pod startup via labeled K8s Secrets, so adding a new repository never requires a Helm-values change.

Where the Secrets live

  • K8s Secret · <repo>-deploy-key in the trustacks-system namespace.
  • Labels · trustacks.io/role=git-deploy-key, trustacks.io/repo=<repo-name>.
  • Materialized path · /var/run/secrets/git/<repo>/.

How they get provisioned

Two paths.

Connect-a-repo wizard (Gitea path) · the UI generates an ed25519 keypair in-memory, registers the public key with Gitea, creates the labeled K8s Secret, and rolls the Runner. The private key never touches disk outside of the K8s Secret.

scripts/setup_runner_access.py (local-dev bootstrap) · provisions read-only keys for every service sample plus overlay, and a read-write key for platform. Runs as part of make seed.

Permission model

  • Read-only on application repos and on the overlay repo. Agents read code for analysis; they do not write to your application code.
  • Pull-request write only on platform and overlay repos. Agents push feature branches and open PRs. They cannot push to main. They cannot delete branches. They cannot force-push.

Git API token (Gitea or GitHub App)

For opening pull requests, the Runner needs an API credential. Two shapes, depending on provider.

Gitea (local-dev)

A Gitea API token stored as the K8s Secret gitea-api-token in trustacks-system. Provisioned automatically by scripts/setup_runner_access.py during make seed. The DevOps Engineer agent uses it to open PRs against the platform repo.

GitHub App (production-shaped)

The Runner clones via HTTPS using a GitHub App installation token (per ADR-0018 in trustacks-mvp). This requires a one-time operator step.

  1. Register the TruStacks GitHub App on github.com.
  2. Apply the trustacks-github-app Secret in trustacks-system with three fields: app-id, private-key.pem, webhook-secret.
  3. Set the CP environment variable TRUSTACKS_CP_GITHUB_APP_SLUG to your App’s URL slug.

Until that operator setup completes, the Connect-a-repo wizard’s GitHub branch shows an amber “ask your admin” gate. That is the correct behavior.

After the App is registered, each customer installation auto-provisions its own installation ID through the wizard. The installation token is short-lived; the Runner re-mints it from the App private key on every clone.

Signed policy bundle

The constitution is shipped as a Cosign-signed OCI artifact. The Runner’s init container pulls the bundle and verifies the signature before extracting it; an unsigned or tampered bundle never reaches the OPA evaluator.

Where the bundle lives

  • Local-dev · pushed by make policy-bundle to your in-cluster Zot at zot.trustacks-system.svc.cluster.local:5000/trustacks/policy/constitution:dev.
  • Production · pulled from the TruStacks registry by the Runner on startup; the bundle version is pinned per Runner release (see the constitution reference).

How signing works

make policy-bundle runs three steps:

  1. opa build -b policy/constitution builds the bundle.
  2. oras push uploads it to Zot.
  3. cosign sign signs it using the project-local keypair at .policy-keys/ (auto-generated by make policy-keys on first run).

The public key is applied as the policy-pub-key ConfigMap (via make policy-pub-deploy). The Runner’s init container reads the ConfigMap and runs cosign verify --key /policy-pub/cosign.pub against each pulled bundle.

Fallback behavior

If the bundle pull fails (Zot unreachable, signature does not verify), the Runner falls back to the image-baked copy under /etc/trustacks/policy/. The cluster stays functional during a Zot wipe, but the UI’s /rules page comes up empty until the bundle is republished, since the rule inventory ships inside the bundle.

To run with an unsigned bundle (CI without keys, hotfix), set policyBundle.verify=false in the chart values. Do not do this in production.

Optional integrations

These are connected through the Environment Profile rather than as required Runner secrets. They are needed only if the corresponding agent wants them.

SecretUsed byHow declared
Container registry pull credentialsImage scanning, build provenanceEnvironment Profile entry + K8s Secret
SAST or scanner API tokens (Snyk, SonarQube, etc.)Baseline Security agentEnvironment Profile entry + K8s Secret
Observability tokens (Datadog, New Relic, etc.)SRE Specialist, Cluster OperatorEnvironment Profile entry + MCP server config
Ticketing tokens (Jira, ServiceNow, Linear)Coordinator (change records, links)Environment Profile entry + MCP server config

See the Environment Profile section of architecture for the full category list.

What the Runner does not hold

Listed explicitly because it is the trust property the platform rests on.

  • No cluster admin credentials. The Runner has a service account scoped to its own namespace. It cannot reach the API server outside that scope.
  • No cloud provider keys. No AWS, no GCP, no Azure. Cloud-resource changes happen through the GitOps controller after a human merges the PR.
  • No production database credentials. Secrets bound to running services are referenced by name in the manifests the agent emits; the agent never resolves them.
  • No write access to your main branch. Every change is a PR against a feature branch. Every PR requires a human approval to merge.

Where to go next

Last updated on