From c15f621ad409c5cd146d087e0a6239261788ab3b Mon Sep 17 00:00:00 2001 From: Adam Outler Date: Sat, 3 Jan 2026 01:13:18 +0000 Subject: [PATCH] New PUID startup sequence --- .devcontainer/Dockerfile | 26 +- .devcontainer/devcontainer.json | 3 +- .../resources/devcontainer-Dockerfile | 3 + .gitignore | 1 + .vscode/settings.json | 12 +- Dockerfile | 23 +- docker-compose.yml | 37 +- docker_build.log | 558 ++---------------- .../entrypoint.d/0-storage-permission.sh | 68 --- .../entrypoint.d/05-data-migration.sh | 34 +- .../entrypoint.d/10-capabilities-audit.sh | 69 +++ .../{10-mounts.py => 15-mounts.py} | 173 +++--- ...t-run-config.sh => 20-first-run-config.sh} | 0 ...{20-first-run-db.sh => 25-first-run-db.sh} | 1 - .../entrypoint.d/25-mandatory-folders.sh | 93 --- .../entrypoint.d/30-mandatory-folders.sh | 103 ++++ ...-override.sh => 35-apply-conf-override.sh} | 4 +- ...itable-config.sh => 40-writable-config.sh} | 4 +- ...{40-nginx-config.sh => 45-nginx-config.sh} | 7 +- .../entrypoint.d/60-expected-user-id-match.sh | 48 ++ .../entrypoint.d/60-user-netalertx.sh | 23 - .../entrypoint.d/85-layer-2-capabilities.sh | 33 -- .../entrypoint.d/90-excessive-capabilities.sh | 12 +- install/production-filesystem/entrypoint.sh | 21 +- .../production-filesystem/root-entrypoint.sh | 130 ++++ .../services/start-nginx.sh | 4 +- .../services/start-php-fpm.sh | 5 +- pyproject.toml | 2 +- test-script.sh | 7 + 29 files changed, 620 insertions(+), 884 deletions(-) delete mode 100755 install/production-filesystem/entrypoint.d/0-storage-permission.sh create mode 100755 install/production-filesystem/entrypoint.d/10-capabilities-audit.sh rename install/production-filesystem/entrypoint.d/{10-mounts.py => 15-mounts.py} (78%) rename install/production-filesystem/entrypoint.d/{15-first-run-config.sh => 20-first-run-config.sh} (100%) rename install/production-filesystem/entrypoint.d/{20-first-run-db.sh => 25-first-run-db.sh} (99%) delete mode 100755 install/production-filesystem/entrypoint.d/25-mandatory-folders.sh create mode 100755 install/production-filesystem/entrypoint.d/30-mandatory-folders.sh rename install/production-filesystem/entrypoint.d/{30-apply-conf-override.sh => 35-apply-conf-override.sh} (90%) rename install/production-filesystem/entrypoint.d/{35-writable-config.sh => 40-writable-config.sh} (97%) rename install/production-filesystem/entrypoint.d/{40-nginx-config.sh => 45-nginx-config.sh} (94%) create mode 100755 install/production-filesystem/entrypoint.d/60-expected-user-id-match.sh delete mode 100755 install/production-filesystem/entrypoint.d/60-user-netalertx.sh delete mode 100755 install/production-filesystem/entrypoint.d/85-layer-2-capabilities.sh create mode 100644 install/production-filesystem/root-entrypoint.sh create mode 100755 test-script.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 739b0763..b46550a0 100755 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -29,6 +29,7 @@ ENV PATH="/opt/venv/bin:$PATH" # Install build dependencies COPY requirements.txt /tmp/requirements.txt +# hadolint ignore=DL3018 RUN apk add --no-cache \ bash \ shadow \ @@ -44,7 +45,8 @@ RUN apk add --no-cache \ && python -m venv /opt/venv # Upgrade pip/wheel/setuptools and install Python packages -RUN python -m pip install --upgrade pip setuptools wheel && \ +# hadolint ignore=DL3013 +RUN python -m pip install --no-cache-dir --upgrade pip setuptools wheel && \ pip install --prefer-binary --no-cache-dir -r /tmp/requirements.txt && \ chmod -R u-rwx,g-rwx /opt @@ -131,11 +133,11 @@ ENV READ_ONLY_USER=readonly READ_ONLY_GROUP=readonly ENV NETALERTX_USER=netalertx NETALERTX_GROUP=netalertx ENV LANG=C.UTF-8 - +# hadolint ignore=DL3018 RUN apk add --no-cache bash mtr libbsd zip lsblk tzdata curl arp-scan iproute2 iproute2-ss nmap \ nmap-scripts traceroute nbtscan net-tools net-snmp-tools bind-tools awake ca-certificates \ sqlite php83 php83-fpm php83-cgi php83-curl php83-sqlite3 php83-session python3 envsubst \ - nginx supercronic shadow && \ + nginx supercronic shadow su-exec && \ rm -Rf /var/cache/apk/* && \ rm -Rf /etc/nginx && \ addgroup -g ${NETALERTX_GID} ${NETALERTX_GROUP} && \ @@ -167,6 +169,7 @@ COPY --from=builder --chown=${READONLY_UID}:${READONLY_GID} ${VIRTUAL_ENV} ${VIR # This is done after the copy of the venv to ensure the venv is in place # although it may be quicker to do it before the copy, it keeps the image # layers smaller to do it after. +# hadolint ignore=DL3018 RUN for vfile in .VERSION .VERSION_PREV; do \ if [ ! -f "${NETALERTX_APP}/${vfile}" ]; then \ echo "DEVELOPMENT 00000000" > "${NETALERTX_APP}/${vfile}"; \ @@ -174,7 +177,6 @@ RUN for vfile in .VERSION .VERSION_PREV; do \ chown ${READONLY_UID}:${READONLY_GID} "${NETALERTX_APP}/${vfile}"; \ done && \ apk add --no-cache libcap && \ - setcap cap_net_raw+ep /bin/busybox && \ setcap cap_net_raw,cap_net_admin+eip /usr/bin/nmap && \ setcap cap_net_raw,cap_net_admin+eip /usr/bin/arp-scan && \ setcap cap_net_raw,cap_net_admin,cap_net_bind_service+eip /usr/bin/nbtscan && \ @@ -189,7 +191,7 @@ RUN for vfile in .VERSION .VERSION_PREV; do \ date +%s > "${NETALERTX_FRONT}/buildtimestamp.txt" -ENTRYPOINT ["/bin/sh","/entrypoint.sh"] +ENTRYPOINT ["/bin/bash","/entrypoint.sh"] # Final hardened stage to improve security by setting least possible permissions and removing sudo access. # When complete, if the image is compromised, there's not much that can be done with it. @@ -222,8 +224,8 @@ RUN chown -R ${READ_ONLY_USER}:${READ_ONLY_GROUP} ${READ_ONLY_FOLDERS} && \ chmod -R 004 ${READ_ONLY_FOLDERS} && \ find ${READ_ONLY_FOLDERS} -type d -exec chmod 005 {} + && \ install -d -o ${NETALERTX_USER} -g ${NETALERTX_GROUP} -m 0777 ${READ_WRITE_FOLDERS} && \ - chown ${READ_ONLY_USER}:${READ_ONLY_GROUP} /entrypoint.sh /opt /opt/venv && \ - chmod 005 /entrypoint.sh ${SYSTEM_SERVICES}/*.sh ${SYSTEM_SERVICES_SCRIPTS}/* ${ENTRYPOINT_CHECKS}/* /app /opt /opt/venv && \ + chown ${READ_ONLY_USER}:${READ_ONLY_GROUP} /entrypoint.sh /root-entrypoint.sh /opt /opt/venv && \ + chmod 005 /entrypoint.sh /root-entrypoint.sh ${SYSTEM_SERVICES}/*.sh ${SYSTEM_SERVICES_SCRIPTS}/* ${ENTRYPOINT_CHECKS}/* /app /opt /opt/venv && \ # Do not bake first-run artifacts into the image. If present, Docker volume copy-up # will persist restrictive ownership/modes into fresh named volumes, breaking # arbitrary non-root UID/GID runs. @@ -236,11 +238,12 @@ RUN chown -R ${READ_ONLY_USER}:${READ_ONLY_GROUP} ${READ_ONLY_FOLDERS} && \ rm -Rf /var /etc/sudoers.d/* /etc/shadow /etc/gshadow /etc/sudoers \ /lib/apk /lib/firmware /lib/modules-load.d /lib/sysctl.d /mnt /home/ /root \ /srv /media && \ - sed -i "/^\(${READ_ONLY_USER}\|${NETALERTX_USER}\):/!d" /etc/passwd && \ - sed -i "/^\(${READ_ONLY_GROUP}\|${NETALERTX_GROUP}\):/!d" /etc/group && \ + # Preserve root and system identities so hardened entrypoint never needs to patch /etc/passwd or /etc/group at runtime. printf '#!/bin/sh\n"$@"\n' > /usr/bin/sudo && chmod +x /usr/bin/sudo +USER "0" -USER netalertx +# Call root-entrypoint.sh which drops priviliges to run entrypoint.sh. +ENTRYPOINT ["/root-entrypoint.sh"] HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD /services/healthcheck.sh @@ -272,6 +275,9 @@ COPY .devcontainer/resources/devcontainer-overlay/ / USER root # Install common tools, create user, and set up sudo +# Ensure entrypoint scripts stay executable in the devcontainer (avoids 126 errors) +RUN chmod +x /entrypoint.sh /root-entrypoint.sh /entrypoint.d/*.sh || true + RUN apk add --no-cache git nano vim jq php83-pecl-xdebug py3-pip nodejs sudo gpgconf pytest \ pytest-cov zsh alpine-zsh-config shfmt github-cli py3-yaml py3-docker-py docker-cli docker-cli-buildx \ docker-cli-compose shellcheck py3-psutil diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 73f1e89f..117a18ec 100755 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -12,7 +12,8 @@ "capAdd": [ "SYS_ADMIN", // For mounting ramdisks "NET_ADMIN", // For network interface configuration - "NET_RAW" // For raw packet manipulation + "NET_RAW", // For raw packet manipulation + "NET_BIND_SERVICE" // For privileged port binding (e.g., UDP 137) ], "runArgs": [ "--security-opt", diff --git a/.devcontainer/resources/devcontainer-Dockerfile b/.devcontainer/resources/devcontainer-Dockerfile index 1c2bc11c..8acbcfcc 100755 --- a/.devcontainer/resources/devcontainer-Dockerfile +++ b/.devcontainer/resources/devcontainer-Dockerfile @@ -22,6 +22,9 @@ COPY .devcontainer/resources/devcontainer-overlay/ / USER root # Install common tools, create user, and set up sudo +# Ensure entrypoint scripts stay executable in the devcontainer (avoids 126 errors) +RUN chmod +x /entrypoint.sh /root-entrypoint.sh /entrypoint.d/*.sh || true + RUN apk add --no-cache git nano vim jq php83-pecl-xdebug py3-pip nodejs sudo gpgconf pytest \ pytest-cov zsh alpine-zsh-config shfmt github-cli py3-yaml py3-docker-py docker-cli docker-cli-buildx \ docker-cli-compose shellcheck py3-psutil diff --git a/.gitignore b/.gitignore index ba75091c..3aa37f33 100755 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ front/css/cloud_services.css docker-compose.yml.ffsb42 .env.omada.ffsb42 +.venv \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 7fb1a20a..9bd7c413 100755 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,10 +4,12 @@ "python.testing.pytestEnabled": true, "python.testing.unittestEnabled": false, "python.testing.pytestArgs": [ - "test" + "test" ], - // Ensure VS Code uses the devcontainer virtualenv + // NetAlertX devcontainer uses /opt/venv; this ensures pip/pytest are available for discovery. "python.defaultInterpreterPath": "/opt/venv/bin/python", + "python.testing.cwd": "${workspaceFolder}", + "python.testing.autoTestDiscoverOnSaveEnabled": true, // Let the Python extension invoke pytest via the interpreter; avoid hardcoded paths // Removed python.testing.pytestPath and legacy pytest.command overrides @@ -16,8 +18,7 @@ "zsh": { "path": "/bin/zsh" } - } - , + }, // Fallback for older VS Code versions or schema validators that don't accept custom profiles "terminal.integrated.shell.linux": "/usr/bin/zsh" , @@ -29,5 +30,6 @@ "python.formatting.provider": "black", "python.formatting.blackArgs": [ "--line-length=180" - ] + ], + } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index db48dca5..51d58b36 100755 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,7 @@ ENV PATH="/opt/venv/bin:$PATH" # Install build dependencies COPY requirements.txt /tmp/requirements.txt +# hadolint ignore=DL3018 RUN apk add --no-cache \ bash \ shadow \ @@ -41,7 +42,8 @@ RUN apk add --no-cache \ && python -m venv /opt/venv # Upgrade pip/wheel/setuptools and install Python packages -RUN python -m pip install --upgrade pip setuptools wheel && \ +# hadolint ignore=DL3013 +RUN python -m pip install --no-cache-dir --upgrade pip setuptools wheel && \ pip install --prefer-binary --no-cache-dir -r /tmp/requirements.txt && \ chmod -R u-rwx,g-rwx /opt @@ -128,11 +130,11 @@ ENV READ_ONLY_USER=readonly READ_ONLY_GROUP=readonly ENV NETALERTX_USER=netalertx NETALERTX_GROUP=netalertx ENV LANG=C.UTF-8 - +# hadolint ignore=DL3018 RUN apk add --no-cache bash mtr libbsd zip lsblk tzdata curl arp-scan iproute2 iproute2-ss nmap \ nmap-scripts traceroute nbtscan net-tools net-snmp-tools bind-tools awake ca-certificates \ sqlite php83 php83-fpm php83-cgi php83-curl php83-sqlite3 php83-session python3 envsubst \ - nginx supercronic shadow && \ + nginx supercronic shadow su-exec && \ rm -Rf /var/cache/apk/* && \ rm -Rf /etc/nginx && \ addgroup -g ${NETALERTX_GID} ${NETALERTX_GROUP} && \ @@ -164,6 +166,7 @@ COPY --from=builder --chown=${READONLY_UID}:${READONLY_GID} ${VIRTUAL_ENV} ${VIR # This is done after the copy of the venv to ensure the venv is in place # although it may be quicker to do it before the copy, it keeps the image # layers smaller to do it after. +# hadolint ignore=DL3018 RUN for vfile in .VERSION .VERSION_PREV; do \ if [ ! -f "${NETALERTX_APP}/${vfile}" ]; then \ echo "DEVELOPMENT 00000000" > "${NETALERTX_APP}/${vfile}"; \ @@ -171,7 +174,6 @@ RUN for vfile in .VERSION .VERSION_PREV; do \ chown ${READONLY_UID}:${READONLY_GID} "${NETALERTX_APP}/${vfile}"; \ done && \ apk add --no-cache libcap && \ - setcap cap_net_raw+ep /bin/busybox && \ setcap cap_net_raw,cap_net_admin+eip /usr/bin/nmap && \ setcap cap_net_raw,cap_net_admin+eip /usr/bin/arp-scan && \ setcap cap_net_raw,cap_net_admin,cap_net_bind_service+eip /usr/bin/nbtscan && \ @@ -186,7 +188,7 @@ RUN for vfile in .VERSION .VERSION_PREV; do \ date +%s > "${NETALERTX_FRONT}/buildtimestamp.txt" -ENTRYPOINT ["/bin/sh","/entrypoint.sh"] +ENTRYPOINT ["/bin/bash","/entrypoint.sh"] # Final hardened stage to improve security by setting least possible permissions and removing sudo access. # When complete, if the image is compromised, there's not much that can be done with it. @@ -219,8 +221,8 @@ RUN chown -R ${READ_ONLY_USER}:${READ_ONLY_GROUP} ${READ_ONLY_FOLDERS} && \ chmod -R 004 ${READ_ONLY_FOLDERS} && \ find ${READ_ONLY_FOLDERS} -type d -exec chmod 005 {} + && \ install -d -o ${NETALERTX_USER} -g ${NETALERTX_GROUP} -m 0777 ${READ_WRITE_FOLDERS} && \ - chown ${READ_ONLY_USER}:${READ_ONLY_GROUP} /entrypoint.sh /opt /opt/venv && \ - chmod 005 /entrypoint.sh ${SYSTEM_SERVICES}/*.sh ${SYSTEM_SERVICES_SCRIPTS}/* ${ENTRYPOINT_CHECKS}/* /app /opt /opt/venv && \ + chown ${READ_ONLY_USER}:${READ_ONLY_GROUP} /entrypoint.sh /root-entrypoint.sh /opt /opt/venv && \ + chmod 005 /entrypoint.sh /root-entrypoint.sh ${SYSTEM_SERVICES}/*.sh ${SYSTEM_SERVICES_SCRIPTS}/* ${ENTRYPOINT_CHECKS}/* /app /opt /opt/venv && \ # Do not bake first-run artifacts into the image. If present, Docker volume copy-up # will persist restrictive ownership/modes into fresh named volumes, breaking # arbitrary non-root UID/GID runs. @@ -233,11 +235,12 @@ RUN chown -R ${READ_ONLY_USER}:${READ_ONLY_GROUP} ${READ_ONLY_FOLDERS} && \ rm -Rf /var /etc/sudoers.d/* /etc/shadow /etc/gshadow /etc/sudoers \ /lib/apk /lib/firmware /lib/modules-load.d /lib/sysctl.d /mnt /home/ /root \ /srv /media && \ - sed -i "/^\(${READ_ONLY_USER}\|${NETALERTX_USER}\):/!d" /etc/passwd && \ - sed -i "/^\(${READ_ONLY_GROUP}\|${NETALERTX_GROUP}\):/!d" /etc/group && \ + # Preserve root and system identities so hardened entrypoint never needs to patch /etc/passwd or /etc/group at runtime. printf '#!/bin/sh\n"$@"\n' > /usr/bin/sudo && chmod +x /usr/bin/sudo +USER "0" -USER netalertx +# Call root-entrypoint.sh which drops priviliges to run entrypoint.sh. +ENTRYPOINT ["/root-entrypoint.sh"] HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD /services/healthcheck.sh diff --git a/docker-compose.yml b/docker-compose.yml index 4a745500..d7e1e21b 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,22 +1,24 @@ services: netalertx: - #use an environmental variable to set host networking mode if needed - network_mode: ${NETALERTX_NETWORK_MODE:-host} # Use host networking for ARP scanning and other services + network_mode: host # Use host networking for ARP scanning and other services build: context: . # Build context is the current directory dockerfile: Dockerfile # Specify the Dockerfile to use 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}" + + # It is most secure to start with user 20211, but then we lose provisioning capabilities. + # user: "${NETALERTX_UID:-20211}:${NETALERTX_GID:-20211}" cap_drop: # Drop all capabilities for enhanced security - ALL cap_add: # Add only the necessary capabilities - - NET_ADMIN # Required for ARP scanning - - NET_RAW # Required for raw socket operations - - NET_BIND_SERVICE # Required to bind to privileged ports (nbtscan) - + - NET_ADMIN # Required for scanning with arp-scan, nmap, nbtscan, traceroute, and zero-conf + - NET_RAW # Required for raw socket operations with arp-scan, nmap, nbtscan, traceroute and zero-conf + - NET_BIND_SERVICE # Required to bind to privileged ports with nbtscan + - CHOWN # Required for root-entrypoint to chown /data + /tmp before dropping privileges + - SETUID # Required for root-entrypoint to switch to non-root user + - SETGID # Required for root-entrypoint to switch to non-root group volumes: - type: volume # Persistent Docker-managed Named Volume for storage @@ -37,22 +39,23 @@ services: target: /etc/localtime read_only: true - # Use a custom Enterprise-configured nginx config for ldap or other settings - # - /custom-enterprise.conf:/tmp/nginx/active-config/netalertx.conf:ro + # Use a custom Enterprise-configured nginx config for ldap or other settings + # - /custom-enterprise.conf:/tmp/nginx/active-config/netalertx.conf:ro - # Test your plugin on the production container - # - /path/on/host:/app/front/plugins/custom + # Test your plugin on the production container + # - /path/on/host:/app/front/plugins/custom - # Retain logs - comment out tmpfs /tmp/log if you want to retain logs between container restarts - # - /path/on/host/log:/tmp/log + # Retain logs - comment out tmpfs /tmp/log if you want to retain logs between container restarts + # - /path/on/host/log:/tmp/log # tmpfs mounts for writable directories in a read-only container and improve system performance # All writes now live under /tmp/* subdirectories which are created dynamically by entrypoint.d scripts - # uid=20211 and gid=20211 is the netalertx user inside the container - # mode=1700 gives rwx------ permissions to the netalertx user only + # mode=1700 gives rwx------ permissions; ownership is set by /root-entrypoint.sh tmpfs: - - "/tmp:uid=${NETALERTX_UID:-20211},gid=${NETALERTX_GID:-20211},mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime" + - "/tmp:mode=1700,uid=0,gid=0,rw,noexec,nosuid,nodev,async,noatime,nodiratime" environment: + PUID: ${NETALERTX_UID:-20211} # Runtime UID after priming (Synology/no-copy-up safe) + PGID: ${NETALERTX_GID:-20211} # Runtime GID after priming (Synology/no-copy-up safe) LISTEN_ADDR: ${LISTEN_ADDR:-0.0.0.0} # Listen for connections on all interfaces PORT: ${PORT:-20211} # Application port GRAPHQL_PORT: ${GRAPHQL_PORT:-20212} # GraphQL API port diff --git a/docker_build.log b/docker_build.log index 60e5f29d..c35726eb 100755 --- a/docker_build.log +++ b/docker_build.log @@ -1,534 +1,74 @@ #0 building with "default" instance using docker driver #1 [internal] load build definition from Dockerfile -#1 transferring dockerfile: 5.29kB done +#1 DONE 0.0s + +#1 [internal] load build definition from Dockerfile +#1 transferring dockerfile: 11.45kB done #1 DONE 0.1s -#2 [auth] library/alpine:pull token for registry-1.docker.io +#2 [internal] load metadata for docker.io/library/alpine:3.22 #2 DONE 0.0s -#3 [internal] load metadata for docker.io/library/alpine:3.22 -#3 DONE 0.4s +#3 [internal] load .dockerignore +#3 transferring context: +#3 transferring context: 222B done +#3 DONE 0.1s -#4 [internal] load .dockerignore -#4 transferring context: 216B done -#4 DONE 0.1s +#4 [builder 1/4] FROM docker.io/library/alpine:3.22 +#4 DONE 0.0s -#5 [builder 1/15] FROM docker.io/library/alpine:3.22@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1 -#5 CACHED +#5 [internal] load build context +#5 transferring context: 46.63kB 0.1s done +#5 DONE 0.2s -#6 [internal] load build context -#6 transferring context: 36.76kB 0.0s done -#6 DONE 0.1s +#6 [builder 3/4] RUN apk add --no-cache bash shadow python3 python3-dev gcc musl-dev libffi-dev openssl-dev git rust cargo && python -m venv /opt/venv +#6 CACHED -#7 [builder 2/15] RUN apk add --no-cache bash shadow python3 python3-dev gcc musl-dev libffi-dev openssl-dev git && python -m venv /opt/venv -#7 0.443 fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/main/x86_64/APKINDEX.tar.gz -#7 0.688 fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/community/x86_64/APKINDEX.tar.gz -#7 1.107 (1/52) Upgrading libcrypto3 (3.5.1-r0 -> 3.5.3-r0) -#7 1.358 (2/52) Upgrading libssl3 (3.5.1-r0 -> 3.5.3-r0) -#7 1.400 (3/52) Installing ncurses-terminfo-base (6.5_p20250503-r0) -#7 1.413 (4/52) Installing libncursesw (6.5_p20250503-r0) -#7 1.444 (5/52) Installing readline (8.2.13-r1) -#7 1.471 (6/52) Installing bash (5.2.37-r0) -#7 1.570 Executing bash-5.2.37-r0.post-install -#7 1.593 (7/52) Installing libgcc (14.2.0-r6) -#7 1.605 (8/52) Installing jansson (2.14.1-r0) -#7 1.613 (9/52) Installing libstdc++ (14.2.0-r6) -#7 1.705 (10/52) Installing zstd-libs (1.5.7-r0) -#7 1.751 (11/52) Installing binutils (2.44-r3) -#7 2.041 (12/52) Installing libgomp (14.2.0-r6) -#7 2.064 (13/52) Installing libatomic (14.2.0-r6) -#7 2.071 (14/52) Installing gmp (6.3.0-r3) -#7 2.097 (15/52) Installing isl26 (0.26-r1) -#7 2.183 (16/52) Installing mpfr4 (4.2.1_p1-r0) -#7 2.219 (17/52) Installing mpc1 (1.3.1-r1) -#7 2.231 (18/52) Installing gcc (14.2.0-r6) -#7 6.782 (19/52) Installing brotli-libs (1.1.0-r2) -#7 6.828 (20/52) Installing c-ares (1.34.5-r0) -#7 6.846 (21/52) Installing libunistring (1.3-r0) -#7 6.919 (22/52) Installing libidn2 (2.3.7-r0) -#7 6.937 (23/52) Installing nghttp2-libs (1.65.0-r0) -#7 6.950 (24/52) Installing libpsl (0.21.5-r3) -#7 6.960 (25/52) Installing libcurl (8.14.1-r1) -#7 7.015 (26/52) Installing libexpat (2.7.2-r0) -#7 7.029 (27/52) Installing pcre2 (10.43-r1) -#7 7.069 (28/52) Installing git (2.49.1-r0) -#7 7.397 (29/52) Installing git-init-template (2.49.1-r0) -#7 7.404 (30/52) Installing linux-headers (6.14.2-r0) -#7 7.572 (31/52) Installing libffi (3.4.8-r0) -#7 7.578 (32/52) Installing pkgconf (2.4.3-r0) -#7 7.593 (33/52) Installing libffi-dev (3.4.8-r0) -#7 7.607 (34/52) Installing musl-dev (1.2.5-r10) -#7 7.961 (35/52) Installing openssl-dev (3.5.3-r0) -#7 8.021 (36/52) Installing libbz2 (1.0.8-r6) -#7 8.045 (37/52) Installing gdbm (1.24-r0) -#7 8.055 (38/52) Installing xz-libs (5.8.1-r0) -#7 8.071 (39/52) Installing mpdecimal (4.0.1-r0) -#7 8.090 (40/52) Installing libpanelw (6.5_p20250503-r0) -#7 8.098 (41/52) Installing sqlite-libs (3.49.2-r1) -#7 8.185 (42/52) Installing python3 (3.12.11-r0) -#7 8.904 (43/52) Installing python3-pycache-pyc0 (3.12.11-r0) -#7 9.292 (44/52) Installing pyc (3.12.11-r0) -#7 9.292 (45/52) Installing python3-pyc (3.12.11-r0) -#7 9.292 (46/52) Installing python3-dev (3.12.11-r0) -#7 10.71 (47/52) Installing libmd (1.1.0-r0) -#7 10.72 (48/52) Installing libbsd (0.12.2-r0) -#7 10.73 (49/52) Installing skalibs-libs (2.14.4.0-r0) -#7 10.75 (50/52) Installing utmps-libs (0.1.3.1-r0) -#7 10.76 (51/52) Installing linux-pam (1.7.0-r4) -#7 10.82 (52/52) Installing shadow (4.17.3-r0) -#7 10.88 Executing busybox-1.37.0-r18.trigger -#7 10.90 OK: 274 MiB in 66 packages -#7 DONE 14.4s +#7 [runner 6/11] COPY --chown=netalertx:netalertx --chmod=755 server /app/server +#7 CACHED -#8 [builder 3/15] RUN mkdir -p /app -#8 DONE 0.5s +#8 [runner 5/11] COPY --chown=netalertx:netalertx --chmod=755 front /app/front +#8 CACHED -#9 [builder 4/15] COPY api /app/api -#9 DONE 0.3s +#9 [runner 2/11] RUN apk add --no-cache bash mtr libbsd zip lsblk tzdata curl arp-scan iproute2 iproute2-ss nmap nmap-scripts traceroute nbtscan net-tools net-snmp-tools bind-tools awake ca-certificates sqlite php83 php83-fpm php83-cgi php83-curl php83-sqlite3 php83-session python3 envsubst nginx supercronic shadow su-exec && rm -Rf /var/cache/apk/* && rm -Rf /etc/nginx && addgroup -g 20211 netalertx && adduser -u 20211 -D -h /app -G netalertx netalertx && apk del shadow +#9 CACHED -#10 [builder 5/15] COPY back /app/back -#10 DONE 0.3s +#10 [runner 4/11] COPY --chown=netalertx:netalertx --chmod=755 back /app/back +#10 CACHED -#11 [builder 6/15] COPY config /app/config -#11 DONE 0.3s +#11 [builder 2/4] COPY requirements.txt /tmp/requirements.txt +#11 CACHED -#12 [builder 7/15] COPY db /app/db -#12 DONE 0.3s +#12 [runner 7/11] RUN install -d -o netalertx -g netalertx -m 700 /data /data/config /data/db /tmp/api /tmp/log /tmp/log/plugins /tmp/run /tmp/run/tmp /tmp/run/logs /tmp/nginx/active-config && sh -c "find /app -type f \( -name '*.sh' -o -name 'speedtest-cli' \) -exec chmod 750 {} \;" +#12 CACHED -#13 [builder 8/15] COPY dockerfiles /app/dockerfiles -#13 DONE 0.3s +#13 [hardened 1/2] RUN addgroup -g 20212 "readonly" && adduser -u 20212 -G "readonly" -D -h /app "readonly" +#13 CACHED -#14 [builder 9/15] COPY front /app/front -#14 DONE 0.4s +#14 [runner 8/11] COPY --chown=netalertx:netalertx .[V]ERSION /app/.VERSION +#14 CACHED -#15 [builder 10/15] COPY server /app/server -#15 DONE 0.3s +#15 [runner 9/11] COPY --chown=netalertx:netalertx .[V]ERSION /app/.VERSION_PREV +#15 CACHED -#16 [builder 11/15] COPY install/crontab /etc/crontabs/root -#16 DONE 0.3s +#16 [runner 11/11] RUN for vfile in .VERSION .VERSION_PREV; do if [ ! -f "/app/${vfile}" ]; then echo "DEVELOPMENT 00000000" > "/app/${vfile}"; fi; chown 20212:20212 "/app/${vfile}"; done && apk add --no-cache libcap && setcap cap_net_raw,cap_net_admin+eip /usr/bin/nmap && setcap cap_net_raw,cap_net_admin+eip /usr/bin/arp-scan && setcap cap_net_raw,cap_net_admin,cap_net_bind_service+eip /usr/bin/nbtscan && setcap cap_net_raw,cap_net_admin+eip /usr/bin/traceroute && setcap cap_net_raw,cap_net_admin+eip "$(readlink -f /opt/venv/bin/python)" && /bin/sh /build/init-nginx.sh && /bin/sh /build/init-php-fpm.sh && /bin/sh /build/init-cron.sh && /bin/sh /build/init-backend.sh && rm -rf /build && apk del libcap && date +%s > "/app/front/buildtimestamp.txt" +#16 CACHED -#17 [builder 12/15] COPY dockerfiles/start* /start*.sh -#17 DONE 0.3s +#17 [builder 4/4] RUN python -m pip install --no-cache-dir --upgrade pip setuptools wheel && pip install --prefer-binary --no-cache-dir -r /tmp/requirements.txt && chmod -R u-rwx,g-rwx /opt +#17 CACHED -#18 [builder 13/15] RUN pip install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors unifi-sm-api tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag git+https://github.com/foreign-sub/aiofreepybox.git -#18 0.737 Collecting git+https://github.com/foreign-sub/aiofreepybox.git -#18 0.737 Cloning https://github.com/foreign-sub/aiofreepybox.git to /tmp/pip-req-build-waf5_npl -#18 0.738 Running command git clone --filter=blob:none --quiet https://github.com/foreign-sub/aiofreepybox.git /tmp/pip-req-build-waf5_npl -#18 1.617 Resolved https://github.com/foreign-sub/aiofreepybox.git to commit 4ee18ea0f3e76edc839c48eb8df1da59c1baee3d -#18 1.620 Installing build dependencies: started -#18 3.337 Installing build dependencies: finished with status 'done' -#18 3.337 Getting requirements to build wheel: started -#18 3.491 Getting requirements to build wheel: finished with status 'done' -#18 3.492 Preparing metadata (pyproject.toml): started -#18 3.650 Preparing metadata (pyproject.toml): finished with status 'done' -#18 3.724 Collecting openwrt-luci-rpc -#18 3.753 Downloading openwrt_luci_rpc-1.1.17-py2.py3-none-any.whl.metadata (4.9 kB) -#18 3.892 Collecting asusrouter -#18 3.900 Downloading asusrouter-1.21.0-py3-none-any.whl.metadata (33 kB) -#18 3.999 Collecting asyncio -#18 4.007 Downloading asyncio-4.0.0-py3-none-any.whl.metadata (994 bytes) -#18 4.576 Collecting aiohttp -#18 4.582 Downloading aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl.metadata (7.7 kB) -#18 4.729 Collecting graphene -#18 4.735 Downloading graphene-3.4.3-py2.py3-none-any.whl.metadata (6.9 kB) -#18 4.858 Collecting flask -#18 4.866 Downloading flask-3.1.2-py3-none-any.whl.metadata (3.2 kB) -#18 4.963 Collecting flask-cors -#18 4.972 Downloading flask_cors-6.0.1-py3-none-any.whl.metadata (5.3 kB) -#18 5.055 Collecting unifi-sm-api -#18 5.065 Downloading unifi_sm_api-0.2.1-py3-none-any.whl.metadata (2.3 kB) -#18 5.155 Collecting tplink-omada-client -#18 5.166 Downloading tplink_omada_client-1.4.4-py3-none-any.whl.metadata (3.5 kB) -#18 5.262 Collecting wakeonlan -#18 5.274 Downloading wakeonlan-3.1.0-py3-none-any.whl.metadata (4.3 kB) -#18 5.500 Collecting pycryptodome -#18 5.505 Downloading pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl.metadata (3.4 kB) -#18 5.653 Collecting requests -#18 5.660 Downloading requests-2.32.5-py3-none-any.whl.metadata (4.9 kB) -#18 5.764 Collecting paho-mqtt -#18 5.775 Downloading paho_mqtt-2.1.0-py3-none-any.whl.metadata (23 kB) -#18 5.890 Collecting scapy -#18 5.902 Downloading scapy-2.6.1-py3-none-any.whl.metadata (5.6 kB) -#18 6.002 Collecting cron-converter -#18 6.013 Downloading cron_converter-1.2.2-py3-none-any.whl.metadata (8.1 kB) -#18 6.187 Collecting pytz -#18 6.193 Downloading pytz-2025.2-py2.py3-none-any.whl.metadata (22 kB) -#18 6.285 Collecting json2table -#18 6.294 Downloading json2table-1.1.5-py2.py3-none-any.whl.metadata (6.0 kB) -#18 6.381 Collecting dhcp-leases -#18 6.387 Downloading dhcp_leases-0.1.6-py3-none-any.whl.metadata (5.9 kB) -#18 6.461 Collecting pyunifi -#18 6.471 Downloading pyunifi-2.21-py3-none-any.whl.metadata (274 bytes) -#18 6.582 Collecting speedtest-cli -#18 6.596 Downloading speedtest_cli-2.1.3-py2.py3-none-any.whl.metadata (6.8 kB) -#18 6.767 Collecting chardet -#18 6.780 Downloading chardet-5.2.0-py3-none-any.whl.metadata (3.4 kB) -#18 6.878 Collecting python-nmap -#18 6.886 Downloading python-nmap-0.7.1.tar.gz (44 kB) -#18 6.937 Installing build dependencies: started -#18 8.245 Installing build dependencies: finished with status 'done' -#18 8.246 Getting requirements to build wheel: started -#18 8.411 Getting requirements to build wheel: finished with status 'done' -#18 8.412 Preparing metadata (pyproject.toml): started -#18 8.575 Preparing metadata (pyproject.toml): finished with status 'done' -#18 8.648 Collecting dnspython -#18 8.654 Downloading dnspython-2.8.0-py3-none-any.whl.metadata (5.7 kB) -#18 8.741 Collecting librouteros -#18 8.752 Downloading librouteros-3.4.1-py3-none-any.whl.metadata (1.6 kB) -#18 8.869 Collecting yattag -#18 8.881 Downloading yattag-1.16.1.tar.gz (29 kB) -#18 8.925 Installing build dependencies: started -#18 10.23 Installing build dependencies: finished with status 'done' -#18 10.23 Getting requirements to build wheel: started -#18 10.38 Getting requirements to build wheel: finished with status 'done' -#18 10.39 Preparing metadata (pyproject.toml): started -#18 10.55 Preparing metadata (pyproject.toml): finished with status 'done' -#18 10.60 Collecting Click>=6.0 (from openwrt-luci-rpc) -#18 10.60 Downloading click-8.3.0-py3-none-any.whl.metadata (2.6 kB) -#18 10.70 Collecting packaging>=19.1 (from openwrt-luci-rpc) -#18 10.71 Downloading packaging-25.0-py3-none-any.whl.metadata (3.3 kB) -#18 10.87 Collecting urllib3>=1.26.14 (from asusrouter) -#18 10.88 Downloading urllib3-2.5.0-py3-none-any.whl.metadata (6.5 kB) -#18 10.98 Collecting xmltodict>=0.12.0 (from asusrouter) -#18 10.98 Downloading xmltodict-1.0.2-py3-none-any.whl.metadata (15 kB) -#18 11.09 Collecting aiohappyeyeballs>=2.5.0 (from aiohttp) -#18 11.10 Downloading aiohappyeyeballs-2.6.1-py3-none-any.whl.metadata (5.9 kB) -#18 11.19 Collecting aiosignal>=1.4.0 (from aiohttp) -#18 11.20 Downloading aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB) -#18 11.32 Collecting attrs>=17.3.0 (from aiohttp) -#18 11.33 Downloading attrs-25.3.0-py3-none-any.whl.metadata (10 kB) -#18 11.47 Collecting frozenlist>=1.1.1 (from aiohttp) -#18 11.47 Downloading frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl.metadata (18 kB) -#18 11.76 Collecting multidict<7.0,>=4.5 (from aiohttp) -#18 11.77 Downloading multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl.metadata (5.3 kB) -#18 11.87 Collecting propcache>=0.2.0 (from aiohttp) -#18 11.88 Downloading propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl.metadata (12 kB) -#18 12.19 Collecting yarl<2.0,>=1.17.0 (from aiohttp) -#18 12.20 Downloading yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl.metadata (73 kB) -#18 12.31 Collecting graphql-core<3.3,>=3.1 (from graphene) -#18 12.32 Downloading graphql_core-3.2.6-py3-none-any.whl.metadata (11 kB) -#18 12.41 Collecting graphql-relay<3.3,>=3.1 (from graphene) -#18 12.42 Downloading graphql_relay-3.2.0-py3-none-any.whl.metadata (12 kB) -#18 12.50 Collecting python-dateutil<3,>=2.7.0 (from graphene) -#18 12.51 Downloading python_dateutil-2.9.0.post0-py2.py3-none-any.whl.metadata (8.4 kB) -#18 12.61 Collecting typing-extensions<5,>=4.7.1 (from graphene) -#18 12.61 Downloading typing_extensions-4.15.0-py3-none-any.whl.metadata (3.3 kB) -#18 12.71 Collecting blinker>=1.9.0 (from flask) -#18 12.72 Downloading blinker-1.9.0-py3-none-any.whl.metadata (1.6 kB) -#18 12.84 Collecting itsdangerous>=2.2.0 (from flask) -#18 12.85 Downloading itsdangerous-2.2.0-py3-none-any.whl.metadata (1.9 kB) -#18 12.97 Collecting jinja2>=3.1.2 (from flask) -#18 12.98 Downloading jinja2-3.1.6-py3-none-any.whl.metadata (2.9 kB) -#18 13.15 Collecting markupsafe>=2.1.1 (from flask) -#18 13.15 Downloading MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl.metadata (4.0 kB) -#18 13.28 Collecting werkzeug>=3.1.0 (from flask) -#18 13.29 Downloading werkzeug-3.1.3-py3-none-any.whl.metadata (3.7 kB) -#18 13.42 Collecting awesomeversion>=22.9.0 (from tplink-omada-client) -#18 13.42 Downloading awesomeversion-25.8.0-py3-none-any.whl.metadata (9.8 kB) -#18 13.59 Collecting charset_normalizer<4,>=2 (from requests) -#18 13.59 Downloading charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl.metadata (36 kB) -#18 13.77 Collecting idna<4,>=2.5 (from requests) -#18 13.78 Downloading idna-3.10-py3-none-any.whl.metadata (10 kB) -#18 13.94 Collecting certifi>=2017.4.17 (from requests) -#18 13.94 Downloading certifi-2025.8.3-py3-none-any.whl.metadata (2.4 kB) -#18 14.06 Collecting toml<0.11.0,>=0.10.2 (from librouteros) -#18 14.07 Downloading toml-0.10.2-py2.py3-none-any.whl.metadata (7.1 kB) -#18 14.25 Collecting six>=1.5 (from python-dateutil<3,>=2.7.0->graphene) -#18 14.26 Downloading six-1.17.0-py2.py3-none-any.whl.metadata (1.7 kB) -#18 14.33 Downloading openwrt_luci_rpc-1.1.17-py2.py3-none-any.whl (9.5 kB) -#18 14.37 Downloading asusrouter-1.21.0-py3-none-any.whl (131 kB) -#18 14.43 Downloading asyncio-4.0.0-py3-none-any.whl (5.6 kB) -#18 14.47 Downloading aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl (1.7 MB) -#18 14.67 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.7/1.7 MB 8.3 MB/s eta 0:00:00 -#18 14.68 Downloading graphene-3.4.3-py2.py3-none-any.whl (114 kB) -#18 14.73 Downloading flask-3.1.2-py3-none-any.whl (103 kB) -#18 14.78 Downloading flask_cors-6.0.1-py3-none-any.whl (13 kB) -#18 14.84 Downloading unifi_sm_api-0.2.1-py3-none-any.whl (16 kB) -#18 14.88 Downloading tplink_omada_client-1.4.4-py3-none-any.whl (46 kB) -#18 14.93 Downloading wakeonlan-3.1.0-py3-none-any.whl (5.0 kB) -#18 14.99 Downloading pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl (2.3 MB) -#18 15.23 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.3/2.3 MB 8.9 MB/s eta 0:00:00 -#18 15.24 Downloading requests-2.32.5-py3-none-any.whl (64 kB) -#18 15.30 Downloading paho_mqtt-2.1.0-py3-none-any.whl (67 kB) -#18 15.34 Downloading scapy-2.6.1-py3-none-any.whl (2.4 MB) -#18 15.62 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.4/2.4 MB 8.5 MB/s eta 0:00:00 -#18 15.63 Downloading cron_converter-1.2.2-py3-none-any.whl (13 kB) -#18 15.67 Downloading pytz-2025.2-py2.py3-none-any.whl (509 kB) -#18 15.76 Downloading json2table-1.1.5-py2.py3-none-any.whl (8.7 kB) -#18 15.81 Downloading dhcp_leases-0.1.6-py3-none-any.whl (11 kB) -#18 15.86 Downloading pyunifi-2.21-py3-none-any.whl (11 kB) -#18 15.90 Downloading speedtest_cli-2.1.3-py2.py3-none-any.whl (23 kB) -#18 15.95 Downloading chardet-5.2.0-py3-none-any.whl (199 kB) -#18 16.01 Downloading dnspython-2.8.0-py3-none-any.whl (331 kB) -#18 16.10 Downloading librouteros-3.4.1-py3-none-any.whl (16 kB) -#18 16.14 Downloading aiohappyeyeballs-2.6.1-py3-none-any.whl (15 kB) -#18 16.20 Downloading aiosignal-1.4.0-py3-none-any.whl (7.5 kB) -#18 16.24 Downloading attrs-25.3.0-py3-none-any.whl (63 kB) -#18 16.30 Downloading awesomeversion-25.8.0-py3-none-any.whl (15 kB) -#18 16.34 Downloading blinker-1.9.0-py3-none-any.whl (8.5 kB) -#18 16.39 Downloading certifi-2025.8.3-py3-none-any.whl (161 kB) -#18 16.45 Downloading charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl (153 kB) -#18 16.50 Downloading click-8.3.0-py3-none-any.whl (107 kB) -#18 16.55 Downloading frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl (237 kB) -#18 16.62 Downloading graphql_core-3.2.6-py3-none-any.whl (203 kB) -#18 16.69 Downloading graphql_relay-3.2.0-py3-none-any.whl (16 kB) -#18 16.73 Downloading idna-3.10-py3-none-any.whl (70 kB) -#18 16.79 Downloading itsdangerous-2.2.0-py3-none-any.whl (16 kB) -#18 16.84 Downloading jinja2-3.1.6-py3-none-any.whl (134 kB) -#18 16.96 Downloading MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl (23 kB) -#18 17.02 Downloading multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl (251 kB) -#18 17.09 Downloading packaging-25.0-py3-none-any.whl (66 kB) -#18 17.14 Downloading propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl (222 kB) -#18 17.21 Downloading python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB) -#18 17.28 Downloading toml-0.10.2-py2.py3-none-any.whl (16 kB) -#18 17.33 Downloading typing_extensions-4.15.0-py3-none-any.whl (44 kB) -#18 17.39 Downloading urllib3-2.5.0-py3-none-any.whl (129 kB) -#18 17.44 Downloading werkzeug-3.1.3-py3-none-any.whl (224 kB) -#18 17.51 Downloading xmltodict-1.0.2-py3-none-any.whl (13 kB) -#18 17.56 Downloading yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl (374 kB) -#18 17.65 Downloading six-1.17.0-py2.py3-none-any.whl (11 kB) -#18 17.77 Building wheels for collected packages: python-nmap, yattag, aiofreepybox -#18 17.77 Building wheel for python-nmap (pyproject.toml): started -#18 17.95 Building wheel for python-nmap (pyproject.toml): finished with status 'done' -#18 17.96 Created wheel for python-nmap: filename=python_nmap-0.7.1-py2.py3-none-any.whl size=20679 sha256=ecd9b14109651cfaa5bf035f90076b9442985cc254fa5f8a49868fc896e86edb -#18 17.96 Stored in directory: /root/.cache/pip/wheels/06/fc/d4/0957e1d9942e696188208772ea0abf909fe6eb3d9dff6e5a9e -#18 17.96 Building wheel for yattag (pyproject.toml): started -#18 18.14 Building wheel for yattag (pyproject.toml): finished with status 'done' -#18 18.14 Created wheel for yattag: filename=yattag-1.16.1-py3-none-any.whl size=15930 sha256=2135fc2034a3847c81eb6a0d7b85608e8272339fa5c1961f87b02dfe6d74d0ad -#18 18.14 Stored in directory: /root/.cache/pip/wheels/d2/2f/52/049ff4f7c8c9c932b2ece7ec800d7facf2a141ac5ab0ce7e51 -#18 18.15 Building wheel for aiofreepybox (pyproject.toml): started -#18 18.36 Building wheel for aiofreepybox (pyproject.toml): finished with status 'done' -#18 18.36 Created wheel for aiofreepybox: filename=aiofreepybox-6.0.0-py3-none-any.whl size=60051 sha256=dbdee5350b10b6550ede50bc779381b7f39f1e5d5da889f2ee98cb5a869d3425 -#18 18.36 Stored in directory: /tmp/pip-ephem-wheel-cache-93bgc4e2/wheels/3c/d3/ae/fb97a84a29a5fbe8517de58d67e66586505440af35981e0dd3 -#18 18.36 Successfully built python-nmap yattag aiofreepybox -#18 18.45 Installing collected packages: yattag, speedtest-cli, pytz, python-nmap, json2table, dhcp-leases, xmltodict, wakeonlan, urllib3, typing-extensions, toml, six, scapy, pycryptodome, propcache, paho-mqtt, packaging, multidict, markupsafe, itsdangerous, idna, graphql-core, frozenlist, dnspython, Click, charset_normalizer, chardet, certifi, blinker, awesomeversion, attrs, asyncio, aiohappyeyeballs, yarl, werkzeug, requests, python-dateutil, librouteros, jinja2, graphql-relay, aiosignal, unifi-sm-api, pyunifi, openwrt-luci-rpc, graphene, flask, cron-converter, aiohttp, tplink-omada-client, flask-cors, asusrouter, aiofreepybox -#18 24.35 Successfully installed Click-8.3.0 aiofreepybox-6.0.0 aiohappyeyeballs-2.6.1 aiohttp-3.12.15 aiosignal-1.4.0 asusrouter-1.21.0 asyncio-4.0.0 attrs-25.3.0 awesomeversion-25.8.0 blinker-1.9.0 certifi-2025.8.3 chardet-5.2.0 charset_normalizer-3.4.3 cron-converter-1.2.2 dhcp-leases-0.1.6 dnspython-2.8.0 flask-3.1.2 flask-cors-6.0.1 frozenlist-1.7.0 graphene-3.4.3 graphql-core-3.2.6 graphql-relay-3.2.0 idna-3.10 itsdangerous-2.2.0 jinja2-3.1.6 json2table-1.1.5 librouteros-3.4.1 markupsafe-3.0.2 multidict-6.6.4 openwrt-luci-rpc-1.1.17 packaging-25.0 paho-mqtt-2.1.0 propcache-0.3.2 pycryptodome-3.23.0 python-dateutil-2.9.0.post0 python-nmap-0.7.1 pytz-2025.2 pyunifi-2.21 requests-2.32.5 scapy-2.6.1 six-1.17.0 speedtest-cli-2.1.3 toml-0.10.2 tplink-omada-client-1.4.4 typing-extensions-4.15.0 unifi-sm-api-0.2.1 urllib3-2.5.0 wakeonlan-3.1.0 werkzeug-3.1.3 xmltodict-1.0.2 yarl-1.20.1 yattag-1.16.1 -#18 24.47 -#18 24.47 [notice] A new release of pip is available: 25.0.1 -> 25.2 -#18 24.47 [notice] To update, run: pip install --upgrade pip -#18 DONE 25.1s +#18 [runner 10/11] COPY --from=builder --chown=20212:20212 /opt/venv /opt/venv +#18 CACHED -#19 [builder 14/15] RUN bash -c "find /app -type d -exec chmod 750 {} \;" && bash -c "find /app -type f -exec chmod 640 {} \;" && bash -c "find /app -type f \( -name '*.sh' -o -name '*.py' -o -name 'speedtest-cli' \) -exec chmod 750 {} \;" -#19 DONE 11.9s +#19 [runner 3/11] COPY --chown=netalertx:netalertx install/production-filesystem/ / +#19 CACHED -#20 [builder 15/15] COPY install/freebox_certificate.pem /opt/venv/lib/python3.12/site-packages/aiofreepybox/freebox_certificates.pem -#20 DONE 0.4s +#20 [hardened 2/2] RUN chown -R readonly:readonly /app/back /app/front /app/server /services /services/config /entrypoint.d && chmod -R 004 /app/back /app/front /app/server /services /services/config /entrypoint.d && find /app/back /app/front /app/server /services /services/config /entrypoint.d -type d -exec chmod 005 {} + && install -d -o netalertx -g netalertx -m 0777 /data /data/config /data/db /tmp/api /tmp/log /tmp/log/plugins /tmp/run /tmp/run/tmp /tmp/run/logs /tmp/nginx/active-config && chown readonly:readonly /entrypoint.sh /root-entrypoint.sh /opt /opt/venv && chmod 005 /entrypoint.sh /root-entrypoint.sh /services/*.sh /services/scripts/* /entrypoint.d/* /app /opt /opt/venv && rm -f "/data/config/app.conf" "/data/db/app.db" "/data/db/app.db-shm" "/data/db/app.db-wal" || true && apk del apk-tools && rm -Rf /var /etc/sudoers.d/* /etc/shadow /etc/gshadow /etc/sudoers /lib/apk /lib/firmware /lib/modules-load.d /lib/sysctl.d /mnt /home/ /root /srv /media && printf '#!/bin/sh\n"$@"\n' > /usr/bin/sudo && chmod +x /usr/bin/sudo +#20 CACHED -#21 [runner 2/14] COPY --from=builder /opt/venv /opt/venv -#21 DONE 0.8s - -#22 [runner 3/14] COPY --from=builder /usr/sbin/usermod /usr/sbin/groupmod /usr/sbin/ -#22 DONE 0.4s - -#23 [runner 4/14] RUN apk update --no-cache && apk add --no-cache bash libbsd zip lsblk gettext-envsubst sudo mtr tzdata s6-overlay && apk add --no-cache curl arp-scan iproute2 iproute2-ss nmap nmap-scripts traceroute nbtscan avahi avahi-tools openrc dbus net-tools net-snmp-tools bind-tools awake ca-certificates && apk add --no-cache sqlite php83 php83-fpm php83-cgi php83-curl php83-sqlite3 php83-session && apk add --no-cache python3 nginx && ln -s /usr/bin/awake /usr/bin/wakeonlan && bash -c "install -d -m 750 -o nginx -g www-data /app /app" && rm -f /etc/nginx/http.d/default.conf -#23 0.487 fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/main/x86_64/APKINDEX.tar.gz -#23 0.696 fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/community/x86_64/APKINDEX.tar.gz -#23 1.156 v3.22.1-472-ga67443520d6 [https://dl-cdn.alpinelinux.org/alpine/v3.22/main] -#23 1.156 v3.22.1-473-gcd551a4e006 [https://dl-cdn.alpinelinux.org/alpine/v3.22/community] -#23 1.156 OK: 26326 distinct packages available -#23 1.195 fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/main/x86_64/APKINDEX.tar.gz -#23 1.276 fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/community/x86_64/APKINDEX.tar.gz -#23 1.568 (1/38) Installing ncurses-terminfo-base (6.5_p20250503-r0) -#23 1.580 (2/38) Installing libncursesw (6.5_p20250503-r0) -#23 1.629 (3/38) Installing readline (8.2.13-r1) -#23 1.659 (4/38) Installing bash (5.2.37-r0) -#23 1.723 Executing bash-5.2.37-r0.post-install -#23 1.740 (5/38) Installing libintl (0.24.1-r0) -#23 1.749 (6/38) Installing gettext-envsubst (0.24.1-r0) -#23 1.775 (7/38) Installing libmd (1.1.0-r0) -#23 1.782 (8/38) Installing libbsd (0.12.2-r0) -#23 1.807 (9/38) Installing libeconf (0.6.3-r0) -#23 1.812 (10/38) Installing libblkid (2.41-r9) -#23 1.831 (11/38) Installing libmount (2.41-r9) -#23 1.857 (12/38) Installing libsmartcols (2.41-r9) -#23 1.872 (13/38) Installing lsblk (2.41-r9) -#23 1.886 (14/38) Installing libcap2 (2.76-r0) -#23 1.897 (15/38) Installing jansson (2.14.1-r0) -#23 1.910 (16/38) Installing mtr (0.96-r0) -#23 1.948 (17/38) Installing skalibs-libs (2.14.4.0-r0) -#23 1.966 (18/38) Installing execline-libs (2.9.7.0-r0) -#23 1.974 (19/38) Installing execline (2.9.7.0-r0) -#23 1.996 Executing execline-2.9.7.0-r0.post-install -#23 2.004 (20/38) Installing s6-ipcserver (2.13.2.0-r0) -#23 2.010 (21/38) Installing s6-libs (2.13.2.0-r0) -#23 2.016 (22/38) Installing s6 (2.13.2.0-r0) -#23 2.033 Executing s6-2.13.2.0-r0.pre-install -#23 2.159 (23/38) Installing s6-rc-libs (0.5.6.0-r0) -#23 2.164 (24/38) Installing s6-rc (0.5.6.0-r0) -#23 2.175 (25/38) Installing s6-linux-init (1.1.3.0-r0) -#23 2.185 (26/38) Installing s6-portable-utils (2.3.1.0-r0) -#23 2.193 (27/38) Installing s6-linux-utils (2.6.3.0-r0) -#23 2.200 (28/38) Installing s6-dns-libs (2.4.1.0-r0) -#23 2.208 (29/38) Installing s6-dns (2.4.1.0-r0) -#23 2.222 (30/38) Installing bearssl-libs (0.6_git20241009-r0) -#23 2.254 (31/38) Installing s6-networking-libs (2.7.1.0-r0) -#23 2.264 (32/38) Installing s6-networking (2.7.1.0-r0) -#23 2.286 (33/38) Installing s6-overlay-helpers (0.1.2.0-r0) -#23 2.355 (34/38) Installing s6-overlay (3.2.0.3-r0) -#23 2.380 (35/38) Installing sudo (1.9.17_p2-r0) -#23 2.511 (36/38) Installing tzdata (2025b-r0) -#23 2.641 (37/38) Installing unzip (6.0-r15) -#23 2.659 (38/38) Installing zip (3.0-r13) -#23 2.694 Executing busybox-1.37.0-r18.trigger -#23 2.725 OK: 16 MiB in 54 packages -#23 2.778 fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/main/x86_64/APKINDEX.tar.gz -#23 2.918 fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/community/x86_64/APKINDEX.tar.gz -#23 3.218 (1/77) Installing libpcap (1.10.5-r1) -#23 3.234 (2/77) Installing arp-scan (1.10.0-r2) -#23 3.289 (3/77) Installing dbus-libs (1.16.2-r1) -#23 3.307 (4/77) Installing avahi-libs (0.8-r21) -#23 3.315 (5/77) Installing libdaemon (0.14-r6) -#23 3.322 (6/77) Installing libevent (2.1.12-r8) -#23 3.355 (7/77) Installing libexpat (2.7.2-r0) -#23 3.368 (8/77) Installing avahi (0.8-r21) -#23 3.387 Executing avahi-0.8-r21.pre-install -#23 3.465 (9/77) Installing gdbm (1.24-r0) -#23 3.477 (10/77) Installing avahi-tools (0.8-r21) -#23 3.483 (11/77) Installing libbz2 (1.0.8-r6) -#23 3.490 (12/77) Installing libffi (3.4.8-r0) -#23 3.496 (13/77) Installing xz-libs (5.8.1-r0) -#23 3.517 (14/77) Installing libgcc (14.2.0-r6) -#23 3.529 (15/77) Installing libstdc++ (14.2.0-r6) -#23 3.613 (16/77) Installing mpdecimal (4.0.1-r0) -#23 3.628 (17/77) Installing libpanelw (6.5_p20250503-r0) -#23 3.634 (18/77) Installing sqlite-libs (3.49.2-r1) -#23 3.783 (19/77) Installing python3 (3.12.11-r0) -#23 4.494 (20/77) Installing python3-pycache-pyc0 (3.12.11-r0) -#23 4.915 (21/77) Installing pyc (3.12.11-r0) -#23 4.915 (22/77) Installing py3-awake-pyc (1.0-r12) -#23 4.922 (23/77) Installing python3-pyc (3.12.11-r0) -#23 4.922 (24/77) Installing py3-awake (1.0-r12) -#23 4.928 (25/77) Installing awake (1.0-r12) -#23 4.932 (26/77) Installing fstrm (0.6.1-r4) -#23 4.940 (27/77) Installing krb5-conf (1.0-r2) -#23 5.017 (28/77) Installing libcom_err (1.47.2-r2) -#23 5.026 (29/77) Installing keyutils-libs (1.6.3-r4) -#23 5.033 (30/77) Installing libverto (0.3.2-r2) -#23 5.039 (31/77) Installing krb5-libs (1.21.3-r0) -#23 5.115 (32/77) Installing json-c (0.18-r1) -#23 5.123 (33/77) Installing nghttp2-libs (1.65.0-r0) -#23 5.136 (34/77) Installing protobuf-c (1.5.2-r0) -#23 5.142 (35/77) Installing userspace-rcu (0.15.2-r0) -#23 5.161 (36/77) Installing libuv (1.51.0-r0) -#23 5.178 (37/77) Installing libxml2 (2.13.8-r0) -#23 5.232 (38/77) Installing bind-libs (9.20.13-r0) -#23 5.355 (39/77) Installing bind-tools (9.20.13-r0) -#23 5.395 (40/77) Installing ca-certificates (20250619-r0) -#23 5.518 (41/77) Installing brotli-libs (1.1.0-r2) -#23 5.559 (42/77) Installing c-ares (1.34.5-r0) -#23 5.573 (43/77) Installing libunistring (1.3-r0) -#23 5.645 (44/77) Installing libidn2 (2.3.7-r0) -#23 5.664 (45/77) Installing libpsl (0.21.5-r3) -#23 5.676 (46/77) Installing zstd-libs (1.5.7-r0) -#23 5.720 (47/77) Installing libcurl (8.14.1-r1) -#23 5.753 (48/77) Installing curl (8.14.1-r1) -#23 5.778 (49/77) Installing dbus (1.16.2-r1) -#23 5.796 Executing dbus-1.16.2-r1.pre-install -#23 5.869 Executing dbus-1.16.2-r1.post-install -#23 5.887 (50/77) Installing dbus-daemon-launch-helper (1.16.2-r1) -#23 5.896 (51/77) Installing libelf (0.193-r0) -#23 5.908 (52/77) Installing libmnl (1.0.5-r2) -#23 5.915 (53/77) Installing iproute2-minimal (6.15.0-r0) -#23 5.954 (54/77) Installing libxtables (1.8.11-r1) -#23 5.963 (55/77) Installing iproute2-tc (6.15.0-r0) -#23 6.001 (56/77) Installing iproute2-ss (6.15.0-r0) -#23 6.014 (57/77) Installing iproute2 (6.15.0-r0) -#23 6.042 Executing iproute2-6.15.0-r0.post-install -#23 6.047 (58/77) Installing nbtscan (1.7.2-r0) -#23 6.053 (59/77) Installing net-snmp-libs (5.9.4-r1) -#23 6.112 (60/77) Installing net-snmp-agent-libs (5.9.4-r1) -#23 6.179 (61/77) Installing net-snmp-tools (5.9.4-r1) -#23 6.205 (62/77) Installing mii-tool (2.10-r3) -#23 6.211 (63/77) Installing net-tools (2.10-r3) -#23 6.235 (64/77) Installing lua5.4-libs (5.4.7-r0) -#23 6.258 (65/77) Installing libssh2 (1.11.1-r0) -#23 6.279 (66/77) Installing nmap (7.97-r0) -#23 6.524 (67/77) Installing nmap-nselibs (7.97-r0) -#23 6.729 (68/77) Installing nmap-scripts (7.97-r0) -#23 6.842 (69/77) Installing bridge (1.5-r5) -#23 6.904 (70/77) Installing ifupdown-ng (0.12.1-r7) -#23 6.915 (71/77) Installing ifupdown-ng-iproute2 (0.12.1-r7) -#23 6.920 (72/77) Installing openrc-user (0.62.6-r0) -#23 6.924 (73/77) Installing openrc (0.62.6-r0) -#23 7.013 Executing openrc-0.62.6-r0.post-install -#23 7.016 (74/77) Installing avahi-openrc (0.8-r21) -#23 7.021 (75/77) Installing dbus-openrc (1.16.2-r1) -#23 7.026 (76/77) Installing s6-openrc (2.13.2.0-r0) -#23 7.032 (77/77) Installing traceroute (2.1.6-r0) -#23 7.040 Executing busybox-1.37.0-r18.trigger -#23 7.042 Executing ca-certificates-20250619-r0.trigger -#23 7.101 Executing dbus-1.16.2-r1.trigger -#23 7.104 OK: 102 MiB in 131 packages -#23 7.156 fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/main/x86_64/APKINDEX.tar.gz -#23 7.243 fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/community/x86_64/APKINDEX.tar.gz -#23 7.543 (1/12) Installing php83-common (8.3.24-r0) -#23 7.551 (2/12) Installing argon2-libs (20190702-r5) -#23 7.557 (3/12) Installing libedit (20250104.3.1-r1) -#23 7.568 (4/12) Installing pcre2 (10.43-r1) -#23 7.600 (5/12) Installing php83 (8.3.24-r0) -#23 7.777 (6/12) Installing php83-cgi (8.3.24-r0) -#23 7.953 (7/12) Installing php83-curl (8.3.24-r0) -#23 7.968 (8/12) Installing acl-libs (2.3.2-r1) -#23 7.975 (9/12) Installing php83-fpm (8.3.24-r0) -#23 8.193 (10/12) Installing php83-session (8.3.24-r0) -#23 8.204 (11/12) Installing php83-sqlite3 (8.3.24-r0) -#23 8.213 (12/12) Installing sqlite (3.49.2-r1) -#23 8.309 Executing busybox-1.37.0-r18.trigger -#23 8.317 OK: 129 MiB in 143 packages -#23 8.369 fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/main/x86_64/APKINDEX.tar.gz -#23 8.449 fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/community/x86_64/APKINDEX.tar.gz -#23 8.747 (1/2) Installing nginx (1.28.0-r3) -#23 8.766 Executing nginx-1.28.0-r3.pre-install -#23 8.863 Executing nginx-1.28.0-r3.post-install -#23 8.865 (2/2) Installing nginx-openrc (1.28.0-r3) -#23 8.870 Executing busybox-1.37.0-r18.trigger -#23 8.873 OK: 130 MiB in 145 packages -#23 DONE 9.5s - -#24 [runner 5/14] COPY --from=builder --chown=nginx:www-data /app/ /app/ -#24 DONE 0.5s - -#25 [runner 6/14] RUN mkdir -p /app/config /app/db /app/log/plugins -#25 DONE 0.5s - -#26 [runner 7/14] COPY --chmod=600 --chown=root:root install/crontab /etc/crontabs/root -#26 DONE 0.3s - -#27 [runner 8/14] COPY --chmod=755 dockerfiles/healthcheck.sh /usr/local/bin/healthcheck.sh -#27 DONE 0.3s - -#28 [runner 9/14] RUN touch /app/log/app.log && touch /app/log/execution_queue.log && touch /app/log/app_front.log && touch /app/log/app.php_errors.log && touch /app/log/stderr.log && touch /app/log/stdout.log && touch /app/log/db_is_locked.log && touch /app/log/IP_changes.log && touch /app/log/report_output.txt && touch /app/log/report_output.html && touch /app/log/report_output.json && touch /app/api/user_notifications.json -#28 DONE 0.6s - -#29 [runner 10/14] COPY dockerfiles /app/dockerfiles -#29 DONE 0.3s - -#30 [runner 11/14] RUN chmod +x /app/dockerfiles/*.sh -#30 DONE 0.8s - -#31 [runner 12/14] RUN /app/dockerfiles/init-nginx.sh && /app/dockerfiles/init-php-fpm.sh && /app/dockerfiles/init-crond.sh && /app/dockerfiles/init-backend.sh -#31 0.417 Initializing nginx... -#31 0.417 Setting webserver to address (0.0.0.0) and port (20211) -#31 0.418 /app/dockerfiles/init-nginx.sh: line 5: /app/install/netalertx.template.conf: No such file or directory -#31 0.611 nginx initialized. -#31 0.612 Initializing php-fpm... -#31 0.654 php-fpm initialized. -#31 0.655 Initializing crond... -#31 0.689 crond initialized. -#31 0.690 Initializing backend... -#31 12.19 Backend initialized. -#31 DONE 12.3s - -#32 [runner 13/14] RUN rm -rf /app/dockerfiles -#32 DONE 0.6s - -#33 [runner 14/14] RUN date +%s > /app/front/buildtimestamp.txt -#33 DONE 0.6s - -#34 exporting to image -#34 exporting layers -#34 exporting layers 2.4s done -#34 writing image sha256:0afcbc41473de559eff0dd93250595494fe4d8ea620861e9e90d50a248fcefda 0.0s done -#34 naming to docker.io/library/netalertx 0.0s done -#34 DONE 2.5s +#21 exporting to image +#21 exporting layers done +#21 writing image sha256:7aac94268b770de42da767c06b8e9fecaeabf7ce1277cec1c83092484debd4c3 0.0s done +#21 naming to docker.io/library/netalertx-test 0.0s done +#21 DONE 0.1s diff --git a/install/production-filesystem/entrypoint.d/0-storage-permission.sh b/install/production-filesystem/entrypoint.d/0-storage-permission.sh deleted file mode 100755 index 1a7c390d..00000000 --- a/install/production-filesystem/entrypoint.d/0-storage-permission.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/sh - -# 0-storage-permission.sh: Fix permissions if running as root. -# -# This script checks if running as root and fixes ownership and permissions -# for read-write paths to ensure proper operation. - -# --- Color Codes --- -MAGENTA=$(printf '\033[1;35m') -RESET=$(printf '\033[0m') - -# --- Main Logic --- - -# Define paths that need read-write access -READ_WRITE_PATHS=" -${NETALERTX_DATA} -${NETALERTX_DB} -${NETALERTX_API} -${NETALERTX_LOG} -${SYSTEM_SERVICES_RUN} -${NETALERTX_CONFIG} -${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}" - >&2 cat <<'EOF' -══════════════════════════════════════════════════════════════════════════════ -🚨 CRITICAL SECURITY ALERT: NetAlertX is running as ROOT (UID 0)! 🚨 - - This configuration bypasses all built-in security hardening measures. - You've granted a network monitoring application unrestricted access to - your host system. A successful compromise here could jeopardize your - entire infrastructure. - - IMMEDIATE ACTION REQUIRED: Switch to the dedicated 'netalertx' user: - * Remove any 'user:' directive specifying UID 0 from docker-compose.yml or - * switch to the default USER in the image (20211:20211) - - IMPORTANT: This corrective mode automatically adjusts ownership of - /data/db and /data/config directories to the netalertx user, ensuring - proper operation in subsequent runs. - - Remember: Never operate security-critical tools as root unless you're - actively trying to get pwned. - - https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/running-as-root.md -══════════════════════════════════════════════════════════════════════════════ -EOF - >&2 printf "%s" "${RESET}" - - # 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 "${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 ${TARGET_USER}. - sleep infinity & wait $! -fi - - - diff --git a/install/production-filesystem/entrypoint.d/05-data-migration.sh b/install/production-filesystem/entrypoint.d/05-data-migration.sh index aebc4582..fe0c2e73 100755 --- a/install/production-filesystem/entrypoint.d/05-data-migration.sh +++ b/install/production-filesystem/entrypoint.d/05-data-migration.sh @@ -1,5 +1,28 @@ #!/bin/sh -# 01-data-migration.sh - consolidate legacy /app mounts into /data +# 05-data-migration.sh - Consolidate legacy /app mounts into /data +# +# This script migrates NetAlertX data from legacy mount points (/app/config and /app/db) +# to the new consolidated /data directory. It runs during container startup as part of the +# entrypoint process. +# +# Function: +# - Checks for existing migration markers to avoid re-migration. +# - Detects if legacy directories are mounted. +# - Ensures the new /data directory is mounted. +# - Copies configuration and database files from legacy paths to /data. +# - Sets migration markers in legacy directories to prevent future migrations. +# - Provides warnings and errors for various mount states. +# +# Migration Conditions: +# - Both /app/config and /app/db must be mounted (legacy mounts present). +# - /data must be mounted (new consolidated volume). +# - No .migration marker files exist in legacy directories (not already migrated). +# +# Exit Codes: +# - 0: Success, no action needed, or migration completed. +# - 1: Migration failure (e.g., copy errors). +# +# The script exits early with 0 for non-fatal conditions like partial mounts or already migrated. set -eu @@ -37,7 +60,7 @@ EOF >&2 printf "%s" "${RESET}" } -fatal_missing_data_mount() { +possibly_fatal_missing_data_mount() { # Fatal if read-only mode, data loss if not. >&2 printf "%s" "${RED}" >&2 cat </dev/null || echo "0") +# Convert hex to dec (POSIX compliant) +cap_bnd_dec=$(awk -v hex="$cap_bnd_hex" 'BEGIN { h = "0x" hex; if (h ~ /^0x[0-9A-Fa-f]+$/) { printf "%d", h } else { print 0 } }') + +has_cap() { + bit=$1 + # Check if bit is set in cap_bnd_dec + [ $(( (cap_bnd_dec >> bit) & 1 )) -eq 1 ] +} + +# 1. ALERT: Python Requirements (NET_RAW=13, NET_ADMIN=12) +if ! has_cap 13 || ! has_cap 12; then + printf "%s" "${RED}" + cat <<'EOF' +══════════════════════════════════════════════════════════════════════════════ +🚨 ALERT: Python execution capabilities (NET_RAW/NET_ADMIN) are missing. + + The Python binary in this image has file capabilities (+eip) that + require these bits in the container's bounding set. Without them, + the binary will fail to execute (Operation not permitted). + + Restart with: --cap-add=NET_RAW --cap-add=NET_ADMIN +══════════════════════════════════════════════════════════════════════════════ +EOF + printf "%s" "${RESET}" +fi + +# 2. WARNING: NET_BIND_SERVICE (10) +if ! has_cap 10; then + printf "%s" "${YELLOW}" + cat <<'EOF' +══════════════════════════════════════════════════════════════════════════════ +⚠️ WARNING: Reduced functionality (NET_BIND_SERVICE missing). + + Tools like nbtscan cannot bind to privileged ports (UDP 137). + This will reduce discovery accuracy for legacy devices. + + Consider adding: --cap-add=NET_BIND_SERVICE +══════════════════════════════════════════════════════════════════════════════ +EOF + printf "%s" "${RESET}" +fi + +# 3. NOTE: Security Context (CHOWN=0, SETGID=6, SETUID=7) +missing_admin="" +has_cap 0 || missing_admin="${missing_admin} CHOWN" +has_cap 6 || missing_admin="${missing_admin} SETGID" +has_cap 7 || missing_admin="${missing_admin} SETUID" + +if [ -n "${missing_admin}" ]; then + printf "%sSecurity context: Operational capabilities (%s) not granted.%s\n" "${GREY}" "${missing_admin# }" "${RESET}" + if echo "${missing_admin}" | grep -q "CHOWN"; then + printf "%sSee https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/missing-capabilities.md%s\n" "${GREY}" "${RESET}" + fi +fi + +exit 0 diff --git a/install/production-filesystem/entrypoint.d/10-mounts.py b/install/production-filesystem/entrypoint.d/15-mounts.py similarity index 78% rename from install/production-filesystem/entrypoint.d/10-mounts.py rename to install/production-filesystem/entrypoint.d/15-mounts.py index bc35f396..0361cbb5 100755 --- a/install/production-filesystem/entrypoint.d/10-mounts.py +++ b/install/production-filesystem/entrypoint.d/15-mounts.py @@ -326,8 +326,7 @@ def _apply_primary_rules(specs: list[PathSpec], results_map: dict[str, MountChec suppress_primary = False if all_core_subs_healthy and all_core_subs_are_mounts: - if not result.is_mount_point and not result.error and not result.write_error and not result.read_error: - suppress_primary = True + suppress_primary = True if suppress_primary: # All sub-paths are healthy and mounted; suppress the aggregate row. @@ -368,104 +367,110 @@ def main(): r.dataloss_risk or r.error or r.write_error or r.read_error or r.performance_issue for r in results ) - has_rw_errors = any(r.write_error or r.read_error for r in results) + has_rw_errors = any( + (r.write_error or r.read_error) and r.category == "persist" + for r in results + ) + has_primary_dataloss = any( + r.category == "persist" and r.role == "primary" and r.dataloss_risk and r.is_mount_point + for r in results + ) - if has_issues or True: # Always print table for diagnostic purposes - # --- Print Table --- - headers = ["Path", "R", "W", "Mount", "RAMDisk", "Performance", "DataLoss"] + # --- Print Table --- + headers = ["Path", "R", "W", "Mount", "RAMDisk", "Performance", "DataLoss"] - CHECK_SYMBOL = "✅" - CROSS_SYMBOL = "❌" - BLANK_SYMBOL = "➖" + CHECK_SYMBOL = "✅" + CROSS_SYMBOL = "❌" + BLANK_SYMBOL = "➖" - def bool_to_check(is_good): - return CHECK_SYMBOL if is_good else CROSS_SYMBOL + def bool_to_check(is_good): + return CHECK_SYMBOL if is_good else CROSS_SYMBOL - col_widths = [len(h) for h in headers] - for r in results: - col_widths[0] = max(col_widths[0], len(str(r.path))) + col_widths = [len(h) for h in headers] + for r in results: + col_widths[0] = max(col_widths[0], len(str(r.path))) - header_fmt = ( - f" {{:<{col_widths[0]}}} |" - f" {{:^{col_widths[1]}}} |" - f" {{:^{col_widths[2]}}} |" - f" {{:^{col_widths[3]}}} |" - f" {{:^{col_widths[4]}}} |" - f" {{:^{col_widths[5]}}} |" - f" {{:^{col_widths[6]}}} " - ) + header_fmt = ( + f" {{:<{col_widths[0]}}} |" + f" {{:^{col_widths[1]}}} |" + f" {{:^{col_widths[2]}}} |" + f" {{:^{col_widths[3]}}} |" + f" {{:^{col_widths[4]}}} |" + f" {{:^{col_widths[5]}}} |" + f" {{:^{col_widths[6]}}} " + ) - row_fmt = ( - f" {{:<{col_widths[0]}}} |" - f" {{:^{col_widths[1]}}}|" # No space - f" {{:^{col_widths[2]}}}|" # No space - f" {{:^{col_widths[3]}}}|" # No space - f" {{:^{col_widths[4]}}}|" # No space - f" {{:^{col_widths[5]}}}|" # No space - f" {{:^{col_widths[6]}}} " # DataLoss is last, needs space - ) + row_fmt = ( + f" {{:<{col_widths[0]}}} |" + f" {{:^{col_widths[1]}}}|" # No space - intentional + f" {{:^{col_widths[2]}}}|" # No space - intentional + f" {{:^{col_widths[3]}}}|" # No space - intentional + f" {{:^{col_widths[4]}}}|" # No space - intentional + f" {{:^{col_widths[5]}}}|" # No space - intentional + f" {{:^{col_widths[6]}}} " # DataLoss is last, needs space + ) - separator = "".join([ - "-" * (col_widths[0] + 2), - "+", - "-" * (col_widths[1] + 2), - "+", - "-" * (col_widths[2] + 2), - "+", - "-" * (col_widths[3] + 2), - "+", - "-" * (col_widths[4] + 2), - "+", - "-" * (col_widths[5] + 2), - "+", - "-" * (col_widths[6] + 2) - ]) + separator = "".join([ + "-" * (col_widths[0] + 2), + "+", + "-" * (col_widths[1] + 2), + "+", + "-" * (col_widths[2] + 2), + "+", + "-" * (col_widths[3] + 2), + "+", + "-" * (col_widths[4] + 2), + "+", + "-" * (col_widths[5] + 2), + "+", + "-" * (col_widths[6] + 2) + ]) - print(header_fmt.format(*headers), file=sys.stderr) - print(separator, file=sys.stderr) - for r in results: - # Symbol Logic - read_symbol = bool_to_check(r.is_readable) - write_symbol = bool_to_check(r.is_writeable) + print(header_fmt.format(*headers), file=sys.stderr) + print(separator, file=sys.stderr) + for r in results: + # Symbol Logic + read_symbol = bool_to_check(r.is_readable) + write_symbol = bool_to_check(r.is_writeable) - mount_symbol = CHECK_SYMBOL if r.is_mounted else CROSS_SYMBOL + mount_symbol = CHECK_SYMBOL if r.is_mounted else CROSS_SYMBOL - if r.category == "persist": - if r.underlying_fs_is_ramdisk or r.is_ramdisk: - ramdisk_symbol = CROSS_SYMBOL - else: - ramdisk_symbol = BLANK_SYMBOL - perf_symbol = BLANK_SYMBOL - elif r.category == "ramdisk": - ramdisk_symbol = CHECK_SYMBOL if r.is_ramdisk else CROSS_SYMBOL - perf_symbol = bool_to_check(not r.performance_issue) + if r.category == "persist": + if r.underlying_fs_is_ramdisk or r.is_ramdisk: + ramdisk_symbol = CROSS_SYMBOL else: ramdisk_symbol = BLANK_SYMBOL - perf_symbol = bool_to_check(not r.performance_issue) + perf_symbol = BLANK_SYMBOL + elif r.category == "ramdisk": + ramdisk_symbol = CHECK_SYMBOL if r.is_ramdisk else CROSS_SYMBOL + perf_symbol = bool_to_check(not r.performance_issue) + else: + ramdisk_symbol = BLANK_SYMBOL + perf_symbol = bool_to_check(not r.performance_issue) - dataloss_symbol = bool_to_check(not r.dataloss_risk) + dataloss_symbol = bool_to_check(not r.dataloss_risk) - print( - row_fmt.format( - r.path, - read_symbol, - write_symbol, - mount_symbol, - ramdisk_symbol, - perf_symbol, - dataloss_symbol, - ), - file=sys.stderr - ) + print( + row_fmt.format( + r.path, + read_symbol, + write_symbol, + mount_symbol, + ramdisk_symbol, + perf_symbol, + dataloss_symbol, + ), + file=sys.stderr + ) - # --- Print Warning --- - if has_issues: - print("\n", file=sys.stderr) - print_warning_message(results) + # --- Print Warning --- + if has_issues: + print("\n", file=sys.stderr) + print_warning_message(results) - # Exit with error only if there are read/write permission issues - if has_rw_errors and os.environ.get("NETALERTX_DEBUG") != "1": - sys.exit(1) + # Exit with error only if there are read/write permission issues + if (has_rw_errors or has_primary_dataloss) and os.environ.get("NETALERTX_DEBUG") != "1": + sys.exit(1) if __name__ == "__main__": diff --git a/install/production-filesystem/entrypoint.d/15-first-run-config.sh b/install/production-filesystem/entrypoint.d/20-first-run-config.sh similarity index 100% rename from install/production-filesystem/entrypoint.d/15-first-run-config.sh rename to install/production-filesystem/entrypoint.d/20-first-run-config.sh diff --git a/install/production-filesystem/entrypoint.d/20-first-run-db.sh b/install/production-filesystem/entrypoint.d/25-first-run-db.sh similarity index 99% rename from install/production-filesystem/entrypoint.d/20-first-run-db.sh rename to install/production-filesystem/entrypoint.d/25-first-run-db.sh index de4d7b78..7767964c 100755 --- a/install/production-filesystem/entrypoint.d/20-first-run-db.sh +++ b/install/production-filesystem/entrypoint.d/25-first-run-db.sh @@ -4,7 +4,6 @@ set -eu -YELLOW=$(printf '\033[1;33m') CYAN=$(printf '\033[1;36m') RED=$(printf '\033[1;31m') RESET=$(printf '\033[0m') diff --git a/install/production-filesystem/entrypoint.d/25-mandatory-folders.sh b/install/production-filesystem/entrypoint.d/25-mandatory-folders.sh deleted file mode 100755 index 87dd6f2b..00000000 --- a/install/production-filesystem/entrypoint.d/25-mandatory-folders.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/bin/sh -# Initialize required directories and log files -# These must exist before services start to avoid permission/write errors - -check_mandatory_folders() { - # Base volatile directories live on /tmp mounts and must always exist - if [ ! -d "${NETALERTX_LOG}" ]; then - echo " * Creating NetAlertX log directory." - if ! mkdir -p "${NETALERTX_LOG}"; then - echo "Error: Failed to create log directory: ${NETALERTX_LOG}" - return 1 - fi - chmod 700 "${NETALERTX_LOG}" 2>/dev/null || true - fi - - if [ ! -d "${NETALERTX_API}" ]; then - echo " * Creating NetAlertX API cache." - if ! mkdir -p "${NETALERTX_API}"; then - echo "Error: Failed to create API cache directory: ${NETALERTX_API}" - return 1 - fi - chmod 700 "${NETALERTX_API}" 2>/dev/null || true - fi - - if [ ! -d "${SYSTEM_SERVICES_RUN}" ]; then - echo " * Creating System services runtime directory." - if ! mkdir -p "${SYSTEM_SERVICES_RUN}"; then - echo "Error: Failed to create System services runtime directory: ${SYSTEM_SERVICES_RUN}" - return 1 - fi - chmod 700 "${SYSTEM_SERVICES_RUN}" 2>/dev/null || true - fi - - if [ ! -d "${SYSTEM_SERVICES_ACTIVE_CONFIG}" ]; then - echo " * Creating nginx active configuration directory." - if ! mkdir -p "${SYSTEM_SERVICES_ACTIVE_CONFIG}"; then - echo "Error: Failed to create nginx active configuration directory: ${SYSTEM_SERVICES_ACTIVE_CONFIG}" - return 1 - fi - chmod 700 "${SYSTEM_SERVICES_ACTIVE_CONFIG}" 2>/dev/null || true - fi - - # Check and create plugins log directory - if [ ! -d "${NETALERTX_PLUGINS_LOG}" ]; then - echo " * Creating Plugins log." - if ! mkdir -p "${NETALERTX_PLUGINS_LOG}"; then - echo "Error: Failed to create plugins log directory: ${NETALERTX_PLUGINS_LOG}" - return 1 - fi - chmod 700 "${NETALERTX_PLUGINS_LOG}" 2>/dev/null || true - fi - - # Check and create system services run log directory - if [ ! -d "${SYSTEM_SERVICES_RUN_LOG}" ]; then - echo " * Creating System services run log." - if ! mkdir -p "${SYSTEM_SERVICES_RUN_LOG}"; then - echo "Error: Failed to create system services run log directory: ${SYSTEM_SERVICES_RUN_LOG}" - return 1 - fi - chmod 700 "${SYSTEM_SERVICES_RUN_LOG}" 2>/dev/null || true - fi - - # Check and create system services run tmp directory - if [ ! -d "${SYSTEM_SERVICES_RUN_TMP}" ]; then - echo " * Creating System services run tmp." - if ! mkdir -p "${SYSTEM_SERVICES_RUN_TMP}"; then - echo "Error: Failed to create system services run tmp directory: ${SYSTEM_SERVICES_RUN_TMP}" - return 1 - fi - chmod 700 "${SYSTEM_SERVICES_RUN_TMP}" 2>/dev/null || true - fi - - # Check and create DB locked log file - if [ ! -f "${LOG_DB_IS_LOCKED}" ]; then - echo " * Creating DB locked log." - if ! touch "${LOG_DB_IS_LOCKED}"; then - echo "Error: Failed to create DB locked log file: ${LOG_DB_IS_LOCKED}" - return 1 - fi - fi - - # Check and create execution queue log file - if [ ! -f "${LOG_EXECUTION_QUEUE}" ]; then - echo " * Creating Execution queue log." - if ! touch "${LOG_EXECUTION_QUEUE}"; then - echo "Error: Failed to create execution queue log file: ${LOG_EXECUTION_QUEUE}" - return 1 - fi - fi -} - -# Run the function -check_mandatory_folders \ No newline at end of file diff --git a/install/production-filesystem/entrypoint.d/30-mandatory-folders.sh b/install/production-filesystem/entrypoint.d/30-mandatory-folders.sh new file mode 100755 index 00000000..cc5204ca --- /dev/null +++ b/install/production-filesystem/entrypoint.d/30-mandatory-folders.sh @@ -0,0 +1,103 @@ +#!/bin/sh +# Initialize required directories and log files +# These must exist before services start to avoid permission/write errors +# This script is intended to enhance observability of system startup issues. + + + +is_tmp_path() { + case "$1" in + /tmp/*|/tmp) return 0 ;; + *) return 1 ;; + esac +} + +warn_tmp_skip() { + echo "Warning: Unable to create $2 at $1 (tmpfs not writable with current capabilities)." +} + +ensure_dir() { + # When creating as the user running the services, we ensure correct ownership and access + path="$1" + label="$2" + if ! mkdir -p "${path}" 2>/dev/null; then + if is_tmp_path "${path}"; then + warn_tmp_skip "${path}" "${label}" + return 0 + fi + echo "Error: Failed to create ${label}: ${path}" + return 1 + fi + chmod 700 "${path}" 2>/dev/null || true +} + +ensure_file() { + path="$1" + label="$2" + # When we touch as the user running the services, we ensure correct ownership + if ! touch "${path}" 2>/dev/null; then + if is_tmp_path "${path}"; then + warn_tmp_skip "${path}" "${label}" + return 0 + fi + echo "Error: Failed to create ${label}: ${path}" + return 1 + fi +} + +check_mandatory_folders() { + # Base volatile directories live on /tmp mounts and must always exist + if [ ! -d "${NETALERTX_LOG}" ]; then + echo " * Creating NetAlertX log directory." + ensure_dir "${NETALERTX_LOG}" "log directory" || return 1 + fi + + if [ ! -d "${NETALERTX_API}" ]; then + echo " * Creating NetAlertX API cache." + ensure_dir "${NETALERTX_API}" "API cache directory" || return 1 + fi + + if [ ! -d "${SYSTEM_SERVICES_RUN}" ]; then + echo " * Creating System services runtime directory." + ensure_dir "${SYSTEM_SERVICES_RUN}" "System services runtime directory" || return 1 + fi + + if [ ! -d "${SYSTEM_SERVICES_ACTIVE_CONFIG}" ]; then + echo " * Creating nginx active configuration directory." + ensure_dir "${SYSTEM_SERVICES_ACTIVE_CONFIG}" "nginx active configuration directory" || return 1 + fi + + # Check and create plugins log directory + if [ ! -d "${NETALERTX_PLUGINS_LOG}" ]; then + echo " * Creating Plugins log." + ensure_dir "${NETALERTX_PLUGINS_LOG}" "plugins log directory" || return 1 + fi + + # Check and create system services run log directory + if [ ! -d "${SYSTEM_SERVICES_RUN_LOG}" ]; then + echo " * Creating System services run log." + ensure_dir "${SYSTEM_SERVICES_RUN_LOG}" "system services run log directory" || return 1 + fi + + # Check and create system services run tmp directory + if [ ! -d "${SYSTEM_SERVICES_RUN_TMP}" ]; then + echo " * Creating System services run tmp." + ensure_dir "${SYSTEM_SERVICES_RUN_TMP}" "system services run tmp directory" || return 1 + fi + + # Check and create DB locked log file + if [ ! -f "${LOG_DB_IS_LOCKED}" ]; then + echo " * Creating DB locked log." + ensure_file "${LOG_DB_IS_LOCKED}" "DB locked log file" || return 1 + fi + + # Check and create execution queue log file + if [ ! -f "${LOG_EXECUTION_QUEUE}" ]; then + echo " * Creating Execution queue log." + ensure_file "${LOG_EXECUTION_QUEUE}" "execution queue log file" || return 1 + fi +} + +# Create the folders and files. +# Create a log message for observability if any fail. +check_mandatory_folders \ No newline at end of file diff --git a/install/production-filesystem/entrypoint.d/30-apply-conf-override.sh b/install/production-filesystem/entrypoint.d/35-apply-conf-override.sh similarity index 90% rename from install/production-filesystem/entrypoint.d/30-apply-conf-override.sh rename to install/production-filesystem/entrypoint.d/35-apply-conf-override.sh index cf1507f2..ad584305 100644 --- a/install/production-filesystem/entrypoint.d/30-apply-conf-override.sh +++ b/install/production-filesystem/entrypoint.d/35-apply-conf-override.sh @@ -4,8 +4,8 @@ OVERRIDE_FILE="${NETALERTX_CONFIG}/app_conf_override.json" # Ensure config directory exists -mkdir -p "$(dirname "$NETALERTX_CONFIG")" || { - >&2 echo "ERROR: Failed to create config directory $(dirname "$NETALERTX_CONFIG")" +mkdir -p "$NETALERTX_CONFIG" || { + >&2 echo "ERROR: Failed to create config directory $NETALERTX_CONFIG" exit 1 } diff --git a/install/production-filesystem/entrypoint.d/35-writable-config.sh b/install/production-filesystem/entrypoint.d/40-writable-config.sh similarity index 97% rename from install/production-filesystem/entrypoint.d/35-writable-config.sh rename to install/production-filesystem/entrypoint.d/40-writable-config.sh index a9edf8f5..df7a2fbb 100755 --- a/install/production-filesystem/entrypoint.d/35-writable-config.sh +++ b/install/production-filesystem/entrypoint.d/40-writable-config.sh @@ -1,6 +1,6 @@ #!/bin/sh -# 30-writable-config.sh: Verify read/write permissions for config and database files. +# 40-writable-config.sh: Verify read/write permissions for config and database files. # # This script ensures that the application can read from and write to the # critical configuration and database files after startup. @@ -72,7 +72,7 @@ EOF >&2 printf "%s" "${YELLOW}" >&2 cat <&2 printf "%s" "${RESET}" - exit 1 + exit 0 fi TMP_FILE="${CONF_ACTIVE_DIR}/.netalertx-write-test" @@ -52,7 +53,7 @@ if ! ( : >"${TMP_FILE}" ) 2>/dev/null; then ══════════════════════════════════════════════════════════════════════════════ EOF >&2 printf "%s" "${RESET}" - exit 1 + exit 0 # Nginx can continue using default config on port 20211 fi rm -f "${TMP_FILE}" diff --git a/install/production-filesystem/entrypoint.d/60-expected-user-id-match.sh b/install/production-filesystem/entrypoint.d/60-expected-user-id-match.sh new file mode 100755 index 00000000..b6553210 --- /dev/null +++ b/install/production-filesystem/entrypoint.d/60-expected-user-id-match.sh @@ -0,0 +1,48 @@ +#!/bin/sh +# expected-user-id-match.sh - ensure the container is running as the intended runtime UID/GID. + +EXPECTED_USER="${NETALERTX_USER:-netalertx}" +CURRENT_UID="$(id -u)" +CURRENT_GID="$(id -g)" + +# If PUID/PGID explicitly set, require that we are running as them. +if [ -n "${PUID:-}" ] || [ -n "${PGID:-}" ]; then + TARGET_UID="${PUID:-${CURRENT_UID}}" + TARGET_GID="${PGID:-${CURRENT_GID}}" + + if [ "${CURRENT_UID}" -ne "${TARGET_UID}" ] || [ "${CURRENT_GID}" -ne "${TARGET_GID}" ]; then + if [ "${NETALERTX_PRIVDROP_FAILED:-0}" -ne 0 ]; then + >&2 printf 'Note: PUID/PGID=%s:%s requested but privilege drop failed; continuing as UID %s GID %s. See docs/docker-troubleshooting/missing-capabilities.md\n' \ + "${TARGET_UID}" "${TARGET_GID}" "${CURRENT_UID}" "${CURRENT_GID}" + exit 0 + fi + if [ "${CURRENT_UID}" -ne 0 ]; then + >&2 printf 'Note: PUID/PGID=%s:%s requested but container is running as fixed UID %s GID %s; PUID/PGID will not be applied.\n' \ + "${TARGET_UID}" "${TARGET_GID}" "${CURRENT_UID}" "${CURRENT_GID}" + exit 0 + fi + + >&2 printf 'FATAL: NetAlertX running as UID %s GID %s, expected PUID/PGID %s:%s\n' \ + "${CURRENT_UID}" "${CURRENT_GID}" "${TARGET_UID}" "${TARGET_GID}" + exit 1 + fi + exit 0 +fi + +EXPECTED_UID="$(getent passwd "${EXPECTED_USER}" 2>/dev/null | cut -d: -f3)" +EXPECTED_GID="$(getent passwd "${EXPECTED_USER}" 2>/dev/null | cut -d: -f4)" + +# Fallback to known defaults when lookups fail +if [ -z "${EXPECTED_UID}" ]; then + EXPECTED_UID="${CURRENT_UID}" +fi +if [ -z "${EXPECTED_GID}" ]; then + EXPECTED_GID="${CURRENT_GID}" +fi + +if [ "${CURRENT_UID}" -eq "${EXPECTED_UID}" ] && [ "${CURRENT_GID}" -eq "${EXPECTED_GID}" ]; then + exit 0 +fi +>&2 printf '\nNetAlertX note: current UID %s GID %s, expected UID %s GID %s\n' \ + "${CURRENT_UID}" "${CURRENT_GID}" "${EXPECTED_UID}" "${EXPECTED_GID}" +exit 0 diff --git a/install/production-filesystem/entrypoint.d/60-user-netalertx.sh b/install/production-filesystem/entrypoint.d/60-user-netalertx.sh deleted file mode 100755 index 535225f6..00000000 --- a/install/production-filesystem/entrypoint.d/60-user-netalertx.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh -# check-user-netalertx.sh - ensure the container is running as the hardened service user. - -EXPECTED_USER="${NETALERTX_USER:-netalertx}" -EXPECTED_UID="$(getent passwd "${EXPECTED_USER}" 2>/dev/null | cut -d: -f3)" -EXPECTED_GID="$(getent passwd "${EXPECTED_USER}" 2>/dev/null | cut -d: -f4)" -CURRENT_UID="$(id -u)" -CURRENT_GID="$(id -g)" - -# Fallback to known defaults when lookups fail -if [ -z "${EXPECTED_UID}" ]; then - EXPECTED_UID="${CURRENT_UID}" -fi -if [ -z "${EXPECTED_GID}" ]; then - EXPECTED_GID="${CURRENT_GID}" -fi - -if [ "${CURRENT_UID}" -eq "${EXPECTED_UID}" ] && [ "${CURRENT_GID}" -eq "${EXPECTED_GID}" ]; then - exit 0 -fi ->&2 printf '\nNetAlertX note: current UID %s GID %s, expected UID %s GID %s\n' \ - "${CURRENT_UID}" "${CURRENT_GID}" "${EXPECTED_UID}" "${EXPECTED_GID}" -exit 0 diff --git a/install/production-filesystem/entrypoint.d/85-layer-2-capabilities.sh b/install/production-filesystem/entrypoint.d/85-layer-2-capabilities.sh deleted file mode 100755 index 9c7caee8..00000000 --- a/install/production-filesystem/entrypoint.d/85-layer-2-capabilities.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh -# layer-2-network.sh - Uses a real nmap command to detect missing container -# privileges and warns the user. It is silent on success. - -# Run a fast nmap command that requires raw sockets, capturing only stderr. -ERROR_OUTPUT=$(nmap --privileged -sS -p 20211 127.0.0.1 2>&1) -EXIT_CODE=$? - -# Flag common capability errors regardless of exact exit code. -if [ "$EXIT_CODE" -ne 0 ] && \ - echo "$ERROR_OUTPUT" | grep -q -e "Operation not permitted" -e "requires root privileges" -then - YELLOW=$(printf '\033[1;33m') - RESET=$(printf '\033[0m') - >&2 printf "%s" "${YELLOW}" - >&2 cat <<'EOF' -══════════════════════════════════════════════════════════════════════════════ -⚠️ ATTENTION: Raw network capabilities are missing. - - Tools that rely on NET_RAW/NET_ADMIN/NET_BIND_SERVICE (e.g. nmap -sS, - arp-scan, nbtscan) will not function. Restart the container with: - - --cap-add=NET_RAW --cap-add=NET_ADMIN --cap-add=NET_BIND_SERVICE - - Without those caps, NetAlertX cannot inspect your network. Fix it before - trusting any results. - - https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/missing-capabilities.md -══════════════════════════════════════════════════════════════════════════════ -EOF - >&2 printf "%s" "${RESET}" -fi -exit 0 # Always exit success even after warnings \ No newline at end of file diff --git a/install/production-filesystem/entrypoint.d/90-excessive-capabilities.sh b/install/production-filesystem/entrypoint.d/90-excessive-capabilities.sh index 924da04e..4aae3c3f 100755 --- a/install/production-filesystem/entrypoint.d/90-excessive-capabilities.sh +++ b/install/production-filesystem/entrypoint.d/90-excessive-capabilities.sh @@ -1,13 +1,13 @@ -#!/bin/bash -# Bash used in this check for simplicty of math operations. +#!/bin/sh +# POSIX-compliant shell script for capability checking. # excessive-capabilities.sh checks that no more than the necessary # NET_ADMIN NET_BIND_SERVICE and NET_RAW capabilities are present. -# if we are running in devcontainer then we should exit imemditely without checking +# if we are running in devcontainer then we should exit immediately without checking # The devcontainer is set up to have additional permissions which are not granted # in production so this check would always fail there. -if [ "${NETALERTX_DEBUG}" == "1" ]; then +if [ "${NETALERTX_DEBUG}" = "1" ]; then exit 0 fi @@ -18,8 +18,8 @@ if [ -z "$BND_HEX" ]; then exit 0 fi -# Convert hex to decimal -BND_DEC=$(( 16#$BND_HEX )) || exit 0 +#POSIX compliant base16 on permissions +BND_DEC=$(awk 'BEGIN { h = "0x'"$BND_HEX"'"; if (h ~ /^0x[0-9A-Fa-f]+$/) { printf "%d", h; exit 0 } else { exit 1 } }') || exit 0 # Allowed capabilities: NET_BIND_SERVICE (10), NET_ADMIN (12), NET_RAW (13) ALLOWED_DEC=$(( ( 1 << 10 ) | ( 1 << 12 ) | ( 1 << 13 ) )) diff --git a/install/production-filesystem/entrypoint.sh b/install/production-filesystem/entrypoint.sh index 0df82d6f..d5d9ee22 100755 --- a/install/production-filesystem/entrypoint.sh +++ b/install/production-filesystem/entrypoint.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash ################################################################################ # NetAlertX Container Entrypoint @@ -46,6 +46,17 @@ if [ "$#" -gt 0 ]; then esac fi +# If invoked directly (bypassing root-entrypoint), re-enter through it once for priming +# and privilege drop. Guard with ENTRYPOINT_PRIMED to avoid loops when root-entrypoint +# hands control back to this script. +if [ "${ENTRYPOINT_PRIMED:-0}" != "1" ] && [ "$(id -u)" -eq 0 ] && [ -x "/root-entrypoint.sh" ]; then + >&2 cat <<'EOF' +NetAlertX is running as ROOT (UID 0). Prefer setting PUID/PGID to 20211 for better isolation. +EOF + export ENTRYPOINT_PRIMED=1 + exec /root-entrypoint.sh "$@" +fi + # Banner display RED='\033[1;31m' GREY='\033[90m' @@ -92,12 +103,9 @@ https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/trou EOF >&2 printf "%s" "${RESET}" + FAILED_STATUS="1" if [ "${NETALERTX_DEBUG:-0}" -eq 1 ]; then - - FAILED_STATUS="1" echo "NETALERTX_DEBUG=1, continuing despite critical failure in ${script_name}." - else - exit 1 fi elif [ ${NETALERTX_DOCKER_ERROR_CHECK} -ne 0 ]; then # fail but continue checks so user can see all issues @@ -264,9 +272,6 @@ trap on_signal INT TERM -################################################################################ -# Service Startup Section -################################################################################ # Start services based on environment configuration # Only start crond scheduler on Alpine (non-Debian) environments diff --git a/install/production-filesystem/root-entrypoint.sh b/install/production-filesystem/root-entrypoint.sh new file mode 100644 index 00000000..3896f0a0 --- /dev/null +++ b/install/production-filesystem/root-entrypoint.sh @@ -0,0 +1,130 @@ +#!/bin/bash +# NetAlertX Root-Priming Entrypoint — best-effort permission priming 🔧 +# +# Purpose: +# - Provide a runtime, best-effort remedy for host volume ownership/mode issues +# (common on appliances like Synology where Docker volume copy‑up is limited). +# - Ensure writable paths exist, attempt to `chown`/`chmod` 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 +# 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 +# operator's responsibility to ensure matching ownership on the host. +# - 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. + +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') + + +_validate_id() { + value="$1" + name="$2" + + if ! printf '%s' "${value}" | grep -qxE '[0-9]+'; then + >&2 printf "%s" "${RED}" + >&2 cat <&2 printf "%s" "${RESET}" + exit 1 + fi +} + +_validate_id "${PUID}" "PUID" +_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 + + # 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 } }') + + has_setgid=0 + has_setuid=0 + has_net_caps=0 + + if [ $((cap_dec & (1 << 6))) -ne 0 ]; then + has_setgid=1 + fi + if [ $((cap_dec & (1 << 7))) -ne 0 ]; 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 + has_net_caps=1 + fi + + if [ "${has_net_caps}" -eq 1 ] && { [ "${has_setgid}" -eq 0 ] || [ "${has_setuid}" -eq 0 ]; }; then + >&2 echo "Note: CAP_SETUID/CAP_SETGID unavailable alongside NET_* caps; continuing as current user." + fi +} + +_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}" + 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" + exec /entrypoint.sh "$@" +fi + +_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 + + 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 + chown -R "${PUID}:${PGID}" "${path}" 2>/dev/null || true + chmod -R u+rwX "${path}" 2>/dev/null || true + 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}" + >&2 echo "Note: su-exec failed (exit ${rc}); continuing as current user without privilege drop." + exec /entrypoint.sh "$@" +fi \ No newline at end of file diff --git a/install/production-filesystem/services/start-nginx.sh b/install/production-filesystem/services/start-nginx.sh index d9046f76..881f8e6b 100755 --- a/install/production-filesystem/services/start-nginx.sh +++ b/install/production-filesystem/services/start-nginx.sh @@ -54,11 +54,11 @@ chmod -R 777 "/tmp/nginx" 2>/dev/null || true # Execute nginx with overrides # echo the full nginx command then run it -echo "Starting /usr/sbin/nginx -p \"${RUN_DIR}/\" -c \"${SYSTEM_SERVICES_ACTIVE_CONFIG_FILE}\" -g \"error_log /dev/stderr; error_log ${NETALERTX_LOG}/nginx-error.log; daemon off;\" &" +echo "Starting /usr/sbin/nginx -p \"${RUN_DIR}/\" -c \"${SYSTEM_SERVICES_ACTIVE_CONFIG_FILE}\" -g \"error_log stderr; error_log ${NETALERTX_LOG}/nginx-error.log; daemon off;\" &" /usr/sbin/nginx \ -p "${RUN_DIR}/" \ -c "${SYSTEM_SERVICES_ACTIVE_CONFIG_FILE}" \ - -g "error_log /dev/stderr; error_log ${NETALERTX_LOG}/nginx-error.log; daemon off;" & + -g "error_log stderr; error_log ${NETALERTX_LOG}/nginx-error.log; daemon off;" & nginx_pid=$! wait "${nginx_pid}" diff --git a/install/production-filesystem/services/start-php-fpm.sh b/install/production-filesystem/services/start-php-fpm.sh index fc6d5a21..81a245ce 100755 --- a/install/production-filesystem/services/start-php-fpm.sh +++ b/install/production-filesystem/services/start-php-fpm.sh @@ -26,8 +26,9 @@ done trap cleanup EXIT trap forward_signal INT TERM -echo "Starting /usr/sbin/php-fpm83 -y \"${PHP_FPM_CONFIG_FILE}\" -F >>\"${LOG_APP_PHP_ERRORS}\" 2>/dev/stderr &" -/usr/sbin/php-fpm83 -y "${PHP_FPM_CONFIG_FILE}" -F >>"${LOG_APP_PHP_ERRORS}" 2> /dev/stderr & +echo "Starting /usr/sbin/php-fpm83 -y \"${PHP_FPM_CONFIG_FILE}\" -F (tee stderr to app.php_errors.log)" +php_fpm_cmd=(/usr/sbin/php-fpm83 -y "${PHP_FPM_CONFIG_FILE}" -F) +"${php_fpm_cmd[@]}" 2> >(tee -a "${LOG_APP_PHP_ERRORS}" >&2) & php_fpm_pid=$! wait "${php_fpm_pid}" diff --git a/pyproject.toml b/pyproject.toml index 047eade6..4377ec77 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ python_classes = ["Test", "Describe"] python_functions = ["test_", "it_", "and_", "but_", "they_"] python_files = ["test_*.py",] -testpaths = ["test", "tests/docker_tests"] +testpaths = ["test", "test/docker_tests"] norecursedirs = [".git", ".venv", "venv", "node_modules", "__pycache__", "*.egg-info", "build", "dist", "tmp", "api", "log"] markers = [ "docker: requires docker socket and elevated container permissions", diff --git a/test-script.sh b/test-script.sh new file mode 100755 index 00000000..b1f6c904 --- /dev/null +++ b/test-script.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +LOGFILE="/workspaces/NetAlertX/test-script.log" +CMD="/usr/bin/python -m pytest -q test/docker_tests/test_container_environment.py -k missing_app_conf_triggers_seed --maxfail=1 -vv" + +echo "Running: ${CMD}" | tee "${LOGFILE}" +${CMD} 2>&1 | tee -a "${LOGFILE}"