Bundle Reference

Complete annotated YAML schema for the keyzero bundle configuration

A bundle is a single YAML file that defines the complete keyzero server configuration. Pass it to kz server start with --bundle <path>, or validate it with kz server check --bundle <path>.

Top-Level Structure

version: "1"           # Required. Bundle schema version.
issuers: [...]         # Required. JWT issuer configurations with type and profiles.
policies: [...]        # Required. CEL authorization rules.
resources: [...]       # Required. Secret refs with glob patterns.
backends: { ... }      # Required. Secret storage backend definitions.

issuers

Each issuer defines a trusted JWT source. At least one of jwks_url or static_keys must be provided.

FieldTypeRequiredDescription
namestringyesUnique issuer name
issuer_urlstringyesExpected iss claim value
jwks_urlstringnoURL to fetch JSON Web Key Set
audiencestringnoExpected aud claim value. If omitted, audience is not validated
typestringyesIssuer type: github-actions, kubernetes, gcp, aws, or custom
mapmapnoOverride or extend the built-in claim mappings for this issuer type
static_keyslistnoInline public keys for verification

Issuer types and built-in profiles

Each issuer type has a built-in profile that maps JWT claims to standard variables automatically. You can override any mapping with the map field.

Typeorgserviceenvactionbranchactor
github-actionsrepository_ownerrepositoryenvironmentworkflowrefactor
kubernetesnamespaceserviceaccount--------
gcpproject_idemail--------
awsaccount_idarn--------
custom------------

The custom type has no default mappings. You must provide an explicit map for any variables you want populated.

static_keys entry

FieldTypeRequiredDescription
kidstringyesKey ID, matched against token header kid
algstringyesAlgorithm: RS256, ES256, HS256, etc.
pemstringyesPEM-encoded key material
issuers:
  - name: github-actions
    issuer_url: "https://token.actions.githubusercontent.com"
    jwks_url: "https://token.actions.githubusercontent.com/.well-known/jwks"
    audience: "keyzero"
    type: github-actions
  - name: k8s
    issuer_url: "https://kubernetes.default.svc"
    jwks_url: "https://kubernetes.default.svc/openid/v1/jwks"
    type: kubernetes
  - name: local
    issuer_url: "https://local.dev"
    audience: "keyzero"
    type: custom
    map:
      org: team
      service: sub
    static_keys:
      - kid: "dev-key-01"
        alg: RS256
        pem: |
          -----BEGIN PUBLIC KEY-----
          MIIBIjANBg...
          -----END PUBLIC KEY-----

policies

CEL-based authorization rules. Evaluated top-down, first-match-wins. If no policy matches, an implicit deny applies.

FieldTypeRequiredDescription
namestringyesUnique policy name (returned in responses)
descriptionstringnoHuman-readable description
rulestringyesCEL expression. Must return a boolean
effectstringyesallow or deny

CEL context variables:

VariableTypeTrustDescription
issuerstringVerifiedMatched issuer name
orgstringVerifiedOrganizational boundary (mapped from JWT)
servicestringVerifiedWorkload identity
envstringVerifiedEnvironment
actionstringVerifiedWhat the workload is doing
branchstringVerifiedCode version/ref
actorstringVerifiedHuman who triggered
groupslistVerifiedGroup membership
claims.*mapVerifiedRaw JWT claims (e.g. claims.repository)
refstringRequestRequested secret path
context.*mapUntrustedCaller-declared fields

Use ref.matches() with glob patterns to scope policies to specific secret paths instead of tag-based pre-filters:

policies:
  - name: allow-backend-prod
    description: "Backend team can access prod secrets"
    rule: "org == 'myorg' && service == 'myorg/backend' && ref.matches('secret/data/prod/**')"
    effect: allow
  - name: allow-dev
    description: "Any authenticated caller can access dev secrets"
    rule: "ref.matches('secret/data/dev/**')"
    effect: allow
  - name: default-deny
    rule: "true"
    effect: deny

resources

Secret references with glob patterns and a single resolver per resource.

FieldTypeRequiredDescription
refstringyesPath or glob pattern (e.g. secret/data/prod/**)
resolverstringyesReferences a key in backends
modestringnodirect (default) or short_lived
credential_locationstringnoWhere to place the credential in proxied requests (default: header:Authorization:Bearer)
ttlintegernoTime-to-live in seconds for short_lived mode (default: 300)
pathstringnoBackend-specific path
fieldstringnoField to extract from the secret
role_arnstringnoAWS STS role ARN for AssumeRole
duration_secondsintegernoSTS session duration (default: 900)
secret_idstringnoAWS Secrets Manager secret ID

Match priority

When a request matches multiple resource entries, the most specific match wins:

  1. Exact match -- literal path with no globs
  2. Narrower glob -- more specific pattern (e.g. secret/data/prod/db/*)
  3. Broader glob -- wider pattern (e.g. secret/data/prod/**)
  4. First defined -- declaration order as tiebreaker
resources:
  - ref: "secret/data/prod/db/password"
    resolver: vault-prod
    mode: direct
    path: secret/data/db/prod
    field: password
    credential_location: "header:Authorization:Bearer"
  - ref: "secret/data/prod/**"
    resolver: vault-prod
    mode: direct
  - ref: "secret/data/dev/**"
    resolver: local
    mode: direct

backends

Named backend configurations. Each key is referenced by resource resolver fields.

FieldTypeRequiredDescription
typestringyesBackend type (see table below)
addressstringdependsServer address (Vault)
auth.methodstringnoAuth method (Vault)
auth.rolestringnoAuth role (Vault)
regionstringnoAWS region
pathstringdependsFile path (env_file)
vaultstringno1Password vault name

Backend types

TypeDescriptionRequired backend fields
env_fileRead from a KEY=VALUE filepath
hashicorp_vaultHashiCorp Vault KV v2address
aws_secrets_managerAWS Secrets Managerregion (optional)
aws_stsAWS STS AssumeRoleregion (optional)
onepassword_cli1Password CLI (op)vault (optional)
backends:
  local:
    type: env_file
    path: ./secrets.env
  vault-prod:
    type: hashicorp_vault
    address: https://vault.example.com
  aws:
    type: aws_secrets_manager
    region: us-east-1

Complete Example

version: "1"

issuers:
  - name: github-actions
    issuer_url: "https://token.actions.githubusercontent.com"
    jwks_url: "https://token.actions.githubusercontent.com/.well-known/jwks"
    audience: "keyzero"
    type: github-actions
  - name: local
    issuer_url: "https://local.dev"
    audience: "keyzero"
    type: custom
    map:
      org: team
      service: sub
    static_keys:
      - kid: "dev-key-01"
        alg: RS256
        pem: |
          -----BEGIN PUBLIC KEY-----
          MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...
          -----END PUBLIC KEY-----

policies:
  - name: allow-backend-prod
    description: "Backend team can access prod secrets"
    rule: "org == 'myorg' && service == 'myorg/backend' && ref.matches('secret/data/prod/**')"
    effect: allow
  - name: allow-dev
    description: "Any authenticated caller can access dev secrets"
    rule: "ref.matches('secret/data/dev/**')"
    effect: allow
  - name: block-compromised
    description: "Block known-compromised service"
    rule: "service == 'compromised-service'"
    effect: deny
  - name: default-deny
    rule: "true"
    effect: deny

resources:
  - ref: "secret/data/prod/db/password"
    resolver: vault-prod
    mode: direct
    path: secret/data/db/prod
    field: password
    credential_location: "header:Authorization:Bearer"
  - ref: "secret/data/dev/**"
    resolver: local
    mode: direct
    field: DB_PASSWORD
  - ref: "secret/data/dev/api-key"
    resolver: local
    mode: direct
    field: API_KEY
    credential_location: "header:X-API-Key:"

backends:
  local:
    type: env_file
    path: ./playground/secrets.env
  vault-prod:
    type: hashicorp_vault
    address: https://vault.example.com