/courtyard/* and /chatzer/* admitted any `Authorization: Bearer <x>`. Every test sent `Bearer test-token`; none sent a wrong one. Every admitted request spends the day's manna. VavPipeline.BigdeiKodesh.Gate: keys are a file (Construction §4.6 — never env, never config, never git), one `tier key` per line, tiers courtyard < chatzer (the inner ring opens the outer). Constant-time compare against every line; no file / empty / unreadable → nobody passes, GATE_NO_KEYS in the log, the caller told only "missing or invalid". 403 FORBIDDEN for an outer key at an inner gate. Read on every request so a rotated key is live in ~1 min without a rollout. Deploy: Secret shorashim-api-keys projected at /etc/shorashim/secrets/api_keys (vav-pipeline only, 0440), API_KEYS_FILE in the configmap, template + issue/rotate recipe in secrets.yaml and KOHEN.md "Issuing a key". Spec: tamid §1.1 (gate, 403), vayakhel §4.6. Doorpost 91d09d8d… → 42b38626…. Tests: tablet45_gate_test (parse, tiers, wrong/near/empty/no key, no file) — Elixir 527/0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
126 lines
4.9 KiB
YAML
126 lines
4.9 KiB
YAML
# Shorashim Secrets — template only
|
|
# NEVER commit actual secret values to git.
|
|
# Apply with: kubectl apply -f - after filling in values.
|
|
#
|
|
# The DATABASE_URL connects to Exoscale PostgreSQL (temple-council-db)
|
|
# in ch-gva-2 zone. Retrieve credentials from:
|
|
# exo dbaas show temple-council-db --zone ch-gva-2
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: shorashim-secrets
|
|
namespace: shorashim
|
|
type: Opaque
|
|
stringData:
|
|
# PostgreSQL connection for L3 Oral Tradition — the APPLICATION role.
|
|
# Format: ecto://user:password@host:port/database?sslmode=require
|
|
# This is shorashim_app: INSERT and SELECT on the L3 tables, no DDL,
|
|
# no CREATE on schema public. Append-only is enforced by the grants,
|
|
# not by the application's good behaviour.
|
|
DATABASE_URL: "REPLACE_WITH_ACTUAL_DATABASE_URL"
|
|
|
|
---
|
|
# ============================================================
|
|
# shorashim-migrate-secrets — the DDL role
|
|
# ============================================================
|
|
#
|
|
# KOHEN.md:184 — "the migration runs as avnadmin; the application runs
|
|
# as shorashim_app." TWO ROLES, TWO SECRETS. Only the `migrate` init
|
|
# container reads this one (deployment.yaml overrides DATABASE_URL for
|
|
# that container alone); every app container keeps shorashim-secrets
|
|
# above.
|
|
#
|
|
# WHY IT IS SEPARATE AND NOT JUST A GRANT: Ecto.Migrator.run/3 calls
|
|
# ensure_schema_migrations_table! BEFORE checking what is pending, so
|
|
# it issues CREATE TABLE IF NOT EXISTS schema_migrations on EVERY pod
|
|
# start, even with migrations current. As shorashim_app that is
|
|
# ERROR 42501 permission denied for schema public, and the pod never
|
|
# reaches Running. The alternative — granting shorashim_app CREATE on
|
|
# schema public — collapses the app/migration boundary the runbook
|
|
# draws deliberately. Do not take it.
|
|
#
|
|
# THIS FILE IS A TEMPLATE. Never commit a real value. Create the secret
|
|
# from the live database credential instead:
|
|
#
|
|
# export KUBECONFIG=~/.kube/exoscale-temple-council.yaml
|
|
# umask 077
|
|
# TMP=$(mktemp)
|
|
#
|
|
# exo dbaas user reveal-secrets temple-council-db avnadmin \
|
|
# --zone ch-gva-2 -O json \
|
|
# | python3 -c '
|
|
# import base64, json, subprocess, sys, urllib.parse
|
|
# avn = json.load(sys.stdin)["password"]
|
|
# app = subprocess.run(
|
|
# ["kubectl", "get", "secret", "shorashim-secrets", "-n", "shorashim",
|
|
# "-o", "jsonpath={.data.DATABASE_URL}"],
|
|
# capture_output=True, text=True, check=True).stdout
|
|
# u = urllib.parse.urlsplit(base64.b64decode(app).decode())
|
|
# pw = urllib.parse.quote(avn, safe="")
|
|
# netloc = "avnadmin:%s@%s:%d" % (pw, u.hostname, u.port)
|
|
# sys.stdout.write(
|
|
# urllib.parse.urlunsplit((u.scheme, netloc, u.path, u.query, u.fragment)))
|
|
# ' > "$TMP"
|
|
#
|
|
# kubectl create secret generic shorashim-migrate-secrets \
|
|
# --namespace shorashim --from-file=DATABASE_URL="$TMP"
|
|
#
|
|
# rm -f "$TMP"
|
|
#
|
|
# The rewrite is done in Python, not with `sed s|...|...|`, on purpose.
|
|
# A generated password containing & or | or \ corrupts a sed
|
|
# replacement silently — & re-inserts the matched text and | ends the
|
|
# expression early — and the result is a DATABASE_URL that looks
|
|
# plausible and does not authenticate. quote(safe="") also percent-
|
|
# encodes any character that is not URL-safe in the userinfo field,
|
|
# which sed would have passed through raw. The value goes via a
|
|
# umask-077 temp file rather than --from-literal so the password is
|
|
# never an argv entry visible to `ps`.
|
|
#
|
|
# Absence is meant to be loud: without this secret the pod stops at
|
|
# CreateContainerConfigError. There is no fallback to the app role.
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: shorashim-migrate-secrets
|
|
namespace: shorashim
|
|
type: Opaque
|
|
stringData:
|
|
# ecto://avnadmin:PASSWORD@host:port/database?sslmode=require
|
|
DATABASE_URL: "REPLACE_WITH_AVNADMIN_DATABASE_URL"
|
|
|
|
---
|
|
# ============================================================
|
|
# shorashim-api-keys — who may enter the courtyard
|
|
# ============================================================
|
|
#
|
|
# Mounted as the file /etc/shorashim/secrets/api_keys in vav-pipeline
|
|
# (Construction §4.6: a secret is a file). One key per line:
|
|
#
|
|
# courtyard <key> offerings and bedikot (/courtyard/*)
|
|
# chatzer <key> also the Sanhedrin (/chatzer/*); opens the courtyard too
|
|
#
|
|
# Issue a key (KOHEN.md "Issuing a key"):
|
|
#
|
|
# umask 077; TMP=$(mktemp)
|
|
# printf 'courtyard %s\n' "$(openssl rand -hex 32)" >> "$TMP"
|
|
# kubectl create secret generic shorashim-api-keys -n shorashim \
|
|
# --from-file=api_keys="$TMP"
|
|
# # rotate: kubectl create ... --dry-run=client -o yaml | kubectl apply -f -
|
|
# rm -f "$TMP"
|
|
#
|
|
# The kubelet refreshes a projected Secret within about a minute; the
|
|
# gate reads the file on every request, so a rotated key takes effect
|
|
# without a rollout. No file, no keys → nobody passes (GATE_NO_KEYS in
|
|
# the log).
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: shorashim-api-keys
|
|
namespace: shorashim
|
|
type: Opaque
|
|
stringData:
|
|
api_keys: |
|
|
# tier key
|
|
courtyard REPLACE_WITH_openssl_rand_hex_32
|