Skip to content
Draft
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
93 changes: 93 additions & 0 deletions ansible/files/permission_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,96 @@ def check_postgresql_mount():
print("postgresql.service mounts /etc as read-only.")


def check_copy_program_blocked():
"""Check that COPY ... PROGRAM is blocked by AppArmor."""
import psycopg2
from psycopg2 import sql

errors = []

try:
# Connect to PostgreSQL as the postgres superuser
conn = psycopg2.connect(
host="/var/run/postgresql", database="postgres", user="postgres"
)
cursor = conn.cursor()

# Test 1: COPY TO PROGRAM should be blocked
try:
cursor.execute(
"COPY (SELECT 'test') TO PROGRAM 'cat > /tmp/apparmor_copy_test.txt'"
)
errors.append(
"ERROR: COPY TO PROGRAM was not blocked by AppArmor!\n"
" This is a security vulnerability - postgres should not be able to execute arbitrary programs."
)
except psycopg2.Error as e:
# Expected to fail - check if it's a permission denied error
error_msg = str(e).lower()
if (
"permission denied" in error_msg
or "cannot execute" in error_msg
or "could not execute" in error_msg
):
print("✓ COPY TO PROGRAM correctly blocked by AppArmor")
else:
errors.append(
f"ERROR: COPY TO PROGRAM failed but with unexpected error:\n"
f" {str(e)}\n"
f" Expected permission denied error from AppArmor"
)

# Test 2: COPY FROM PROGRAM should be blocked
try:
cursor.execute("CREATE TEMP TABLE apparmor_test (data text)")
cursor.execute("COPY apparmor_test FROM PROGRAM 'echo test'")
errors.append(
"ERROR: COPY FROM PROGRAM was not blocked by AppArmor!\n"
" This is a security vulnerability - postgres should not be able to execute arbitrary programs."
)
except psycopg2.Error as e:
error_msg = str(e).lower()
if (
"permission denied" in error_msg
or "cannot execute" in error_msg
or "could not execute" in error_msg
):
print("✓ COPY FROM PROGRAM correctly blocked by AppArmor")
else:
errors.append(
f"ERROR: COPY FROM PROGRAM failed but with unexpected error:\n"
f" {str(e)}\n"
f" Expected permission denied error from AppArmor"
)

# Verify no test files were created
import os

if os.path.exists("/tmp/apparmor_copy_test.txt"):
errors.append(
"ERROR: Test file /tmp/apparmor_copy_test.txt was created!\n"
" AppArmor did not successfully block COPY TO PROGRAM"
)
os.remove("/tmp/apparmor_copy_test.txt")

cursor.close()
conn.close()

except Exception as e:
errors.append(f"ERROR: Failed to test COPY PROGRAM blocking: {str(e)}")

if errors:
print("\n" + "=" * 80)
print("COPY PROGRAM BLOCKING ERRORS DETECTED:")
print("=" * 80)
for error in errors:
print(error)
print("=" * 80)
sys.exit(1)

print("\nCOPY ... PROGRAM is correctly blocked by AppArmor.")


def check_directory_permissions():
"""Check that security-critical directories have the correct permissions."""
errors = []
Expand Down Expand Up @@ -385,6 +475,9 @@ def main():
# Check directory permissions for security-critical paths
check_directory_permissions()

# Check that COPY ... PROGRAM is blocked by AppArmor
check_copy_program_blocked()


if __name__ == "__main__":
main()
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.5.1.069-orioledb"
postgres17: "17.6.1.048"
postgres15: "15.14.1.048"
postgresorioledb-17: "17.5.1.069-orioledb-copy-1"
postgres17: "17.6.1.048-copy-1"
postgres15: "15.14.1.048-copy-1"

# Non Postgres Extensions
pgbouncer_release: 1.19.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ capability dac_read_search,

deny @{HOME}/** rwx,

# Explicitly deny shell execution to block COPY ... PROGRAM
# This prevents arbitrary command execution via postgres
deny /bin/sh x,
deny /bin/bash x,
deny /bin/dash x,
deny /usr/bin/sh x,
deny /usr/bin/bash x,
deny /usr/bin/dash x,
deny /usr/bin/cat x,
deny /usr/bin/mknod x,

/data/pgdata/** r,
/dev/shm rw,
/etc/java-11-openjdk/logging.properties r,
Expand All @@ -26,11 +37,10 @@ deny @{HOME}/** rwx,
/etc/timezone r,
/etc/wal-g/config.json r,
/run/systemd/notify rw,
/usr/bin/cat rix,
/usr/bin/dash rix,
/usr/bin/mknod rix,
# admin-mgr: unrestricted for WAL operations (archive_command/restore_command)
/usr/bin/admin-mgr Ux,
/usr/lib/postgresql/bin/* mrix,
# wal-g: allowed for backup operations
/usr/local/bin/wal-g rix,
/usr/local/lib/groonga/plugins/tokenizers/mecab.so mr,
/usr/local/lib/libSFCGAL.so.* mr,
Expand Down
Loading