security(docker): remove privileged mode, SYS_MODULE; harden WireGuard (#49 #50)

- 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
This commit is contained in:
NW
2026-06-22 01:26:35 +01:00
parent d0b26dae25
commit ba80784ae7
6 changed files with 113 additions and 113 deletions

View File

@@ -36,6 +36,7 @@ WG_PRESHARED_KEY=
WG_ENDPOINT= WG_ENDPOINT=
WG_ADDRESS= WG_ADDRESS=
WG_DNS= WG_DNS=
WG_ALLOWED_IPS=0.0.0.0/0,::/0
# --- Gitea API (для CI/CD и пайплайна) --- # --- Gitea API (для CI/CD и пайплайна) ---
GITEA_API_URL=https://git.softuniq.eu/api/v1 GITEA_API_URL=https://git.softuniq.eu/api/v1

View File

@@ -11,6 +11,10 @@ RUN apk update && \
curl && \ curl && \
rm -rf /var/cache/apk/* rm -rf /var/cache/apk/*
# Создаём непривилегированного пользователя
RUN addgroup -S appgroup && \
adduser -S appuser -G appgroup
# Рабочая директория # Рабочая директория
WORKDIR /app WORKDIR /app
@@ -18,9 +22,15 @@ WORKDIR /app
COPY package*.json ./ COPY package*.json ./
RUN npm install 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 RUN chmod +x /app/start.sh
# Переключаемся на непривилегированного пользователя
USER appuser
# Команда для запуска # Команда для запуска
CMD ["/bin/bash", "/app/start.sh"] CMD ["/bin/bash", "/app/start.sh"]

View File

@@ -10,18 +10,19 @@ services:
env_file: env_file:
- .env - .env
volumes: volumes:
- ./db:/app/db/ # Синхронизация базы данных - ./db:/app/db/ # Синхронизация базы данных (persistence)
- ./src:/app/src/ # Синхронизация исходного кода - ./wg/start.sh:/app/start.sh # Монтируем start.sh (генерирует wg0.conf из env)
- ./package.json:/app/package.json # Синхронизация package.json cap_add: # Минимальные привилегии, необходимые только для WireGuard
- ./package-lock.json:/app/package-lock.json # Синхронизация package-lock.json - NET_ADMIN
- ./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
sysctls: sysctls:
- net.ipv4.conf.all.src_valid_mark=1 # Необходимо для маршрутизации - 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: networks:
default: default:

View File

@@ -1 +1,2 @@
nameserver 9.9.9.11 # Generated from WG_DNS environment variable at container startup
# See wg/start.sh

View File

@@ -1,12 +1,7 @@
# Autogenerated by WireGuard UI (WireAdmin) # SECURITY: This file is intentionally empty.
[Interface] # WireGuard configuration is generated from environment variables at container startup.
PrivateKey = ePxlvZTgr+fJ7ntU6oWti13X8h2100CrjnZFOkSLUWQ= # See wg/start.sh and .env.example (WG_PRIVATE_KEY, WG_ADDRESS, WG_DNS, etc.).
Address = 10.8.0.4/24 #
DNS = 9.9.9.11 # 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.
[Peer] # Generate new keys with: wg genkey | tee /dev/stderr | wg pubkey
PublicKey = PYJSZlU38l9OzZnb7iANVk3LotbTg5MdyB2nInxhdA0=
PresharedKey = gK0SjJAvE0oFT6q9yDOQpBP6CyUOclX5yMqAm3hNa1Q=
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 0
Endpoint = 194.87.105.23:51820

View File

@@ -16,13 +16,13 @@ print_result() {
local status=$? local status=$?
local message=$1 local message=$1
local action=$2 local action=$2
if [ -n "$action" ]; then if [ -n "$action" ]; then
case "$action" in case "$action" in
"created") "created")
echo "║ 🆕 $message" echo "║ 🆕 $message"
;; ;;
"exists") "exists")
echo "║ ✅ $message" echo "║ ✅ $message"
;; ;;
*) *)
@@ -40,11 +40,13 @@ print_result() {
echo "║ ❌ $message" echo "║ ❌ $message"
fi fi
fi fi
print_separator print_separator
} }
# Проверка включения WireGuard # ============================================================
# WireGuard: полное отключение
# ============================================================
if [ "$WG_ENABLED" = "false" ]; then if [ "$WG_ENABLED" = "false" ]; then
print_stage "WireGuard is disabled" print_stage "WireGuard is disabled"
print_result "Skipping WireGuard setup" print_result "Skipping WireGuard setup"
@@ -54,37 +56,60 @@ if [ "$WG_ENABLED" = "false" ]; then
exit 0 exit 0
fi fi
# Проверка наличия /etc/resolv.conf # ============================================================
print_stage "Checking /etc/resolv.conf" # WireGuard: включён, но нет приватного ключа — warn и skip
if [ ! -f /etc/resolv.conf ]; then # ============================================================
echo "║ /etc/resolv.conf not found. Creating it..." 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 1.1.1.1" > /etc/resolv.conf
echo "nameserver 8.8.8.8" >> /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 fi
print_result "/etc/resolv.conf configured." "created"
# Проверка наличия конфига WireGuard # ============================================================
print_stage "Checking WireGuard config" # Генерация /etc/wireguard/wg0.conf из env vars
if [ ! -f /etc/wireguard/wg0.conf ]; then # ============================================================
echo "║ Error: WireGuard config not found!" print_stage "Generating /etc/wireguard/wg0.conf from environment"
exit 1 cat > /etc/wireguard/wg0.conf <<EOF
else # Generated from environment variables at container start.
if [ -r /etc/wireguard/wg0.conf ]; then # DO NOT commit real keys to the repository.
print_result "WireGuard config found and readable." "exists" [Interface]
else PrivateKey = ${WG_PRIVATE_KEY}
print_result "WireGuard config found but not readable!" Address = ${WG_ADDRESS}
exit 1 DNS = ${WG_DNS}
fi
fi
[Peer]
PublicKey = ${WG_PUBLIC_KEY}
PresharedKey = ${WG_PRESHARED_KEY}
AllowedIPs = ${WG_ALLOWED_IPS:-0.0.0.0/0,::/0}
PersistentKeepalive = 0
Endpoint = ${WG_ENDPOINT}
EOF
chmod 600 /etc/wireguard/wg0.conf
print_result "wg0.conf generated with mode 0600." "created"
# ============================================================
# Проверка сети ДО включения WireGuard # Проверка сети ДО включения WireGuard
# ============================================================
print_stage "Testing connectivity BEFORE WireGuard" print_stage "Testing connectivity BEFORE WireGuard"
echo "║ Pinging 1.1.1.1..." echo "║ Pinging 1.1.1.1..."
ping -c 4 1.1.1.1 > /tmp/ping.log 2>&1 ping -c 4 1.1.1.1 > /tmp/ping.log 2>&1
@@ -96,71 +121,38 @@ else
fi fi
print_separator print_separator
# Извлекаем DNS из конфига WireGuard # ============================================================
WG_DNS=$(awk -F= '/DNS/ {print $2}' /etc/wireguard/wg0.conf | xargs) # Запуск WireGuard
# ============================================================
# Настройка DNS print_stage "Starting WireGuard"
print_stage "Configuring DNS" wg-quick up wg0 2>&1 | tee /tmp/wg.log
if [ -n "$WG_DNS" ]; then wg_status=$?
echo "║ Using DNS from WireGuard config: $WG_DNS" if [ $wg_status -eq 0 ]; then
echo "nameserver $WG_DNS" > /etc/resolv.conf echo "║ WireGuard started successfully."
if [ $? -eq 0 ]; then print_result "WireGuard interface activated successfully."
print_result "DNS configured using WireGuard settings." "created"
else
print_result "Failed to configure DNS!"
exit 1
fi
else else
echo "║ Using fallback DNS: 1.1.1.1, 8.8.8.8" echo "║ WireGuard failed to start. Logs:"
echo "nameserver 1.1.1.1" > /etc/resolv.conf cat /tmp/wg.log | sed 's/^/║ /'
echo "nameserver 8.8.8.8" >> /etc/resolv.conf print_result "Failed to start WireGuard interface!"
if [ $? -eq 0 ]; then exit 1
print_result "DNS configured using fallback settings." "created"
else
print_result "Failed to configure DNS!"
exit 1
fi
fi fi
# Проверка включения WireGuard # Проверка маршрутизации после запуска WireGuard
print_stage "Checking WireGuard status" print_stage "Routing table AFTER WireGuard"
if [ "$WG_ENABLED" = "true" ]; then ip route | sed 's/^/║ /'
echo "║ WireGuard is enabled. Starting..." print_separator
# Запуск 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 # Проверка сети ПОСЛЕ включения WireGuard
print_stage "Testing connectivity AFTER WireGuard" print_stage "Testing connectivity AFTER WireGuard"
echo "║ Pinging 1.1.1.1..." echo "║ Pinging 1.1.1.1..."
ping -c 4 1.1.1.1 > /tmp/ping.log 2>&1 ping -c 4 1.1.1.1 > /tmp/ping.log 2>&1
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
echo "║ Ping successful." echo "║ Ping successful."
cat /tmp/ping.log | sed 's/^/║ /' cat /tmp/ping.log | sed 's/^/║ /'
else
echo "║ Ping failed."
fi
print_separator
else else
echo "║ WireGuard is disabled. Skipping..." echo "║ Ping failed."
print_result "WireGuard is disabled in configuration."
fi fi
print_separator
# Проверка DNS # Проверка DNS
print_stage "Testing DNS" print_stage "Testing DNS"