Files
cozystack/hack/check-postgres-preload-allowlist.bats
Mattia Eleuteri 25cdd51881 feat(postgres): allow preloading an allowlist of shipped modules
shared_preload_libraries is blocked in postgresql.parameters, and
rightly so: the parameter is free text and accepts a path, so it can
point at a writable location such as the PGDATA volume. What the block
leaves is an indirect route. CloudNativePG preloads pg_stat_statements,
auto_explain and pgaudit by itself as soon as a parameter under their
prefix is set, such as pg_stat_statements.track, so enabling one of
them today means setting one of its tuning parameters, even to its
default value. pg_prewarm has no route at all, and its autoprewarm
worker only runs when the module is preloaded.

postgresql.sharedPreloadLibraries is the explicit switch. It is a
closed enum of four module names that already ship in the CloudNativePG
PostgreSQL image, rendered onto the CNPG Cluster field of the same
meaning, which the operator merges with the libraries it preloads on
its own. A bare name can only resolve inside $libdir, which sits on the
read-only image filesystem, and dynamic_library_path stays blocked, so
no path can be introduced. The blocklist on postgresql.parameters is
left untouched.

The template repeats the enum as a backstop for renders that skip
schema validation. helm-unittest always validates the schema and never
reaches it, so a bats suite renders with --skip-schema-validation to
keep the backstop, and its agreement with the enum, under test.

Verified on a live CloudNativePG 1.27.3 cluster: with the field set,
the operator writes shared_preload_libraries into postgresql.conf,
SHOW reports the modules, and pg_stat_statements returns rows.
Changing the list restarts PostgreSQL on every instance, the primary
included, through the instance manager and without restarting the pod.

Assisted-by: LLM
Signed-off-by: Mattia Eleuteri <mattia@hidora.io>
2026-09-29 17:02:44 +02:00

51 lines
2.4 KiB
Bash

#!/usr/bin/env bats
# Unit test: the template-side allowlist for postgresql.sharedPreloadLibraries
# in packages/apps/postgres/templates/db.yaml.
#
# The enum in values.schema.json rejects a bad name before rendering starts, and
# helm-unittest always validates the schema, so the chart's own suite never
# reaches the template check behind it. Rendering with --skip-schema-validation
# is the only way to exercise that backstop, and it is the case the backstop
# exists for.
REPO_ROOT="$(cd "$(dirname "${BATS_TEST_FILENAME:-$0}")/.." && pwd)"
CHART="$REPO_ROOT/packages/apps/postgres"
render_without_schema() {
helm template pg "$CHART" --namespace tenant-test --skip-schema-validation \
--set _cluster.cluster-domain=cozy.local \
--set "postgresql.sharedPreloadLibraries[0]=$1" 2>&1
}
@test "an allowed name renders when the schema is skipped" {
# Without this, the rejections below could pass on an unrelated render error.
out=$(render_without_schema pg_stat_statements) || { echo "$out" >&2; return 1; }
printf '%s\n' "$out" | grep -q 'shared_preload_libraries:'
}
@test "the template rejects a name outside the allowlist when the schema is skipped" {
if out=$(render_without_schema pg_cron); then
echo "render succeeded with pg_cron preloaded" >&2
return 1
fi
printf '%s\n' "$out" | grep -qF 'SECURITY: shared preload library "pg_cron" is not allowed.'
}
@test "the template rejects a library path when the schema is skipped" {
if out=$(render_without_schema /var/lib/postgresql/data/pgdata/evil.so); then
echo "render succeeded with a library path preloaded" >&2
return 1
fi
printf '%s\n' "$out" | grep -qF 'SECURITY: shared preload library "/var/lib/postgresql/data/pgdata/evil.so" is not allowed.'
}
@test "the template allowlist matches the schema enum" {
# The template names its list in the rejection message; the two lists drifting
# apart would leave one guard accepting what the other refuses.
out=$(render_without_schema pg_cron) && return 1
template=$(printf '%s\n' "$out" | sed -n 's/.* Allowed: \([^.]*\)\..*/\1/p' | tr -d ' ' | tr ',' '\n' | sort)
schema=$(jq -r '.properties.postgresql.properties.sharedPreloadLibraries.items.enum[]' "$CHART/values.schema.json" | sort)
[ -n "$template" ] || { echo "no allowlist found in: $out" >&2; return 1; }
[ "$template" = "$schema" ] || { printf 'template:\n%s\nschema:\n%s\n' "$template" "$schema" >&2; return 1; }
}