Skip to content

Commit 3ec15ff

Browse files
committed
feat: add pam_jit_pg.so to image
Adds https://github.com/supabase/jit-db-gatekeeper to allow for PAM based auth
1 parent 9773377 commit 3ec15ff

File tree

6 files changed

+299
-3
lines changed

6 files changed

+299
-3
lines changed

ansible/tasks/setup-postgres.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,19 @@
175175
group: 'postgres'
176176
src: 'files/postgresql_config/conf.d/read_replica.conf'
177177

178+
- name: Check if psql_version is psql_15
179+
set_fact:
180+
is_psql_15: "{{ psql_version in ['psql_15'] }}"
181+
182+
- name: create placeholder pam config
183+
file:
184+
path: '/etc/pam.d/postgresql'
185+
state: touch
186+
owner: postgres
187+
group: postgres
188+
mode: 0664
189+
when: not is_psql_15
190+
178191
# Install extensions before init
179192
- name: Install Postgres extensions
180193
ansible.builtin.import_tasks:

ansible/tasks/stage2-setup-postgres.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,25 @@
155155
path: '/var/lib/postgresql/.nix-profile/bin/'
156156
register: 'nix_links'
157157

158+
- name: Check if psql_version is psql_15
159+
set_fact:
160+
is_psql_15: "{{ psql_version == 'psql_15' }}"
161+
162+
- name: Install gatekeeper if not pg15
163+
when:
164+
- stage2_nix
165+
- not is_psql_15
166+
block:
167+
- name: Install gatekeeper from nix binary cache
168+
become: yes
169+
shell: |
170+
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"
171+
172+
- name: Create symbolic link for linux-pam to find pam_jit_pg.so
173+
become: yes
174+
shell: |
175+
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
176+
158177
- name: Create symlinks for Nix files into /usr/lib/postgresql/bin
159178
ansible.builtin.file:
160179
group: 'postgres'

ansible/vars.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ postgres_major:
1010

1111
# Full version strings for each major version
1212
postgres_release:
13-
postgresorioledb-17: "17.6.0.021-orioledb"
14-
postgres17: "17.6.1.064"
15-
postgres15: "15.14.1.064"
13+
postgresorioledb-17: "17.6.0.022-orioledb"
14+
postgres17: "17.6.1.065"
15+
postgres15: "15.14.1.065"
1616

1717
# Non Postgres Extensions
1818
pgbouncer_release: 1.19.0

nix/packages/default.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
github-matrix = pkgs.callPackage ./github-matrix {
4242
nix-eval-jobs = inputs'.nix-eval-jobs.packages.default;
4343
};
44+
gatekeeper = pkgs.callPackage ./gatekeeper.nix { inherit inputs pkgs; };
4445
supabase-groonga = pkgs.callPackage ./groonga { };
4546
http-mock-server = pkgs.callPackage ./http-mock-server.nix { };
4647
local-infra-bootstrap = pkgs.callPackage ./local-infra-bootstrap.nix { };

nix/packages/gatekeeper.nix

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{ pkgs, ... }:
2+
let
3+
upstream-src = pkgs.fetchFromGitHub {
4+
owner = "supabase";
5+
repo = "jit-db-gatekeeper";
6+
rev = "v1.0.0";
7+
sha256 = "sha256-C4RPyzpItJrM/FxINpEIKvkYdbfaFXK0hBJe17PpejM=";
8+
};
9+
10+
upstream-gatekeeper = pkgs.buildGoModule {
11+
pname = "jit-db-gatekeeper";
12+
version = "1.0.0";
13+
14+
src = upstream-src;
15+
16+
# Get vendorHash by setting to null first, building, and using error message
17+
vendorHash = null;
18+
19+
# Environment variables - choose ONE approach
20+
CGO_ENABLED = "1";
21+
22+
# Build flags
23+
ldflags = [
24+
"-s"
25+
"-w"
26+
];
27+
};
28+
in
29+
30+
pkgs.stdenv.mkDerivation {
31+
pname = "gatekeeper";
32+
version = "1.0.0";
33+
34+
buildInputs = [ upstream-gatekeeper ];
35+
36+
dontUnpack = true;
37+
38+
installPhase = ''
39+
mkdir -p $out/lib/security/
40+
cp ${upstream-gatekeeper}/pam_jit_pg.so $out/lib/security/
41+
'';
42+
}

testinfra/test_ami_nix.py

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,227 @@ def test_libpq5_version(host):
633633
print("✓ libpq5 version is >= 14")
634634

635635

636+
def test_jit_pam_module_installed(host):
637+
"""Test that the JIT PAM module (pam_jit_pg.so) is properly installed."""
638+
# Check PostgreSQL version first
639+
result = run_ssh_command(
640+
host["ssh"], "sudo -u postgres psql --version | grep -oE '[0-9]+' | head -1"
641+
)
642+
pg_major_version = 15 # Default
643+
if result["succeeded"] and result["stdout"].strip():
644+
try:
645+
pg_major_version = int(result["stdout"].strip())
646+
except ValueError:
647+
pass
648+
649+
# Skip test for PostgreSQL 15 as gatekeeper is not installed for PG15
650+
if pg_major_version == 15:
651+
print("\nSkipping JIT PAM module test for PostgreSQL 15 (not installed)")
652+
return
653+
654+
# Check if gatekeeper is installed via Nix
655+
result = run_ssh_command(
656+
host["ssh"],
657+
"sudo -u postgres ls -la /var/lib/postgresql/.nix-profile/lib/security/pam_jit_pg.so 2>/dev/null",
658+
)
659+
if result["succeeded"]:
660+
print(f"\nJIT PAM module found in Nix profile:\n{result['stdout']}")
661+
else:
662+
print("\nJIT PAM module not found in postgres user's Nix profile")
663+
assert False, "JIT PAM module (pam_jit_pg.so) not found in expected location"
664+
665+
# Check if the symlink exists in the Linux PAM security directory
666+
result = run_ssh_command(
667+
host["ssh"],
668+
"find /nix/store -type f -path '*/lib/security/pam_jit_pg.so' 2>/dev/null | head -5",
669+
)
670+
if result["succeeded"] and result["stdout"].strip():
671+
print(f"\nJIT PAM module symlinks found:\n{result['stdout']}")
672+
else:
673+
print("\nNo JIT PAM module symlinks found in /nix/store")
674+
675+
# Verify the module is a valid shared library
676+
result = run_ssh_command(
677+
host["ssh"], "file /var/lib/postgresql/.nix-profile/lib/security/pam_jit_pg.so"
678+
)
679+
if result["succeeded"]:
680+
print(f"\nJIT PAM module file type:\n{result['stdout']}")
681+
assert (
682+
"shared object" in result["stdout"].lower()
683+
or "dynamically linked" in result["stdout"].lower()
684+
), "JIT PAM module is not a valid shared library"
685+
686+
print("✓ JIT PAM module is properly installed")
687+
688+
689+
def test_pam_postgresql_config(host):
690+
"""Test that the PAM configuration for PostgreSQL exists and is properly configured."""
691+
# Check PostgreSQL version to determine if PAM config should exist
692+
result = run_ssh_command(
693+
host["ssh"], "sudo -u postgres psql --version | grep -oE '[0-9]+' | head -1"
694+
)
695+
pg_major_version = 15 # Default
696+
if result["succeeded"] and result["stdout"].strip():
697+
try:
698+
pg_major_version = int(result["stdout"].strip())
699+
except ValueError:
700+
pass
701+
702+
print(f"\nPostgreSQL major version: {pg_major_version}")
703+
704+
# PAM config should exist for non-PostgreSQL 15 versions
705+
if pg_major_version != 15:
706+
# Check if PAM config file exists
707+
result = run_ssh_command(host["ssh"], "ls -la /etc/pam.d/postgresql")
708+
if result["succeeded"]:
709+
print(f"\nPAM config file found:\n{result['stdout']}")
710+
711+
# Check file permissions
712+
result = run_ssh_command(
713+
host["ssh"], "stat -c '%a %U %G' /etc/pam.d/postgresql"
714+
)
715+
if result["succeeded"]:
716+
perms = result["stdout"].strip()
717+
print(f"PAM config permissions: {perms}")
718+
# Should be owned by postgres:postgres with 664 permissions
719+
assert (
720+
"postgres postgres" in perms
721+
), "PAM config not owned by postgres:postgres"
722+
else:
723+
print("\nPAM config file not found")
724+
assert False, "PAM configuration file /etc/pam.d/postgresql not found"
725+
else:
726+
print("\nSkipping PAM config check for PostgreSQL 15")
727+
# For PostgreSQL 15, the PAM config should NOT exist
728+
result = run_ssh_command(host["ssh"], "test -f /etc/pam.d/postgresql")
729+
if result["succeeded"]:
730+
print("\nWARNING: PAM config exists for PostgreSQL 15 (not expected)")
731+
732+
print("✓ PAM configuration is properly set up")
733+
734+
735+
def test_jit_pam_gatekeeper_profile(host):
736+
"""Test that the gatekeeper package is properly installed in the postgres user's Nix profile."""
737+
# Check PostgreSQL version first
738+
result = run_ssh_command(
739+
host["ssh"], "sudo -u postgres psql --version | grep -oE '[0-9]+' | head -1"
740+
)
741+
pg_major_version = 15 # Default
742+
if result["succeeded"] and result["stdout"].strip():
743+
try:
744+
pg_major_version = int(result["stdout"].strip())
745+
except ValueError:
746+
pass
747+
748+
# Skip test for PostgreSQL 15 as gatekeeper is not installed for PG15
749+
if pg_major_version == 15:
750+
print("\nSkipping gatekeeper profile test for PostgreSQL 15 (not installed)")
751+
return
752+
753+
# Check if gatekeeper is in the postgres user's Nix profile
754+
result = run_ssh_command(
755+
host["ssh"],
756+
"sudo -u postgres nix profile list --json | jq -r '.elements.gatekeeper.storePaths[0]'",
757+
)
758+
if result["succeeded"] and result["stdout"].strip():
759+
print(f"\nGatekeeper found in Nix profile:\n{result['stdout']}")
760+
else:
761+
# Try alternative check
762+
result = run_ssh_command(
763+
host["ssh"],
764+
"sudo -u postgres ls -la /var/lib/postgresql/.nix-profile/ | grep -i gate",
765+
)
766+
if result["succeeded"] and result["stdout"].strip():
767+
print(f"\nGatekeeper-related files in profile:\n{result['stdout']}")
768+
else:
769+
print("\nGatekeeper not found in postgres user's Nix profile")
770+
# This might be expected if it's installed system-wide instead
771+
772+
# Check if we can find the gatekeeper derivation
773+
result = run_ssh_command(
774+
host["ssh"],
775+
"find /nix/store -maxdepth 1 -type d -name '*gatekeeper*' 2>/dev/null | head -5",
776+
)
777+
if result["succeeded"] and result["stdout"].strip():
778+
print(f"\nGatekeeper derivations found:\n{result['stdout']}")
779+
else:
780+
print("\nNo gatekeeper derivations found in /nix/store")
781+
782+
print("✓ Gatekeeper package installation check completed")
783+
784+
785+
def test_jit_pam_module_dependencies(host):
786+
"""Test that the JIT PAM module has all required dependencies."""
787+
# Check PostgreSQL version first
788+
result = run_ssh_command(
789+
host["ssh"], "sudo -u postgres psql --version | grep -oE '[0-9]+' | head -1"
790+
)
791+
pg_major_version = 15 # Default
792+
if result["succeeded"] and result["stdout"].strip():
793+
try:
794+
pg_major_version = int(result["stdout"].strip())
795+
except ValueError:
796+
pass
797+
798+
# Skip test for PostgreSQL 15 as gatekeeper is not installed for PG15
799+
if pg_major_version == 15:
800+
print(
801+
"\nSkipping JIT PAM module dependencies test for PostgreSQL 15 (not installed)"
802+
)
803+
return
804+
805+
# Check dependencies of the PAM module
806+
result = run_ssh_command(
807+
host["ssh"],
808+
"ldd /var/lib/postgresql/.nix-profile/lib/security/pam_jit_pg.so 2>/dev/null",
809+
)
810+
if result["succeeded"]:
811+
print(f"\nJIT PAM module dependencies:\n{result['stdout']}")
812+
813+
# Check for required libraries
814+
required_libs = ["libpam", "libc"]
815+
for lib in required_libs:
816+
if lib not in result["stdout"].lower():
817+
print(f"WARNING: Required library {lib} not found in dependencies")
818+
819+
# Check for any missing dependencies
820+
if "not found" in result["stdout"].lower():
821+
assert False, "JIT PAM module has missing dependencies"
822+
else:
823+
print("\nCould not check JIT PAM module dependencies")
824+
825+
print("✓ JIT PAM module dependencies are satisfied")
826+
827+
828+
def test_jit_pam_postgresql_integration(host):
829+
"""Test that PostgreSQL can be configured to use PAM authentication."""
830+
# Check if PAM is available as an authentication method in PostgreSQL
831+
result = run_ssh_command(
832+
host["ssh"],
833+
"sudo -u postgres psql -c \"SELECT name, setting FROM pg_settings WHERE name LIKE '%pam%';\" 2>/dev/null",
834+
)
835+
if result["succeeded"]:
836+
print(f"\nPostgreSQL PAM-related settings:\n{result['stdout']}")
837+
838+
# Check pg_hba.conf for potential PAM entries (even if not currently active)
839+
result = run_ssh_command(
840+
host["ssh"],
841+
"grep -i pam /etc/postgresql/pg_hba.conf 2>/dev/null || echo 'No PAM entries in pg_hba.conf'",
842+
)
843+
if result["succeeded"]:
844+
print(f"\nPAM entries in pg_hba.conf:\n{result['stdout']}")
845+
846+
# Verify PostgreSQL was compiled with PAM support
847+
result = run_ssh_command(
848+
host["ssh"],
849+
"sudo -u postgres pg_config --configure 2>/dev/null | grep -i pam || echo 'PAM compile flag not found'",
850+
)
851+
if result["succeeded"]:
852+
print(f"\nPostgreSQL PAM compile flags:\n{result['stdout']}")
853+
854+
print("✓ PostgreSQL PAM integration check completed")
855+
856+
636857
def test_postgrest_read_only_session_attrs(host):
637858
"""Test PostgREST with target_session_attrs=read-only and check for session errors."""
638859
# First, check if PostgreSQL is configured for read-only mode

0 commit comments

Comments
 (0)