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
128 changes: 72 additions & 56 deletions ansible/files/permission_check.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import subprocess
import json
import sys
import argparse
import os
import stat
import pwd
import grp


# Expected groups for each user
Expand Down Expand Up @@ -159,21 +159,25 @@
}


# This program depends on osquery being installed on the system
# Function to run osquery
def run_osquery(query):
process = subprocess.Popen(
["osqueryi", "--json", query], stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
output, error = process.communicate()
return output.decode("utf-8")


def parse_json(json_str):
def get_user_groups(username):
"""Get all groups that a user belongs to using Python's pwd and grp modules."""
try:
return json.loads(json_str)
except json.JSONDecodeError as e:
print("Error decoding JSON:", e)
user_info = pwd.getpwnam(username)
user_uid = user_info.pw_uid
user_gid = user_info.pw_gid

# Get all groups
groups = []
for group in grp.getgrall():
# Check if user is in the group (either as primary group or in member list)
if user_gid == group.gr_gid or username in group.gr_mem:
groups.append({"username": username, "groupname": group.gr_name})

# Sort by groupname to match expected behavior
groups.sort(key=lambda x: x["groupname"])
return groups
except KeyError:
print(f"User '{username}' not found")
sys.exit(1)


Expand All @@ -195,43 +199,60 @@ def compare_results(username, query_result):


def check_nixbld_users():
query = """
SELECT u.username, g.groupname
FROM users u
JOIN user_groups ug ON u.uid = ug.uid
JOIN groups g ON ug.gid = g.gid
WHERE u.username LIKE 'nixbld%';
"""
query_result = run_osquery(query)
parsed_result = parse_json(query_result)

for user in parsed_result:
if user["groupname"] != "nixbld":
print(
f"User '{user['username']}' is in group '{user['groupname']}' instead of 'nixbld'."
)
sys.exit(1)
"""Check that all nixbld users are only in the nixbld group."""
# Get all users that match the pattern nixbld*
nixbld_users = []
for user in pwd.getpwall():
if user.pw_name.startswith("nixbld"):
nixbld_users.append(user.pw_name)

if not nixbld_users:
print("No nixbld users found")
return

# Check each nixbld user's groups
for username in nixbld_users:
groups = get_user_groups(username)
for user_group in groups:
if user_group["groupname"] != "nixbld":
print(
f"User '{username}' is in group '{user_group['groupname']}' instead of 'nixbld'."
)
sys.exit(1)

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


def check_postgresql_mount():
# 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
"""Check that postgresql.service mounts /etc as read-only."""
# Find the postgres process by reading /proc
# We're looking for a process with .postgres-wrapped in the path
# and -D /etc/postgresql in the command line
pid = None

for proc_dir in os.listdir("/proc"):
if not proc_dir.isdigit():
continue

try:
# Read the command line
with open(f"/proc/{proc_dir}/cmdline", "r") as f:
cmdline = f.read()
# Check if this is a postgres process with the right data directory
if ".postgres-wrapped" in cmdline and "-D /etc/postgresql" in cmdline:
pid = proc_dir
break
except (FileNotFoundError, PermissionError):
# Process might have disappeared or we don't have permission
continue

if pid is None:
print(
"Could not find postgres process with .postgres-wrapped and -D /etc/postgresql"
)
sys.exit(1)

# 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:
Expand Down Expand Up @@ -265,9 +286,6 @@ def check_directory_permissions():
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

Expand Down Expand Up @@ -369,12 +387,10 @@ def main():
if not qemu_artifact:
usernames.append("ec2-instance-connect")

# Iterate over usernames, run the query, and compare results
# Iterate over usernames, get their groups, and compare results
for username in usernames:
query = f"SELECT u.username, g.groupname FROM users u JOIN user_groups ug ON u.uid = ug.uid JOIN groups g ON ug.gid = g.gid WHERE u.username = '{username}' ORDER BY g.groupname;"
query_result = run_osquery(query)
parsed_result = parse_json(query_result)
compare_results(username, parsed_result)
user_groups = get_user_groups(username)
compare_results(username, user_groups)

# Check if all nixbld users are in the nixbld group
check_nixbld_users()
Expand Down
16 changes: 2 additions & 14 deletions ansible/playbook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -204,17 +204,11 @@
apt autoremove -y --purge snapd
when: stage2_nix

- name: Install osquery from nixpkgs binary cache
become: yes
shell: |
sudo -u ubuntu bash -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && nix profile install github:nixos/nixpkgs/f98ec4f73c762223d62bee706726138cb6ea27cc#osquery"
when: stage2_nix

- name: Run osquery permission checks
- name: Run 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 '' }}"
/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

Expand All @@ -226,12 +220,6 @@
systemctl stop fail2ban.service
when: stage2_nix

- name: Remove osquery
become: yes
shell: |
sudo -u ubuntu bash -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && nix profile remove osquery"
when: stage2_nix

- name: nix collect garbage
become: yes
shell: |
Expand Down
10 changes: 10 additions & 0 deletions ansible/tasks/setup-wal-g.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,21 @@
cmd: sudo -u wal-g bash -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && nix profile install github:supabase/postgres/{{ git_commit_sha }}#wal-g-2"
become: true

- name: nix collect garbage
ansible.builtin.shell:
cmd: sudo -u ubuntu bash -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && nix-collect-garbage -d"
become: true

- name: Install wal-g 3 from nix binary cache
ansible.builtin.shell:
cmd: sudo -u wal-g bash -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && nix profile install github:supabase/postgres/{{ git_commit_sha }}#wal-g-3"
become: true

- name: nix collect garbage
ansible.builtin.shell:
cmd: sudo -u ubuntu bash -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && nix-collect-garbage -d"
become: true

- name: Create symlink for wal-g-3 from Nix profile to /usr/local/bin
ansible.builtin.file:
dest: '/usr/local/bin/wal-g-v3'
Expand Down