Handle more edge cases; more clear warnings

This commit is contained in:
Adam Outler
2026-01-05 02:08:32 +00:00
parent 16375abb51
commit c86d0c8772
15 changed files with 613 additions and 1482 deletions

View File

@@ -3,6 +3,9 @@
#
# This script runs early to detect missing capabilities that would cause later
# scripts (like Python-based checks) to fail with "Operation not permitted".
# This is not for checking excessive capabilities, which is handled in another
# startup script.
RED=$(printf '\033[1;31m')
YELLOW=$(printf '\033[1;33m')

View File

@@ -1,6 +1,11 @@
#!/bin/sh
# first-run-check.sh - Checks and initializes configuration files on first run
# Fix permissions if config directory exists but is unreadable
if [ -d "${NETALERTX_CONFIG}" ]; then
chmod u+rwX "${NETALERTX_CONFIG}" 2>/dev/null || true
fi
chmod u+rw "${NETALERTX_CONFIG}/app.conf" 2>/dev/null || true
# Check for app.conf and deploy if required
if [ ! -f "${NETALERTX_CONFIG}/app.conf" ]; then
mkdir -p "${NETALERTX_CONFIG}" || {

View File

@@ -2,6 +2,12 @@
# Ensures the database exists, or creates a new one on first run.
# Intended to run only at initial startup.
# Fix permissions if DB directory exists but is unreadable
if [ -d "${NETALERTX_DB}" ]; then
chmod u+rwX "${NETALERTX_DB}" 2>/dev/null || true
fi
chmod u+rw "${NETALERTX_DB_FILE}" 2>/dev/null || true
set -eu
CYAN=$(printf '\033[1;36m')

View File

@@ -20,6 +20,12 @@ ensure_dir() {
# When creating as the user running the services, we ensure correct ownership and access
path="$1"
label="$2"
# Fix permissions if directory exists but is unreadable/unwritable
# It's expected chown is done as root during root-entrypoint, and now we own the files
# here we will set correct access.
if [ -d "${path}" ]; then
chmod u+rwX "${path}" 2>/dev/null || true
fi
if ! mkdir -p "${path}" 2>/dev/null; then
if is_tmp_path "${path}"; then
warn_tmp_skip "${path}" "${label}"

View File

@@ -33,7 +33,7 @@ if [ "$EXTRA" -ne 0 ]; then
⚠️ Warning: Excessive capabilities detected (bounding caps: 0x$BND_HEX).
Only CHOWN, SETGID, SETUID, NET_ADMIN, NET_BIND_SERVICE, and NET_RAW are
required in this container. Please remove unnecessary capabilities.
required in this container. Please remove unnecessary capabilities.
https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/excessive-capabilities.md
══════════════════════════════════════════════════════════════════════════════

View File

@@ -1,14 +1,14 @@
#!/bin/bash
# NetAlertX Root-Priming Entrypoint — best-effort permission priming 🔧
#
# Purpose:
# Responsibilities:
# - Provide a runtime, best-effort remedy for host volume ownership/mode issues
# (common on appliances like Synology where Docker volume copyup is limited).
# - Ensure writable paths exist, attempt to `chown`/`chmod` to a runtime `PUID`/`PGID`
# - Ensure writable paths exist, attempt to `chown` to a runtime `PUID`/`PGID`
# (defaults to 20211), then drop privileges via `su-exec` if possible.
#
# Design & behavior notes:
# - This script is intentionally *non-fatal* for chown/chmod failures; operations are
# - This script is intentionally *non-fatal* for chown failures; operations are
# best-effort so we avoid blocking container startup on imperfect hosts.
# - Runtime defaults are used so the image works without requiring build-time args.
# - If the container is started as non-root (`user:`), priming is skipped and it's the
@@ -16,42 +16,60 @@
# - If `su-exec` cannot drop privileges, we log a note and continue as the current user
# rather than aborting (keeps first-run resilient).
#
# Operational recommendation:
# - For deterministic ownership, explicitly set `PUID`/`PGID` (or pre-chown host volumes),
# and when hardening capabilities add `cap_add: [CHOWN]` so priming can succeed.
# Behavioral conditions:
# 1. RUNTIME: NON-ROOT (Container started as user: 1000)
# - PUID/PGID env vars are ignored (cannot switch users).
# - Write permissions check performed on /data and /tmp.
# - EXEC: Direct entrypoint execution as current user.
#
# 2. RUNTIME: ROOT (Container started as user: 0)
# A. TARGET: PUID=0 (User requested root)
# - Permissions priming skipped (already root).
# - EXEC: Direct entrypoint execution as root (with security warning).
#
# B. TARGET: PUID > 0 (User requested privilege drop)
# - PRIMING: Attempt chown on /data & /tmp to PUID:PGID.
# (Failures logged but non-fatal to support NFS/ReadOnly mounts).
# - EXEC: Attempt `su-exec PUID:PGID`.
# - Success: Process runs as PUID.
# - Failure (Missing CAPS): Fallback to running as root to prevent crash.
# - If PUID=0, log a warning and run directly.
# - Otherwise, attempt to prime paths and `su-exec` to PUID:PG
PUID="${PUID:-${NETALERTX_UID:-20211}}"
PGID="${PGID:-${NETALERTX_GID:-20211}}"
# Pretty terminal colors used for fatal messages (kept minimal + POSIX printf)
RED=$(printf '\033[1;31m')
RESET=$(printf '\033[0m')
_error_msg() {
title="$1"
body="$2"
>&2 printf "%s" "${RED}"
>&2 cat <<EOF
══════════════════════════════════════════════════════════════════════════════
🔒 SECURITY - FATAL: ${title}
${body}
══════════════════════════════════════════════════════════════════════════════
EOF
>&2 printf "%s" "${RESET}"
}
_validate_id() {
value="$1"
name="$2"
if ! printf '%s' "${value}" | grep -qxE '[0-9]+'; then
>&2 printf "%s" "${RED}"
>&2 cat <<EOF
══════════════════════════════════════════════════════════════════════════════
🔒 SECURITY - FATAL: invalid ${name} value (non-numeric)
_error_msg "INVALID ${name} VALUE (non-numeric)" \
" Startup halted because the provided ${name} environmental variable
contains non-digit characters.
Startup halted because the provided ${name} environmental variable
contains non-digit characters. This is a deliberate security measure to
prevent environment-variable command injection while the container runs as
root during initial startup.
Action: set a numeric ${name} (for example: PUID=1000) in your environment
or docker-compose file and restart the container. Default: 20211.
For more information and troubleshooting, see:
https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/PUID_PGID_SECURITY.md
══════════════════════════════════════════════════════════════════════════════
EOF
>&2 printf "%s" "${RESET}"
exit 1
Action: set a numeric ${name} (for example: ${name}=1000) in your environment
or docker-compose file. Default: 20211."
exit 1
fi
}
@@ -61,25 +79,29 @@ _validate_id "${PGID}" "PGID"
_cap_bits_warn_missing_setid() {
cap_hex=$(awk '/CapEff/ {print $2}' /proc/self/status 2>/dev/null || echo "")
[ -n "${cap_hex}" ] || return
cap_dec=$((0x${cap_hex}))
has_setgid=0; has_setuid=0; has_net_caps=0
# POSIX compliant base16 on permissions
cap_dec=$(awk 'BEGIN { h = "0x'"${cap_hex}"'"; if (h ~ /^0x[0-9A-Fa-f]+$/) { printf "%d", h } else { print 0 } }')
# Bit masks (use numeric constants to avoid editor/HL issues and improve clarity)
# 1 << 6 = 64
# 1 << 7 = 128
# (1<<10)|(1<<12)|(1<<13) = 1024 + 4096 + 8192 = 13312
SETGID_MASK=64
SETUID_MASK=128
NET_MASK=13312
has_setgid=0
has_setuid=0
has_net_caps=0
if [ $((cap_dec & (1 << 6))) -ne 0 ]; then
if (( cap_dec & SETGID_MASK )); then
has_setgid=1
fi
if [ $((cap_dec & (1 << 7))) -ne 0 ]; then
if (( cap_dec & SETUID_MASK )); then
has_setuid=1
fi
if [ $((cap_dec & (1 << 10))) -ne 0 ] || [ $((cap_dec & (1 << 12))) -ne 0 ] || [ $((cap_dec & (1 << 13))) -ne 0 ]; then
if (( cap_dec & NET_MASK )); then
has_net_caps=1
fi
if [ "${has_net_caps}" -eq 1 ] && { [ "${has_setgid}" -eq 0 ] || [ "${has_setuid}" -eq 0 ]; }; then
if (( has_net_caps == 1 && ( has_setgid == 0 || has_setuid == 0 ) )); then
>&2 echo "Note: CAP_SETUID/CAP_SETGID unavailable alongside NET_* caps; continuing as current user."
fi
}
@@ -87,15 +109,29 @@ _cap_bits_warn_missing_setid() {
_cap_bits_warn_missing_setid
if [ "$(id -u)" -ne 0 ]; then
if [ -n "${PUID:-}" ] || [ -n "${PGID:-}" ]; then
>&2 printf 'Note: container running as UID %s GID %s; requested PUID/PGID=%s:%s will not be applied.\n' \
"$(id -u)" "$(id -g)" "${PUID}" "${PGID}"
for path in "/tmp" "${NETALERTX_DATA:-/data}"; do
if [ -n "$path" ] && [ ! -w "$path" ]; then
_error_msg "FILESYSTEM PERMISSIONS ERROR" \
" Container is running as User $(id -u), but cannot write to:
${path}
Because the container is not running as root, it cannot fix these
permissions automatically.
Action:
1. Update Host Volume permissions (e.g. 'chmod 755 ${path}' on host).
2. Or, run container as root (user: 0) and let PUID/PGID logic handle it."
fi
done
if [ -n "${PUID:-}" ] && [ "${PUID}" != "$(id -u)" ]; then
>&2 printf 'Note: container running as UID %s; requested PUID=%s ignored.\n' "$(id -u)" "${PUID}"
fi
exec /entrypoint.sh "$@"
fi
if [ "${PUID}" -eq 0 ]; then
>&2 echo "WARNING: Running as root (PUID=0). Prefer a non-root PUID. See https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/file-permissions.md"
>&2 echo "WARNING: Running as root (PUID=0). Prefer a non-root PUID."
exec /entrypoint.sh "$@"
fi
@@ -103,28 +139,26 @@ _prime_paths() {
runtime_root="${NETALERTX_RUNTIME_BASE:-/tmp}"
paths="/tmp ${NETALERTX_DATA:-/data} ${NETALERTX_CONFIG:-/data/config} ${NETALERTX_DB:-/data/db} ${NETALERTX_LOG:-${runtime_root}/log} ${NETALERTX_PLUGINS_LOG:-${runtime_root}/log/plugins} ${NETALERTX_API:-${runtime_root}/api} ${SYSTEM_SERVICES_RUN:-${runtime_root}/run} ${SYSTEM_SERVICES_RUN_TMP:-${runtime_root}/run/tmp} ${SYSTEM_SERVICES_RUN_LOG:-${runtime_root}/run/logs} ${SYSTEM_SERVICES_ACTIVE_CONFIG:-${runtime_root}/nginx/active-config} ${runtime_root}/nginx"
chmod 1777 /tmp 2>/dev/null || true
# Always chown core roots up front so non-root runtime can chmod later.
chown -R "${PUID}:${PGID}" /data 2>/dev/null || true
chown -R "${PUID}:${PGID}" /tmp 2>/dev/null || true
for path in ${paths}; do
[ -n "${path}" ] || continue
if [ "${path}" = "/tmp" ]; then
continue
fi
install -d -o "${PUID}" -g "${PGID}" -m 700 "${path}" 2>/dev/null || true
if [ "${path}" = "/tmp" ]; then continue; fi
install -d -o "${PUID}" -g "${PGID}" "${path}" 2>/dev/null || true
chown -R "${PUID}:${PGID}" "${path}" 2>/dev/null || true
chmod -R u+rwX "${path}" 2>/dev/null || true
# Note: chown must be done by root, chmod can be done by non-root
# (chmod removed as non-root runtime will handle modes after ownership is set)
done
>&2 echo "Permissions prepared for PUID=${PUID}."
}
_prime_paths
unset NETALERTX_PRIVDROP_FAILED
if ! su-exec "${PUID}:${PGID}" /entrypoint.sh "$@"; then
rc=$?
export NETALERTX_PRIVDROP_FAILED=1
export NETALERTX_CHECK_ONLY="${NETALERTX_CHECK_ONLY:-1}"
export NETALERTX_CHECK_ONLY="${NETALERTX_CHECK_ONLY:-0}"
>&2 echo "Note: su-exec failed (exit ${rc}); continuing as current user without privilege drop."
exec /entrypoint.sh "$@"
fi