Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
/timescaledb-oss/ @cloudnative-pg/postgres-extensions-containers-owners @shusaan
/wal2json/ @cloudnative-pg/postgres-extensions-containers-owners @solidDoWant
/pg-ivm/ @cloudnative-pg/postgres-extensions-containers-owners @shusaan
/postgresql-anonymizer/ @cloudnative-pg/postgres-extensions-containers-owners @pchovelon @daamien
2 changes: 2 additions & 0 deletions SECURITY-INSIGHTS.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ repository:
comment: GitHub packages for the TimescaleDB (OSS) extension image
- uri: https://github.com/cloudnative-pg/postgres-extensions-containers/pkgs/container/wal2json
comment: GitHub packages for the wal2json extension image
- uri: https://github.com/cloudnative-pg/postgres-extensions-containers/pkgs/container/postgresql_anonymizer
comment: GitHub packages for the PostgreSQL Anonymizer extension image

security:
tools:
Expand Down
33 changes: 33 additions & 0 deletions postgresql-anonymizer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
ARG BASE=ghcr.io/cloudnative-pg/postgresql:18-minimal-trixie
FROM $BASE AS builder

ARG PG_MAJOR
ARG EXT_VERSION

USER 0

RUN apt-get update && \
apt-get install -y curl lsb-release

#RUN CODENAME=$(. /etc/os-release && echo "$VERSION_CODENAME")
#RUN echo "deb https://repo.dalibo.com/apt/ $CODENAME main" > /etc/apt/sources.list.d/dalibo_labs.list
RUN curl -fsSL https://repo.dalibo.com/install.sh | sh
# RUN cat /etc/apt/sources.list.d/dalibo_labs.list
# RUN curl -fsSL -o /etc/apt/trusted.gpg.d/dalibo_labs.asc https://repo.dalibo.com/dalibo_labs.asc

RUN apt-get update && \
apt-get install -y postgresql_anonymizer_${PG_MAJOR}=${EXT_VERSION}

FROM scratch
ARG PG_MAJOR

# Licenses
#COPY --from=builder /usr/share/doc/postgresql-18-pganon/copyright /licenses/postgresql-18-pganon/

# Libraries
COPY --from=builder /usr/lib/postgresql/${PG_MAJOR}/lib/anon* /lib/

# Share
COPY --from=builder /usr/share/postgresql/${PG_MAJOR}/extension/anon* /share/extension/

USER 65532:65532
155 changes: 155 additions & 0 deletions postgresql-anonymizer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# PostgreSQL Anonymizer
<!--
SPDX-FileCopyrightText: Copyright © contributors to CloudNativePG, established as CloudNativePG a Series of LF Projects, LLC.
SPDX-License-Identifier: Apache-2.0
-->

The [PostgreSQL Anonymizer](https://gitlab.com/dalibo/postgresql_anonymizer) (or
`anon`) is an extension to mask or replace [personally identifiable information] (PII) or
commercially sensitive data from a Postgres database. For more information, see the [official documentation](https://postgresql-anonymizer.readthedocs.io).

## Usage

### 1. Add the PostgreSQL Anonymizer extension image to your Cluster

Define the PostgreSQL Anonymizer extension `anon` under the
`postgresql.extensions` section of your `Cluster` resource.

For example:

```yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-postgresql-anonymizer
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:18-minimal-trixie
instances: 1

storage:
size: 1Gi

postgresql:
extensions:
- name: anon
image:
# renovate: suite=trixie-pgdg depName=postgresql-18-postgresql-anonymizer
reference: ghcr.io/cloudnative-pg/postgresql_anonymizer:3.2.2-18-trixie
```

### 2. Enable the extension in a database

You can install PostgreSQL Anonymizer extension `anon` in a specific database by
creating or updating a `Database` resource. For example, to enable it in a
`db` database:

```yaml
apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
name: database-postgresql-anonymizer
spec:
name: db
owner: app
cluster:
name: cluster-postgresql-anonymizer
extensions:
- name: anon
# renovate: suite=trixie-pgdg depName=postgresql-18-postgresql-anonymizer extractVersion=^(?<version>\d+\.\d+\.\d+)
version: '3.2.2'
```

On top of the creation, it's recommended to load the extension in the `db` database.
This can be done with this command :

```sql
ALTER DATABASE db SET session_preload_libraries = 'anon';
```

The setting will be applied for the next sessions.
You need to reconnect to the database for the change to visible.

### 3. Initialize the extension

After reconnection, you can initialize the extension with the `anon.init()` function :

```sql
db=# SELECT anon.init('/extensions/anon/share/extension/');
init
------
t
(1 row)
```

### 4. Verify installation

Once the `db` database is ready, connect to it with `psql -d db` and run :

```sql
SELECT * FROM pg_extension WHERE extname = 'anon';
```

```sql
db=# SELECT * FROM pg_extension WHERE extname = 'anon';
oid | extname | extowner | extnamespace | extrelocatable | extversion | extconfig | extcondition
-------+---------+----------+--------------+----------------+------------+-----------+--------------
16394 | anon | 10 | 2200 | f | 3.2.2 | |
(1 row)
```

If the result is empty, the extension is not declared in your database.

Finally, look at the state of the extension with :

```sql
SELECT anon.is_initialized();
```

```console
db=# SELECT anon.is_initialized();
is_initialized
----------------
t
(1 row)
```

If the result is not `t`, the extension's data is not present.

## Contributors

This extension is maintained by:

- Damien Clochard (@daamien)
- Pierrick Chovelon (@pchovelon)

The maintainers are responsible for:

- Monitoring upstream releases and security vulnerabilities.
- Ensuring compatibility with supported PostgreSQL versions.
- Reviewing and merging contributions specific to this extension's container
image and lifecycle.

---

## Licenses and Copyright

The PostgreSQL License
===============================================================================

Copyright (c) 2018-2026, DALIBO SCOP

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose, without fee, and without a written agreement
is hereby granted, provided that the above copyright notice and this paragraph
and the following two paragraphs appear in all copies.

IN NO EVENT SHALL DALIBO SCOP BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING
OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF DALIBO SCOP
HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

DALIBO SCOP SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
AND DALIBO SCOP HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
35 changes: 35 additions & 0 deletions postgresql-anonymizer/metadata.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# SPDX-FileCopyrightText: Copyright © contributors to CloudNativePG, established as CloudNativePG a Series of LF Projects, LLC.
# SPDX-License-Identifier: Apache-2.0
metadata = {
name = "postgresql-anonymizer"
sql_name = "anon"
image_name = "postgresql_anonymizer"
licenses = ["PostgreSQL"]
shared_preload_libraries = []
postgresql_parameters = {}
extension_control_path = []
dynamic_library_path = []
ld_library_path = []
bin_path = []
env = {}
auto_update_os_libs = false
required_extensions = []
create_extension = true

versions = {
bookworm = {
"18" = {
// renovate: suite=bookworm-pgdg depName=postgresql-18-postgresql-anonymizer
package = "3.2.2"
sql = "3.2.2"
}
}
trixie = {
"18" = {
// renovate: suite=trixie-pgdg depName=postgresql-18-postgresql-anonymizer
package = "3.2.2"
sql = "3.2.2"
}
}
}
}
28 changes: 28 additions & 0 deletions postgresql-anonymizer/test/chainsaw-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
name: verify-postgresql-anonymizer-extension
spec:
timeouts:
apply: 5s
assert: 3m
delete: 30s
description: Verify postgresql_anonymizer extension is properly installed
steps:
- name: Create a Cluster and a Database with the extension
try:
- apply:
file: cluster.yaml
- apply:
file: database.yaml
- assert:
file: cluster-assert.yaml
- assert:
file: database-assert.yaml

- name: Verify extension is installed
try:
- apply:
file: check-extension.yaml
- assert:
file: check-extension-assert.yaml
6 changes: 6 additions & 0 deletions postgresql-anonymizer/test/check-extension-assert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: batch/v1
kind: Job
metadata:
name: extension-installed
status:
succeeded: 1
38 changes: 38 additions & 0 deletions postgresql-anonymizer/test/check-extension.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
apiVersion: batch/v1
kind: Job
metadata:
name: extension-installed
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: data-test
env:
# Clear the image-baked PGSSLROOTCERT=system so libpq doesn't
# try to verify the CNPG self-signed CA against the OS trust store.
- name: PGSSLROOTCERT
- name: EXT_SQL_NAME
value: ($values.sql_name)
- name: EXT_VERSION
value: ($values.version)
- name: CREATE_EXTENSION
value: (to_string($values.create_extension))
- name: DB_URI
valueFrom:
secretKeyRef:
name: (join('-', [$values.name, 'app']))
key: uri
# renovate: datasource=docker depName=alpine/psql versioning=docker
image: alpine/psql:18.6@sha256:466b9fb940b45d317099c89d26ee8f1078dced59ef9304f9f4eab2d5fc616d46
command: ['sh', '-c']
args:
- |
set -e
if [ "$CREATE_EXTENSION" != "true" ]; then
echo "Skipping extension check (create_extension=false)"
exit 0
fi
DB_URI=$(echo $DB_URI | sed "s|/\*|/|")
test "$(psql "$DB_URI" -tAc "SELECT EXISTS (SELECT FROM pg_catalog.pg_extension WHERE extname = '${EXT_SQL_NAME}' AND extversion = '${EXT_VERSION}')" -q)" = "t"
echo "Extension '${EXT_SQL_NAME} v${EXT_VERSION}' is installed!"
8 changes: 8 additions & 0 deletions postgresql-anonymizer/test/cluster-assert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: ($values.name)
status:
readyInstances: 1
phase: Cluster in healthy state
image: ($values.pg_image)
15 changes: 15 additions & 0 deletions postgresql-anonymizer/test/cluster.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: ($values.name)
spec:
imageName: ($values.pg_image)
instances: 1

storage:
size: 1Gi

postgresql:
parameters: ($values.postgresql_parameters)
shared_preload_libraries: ($values.shared_preload_libraries)
extensions: ($values.extensions)
5 changes: 5 additions & 0 deletions postgresql-anonymizer/test/database-assert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
name: (join('-', [$values.name, 'app']))
status: ($values.database_assert_status)
10 changes: 10 additions & 0 deletions postgresql-anonymizer/test/database.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
name: (join('-', [$values.name, 'app']))
spec:
name: app
owner: app
cluster:
name: ($values.name)
extensions: ($values.database_config.extensions_spec)