From 67747813cb8c17be8b2349647b9cc8930b15de2f Mon Sep 17 00:00:00 2001 From: SimpleTest Date: Mon, 3 Aug 2026 23:15:28 +0300 Subject: [PATCH] Reconcile development provider secrets --- README.md | 8 ++ scripts/kind-up.sh | 10 ++ scripts/quality.sh | 33 ++++++ scripts/sync-kind-runtime-secret.sh | 160 ++++++++++++++++++++++++++++ 4 files changed, 211 insertions(+) create mode 100755 scripts/sync-kind-runtime-secret.sh diff --git a/README.md b/README.md index 6ac0e11..0b3f701 100644 --- a/README.md +++ b/README.md @@ -669,6 +669,14 @@ that dump. After a successful rollout it also removes the obsolete chart Secret and only the local Helm history revisions that stored the former inline credential fields. +When the ignored mode-`0600` `.env` exists, every `kind-up.sh` run also +reconciles a fixed allowlist of development provider settings into that same +Secret: Google/GitHub sign-in, browser push, FCM delivery, Android App Links, +sender identity, and support routing. It never prints their values and does not +replace the independently generated database or application secrets. Empty +allowlisted values remove stale provider settings so `.env` remains the single +development source of truth. + Exercise the verified local rolling-update path without recreating PostGIS or the Secret: diff --git a/scripts/kind-up.sh b/scripts/kind-up.sh index 496e22f..0c864b8 100755 --- a/scripts/kind-up.sh +++ b/scripts/kind-up.sh @@ -144,6 +144,16 @@ elif [ -z "$(kube --namespace "$NAMESPACE" get secret "$SECRET_NAME" -o jsonpath unset metrics_token fi +if [ -f "$ROOT/.env" ]; then + KUBECTL_BIN="$ROOT/.tools/bin/kubectl" \ + KUBE_CONTEXT="kind-${CLUSTER}" \ + KUBE_NAMESPACE="$NAMESPACE" \ + KUBE_SECRET_NAME="$SECRET_NAME" \ + "$ROOT/scripts/sync-kind-runtime-secret.sh" "$ROOT/.env" +else + echo "No .env found; retained the existing optional kind runtime keys." +fi + if [ -n "$legacy_backup" ]; then kube --namespace "$NAMESPACE" delete deployment postgis --wait=true fi diff --git a/scripts/quality.sh b/scripts/quality.sh index 1913768..285d490 100755 --- a/scripts/quality.sh +++ b/scripts/quality.sh @@ -83,6 +83,39 @@ scan_image() { scan_image_sequence=0 +echo "Checking the development kind runtime Secret allowlist" +kind_runtime_env="$scan_dir/kind-runtime.env" +kind_runtime_rendered="$scan_dir/kind-runtime.rendered" +cat >"$kind_runtime_env" <<'EOF' +DATABASE_URL=must-not-be-copied +GOOGLE_OAUTH_CLIENT_ID=quality-google-id +GOOGLE_OAUTH_CLIENT_SECRET="quality-google-secret" +GOOGLE_OAUTH_AUTHORIZED_PARTY_IDS=quality-android-id +WEB_PUSH_VAPID_PUBLIC_KEY=quality-vapid-public +FCM_PROJECT_ID= +EOF +chmod 600 "$kind_runtime_env" +./scripts/sync-kind-runtime-secret.sh \ + "$kind_runtime_env" --render-only "$kind_runtime_rendered" >/dev/null +grep -Fx 'GOOGLE_OAUTH_CLIENT_ID=quality-google-id' \ + "$kind_runtime_rendered" >/dev/null +grep -Fx 'GOOGLE_OAUTH_CLIENT_SECRET=quality-google-secret' \ + "$kind_runtime_rendered" >/dev/null +grep -Fx 'GOOGLE_OAUTH_AUTHORIZED_PARTY_IDS=quality-android-id' \ + "$kind_runtime_rendered" >/dev/null +grep -Fx 'WEB_PUSH_VAPID_PUBLIC_KEY=quality-vapid-public' \ + "$kind_runtime_rendered" >/dev/null +if grep -Eq '^(DATABASE_URL|FCM_PROJECT_ID)=' "$kind_runtime_rendered"; then + echo "Kind runtime Secret renderer copied an unmanaged or empty value." >&2 + exit 1 +fi +printf '%s\n' 'GOOGLE_OAUTH_CLIENT_ID=duplicate' >>"$kind_runtime_env" +if ./scripts/sync-kind-runtime-secret.sh \ + "$kind_runtime_env" --render-only "$kind_runtime_rendered" >/dev/null 2>&1; then + echo "Kind runtime Secret renderer accepted a duplicate managed key." >&2 + exit 1 +fi + echo "Checking shell scripts with ShellCheck 0.11.0" # Word splitting is intentional: find emits repository-controlled paths and # ShellCheck expects each file as a separate argument. diff --git a/scripts/sync-kind-runtime-secret.sh b/scripts/sync-kind-runtime-secret.sh new file mode 100755 index 0000000..e211488 --- /dev/null +++ b/scripts/sync-kind-runtime-secret.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +set -euo pipefail +umask 077 + +ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) +env_file=${1:-"$ROOT/.env"} +mode=${2:-} +render_target=${3:-} + +if [[ "$env_file" != /* ]]; then + env_file="$ROOT/$env_file" +fi + +if [[ -n "$mode" && "$mode" != "--render-only" ]]; then + echo "Usage: $0 [ENV_FILE] [--render-only OUTPUT_FILE]" >&2 + exit 2 +fi + +if [[ "$mode" == "--render-only" && -z "$render_target" ]]; then + echo "Usage: $0 [ENV_FILE] [--render-only OUTPUT_FILE]" >&2 + exit 2 +fi + +[[ -f "$env_file" ]] || { + echo "Development environment does not exist: $env_file" >&2 + exit 2 +} + +[[ "$(stat -c '%a' "$env_file")" == 600 ]] || { + echo "Development environment must have mode 0600: $env_file" >&2 + exit 2 +} + +[[ "$(stat -c '%u' "$env_file")" == "$(id -u)" ]] || { + echo "Development environment must be owned by the current user: $env_file" >&2 + exit 2 +} + +managed_keys=( + ANDROID_APP_LINKS_PACKAGE_NAME + ANDROID_APP_LINKS_SHA256_CERT_FINGERPRINTS + EMAIL_FROM_ADDRESS + EMAIL_FROM_NAME + FCM_PROJECT_ID + FCM_SERVICE_ACCOUNT_JSON_BASE64 + GITHUB_OAUTH_CLIENT_ID + GITHUB_OAUTH_CLIENT_SECRET + GOOGLE_OAUTH_AUTHORIZED_PARTY_IDS + GOOGLE_OAUTH_CLIENT_ID + GOOGLE_OAUTH_CLIENT_SECRET + SUPPORT_INBOX_ADDRESS + SUPPORT_OPERATOR_EMAIL_MODE + WEB_PUSH_VAPID_PRIVATE_KEY + WEB_PUSH_VAPID_PUBLIC_KEY + WEB_PUSH_VAPID_SUBJECT +) + +runtime_dir=$(mktemp -d "${TMPDIR:-/tmp}/wnh-kind-runtime-secret.XXXXXX") +cleanup() { + status=$? + trap - EXIT HUP INT TERM + find "$runtime_dir" -xdev -depth -delete 2>/dev/null || true + exit "$status" +} +trap cleanup EXIT HUP INT TERM + +keys_file="$runtime_dir/managed-keys" +printf '%s\n' "${managed_keys[@]}" >"$keys_file" +filtered_env="$runtime_dir/runtime.env" + +awk -F= ' + NR == FNR { + managed[$1] = 1 + next + } + { + key = $1 + if (!(key in managed)) next + seen[key]++ + if (seen[key] > 1) { + printf "Managed development key occurs more than once: %s\n", key > "/dev/stderr" + invalid = 1 + next + } + value = substr($0, length(key) + 2) + if ((value ~ /^".*"$/) || (value ~ /^\047.*\047$/)) { + value = substr(value, 2, length(value) - 2) + } + if (length(value) > 0) print key "=" value + } + END { if (invalid) exit 1 } +' "$keys_file" "$env_file" >"$filtered_env" +chmod 600 "$filtered_env" + +if [[ "$mode" == "--render-only" ]]; then + case "$render_target" in + /*) ;; + *) render_target="$ROOT/$render_target" ;; + esac + install -m 600 "$filtered_env" "$render_target" + echo "Rendered managed development runtime keys without printing their values." + exit 0 +fi + +KUBECTL_BIN=${KUBECTL_BIN:-kubectl} +KUBE_CONTEXT=${KUBE_CONTEXT:-kind-who-need-help} +KUBE_NAMESPACE=${KUBE_NAMESPACE:-who-need-help} +KUBE_SECRET_NAME=${KUBE_SECRET_NAME:-who-need-help-local} + +existing_json="$runtime_dir/existing.json" +extra_json="$runtime_dir/extra.json" +desired_json="$runtime_dir/desired.json" +observed_json="$runtime_dir/observed.json" + +"$KUBECTL_BIN" --context "$KUBE_CONTEXT" --namespace "$KUBE_NAMESPACE" \ + get secret "$KUBE_SECRET_NAME" --output json >"$existing_json" + +if [[ -s "$filtered_env" ]]; then + "$KUBECTL_BIN" create secret generic "$KUBE_SECRET_NAME" \ + --namespace "$KUBE_NAMESPACE" \ + --from-env-file="$filtered_env" \ + --dry-run=client \ + --output json >"$extra_json" +else + printf '%s\n' '{"data":{}}' >"$extra_json" +fi + +jq --slurpfile extra "$extra_json" --rawfile managed "$keys_file" ' + ($managed | split("\n") | map(select(length > 0))) as $managed_keys + | .data = ( + ((.data // {}) + | with_entries(select(.key as $key | ($managed_keys | index($key) | not)))) + + ($extra[0].data // {}) + ) + | { + apiVersion: "v1", + kind: "Secret", + metadata: { + name: .metadata.name, + namespace: .metadata.namespace, + resourceVersion: .metadata.resourceVersion + }, + type: (.type // "Opaque"), + data: .data + } +' "$existing_json" >"$desired_json" + +"$KUBECTL_BIN" --context "$KUBE_CONTEXT" --namespace "$KUBE_NAMESPACE" \ + replace --filename "$desired_json" >/dev/null +"$KUBECTL_BIN" --context "$KUBE_CONTEXT" --namespace "$KUBE_NAMESPACE" \ + get secret "$KUBE_SECRET_NAME" --output json >"$observed_json" + +jq --exit-status --slurpfile desired "$desired_json" \ + '.data == $desired[0].data' "$observed_json" >/dev/null || { + echo "Kubernetes Secret verification did not match the desired key set." >&2 + exit 1 +} + +synced_count=$(wc -l <"$filtered_env" | tr -d '[:space:]') +echo "Synchronized $synced_count managed development runtime keys without printing their values."