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

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions ansible/tasks/setup-postgres.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,21 @@
group: 'postgres'
src: 'files/postgresql_config/conf.d/read_replica.conf'

- name: configure pam
block:
- name: Check if psql_version is psql_15
ansible.builtin.set_fact:
is_psql_15: "{{ psql_version in ['psql_15'] }}"

- name: create placeholder pam config
file:
path: '/etc/pam.d/postgresql'
state: touch
owner: postgres
group: postgres
mode: 0664
when: not is_psql_15

# Install extensions before init
- name: Install Postgres extensions
ansible.builtin.import_tasks:
Expand Down
21 changes: 21 additions & 0 deletions ansible/tasks/stage2-setup-postgres.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,27 @@
path: '/var/lib/postgresql/.nix-profile/bin/'
register: 'nix_links'

- name: setup gatekeeper
block:
- name: Check if psql_version is psql_15
ansible.builtin.set_fact:
is_psql_15: "{{ psql_version == 'psql_15' }}"

- name: Install gatekeeper if not pg15
when:
- stage2_nix
- not is_psql_15
block:
- name: Install gatekeeper from nix binary cache
become: yes
shell: |
sudo -u postgres bash -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && nix profile install github:supabase/postgres/{{ git_commit_sha }}#gatekeeper"

- name: Create symbolic link for linux-pam to find pam_jit_pg.so
become: yes
shell: |
sudo ln -s /var/lib/postgresql/.nix-profile/lib/security/pam_jit_pg.so $(find /nix/store -type d -path "/nix/store/*-linux-pam-*/lib/security" -print -quit)/pam_jit_pg.so

- name: Create symlinks for Nix files into /usr/lib/postgresql/bin
ansible.builtin.file:
group: 'postgres'
Expand Down
6 changes: 3 additions & 3 deletions ansible/vars.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ postgres_major:

# Full version strings for each major version
postgres_release:
postgresorioledb-17: "17.6.0.021-orioledb"
postgres17: "17.6.1.064"
postgres15: "15.14.1.064"
postgresorioledb-17: "17.6.0.022-orioledb-pam"
postgres17: "17.6.1.065-pam"
postgres15: "15.14.1.065-pam"

# Non Postgres Extensions
pgbouncer_release: 1.19.0
Expand Down
1 change: 1 addition & 0 deletions nix/packages/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
github-matrix = pkgs.callPackage ./github-matrix {
nix-eval-jobs = inputs'.nix-eval-jobs.packages.default;
};
gatekeeper = pkgs.callPackage ./gatekeeper.nix { inherit inputs pkgs; };
supabase-groonga = pkgs.callPackage ./groonga { };
http-mock-server = pkgs.callPackage ./http-mock-server.nix { };
local-infra-bootstrap = pkgs.callPackage ./local-infra-bootstrap.nix { };
Expand Down
43 changes: 43 additions & 0 deletions nix/packages/gatekeeper.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{ pkgs, inputs, ... }:
let

go124 = inputs.nixpkgs-go124.legacyPackages.${pkgs.system}.go_1_24;
buildGoModule124 = pkgs.buildGoModule.override { go = go124; };

upstream-gatekeeper = buildGoModule124 {
pname = "jit-db-gatekeeper";
version = "1.0.0";
src = pkgs.fetchFromGitHub {
owner = "supabase";
repo = "jit-db-gatekeeper";
rev = "v1.0.0";
sha256 = "sha256-hdy2uaq1igNouCs6GHhRYQADeyWnXZ4+W+4YiyEUtZw=";
};
vendorHash = null;

buildInputs = [ pkgs.pam ];

# Environment variables - choose ONE approach
CGO_ENABLED = "1";

# Build flags
ldflags = [
"-s"
"-w"
];
};
in

pkgs.stdenv.mkDerivation {
pname = "gatekeeper";
version = "1.0.0";

buildInputs = [ upstream-gatekeeper ];

dontUnpack = true;

installPhase = ''
mkdir -p $out/lib/security/
cp ${upstream-gatekeeper}/bin/jit-db-gatekeeper $out/lib/security/pam_jit_pg.so
'';
}
221 changes: 221 additions & 0 deletions testinfra/test_ami_nix.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,227 @@ def test_libpq5_version(host):
print("✓ libpq5 version is >= 14")


def test_jit_pam_module_installed(host):
"""Test that the JIT PAM module (pam_jit_pg.so) is properly installed."""
# Check PostgreSQL version first
result = run_ssh_command(
host["ssh"], "sudo -u postgres psql --version | grep -oE '[0-9]+' | head -1"
)
pg_major_version = 15 # Default
if result["succeeded"] and result["stdout"].strip():
try:
pg_major_version = int(result["stdout"].strip())
except ValueError:
pass
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to know if we cannot parse the postgresql version. We should fail instead of using a default value.


# Skip test for PostgreSQL 15 as gatekeeper is not installed for PG15
if pg_major_version == 15:
print("\nSkipping JIT PAM module test for PostgreSQL 15 (not installed)")
return

# Check if gatekeeper is installed via Nix
result = run_ssh_command(
host["ssh"],
"sudo -u postgres ls -la /var/lib/postgresql/.nix-profile/lib/security/pam_jit_pg.so 2>/dev/null",
)
if result["succeeded"]:
print(f"\nJIT PAM module found in Nix profile:\n{result['stdout']}")
else:
print("\nJIT PAM module not found in postgres user's Nix profile")
assert False, "JIT PAM module (pam_jit_pg.so) not found in expected location"

# Check if the symlink exists in the Linux PAM security directory
result = run_ssh_command(
host["ssh"],
"find /nix/store -type f -path '*/lib/security/pam_jit_pg.so' 2>/dev/null | head -5",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be related to a previous comment but this will return the lib in the gatekeeper package like this: /nix/store/miwaglm79i2vpilmp8rxd2mymjs7nibz-gatekeeper-0.1.0/lib/security/pam_jit_pg.so. I guess we want to check that /usr/lib/x86_64-linux-gnu/security/pam_jit_pg.so exists instead ?

)
if result["succeeded"] and result["stdout"].strip():
print(f"\nJIT PAM module symlinks found:\n{result['stdout']}")
else:
print("\nNo JIT PAM module symlinks found in /nix/store")

# Verify the module is a valid shared library
result = run_ssh_command(
host["ssh"], "file /var/lib/postgresql/.nix-profile/lib/security/pam_jit_pg.so"
)
if result["succeeded"]:
print(f"\nJIT PAM module file type:\n{result['stdout']}")
assert (
"shared object" in result["stdout"].lower()
or "dynamically linked" in result["stdout"].lower()
), "JIT PAM module is not a valid shared library"

print("✓ JIT PAM module is properly installed")


def test_pam_postgresql_config(host):
"""Test that the PAM configuration for PostgreSQL exists and is properly configured."""
# Check PostgreSQL version to determine if PAM config should exist
result = run_ssh_command(
host["ssh"], "sudo -u postgres psql --version | grep -oE '[0-9]+' | head -1"
)
pg_major_version = 15 # Default
if result["succeeded"] and result["stdout"].strip():
try:
pg_major_version = int(result["stdout"].strip())
except ValueError:
pass

print(f"\nPostgreSQL major version: {pg_major_version}")

# PAM config should exist for non-PostgreSQL 15 versions
if pg_major_version != 15:
# Check if PAM config file exists
result = run_ssh_command(host["ssh"], "ls -la /etc/pam.d/postgresql")
if result["succeeded"]:
print(f"\nPAM config file found:\n{result['stdout']}")

# Check file permissions
result = run_ssh_command(
host["ssh"], "stat -c '%a %U %G' /etc/pam.d/postgresql"
)
if result["succeeded"]:
perms = result["stdout"].strip()
print(f"PAM config permissions: {perms}")
# Should be owned by postgres:postgres with 664 permissions
assert (
"postgres postgres" in perms
), "PAM config not owned by postgres:postgres"
else:
print("\nPAM config file not found")
assert False, "PAM configuration file /etc/pam.d/postgresql not found"
else:
print("\nSkipping PAM config check for PostgreSQL 15")
# For PostgreSQL 15, the PAM config should NOT exist
result = run_ssh_command(host["ssh"], "test -f /etc/pam.d/postgresql")
if result["succeeded"]:
print("\nWARNING: PAM config exists for PostgreSQL 15 (not expected)")

print("✓ PAM configuration is properly set up")


def test_jit_pam_gatekeeper_profile(host):
"""Test that the gatekeeper package is properly installed in the postgres user's Nix profile."""
# Check PostgreSQL version first
result = run_ssh_command(
host["ssh"], "sudo -u postgres psql --version | grep -oE '[0-9]+' | head -1"
)
pg_major_version = 15 # Default
if result["succeeded"] and result["stdout"].strip():
try:
pg_major_version = int(result["stdout"].strip())
except ValueError:
pass

# Skip test for PostgreSQL 15 as gatekeeper is not installed for PG15
if pg_major_version == 15:
print("\nSkipping gatekeeper profile test for PostgreSQL 15 (not installed)")
return

# Check if gatekeeper is in the postgres user's Nix profile
result = run_ssh_command(
host["ssh"],
"sudo -u postgres nix profile list --json | jq -r '.elements.gatekeeper.storePaths[0]'",
)
if result["succeeded"] and result["stdout"].strip():
print(f"\nGatekeeper found in Nix profile:\n{result['stdout']}")
else:
# Try alternative check
result = run_ssh_command(
host["ssh"],
"sudo -u postgres ls -la /var/lib/postgresql/.nix-profile/ | grep -i gate",
)
if result["succeeded"] and result["stdout"].strip():
print(f"\nGatekeeper-related files in profile:\n{result['stdout']}")
else:
print("\nGatekeeper not found in postgres user's Nix profile")
# This might be expected if it's installed system-wide instead

# Check if we can find the gatekeeper derivation
result = run_ssh_command(
host["ssh"],
"find /nix/store -maxdepth 1 -type d -name '*gatekeeper*' 2>/dev/null | head -5",
)
if result["succeeded"] and result["stdout"].strip():
print(f"\nGatekeeper derivations found:\n{result['stdout']}")
else:
print("\nNo gatekeeper derivations found in /nix/store")

print("✓ Gatekeeper package installation check completed")


def test_jit_pam_module_dependencies(host):
"""Test that the JIT PAM module has all required dependencies."""
# Check PostgreSQL version first
result = run_ssh_command(
host["ssh"], "sudo -u postgres psql --version | grep -oE '[0-9]+' | head -1"
)
pg_major_version = 15 # Default
if result["succeeded"] and result["stdout"].strip():
try:
pg_major_version = int(result["stdout"].strip())
except ValueError:
pass

# Skip test for PostgreSQL 15 as gatekeeper is not installed for PG15
if pg_major_version == 15:
print(
"\nSkipping JIT PAM module dependencies test for PostgreSQL 15 (not installed)"
)
return

# Check dependencies of the PAM module
result = run_ssh_command(
host["ssh"],
"ldd /var/lib/postgresql/.nix-profile/lib/security/pam_jit_pg.so 2>/dev/null",
)
if result["succeeded"]:
print(f"\nJIT PAM module dependencies:\n{result['stdout']}")

# Check for required libraries
required_libs = ["libpam", "libc"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that these dynamic dependencies/libraries are not coming from Ubuntu but from Nix which could cause problem as Nix and Ubuntu might not use the same PAM version. In the following test, we see that the module is loading PAM 1.6.0 but that version of Ubuntu is using PAM 1.5.3:

$ ldd /var/lib/postgresql/.nix-profile/lib/security/pam_jit_pg.so linux-vdso.so.1 (0x00007fffa3338000) libpam.so.0 => /nix/store/k491acz8rcfhrpivv0rg6q83a14zss2j-linux-pam-1.6.0/lib/libpam.so.0 (0x00007fe0c901c000) libpthread.so.0 => /nix/store/ddwyrxif62r8n6xclvskjyy6szdhvj60-glibc-2.39-5/lib/libpthread.so.0 (0x00007fe0c9017000) libresolv.so.2 => /nix/store/ddwyrxif62r8n6xclvskjyy6szdhvj60-glibc-2.39-5/lib/libresolv.so.2 (0x00007fe0c9006000) libc.so.6 => /nix/store/ddwyrxif62r8n6xclvskjyy6szdhvj60-glibc-2.39-5/lib/libc.so.6 (0x00007fe0c8613000) libaudit.so.1 => /nix/store/0i8hhak73jnlilfgfsh1wh0fd53pb0r9-audit-3.1.2/lib/libaudit.so.1 (0x00007fe0c8fd1000) $ patchelf --print-rpath /root/.nix-profile/lib/security/pam_jit_pg.so /nix/store/k491acz8rcfhrpivv0rg6q83a14zss2j-linux-pam-1.6.0/lib:/nix/store/ddwyrxif62r8n6xclvskjyy6szdhvj60-glibc-2.39-5/lib $ dpkg -l libpam-runtime Desired=Unknown/Install/Remove/Purge/Hold | Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend |/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad) ||/ Name Version Architecture Description +++-==============-================-============-=================================== ii libpam-runtime 1.5.3-5ubuntu5.4 all Runtime support for the PAM library 

We can instead remove the rpath and let the dynamic linker do its job (without looking at other nix libraries):

$ cp /var/lib/postgres/.nix-profile/lib/security/pam_jit_pg.so /usr/lib/x86_64-linux-gnu/security/ $ patchelf --remove-rpath /usr/lib/x86_64-linux-gnu/security/pam_jit_pg.so $ ldd /usr/lib/x86_64-linux-gnu/security/pam_jit_pg.so linux-vdso.so.1 (0x00007fff3179e000) libpam.so.0 => /lib/x86_64-linux-gnu/libpam.so.0 (0x000078aa645ea000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x000078aa645e5000) libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x000078aa645d2000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x000078aa63a00000) libaudit.so.1 => /lib/x86_64-linux-gnu/libaudit.so.1 (0x000078aa645a4000) /lib64/ld-linux-x86-64.so.2 (0x000078aa64602000) libcap-ng.so.0 => /lib/x86_64-linux-gnu/libcap-ng.so.0 (0x000078aa6459a000) 

We could remove that rpath after the build of the package:

 postFixup = '' patchelf --remove-rpath $out/lib/security/pam_jit_pg.so ''; 

But still there might be other surprises as the gatekeeper module has been build based on PAM 1.6.0.
Usually PAM has a good backward compatibility and the amount of dyn symbols used by gatekeeper is rather limited:

$ objdump -T /var/lib/postgres/.nix-profile/lib/security/pam_jit_pg.so | grep PAM 0000000000000000 DF *UND* 0000000000000000 (LIBPAM_1.0) pam_strerror 0000000000000000 DF *UND* 0000000000000000 (LIBPAM_EXTENSION_1.0) pam_syslog 0000000000000000 DF *UND* 0000000000000000 (LIBPAM_1.0) pam_get_item 0000000000000000 DF *UND* 0000000000000000 (LIBPAM_1.0) pam_get_user 0000000000000000 DF *UND* 0000000000000000 (LIBPAM_EXTENSION_1.1) pam_get_authtok 

If (and only if) we face issue (I would test if further first), I would build it against the same version using https://lazamar.co.uk/nix-versions/?package=linux-pam&version=1.5.3&fullName=linux-pam-1.5.3&keyName=linux-pam&revision=7a339d87931bba829f68e94621536cad9132971a&channel=nixpkgs-unstable#instructions

We might want to create an automated test that exercice the module using pamtester just to verify that we don't get any dlopen errors in the logs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we were/are building against 1.6.0 because of postgresql having been built against it (and hence also the hackery of copying the .so to /nix/store/*-linux-pam-*/lib/security because the linker looks for the modules there instead of the default Ubuntu path)

$ patchelf --print-rpath /nix/store/2x79j9fi9j13hhf5n0yc9825qa4nynv3-postgresql-and-plugins-17.6/bin/.postgres-wrapped /nix/store/bvjhqzpwbzmfi350nir8gf7h45igvp5z-libxml2-2.12.6/lib:/nix/store/wcwqdhl9jpwgj1fdr0pw9dbaf5m43f17-lz4-1.9.4/lib:/nix/store/mxvqrp4jj6zklxc1i32ra8iapnzynyzc-zstd-1.5.5/lib:/nix/store/ram2bh4rs56rp8sr0gcdb108wcc609si-icu4c-73.2/lib:/nix/store/ymav8nb29vsdryddi435xcig3pk7i00q-zlib-1.3.1/lib:/nix/store/0kfbzhd8qdsxsd4qf9dkpcd3f916krvr-openssl-3.0.13/lib:/nix/store/ss6bfrdx69ng9f448d5hq1683hkpxcng-systemd-255.4/lib:/nix/store/vaz0prypndg3y6zxmdcpabinwg9dmmq9-libkrb5-1.21.2/lib:/nix/store/4rdpzzfssknp4k4cn59wc7xcdiyv9j53-linux-pam-1.6.0/lib:/nix/store/10pd749c2v3m35d4hk5xdfylyfr1hzqz-glibc-2.39-5/lib 

-->

/nix/store/4rdpzzfssknp4k4cn59wc7xcdiyv9j53-linux-pam-1.6.0/lib 

Tried modifying the rpath on postgres-wrapped but that leads to problems looking up libaudit.so.1 (even though it is in the new rpath and when specifying the path manually to the location in the store)

for lib in required_libs:
if lib not in result["stdout"].lower():
print(f"WARNING: Required library {lib} not found in dependencies")

# Check for any missing dependencies
if "not found" in result["stdout"].lower():
assert False, "JIT PAM module has missing dependencies"
else:
print("\nCould not check JIT PAM module dependencies")

print("✓ JIT PAM module dependencies are satisfied")


def test_jit_pam_postgresql_integration(host):
"""Test that PostgreSQL can be configured to use PAM authentication."""
# Check if PAM is available as an authentication method in PostgreSQL
result = run_ssh_command(
host["ssh"],
"sudo -u postgres psql -c \"SELECT name, setting FROM pg_settings WHERE name LIKE '%pam%';\" 2>/dev/null",
)
if result["succeeded"]:
print(f"\nPostgreSQL PAM-related settings:\n{result['stdout']}")

# Check pg_hba.conf for potential PAM entries (even if not currently active)
result = run_ssh_command(
host["ssh"],
"grep -i pam /etc/postgresql/pg_hba.conf 2>/dev/null || echo 'No PAM entries in pg_hba.conf'",
)
if result["succeeded"]:
print(f"\nPAM entries in pg_hba.conf:\n{result['stdout']}")

# Verify PostgreSQL was compiled with PAM support
result = run_ssh_command(
host["ssh"],
"sudo -u postgres pg_config --configure 2>/dev/null | grep -i pam || echo 'PAM compile flag not found'",
)
if result["succeeded"]:
print(f"\nPostgreSQL PAM compile flags:\n{result['stdout']}")

print("✓ PostgreSQL PAM integration check completed")


def test_postgrest_read_only_session_attrs(host):
"""Test PostgREST with target_session_attrs=read-only and check for session errors."""
# First, check if PostgreSQL is configured for read-only mode
Expand Down
Loading