Skip to content

The installation document

One installation, written once, from which both documents are rendered (ADR 0038): apiVersion: sluis.truvity.github.io/installation/v1, held to schemas/config/installation.schema.json. It holds no secret.

sluisctl render --installation <file> --out <dir> writes sluis.yaml and policy.yaml (sluisctl). --check exits 1 when the files differ. The Pulumi library takes it as LambdaArgs.Installation. Go programs call config.Render from github.com/truvity/sluis/config.

It is a superset of the service and policy documents: each section sits under its document's key with that document's schema.

Key What it is
instance, shape the installation's name (its SSM root is /sluis/<instance>) and where it runs: lambda, kubernetes or server. Required.
preset, release, cluster, policyFile the service document's own keys. preset follows the shape when unset: aws-hybrid for lambda, k8s-aws for kubernetes with aws. The other defaults, k8s-openbao (with openbao), k8s-minimal and server, are unavailable: loading one fails naming the missing adapters (adapters). policyFile follows the shape too (lambda: /opt/sluis/policy.yaml, in the layer; kubernetes: /var/run/access-issuer/policy/policy.yaml, where the chart mounts it; server: /etc/sluis/policy.yaml).
issuer url (required), consoleURL (default <url>/console), rootURL, secureCookies, groupsScoping.
log, lifetimes, freshness, hsts, secrets, keys, signingKey, recovery, conformance, login, console, oauthClient, valkey, directory, audit the service document's, unchanged. For a KMS signer the state secret is named issuer/state-secret when left out.
aws account, region (required for lambda), functionName, functions (the functions' names by module, issuer, github, slack, google, cloudflare or backup; default sluis-<instance>-<module>; used by RenderModules, which writes a document for each function and has the issuer's call them), and the resources bucket, auditQueueURL, each becoming the setting of the adapter that uses it (blobs: s3, audit: sqs). The DynamoDB tables are adapters.state settings (tables, one per module); on lambda the library writes them.
openbao the openbao secrets adapter's settings (address, caFile, mount, namespace, root, auth); it chooses that adapter for secrets.
adapters names single concerns, over what the preset and the resources above give.
cloudflare the accounts and presets for the service document's cloudflare, and grants for the policy document's.
exchange audience (the service document's) and the policy document's clusters, aws and github.owners.
apps the policy document's, unchanged.
controllers github and slack, each present when the controller is on: its own keys go to the service document's controllers, enabledOrgs and enabledWorkspaces to the policy document's.
access the access model's tables (groups, clients, resources, github, slack, ...), the policy document's, unchanged. config.FromAccess builds it.

For shape lambda the renderer writes what the library owns (secrets as ssm with the instance's root, region and layout: v5, recovery.verifierSecret, the invoke trigger's function) and refuses a different value. A named adapter replaces what a resource stands for. The output is deterministic and held to the service's loader. Start checks adapter platform answers and OpenBao reachability.

Write one

config.MarshalInstallation(in, MarshalOptions{Header}) writes the canonical document; config.CheckInstallation(existing, in, opts) reports whether existing is that document. When it is not, it returns a line diff, for a --check mode.

apiVersion comes first and the other keys are sorted. Durations are 1h or 15m, never 1h0m0s. Unset fields and empty lists are left out. A set but empty section is kept, as controllers.github: {} is a controller that is on. Header is a comment block. An installation the loader would refuse is not written.