From ba80784ae7d0ba73d6d42c007bb9b526af2c1775 Mon Sep 17 00:00:00 2001 From: NW Date: Mon, 22 Jun 2026 01:26:35 +0100 Subject: [PATCH] security(docker): remove privileged mode, SYS_MODULE; harden WireGuard (#49 #50) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removed privileged: true from docker-compose.yml - Removed SYS_MODULE cap_add (kept NET_ADMIN for WireGuard) - Removed source code bind mounts (./src, package.json) - Removed wg0.conf and resolv.conf bind mounts (now generated from env) - Added resource limits: mem_limit 512m, cpus 1.0 - Added healthcheck with curl - Added non-root user appuser:appgroup in Dockerfile - wg0.conf now generated from env vars at container startup (WG_PRIVATE_KEY, etc.) - resolv.conf generated from WG_DNS env var - Rotated wg0.conf — private key removed from file - Added WG_ALLOWED_IPS to .env.example SECURITY: Rotate WireGuard keys on server if previously used in production --- .env.example | 1 + Dockerfile | 12 ++- docker-compose.yml | 23 +++--- wg/config/resolv.conf | 3 +- wg/config/wg0.conf | 19 ++--- wg/start.sh | 168 ++++++++++++++++++++---------------------- 6 files changed, 113 insertions(+), 113 deletions(-) diff --git a/.env.example b/.env.example index b82142e..9979768 100644 --- a/.env.example +++ b/.env.example @@ -36,6 +36,7 @@ WG_PRESHARED_KEY= WG_ENDPOINT= WG_ADDRESS= WG_DNS= +WG_ALLOWED_IPS=0.0.0.0/0,::/0 # --- Gitea API (для CI/CD и пайплайна) --- GITEA_API_URL=https://git.softuniq.eu/api/v1 diff --git a/Dockerfile b/Dockerfile index cf8d492..7b45307 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,10 @@ RUN apk update && \ curl && \ rm -rf /var/cache/apk/* +# Создаём непривилегированного пользователя +RUN addgroup -S appgroup && \ + adduser -S appuser -G appgroup + # Рабочая директория WORKDIR /app @@ -18,9 +22,15 @@ WORKDIR /app COPY package*.json ./ RUN npm install +# Копируем исходный код с правильным владельцем +COPY --chown=appuser:appgroup ./src ./src + # Копируем скрипт запуска -COPY ./wg/start.sh /app/start.sh +COPY --chown=appuser:appgroup ./wg/start.sh /app/start.sh RUN chmod +x /app/start.sh +# Переключаемся на непривилегированного пользователя +USER appuser + # Команда для запуска CMD ["/bin/bash", "/app/start.sh"] diff --git a/docker-compose.yml b/docker-compose.yml index 1889c75..c3bb73b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,18 +10,19 @@ services: env_file: - .env volumes: - - ./db:/app/db/ # Синхронизация базы данных - - ./src:/app/src/ # Синхронизация исходного кода - - ./package.json:/app/package.json # Синхронизация package.json - - ./package-lock.json:/app/package-lock.json # Синхронизация package-lock.json - - ./wg/config/wg0.conf:/etc/wireguard/wg0.conf # Монтируем конфиг WireGuard - - ./wg/config/resolv.conf:/etc/resolv.conf # Монтируем resolv.conf - - ./wg/start.sh:/app/start.sh # Монтируем start.sh - cap_add: # Необходимо для работы WireGuard - - NET_ADMIN - - SYS_MODULE + - ./db:/app/db/ # Синхронизация базы данных (persistence) + - ./wg/start.sh:/app/start.sh # Монтируем start.sh (генерирует wg0.conf из env) + cap_add: # Минимальные привилегии, необходимые только для WireGuard + - NET_ADMIN sysctls: - net.ipv4.conf.all.src_valid_mark=1 # Необходимо для маршрутизации - privileged: true # Даем контейнеру повышенные привилегии + mem_limit: 512m + cpus: "1.0" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s networks: default: diff --git a/wg/config/resolv.conf b/wg/config/resolv.conf index c71439e..2c2b137 100644 --- a/wg/config/resolv.conf +++ b/wg/config/resolv.conf @@ -1 +1,2 @@ -nameserver 9.9.9.11 +# Generated from WG_DNS environment variable at container startup +# See wg/start.sh \ No newline at end of file diff --git a/wg/config/wg0.conf b/wg/config/wg0.conf index a86e42e..731c32b 100644 --- a/wg/config/wg0.conf +++ b/wg/config/wg0.conf @@ -1,12 +1,7 @@ -# Autogenerated by WireGuard UI (WireAdmin) -[Interface] -PrivateKey = ePxlvZTgr+fJ7ntU6oWti13X8h2100CrjnZFOkSLUWQ= -Address = 10.8.0.4/24 -DNS = 9.9.9.11 - -[Peer] -PublicKey = PYJSZlU38l9OzZnb7iANVk3LotbTg5MdyB2nInxhdA0= -PresharedKey = gK0SjJAvE0oFT6q9yDOQpBP6CyUOclX5yMqAm3hNa1Q= -AllowedIPs = 0.0.0.0/0, ::/0 -PersistentKeepalive = 0 -Endpoint = 194.87.105.23:51820 \ No newline at end of file +# SECURITY: This file is intentionally empty. +# WireGuard configuration is generated from environment variables at container startup. +# See wg/start.sh and .env.example (WG_PRIVATE_KEY, WG_ADDRESS, WG_DNS, etc.). +# +# IMPORTANT: The previous version of this file contained a real WireGuard private key. +# ROTATE THE WIREGUARD KEYS ON THE SERVER SIDE if this key was ever used in production. +# Generate new keys with: wg genkey | tee /dev/stderr | wg pubkey \ No newline at end of file diff --git a/wg/start.sh b/wg/start.sh index 3ecd6a4..28a4e34 100644 --- a/wg/start.sh +++ b/wg/start.sh @@ -16,13 +16,13 @@ print_result() { local status=$? local message=$1 local action=$2 - + if [ -n "$action" ]; then case "$action" in "created") echo "║ 🆕 $message" ;; - "exists") + "exists") echo "║ ✅ $message" ;; *) @@ -40,11 +40,13 @@ print_result() { echo "║ ❌ $message" fi fi - + print_separator } -# Проверка включения WireGuard +# ============================================================ +# WireGuard: полное отключение +# ============================================================ if [ "$WG_ENABLED" = "false" ]; then print_stage "WireGuard is disabled" print_result "Skipping WireGuard setup" @@ -54,37 +56,60 @@ if [ "$WG_ENABLED" = "false" ]; then exit 0 fi -# Проверка наличия /etc/resolv.conf -print_stage "Checking /etc/resolv.conf" -if [ ! -f /etc/resolv.conf ]; then - echo "║ /etc/resolv.conf not found. Creating it..." +# ============================================================ +# WireGuard: включён, но нет приватного ключа — warn и skip +# ============================================================ +if [ -z "$WG_PRIVATE_KEY" ]; then + print_stage "WireGuard misconfiguration" + echo "║ ⚠️ WG_ENABLED=true but WG_PRIVATE_KEY is empty" + echo "║ ⚠️ Skipping WireGuard setup. Set WG_PRIVATE_KEY or set WG_ENABLED=false" + print_result "WireGuard skipped (missing private key)" + print_stage "Starting application" + echo "║ Application is starting without VPN..." + exec node src/index.js + exit 0 +fi + +# ============================================================ +# Генерация /etc/resolv.conf из WG_DNS +# ============================================================ +print_stage "Configuring /etc/resolv.conf" +if [ -n "$WG_DNS" ]; then + echo "║ Using DNS from env WG_DNS: $WG_DNS" + echo "nameserver $WG_DNS" > /etc/resolv.conf +else + echo "║ WG_DNS empty — using fallback DNS: 1.1.1.1, 8.8.8.8" echo "nameserver 1.1.1.1" > /etc/resolv.conf echo "nameserver 8.8.8.8" >> /etc/resolv.conf - if [ $? -eq 0 ]; then - print_result "/etc/resolv.conf created successfully." "created" - else - print_result "Failed to create /etc/resolv.conf" - exit 1 - fi -else - print_result "/etc/resolv.conf already exists." "exists" fi +print_result "/etc/resolv.conf configured." "created" -# Проверка наличия конфига WireGuard -print_stage "Checking WireGuard config" -if [ ! -f /etc/wireguard/wg0.conf ]; then - echo "║ Error: WireGuard config not found!" - exit 1 -else - if [ -r /etc/wireguard/wg0.conf ]; then - print_result "WireGuard config found and readable." "exists" - else - print_result "WireGuard config found but not readable!" - exit 1 - fi -fi +# ============================================================ +# Генерация /etc/wireguard/wg0.conf из env vars +# ============================================================ +print_stage "Generating /etc/wireguard/wg0.conf from environment" +cat > /etc/wireguard/wg0.conf < /tmp/ping.log 2>&1 @@ -96,71 +121,38 @@ else fi print_separator -# Извлекаем DNS из конфига WireGuard -WG_DNS=$(awk -F= '/DNS/ {print $2}' /etc/wireguard/wg0.conf | xargs) - -# Настройка DNS -print_stage "Configuring DNS" -if [ -n "$WG_DNS" ]; then - echo "║ Using DNS from WireGuard config: $WG_DNS" - echo "nameserver $WG_DNS" > /etc/resolv.conf - if [ $? -eq 0 ]; then - print_result "DNS configured using WireGuard settings." "created" - else - print_result "Failed to configure DNS!" - exit 1 - fi +# ============================================================ +# Запуск WireGuard +# ============================================================ +print_stage "Starting WireGuard" +wg-quick up wg0 2>&1 | tee /tmp/wg.log +wg_status=$? +if [ $wg_status -eq 0 ]; then + echo "║ WireGuard started successfully." + print_result "WireGuard interface activated successfully." else - echo "║ Using fallback DNS: 1.1.1.1, 8.8.8.8" - echo "nameserver 1.1.1.1" > /etc/resolv.conf - echo "nameserver 8.8.8.8" >> /etc/resolv.conf - if [ $? -eq 0 ]; then - print_result "DNS configured using fallback settings." "created" - else - print_result "Failed to configure DNS!" - exit 1 - fi + echo "║ WireGuard failed to start. Logs:" + cat /tmp/wg.log | sed 's/^/║ /' + print_result "Failed to start WireGuard interface!" + exit 1 fi -# Проверка включения WireGuard -print_stage "Checking WireGuard status" -if [ "$WG_ENABLED" = "true" ]; then - echo "║ WireGuard is enabled. Starting..." - - # Запуск WireGuard - wg-quick up wg0 2>&1 | tee /tmp/wg.log - wg_status=$? - if [ $wg_status -eq 0 ]; then - echo "║ WireGuard started successfully." - print_result "WireGuard interface activated successfully." - else - echo "║ WireGuard failed to start. Logs:" - cat /tmp/wg.log | sed 's/^/║ /' - print_result "Failed to start WireGuard interface!" - exit 1 - fi - - # Проверка маршрутизации после запуска WireGuard - print_stage "Routing table AFTER WireGuard" - ip route | sed 's/^/║ /' - print_separator +# Проверка маршрутизации после запуска WireGuard +print_stage "Routing table AFTER WireGuard" +ip route | sed 's/^/║ /' +print_separator - # Проверка сети ПОСЛЕ включения WireGuard - print_stage "Testing connectivity AFTER WireGuard" - echo "║ Pinging 1.1.1.1..." - ping -c 4 1.1.1.1 > /tmp/ping.log 2>&1 - if [ $? -eq 0 ]; then - echo "║ Ping successful." - cat /tmp/ping.log | sed 's/^/║ /' - else - echo "║ Ping failed." - fi - print_separator +# Проверка сети ПОСЛЕ включения WireGuard +print_stage "Testing connectivity AFTER WireGuard" +echo "║ Pinging 1.1.1.1..." +ping -c 4 1.1.1.1 > /tmp/ping.log 2>&1 +if [ $? -eq 0 ]; then + echo "║ Ping successful." + cat /tmp/ping.log | sed 's/^/║ /' else - echo "║ WireGuard is disabled. Skipping..." - print_result "WireGuard is disabled in configuration." + echo "║ Ping failed." fi - +print_separator # Проверка DNS print_stage "Testing DNS"