#!/usr/bin/env bash set -euo pipefail root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) gate=$root/deploy/docker/database_client_entrypoint.sh gochat_image=${GOCHAT_TEST_IMAGE:-gochat:production-smoke} postgres_image='pgvector/pgvector:pg16@sha256:ccc6e83d6e35e931dc7c5def2022729d5a6c370318d099181995567ff1fb4d6b' exporter_image='quay.io/prometheuscommunity/postgres-exporter:v0.17.1@sha256:38606faa38c54787525fb0ff2fd6b41b4cfb75d455c1df294927c5f611699b17' tmp=$(mktemp -d) network=gochat-db-gate-$$ database=gochat-db-gate-postgres-$$ exporter=gochat-db-gate-exporter-$$ cleanup() { docker rm -f "$exporter" "$database" >/dev/null 2>&1 || true docker network rm "$network" >/dev/null 2>&1 || true rm -rf "$tmp" } trap cleanup EXIT mkdir -p "$tmp/ca" "$tmp/server" "$tmp/client" "$tmp/denied" openssl req -x509 -newkey rsa:2048 -nodes -days 1 -subj '/CN=GoChat test CA' \ -keyout "$tmp/ca/ca.key" -out "$tmp/ca/ca.crt" >/dev/null 2>&1 openssl req -newkey rsa:2048 -nodes -subj '/CN=tls-db' \ -keyout "$tmp/server/server.key" -out "$tmp/server/server.csr" >/dev/null 2>&1 printf 'subjectAltName=DNS:tls-db\n' >"$tmp/server/server.ext" openssl x509 -req -days 1 -in "$tmp/server/server.csr" -CA "$tmp/ca/ca.crt" -CAkey "$tmp/ca/ca.key" \ -CAcreateserial -extfile "$tmp/server/server.ext" -out "$tmp/server/server.crt" >/dev/null 2>&1 openssl req -newkey rsa:2048 -nodes -subj '/CN=gochat-client' \ -keyout "$tmp/client/external-db-client.key" -out "$tmp/client/client.csr" >/dev/null 2>&1 openssl x509 -req -days 1 -in "$tmp/client/client.csr" -CA "$tmp/ca/ca.crt" -CAkey "$tmp/ca/ca.key" \ -CAcreateserial -out "$tmp/client/external-db-client.crt" >/dev/null 2>&1 cp "$tmp/ca/ca.crt" "$tmp/client/external-db-ca.crt" cp "$tmp/client/external-db-client.key" "$tmp/denied/external-db-client.key" tls_gid=4242 postgres_uid=$(docker run --rm --entrypoint id "$postgres_image" -u postgres) postgres_gid=$(docker run --rm --entrypoint id "$postgres_image" -g postgres) docker run --rm --entrypoint sh -v "$tmp:/work" "$postgres_image" -c \ "chown $postgres_uid:$postgres_gid /work/server/server.crt /work/server/server.key && chmod 0644 /work/server/server.crt && chmod 0600 /work/server/server.key && chown 0:$tls_gid /work/client/external-db-*.crt /work/client/external-db-client.key && chmod 0644 /work/client/external-db-*.crt && chmod 0640 /work/client/external-db-client.key && chown 0:0 /work/denied/external-db-client.key && chmod 0600 /work/denied/external-db-client.key" bad_dsn='postgres://postgres:test-password@tls-db:5432/postgres?sslmode=disable&sslmode=verify-full' reject_duplicate() { local label=$1 image=$2 variable=$3 user=$4 shift 4 local args=(docker run --rm -e "$variable=$bad_dsn" -v "$gate:/usr/local/bin/database-client-entrypoint:ro" --entrypoint /usr/local/bin/database-client-entrypoint) [[ -z $user ]] || args+=(--user "$user") if "${args[@]}" "$image" "$@" >"$tmp/$label.log" 2>&1; then echo "$label accepted duplicate sslmode" >&2 exit 1 fi grep -F 'exactly one sslmode' "$tmp/$label.log" >/dev/null } reject_duplicate gochat "$gochat_image" GOCHAT_DATABASE_DSN '' /app/gochat --help reject_duplicate worker "$gochat_image" GOCHAT_DATABASE_DSN '' /app/gochat worker reject_duplicate migrate "$gochat_image" GOCHAT_DATABASE_DSN '' /app/migrate version reject_duplicate backup "$gochat_image" GOCHAT_DATABASE_DSN 0:0 /app/scripts/db_backup.sh reject_duplicate restore "$gochat_image" GOCHAT_DATABASE_DSN 0:0 /app/scripts/db_restore.sh /tmp/missing.tar.enc reject_duplicate postgres-exporter "$exporter_image" DATA_SOURCE_NAME '' /bin/postgres_exporter --version production_env=( -e GOCHAT_ENV=production -e GOCHAT_DATABASE_DSN="$bad_dsn" -e GOCHAT_REDIS_DSN=redis://:test-password@redis:6379 -e GOCHAT_JWT_SECRET=ci-smoke-jwt-secret-at-least-32-characters -e GOCHAT_SEARCH_API_KEY=ci-meili-secret-16 ) for command in serve worker; do if docker run --rm "${production_env[@]}" --entrypoint /app/gochat "$gochat_image" "$command" >"$tmp/direct-$command.log" 2>&1; then echo "direct GoChat $command accepted duplicate sslmode" >&2 exit 1 fi grep -F 'production database DSN must contain exactly one sslmode' "$tmp/direct-$command.log" >/dev/null done if docker run --rm "${production_env[@]}" --entrypoint /app/migrate "$gochat_image" version >"$tmp/direct-migrate.log" 2>&1; then echo 'direct migrate accepted duplicate sslmode' >&2 exit 1 fi grep -F 'production database DSN must contain exactly one sslmode' "$tmp/direct-migrate.log" >/dev/null for client in backup restore; do script=/app/scripts/db_${client}.sh args=() [[ $client == backup ]] || args=(/tmp/missing.tar.enc) if docker run --rm --user 0:0 -e GOCHAT_DATABASE_DSN="$bad_dsn" --entrypoint "$script" "$gochat_image" "${args[@]}" >"$tmp/direct-$client.log" 2>&1; then echo "direct $client accepted duplicate sslmode" >&2 exit 1 fi grep -F 'database DSN must contain exactly one sslmode' "$tmp/direct-$client.log" >/dev/null done test "$(docker run --rm --entrypoint id "$gochat_image" -u)" -ne 0 test "$(docker run --rm --entrypoint id "$exporter_image" -u)" -ne 0 dsn='postgres://postgres:test-password@tls-db:5432/postgres?sslmode=verify-full&sslrootcert=/run/secrets/external-db-ca.crt&sslcert=/run/secrets/external-db-client.crt&sslkey=/run/secrets/external-db-client.key' if docker run --rm --group-add "$tls_gid" -e GOCHAT_DATABASE_DSN="$dsn" \ -v "$gate:/usr/local/bin/database-client-entrypoint:ro" \ -v "$tmp/client/external-db-ca.crt:/run/secrets/external-db-ca.crt:ro" \ -v "$tmp/client/external-db-client.crt:/run/secrets/external-db-client.crt:ro" \ -v "$tmp/denied/external-db-client.key:/run/secrets/external-db-client.key:ro" \ --entrypoint /usr/local/bin/database-client-entrypoint "$gochat_image" /bin/true >"$tmp/permissions.log" 2>&1; then echo 'non-root GoChat client read a root-owned 0600 private key' >&2 exit 1 fi grep -F 'database TLS file is missing or unreadable' "$tmp/permissions.log" >/dev/null docker network create "$network" >/dev/null docker run -d --name "$database" --network "$network" --network-alias tls-db \ -e POSTGRES_PASSWORD=test-password \ -v "$tmp/server/server.crt:/certs/server.crt:ro" \ -v "$tmp/server/server.key:/certs/server.key:ro" \ "$postgres_image" -c ssl=on -c ssl_cert_file=/certs/server.crt -c ssl_key_file=/certs/server.key >/dev/null for _ in $(seq 1 30); do docker exec "$database" pg_isready -U postgres >/dev/null 2>&1 && break sleep 1 done docker exec "$database" pg_isready -U postgres >/dev/null psql_result=$(docker run --rm --network "$network" --group-add "$tls_gid" -e GOCHAT_DATABASE_DSN="$dsn" \ -v "$gate:/usr/local/bin/database-client-entrypoint:ro" \ -v "$tmp/client/external-db-ca.crt:/run/secrets/external-db-ca.crt:ro" \ -v "$tmp/client/external-db-client.crt:/run/secrets/external-db-client.crt:ro" \ -v "$tmp/client/external-db-client.key:/run/secrets/external-db-client.key:ro" \ --entrypoint /usr/local/bin/database-client-entrypoint "$gochat_image" psql "$dsn" -Atqc 'SELECT 1') test "$psql_result" = 1 docker run -d --name "$exporter" --network "$network" --group-add "$tls_gid" -e DATA_SOURCE_NAME="$dsn" \ -v "$gate:/usr/local/bin/database-client-entrypoint:ro" \ -v "$tmp/client/external-db-ca.crt:/run/secrets/external-db-ca.crt:ro" \ -v "$tmp/client/external-db-client.crt:/run/secrets/external-db-client.crt:ro" \ -v "$tmp/client/external-db-client.key:/run/secrets/external-db-client.key:ro" \ --entrypoint /usr/local/bin/database-client-entrypoint "$exporter_image" /bin/postgres_exporter >/dev/null for _ in $(seq 1 20); do if docker run --rm --network "$network" --entrypoint sh "$gochat_image" -c \ 'curl -fsS http://gochat-db-gate-exporter-'"$$"':9187/metrics' >"$tmp/metrics" 2>/dev/null; then break fi sleep 1 done grep -E '^pg_up 1$' "$tmp/metrics" >/dev/null echo 'database client entrypoint tests passed'