Files
cozystack/packages/apps/postgres/tests/shared_preload_libraries_test.yaml
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

126 lines
4.2 KiB
YAML

suite: shared preload libraries allowlist
# postgresql.sharedPreloadLibraries maps onto the CloudNativePG Cluster field of the
# same meaning, which the operator renders into postgresql.conf and which is additive
# to the operator's own defaults.
#
# It exists because shared_preload_libraries stays blocked in postgresql.parameters:
# that parameter is free text and accepts a path, so it can point at a writable
# location such as the PGDATA volume. A bare library name from this allowlist can only
# resolve inside $libdir, which sits on the read-only image filesystem, so the code
# execution vector the block defends against does not apply. Every name in the list
# ships with the CloudNativePG PostgreSQL image.
#
# CloudNativePG already preloads pg_stat_statements, auto_explain and pgaudit on its
# own when a parameter under their prefix is set (ManagedExtensions in its
# pkg/postgres/configuration.go). This list is the explicit switch that needs no
# tuning parameter, and the only route for pg_prewarm.
#
# The enum in values.schema.json is the guard the negative cases below exercise: it
# rejects a value before rendering starts. The template carries the same allowlist as
# a backstop for renders that bypass schema validation, which is why those two checks
# assert the schema error rather than the template one; the backstop itself is pinned
# by hack/check-postgres-preload-allowlist.bats.
templates:
- templates/db.yaml
release:
name: pg-test
namespace: tenant-test
set:
_cluster:
scheduling: {}
cluster-domain: cozy.local
tests:
- it: "the field is absent by default, leaving the operator defaults untouched"
documentSelector:
path: kind
value: Cluster
asserts:
- notExists:
path: spec.postgresql.shared_preload_libraries
- it: "an allowed library is rendered onto the CNPG field"
set:
postgresql:
sharedPreloadLibraries:
- pg_stat_statements
documentSelector:
path: kind
value: Cluster
asserts:
- equal:
path: spec.postgresql.shared_preload_libraries
value:
- pg_stat_statements
- it: "order is preserved for several allowed libraries"
set:
postgresql:
sharedPreloadLibraries:
- pg_stat_statements
- auto_explain
- pgaudit
- pg_prewarm
documentSelector:
path: kind
value: Cluster
asserts:
- equal:
path: spec.postgresql.shared_preload_libraries
value:
- pg_stat_statements
- auto_explain
- pgaudit
- pg_prewarm
- it: "preloading does not disturb postgresql.parameters"
set:
postgresql:
sharedPreloadLibraries:
- pg_stat_statements
parameters:
max_connections: 200
documentSelector:
path: kind
value: Cluster
asserts:
- equal:
path: spec.postgresql.parameters.max_connections
value: "200"
- equal:
path: spec.postgresql.parameters.max_wal_senders
value: "30"
- it: "a library outside the allowlist is rejected by the schema"
set:
postgresql:
sharedPreloadLibraries:
- pg_cron
asserts:
- failedTemplate:
errorPattern: "postgresql/sharedPreloadLibraries/0.*value must be one of 'pg_stat_statements', 'auto_explain', 'pgaudit', 'pg_prewarm'"
- it: "an absolute path is rejected by the schema, which is the vector the parameter block defends against"
set:
postgresql:
sharedPreloadLibraries:
- /var/lib/postgresql/data/pgdata/evil.so
asserts:
- failedTemplate:
errorPattern: "postgresql/sharedPreloadLibraries/0.*value must be one of 'pg_stat_statements', 'auto_explain', 'pgaudit', 'pg_prewarm'"
- it: "shared_preload_libraries stays blocked as a parameter even when the allowlist is used"
set:
postgresql:
sharedPreloadLibraries:
- pg_stat_statements
parameters:
shared_preload_libraries: pg_stat_statements
asserts:
- failedTemplate:
errorMessage: "SECURITY: Parameter 'shared_preload_libraries' is blocked. This parameter can enable arbitrary code execution and must not be set by tenants."