10 Commits

Author SHA1 Message Date
embeddevio b69c3b67e2 fix(#61): не оставлять pip в runtime-образе
ci / lint (push) Successful in 52s
ci / lint (pull_request) Successful in 44s
contract-openapi / openapi-contract (push) Successful in 50s
contract-openapi / ts-client-is-current (push) Successful in 52s
contract-openapi / openapi-contract (pull_request) Failing after 13m2s
ci / unit (push) Has been cancelled
ci / security (push) Has been cancelled
ci / unit (pull_request) Has been cancelled
ci / security (pull_request) Has been cancelled
contract-openapi / ts-client-is-current (pull_request) Has been cancelled
ci / build (push) Has been cancelled
ci / image-scan (push) Has been cancelled
ci / push (push) Has been cancelled
ci / build (pull_request) Has been cancelled
ci / image-scan (pull_request) Has been cancelled
ci / push (pull_request) Has been cancelled
trivy разбирает pip/_vendor/vendor.txt и репортит вендоренные msgpack 1.1.2 /
urllib3 2.7.0 / setuptools 70.3.0 как установленные пакеты — при том что в venv
реально стоят 1.2.4 / 2.8.0 / 84.2.1 (проверено pip list внутри образа staging-cc3621a1).
py3-pip переведён в .build-deps и удаляется вместе с ними; pip в venv и системный
pip вычищаются — runtime не должен содержать инсталлятора.

Refs #61
2026-10-29 22:41:58 +03:00
embeddevio cc3621a12b fix(#61): пины транзитивных python-зависимостей под гейт image-scan
ci / lint (push) Successful in 51s
ci / lint (pull_request) Successful in 44s
contract-openapi / openapi-contract (push) Successful in 49s
contract-openapi / ts-client-is-current (push) Successful in 43s
contract-openapi / openapi-contract (pull_request) Successful in 58s
contract-openapi / ts-client-is-current (pull_request) Successful in 49s
ci / unit (push) Successful in 51s
ci / security (push) Successful in 1m11s
ci / unit (pull_request) Successful in 56s
ci / security (pull_request) Successful in 2m42s
ci / build (push) Successful in 5m35s
ci / build (pull_request) Failing after 42s
ci / image-scan (push) Failing after 45s
ci / image-scan (pull_request) Has been skipped
ci / push (push) Has been skipped
ci / push (pull_request) Has been skipped
После бампа Go/alpine/x-crypto/x-net в образе остались 5 HIGH — все в venv:
  urllib3 2.7.0 → 2.8.0, msgpack 1.1.2 → 1.2.3, setuptools 70.3.0 → 78.1.1

Refs #61
2026-10-29 22:16:53 +03:00
embeddevio ea63e904b4 fix(#61): x/crypto v0.58.0 + x/net v0.62.0
ci / lint (pull_request) Successful in 47s
contract-openapi / openapi-contract (push) Successful in 53s
contract-openapi / ts-client-is-current (push) Successful in 36s
contract-openapi / openapi-contract (pull_request) Successful in 58s
contract-openapi / ts-client-is-current (pull_request) Successful in 42s
ci / unit (pull_request) Successful in 53s
ci / security (pull_request) Successful in 1m21s
ci / build (pull_request) Successful in 40s
ci / image-scan (pull_request) Failing after 7s
ci / push (pull_request) Has been skipped
ci / lint (push) Successful in 50s
ci / unit (push) Successful in 52s
ci / security (push) Successful in 1m19s
ci / build (push) Successful in 39s
ci / image-scan (push) Failing after 3s
ci / push (push) Has been skipped
После бампа Go 1.26 / alpine 3.23 в образе остались только Go-модули:
  x/crypto v0.43.0 — 5 HIGH (ssh*, фиксы до v0.55.0)
  x/net    v0.45.0 — 16 HIGH (http2/idna/dnsmessage, фиксы до v0.60.0)
Оба подняты на актуальные версии из proxy.golang.org (v0.58.0 / v0.62.0).
В alpine 3.23.7 находок нет (66 пакетов).

Refs #61
2026-10-29 21:51:41 +03:00
embeddevio 20db51b7a2 fix(#61): бамп базовых образов и x/crypto под гейт image-scan
ci / lint (push) Successful in 40s
ci / lint (pull_request) Successful in 40s
contract-openapi / openapi-contract (push) Successful in 51s
contract-openapi / ts-client-is-current (push) Successful in 42s
contract-openapi / openapi-contract (pull_request) Successful in 54s
contract-openapi / ts-client-is-current (pull_request) Successful in 39s
ci / unit (push) Successful in 46s
ci / security (push) Successful in 1m24s
ci / unit (pull_request) Successful in 58s
ci / security (pull_request) Successful in 1m27s
ci / build (push) Successful in 9m44s
ci / build (pull_request) Successful in 35s
ci / image-scan (push) Failing after 7s
ci / image-scan (pull_request) Failing after 3s
ci / push (push) Has been skipped
ci / push (pull_request) Has been skipped
- GO_IMAGE golang:1.25-alpine → golang:1.26-alpine (CVE Go stdlib 1.25.14: CVE-2026-56857 CRITICAL, CVE-2026-111943/78667/97031 HIGH)
- golang.org/x/crypto → v0.43.0 в amneziawg-go (CVE-2025-47913 HIGH)
- runtime alpine:3.21 → alpine:3.23 + apk upgrade (HIGH в пакетах базы)

Refs #61, Refs #18, Refs #17
2026-10-29 21:09:52 +03:00
embeddevio e153143cc1 Merge branch 'main' into feat/18-docker-runtime
ci / lint (push) Successful in 51s
ci / lint (pull_request) Successful in 49s
contract-openapi / openapi-contract (push) Successful in 59s
contract-openapi / ts-client-is-current (push) Successful in 48s
contract-openapi / openapi-contract (pull_request) Successful in 59s
contract-openapi / ts-client-is-current (pull_request) Successful in 43s
ci / unit (push) Successful in 52s
ci / security (push) Successful in 1m21s
ci / unit (pull_request) Successful in 55s
ci / security (pull_request) Successful in 1m20s
ci / build (push) Successful in 36s
ci / build (pull_request) Successful in 36s
ci / image-scan (push) Failing after 3s
ci / image-scan (pull_request) Failing after 3s
ci / push (push) Has been skipped
ci / push (pull_request) Has been skipped
2026-10-29 21:09:40 +03:00
embeddevio d2f6e89d58 fix(#18): создавать /etc/iproute2/rt_tables (в alpine его нет) + понятное предупреждение про ip_forward 2026-10-21 23:47:15 +03:00
embeddevio 789c1854fe fix(#18): amneziawg-tools/src собирает бинарь wg — копируем его как awg (+awg-quick) 2026-10-21 23:42:43 +03:00
embeddevio ec44dcca4e fix(#18): builder на golang:1.25 — amneziawg-go требует go >= 1.25 (падение сборки на 1.23) 2026-10-21 23:38:47 +03:00
embeddevio 71e9891247 feat(#18): супервизор entrypoint.sh и пример .conf 2026-10-21 23:31:04 +03:00
embeddevio 2adc02cb3f feat(#18): multi-stage Dockerfile (awg-tools + runtime alpine) 2026-10-21 23:30:57 +03:00
4 changed files with 219 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
# syntax=docker/dockerfile:1.7
#
# awg-dash — единый образ: AmneziaWG gateway + policy routing + REST API + Web UI.
# Задача: #18 (эпик #15). ADR: docs/adr/002-single-image-modular-monolith.md
#
# ВАЖНО: AMNEZIAWG_GO_REF / AMNEZIAWG_TOOLS_REF по умолчанию указывают на master
# и должны быть запинены на проверенный тег/коммит перед релизом (см. #45).
# Базовые образы и зависимости подняты под гейт image-scan (issue #61):
# golang:1.26-alpine — закрывает CVE Go stdlib 1.25.14 (CRITICAL CVE-2026-56857 + HIGH)
# golang.org/x/crypto v0.58.0 + golang.org/x/net v0.62.0 — закрывают CVE в ssh/* и http2/idna
# alpine:3.23 — закрывает HIGH в пакетах alpine 3.21
ARG GO_IMAGE=golang:1.26-alpine
ARG X_CRYPTO_VERSION=v0.58.0
ARG X_NET_VERSION=v0.62.0
# --- stage 1: сборка awg-утилит (Go) -----------------------------------------
FROM ${GO_IMAGE} AS go-build
ARG AMNEZIAWG_GO_REPO=https://github.com/amnezia-vpn/amneziawg-go.git
ARG AMNEZIAWG_GO_REF=master
ARG AMNEZIAWG_TOOLS_REPO=https://github.com/amnezia-vpn/amneziawg-tools.git
ARG AMNEZIAWG_TOOLS_REF=master
ARG X_CRYPTO_VERSION
ARG X_NET_VERSION
RUN apk add --no-cache git make gcc musl-dev linux-headers
WORKDIR /src
RUN git clone --depth 1 --branch "${AMNEZIAWG_GO_REF}" "${AMNEZIAWG_GO_REPO}" amneziawg-go \
&& cd amneziawg-go \
&& go get "golang.org/x/crypto@${X_CRYPTO_VERSION}" "golang.org/x/net@${X_NET_VERSION}" \
&& go mod tidy \
&& go list -m golang.org/x/crypto golang.org/x/net \
&& make \
&& cp amneziawg-go /usr/local/bin/amneziawg-go
RUN git clone --depth 1 --branch "${AMNEZIAWG_TOOLS_REF}" "${AMNEZIAWG_TOOLS_REPO}" amneziawg-tools \
&& cd amneziawg-tools/src && make \
&& cp wg /usr/local/bin/awg \
&& if [ -f wg-quick ]; then cp wg-quick /usr/local/bin/awg-quick; fi
# --- stage 2: runtime -------------------------------------------------------
FROM alpine:3.23 AS runtime
# Пакеты базы обновляем на момент сборки: trivy --ignore-unfixed учитывает только исправимые CVE.
RUN apk upgrade --no-cache
LABEL org.opencontainers.image.title="awg-dash" \
org.opencontainers.image.description="AmneziaWG gateway with intelligent routing: REST API + Web UI + split-tunnel" \
org.opencontainers.image.source="http://192.168.1.111:3000/embeddevio/awg-dash"
# Сетевые утилиты: iptables/ip6tables, ipset, iproute2 (policy routing), dnsmasq (ADR-005),
# curl (HEALTHCHECK), tini (PID 1), su-exec (запуск API под непривилегированным пользователем).
RUN apk add --no-cache \
bash curl iproute2 ipset iptables ip6tables dnsmasq \
python3 tini su-exec \
&& addgroup -S awg \
&& adduser -S -G awg -h /app awg
ENV PATH=/opt/venv/bin:$PATH \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONPATH=/app/backend \
AWG_CONFIG_DIR=/config \
AWG_DATA_DIR=/data \
AWG_RUN_DIR=/run/awg \
AWG_API_HOST=0.0.0.0 \
AWG_API_PORT=8080
WORKDIR /app
COPY requirements.txt /app/requirements.txt
# py3-pip нужен только на время сборки venv: в runtime pip не остаётся. Иначе trivy разбирает
# pip/_vendor/vendor.txt и репортит вендоренные версии (msgpack 1.1.2 / urllib3 2.7.0 /
# setuptools 70.3.0) как установленные пакеты — ложные HIGH на образе, где стоят 1.2.4/2.8.0/84.2.1 (issue #61).
RUN apk add --no-cache --virtual .build-deps gcc musl-dev python3-dev libffi-dev py3-pip \
&& python3 -m venv /opt/venv \
&& /opt/venv/bin/pip install --no-cache-dir --upgrade pip \
&& /opt/venv/bin/pip install --no-cache-dir -r /app/requirements.txt \
&& apk del .build-deps \
&& rm -rf /opt/venv/lib/python3*/site-packages/pip /opt/venv/lib/python3*/site-packages/pip-* \
&& rm -f /opt/venv/bin/pip /opt/venv/bin/pip3 /opt/venv/bin/pip3.* \
&& rm -rf /usr/lib/python3*/site-packages/pip /usr/lib/python3*/site-packages/pip-* \
&& rm -f /usr/bin/pip3 /usr/bin/pip3.*
COPY backend/ /app/backend/
COPY --from=go-build /usr/local/bin/awg /usr/local/bin/awg
COPY --from=go-build /usr/local/bin/amneziawg-go /usr/local/bin/amneziawg-go
COPY deploy/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh \
&& mkdir -p /config /data /run/awg \
&& chown -R awg:awg /app /config /data /run/awg
VOLUME ["/config", "/data"]
EXPOSE 8080
# healthcheck ходит в /api/v1/health (задача #31). До реализации API контейнер
# будет помечаться как unhealthy — это ожидаемое состояние до #31.
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD curl -fsS "http://127.0.0.1:${AWG_API_PORT}/api/v1/health" >/dev/null || exit 1
ENTRYPOINT ["/sbin/tini", "--", "/entrypoint.sh"]
+32
View File
@@ -0,0 +1,32 @@
# Пример клиентского конфига AmneziaWG для awg-dash.
#
# Скопируйте в том /config как awg0.conf (имя файла = имя интерфейса) или
# загрузите через Web UI (задача #38). Значения ниже — заглушки, замените своими.
# Файл в репозитории не является рабочим конфигом и не содержит реальных ключей.
[Interface]
# Адрес внутри туннеля (из панели провайдера AmneziaWG)
Address = 10.8.1.2/32
# DNS: адрес должен указывать на этот шлюз, иначе не сработает DNS-split (ADR-005)
DNS = 10.8.1.1
PrivateKey = <PRIVATE_KEY>
# Обфускация AmneziaWG (Amnezia VPN: экспорт -> AmneziaWG)
Jc = 4
Jmin = 40
Jmax = 70
S1 = 0
S2 = 0
H1 = 1
H2 = 2
H3 = 3
H4 = 4
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
PresharedKey = <PRESHARED_KEY>
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = <SERVER_HOST>:<SERVER_PORT>
PersistentKeepalive = 25
+77
View File
@@ -0,0 +1,77 @@
#!/bin/sh
# Супервизор контейнера awg-dash (задача #18, ADR-002).
# Последовательность: preflight -> подготовка -> запуск awg-api под awg.
# Привилегированный агент awg-agent стартует отсюда же после реализации (#22).
#
# Роль процесса: все сетевые операции выполняет агент под root; здесь только
# проверяем, что нужные возможности и устройства проброшены, и падаем явной
# ошибкой, если контейнер запущен без них (а не тихо деградируем).
set -eu
CONFIG_DIR="${AWG_CONFIG_DIR:-/config}"
DATA_DIR="${AWG_DATA_DIR:-/data}"
RUN_DIR="${AWG_RUN_DIR:-/run/awg}"
API_HOST="${AWG_API_HOST:-0.0.0.0}"
API_PORT="${AWG_API_PORT:-8080}"
ROUTING_TABLE_ID=100
ROUTING_TABLE_NAME=awg-vpn
log() { printf '[entrypoint] %s\n' "$*"; }
fail() { printf '[entrypoint] FATAL: %s\n' "$*" >&2; exit 1; }
log 'preflight'
[ -e /dev/net/tun ] || fail '/dev/net/tun не проброшен: добавьте --device=/dev/net/tun:/dev/net/tun'
iptables -t mangle -L -n >/dev/null 2>&1 || fail 'iptables недоступен: нужен --cap-add=NET_ADMIN (и/или --privileged)'
command -v awg >/dev/null 2>&1 || fail 'бинарь awg не найден в образе'
command -v amneziawg-go >/dev/null 2>&1 || log 'WARN: amneziawg-go не найден — остаётся только kernel-модуль'
command -v ipset >/dev/null 2>&1 || fail 'ipset не найден в образе'
log 'подготовка каталогов состояния'
mkdir -p "$CONFIG_DIR" "$DATA_DIR" "$RUN_DIR"
chown -R awg:awg "$CONFIG_DIR" "$DATA_DIR" "$RUN_DIR" 2>/dev/null || true
chmod 700 "$CONFIG_DIR" "$DATA_DIR" 2>/dev/null || true
mkdir -p /etc/iproute2
touch /etc/iproute2/rt_tables 2>/dev/null || true
if ! grep -q "^${ROUTING_TABLE_ID} ${ROUTING_TABLE_NAME}" /etc/iproute2/rt_tables 2>/dev/null; then
printf '%s %s\n' "$ROUTING_TABLE_ID" "$ROUTING_TABLE_NAME" >> /etc/iproute2/rt_tables \
|| log 'WARN: не удалось записать таблицу маршрутизации в /etc/iproute2/rt_tables'
fi
sysctl -w net.ipv4.ip_forward=1 >/dev/null 2>&1 \
|| log 'WARN: net.ipv4.ip_forward не включён — задайте --sysctl net.ipv4.ip_forward=1 или --privileged (см. runbook #45)'
log "awg: $(awg --version 2>/dev/null | head -n 1 || echo 'version unknown')"
api_pid=''
if [ -f /app/backend/awg_api/app.py ]; then
log "запуск awg-api на ${API_HOST}:${API_PORT}"
su-exec awg /opt/venv/bin/uvicorn awg_api.app:app --host "$API_HOST" --port "$API_PORT" &
api_pid=$!
else
log 'WARN: awg_api/app.py ещё не реализован (задача #31) — API не запущен, контейнер работает только как агент'
fi
stop() {
if [ -n "$api_pid" ] && kill -0 "$api_pid" 2>/dev/null; then
log 'остановка awg-api'
kill -TERM "$api_pid" 2>/dev/null || true
fi
}
trap stop INT TERM
if [ -n "$api_pid" ]; then
# ждём завершения API и прокидываем его код возврата в Docker
set +e
wait "$api_pid"
rc=$?
set -e
log "awg-api завершился с кодом $rc"
exit "$rc"
fi
log 'активных процессов нет (agent #22 и api #31 ещё не реализованы) — ожидание сигнала'
while :; do
sleep 3600 &
wait $!
done
+8
View File
@@ -8,5 +8,13 @@ httpx>=0.27
structlog>=24.4 structlog>=24.4
prometheus-client>=0.21 prometheus-client>=0.21
# Транзитивные зависимости с открытыми HIGH — пиним снизу под гейт image-scan (issue #61):
# urllib3 2.7.0 → CVE-2026-97687 (MITM через HTTPS-прокси) / CVE-2026-97689 (DoS), fix 2.8.0
# msgpack 1.1.2 → CVE-2026-114613 / GHSA-6v7p-g79w-8964, fix 1.2.3
# setuptools 70.3.0 → CVE-2025-47273 (path traversal), fix 78.1.1
urllib3>=2.8.0
msgpack>=1.2.3
setuptools>=78.1.1
# Сетевой слой вызывается через бинари (awg, iptables, ipset, dnsmasq), # Сетевой слой вызывается через бинари (awg, iptables, ipset, dnsmasq),
# поэтому python-зависимостей для сети не требуется. # поэтому python-зависимостей для сети не требуется.