Skip to content

feat: limit /etc to readonly #1451

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 10, 2025
Merged
Changes from 5 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
31 changes: 31 additions & 0 deletions ansible/files/permission_check.py
Original file line number Diff line number Diff line change
@@ -95,6 +95,8 @@
],
}

# postgresql.service is expected to mount /etc as read-only
expected_mount = "/etc ro"

# This program depends on osquery being installed on the system
# Function to run osquery
@@ -151,6 +153,33 @@ def check_nixbld_users():

print("All nixbld users are in the 'nixbld' group.")

def check_postgresql_mount():
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice thank you!

# processes table has the nix .postgres-wrapped path as the
# binary path, rather than /usr/lib/postgresql/bin/postgres which
# is a symlink to /var/lib/postgresql/.nix-profile/bin/postgres, a script
# that ultimately calls /nix/store/...-postgresql-and-plugins-15.8/bin/.postgres-wrapped
query = """
SELECT pid
FROM processes
WHERE path LIKE '%.postgres-wrapped%'
AND cmdline LIKE '%-D /etc/postgresql%';
"""
query_result = run_osquery(query)
parsed_result = parse_json(query_result)

pid = parsed_result[0].get("pid")

# get the mounts for the process
with open(f"/proc/{pid}/mounts", "r") as o:
lines = [line for line in o if "/etc" in line and "ro," in line]
if len(lines) == 0:
print(f"Expected exactly 1 match, got 0")
sys.exit(1)
if len(lines) != 1:
print(f"Expected exactly 1 match, got {len(lines)}: {';'.join(lines)}")
sys.exit(1)

print("postgresql.service mounts /etc as read-only.")

def main():
parser = argparse.ArgumentParser(
@@ -218,6 +247,8 @@ def main():
# Check if all nixbld users are in the nixbld group
check_nixbld_users()

# Check if postgresql.service is using a read-only mount for /etc
check_postgresql_mount()

if __name__ == "__main__":
main()
3 changes: 3 additions & 0 deletions ansible/files/postgresql_config/postgresql.service.j2
Original file line number Diff line number Diff line change
@@ -21,5 +21,8 @@ RestartSec=5
OOMScoreAdjust=-1000
EnvironmentFile=-/etc/environment.d/postgresql.env
LimitNOFILE=16384
{% if supabase_internal is defined %}
ReadOnlyPaths=/etc
{% endif %}
[Install]
WantedBy=multi-user.target
2 changes: 2 additions & 0 deletions ansible/playbook.yml
Original file line number Diff line number Diff line change
@@ -213,7 +213,9 @@
- name: Run osquery permission checks
become: yes
shell: |
systemctl start postgresql.service
sudo -u ubuntu bash -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && /usr/bin/python3 /tmp/ansible-playbook/ansible/files/permission_check.py {{ '--qemu' if qemu_mode is defined else '' }}"
systemctl stop postgresql.service
when: stage2_nix

- name: Remove osquery
4 changes: 2 additions & 2 deletions ansible/vars.yml
Original file line number Diff line number Diff line change
@@ -8,8 +8,8 @@ postgres_major:

# Full version strings for each major version
postgres_release:
postgresorioledb-17: "17.0.1.053-orioledb"
postgres15: "15.8.1.060"
postgresorioledb-17: "17.0.1.054-orioledb"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, before we merge, let's add a suffix to these version numbers here like

postgresorioledb-17: "17.0.1.054-orioledb-etc-1"

Then sometime today we can test this in local infra, prior to merge.

postgres15: "15.8.1.061"

# Non Postgres Extensions
pgbouncer_release: "1.19.0"