Files
ljwx-deploy/apps/ljwx-health/base/provision-db-app-password.sh
T

65 lines
2.7 KiB
Bash
Executable File

#!/bin/bash
# Provision the restricted PostgreSQL runtime role without printing its secret.
set -euo pipefail
NAMESPACE="${NAMESPACE:-ljwx-health}"
SECRET_NAME="${SECRET_NAME:-ljwx-health-secret}"
POSTGRES_NAMESPACE="${POSTGRES_NAMESPACE:-infra}"
POSTGRES_POD="${POSTGRES_POD:-postgres-infra-postgresql-0}"
POSTGRES_DATABASE="${POSTGRES_DATABASE:-ljwx_health}"
APP_ROLE="${APP_ROLE:-ljwx_app}"
DB_HOST="${DB_HOST:-postgres-lb.infra.svc.cluster.local}"
DB_PORT="${DB_PORT:-5432}"
if [[ ! "${APP_ROLE}" =~ ^[a-z_][a-z0-9_]*$ ]]; then
echo "APP_ROLE must be a lowercase PostgreSQL identifier" >&2
exit 1
fi
if ! kubectl -n "${NAMESPACE}" get secret "${SECRET_NAME}" >/dev/null 2>&1; then
echo "missing secret ${NAMESPACE}/${SECRET_NAME}" >&2
exit 1
fi
app_password="${DB_APP_PASSWORD:-}"
if [[ -z "${app_password}" ]]; then
app_password="$(od -An -N32 -tx1 /dev/urandom | tr -d ' \n')"
fi
if [[ ! "${app_password}" =~ ^[0-9A-Za-z._~-]{32,128}$ ]]; then
echo "DB_APP_PASSWORD must contain 32-128 URL-safe characters" >&2
exit 1
fi
# The generated alphabet needs no SQL escaping. Feed the DDL through stdin so
# the password does not appear in argv, terminal output, or shell history.
printf "ALTER ROLE %s LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS PASSWORD '%s';\n" \
"${APP_ROLE}" "${app_password}" \
| kubectl -n "${POSTGRES_NAMESPACE}" exec -i "${POSTGRES_POD}" -- \
psql -U postgres -d "${POSTGRES_DATABASE}" -v ON_ERROR_STOP=1 -f - >/dev/null
# Prove TCP password authentication and role flags before publishing the key.
actual_flags="$({ printf '%s\n' "${app_password}"; } \
| kubectl -n "${POSTGRES_NAMESPACE}" exec -i "${POSTGRES_POD}" -- sh -ec '
IFS= read -r PGPASSWORD
export PGPASSWORD
psql --host="$1" --port="$2" --username="$3" --dbname="$4" \
--no-password --tuples-only --no-align \
--command="SELECT current_user || '\''|'\'' || rolsuper || '\''|'\'' || rolbypassrls FROM pg_roles WHERE rolname = current_user"
' sh "${DB_HOST}" "${DB_PORT}" "${APP_ROLE}" "${POSTGRES_DATABASE}")"
if [[ "${actual_flags}" != "${APP_ROLE}|false|false" ]]; then
echo "restricted-role verification failed" >&2
exit 1
fi
# Preserve every existing Secret key and merge only DB_APP_PASSWORD. The patch
# travels over stdin, so neither the plaintext nor base64 value appears in argv.
DB_APP_PASSWORD_B64="$(printf '%s' "${app_password}" | base64 | tr -d '\n')"
printf '{"data":{"DB_APP_PASSWORD":"%s"}}\n' "${DB_APP_PASSWORD_B64}" \
| kubectl -n "${NAMESPACE}" patch secret "${SECRET_NAME}" \
--type=merge --patch-file=/dev/stdin >/dev/null
unset DB_APP_PASSWORD_B64
unset app_password
echo "provisioned ${APP_ROLE}: LOGIN/NOSUPERUSER/NOBYPASSRLS; secret key updated without disclosure"