Commit 4e57f7d
authored
# Squash Merge Notes: pgsodium, gzip, and pgzstd Extensions
**PR:** [#61](#61)
**Issue:** [#60](#60)
**Branch:** `paudley/pgsodium-gzip-pgzstd-extensions`
**Base:** `main` @ `93258e4`
**Squash commit:** `feat(postgres): add pgsodium, gzip, and pgzstd extensions (#60)`
---
## Overview
Adds three new PostgreSQL extensions to the core_data platform, all built from source since none are available as PGDG apt packages for PostgreSQL 17. Also introduces a startup enforcement mechanism for `shared_preload_libraries` to prevent configuration drift.
**Total diff:** 6 files changed, +130 insertions, -3 deletions
---
## Extensions Added
### pgsodium (v3.1.9)
- **Source:** [github.com/michelp/pgsodium](https://github.com/michelp/pgsodium)
- **Purpose:** Modern cryptographic functions wrapping libsodium — encryption, decryption, hashing, key derivation, digital signatures, and Transparent Column Encryption (TCE) with server-side key management.
- **Build dependency:** `libsodium-dev`
- **Runtime requirement:** Must be in `shared_preload_libraries` for key management and TCE features.
- **Key management:** Uses `pgsodium.getkey_script` pointing to `/opt/core_data/tools/pgsodium_getkey.sh`, which reads the root encryption key from `/opt/core_data/secrets/pgsodium.key` (configurable via `PGSODIUM_KEY_FILE` env var). The key must be exactly 32 bytes (256-bit) of raw key material.
- **Complements:** Existing `pgcrypto` extension. pgsodium provides libsodium's modern algorithms (XChaCha20-Poly1305, Argon2id, Ed25519) versus pgcrypto's OpenSSL-based primitives.
### pgsql-gzip (extension name: `gzip`)
- **Source:** [github.com/pramsey/pgsql-gzip](https://github.com/pramsey/pgsql-gzip)
- **Purpose:** SQL-native gzip compression and decompression of `bytea` data. Provides `gzip()` and `gunzip()` functions for compressing large text/binary payloads and interoperating with gzip-compressed data from external systems.
- **Build dependency:** `zlib1g-dev`
- **Runtime requirement:** None (no `shared_preload_libraries` entry needed).
### pgzstd
- **Source:** [github.com/grahamedgecombe/pgzstd](https://github.com/grahamedgecombe/pgzstd)
- **Purpose:** Zstandard (zstd) compression and decompression in SQL. Provides `zstd_compress()` and `zstd_decompress()` functions. Zstandard achieves better compression ratios than gzip at comparable speeds and significantly faster decompression — ideal for high-throughput data pipelines.
- **Build dependency:** `libzstd-dev`
- **Runtime requirement:** None (no `shared_preload_libraries` entry needed).
---
## File-by-File Changes
### `postgres/Dockerfile`
**Build dependencies added to apt-get install (alphabetically ordered):**
- `libsodium-dev` — required by pgsodium for libsodium C library headers
- `libzstd-dev` — required by pgzstd for Zstandard C library headers
- `zlib1g-dev` — required by pgsql-gzip for zlib C library headers (may already be present as transitive dependency, but made explicit)
**Three new `RUN` blocks added after the existing pg_squeeze build**, following the identical source-build pattern established by Apache AGE and pg_squeeze:
1. **pgsodium** — Clones tag `v3.1.9` with `--depth 1 --single-branch`, builds with `make`/`make install` using `PG_CONFIG`, cleans up temp directory. Version is parameterized via `ARG PGSODIUM_VERSION=3.1.9` for build-time configurability.
2. **pgsql-gzip** — Clones latest `--depth 1`, standard `make`/`make install`, cleanup. No version tag pinning (upstream does not tag releases consistently).
3. **pgzstd** — Clones latest `--depth 1`, standard `make`/`make install`, cleanup. No version tag pinning.
### `scripts/lib/extensions_list.sh`
**`CORE_EXTENSION_LIST` array — 3 entries added (lines 31-33):**
- `pgsodium` — placed after `pgcrypto` (logical grouping: crypto-related extensions)
- `gzip` — placed after `pgsodium`
- `pgzstd` — placed after `gzip` (compression extensions grouped together)
This array drives `02-enable-extensions.sh`, which iterates it to run `CREATE EXTENSION IF NOT EXISTS` in every non-template database plus `template1`. Adding entries here is sufficient for automatic creation in all databases at startup — no changes to `02-enable-extensions.sh` were needed.
**New `REQUIRED_PRELOAD_LIBRARIES` array added (lines 50-62):**
```
age, pgaudit, pg_stat_statements, pg_cron, pg_squeeze,
auto_explain, pg_buffercache, pg_partman_bgw, pgsodium
```
This is the canonical single source of truth for which libraries must be present in `shared_preload_libraries`. It is consumed by the new `enforce_shared_preload_libraries()` function in `00-render-config.sh`.
### `postgres/conf/postgresql.conf.tpl`
**Line 13 — `shared_preload_libraries` updated:**
- Before: `'age,pgaudit,pg_stat_statements,pg_cron,pg_squeeze,auto_explain,pg_buffercache,pg_partman_bgw'`
- After: `'age,pgaudit,pg_stat_statements,pg_cron,pg_squeeze,auto_explain,pg_buffercache,pg_partman_bgw,pgsodium'`
**Line 90 — new pgsodium configuration added:**
- `pgsodium.getkey_script = '/opt/core_data/tools/pgsodium_getkey.sh'` — tells pgsodium how to retrieve the root encryption key for TCE. Placed before `datestyle` at the end of the config file.
### `postgres/initdb/00-render-config.sh`
**New function `enforce_shared_preload_libraries()` (lines 18-49):**
Sources `extensions_list.sh` to get the `REQUIRED_PRELOAD_LIBRARIES` array, then:
1. Reads the current `shared_preload_libraries` value from `postgresql.conf`
2. Checks each required library against the current value using comma-delimited matching
3. If any are missing: appends them to the value, patches the config file in-place with `sed -i`, logs a WARNING with the missing entries, logs the corrected value, and returns exit code 1 (signaling restart needed)
4. If all present: returns 0 (no action needed)
**Early-exit path updated (lines 135-149):**
Previously, when the config sentinel existed and `FORCE_RENDER_CONFIG!=1`, the script only refreshed network allow entries and issued a `pg_ctl reload`. Now it also calls `enforce_shared_preload_libraries()`. If the function corrects the config (returns 1), a `pg_ctl -m fast -w restart` is issued instead of a reload — because `shared_preload_libraries` is a `postmaster`-context GUC that requires a full restart to take effect. A simple reload would silently leave the old libraries loaded.
**Post-render safety belt (lines 233-236):**
After a fresh template render (both first-time and `FORCE_RENDER_CONFIG=1`), `enforce_shared_preload_libraries()` runs as a safety belt to catch any drift between the template's hardcoded value and the canonical `REQUIRED_PRELOAD_LIBRARIES` array. This guards against the template and the list getting out of sync during development.
### `postgres/tools/pgsodium_getkey.sh` (NEW FILE)
Server key retrieval script for pgsodium's Transparent Column Encryption:
- Uses `set -euo pipefail` for strict error handling
- Reads key from `PGSODIUM_KEY_FILE` env var (default: `/opt/core_data/secrets/pgsodium.key`)
- If the key file is missing or not readable, prints an actionable error message with the exact command to generate a key, then exits with code 1
- On success, outputs the raw key to stdout (consumed by pgsodium internally)
### `scripts/lib/extensions.sh`
**`exercise_extensions()` — 5 new smoke queries added (lines 38-48):**
| Extension | Smoke Test | Validates |
|-----------|-----------|-----------|
| pgsodium | `pgsodium.crypto_pwhash_str('test_password')` | Argon2id password hashing works |
| gzip | `gzip('hello world')` + `gunzip(gzip(...))` | Compression produces output, roundtrip preserves data |
| pgzstd | `zstd_compress('hello world'::bytea)` + `zstd_decompress(...)` | Compression produces output, roundtrip preserves data |
Note: gzip roundtrip uses `convert_from(gunzip(...), 'UTF8')` because `gunzip()` returns `bytea`. pgzstd roundtrip compares `bytea` directly.
**`run_pgtap_smoke()` — plan count and assertions updated:**
- Plan count: `39` → `42` (3 new assertions)
- Added `has_extension('pgsodium', ...)`, `has_extension('gzip', ...)`, `has_extension('pgzstd', ...)` after the existing `pgcrypto` assertion (logical grouping)
---
## Architecture Decisions
### Why source builds (not apt packages)
None of these three extensions are available in the PGDG apt repository for PostgreSQL 17. All three follow the identical build pattern already established by Apache AGE and pg_squeeze: shallow git clone → `make` with `PG_CONFIG` → `make install` → cleanup.
### Why `REQUIRED_PRELOAD_LIBRARIES` is separate from `CORE_EXTENSION_LIST`
Not all extensions in `CORE_EXTENSION_LIST` require preloading (most don't — only 9 out of 49 do). A separate canonical list prevents accidentally adding non-preloadable extensions to `shared_preload_libraries`, which would cause postgres to fail to start.
### Why restart instead of reload for preload corrections
`shared_preload_libraries` is a `postmaster`-context GUC in PostgreSQL. It can only take effect when the server process starts. A `pg_ctl reload` (SIGHUP) updates `SIGHUP`-context parameters but silently ignores `postmaster`-context changes. Using restart ensures the correction actually takes effect.
### Why enforce on every startup
The config rendering has an early-exit path: once the sentinel file exists, the template is not re-rendered (unless `FORCE_RENDER_CONFIG=1`). This means manual edits to `postgresql.conf` that remove required preload libraries would persist across container restarts. The enforcement function closes this gap by validating config state independently of whether templates are rendered.
### Why pgsodium needs `shared_preload_libraries` but gzip/pgzstd do not
pgsodium's key management system (which powers TCE) requires a background worker process that must be started at server boot. The `pgsodium.getkey_script` is invoked during this early initialization phase to obtain the root key. gzip and pgzstd are pure SQL function libraries with no background workers or boot-time requirements.
---
## Operational Notes
### Generating a pgsodium key
Before using pgsodium's TCE features, generate a root key:
```bash
head -c 32 /dev/urandom | od -A n -t x1 | tr -d ' \n' > secrets/pgsodium.key
```
The key file must be readable by the postgres user inside the container. It is accessed via the shared secrets volume at `/opt/core_data/secrets/pgsodium.key`.
### Extension count
- **CORE_EXTENSION_LIST:** 46 → 49 extensions
- **REQUIRED_PRELOAD_LIBRARIES:** 9 entries (new array)
- **pgTap assertions:** 39 → 42
### Docker image size impact
Three additional source builds and three new shared library dependencies (`libsodium`, `zlib`, `libzstd`) will increase the Docker image size. The runtime libraries are small (libsodium ~500KB, zlib ~100KB, libzstd ~600KB). Build tools are already present in the image for AGE/pg_squeeze compilation.
1 parent 93258e4 commit 4e57f7d
6 files changed
Lines changed: 130 additions & 3 deletions
File tree
- postgres
- conf
- initdb
- tools
- scripts/lib
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
| 48 | + | |
48 | 49 | | |
49 | 50 | | |
50 | 51 | | |
51 | 52 | | |
| 53 | + | |
| 54 | + | |
52 | 55 | | |
53 | 56 | | |
54 | 57 | | |
| |||
93 | 96 | | |
94 | 97 | | |
95 | 98 | | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
96 | 124 | | |
97 | 125 | | |
98 | 126 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| |||
87 | 87 | | |
88 | 88 | | |
89 | 89 | | |
| 90 | + | |
90 | 91 | | |
91 | 92 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
18 | 52 | | |
19 | 53 | | |
20 | 54 | | |
| |||
101 | 135 | | |
102 | 136 | | |
103 | 137 | | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
104 | 142 | | |
105 | | - | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
106 | 149 | | |
107 | 150 | | |
108 | 151 | | |
| |||
190 | 233 | | |
191 | 234 | | |
192 | 235 | | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
193 | 240 | | |
194 | 241 | | |
195 | 242 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
38 | 49 | | |
39 | 50 | | |
40 | 51 | | |
| |||
122 | 133 | | |
123 | 134 | | |
124 | 135 | | |
125 | | - | |
| 136 | + | |
126 | 137 | | |
127 | 138 | | |
128 | 139 | | |
| |||
134 | 145 | | |
135 | 146 | | |
136 | 147 | | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
137 | 151 | | |
138 | 152 | | |
139 | 153 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
31 | 34 | | |
32 | 35 | | |
33 | 36 | | |
| |||
44 | 47 | | |
45 | 48 | | |
46 | 49 | | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
0 commit comments