Add OpenShift compatibility: rootless, arbitrary-UID support#432
Add OpenShift compatibility: rootless, arbitrary-UID support#432jfstenuit wants to merge 10 commits into
Conversation
…ID support for OpenShift MISP's containers relied on running as root and dropping privilege at runtime (supervisord as root, ~85 `sudo -u www-data` calls, nginx binding 80/443, php-fpm's -R flag, chown-based permission enforcement on every start). None of this works under OpenShift's restricted/restricted-v2 Security Context Constraint, which always runs containers as an arbitrary, unpredictable UID, never root, and forbids privilege escalation. Rather than special-case OpenShift, all three images now run as their final identity from PID 1 everywhere (Docker Compose, Kubernetes, Helm): - misp-core: removed the sudo package and every sudo invocation in configure_misp.sh/utilities.sh; dropped php-fpm's -R flag and all supervisor `user=` directives; nginx moved to unprivileged ports 8080/8443; every system path the entrypoint chain writes to (nginx, php-fpm, supervisord, rsyslog config dirs) is now baked at build time with group-0 ownership, group=owner permissions, and setgid on directories, so files stay accessible regardless of which UID the container actually runs as. Config file rewriting (bootstrap.php, database.php, email.php, app/files seeding) switched from chmod-then-edit-in-place to copy/recreate-then-edit, since chmod and in-place writes require file ownership, not just group-write access. rsyslog's imuxsock module (needs a writable /dev/log) and update-ca-certificates (needs a writable system trust store) are now root-only, skipped otherwise, since neither is achievable without root or a writable image layer. - misp-modules/misp-guard: their service users are now supplementary members of group 0, and their writable directories (/custom, /home/misp, /srv, /home/guard, the shared mitmproxy CA volume) are group-0-owned with setgid, matching the same arbitrary-UID convention. - docker-compose.yml/Makefile: container-internal ports updated to 8080/8443; the sudo-only AUDIT_WRITE capability dropped; added `make init-dirs` since misp-core can no longer chown its bind-mounted host directories itself on first start. - Helm chart: misp.securityContext no longer pins runAsUser/runAsGroup, letting OpenShift assign its own; mariadb/valkey's global.compatibility.openshift.adaptSecurityContext made explicit; documented the bitnamilegacy registry as an accepted short-term tradeoff for OpenShift compatibility on the DB/cache layer. - Raw kubernetes/manifests: replaced fixed fsGroup/runAsUser with restricted-compliant securityContext (no fixed UID, capabilities dropped, no privilege escalation); documented that the stock mariadb/redis images here aren't arbitrary-UID-safe on a fresh PVC and that the Helm chart is the recommended OpenShift path.
- default admin account back to an e-mail address - properly start supervisord and background workers - use a dynamically generated random password for supervisord
…to openshift-ready
|
Thanks for this. We might need to give priority to this though at this stage #430 Would a rebase possible (some of the concerns should already be addressed by that PR) once we merge? |
Clearly, this goes in the right direction. The new nginx container is natively Openshift-compatible and the move to unprivileged ports aligns with the security requirements. After that change, I'll still have to adapt the init scripts and remove any leftover "sudo" or "chown" along the way. There is also the "-R" flag of php-fpm that needs to be dropped. Also, the whole helm chart needs to be reviewed and aligned with the new Dockerfile. I suggest I start a new branch once #430 is merged, with two change sets :
|
|
@jfstenuit Nice! I had the In addition to the permission and UID changes I planned to switch the FPM / PHP config to a template based syntax as the nginx config in #430 instead of all the sed commands. Maybe you're interested in integrating that in your PR too? PHP templates# docker-compose.yml
services:
misp-core:
tmpfs:
- /opt/php:mode=0755,uid=33,gid=33# core/Dockerfile
ENV PHP_INI_SCAN_DIR="/etc/php/${PHP_PACKAGE_VERSION}/conf.d:/opt/php/conf.d"
...
# overwrite global php-fpm config to load dynamic pool configs
COPY <<-EOF /etc/php/${PHP_PACKAGE_VERSION}/fpm/php-fpm.conf
[global]
pid = /run/php/php${PHP_PACKAGE_VERSION}-fpm.pid
error_log = /var/log/php${PHP_PACKAGE_VERSION}-fpm.log
include=/etc/php/${PHP_PACKAGE_VERSION}/fpm/pool.d/*.conf
include=/opt/php/pool.d/*.conf
EOF# core/files/entrypoint_fpm.sh
generate_php_config() {
local REDIS_HOST_WITH_SCHEME
local REDIS_PASSWORD_ESCAPED
local STATUS_LISTEN
mkdir -p /opt/php/{pool.d,conf.d}
REDIS_HOST_WITH_SCHEME=$(echo "$REDIS_HOST" | grep -E '^\w+://' || echo "tcp://$REDIS_HOST")
REDIS_PASSWORD_ESCAPED=$(printf '%s\n' "$REDIS_PASSWORD" | sed -e 's/[\/&]/\\&/g')
if [[ "$ENABLE_REDIS_EMPTY_PASSWORD" == "true" ]]; then
REDIS_SAVE_PATH="${REDIS_HOST_WITH_SCHEME}:${REDIS_PORT}"
elif [[ -n "$REDIS_PASSWORD" ]]; then
REDIS_SAVE_PATH="${REDIS_HOST_WITH_SCHEME}:${REDIS_PORT}?auth=${REDIS_PASSWORD_ESCAPED}"
else
echo "ERROR: REDIS_PASSWORD is not set but ENABLE_REDIS_EMPTY_PASSWORD is false. Please set REDIS_PASSWORD or enable ENABLE_REDIS_EMPTY_PASSWORD=true for passwordless Redis."
exit 1
fi
# FPM listen address
if [[ -n "$PHP_LISTEN_FPM" ]]; then
FPM_LISTEN=$([[ "$DISABLE_IPV6" == "true" ]] && echo "0.0.0.0:9002" || echo "[::]:9002")
fi
# FPM status block
if [[ -n "$FASTCGI_LISTEN_STATUS" ]]; then
STATUS_LISTEN=$([[ "$DISABLE_IPV6" == "true" ]] && echo "0.0.0.0:9003" || echo "[::]:9003")
FPM_STATUS_BLOCK="pm.status_listen=${STATUS_LISTEN}"$'\n'"pm.status_path=/fpm-status"
else
FPM_STATUS_BLOCK=""
fi
# pm.max_spare_servers: must be >= pm.start_servers
if [[ "$PHP_FCGI_START_SERVERS" -gt "$PHP_FCGI_SPARE_SERVERS" ]]; then
PHP_FCGI_MAX_SPARE_SERVERS="$PHP_FCGI_START_SERVERS"
else
PHP_FCGI_MAX_SPARE_SERVERS="$PHP_FCGI_SPARE_SERVERS"
fi
# --- render templates ---
export REDIS_SAVE_PATH FPM_LISTEN FPM_STATUS_BLOCK PHP_FCGI_MAX_SPARE_SERVERS
envsubst < /opt/php/templates/99-runtime.ini > /opt/php/conf.d/99-runtime.ini
envsubst < /opt/php/templates/99-www.conf > /opt/php/pool.d/99-www.conf
}# core/files/opt/php/templates/99-runtime.ini
memory_limit=${PHP_MEMORY_LIMIT}
max_execution_time=${PHP_MAX_EXECUTION_TIME}
upload_max_filesize=${PHP_UPLOAD_MAX_FILESIZE}
max_file_uploads=${PHP_MAX_FILE_UPLOADS}
post_max_size=${PHP_POST_MAX_SIZE}
max_input_time=${PHP_MAX_INPUT_TIME}
date.timezone=${TZ}
session.save_handler=redis
session.save_path='${REDIS_SAVE_PATH}'
session.sid_length=64
session.use_strict_mode=1# core/files/opt/php/templates/99-www.conf
listen=${FPM_LISTEN}
pm.max_children=${PHP_FCGI_CHILDREN}
pm.start_servers=${PHP_FCGI_START_SERVERS}
pm.min_spare_servers=${PHP_FCGI_SPARE_SERVERS}
pm.max_spare_servers=${PHP_FCGI_MAX_SPARE_SERVERS}
pm.max_requests=${PHP_FCGI_MAX_REQUESTS}
${FPM_STATUS_BLOCK} |
MISP's images assumed a fixed UID and root-then-drop-privilege startup (
sudo,chown, binding privileged ports, php-fpm'suser/group/listen.owner/listen.groupdirectives), which conflicts with OpenShift'srestrictedSCC - pods run under a randomly assigned, non-predictable UID with primary GID0, never root, and never withCAP_CHOWN/CAP_SETUID.This PR reworks
misp-core,misp-modules, andmisp-guardto start as their final identity instead of dropping into it, following the standard "support arbitrary user IDs" container pattern (owner + group0,g=upermissions, setgid on directories so new files stay group-writable regardless of the runtime UID). Docker Compose, the raw Kubernetes manifests, and the Helm chart are all updated to match - including moving nginx off privileged ports 80/443, removing hardcodedrunAsUser/fsGroupfrom pod security contexts, and generating all credentials (admin, API key, supervisord, MariaDB) as strong random secrets instead of static defaults. Non-OpenShift usage (plain Docker/Compose/vanilla k8s with a fixed UID) is unaffected - the default UID (33/www-data) is kept and simply added to supplementary group0.