Skip to content

Commit cccfd6d

Browse files
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. That block also makes pg_stat_statements, auto_explain, pgaudit and pg_prewarm unreachable, since a module that allocates shared memory at postmaster start cannot be loaded per session. CREATE EXTENSION pg_stat_statements succeeds today, but every read of the view then fails with 'pg_stat_statements must be loaded via "shared_preload_libraries"'. postgresql.sharedPreloadLibraries exposes the capability without the vector. 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 into its own defaults. 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. 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 through the instance manager, without restarting the pod. Assisted-by: LLM Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Signed-off-by: Mattia Eleuteri <[email protected]>
1 parent a1b32f2 commit cccfd6d

8 files changed

Lines changed: 186 additions & 5 deletions

File tree

‎api/apps/v1alpha1/postgresql/types.go‎

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎api/apps/v1alpha1/postgresql/zz_generated.deepcopy.go‎

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/apps/postgres/README.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,11 @@ For `sslmode=verify-full` to work, the CA bundle retrieved above must be saved t
225225

226226
### Application-specific parameters
227227

228-
| Name | Description | Type | Value |
229-
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ----- |
230-
| `postgresql` | PostgreSQL server configuration. | `object` | `{}` |
231-
| `postgresql.parameters` | PostgreSQL server parameters. Values may be strings or integers; integers are coerced to strings by the template (e.g. both `max_connections: 100` and `max_connections: "100"` are accepted). BLOCKED (enable arbitrary code execution): archive_command, restore_command, ssl_passphrase_command, archive_cleanup_command, recovery_end_command, dynamic_library_path, local_preload_libraries, session_preload_libraries, shared_preload_libraries. Do NOT override CloudNativePG-managed parameters: archive_mode, primary_conninfo, wal_level, max_replication_slots. | `map[string]intOrString` | `{}` |
228+
| Name | Description | Type | Value |
229+
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ----- |
230+
| `postgresql` | PostgreSQL server configuration. | `object` | `{}` |
231+
| `postgresql.sharedPreloadLibraries` | Libraries loaded at server start, restricted to an allowlist of modules already shipped in the PostgreSQL image. Required by modules that allocate shared memory and cannot be loaded per session, `pg_stat_statements` and `auto_explain` among them. Changing the list restarts PostgreSQL. | `[]string` | `[]` |
232+
| `postgresql.parameters` | PostgreSQL server parameters. Values may be strings or integers; integers are coerced to strings by the template (e.g. both `max_connections: 100` and `max_connections: "100"` are accepted). BLOCKED (enable arbitrary code execution): archive_command, restore_command, ssl_passphrase_command, archive_cleanup_command, recovery_end_command, dynamic_library_path, local_preload_libraries, session_preload_libraries, shared_preload_libraries. Do NOT override CloudNativePG-managed parameters: archive_mode, primary_conninfo, wal_level, max_replication_slots. | `map[string]intOrString` | `{}` |
232233

233234

234235
### Quorum-based synchronous replication

‎packages/apps/postgres/templates/db.yaml‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,25 @@ spec:
255255
{{- end }}
256256
{{- end }}
257257
postgresql:
258+
{{- /*
259+
Preloading is exposed as a closed allowlist rather than through the blocked
260+
shared_preload_libraries parameter. The parameter is free text and accepts a
261+
path, so it can point at a writable location; a bare name from this list can
262+
only resolve inside $libdir, which lives on the read-only image filesystem.
263+
The names below all ship with the CloudNativePG PostgreSQL image. The enum in
264+
values.schema.json is the primary guard; this check backs it up for renders
265+
that bypass schema validation.
266+
*/ -}}
267+
{{- with .Values.postgresql.sharedPreloadLibraries }}
268+
{{- $allowedPreload := list "pg_stat_statements" "auto_explain" "pgaudit" "pg_prewarm" }}
269+
shared_preload_libraries:
270+
{{- range . }}
271+
{{- if not (has . $allowedPreload) }}
272+
{{- fail (printf "SECURITY: shared preload library %q is not allowed. Allowed: %s. Loading a library from an arbitrary path enables code execution inside the instance." . (join ", " $allowedPreload)) }}
273+
{{- end }}
274+
- {{ quote . }}
275+
{{- end }}
276+
{{- end }}
258277
parameters:
259278
{{- $defaults := dict "max_wal_senders" "30" }}
260279
{{- $params := merge (dict) .Values.postgresql.parameters $defaults }}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
suite: shared preload libraries allowlist
2+
3+
# postgresql.sharedPreloadLibraries maps onto the CloudNativePG Cluster field of the
4+
# same meaning, which the operator renders into postgresql.conf and which is additive
5+
# to the operator's own defaults.
6+
#
7+
# It exists because shared_preload_libraries stays blocked in postgresql.parameters:
8+
# that parameter is free text and accepts a path, so it can point at a writable
9+
# location such as the PGDATA volume. A bare library name from this allowlist can only
10+
# resolve inside $libdir, which sits on the read-only image filesystem, so the code
11+
# execution vector the block defends against does not apply. Every name in the list
12+
# ships with the CloudNativePG PostgreSQL image.
13+
#
14+
# Preloading is the only way to enable modules that allocate shared memory at
15+
# postmaster start. pg_stat_statements is the motivating case: CREATE EXTENSION
16+
# succeeds without preloading, but every read of the view then fails with
17+
# 'pg_stat_statements must be loaded via "shared_preload_libraries"'.
18+
#
19+
# The enum in values.schema.json is the guard the negative cases below exercise: it
20+
# rejects a value before rendering starts. The template carries the same allowlist as
21+
# a backstop for renders that bypass schema validation, which is why those two checks
22+
# assert the schema error rather than the template one.
23+
24+
templates:
25+
- templates/db.yaml
26+
27+
release:
28+
name: pg-test
29+
namespace: tenant-test
30+
31+
set:
32+
_cluster:
33+
scheduling: {}
34+
cluster-domain: cozy.local
35+
36+
tests:
37+
- it: "the field is absent by default, leaving the operator defaults untouched"
38+
documentSelector:
39+
path: kind
40+
value: Cluster
41+
asserts:
42+
- notExists:
43+
path: spec.postgresql.shared_preload_libraries
44+
45+
- it: "an allowed library is rendered onto the CNPG field"
46+
set:
47+
postgresql:
48+
sharedPreloadLibraries:
49+
- pg_stat_statements
50+
documentSelector:
51+
path: kind
52+
value: Cluster
53+
asserts:
54+
- equal:
55+
path: spec.postgresql.shared_preload_libraries
56+
value:
57+
- pg_stat_statements
58+
59+
- it: "order is preserved for several allowed libraries"
60+
set:
61+
postgresql:
62+
sharedPreloadLibraries:
63+
- pg_stat_statements
64+
- auto_explain
65+
- pgaudit
66+
- pg_prewarm
67+
documentSelector:
68+
path: kind
69+
value: Cluster
70+
asserts:
71+
- equal:
72+
path: spec.postgresql.shared_preload_libraries
73+
value:
74+
- pg_stat_statements
75+
- auto_explain
76+
- pgaudit
77+
- pg_prewarm
78+
79+
- it: "preloading does not disturb postgresql.parameters"
80+
set:
81+
postgresql:
82+
sharedPreloadLibraries:
83+
- pg_stat_statements
84+
parameters:
85+
max_connections: 200
86+
documentSelector:
87+
path: kind
88+
value: Cluster
89+
asserts:
90+
- equal:
91+
path: spec.postgresql.parameters.max_connections
92+
value: "200"
93+
- equal:
94+
path: spec.postgresql.parameters.max_wal_senders
95+
value: "30"
96+
97+
- it: "a library outside the allowlist is rejected by the schema"
98+
set:
99+
postgresql:
100+
sharedPreloadLibraries:
101+
- pg_cron
102+
asserts:
103+
- failedTemplate:
104+
errorPattern: "postgresql/sharedPreloadLibraries/0.*value must be one of 'pg_stat_statements', 'auto_explain', 'pgaudit', 'pg_prewarm'"
105+
106+
- it: "an absolute path is rejected by the schema, which is the vector the parameter block defends against"
107+
set:
108+
postgresql:
109+
sharedPreloadLibraries:
110+
- /var/lib/postgresql/data/pgdata/evil.so
111+
asserts:
112+
- failedTemplate:
113+
errorPattern: "postgresql/sharedPreloadLibraries/0.*value must be one of 'pg_stat_statements', 'auto_explain', 'pgaudit', 'pg_prewarm'"
114+
115+
- it: "shared_preload_libraries stays blocked as a parameter even when the allowlist is used"
116+
set:
117+
postgresql:
118+
sharedPreloadLibraries:
119+
- pg_stat_statements
120+
parameters:
121+
shared_preload_libraries: pg_stat_statements
122+
asserts:
123+
- failedTemplate:
124+
errorMessage: "SECURITY: Parameter 'shared_preload_libraries' is blocked. This parameter can enable arbitrary code execution and must not be set by tenants."

‎packages/apps/postgres/values.schema.json‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,19 @@
173173
],
174174
"x-kubernetes-int-or-string": true
175175
}
176+
},
177+
"sharedPreloadLibraries": {
178+
"description": "Libraries loaded at server start, restricted to an allowlist of modules already shipped in the PostgreSQL image. Required by modules that allocate shared memory and cannot be loaded per session, `pg_stat_statements` and `auto_explain` among them. Changing the list restarts PostgreSQL.",
179+
"type": "array",
180+
"items": {
181+
"type": "string",
182+
"enum": [
183+
"pg_stat_statements",
184+
"auto_explain",
185+
"pgaudit",
186+
"pg_prewarm"
187+
]
188+
}
176189
}
177190
}
178191
},

‎packages/apps/postgres/values.yaml‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,15 @@ tls: {}
100100
## @section Application-specific parameters
101101
##
102102

103+
##
104+
## @enum {string} PreloadLibrary
105+
## @value pg_stat_statements
106+
## @value auto_explain
107+
## @value pgaudit
108+
## @value pg_prewarm
109+
103110
## @typedef {struct} PostgreSQL - PostgreSQL server configuration.
111+
## @field {[]PreloadLibrary} [sharedPreloadLibraries] - Libraries loaded at server start, restricted to an allowlist of modules already shipped in the PostgreSQL image. Required by modules that allocate shared memory and cannot be loaded per session, `pg_stat_statements` and `auto_explain` among them. Changing the list restarts PostgreSQL.
104112
## @field {map[string]intOrString} [parameters] - PostgreSQL server parameters. Values may be strings or integers; integers are coerced to strings by the template (e.g. both `max_connections: 100` and `max_connections: "100"` are accepted). BLOCKED (enable arbitrary code execution): archive_command, restore_command, ssl_passphrase_command, archive_cleanup_command, recovery_end_command, dynamic_library_path, local_preload_libraries, session_preload_libraries, shared_preload_libraries. Do NOT override CloudNativePG-managed parameters: archive_mode, primary_conninfo, wal_level, max_replication_slots.
105113

106114
## @param {PostgreSQL} postgresql - PostgreSQL server configuration.
@@ -120,6 +128,12 @@ postgresql:
120128
## dynamic_library_path, local_preload_libraries, session_preload_libraries,
121129
## shared_preload_libraries (arbitrary .so loading)
122130
##
131+
## To preload a module, use the `sharedPreloadLibraries` allowlist instead of the
132+
## blocked `shared_preload_libraries` parameter:
133+
## sharedPreloadLibraries:
134+
## - pg_stat_statements
135+
## - auto_explain
136+
##
123137
## IMPORTANT: Do NOT override CloudNativePG-managed parameters:
124138
## archive_mode, primary_conninfo, wal_level, max_replication_slots
125139
##

‎packages/system/postgres-rd/cozyrds/postgres.yaml‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)