{{- /* The 1Password items this chart materializes. TWO of them, split BY AUDIENCE rather than by count: -bulwark-app-secret this release's own secrets - the session secret and the admin password. Nobody else knows or needs them. -bulwark-oauth-secret the OIDC client secret. NOT ours: the identity provider (keycloak, #83) creates it when the client is registered and rotates it there, so it is the one of the three that a second system also holds. A separate item is what lets it be repointed and rotated without touching the other two. Same reasoning as garage's split between its RPC secret and its S3 key. WHAT THE FIELD NAMES ARE AND WHY THEY ARE NOT FREE-FORM: the operator materializes one Secret key per FIELD of the item, and this chart references those keys by name - two as file paths in the StatefulSet's volumeMounts, one by secretKeyRef. An item without the expected field leaves the container in CreateContainerConfigError, or - worse for the file form - produces a container that starts with a MISSING file, which readFileEnv answers with null rather than an error. NOTES.txt lists every field for the installer. TWO SECRETS, TWO DIFFERENT ROTATION STORIES, and the difference is a property of the application rather than of this chart: session-secret, oauth-client-secret FILE form. Re-read on every use with no cache, so a rotation is live WITHOUT a restart - see the whole-directory mount in bulwark.sfs.yaml, which is what makes that true. admin-password CONSUMED ONCE. initAdminPassword hashes it on the first start, writes admin.json to the PVC, and never looks at the variable again. Rotating the field after that has NO effect, restart or not. AND THE LIMIT THAT CANNOT BE CLOSED FOR EITHER: a change to the CONTENT of an item restarts nothing, because Helm never sees a secret the 1Password operator materializes and there is nothing to hash. What this chart does cover is the op:// PATH, through checksum/secret-ref on the pod template. The operator's auto-restart annotation is NOT set here and must not be: verified against operator 1.12.0 it enumerates Deployments only, so on a StatefulSet it is a silent no-op advertising a guarantee it does not provide (README, guideline 9). */ -}} {{- $settings := fromYaml (include "bulwark.settings" .) -}} apiVersion: onepassword.com/v1 kind: OnePasswordItem metadata: name: {{ .Release.Name }}-{{ .Chart.Name }}-app-secret labels: app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs spec: # Must carry the fields: session-secret, admin-password # # session-secret: 32 bytes of randomness, base64 - `openssl rand -base64 32`. # MANDATORY. Persistent "remember me" sessions and the encryption of the # synchronized user settings both hang on it, and the second one fails # SILENTLY without it: app/api/config/route.ts computes # `settingsSyncEnabled: configManager.get(...) && hasSessionSecret()`, so # bulwark.settingsSync: true plus a missing secret means synchronization is # simply off, with no error anywhere. # # LOSING IT LOSES DATA THAT AN INTACT VOLUME STILL HOLDS: the settings on the # PVC are encrypted with it. This item belongs in the backup next to the # volume - a backup of the volume alone is not a backup. # # admin-password: the initial password of the ADMIN DASHBOARD. # It is set because the dashboard is the way back out of an OIDC lockout # (see bulwark.oauth.only in values.yaml), not because of any secret leak - # measured, there is none: without ADMIN_PASSWORD Bulwark generates no # random password and logs nothing, it just disables the dashboard. # # CONSUMED ON THE FIRST START ONLY. After admin.json exists on the PVC this # field is never read again, so rotating it here changes nothing; the reset # goes through the dashboard or by deleting admin.json. itemPath: {{ required "You must provide an app secret reference (secrets.appSecretRef) - the 1Password item holding the fields 'session-secret' (openssl rand -base64 32; mandatory, the settings sync is silently disabled without it) and 'admin-password' (the admin dashboard, which is the only way back out of an OIDC lockout)" .Values.secrets.appSecretRef }} {{- if $settings.oauth.enabled }} --- apiVersion: onepassword.com/v1 kind: OnePasswordItem metadata: name: {{ .Release.Name }}-{{ .Chart.Name }}-oauth-secret labels: app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs spec: # Must carry the field: oauth-client-secret # # The confidential OIDC client's secret, as issued by the identity provider # when the client is registered. The registration is a PRE-INSTALL step, not a # follow-up: with bulwark.oauth.only (the default) and no working client, the # first sign-in is impossible. NOTES.txt prints the redirect URI to register. # # It arrives as a FILE, through OAUTH_CLIENT_SECRET_FILE, so it is not in the # container's process environment - which garage (#81) and keycloak (#83) could # not achieve because neither application offered a file form. #81's other half # was checked here and does not apply: lib/read-file-env.ts is readFileSync # plus try/catch with no mode check, so the group-read bit fsGroup adds is # harmless. # # AND THE ONE WAY A ROTATION SILENTLY DOES NOTHING: getGlobalClientSecret # checks the ADMIN OVERRIDE FIRST. If the dashboard's OAuth setup has ever # run, config.json on the PVC carries oauthClientSecret in PLAINTEXT and that # value beats this file forever. That is also why a backup of the volume can # contain this secret in the clear. itemPath: {{ required "You must provide an OAuth client secret reference (secrets.oauthSecretRef) - the 1Password item holding the field 'oauth-client-secret', as issued by the identity provider when the OIDC client was registered. Required while bulwark.oauth.enabled is true; set it to false for a password-login install" .Values.secrets.oauthSecretRef }} {{- end }}