Add coulomb-social-env secret create/patch script
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Builds SECRET_KEY, optional URL-encoded DATABASE_URL from app DB
credentials, and USER_ENGINE_PROXY_SECRET from user-engine-runtime.
Never prints values; Makefile dry-run and apply targets.
This commit is contained in:
tegwick 2026-08-09 02:04:32 +02:00
parent 32610e9090
commit c79f07aadf
3 changed files with 321 additions and 5 deletions

View file

@ -297,6 +297,13 @@ coulomb-social-status: check-production-kubeconfig ## Show coulomb-social pod/sv
coulomb-social-logs: ## Tail coulomb-social app logs
kubectl -n $(COULOMB_SOCIAL_NAMESPACE) logs -l app.kubernetes.io/name=coulomb-social -f --tail=200
coulomb-social-env-secret: ## Create/patch coulomb-social-env (SECRET_KEY, DATABASE_URL, USER_ENGINE_PROXY_SECRET)
@bash tools/create-coulomb-social-env-secret.sh $(COULOMB_SOCIAL_ENV_SECRET_ARGS)
coulomb-social-env-secret-dry-run: ## Plan coulomb-social-env keys without applying
@bash tools/create-coulomb-social-env-secret.sh --dry-run $(COULOMB_SOCIAL_ENV_SECRET_ARGS)
##@ Inter-Hub
inter-hub-render-baseline: ## Render checked-in inter-hub values for chart validation only
@ -577,4 +584,4 @@ help: ## Show this help
/^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } \
/^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST)
.PHONY: check-tools check-sops k8s-server-dry-run apps-pg-status apps-pg-restore-drill check-railiance01-kubeconfig check-inter-hub-image-tag check-inter-hub-image check-vergabe-image-tag check-vergabe-image vergabe-dry-run vergabe-deploy vergabe-ingress-deploy vergabe-status vergabe-migrate vergabe-seed vergabe-superuser vergabe-logs vergabe-db-url-secret check-coulomb-social-image-tag coulomb-social-dry-run coulomb-social-deploy coulomb-social-ingress-deploy coulomb-social-status coulomb-social-logs eso-deploy forgejo-openbao-eso-token-apply forgejo-openbao-store-deploy forgejo-mailer-es-deploy forgejo-mailer-es-status forgejo-dry-run forgejo-server-dry-run forgejo-deploy forgejo-ingress-deploy forgejo-ssh-nodeport-deploy forgejo-status forgejo-smoke forgejo-npm-smoke forgejo-verify forgejo-operator-bootstrap forgejo-runner-registration-sops-bootstrap forgejo-secrets-check forgejo-logs forgejo-runner-registration-deploy forgejo-runner-deploy forgejo-runner-status forgejo-runner-logs inter-hub-render-baseline inter-hub-dry-run inter-hub-server-dry-run inter-hub-deploy inter-hub-status inter-hub-release-info inter-hub-smoke inter-hub-logs reuse-dry-run reuse-deploy reuse-status reuse-smoke reuse-logs reuse-forgejo-webhook reuse-openbao-eso-token-apply reuse-openbao-store-deploy reuse-runtime-es-deploy reuse-runtime-es-status reuse-webhook-smoke help
.PHONY: check-tools check-sops k8s-server-dry-run apps-pg-status apps-pg-restore-drill check-railiance01-kubeconfig check-inter-hub-image-tag check-inter-hub-image check-vergabe-image-tag check-vergabe-image vergabe-dry-run vergabe-deploy vergabe-ingress-deploy vergabe-status vergabe-migrate vergabe-seed vergabe-superuser vergabe-logs vergabe-db-url-secret check-coulomb-social-image-tag coulomb-social-dry-run coulomb-social-deploy coulomb-social-ingress-deploy coulomb-social-status coulomb-social-logs coulomb-social-env-secret coulomb-social-env-secret-dry-run eso-deploy forgejo-openbao-eso-token-apply forgejo-openbao-store-deploy forgejo-mailer-es-deploy forgejo-mailer-es-status forgejo-dry-run forgejo-server-dry-run forgejo-deploy forgejo-ingress-deploy forgejo-ssh-nodeport-deploy forgejo-status forgejo-smoke forgejo-npm-smoke forgejo-verify forgejo-operator-bootstrap forgejo-runner-registration-sops-bootstrap forgejo-secrets-check forgejo-logs forgejo-runner-registration-deploy forgejo-runner-deploy forgejo-runner-status forgejo-runner-logs inter-hub-render-baseline inter-hub-dry-run inter-hub-server-dry-run inter-hub-deploy inter-hub-status inter-hub-release-info inter-hub-smoke inter-hub-logs reuse-dry-run reuse-deploy reuse-status reuse-smoke reuse-logs reuse-forgejo-webhook reuse-openbao-eso-token-apply reuse-openbao-store-deploy reuse-runtime-es-deploy reuse-runtime-es-status reuse-webhook-smoke help

View file

@ -9,15 +9,51 @@ Ingress: `manifests/coulomb-social-ingress.yaml`
- Image published: `forgejo.coulomb.social/coulomb/coulomb-social:<sha>`
- KeyCape client `coulomb-social` registered (see source repo `scripts/register-keycape-client.sh`)
- K8s Secret `coulomb-social-env` in namespace `coulomb-social` with at least:
- `SECRET_KEY`
- `DATABASE_URL` (or platform DB handoff)
- `USER_ENGINE_PROXY_SECRET` (from user-engine-runtime)
- K8s Secret `coulomb-social-env` (see **Env secret** below)
- Optional: DB role Secret `coulomb-social-app-credentials` in the app namespace
(cnpg mirror; same pattern as vergabe) before DATABASE_URL can be filled
- DNS `coulomb.social` → cluster ingress IP
## Env secret
Opaque Secret `coulomb-social-env` is mounted via `envFrom` on the Deployment.
**Never commit values.** Create/patch with:
```bash
# plan only (key names, no values)
make coulomb-social-env-secret-dry-run
# apply: SECRET_KEY (generate or keep), DATABASE_URL (if DB secret exists),
# USER_ENGINE_PROXY_SECRET (copied from user-engine/user-engine-runtime)
make coulomb-social-env-secret
# options via COULOMB_SOCIAL_ENV_SECRET_ARGS:
make coulomb-social-env-secret COULOMB_SOCIAL_ENV_SECRET_ARGS='--rotate-secret-key'
make coulomb-social-env-secret COULOMB_SOCIAL_ENV_SECRET_ARGS='--skip-db'
```
Script: `tools/create-coulomb-social-env-secret.sh`
Source-repo wrapper: `coulomb-social/scripts/create-env-secret.sh`
| Key | Source |
|-----|--------|
| `SECRET_KEY` | generated (or kept if present; `--rotate-secret-key` forces new) |
| `DATABASE_URL` | URL-encoded password from `coulomb-social-app-credentials` |
| `USER_ENGINE_PROXY_SECRET` | `user-engine/user-engine-runtime` key `proxy-secret` |
Non-secret OIDC/issuer settings live in `helm/coulomb-social-values.yaml`, not
this Secret.
After updating an existing deploy:
```bash
kubectl -n coulomb-social rollout restart deploy/coulomb-social
```
## Deploy
```bash
make coulomb-social-env-secret
COULOMB_SOCIAL_IMAGE_TAG=<git-sha> make coulomb-social-dry-run
COULOMB_SOCIAL_IMAGE_TAG=<git-sha> make coulomb-social-deploy
make coulomb-social-ingress-deploy

View file

@ -0,0 +1,273 @@
#!/usr/bin/env bash
# Create or patch the coulomb-social-env Opaque Secret used by the Helm chart
# (envFrom). Never prints secret values to stdout/stderr.
#
# Required keys for a full production deploy:
# SECRET_KEY
# DATABASE_URL
# USER_ENGINE_PROXY_SECRET
#
# Usage:
# make coulomb-social-env-secret
# ./tools/create-coulomb-social-env-secret.sh [options]
#
# Options:
# --dry-run Plan only (list keys, no apply)
# --rotate-secret-key Always generate a new Django SECRET_KEY
# --skip-db Do not set DATABASE_URL
# --skip-user-engine Do not copy USER_ENGINE_PROXY_SECRET
#
# Environment overrides (defaults match Makefile / chart):
# COULOMB_SOCIAL_NAMESPACE, COULOMB_SOCIAL_ENV_SECRET
# COULOMB_SOCIAL_DB_SECRET, COULOMB_SOCIAL_DB_USER, COULOMB_SOCIAL_DB_HOST,
# COULOMB_SOCIAL_DB_PORT, COULOMB_SOCIAL_DB_NAME, COULOMB_SOCIAL_DB_PASSWORD_KEY
# USER_ENGINE_NAMESPACE, USER_ENGINE_RUNTIME_SECRET, USER_ENGINE_PROXY_SECRET_KEY
set -euo pipefail
usage() {
sed -n '2,28p' "$0" | sed 's/^# \?//'
}
DRY_RUN=0
ROTATE_SECRET_KEY=0
SKIP_DB=0
SKIP_USER_ENGINE=0
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=1; shift ;;
--rotate-secret-key) ROTATE_SECRET_KEY=1; shift ;;
--skip-db) SKIP_DB=1; shift ;;
--skip-user-engine) SKIP_USER_ENGINE=1; shift ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;;
esac
done
NS="${COULOMB_SOCIAL_NAMESPACE:-coulomb-social}"
ENV_SECRET="${COULOMB_SOCIAL_ENV_SECRET:-coulomb-social-env}"
DB_SECRET="${COULOMB_SOCIAL_DB_SECRET:-coulomb-social-app-credentials}"
DB_USER="${COULOMB_SOCIAL_DB_USER:-coulomb_social}"
DB_HOST="${COULOMB_SOCIAL_DB_HOST:-apps-pg-rw.databases}"
DB_PORT="${COULOMB_SOCIAL_DB_PORT:-5432}"
DB_NAME="${COULOMB_SOCIAL_DB_NAME:-coulomb_social_db}"
DB_PASSWORD_KEY="${COULOMB_SOCIAL_DB_PASSWORD_KEY:-password}"
DB_SCHEME="${COULOMB_SOCIAL_DB_SCHEME:-postgresql}"
UE_NS="${USER_ENGINE_NAMESPACE:-user-engine}"
UE_RUNTIME_SECRET="${USER_ENGINE_RUNTIME_SECRET:-user-engine-runtime}"
UE_PROXY_KEY="${USER_ENGINE_PROXY_SECRET_KEY:-proxy-secret}"
for cmd in kubectl python3 openssl base64 mktemp; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "ERROR: missing required command: $cmd" >&2
exit 1
fi
done
secret_exists() {
kubectl get secret "$2" -n "$1" >/dev/null 2>&1
}
secret_has_key() {
local b64
b64="$(kubectl get secret "$2" -n "$1" -o "jsonpath={.data.${3}}" 2>/dev/null || true)"
[[ -n "$b64" ]]
}
read_secret_plain() {
local b64
b64="$(kubectl get secret "$2" -n "$1" -o "jsonpath={.data.${3}}" 2>/dev/null || true)"
[[ -n "$b64" ]] || return 1
printf '%s' "$b64" | base64 -d
}
gen_secret_key() {
openssl rand -base64 48 | tr -d '\n' | tr '/+=' 'xyz'
}
# Staging file: JSON object of stringData keys (mode 0600).
STAGE="$(mktemp)"
chmod 600 "$STAGE"
echo '{}' >"$STAGE"
trap 'rm -f "$STAGE"' EXIT
stage_put() {
local key="$1" value="$2"
KEY="$key" VALUE="$value" STAGE="$STAGE" python3 - <<'PY'
import json, os
path = os.environ["STAGE"]
with open(path, encoding="utf-8") as f:
data = json.load(f)
data[os.environ["KEY"]] = os.environ["VALUE"]
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, separators=(",", ":"))
PY
}
PLANNED=()
SKIPPED=()
WARNINGS=()
plan() { PLANNED+=("$1"); }
skip() { SKIPPED+=("$1"); }
warn() { WARNINGS+=("$1"); }
# ── SECRET_KEY ──────────────────────────────────────────────────────────────
if [[ "$ROTATE_SECRET_KEY" -eq 1 ]]; then
stage_put SECRET_KEY "$(gen_secret_key)"
plan "SECRET_KEY (rotated)"
elif secret_exists "$NS" "$ENV_SECRET" && secret_has_key "$NS" "$ENV_SECRET" SECRET_KEY; then
skip "SECRET_KEY (kept existing)"
else
stage_put SECRET_KEY "$(gen_secret_key)"
plan "SECRET_KEY (generated)"
fi
# ── DATABASE_URL ────────────────────────────────────────────────────────────
if [[ "$SKIP_DB" -eq 1 ]]; then
skip "DATABASE_URL (--skip-db)"
elif ! secret_exists "$NS" "$DB_SECRET"; then
warn "DB secret $NS/$DB_SECRET missing — skip DATABASE_URL (create app DB credentials first)"
elif ! secret_has_key "$NS" "$DB_SECRET" "$DB_PASSWORD_KEY"; then
warn "DB secret key '$DB_PASSWORD_KEY' missing — skip DATABASE_URL"
else
raw_password="$(read_secret_plain "$NS" "$DB_SECRET" "$DB_PASSWORD_KEY")"
if [[ -z "${raw_password}" ]]; then
warn "DB password empty — skip DATABASE_URL"
else
encoded_password="$(
RAW_PASSWORD="$raw_password" python3 -c \
'import os, urllib.parse; print(urllib.parse.quote(os.environ["RAW_PASSWORD"], safe=""))'
)"
unset raw_password
stage_put DATABASE_URL \
"${DB_SCHEME}://${DB_USER}:${encoded_password}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
unset encoded_password
plan "DATABASE_URL (from $NS/$DB_SECRET)"
fi
fi
# ── USER_ENGINE_PROXY_SECRET ────────────────────────────────────────────────
if [[ "$SKIP_USER_ENGINE" -eq 1 ]]; then
skip "USER_ENGINE_PROXY_SECRET (--skip-user-engine)"
elif ! secret_exists "$UE_NS" "$UE_RUNTIME_SECRET"; then
warn "user-engine secret $UE_NS/$UE_RUNTIME_SECRET missing — skip USER_ENGINE_PROXY_SECRET"
elif ! secret_has_key "$UE_NS" "$UE_RUNTIME_SECRET" "$UE_PROXY_KEY"; then
warn "user-engine secret key '$UE_PROXY_KEY' missing — skip USER_ENGINE_PROXY_SECRET"
else
proxy="$(read_secret_plain "$UE_NS" "$UE_RUNTIME_SECRET" "$UE_PROXY_KEY")"
if [[ -z "${proxy}" ]]; then
warn "USER_ENGINE_PROXY_SECRET empty after decode — skip"
else
stage_put USER_ENGINE_PROXY_SECRET "$proxy"
unset proxy
plan "USER_ENGINE_PROXY_SECRET (from $UE_NS/$UE_RUNTIME_SECRET)"
fi
fi
# ── report ──────────────────────────────────────────────────────────────────
echo "==> coulomb-social env secret plan"
echo " namespace: $NS"
echo " secret: $ENV_SECRET"
if [[ ${#PLANNED[@]} -gt 0 ]]; then
echo " set/update:"
for k in "${PLANNED[@]}"; do echo " + $k"; done
else
echo " set/update: (none)"
fi
if [[ ${#SKIPPED[@]} -gt 0 ]]; then
echo " skipped:"
for k in "${SKIPPED[@]}"; do echo " · $k"; done
fi
if [[ ${#WARNINGS[@]} -gt 0 ]]; then
echo " warnings:"
for k in "${WARNINGS[@]}"; do echo " ! $k"; done
fi
KEY_COUNT="$(python3 -c 'import json,sys; print(len(json.load(open(sys.argv[1]))))' "$STAGE")"
if [[ "$KEY_COUNT" -eq 0 ]]; then
echo "Nothing to apply."
exit 0
fi
if [[ "$DRY_RUN" -eq 1 ]]; then
echo "Dry-run: no changes applied ($KEY_COUNT key(s) would be written)."
exit 0
fi
# ── apply ───────────────────────────────────────────────────────────────────
kubectl create namespace "$NS" --dry-run=client -o yaml | kubectl apply -f - >/dev/null
STAGE="$STAGE" NS="$NS" ENV_SECRET="$ENV_SECRET" python3 - <<'PY'
import json
import os
import subprocess
import tempfile
stage = os.environ["STAGE"]
ns = os.environ["NS"]
name = os.environ["ENV_SECRET"]
with open(stage, encoding="utf-8") as f:
string_data = json.load(f)
exists = (
subprocess.call(
["kubectl", "get", "secret", name, "-n", ns],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
== 0
)
if exists:
body = json.dumps({"stringData": string_data})
with tempfile.NamedTemporaryFile("w", delete=False, encoding="utf-8") as tf:
tf.write(body)
path = tf.name
try:
os.chmod(path, 0o600)
subprocess.check_call(
[
"kubectl",
"patch",
"secret",
name,
"-n",
ns,
"--type=merge",
f"--patch-file={path}",
]
)
finally:
os.unlink(path)
print(f"Patched secret {ns}/{name} ({len(string_data)} key(s)).")
else:
args = ["kubectl", "create", "secret", "generic", name, "-n", ns]
files = []
try:
for key, value in string_data.items():
with tempfile.NamedTemporaryFile("w", delete=False, encoding="utf-8") as tf:
tf.write(value)
path = tf.name
os.chmod(path, 0o600)
files.append(path)
args.append(f"--from-file={key}={path}")
subprocess.check_call(args)
print(f"Created secret {ns}/{name} ({len(string_data)} key(s)).")
finally:
for path in files:
try:
os.unlink(path)
except OSError:
pass
PY
echo "Keys now present (names only):"
kubectl get secret "$ENV_SECRET" -n "$NS" -o json \
| python3 -c 'import sys,json; d=json.load(sys.stdin).get("data") or {}; print(" ", ", ".join(sorted(d.keys())) or "(none)")'
echo
echo "Next:"
echo " COULOMB_SOCIAL_IMAGE_TAG=<sha> make coulomb-social-deploy"
echo " kubectl -n $NS rollout restart deploy/coulomb-social # if secret updated under a running deploy"