Skip to content
Merged
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
127 changes: 127 additions & 0 deletions ansible/files/permission_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import json
import sys
import argparse
import os
import stat


# Expected groups for each user
Expand Down Expand Up @@ -103,6 +105,59 @@
# postgresql.service is expected to mount /etc as read-only
expected_mount = "/etc ro"

# Expected directory permissions for security-critical paths
# Format: path -> (expected_mode, expected_owner, expected_group, description)
expected_directory_permissions = {
"/var/lib/postgresql": (
"0755",
"postgres",
"postgres",
"PostgreSQL home - must be traversable for nix-profile symlinks",
),
"/var/lib/postgresql/data": (
"0750",
"postgres",
"postgres",
"PostgreSQL data directory symlink - secure, postgres only",
),
"/data/pgdata": (
"0750",
"postgres",
"postgres",
"Actual PostgreSQL data directory - secure, postgres only",
),
"/etc/postgresql": (
"0775",
"postgres",
"postgres",
"PostgreSQL configuration directory - adminapi writable",
),
"/etc/postgresql-custom": (
"0775",
"postgres",
"postgres",
"PostgreSQL custom configuration - adminapi writable",
),
"/etc/ssl/private": (
"0750",
"root",
"ssl-cert",
"SSL private keys directory - secure, ssl-cert group only",
),
"/home/postgres": (
"0750",
"postgres",
"postgres",
"postgres user home directory - secure, postgres only",
),
"/var/log/postgresql": (
"0750",
"postgres",
"postgres",
"PostgreSQL logs directory - secure, postgres only",
),
}


# This program depends on osquery being installed on the system
# Function to run osquery
Expand Down Expand Up @@ -189,6 +244,75 @@ def check_postgresql_mount():
print("postgresql.service mounts /etc as read-only.")


def check_directory_permissions():
"""Check that security-critical directories have the correct permissions."""
errors = []

for path, (
expected_mode,
expected_owner,
expected_group,
description,
) in expected_directory_permissions.items():
# Skip if path doesn't exist (might be a symlink or not created yet)
if not os.path.exists(path):
print(f"Warning: {path} does not exist, skipping permission check")
continue

# Get actual permissions
try:
stat_info = os.stat(path)
actual_mode = oct(stat.S_IMODE(stat_info.st_mode))[2:] # Remove '0o' prefix

# Get owner and group names
import pwd
import grp

actual_owner = pwd.getpwuid(stat_info.st_uid).pw_name
actual_group = grp.getgrgid(stat_info.st_gid).gr_name

# Check permissions
if actual_mode != expected_mode:
errors.append(
f"ERROR: {path} has mode {actual_mode}, expected {expected_mode}\n"
f" Description: {description}\n"
f" Fix: sudo chmod {expected_mode} {path}"
)

# Check ownership
if actual_owner != expected_owner:
errors.append(
f"ERROR: {path} has owner {actual_owner}, expected {expected_owner}\n"
f" Description: {description}\n"
f" Fix: sudo chown {expected_owner}:{actual_group} {path}"
)

# Check group
if actual_group != expected_group:
errors.append(
f"ERROR: {path} has group {actual_group}, expected {expected_group}\n"
f" Description: {description}\n"
f" Fix: sudo chown {actual_owner}:{expected_group} {path}"
)

if not errors or not any(path in err for err in errors):
print(f"✓ {path}: {actual_mode} {actual_owner}:{actual_group} - OK")

except Exception as e:
errors.append(f"ERROR: Failed to check {path}: {str(e)}")

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

print("\nAll directory permissions are correct.")


def main():
parser = argparse.ArgumentParser(
prog="Supabase Postgres Artifact Permissions Checker",
Expand Down Expand Up @@ -258,6 +382,9 @@ def main():
# Check if postgresql.service is using a read-only mount for /etc
check_postgresql_mount()

# Check directory permissions for security-critical paths
check_directory_permissions()


if __name__ == "__main__":
main()
8 changes: 8 additions & 0 deletions ansible/tasks/setup-postgres.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@
loop_control:
loop_var: 'pg_dir_item'

- name: Set /var/lib/postgresql to 0755 for nix-profile symlink traversal
ansible.builtin.file:
group: 'postgres'
mode: '0755'
owner: 'postgres'
path: '/var/lib/postgresql'
state: 'directory'

- name: Allow adminapi to write custom config
ansible.builtin.file:
group: 'postgres'
Expand Down
8 changes: 4 additions & 4 deletions ansible/tasks/stage2-setup-postgres.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@

- name: Create symlinks for Nix files into /usr/bin
ansible.builtin.file:
group: 'postgres'
owner: 'postgres'
group: 'root'
owner: 'root'
path: "/usr/bin/{{ file_item['path'] | basename }}"
src: "{{ file_item['path'] }}"
state: 'link'
Expand All @@ -150,8 +150,8 @@

- name: Create symlinks for Nix files into /usr/bin
ansible.builtin.file:
group: 'postgres'
owner: 'postgres'
group: 'root'
owner: 'root'
path: "/usr/bin/{{ link_item['path'] | basename }}"
src: "{{ link_item['path'] }}"
state: 'link'
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.5.1.064-orioledb"
postgres17: "17.6.1.043"
postgres15: "15.14.1.043"
postgresorioledb-17: "17.5.1.065-orioledb"
postgres17: "17.6.1.044"
postgres15: "15.14.1.044"

# Non Postgres Extensions
pgbouncer_release: 1.19.0
Expand Down
Loading