Support for Read-Only Root Filesystem & Non-Root User (Kubernetes Hardening)

Is your feature request related to a problem? Please describe.

I am deploying Pi-hole in a hardened Kubernetes environment following security best practices:

  • readOnlyRootFilesystem: true

  • runAsNonRoot: true (User 1000)

  • capabilities: drop: ["ALL"] (with specific adds like NET_BIND_SERVICE handled by the runtime)

The current src/bash_functions.sh script assumes a mutable filesystem and root privileges, causing the container to crash loop or fail startup in these environments.

Detailed Analysis of Blockers

Based on the current src/bash_functions.sh, I have identified 5 specific conflict areas:

1. Crash Loop on Capability Check (fix_capabilities)

  • Location: fix_capabilities function.

  • Problem: The script attempts setcap on the pihole-FTL binary. On a Read-Only Root Filesystem (RORFS), this fails. The script explicitly checks if [[ $ret -ne 0 ... and exits with status 1.

  • Context: In Kubernetes, capabilities are often granted to the process by the runtime (CRI-O/Containerd). The binary file itself does not need the attributes set if the process is already privileged.

2. Hardcoded Crontab Modification (start_cron)

  • Location: start_cron function.

  • Problem: The script runs sed -i on /crontab.txt (located at root) to randomize schedule times. This fails on RORFS because the file is not writable.

3. Mandatory Internal Cron Daemon (start_cron)

  • Location: start_cron function.

  • Problem: The script unconditionally starts /usr/sbin/crond. Hardened K8s deployments often prefer using native CronJob resources to manage updates/rotation externally rather than running a multi-process container. Also crond normally needs suid permission.

4. MacVendor DB Permission Check (ftl_config)

  • Location: ftl_config function.

  • Problem: The fallback logic attempts chown pihole:pihole /macvendor.db if the custom file env var is not set. This fails on RORFS.

5. UID/GID Modification on Startup (set_uid_gid)

  • Location: set_uid_gid function.

  • Problem: The script unconditionally runs usermod and groupmod if the env vars differ from the current user.

  • Context: On a RORFS, /etc/passwd cannot be modified. Furthermore, a container running as non-root (UID 1000) lacks the permission to execute these commands.

Proposed Solution

I propose updating src/bash_functions.sh to support "Smart Detection" where possible, with "Opt-In Skips" as fallbacks.

1. Capabilities:

  • Smart Fix: Before failing on setcap, check capsh --print (if available) to see if the required capabilities are effectively present on the process.

  • Fallback: Add a SKIP_SETCAP_CHECK environment variable to bypass the exit 1 failure.

2. Crontab:

  • Smart Fix: Detect if /crontab.txt is writable. If not, copy it to /tmp/crontab.txt (which is usually writable in K8s), perform the sed modification there, and point crond to the new location.

3. Internal Cron:

  • Feature: Add SKIP_INTERNAL_CRON=true environment variable to skip the start_cron function entirely for users managing jobs externally.

4. MacVendor DB:

  • Smart Fix: Wrap the permissions change in a check: [ -w /macvendor.db ] && chown ....

5. UID/GID:

  • Smart Fix: Only attempt usermod/groupmod if the script is running as root (id -u == 0). If running as non-root, assume the container runtime has handled the User ID mapping (e.g. K8s runAsUser).

Additional Context

These changes would allow Pi-hole to run natively in OpenShift and "Restricted" Kubernetes namespaces without requiring dangerous privileges (allowPrivilegeEscalation: true) or writable root filesystems.