Сервер без root
Этот профиль предназначен для обычных TCP/UDP входящих VPN-сервиса. TUN, прозрачный прокси, настройка маршрутов хоста и raw ICMP здесь недоступны. Существующие citrus.service, citrus@.service и основной Dockerfile сохраняют свои настройки для таких режимов.
systemd
Заголовок раздела «systemd»Пакеты включают citrus-server.service, описание пользователя в /usr/lib/sysusers.d/citrus-server.conf и шаблон /usr/share/citrus/server-config.yaml. Профиль включается отдельно; установка пакета его не запускает. После установки пакета:
sudo systemd-sysusers /usr/lib/sysusers.d/citrus-server.confsudo install -d -o root -g citrus-server -m 0750 /etc/citrus-serversudo install -o root -g citrus-server -m 0640 /usr/share/citrus/server-config.yaml /etc/citrus-server/config.yamlsudoedit /etc/citrus-server/config.yaml# Добавьте авторизованные входящие и сертификаты до открытия портов.sudo systemctl daemon-reloadsudo systemctl enable --now citrus-serverjournalctl -u citrus-server -fПри установке из исходников сначала установите бинарник в /usr/bin/citrus, юнит из .github/release/citrus-server.service в /usr/lib/systemd/system/, sysusers-файл в /usr/lib/sysusers.d/citrus-server.conf, а шаблон docker/server-config.yaml в /usr/share/citrus/server-config.yaml.
Ядро работает от citrus-server, имеет только CAP_NET_BIND_SERVICE для портов ниже 1024 и не может повышать привилегии. Для портов от 1024 можно убрать и эту capability через systemctl edit citrus-server:
[Service]CapabilityBoundingSet=AmbientCapabilities=Конфиг, сертификаты и приватные ключи храните в /etc/citrus-server/: владелец root:citrus-server, каталоги 0750, файлы 0640. Все промежуточные каталоги тоже должны быть доступны группе. Относительные пути считаются от /var/lib/citrus-server; для ключей используйте абсолютные пути. SAFE_PATHS=/etc/citrus-server разрешает ядру читать этот каталог, а sandbox systemd оставляет его только для чтения.
Кэш, geodata, providers, UI и прочие обновляемые файлы размещайте в /var/lib/citrus-server/ (параметр -d). systemd создаёт его с владельцем citrus-server и режимом 0700; каталог /run/citrus-server/ подходит для runtime-сокетов. Изменение конфига через API, который записывает файл конфигурации, не поддерживается: редактируйте файл от администратора и выполняйте sudo systemctl reload citrus-server. API управления пользователями в памяти не требует записи конфига. Не запускайте два профиля на одних портах и не делите между ними каталог данных.
Обновляйте бинарник через пакетный менеджер, а контейнер — заменой образа. Права профиля не позволяют самообновлению ядра заменить исполняемый файл.
Отдельный Dockerfile.server использует те же артефакты bin/, что и основной Dockerfile: bin/version.txt и сжатый Linux-бинарник с соответствующим именем (см. docker/file-name.sh). Он не публикуется автоматически под отдельным тегом. Сборка для текущей платформы:
docker build -f Dockerfile.server -t citrus-server:local .Образ работает с UID/GID 65532:65532, не содержит iptables и не скачивает geodata при сборке. Встроенный шаблон не открывает слушающие порты; добавьте свои авторизованные входящие. Перед запуском подготовьте каталог ./server-config, доступный этому UID, с конфигом config.yaml и ключами. Для секретов достаточно каталогов 0750 и файлов 0640 с группой 65532; на системах с SELinux отдельно настройте метки доступа.
docker volume create citrus-server-datadocker run -d --name citrus-server --restart unless-stopped \ --read-only --cap-drop ALL --security-opt no-new-privileges \ --tmpfs /tmp:rw,noexec,nosuid,size=16m \ --mount type=bind,src="$(pwd)/server-config",dst=/etc/citrus,readonly \ --mount type=volume,src=citrus-server-data,dst=/var/lib/citrus \ -p 443:10443/tcp -p 443:10443/udp citrus-server:localВ этом примере входящий слушает порт 10443 внутри контейнера, а Docker публикует 443 на хосте; ни одной capability процессу не требуется. Публикуйте только нужные вашему протоколу TCP/UDP порты. TLS-ключи задавайте абсолютными путями в /etc/citrus, изменяемые файлы — в /var/lib/citrus. Новый named volume получает владельца 65532 из образа. Для существующего volume или bind mount каталога данных заранее выставьте владельца 65532 и режим 0700; образ не меняет владельцев при старте. Обновления geodata, если они нужны конфигу, скачиваются в каталог данных при работе ядра.
Защита исходящего трафика
Заголовок раздела «Защита исходящего трафика»Пользователи серверных входящих (vless, trojan, shadowsocks, snell, tuic, hysteria2, anytls, mieru, sudoku, trusttunnel, shadowquic) не могут попасть через DIRECT в сеть самого сервера. Защита включена по умолчанию. Она не пускает:
- на loopback, частные, link-local, CGNAT (
100.64.0.0/10), ULA, multicast, неуказанные и зарезервированные адреса; - к metadata облака:
169.254.169.254,fd00:ec2::254,100.100.100.200,192.0.0.192; - на адреса сетевых интерфейсов этого хоста, где слушают контроллер, входящие и все остальные локальные сервисы.
Проверяются IP-адреса и адреса, в которые разрешился домен. Проверка идёт прямо перед соединением, поэтому домен не может после правил перепривязаться (DNS rebinding) на адрес в LAN. Защита работает для TCP и UDP. UDP-датаграмма на запрещённый адрес отбрасывается, как и датаграмма, пришедшая с такого адреса. Проверка выполняется внутри DIRECT после правил, поэтому правило вида IP-CIDR,10.0.0.0/8,DIRECT её не отключает. Прокси, кроме DIRECT, не проверяются: к адресу назначения подключается их сервер.
Заблокированное соединение попадает в журнал как предупреждение:
[TCP] dial DIRECT (match Match/) 198.51.100.7:40000 --> 127.0.0.1:9090 error: egress guard blocked 127.0.0.1: private, loopback, link-local, reserved or cloud metadata address, not reachable from inbound vless-in (user alice) (allow it with egress-guard.allow-ips, or egress-guard: false on the listener)Если пользователям нужен доступ к чему-то конкретному, например к базе данных в LAN, добавьте это в allow-ips. Каждая запись — CIDR, одиночный адрес или адрес:порт. Запись с портом открывает только этот порт. Так можно открыть сайт на публичном адресе этого хоста, а контроллер и остальные порты хоста останутся закрытыми:
egress-guard: enable: true # по умолчанию allow-ips: [10.8.0.0/24, 192.168.1.10, 203.0.113.10:443]После перезагрузки конфига новые настройки сразу действуют и на уже открытые UDP-сессии.
У каждого listener есть свой egress-guard, он переопределяет enable для этого listener:
falseоткрывает пользователям доверенного серверного listener доступ к LAN и этому хосту;trueвключает защиту и на клиентском listener, например наsocks-сервере для удалённых пользователей.
Локальные клиентские входящие (http, socks, mixed, redir, tproxy, tun, tunnel) по умолчанию не защищаются, поэтому клиентский конфиг работает как раньше.
Публичный адрес хоста за NAT, которого нет на интерфейсе, защите неизвестен. Контроллер в любом случае держите на 127.0.0.1 или на unix-сокете.
Проверки
Заголовок раздела «Проверки»CI запускает настоящий бинарник в обоих профилях. Для systemd проверяются UID, effective capabilities, запрет повышения привилегий, доступность каталога данных и отсутствие TUN. Docker проверяется с файловой системой только для чтения, нулевыми capabilities, записью в отдельный volume и реальным HTTP-прокси: запрос с паролем проходит, без него получает 407. Локально Docker-проверку можно повторить: bash .github/scripts/test-server-profile.sh /path/to/linux-amd64-citrus.
Параметры sandbox описаны в systemd.exec, формат пользователя — в sysusers.d.
