diff --git a/Dockerfile b/Dockerfile index babc093f..fb562b23 100755 --- a/Dockerfile +++ b/Dockerfile @@ -50,6 +50,12 @@ RUN python -m pip install --upgrade pip setuptools wheel && \ FROM alpine:3.22 AS runner ARG INSTALL_DIR=/app +# Runtime service account (override at build; container user can still be overridden at run time) +ARG NETALERTX_UID=20211 +ARG NETALERTX_GID=20211 +# Read-only lock owner (kept at 20211 by default for immutability) +ARG READONLY_UID=20211 +ARG READONLY_GID=20211 # NetAlertX app directories ENV NETALERTX_APP=${INSTALL_DIR} @@ -129,8 +135,8 @@ RUN apk add --no-cache bash mtr libbsd zip lsblk tzdata curl arp-scan iproute2 i nginx supercronic shadow && \ rm -Rf /var/cache/apk/* && \ rm -Rf /etc/nginx && \ - addgroup -g 20211 ${NETALERTX_GROUP} && \ - adduser -u 20211 -D -h ${NETALERTX_APP} -G ${NETALERTX_GROUP} ${NETALERTX_USER} && \ + addgroup -g ${NETALERTX_GID} ${NETALERTX_GROUP} && \ + adduser -u ${NETALERTX_UID} -D -h ${NETALERTX_APP} -G ${NETALERTX_GROUP} ${NETALERTX_USER} && \ apk del shadow @@ -150,8 +156,8 @@ RUN install -d -o ${NETALERTX_USER} -g ${NETALERTX_GROUP} -m 700 ${READ_WRITE_FO COPY --chown=${NETALERTX_USER}:${NETALERTX_GROUP} .[V]ERSION ${NETALERTX_APP}/.VERSION COPY --chown=${NETALERTX_USER}:${NETALERTX_GROUP} .[V]ERSION ${NETALERTX_APP}/.VERSION_PREV -# Copy the virtualenv from the builder stage -COPY --from=builder --chown=20212:20212 ${VIRTUAL_ENV} ${VIRTUAL_ENV} +# Copy the virtualenv from the builder stage (owned by readonly lock owner) +COPY --from=builder --chown=${READONLY_UID}:${READONLY_GID} ${VIRTUAL_ENV} ${VIRTUAL_ENV} # Initialize each service with the dockerfiles/init-*.sh scripts, once. @@ -162,7 +168,7 @@ RUN for vfile in .VERSION .VERSION_PREV; do \ if [ ! -f "${NETALERTX_APP}/${vfile}" ]; then \ echo "DEVELOPMENT 00000000" > "${NETALERTX_APP}/${vfile}"; \ fi; \ - chown 20212:20212 "${NETALERTX_APP}/${vfile}"; \ + chown ${READONLY_UID}:${READONLY_GID} "${NETALERTX_APP}/${vfile}"; \ done && \ apk add --no-cache libcap && \ setcap cap_net_raw+ep /bin/busybox && \ @@ -187,6 +193,12 @@ ENTRYPOINT ["/bin/sh","/entrypoint.sh"] # This stage is separate from Runner stage so that devcontainer can use the Runner stage. FROM runner AS hardened +# Re-declare UID/GID args for this stage +ARG NETALERTX_UID=20211 +ARG NETALERTX_GID=20211 +ARG READONLY_UID=20211 +ARG READONLY_GID=20211 + ENV UMASK=0077 # Create readonly user and group with no shell access. @@ -194,8 +206,8 @@ ENV UMASK=0077 # AI may claim this is stupid, but it's actually least possible permissions as # read-only user cannot login, cannot sudo, has no write permission, and cannot even # read the files it owns. The read-only user is ownership-as-a-lock hardening pattern. -RUN addgroup -g 20212 "${READ_ONLY_GROUP}" && \ - adduser -u 20212 -G "${READ_ONLY_GROUP}" -D -h /app "${READ_ONLY_USER}" +RUN addgroup -g ${READONLY_GID} "${READ_ONLY_GROUP}" && \ + adduser -u ${READONLY_UID} -G "${READ_ONLY_GROUP}" -D -h /app "${READ_ONLY_USER}" # reduce permissions to minimum necessary for all NetAlertX files and folders diff --git a/docker-compose.yml b/docker-compose.yml index 02f6dd02..4a745500 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,8 @@ services: image: netalertx:latest container_name: netalertx # The name when you docker contiainer ls read_only: true # Make the container filesystem read-only + # Runtime user is configurable; defaults align with image build args + user: "${NETALERTX_UID:-20211}:${NETALERTX_GID:-20211}" cap_drop: # Drop all capabilities for enhanced security - ALL cap_add: # Add only the necessary capabilities @@ -49,7 +51,7 @@ services: # uid=20211 and gid=20211 is the netalertx user inside the container # mode=1700 gives rwx------ permissions to the netalertx user only tmpfs: - - "/tmp:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime" + - "/tmp:uid=${NETALERTX_UID:-20211},gid=${NETALERTX_GID:-20211},mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime" environment: LISTEN_ADDR: ${LISTEN_ADDR:-0.0.0.0} # Listen for connections on all interfaces PORT: ${PORT:-20211} # Application port diff --git a/install/production-filesystem/entrypoint.d/0-storage-permission.sh b/install/production-filesystem/entrypoint.d/0-storage-permission.sh index 29fc0a19..1a7c390d 100644 --- a/install/production-filesystem/entrypoint.d/0-storage-permission.sh +++ b/install/production-filesystem/entrypoint.d/0-storage-permission.sh @@ -23,6 +23,8 @@ ${NETALERTX_CONFIG_FILE} ${NETALERTX_DB_FILE} " +TARGET_USER="${NETALERTX_USER:-netalertx}" + # If running as root, fix permissions first if [ "$(id -u)" -eq 0 ]; then >&2 printf "%s" "${MAGENTA}" @@ -54,11 +56,11 @@ EOF # Set ownership and permissions for each read-write path individually printf '%s\n' "${READ_WRITE_PATHS}" | while IFS= read -r path; do [ -n "${path}" ] || continue - chown -R netalertx "${path}" 2>/dev/null || true + chown -R "${TARGET_USER}" "${path}" 2>/dev/null || true find "${path}" -type d -exec chmod u+rwx {} \; find "${path}" -type f -exec chmod u+rw {} \; done - echo Permissions fixed for read-write paths. Please restart the container as user 20211. + echo Permissions fixed for read-write paths. Please restart the container as user ${TARGET_USER}. sleep infinity & wait $! fi diff --git a/install/production-filesystem/entrypoint.d/60-user-netalertx.sh b/install/production-filesystem/entrypoint.d/60-user-netalertx.sh index df31641c..bf61eeeb 100755 --- a/install/production-filesystem/entrypoint.d/60-user-netalertx.sh +++ b/install/production-filesystem/entrypoint.d/60-user-netalertx.sh @@ -9,10 +9,10 @@ CURRENT_GID="$(id -g)" # Fallback to known defaults when lookups fail if [ -z "${EXPECTED_UID}" ]; then - EXPECTED_UID="20211" + EXPECTED_UID="${CURRENT_UID}" fi if [ -z "${EXPECTED_GID}" ]; then - EXPECTED_GID="20211" + EXPECTED_GID="${CURRENT_GID}" fi if [ "${CURRENT_UID}" -eq "${EXPECTED_UID}" ] && [ "${CURRENT_GID}" -eq "${EXPECTED_GID}" ]; then