Skip to content

Configuration

The service document (sluis.yaml, apiVersion: sluis.truvity.github.io/sluis/v3) configures the process. Model: Configuration: the model.

For See
the service document's keys, the retired variables and keys this page
every value of the chart chart values
the policy document the policy document (the access model's tables: policy)
what an estate writes once to render both the installation document
secret names, the env/file/ssm sources, the SSM layout secrets
keeping secrets in OpenBao the OpenBao Secrets adapter
the retired exports and where they went exports
directory.workspaces declared workspaces
the HTTP endpoints endpoints
the console's roles console roles
the objects the service writes in its namespace Kubernetes objects
the adapters and presets adapters

Loading

Command Reads Notes
sluis issuer --config <file> or SLUIS_CONFIG; policy.file names the policy --version and --help are the only other flags
sluis tick github\|slack <target> the same file target: organisation login, github:links or Slack workspace key. Refuses without a shared State; with the service scaled to 0, --unsafe-local-lease runs it
sluis controller github\|slack v2 controller documents or the v3 controllers.<kind> section deprecated; a later release removes it

An unknown key or a wrong type refuses to start and names the path. The embedded schemas are schemas/config/<document>.schema.json. A secret is named, never written (secrets). Durations are Go strings (30s). Telemetry is the platform's OTEL_* environment.

Documents and apiVersion

The service document is sluis/v3, the policy policy/v2; absent means v1. A binary reads N and N-1 and converts N-1 on load; deploy the binary first. A v2 document with a retired key is refused.

apiVersion: sluis.truvity.github.io/sluis/v3
issuerURL: https://sluis.example
policy: {file: /etc/sluis/policy/policy.yaml}
secrets: {source: file, root: /var/run/sluis/secrets}
controllers:                       # optional: absent is no controller
  github: {consoleURL: "http://sluis.access.svc:8080/console", interval: 15m}
  slack: {consoleURL: "http://sluis.access.svc:8080/console"}

The service document

Source: schemas/config/sluis.schema.json. Generated by just docs-generate; keys are listed with their parents first.

Key Type Default Meaning
adapters object — Names the adapter of single concerns, over the preset and the ports keys. The names and what each needs are in the matrix of docs/reference/sluis/adapters.md.
adapters.audit object — The adapter for the audit sink, replacing the preset's.
adapters.audit.adapter string required The adapter's name, as the compatibility matrix lists it.
adapters.audit.settings object — The adapter's own settings (an object; the adapter refuses a key it does not know).
adapters.blobs object — The adapter for blobs, replacing the preset's.
adapters.blobs.adapter string required The adapter's name, as the compatibility matrix lists it.
adapters.blobs.settings object — The adapter's own settings (an object; the adapter refuses a key it does not know).
adapters.schedule object — The adapter for the schedule of passes, replacing the preset's.
adapters.schedule.adapter string required The adapter's name, as the compatibility matrix lists it.
adapters.schedule.settings object — The adapter's own settings (an object; the adapter refuses a key it does not know).
adapters.secrets object — The adapter for secrets (dynamic secrets), replacing the preset's.
adapters.secrets.adapter string required The adapter's name, as the compatibility matrix lists it.
adapters.secrets.settings object — The adapter's own settings (an object; the adapter refuses a key it does not know).
adapters.signing object — The adapter for token signing, replacing the preset's.
adapters.signing.adapter string required The adapter's name, as the compatibility matrix lists it.
adapters.signing.settings object — The adapter's own settings (an object; the adapter refuses a key it does not know).
adapters.state object — The adapter for state, sessions included, replacing the preset's.
adapters.state.adapter string required The adapter's name, as the compatibility matrix lists it.
adapters.state.settings object — The adapter's own settings (an object; the adapter refuses a key it does not know).
adapters.trigger object — The adapter for the "run a pass now" trigger, replacing the preset's.
adapters.trigger.adapter string required The adapter's name, as the compatibility matrix lists it.
adapters.trigger.settings object — The adapter's own settings (an object; the adapter refuses a key it does not know).
allowInsecure boolean — Accept a plain-http issuer URL, for a local run. When the issuer URL is also on the loopback interface (localhost, 127.0.0.1, ::1), the issuer connects to loopback and private addresses when it fetches client documents and posts back-channel logouts; with any other issuer URL those guards stay on.
apiVersion any required Which version of which document this is. sluis.truvity.github.io/sluis/v3 is the one service document: the process that serves the issuer and the console and, under controllers, runs the GitHub and Slack controllers. A binary still reads the v2 serve document (and v1, which has no apiVersion) as this document with no controllers (docs/reference/sluis/configuration.md).
audit object — The audit installation this service records to. Unset keeps the trail in the log only.
audit.audience string "audit" The client whose audience the Audit page's tokens carry.
audit.forwardedForTrustedHops integer 0 How many of the deployment's own proxies append to X-Forwarded-For; zero records the peer.
audit.queryURL string — The query service, for the console's Audit page. Its own setting: it needs no writer, so the page works with the sqs and log sinks. A path prefix (https://audit.example/sluis) is kept and the procedure path appended.
audit.tokenFile string — This workload's projected service-account token, presented on every call.
audit.writer string — The installation's receiver.
cloudflare object — sluis as the STS for Cloudflare API tokens and R2 credentials (docs/guides/sluis/cloudflare-tokens.md). Cloudflare has no web-identity federation, so sluis holds one minter credential per account and hands out short-lived account tokens cloned from a DISABLED prototype token. Needs the ssm secrets source. Unset is off.
cloudflare.accounts object — The Cloudflare accounts sluis mints in, by the name presets use.
cloudflare.accounts.<name> object — One account.
cloudflare.accounts.<name>.id string required The account id.
cloudflare.accounts.<name>.minter string required The internal address of the minter credential, internal/cloudflare/<account>/minter: a cloudflare-minter/v1 document holding an account token that has Account API Tokens Read and Edit and nothing else. It is never an external address: nobody but sluis is granted it.
cloudflare.forbiddenPermissionGroups array — Permission groups, by the name Cloudflare lists them under, that a prototype may never grant, IN ADDITION to the built-in list: Account API Tokens Edit (Write), Billing, Account Settings, Memberships, and Access: Organizations, Identity Providers, and Groups. The built-in list is the only thing between the minter and everything its creator could do, so it cannot be shortened by configuration, only extended.
cloudflare.presets object — What may be minted, by name (a DNS label). The name is in the external address, external/cloudflare/<preset>, and in the name of every token minted for it.
cloudflare.presets.<name> object — One preset: a prototype token and how long its clones live.
cloudflare.presets.<name>.account string required A key of accounts.
cloudflare.presets.<name>.allowedPermissionGroups array — Limits the prototype to these permission groups, by the name Cloudflare lists them under (case and spacing do not matter). A prototype that grants any other is refused with prototype_forbidden. Unset asks nothing beyond the built-in list. The built-in list and forbiddenPermissionGroups stay the floor: a name here does not lift them. Prefer setting it: the built-in list names five kinds of group, and Cloudflare adds new ones.
cloudflare.presets.<name>.description string required What the preset is for. Shown by the console and sluisctl whoami.
cloudflare.presets.<name>.endpoint string — Makes this an R2 preset: the S3 endpoint of the account (https://<account-id>.r2.cloudflarestorage.com, or https://<account-id>.eu.r2.cloudflarestorage.com for an EU jurisdiction). The stored document then holds an access key and secret instead of a token.
cloudflare.presets.<name>.lifetime string required How long each minted token lives: its expires_on. At least 1m, at most 24h. Past it sluis deletes the token in Cloudflare.
cloudflare.presets.<name>.maxLive integer — How many unexpired on-demand tokens this preset may have in all, whoever holds them; a mint over it is refused (preset_cap). Cloudflare allows an account 500 tokens in all, and an expired one counts until it is deleted. Unset is 50.
cloudflare.presets.<name>.maxLivePerCaller integer — How many unexpired on-demand tokens of this preset one caller may hold; a mint over it is refused (caller_cap). Unset is 10.
cloudflare.presets.<name>.maxMintsPerCallerPerHour integer — How many tokens of this preset one caller may be minted in an hour; one over it is refused (rate_limited). Counted from the tokens Cloudflare still lists as live and from this process's own, so a token that expired inside the hour is only counted where it was minted. Unset is 30.
cloudflare.presets.<name>.prototype string required The id of the prototype: an account token in Cloudflare that is DISABLED and whose policies and condition are the preset's rights. An active prototype is refused (it would be a usable token that never expires), and so is one granting Account API Tokens Edit, Billing, Account Settings, Memberships or Access identity providers. It is read at every mint, so editing it applies at the next rotation.
cloudflare.presets.<name>.rotation string — How often the stored token is replaced. At least 1m and shorter than lifetime: lifetime - rotation is the time consumers have to pick up a new token. Required unless store is none, and then left out.
cloudflare.presets.<name>.store one of external, none "external" Where the schedule keeps this preset's token. external rotates it into external/cloudflare/<preset> for consumers to read. none keeps nothing: the schedule never mints it, no parameter exists for it, and a token is minted only on demand (sluisctl cloudflare token <preset>) for a caller the policy's grants allow.
cloudflare.remote object — Where the minter is when it is not this process: the on-demand token exchange calls the Cloudflare module there (docs/decisions/0071), and this document then declares no accounts or presets. Set exactly one of function and url. Unset keeps the minter in this process.
cloudflare.remote.audience string — The audience of that token. The module's name, cloudflare, when unset.
cloudflare.remote.function string — The module's Lambda function, name or ARN. It is invoked through its live-issuer alias.
cloudflare.remote.tokenFile string — Where the projected token is mounted. /var/run/secrets/sluis/cloudflare/token when unset.
cloudflare.remote.url string — The module's Kubernetes Service. A call carries the pod's projected ServiceAccount token.
cloudflare.serve object — Makes the Cloudflare module answer other modules' calls (docs/decisions/0071): the issuer's token exchange when its remote names this module. On Kubernetes the module listens on address and accepts a call that carries a projected ServiceAccount token minted for audience, checked with a TokenReview; any other call is a 401. On Lambda the function answers rpc events, and IAM decides who may invoke it. Needs presets; not with remote. Unset serves nothing.
cloudflare.serve.address string — The listener on Kubernetes, host:port; it also answers GET /healthz. Unused on Lambda.
cloudflare.serve.audience string — The audience a caller's token must be minted for. The module's name, cloudflare, when unset.
cloudflare.serve.issuers array — The issuer's ServiceAccount, as system:serviceaccount:<namespace>:<name>, which may call the listener on Kubernetes. A call from it is both the issuer class (the mint) and the console class; a token of any other subject is a 401. The listener refuses to start with none. Unused on Lambda.
cloudflare.tick string — How often the rotation looks at the presets: the interval of the schedule that invokes it on Lambda (the library fills it from CloudflareRotation when unset), a minute for the Kubernetes loop. A credential rotates up to one tick after its preset's rotation, so the console calls one overdue only past rotation + tick; make rotation a multiple of the tick to rotate when intended. Unset is a minute.
cluster string — Names this cluster in a ServiceAccount's subject. A pod cannot discover it; unset keeps the older unqualified subject.
conformance object — The test account of the OpenID conformance harness: a non-privileged subject that signs in with a username and password, only to clients the policy marks harness, and only while enabled. Off unless turned on (docs/decisions/0076).
conformance.enabled boolean — Turn the account on. The sign-in page offers it only for a request of a client marked harness; a Lambda stack also generates its password and verifier.
conformance.verifierSecret string — The secret the account's SRP verifier is (conformance/verifier): a salt and a verifier, never the password.
console object — Where the console is published, for the sign-in that starts there.
console.awsAudience string — The audience an AWS role's web identity token must be minted for to be a bearer at the console (a Lambda controller). Its own, distinct from the AWS federation file's, so a token for token exchange is no proof here and the reverse. Default <issuerURL>/console.
console.backup object — Connects the console's Backups page to the backup module and the restore function (docs/decisions/0071). The console only reads from them, as the caller class console. Unset has no page.
console.backup.audience string — The audience of the projected token. Each module's name when unset.
console.backup.function string — The backup module's Lambda function, name or ARN, invoked through its live-console alias. Set exactly one of function and url.
console.backup.restoreFunction string — The restore function, name or ARN, invoked through its live-console alias. At most one of it and restoreURL; without either the page says there is none.
console.backup.restoreURL string — The restore function's Kubernetes Service.
console.backup.tokenFile string — Where the projected token is mounted. /var/run/secrets/sluis/backup/token when unset.
console.backup.url string — The backup module's Kubernetes Service. A call carries the pod's projected ServiceAccount token.
console.client string — The client id the console signs in as.
console.origin string — The console's origin, when it is not the issuer's.
demo boolean — Two tenants held in memory, which need no credential and no network.
directory object — The corporate directories this deployment declares: each adopted at start (the console connects the others). Unset declares none.
directory.workspaces array — The declared workspaces. One that cannot be adopted stops the service.
directory.workspaces[].admin string required The account the credential impersonates.
directory.workspaces[].backend one of google required The implementation that reads it.
directory.workspaces[].id string — The backend's tenant id. Optional: given, the adoption checks it.
directory.workspaces[].keySecret string required The secret the service-account key is: directory/<id>/key.
directory.workspaces[].serve array — Narrows the tenant to these domains. Empty serves every domain discovered.
directory.workspaces[].syncGroups array — Narrows the tenant to these groups. Empty keeps every group in the served domains.
exchange object — How the token exchange verifies workloads. Whom it trusts (the clusters, the AWS accounts, the GitHub owners) is the policy document's exchange.
exchange.audience string — The audience a workload token must be minted for. Defaults to release, so two issuers in one cluster cannot accept each other's proofs.
freshness object — How the directory's snapshot is kept current.
freshness.freshnessWindow string "30m" How old a snapshot may be and still be answered from.
freshness.probeInterval string "5m" How often the directory is probed.
freshness.refreshInterval string "15m" How often a snapshot is refreshed.
github object — The GitHub module's routing (docs/decisions/0071). Unset keeps the module in this process.
github.remote object — Where the GitHub module is when it is not this process: the calls to it go there. Set exactly one of function and url. Unset keeps the module in this process.
github.remote.audience string — The audience of that token. The module's name, github, when unset.
github.remote.function string — The module's Lambda function, name or ARN. It is invoked through the live-<class> alias of this process's caller class.
github.remote.tokenFile string — Where the projected token is mounted. /var/run/secrets/sluis/github/token when unset.
github.remote.url string — The module's Kubernetes Service. A call carries the pod's projected ServiceAccount token.
github.serve object — Makes the GitHub module answer the issuer's calls on Kubernetes (docs/decisions/0071). Only the module's own document sets it. The module listens on address and accepts a call whose projected ServiceAccount token was minted for audience, checked with a TokenReview, and whose subject is one of issuers; any other call is a 401. On Lambda the function answers rpc events and IAM decides who may invoke it. Not with remote. Unset serves nothing.
github.serve.address string — The listener, host:port; it also answers GET /healthz. :8081 when unset.
github.serve.audience string — The audience a caller's token must be minted for. The module's name, github, when unset.
github.serve.issuers array required The issuer's ServiceAccount, as system:serviceaccount:<namespace>:<name>. A call from it is both the issuer class (mint_installation) and the console class (the console methods); a token of any other subject is a 401.
google object — The Google module's routing (docs/decisions/0071). Unset keeps the module in this process.
google.remote object — Where the Google module is when it is not this process: the calls to it go there. Set exactly one of function and url. Unset keeps the module in this process.
google.remote.audience string — The audience of that token. The module's name, google, when unset.
google.remote.function string — The module's Lambda function, name or ARN. It is invoked through the live-<class> alias of this process's caller class.
google.remote.tokenFile string — Where the projected token is mounted. /var/run/secrets/sluis/google/token when unset.
google.remote.url string — The module's Kubernetes Service. A call carries the pod's projected ServiceAccount token.
groupsScoping one of off, report, enforce "report" How far this installation has moved toward per-audience groups scoping: off, report or enforce.
hsts object — The Strict-Transport-Security header the issuer and the console send on a response that arrived over https. Never preload; never sent over http or to a loopback host.
hsts.includeSubDomains boolean false Extend the rule to the host's subdomains. Off by default: an issuer on an apex or parent domain would pin every sibling subdomain to https for maxAge. Turn it on only when every subdomain answers https.
hsts.maxAge string "8760h" How long a browser keeps the rule, in whole seconds. Zero sends no header.
inCluster boolean — Prove recovery against the cluster the pod runs in. Set by a deployment that turns recovery on.
instance string — The installation's name, bound into the default encryption context of keys ({instance, purpose}): stable and not secret, because a ciphertext made under it opens only with it. Unset is release.
issuerURL string required The issuer: baked into every token and every relying party's trust, so there is no default. No trailing slash is kept.
keys object — Which key serves which purpose. Keys are named by alias (KMS) or by name (transit), never by ARN or key id. See the package documentation of github.com/truvity/sluis/storage/keys.
keys.adapter one of kms, local, transit required The key service.
keys.archive any — Deprecated, ignored: the archive key of backup format 1. Accepted so a document that still sets it loads; backups are age-encrypted to backup.recipients.
keys.archive.context any — The encryption context: "default" sends {instance, purpose}; "off" sends none; an object is sent as is.
keys.archive.key string — An alias such as alias/sluis--audit-seal (kms) or a transit key name. An ARN or a key id is refused.
keys.conceal any — Values that must be recoverable.
keys.conceal.context any — The encryption context: "default" sends {instance, purpose}; "off" sends none; an object is sent as is.
keys.conceal.key string — An alias such as alias/sluis--audit-seal (kms) or a transit key name. An ARN or a key id is refused.
keys.pseudonym any — Per-tenant pseudonyms.
keys.pseudonym.context any — The encryption context: "default" sends {instance, purpose}; "off" sends none; an object is sent as is.
keys.pseudonym.key string — An alias such as alias/sluis--audit-seal (kms) or a transit key name. An ARN or a key id is refused.
keys.seal any — Sealing the audit trail.
keys.seal.context any — The encryption context: "default" sends {instance, purpose}; "off" sends none; an object is sent as is.
keys.seal.key string — An alias such as alias/sluis--audit-seal (kms) or a transit key name. An ARN or a key id is refused.
keys.sign any — Token signing: the symmetric key that wraps a locally generated signing ring, or an asymmetric key that signs directly (deprecated).
keys.sign.context any — The encryption context: "default" sends {instance, purpose}; "off" sends none; an object is sent as is.
keys.sign.key string — An alias such as alias/sluis--audit-seal (kms) or a transit key name. An ARN or a key id is refused.
lifetimes object — How long what the issuer hands out lives.
lifetimes.absolute string "24h" A session, at most. Positive, and at least token: a session has to end SOMETIME after sign-in, and an access token cannot outlive the session that grants it.
lifetimes.agent object — The refresh chains of clients the policy marks session: agent, such as MCP hosts (docs/decisions/0040). Their chains are held to these instead of refresh and absolute.
lifetimes.agent.absolute string "720h" An agent chain, at most, from auth_time. Positive, and at most 2160h (90 days).
lifetimes.agent.access string "30m" The longest an agent client's access or ID token lives. Positive, and at most 1h.
lifetimes.agent.refresh string "336h" An agent chain's idle limit. Positive, and at most absolute.
lifetimes.hold string "4h" How long a removal is held before it takes effect.
lifetimes.refresh string "12h" A refresh token.
lifetimes.session string "12h" The console's own session cookie, capped at absolute.
lifetimes.token string "1h" An access token.
listen fragment: listen.json {"address":":8080"}
log fragment: log.json —
login object — How a person signs in to the console.
login.directory boolean true Sign in with the corporate directory.
login.forwarded object — A sign-in an authenticating proxy has already done.
login.forwarded.audience string — The audience its token carries.
login.forwarded.emailHeader string — The header that carries the signed-in address.
login.forwarded.issuer string — The proxy's issuer.
login.signOutURL string — Where sign-out sends the browser.
oauthClient object — The OAuth client registered once with the directory backend: it drives both admin consent and operator sign-in.
oauthClient.id string — The client id, for a local run. Not a secret.
oauthClient.idKey string — The key of the id in that Secret.
oauthClient.provider string — Names the client's secrets: providers/google//client-secret, and providers/google//client-id unless id gives it. One segment of a secret's name.
oauthClient.secretKey string — The key of the secret in that Secret.
oauthClient.secretName string — The Kubernetes Secret the client is declared in, which the console shows and cannot change.
outline object — The Outline module's routing (docs/decisions/0077). The issuer never runs the module: unset, the console's Outline pages say there is none.
outline.remote object — Where the Outline module is: the console's Outline pages are its. Set exactly one of function and url.
outline.remote.audience string — The audience of that token. The module's name, outline, when unset.
outline.remote.function string — The module's Lambda function, name or ARN. It is invoked through the live-<class> alias of this process's caller class.
outline.remote.tokenFile string — Where the projected token is mounted. /var/run/secrets/sluis/outline/token when unset.
outline.remote.url string — The module's Kubernetes Service. A call carries the pod's projected ServiceAccount token.
platform object — What this installation has to build on. The answers pick a preset (the decision tree in docs/concepts/sluis/ports.md), and start refuses an adapter that needs an answer that is no. Absent, the ports keys decide and nothing is checked against the platform.
platform.aws boolean — AWS is available: its credentials, DynamoDB, S3, SSM, KMS, SQS and EventBridge.
platform.kubernetes boolean — A Kubernetes cluster is available.
platform.openbao boolean — An OpenBao is available.
platform.replicas integer 1 How many replicas share this installation's state. Above 1 start refuses an adapter that keeps its data in the process (memory).
platform.runtime one of kubernetes, lambda, process — Where sluis itself runs. Absent: kubernetes with a cluster, else lambda on AWS, else process.
policy object — The policy document this service decides by. Unset is the built-in two groups, or the demonstration policy under demo.
policy.file string required The policy document: the one canonical file sluisctl policy render writes (schemas/config/policy.schema.json). Read once, at start: a change is a new instance.
ports object — The adapters behind the storage ports (docs/concepts/sluis/ports.md).
ports.adapter one of legacy, memory, dynamodb "legacy" legacy, the default, is deprecated since v1.74.0 and still ships: migrate with sluis migrate. It keeps state where it has always been kept: the namespace's ConfigMaps and Secrets and, when valkey is set, Valkey. memory keeps all of it in this process, which a restart loses: for a local run and the demonstration, and not with store: kubernetes or valkey. dynamodb keeps the same in one DynamoDB table (ports.dynamodb), with the platform's credentials, and takes its Blob from legacy unless ports.blob names its own.
ports.blob object — Replaces the Blob port (status reports, directory snapshots) with an adapter of its own, whatever ports.adapter is. Absent, the Blob is ports.adapter's.
ports.blob.adapter one of s3 required s3 keeps the blobs in an S3 bucket.
ports.blob.s3 object — Where the S3 adapter keeps its objects. Credentials are the platform's (EKS Pod Identity, IRSA, a Lambda role) and are never configured here.
ports.blob.s3.bucket string required The bucket. It must exist, with public access blocked.
ports.blob.s3.checksum one of sha256, md5, none "sha256" The integrity header of every upload: sha256 (x-amz-checksum-sha256, computed over the whole body and sent precomputed, never as an aws-chunked trailer), md5 (Content-MD5) or none, for a store that refuses SHA-256. AWS refuses a PutObject without one on an Object Lock bucket.
ports.blob.s3.credentials object — Instead of a static document, sluis mints its own R2 credentials from a Cloudflare preset: the account's minter credential clones the preset's prototype, the credentials are renewed with a third of their lifetime left and minted again once after a 403. Exclusive with credentialsRef. Needs endpoint, the ssm secrets source, and the cloudflare section.
ports.blob.s3.credentials.preset string required A key of cloudflare.presets that has an endpoint (an R2 preset).
ports.blob.s3.credentialsRef string — Where the credentials for endpoint are, in the installation's secrets store (the ssm secrets source): the internal address, internal/<module>/<name>, of the s3-credentials/v1 document (access_key_id, secret_access_key) that holds static credentials, or external/cloudflare/<preset>, the cloudflare/v1 document the Cloudflare module rotates for an R2 preset with store: external (the access key is the token's id; it is read again with a third of its time left and after a 403). Read at start and again after an answer of 403, at most once a minute. Needs endpoint; region defaults to auto with it. Any other address is refused. This is the simple way and needs no cloudflare section in this document, so it is the one to use when the presets are another module's (cloudflare.remote).
ports.blob.s3.endpoint string — Overrides the S3 address: LocalStack or an S3-compatible store (Cloudflare R2, MinIO).
ports.blob.s3.kmsKey string — A KMS key id, ARN or alias for server-side encryption (SSE-KMS) of every write. Absent, the bucket's default encryption applies.
ports.blob.s3.pathStyle boolean — Addresses the bucket in the path and not the host name, which LocalStack and most S3-compatible stores need.
ports.blob.s3.prefix string — A key prefix inside the bucket, for an installation that shares it.
ports.blob.s3.region string — The bucket's region. Absent, the SDK's own resolution (AWS_REGION).
ports.dynamodb object — The DynamoDB tables of the dynamodb adapter: one table per module (tables, layout v5). Each has a string partition key pk, a string sort key sk and the TTL attribute expires. Credentials are the platform's (EKS Pod Identity, IRSA, a Lambda role) and are never configured here; an S3-compatible endpoint with no such identity names a credentials document by address (credentialsRef), never by value.
ports.dynamodb.create boolean false Create the table (on-demand, TTL on expires) at start when it is not there, for a test or a development installation. Off, the table must exist: production uses the one the infrastructure code made, and the role needs dynamodb:DescribeTable on it (layout v5: the readiness probe asks, not the start).
ports.dynamodb.endpoint string — Overrides the DynamoDB address: LocalStack or DynamoDB Local.
ports.dynamodb.region string — The table's region. Absent, the SDK's own resolution (AWS_REGION).
ports.dynamodb.tables object required Layout v5 (docs/decisions/0072-storage-layout-v5-module-first.md): the table of each module, by module name. A process names its own module's table and the tables of the peers it reads; a module it does not name is not opened. The single table of layout v4 (table) was removed in v1.77.1.
ports.dynamodb.tables.backup string — The name of the backup module's table. The library default is sluis-<instance>-backup.
ports.dynamodb.tables.cloudflare string — The name of the cloudflare module's table. The library default is sluis-<instance>-cloudflare.
ports.dynamodb.tables.github string — The name of the github module's table. The library default is sluis-<instance>-github.
ports.dynamodb.tables.google string — The name of the google module's table. The library default is sluis-<instance>-google.
ports.dynamodb.tables.oidc string — The name of the oidc module's table. The library default is sluis-<instance>-oidc.
ports.dynamodb.tables.outline string — The name of the outline module's table. The library default is sluis-<instance>-outline.
ports.dynamodb.tables.slack string — The name of the slack module's table. The library default is sluis-<instance>-slack.
preset one of server, k8s-minimal, k8s-openbao, aws-serverless, aws-hybrid, k8s-aws, aws-eks — The adapters of a whole platform, one per concern: server (no AWS, no Kubernetes), k8s-minimal, k8s-openbao, aws-serverless, aws-hybrid (sluis on Lambda, a cluster for the workloads), k8s-aws (sluis as a pod on Kubernetes with AWS storage: DynamoDB, S3, KMS-wrapped signing; SSM secrets, or OpenBao with adapters.secrets). aws-eks is the deprecated name of k8s-aws. Absent, the preset the platform answers lead to; with neither, the ports keys decide. adapters and the ports keys override single concerns.
probes fragment: probes.json {"address":":7070"}
publicRootURL string — The host's root, never carrying the console's mount: the bootstrap surface stays there. Unset follows publicURL.
publicURL string — Where a browser reaches the console, including its mount. The admin-consent redirect URI and the values the setup steps show are built from it. Default http://localhost:8081.
recovery object — The sign-in that needs no directory: in a cluster, a token for a ServiceAccount proven against the API server; anywhere else, a password. Unset is off for the issuer and, for the hub, on.
recovery.audience string — The audience its token must carry.
recovery.enabled boolean — Turn recovery on. Needs inCluster, serviceAccount and audience to be usable.
recovery.generation string — Names this configuration's decision to have recovery on. An operator may disable recovery from the console (only after signing in through the identity provider); the disable is kept in the State against the generation in force and covers only it. A redeploy of the same document keeps recovery disabled; change this value, to any other string, to turn it back on. Unset is the generation "".
recovery.passwordSecret string — Outside a cluster: the secret the recovery password is (recovery/password), read to derive the account's SRP verifier, which is all the hub holds of it, unless verifierSecret names one (docs/decisions/0076). Unset generates one and prints it, except on a function, where recovery is then off. enabled: false leaves it untouched and refuses the sign-in, so turning it back on needs no new password.
recovery.serviceAccount string — The ServiceAccount whose token signs in.
recovery.verifierSecret string — Outside a cluster: the secret the recovery account's SRP verifier is (recovery/verifier): a salt and a verifier, never the password. The sign-in page proves knowledge of the password against it without sending it (docs/decisions/0076). Unset derives one from the password at start.
release string "sluis" The name this installation's objects carry: the Kubernetes object names (<release>-github-orgs, ...) and the prefix of its keys in a shared store. The chart requires it to be the release's full name.
secrets object — How the secrets this document names are delivered (truvity/policy config.md section 5). Absent is env.
secrets.endpoint string — ssm: overrides the SSM address, for LocalStack.
secrets.grace string "24h" ssm: how long the previous value of a rotated client secret is still accepted: the document's previous revision, while the current one is younger than this (docs/decisions/0039-the-issuer-generates-confidential-client-secrets.md, the overlap).
secrets.kmsKeyId string — ssm: the id, ARN or alias of the customer-managed KMS key the parameters the service itself writes (its credentials) are encrypted with. Unset, the AWS-managed alias/aws/ssm. The ssm secrets adapter's kmsKeyId setting; naming another there is refused.
secrets.layout one of v5 — ssm, required: the storage layout of the installation's secrets, v5, the only one: the module first, <root>/internal/<module>/ and <root>/external/<module>/ (docs/decisions/0072-storage-layout-v5-module-first.md). Layout v4 was removed in v1.77.1 (an installation still on it migrates with sluis migrate v5 of v1.77.0 first), and with it the reading of an absent layout as v4.
secrets.readCache string "5m" ssm: how long a process keeps a document it read; a write drops it. 0s is off. SLUIS_SECRETS_READ_CACHE_TTL overrides.
secrets.refresh string "1m" ssm: how old a secret's copy may be before it is read again: a rotated secret reaches every instance within it.
secrets.region string — ssm: the region. Unset follows the AWS SDK's own resolution.
secrets.root string — file: the directory the secrets are mounted under. ssm: the installation's root, /sluis/.
secrets.source one of env, file, ssm "env" env: the variable SLUIS_SECRET_ (the name upper-cased, every other character an underscore), for a local run. file: the file /, read on every use, so a rotated mount takes effect without a restart. ssm: a SecureString in AWS SSM Parameter Store under (/sluis/), on layout v5 (layout): a name the service knows is read at its module's address (<root>/internal/oidc/state-secret, ...), any other at <root>/internal/config/<name>.
secureCookies boolean — Mark session cookies Secure. Unset follows the scheme the browser will use: https in the URL. false is for a plain-http local run: the service refuses it when issuerURL or publicRootURL is https.
signingKey object — The issuer's signing keys: provisioned, never minted here.
signingKey.activationDelay string "15m" How long a newly published key waits before a replica signs with it. At least pollInterval.
signingKey.additionalFiles array — Every OTHER algorithm this installation signs with at once, one file per algorithm.
signingKey.file string — The primary key. Unset generates one for this process, which a local run may do and nothing else should. Exclusive with kms.
signingKey.kms object — Sign with AWS KMS keys instead of a file: the private key never leaves KMS. Exclusive with file.
signingKey.kms.additional array — Every OTHER algorithm this installation signs with at once, each on its own rotation track, as signingKey.additionalFiles does for files. RS256 is the one that exists: for relying parties that need it (Kargo, EKS's OIDC provider).
signingKey.kms.additional[].alg one of RS256 required The algorithm.
signingKey.kms.additional[].keys array required RSA_2048, RSA_3072 or RSA_4096 SIGN_VERIFY keys, oldest first, the last signing; same rotation rules as keys.
signingKey.kms.keys array required ECC_NIST_P384 SIGN_VERIFY keys, as ids, ARNs or aliases, oldest first. The LAST signs; the earlier ones stay published until overlap after the next one activates. Rotation appends a key. The role needs kms:Sign and kms:GetPublicKey on each.
signingKey.kms.region string — The keys' region. Unset follows the AWS SDK's own resolution.
signingKey.kms.stateSecret string required The secret (issuer/state-secret) holding at least 32 random bytes as base64 or hex (openssl rand -base64 32), the same in every replica (a replica whose secret differs refuses to start), from which the sign-in state is derived: a KMS key has no private bytes to derive from.
signingKey.kmsWrapped object — Sign with key pairs AWS KMS generates and wraps under ONE symmetric key (the kms-wrapped adapter): a new pair per algorithm every rotateEvery, published before it signs and kept after it is replaced. The private key is decrypted into process memory to sign. Exclusive with file and kms.
signingKey.kmsWrapped.algorithms array — The algorithms signed with, the first the installation default. Unset is ES384 and RS256. EdDSA is not supported yet.
signingKey.kmsWrapped.keyId string — DEPRECATED, accepted for one release with a warning: name the symmetric key as keys.sign instead. An alias given here is mapped onto keys.sign; an ARN or an id is refused.
signingKey.kmsWrapped.prepublish string "15m" How long a new key is published before anything signs with it: longer than a verifier caches the key set (Envoy's jwt_authn: 10m). Unset is activationDelay.
signingKey.kmsWrapped.region string — The key's region. Unset follows the AWS SDK's own resolution.
signingKey.kmsWrapped.retain string — How long a replaced key stays published: at least lifetimes.token plus a skew margin. Unset is overlap.
signingKey.kmsWrapped.rotateEvery string "24h" How often a new key pair is generated for each algorithm. Longer than prepublish, at most 168h.
signingKey.kmsWrapped.stateSecret string required The secret (issuer/state-secret) holding at least 32 random bytes as base64 or hex, the same in every replica, from which the sign-in state is derived: a wrapped key is replaced daily and the state must outlive it.
signingKey.overlap string — How long a rotated key stays published. Unset is lifetimes.token plus a margin for clock skew.
signingKey.pollInterval string "30s" How often the files are re-read.
signingKey.verifyOnly array — PUBLIC keys published in the JWKS and never signed with, so tokens an earlier signer issued keep verifying until they expire: the overlap of a cutover from file keys to kmsWrapped. Each is dropped from the JWKS at its until. A private key stops the start.
signingKey.verifyOnly[].alg one of ES384, RS256 — The key's algorithm. Unset follows the key.
signingKey.verifyOnly[].file string required A PEM public key (PUBLIC KEY, RSA PUBLIC KEY, or a certificate) or a JWK file: ECDSA on P-384 (ES384) or RSA of 2048 bits or more (RS256). Another curve or a shorter RSA key is refused. Never a private key.
signingKey.verifyOnly[].kid string — The kid the old tokens carry. Unset is the RFC 7638 thumbprint of the key, which is what a file signer derived for it.
signingKey.verifyOnly[].until string (date-time) required An RFC 3339 instant after which the key is no longer published: the old tokens' last expiry, plus the verifiers' cache. Required: an overlap has an end.
slack object — The Slack module's routing (docs/decisions/0071). Unset keeps the module in this process.
slack.remote object — Where the Slack module is when it is not this process: the calls to it go there. Set exactly one of function and url. Unset keeps the module in this process.
slack.remote.audience string — The audience of that token. The module's name, slack, when unset.
slack.remote.function string — The module's Lambda function, name or ARN. It is invoked through the live-<class> alias of this process's caller class.
slack.remote.tokenFile string — Where the projected token is mounted. /var/run/secrets/sluis/slack/token when unset.
slack.remote.url string — The module's Kubernetes Service. A call carries the pod's projected ServiceAccount token.
slack.serve object — Makes the Slack module answer the issuer's calls on Kubernetes (docs/decisions/0071). Only the module's own document sets it. The module listens on address and accepts a call whose projected ServiceAccount token was minted for audience, checked with a TokenReview, and whose subject is one of issuers; any other call is a 401. On Lambda the function answers rpc events and IAM decides who may invoke it. Not with remote. Unset serves nothing.
slack.serve.address string — The listener, host:port; it also answers GET /healthz. :8081 when unset.
slack.serve.audience string — The audience a caller's token must be minted for. The module's name, slack, when unset.
slack.serve.issuers array required The issuer's ServiceAccount, as system:serviceaccount:<namespace>:<name>. A call from it is both the issuer class (mint_installation) and the console class (the console methods); a token of any other subject is a 401.
store one of memory, kubernetes "memory" Where what an operator connected is kept: memory keeps nothing (a restart is a fresh installation), kubernetes keeps it in this namespace.
valkey object — Deprecated since v1.74.0 and still read: migrate with sluis migrate. The shared store for logins in progress and snapshots. Unset keeps both in memory, correct for one replica.
valkey.address string — host:port, with no credentials.
valkey.cluster boolean true Speak the cluster protocol. A plain single server needs it off.
valkey.passwordSecret string — The secret the password is (valkey/password). Unset connects with none.
valkey.tls boolean — Speak TLS to the server.

controllers: the GitHub and Slack controllers

controllers.github and controllers.slack each run a loop in the issuer. An absent section is a controller that is off, and github: {} takes the defaults. policy.file is required. /readyz waits for each controller to begin, and a controller that fails to start stops the process. Each organisation or workspace is a dry run until the policy's enabledOrgs or enabledWorkspaces lists it (0037).

Source: schemas/config/sluis.schema.json. Generated by just docs-generate; keys are listed with their parents first.

Key Type Default Meaning
controllers object — The controllers this process runs beside the issuer and the console, each in its own loop. A controller that is absent is off. Which organisations and workspaces a running controller may CHANGE is the policy document's controllers.<kind>.enabledOrgs and enabledWorkspaces, as before. A controller reads the console's API as a workload, so the policy's exchange must admit its proof (a ServiceAccount token on Kubernetes, the function role's web identity token on Lambda).
controllers.github object — The GitHub controller: makes each GitHub organisation's teams match the policy's github table.
controllers.github.appsDir string "/var/run/github-roster/apps" One file per connected organisation: its App's credentials. Read only with ports.adapter: legacy; with another adapter they are on the State port.
controllers.github.console object — How the controller proves itself to the console, when the pod's tokenFile is not the way.
controllers.github.console.auth object — The proof. Absent, tokenFile.
controllers.github.console.auth.aws object — The function role's AWS outbound web identity token (sts:GetWebIdentityToken), re-minted every four minutes. The console's issuer must federate the account, and its policy must declare an aws matcher for the role.
controllers.github.console.auth.aws.audience string required The audience requested from STS. It must equal console.awsAudience (default <issuerURL>/console), NOT the audience of the issuer's AWS federation file.
controllers.github.consoleURL string — The console's API, which answers who holds a group. Unset is publicURL. On Kubernetes it is this release's own Service, which the chart writes.
controllers.github.interval string "15m" How long between passes. Positive.
controllers.github.recordsDir string "/var/run/github-roster/records" The console's records, mounted. Read only with ports.adapter: legacy.
controllers.github.tokenFile string "/var/run/secrets/github-roster/token" This pod's projected ServiceAccount token, presented to the console and read afresh on every call.
controllers.slack object — The Slack controller: makes each Slack workspace's user groups match the policy's slack table.
controllers.slack.console object — How the controller proves itself to the console, when the pod's tokenFile is not the way.
controllers.slack.console.auth object — The proof. Absent, tokenFile.
controllers.slack.console.auth.aws object — The function role's AWS outbound web identity token (sts:GetWebIdentityToken), re-minted every four minutes. The console's issuer must federate the account, and its policy must declare an aws matcher for the role.
controllers.slack.console.auth.aws.audience string required The audience requested from STS. It must equal console.awsAudience (default <issuerURL>/console), NOT the audience of the issuer's AWS federation file.
controllers.slack.consoleURL string — The console's API, which answers who holds a group. Unset is publicURL. On Kubernetes it is this release's own Service, which the chart writes.
controllers.slack.credentialsDir string "/var/run/slack-roster/credentials" One file per connected workspace: the app's credentials and its bot token. Read only with ports.adapter: legacy; with another adapter they are on the State port.
controllers.slack.interval string "15m" How long between passes. Positive.
controllers.slack.recordsDir string "/var/run/slack-roster/workspaces" The console's records, mounted. Read only with ports.adapter: legacy.
controllers.slack.tokenFile string "/var/run/secrets/slack-roster/token" This pod's projected ServiceAccount token, presented to the console and read afresh on every call.

The chart refuses a tokenFile, appsDir, credentialsDir or recordsDir outside its mounts. RBAC: Kubernetes objects. Guides: GitHub organisation, Slack workspace.

Retired environment variables

A retired variable that is still set stops the process, naming its replacement key. Procedure: migrate from environment variables.

The service

Old variable Now, in the service document (the policy document where said)
ISSUER_URL issuerURL
PORT listen.address (:<port>)
HEALTH_PORT probes.address
API_PORT, CONSOLE_PORT none: the directory's own listeners are not served by sluis issuer; the console is on the issuer's listener
DEMO demo
ALLOW_INSECURE allowInsecure
IN_CLUSTER inCluster
PUBLIC_URL, PUBLIC_ROOT_URL publicURL, publicRootURL
SECURE_COOKIES secureCookies
STORE store
OVERLAY_FILE directory.workspaces, the declared workspaces themselves (each key by keySecret)
POLICY_DIR policy.file, the one rendered policy document
GROUPS_SCOPING groupsScoping
CLUSTER cluster
RELEASE_NAME release
CLIENT_SECRETS_DIR none: a confidential client's secret is the name clients/<client-id>/secret, delivered by secrets
ADMIN_PASSWORD recovery.passwordSecret, which names the secret (recovery/password)
RECOVERY_ENABLED, RECOVERY_SERVICE_ACCOUNT, RECOVERY_AUDIENCE recovery.enabled, recovery.serviceAccount, recovery.audience
API_AUDIENCE, CONSUMERS_FILE none: sluis issuer serves no directory API listener, so its guard configured nothing
LOGIN_DIRECTORY, SIGN_OUT_URL login.directory, login.signOutURL
FORWARDED_EMAIL_HEADER, FORWARDED_ISSUER, FORWARDED_AUDIENCE login.forwarded.emailHeader, .issuer, .audience
CONSOLE_ORIGIN, CONSOLE_CLIENT_ID console.origin, console.client
OAUTH_CLIENT_ID, OAUTH_CLIENT_ID_FILE oauthClient.id, or the secret providers/google/<provider>/client-id named by oauthClient.provider
OAUTH_CLIENT_SECRET, OAUTH_CLIENT_SECRET_FILE the secret providers/google/<provider>/client-secret, named by oauthClient.provider
OAUTH_CLIENT_SECRET_NAME, OAUTH_CLIENT_ID_KEY, OAUTH_CLIENT_SECRET_KEY oauthClient.secretName, .idKey, .secretKey
SIGNING_KEY_FILE, SIGNING_KEY_FILES signingKey.file, signingKey.additionalFiles
SIGNING_KEY_POLL_INTERVAL, SIGNING_KEY_ACTIVATION_DELAY, SIGNING_KEY_OVERLAP signingKey.pollInterval, .activationDelay, .overlap
TOKEN_LIFETIME, REFRESH_LIFETIME, ABSOLUTE_LIFETIME, HOLD_WINDOW lifetimes.token, .refresh, .absolute, .hold
SESSION_LIFETIME lifetimes.session
REFRESH_INTERVAL, FRESHNESS_WINDOW, PROBE_INTERVAL freshness.refreshInterval, .freshnessWindow, .probeInterval
EXCHANGE_AUDIENCE, CLUSTERS_FILE, AWS_FEDERATION_FILE exchange.audience; and, in the policy document, exchange.clusters and exchange.aws, the rows themselves
VALKEY_ADDRESS, VALKEY_TLS, VALKEY_CLUSTER valkey.address, .tls, .cluster
VALKEY_PASSWORD valkey.passwordSecret, which names the secret (valkey/password)
GITHUB_OWNERS, GITHUB_RUNNER_TIERS, GITHUB_APPS_CATALOGUE_FILE the policy document's exchange.github.owners, apps.github.apps
SLACK_APPS_CATALOGUE_FILE the policy document's apps.slack.catalogue
AUDIT_WRITER_URL, AUDIT_TOKEN_FILE audit.writer, audit.tokenFile
AUDIT_QUERY_URL, AUDIT_AUDIENCE, AUDIT_FORWARDED_FOR_TRUSTED_HOPS audit.queryURL, .audience, .forwardedForTrustedHops
LOG_LEVEL log.level
SECRET_MANAGERS_FILE none: the console's secret-store view was removed in v1.30.0 (0002)
OTEL_EXPORTER_OTLP_ENDPOINT (and the other OTEL_*) unchanged: set by the platform, not by the file. The chart's telemetry.otlpEndpoint is removed

The controllers

Old variable controller-github controller-slack
RELEASE_NAME release release
POLICY_DIR policy.file policy.file
CONSOLE_URL consoleURL consoleURL
TOKEN_FILE tokenFile tokenFile
APPS_DIR appsDir none
CREDENTIALS_DIR none credentialsDir
RECORDS_DIR recordsDir recordsDir
INTERVAL interval interval
ENABLED_ORGS the policy document's controllers.github.enabledOrgs none
ENABLED_WORKSPACES none the policy document's controllers.slack.enabledWorkspaces
GITHUB_APPS_CATALOGUE_FILE none: the catalogue is the policy document's apps.github.apps none
AUDIT_WRITER_URL, AUDIT_TOKEN_FILE audit.writer, audit.tokenFile audit.writer, audit.tokenFile
LOG_LEVEL log.level log.level

The function's environment (AWS Lambda)

A function that sets one stops at start (AWS Lambda).

Old variable Now
SLUIS_CONFIG_FILE SLUIS_CONFIG, which names the service document: /opt/sluis/sluis.yaml in the configuration layer
SLUIS_SECRET_FILES the document's secrets source (ssm, root /sluis/<instance>) and the names its keys give: signingKey.kms.stateSecret, recovery.verifierSecret
SLUIS_ROLE nothing: a zip's module is pinned at build, and the Pulumi library deploys a function per module (package)
any variable set to ssm:<path> the same: a secret is named in the document and read by its secrets source

Retired keys

A v2 document that names a retired key is refused with where it went. A v1 document keeps working.

Document Retired key Now
serve policyDir policy.file: render the directory with sluisctl policy render <dir> and name the result
serve overlayFile directory.workspaces, in the document
serve api (api.audience, api.consumersFile) removed: sluis issuer serves no directory API listener
serve github.owners policy exchange.github.owners
serve github.runnerTiers, github.catalogueFile policy apps.github.apps
serve slack.catalogueFile policy apps.slack.catalogue
serve exports, ports.export retired (ADR 0041): a consumer reads the typed document at external/<kind>/<id>, see exports
serve exchange.clustersFile, exchange.awsFile policy exchange.clusters, exchange.aws, the rows themselves
serve valkey.passwordEnv valkey.passwordSecret (valkey/password)
serve oauthClient.idFile, .secretFile, .secretEnv oauthClient.provider: the secrets providers/google/<provider>/client-id and client-secret
serve adminPasswordEnv, recovery.passwordFile recovery.passwordSecret (recovery/password)
serve clientSecretsDir none: clients/<client-id>/secret, delivered by secrets
serve signingKey.kms.stateSecretFile, signingKey.kmsWrapped.stateSecretFile stateSecret (issuer/state-secret); the kms and kms-wrapped adapters' setting is stateSecret too
the v1 overlay file directory.workspaces[].keyFile keySecret (directory/<id>/key)
controller-github policyDir, catalogueFile, enabledOrgs policy.file; the policy's apps.github.apps; the policy's controllers.github.enabledOrgs
controller-slack policyDir, enabledWorkspaces policy.file; the policy's controllers.slack.enabledWorkspaces
policy version: 1 apiVersion: sluis.truvity.github.io/policy/v2
policy access, overlay an access document is a layer, not a policy document: sluisctl policy render reshapes it into one