mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-03-30 23:03:03 -07:00
coderabbit changes
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -44,4 +44,5 @@ front/css/cloud_services.css
|
||||
|
||||
docker-compose.yml.ffsb42
|
||||
.env.omada.ffsb42
|
||||
.venv
|
||||
.venv
|
||||
test_mounts/
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/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}"
|
||||
@@ -28,6 +28,32 @@ services:
|
||||
APP_CONF_OVERRIDE: ${GRAPHQL_PORT:-20212}
|
||||
ALWAYS_FRESH_INSTALL: ${ALWAYS_FRESH_INSTALL:-false}
|
||||
NETALERTX_DEBUG: ${NETALERTX_DEBUG:-0}
|
||||
# Environment variable: NETALERTX_CHECK_ONLY
|
||||
#
|
||||
# Purpose: Enables check-only mode for container startup diagnostics and capability testing.
|
||||
#
|
||||
# When set to 1 (enabled):
|
||||
# - Container runs all startup checks and prints diagnostic information
|
||||
# - Services are NOT started (container exits after checks complete)
|
||||
# - Useful for testing configurations, auditing capabilities, or troubleshooting
|
||||
#
|
||||
# When set to 0 (disabled):
|
||||
# - Normal operation: container starts all services after passing checks
|
||||
#
|
||||
# Default: 1 in this compose file (check-only mode for testing)
|
||||
# Production default: 0 (full startup)
|
||||
#
|
||||
# Automatic behavior:
|
||||
# - May be automatically set by root-entrypoint.sh when privilege drop fails
|
||||
# - Triggers immediate exit path in entrypoint.sh after diagnostic output
|
||||
#
|
||||
# Usage examples:
|
||||
# NETALERTX_CHECK_ONLY: 0 # Normal startup with services
|
||||
# NETALERTX_CHECK_ONLY: 1 # Check-only mode (exits after diagnostics)
|
||||
#
|
||||
# Troubleshooting:
|
||||
# If container exits immediately after startup checks, verify this variable is set to 0
|
||||
# for production deployments. Check container logs for diagnostic output from startup checks.
|
||||
NETALERTX_CHECK_ONLY: ${NETALERTX_CHECK_ONLY:-1}
|
||||
|
||||
mem_limit: 2048m
|
||||
|
||||
@@ -14,6 +14,8 @@ services:
|
||||
cap_add:
|
||||
- SETUID
|
||||
- SETGID
|
||||
- NET_RAW
|
||||
- NET_ADMIN
|
||||
# Intentionally drop CHOWN to prove failure path while leaving defaults intact
|
||||
environment:
|
||||
LISTEN_ADDR: 0.0.0.0
|
||||
|
||||
@@ -31,11 +31,11 @@ services:
|
||||
target: /data/config
|
||||
read_only: false
|
||||
tmpfs:
|
||||
- "/data/db:mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/api:mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/log:mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/run:mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/nginx/active-config:mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/data/db:mode=1700,uid=0,gid=0,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/api:mode=1700,uid=0,gid=0,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/log:mode=1700,uid=0,gid=0,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/run:mode=1700,uid=0,gid=0,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/nginx/active-config:mode=1700,uid=0,gid=0,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
volumes:
|
||||
netalertx_config:
|
||||
netalertx_db:
|
||||
|
||||
@@ -35,10 +35,10 @@ services:
|
||||
target: /data/config
|
||||
read_only: false
|
||||
tmpfs:
|
||||
- "/tmp/api:mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/log:mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/run:mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/nginx/active-config:mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/api:mode=1700,uid=0,gid=0,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/log:mode=1700,uid=0,gid=0,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/run:mode=1700,uid=0,gid=0,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/nginx/active-config:mode=1700,uid=0,gid=0,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
volumes:
|
||||
netalertx_config:
|
||||
test_netalertx_db:
|
||||
@@ -39,9 +39,9 @@ services:
|
||||
target: /tmp/log
|
||||
read_only: false
|
||||
tmpfs:
|
||||
- "/tmp/api:mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/run:mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/nginx/active-config:mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/api:mode=1700,uid=20211,gid=20211,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/run:mode=1700,uid=20211,gid=20211,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
- "/tmp/nginx/active-config:mode=1700,uid=20211,gid=20211,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
|
||||
volumes:
|
||||
netalertx_config:
|
||||
netalertx_db:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -13,8 +14,48 @@ def _announce(request: pytest.FixtureRequest, message: str) -> None:
|
||||
print(message)
|
||||
|
||||
|
||||
def _clean_test_mounts(project_root: pathlib.Path) -> None:
|
||||
"""Clean up the test_mounts directory, handling root-owned files via Docker."""
|
||||
mounts_dir = project_root / "test_mounts"
|
||||
if not mounts_dir.exists():
|
||||
return
|
||||
|
||||
# Try python removal first (faster)
|
||||
try:
|
||||
shutil.rmtree(mounts_dir)
|
||||
except PermissionError:
|
||||
# Fallback to docker for root-owned files
|
||||
# We mount the parent directory to delete the directory itself
|
||||
cmd = [
|
||||
"docker", "run", "--rm",
|
||||
"-v", f"{project_root}:/work",
|
||||
"alpine:3.22",
|
||||
"rm", "-rf", "/work/test_mounts"
|
||||
]
|
||||
subprocess.run(
|
||||
cmd,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
check=False
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def cleanup_artifacts(request: pytest.FixtureRequest) -> None:
|
||||
"""Ensure test artifacts are cleaned up before and after the session."""
|
||||
project_root = pathlib.Path(__file__).resolve().parents[2]
|
||||
|
||||
_announce(request, "[docker-tests] Cleaning up previous test artifacts...")
|
||||
_clean_test_mounts(project_root)
|
||||
|
||||
yield
|
||||
|
||||
_announce(request, "[docker-tests] Cleaning up test artifacts...")
|
||||
_clean_test_mounts(project_root)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def build_netalertx_test_image(request: pytest.FixtureRequest) -> None:
|
||||
def build_netalertx_test_image(request: pytest.FixtureRequest, cleanup_artifacts: None) -> None:
|
||||
"""Build the docker test image before running any docker-based tests."""
|
||||
|
||||
image = os.environ.get("NETALERTX_TEST_IMAGE", "netalertx-test")
|
||||
|
||||
@@ -87,10 +87,11 @@ def _docker_visible_tmp_root() -> pathlib.Path:
|
||||
|
||||
Pytest's default tmp_path lives under /tmp inside the devcontainer, which may
|
||||
not be visible to the Docker daemon that evaluates bind mount source paths.
|
||||
We use /tmp/pytest-docker-mounts instead of the repo.
|
||||
We use a directory under the repo root which is guaranteed to be shared.
|
||||
"""
|
||||
|
||||
root = pathlib.Path("/tmp/pytest-docker-mounts")
|
||||
# Use a directory inside the workspace to ensure visibility to Docker daemon
|
||||
root = _repo_root() / "test_mounts"
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
root.chmod(0o777)
|
||||
@@ -1259,6 +1260,8 @@ def test_mount_analysis_ram_disk_performance(tmp_path: pathlib.Path) -> None:
|
||||
f"{VOLUME_MAP['app_db']}:uid=20211,gid=20211,mode=755",
|
||||
"--tmpfs",
|
||||
f"{VOLUME_MAP['app_config']}:uid=20211,gid=20211,mode=755",
|
||||
"--tmpfs",
|
||||
"/tmp/nginx:uid=20211,gid=20211,mode=755",
|
||||
]
|
||||
result = _run_container(
|
||||
"ram-disk-mount", volumes=volumes, extra_args=extra_args, user="20211:20211"
|
||||
@@ -1314,6 +1317,8 @@ def test_mount_analysis_dataloss_risk(tmp_path: pathlib.Path) -> None:
|
||||
f"{VOLUME_MAP['app_db']}:uid=20211,gid=20211,mode=755",
|
||||
"--tmpfs",
|
||||
f"{VOLUME_MAP['app_config']}:uid=20211,gid=20211,mode=755",
|
||||
"--tmpfs",
|
||||
"/tmp/nginx:uid=20211,gid=20211,mode=755",
|
||||
]
|
||||
result = _run_container(
|
||||
"dataloss-risk", volumes=volumes, extra_args=extra_args, user="20211:20211"
|
||||
@@ -1354,16 +1359,16 @@ def test_restrictive_permissions_handling(tmp_path: pathlib.Path) -> None:
|
||||
"""
|
||||
paths = _setup_mount_tree(tmp_path, "restrictive_perms")
|
||||
|
||||
# Helper to chown without userns host (workaround for potential devcontainer hang)
|
||||
def _chown_root_safe(host_path: pathlib.Path) -> None:
|
||||
# Helper to chown/chmod without userns host (workaround for potential devcontainer hang)
|
||||
def _setup_restrictive_dir(host_path: pathlib.Path) -> None:
|
||||
cmd = [
|
||||
"docker", "run", "--rm",
|
||||
# "--userns", "host", # Removed to avoid hang
|
||||
"--user", "0:0",
|
||||
"--entrypoint", "/bin/chown",
|
||||
"--entrypoint", "/bin/sh",
|
||||
"-v", f"{host_path}:/mnt",
|
||||
IMAGE,
|
||||
"-R", "0:0", "/mnt",
|
||||
"-c", "chown -R 0:0 /mnt && chmod 755 /mnt",
|
||||
]
|
||||
subprocess.run(
|
||||
cmd,
|
||||
@@ -1375,8 +1380,7 @@ def test_restrictive_permissions_handling(tmp_path: pathlib.Path) -> None:
|
||||
|
||||
# Set up a restrictive directory (root owned, 755)
|
||||
target_dir = paths["app_db"]
|
||||
_chown_root_safe(target_dir)
|
||||
target_dir.chmod(0o755)
|
||||
_setup_restrictive_dir(target_dir)
|
||||
|
||||
# Mount ALL volumes to avoid errors during permission checks
|
||||
keys = {"data", "app_db", "app_config", "app_log", "app_api", "services_run", "nginx_conf"}
|
||||
|
||||
@@ -17,12 +17,12 @@ def test_run_docker_compose_returns_output(monkeypatch, tmp_path):
|
||||
subprocess.CompletedProcess([], 0, stdout="down-initial\n", stderr=""),
|
||||
subprocess.CompletedProcess(["up"], 0, stdout="up-out\n", stderr=""),
|
||||
subprocess.CompletedProcess(["logs"], 0, stdout="log-out\n", stderr=""),
|
||||
# ps_proc: cause compose ps parsing to fail (no containers listed)
|
||||
subprocess.CompletedProcess(["ps"], 0, stdout="", stderr="no containers"),
|
||||
# ps_proc: return valid container entries
|
||||
subprocess.CompletedProcess(["ps"], 0, stdout="test-container Running 0\n", stderr=""),
|
||||
subprocess.CompletedProcess([], 0, stdout="down-final\n", stderr=""),
|
||||
]
|
||||
|
||||
def fake_run(*args, **kwargs):
|
||||
def fake_run(*_, **__):
|
||||
try:
|
||||
return cps.pop(0)
|
||||
except IndexError:
|
||||
|
||||
@@ -651,7 +651,7 @@ def test_mount_diagnostic(netalertx_test_image, test_scenario):
|
||||
|
||||
# Wait for container to be ready
|
||||
import time
|
||||
# Container is still running - validate the diagnostics already run at startup
|
||||
# Container is still running - validate the diagnostics already run at startup
|
||||
# Give entrypoint scripts a moment to finish outputting to logs
|
||||
time.sleep(2)
|
||||
|
||||
@@ -727,7 +727,7 @@ def test_table_parsing():
|
||||
|
||||
|
||||
@pytest.mark.docker
|
||||
def test_cap_chown_required_when_caps_dropped(netalertx_test_image):
|
||||
def test_cap_chown_required_when_caps_dropped():
|
||||
"""Ensure startup warns (but runs) when CHOWN capability is removed."""
|
||||
|
||||
compose_file = CONFIG_DIR / "mount-tests" / "docker-compose.mount-test.cap_chown_missing.yml"
|
||||
@@ -747,7 +747,7 @@ def test_cap_chown_required_when_caps_dropped(netalertx_test_image):
|
||||
container_name = "netalertx-test-mount-cap_chown_missing"
|
||||
|
||||
result = subprocess.run(
|
||||
base_cmd + ["down", "-v"], capture_output=True, text=True, timeout=30, env=compose_env
|
||||
[*base_cmd, "down", "-v"], capture_output=True, text=True, timeout=30, env=compose_env
|
||||
)
|
||||
print(result.stdout) # DO NOT REMOVE OR MODIFY - MANDATORY LOGGING FOR DEBUGGING & CI.
|
||||
print(result.stderr) # DO NOT REMOVE OR MODIFY - MANDATORY LOGGING FOR DEBUGGING & CI.
|
||||
@@ -762,7 +762,7 @@ def test_cap_chown_required_when_caps_dropped(netalertx_test_image):
|
||||
print(result.stdout) # DO NOT REMOVE OR MODIFY - MANDATORY LOGGING FOR DEBUGGING & CI.
|
||||
print(result.stderr) # DO NOT REMOVE OR MODIFY - MANDATORY LOGGING FOR DEBUGGING & CI.
|
||||
|
||||
cmd_up = base_cmd + ["up", "-d"]
|
||||
cmd_up = [*base_cmd, "up", "-d"]
|
||||
|
||||
try:
|
||||
result_up = subprocess.run(
|
||||
@@ -806,7 +806,7 @@ def test_cap_chown_required_when_caps_dropped(netalertx_test_image):
|
||||
|
||||
finally:
|
||||
result = subprocess.run(
|
||||
base_cmd + ["down", "-v"], capture_output=True, text=True, timeout=30, env=compose_env
|
||||
[*base_cmd, "down", "-v"], capture_output=True, text=True, timeout=30, env=compose_env
|
||||
)
|
||||
print(result.stdout) # DO NOT REMOVE OR MODIFY - MANDATORY LOGGING FOR DEBUGGING & CI.
|
||||
print(result.stderr) # DO NOT REMOVE OR MODIFY - MANDATORY LOGGING FOR DEBUGGING & CI.
|
||||
|
||||
Reference in New Issue
Block a user