DevDays
Від GitHub до робочого Transparent Gateway
У попередній статті відтворив Transparent Gateway із чистого шаблону Proxmox. Це довело, що власний стенд можна зібрати заново без ручного ремонту. Але людина, яка відкриє репозиторій TransparentGateway, не отримає мій шаблон, мережеві мости й машину керування. Вона отримає GitHub і README.
Тому цього разу проходжу саме користувацький шлях: беру чистий Debian, додаю один дозволений проксі, запускаю gateway і проводжу через нього DNS та HTTPS-запит з окремої клієнтської машини.
Кінцевий маршрут виглядає так:
Показати код
клієнтська машина
→ Transparent Gateway
→ дозволений зовнішній проксі
→ потрібний сайтProxmox тут не є частиною рішення. Я використовую його лише тому, що на ньому працює мій домашній стенд. Gateway так само можна встановити на фізичний сервер або VM в іншому гіпервізорі.
Що потрібно підготувати
На gateway потрібен Linux із підтримкою iptables, TPROXY та policy routing. Для цієї перевірки використовую Debian 13 із двома мережевими інтерфейсами:
- перший підключений до мережі керування та має звичайний вихід в інтернет;
- другий дивиться у виділену клієнтську мережу
192.168.91.0/24; - адреса gateway у клієнтській мережі —
192.168.91.1.
Також потрібні Git, Python 3, Docker Engine, Docker Compose та один проксі.
Для фінальної перевірки використовую окрему клієнтську машину. Вона має лише один маршрут до інтернету — через 192.168.91.1, адресу Transparent Gateway. Якщо gateway не пропустить трафік, запит завершиться помилкою: піти напряму через інший шлюз він не зможе.
Забираю код із GitHub
На чистій Debian VM перевіряю Git і Docker, після чого клоную репозиторій:
Bash-код
git --version
docker --version
docker compose version
git clone https://github.com/Va1b0rt/TransparentGateway.git
cd TransparentGatewayУ корені лежить docker-compose.yml, а налаштування, запуск, зупинку та перегляд стану зібрав у Python-скрипті scripts/tgctl.py із цього ж репозиторію. Наступні команди виконую на машині, яка стане gateway, з каталогу TransparentGateway.

Готую конфігурацію
Для запуску потрібні параметри клієнтської мережі, адреса дозволеного проксі та, за потреби, логін із паролем. Щоб не розкладати ці значення вручну між .env, inventory та файлом із даними доступу, додав інтерактивне налаштування:
Bash-код
sudo python3 scripts/tgctl.py configureСкрипт поставить кілька запитань і створить робочі файли з потрібними правами. Контейнери та мережеві правила на цьому етапі він не запускає.
Що запитує configure
Перший запит стосується клієнтської мережі:
Показати код
Client IPv4 subnet routed through this gateway [192.168.91.0/24]:У моєму стенді клієнт має адресу 192.168.91.2, а Transparent Gateway — 192.168.91.1. Обидві адреси входять до мережі 192.168.91.0/24, саме її й просить скрипт. Тому залишаю запропоноване значення й натискаю Enter.
Далі скрипт просить параметри дозволеного проксі:
Показати код
Protocol specified by your proxy provider (http, https, socks4, socks5) [http]:
Authorised upstream proxy host or IP:
Proxy port [3128]:
Does the proxy require a username and password? [y/N]:Тут потрібно ввести дані реального зовнішнього проксі, через який Transparent Gateway випускатиме клієнтський трафік в інтернет.
Усі значення беру з налаштувань цього проксі:
Protocol— протокол, указаний провайдером:http,https,socks4абоsocks5.host or IP— доменне ім’я або IP-адреса проксі.port— порт проксі.- На запит про ім’я користувача та пароль відповідаю
y, мій проксі вимагає авторизацію, після чого вводжу видані логін і пароль. Для проксі без авторизації потрібно відповістиn.
Наприклад, для SOCKS5-проксі з авторизацією діалог виглядатиме так:
Показати код
Protocol specified by your proxy provider (http, https, socks4, socks5) [http]: socks5
Authorised upstream proxy host or IP: proxy.example.com
Proxy port [3128]: 1080
Does the proxy require a username and password? [y/N]: y
Proxy username: student
Proxy password:proxy.example.com, 1080 і student тут лише приклади. Пароль під час введення в терміналі не відображається.
Останнім з’являється echo-сервіс:
Показати код
Validation echo URL (press Enter to use the public first-run service) [https://echo.devdays.net.ua/]:Для першого запуску залишаю запропоновану адресу. Echo-сервіс повертає IP-адресу та заголовки отриманого запиту. Validator звертається до нього через проксі й дивиться, з якої адреси прийшов запит.
Після створення файлів запускаю безпечну перевірку:
Bash-код
sudo python3 scripts/tgctl.py checkЯкщо проксі потребує авторизації, у inventory/upstreams.jsonl поруч з адресою записується лише ідентифікатор credential_ref. Логін і пароль із таким ідентифікатором лежать у secrets/proxy-credentials.json.
Навіщо така морока з двома файлами? Список проксі проходить через увесь конвеєр: fetcher збирає адреси, validator перевіряє, чи працюють проксі й чи не видають адресу клієнта, ranker вибирає найкращі для активного пулу, а Redis передає результат між сервісами. Якби логін і пароль лежали поруч із кожною адресою, секрет мандрував би через увесь проєкт і рано чи пізно міг засвітитися в діагностичному виводі.
credential_ref обриває цю подорож. Конвеєр бачить лише умовну мітку. Сам пароль читає validator, коли авторизується на проксі під час перевірки, і gateway, коли відправляє через цей проксі клієнтський трафік. Іншим сервісам він не потрібен.
Це не сейф: обидва файли зберігаються на диску без шифрування, а root може їх прочитати. Нинішнє розділення лише не дає паролю роз’їжджати по всіх компонентах і готує проєкт до нормального сховища секретів. Зараз credential_ref веде до запису в JSON-файлі. Пізніше цей файл можна замінити на Vault, Docker Secrets або інше сховище, не перебудовуючи весь конвеєр. Зміниться тільки спосіб, яким validator і gateway отримують пароль.
Перевірка переконується, що для кожного credential_ref існує відповідний набір даних.
Команда також перевіряє права файлів, поля host і port, адресу echo-сервісу та підсумкову конфігурацію Docker Compose.
Очікуваний результат:
Показати код
CONFIG_CHECK PASS
connectors=1
candidates=1
credential_sets=1
compose=validДля проксі без авторизації нормально побачити credential_sets=0.

Запускаю gateway
Наступна команда збирає образи, запускає шість сервісів і чекає, поки validator перевірить проксі, а ranker додасть його до робочого пулу:
Bash-код
sudo python3 scripts/tgctl.py up --timeout 300--timeout 300 дає скрипту до п’яти хвилин на очікування проксі в активному пулі. Якщо той з’явиться раніше, очікування завершиться одразу. Якщо ні — команда повідомить про помилку, а запущені контейнери залишаться працювати. Побудова образів до цього очікування не входить.
На цьому кроці контейнер gateway отримує NET_ADMIN — дозвіл керувати мережею — і додає правила TG_* у iptables, щоб перехоплювати трафік клієнтської мережі.
Після першого циклу дивлюся компактний стан без повного JSON із Redis:
Bash-код
sudo python3 scripts/tgctl.py statusМені потрібні шість запущених сервісів і ACTIVE_POOL ready=1 — один перевірений проксі в активному пулі. generated_at показує час формування пулу: це кількість секунд від 1 січня 1970 року. Короткий active_exit_ids знадобиться для порівняння з клієнтським запитом, але не розкриє адресу проксі в кадрі.

Світло перевірило systemd краще за мене
Поки готував клієнтську перевірку, світло згасло, а tg-quickstart-01 перезавантажилася. Безкоштовний reboot-тест, який я сам чомусь не додав 😅
Після завантаження Docker працював, а шість контейнерів Transparent Gateway — ні. Контейнерами має керувати systemd. Проблема виявилася дитячою 🍼 — у tgctl.py up я забув установити саму systemd-службу. Поки машина не перезавантажувалася, я цього не помічав.
Додав установлення служби до тієї самої команди запуску. Тепер вона встановлює та вмикає службу systemd. Після перезавантаження служба дочекається Docker, запустить уже зібрані контейнери й перевірить появу активного проксі.
Повторив запуск уже з виправленим tgctl.py up, який установлює службу. Контейнери запрацювали, але клієнтська перевірка все одно спіткнулася на DNS:
Показати код
CLIENT_ROUTE PASS gateway=192.168.91.1 dev=eth1
CLIENT_CHECK FAIL: [Errno -2] Name or service not knownКлієнт використовував 192.168.91.1 як DNS-сервер. Правила gateway уже спрямовували запити до 1.1.1.1 і 1.0.0.1 через DNS-over-HTTPS, але не враховували запит до власної адреси. Він помилково потрапляв у загальний TCP-релей і завершувався 502 Bad Gateway.
Додав окреме правило: DNS-запит до адреси самого gateway тепер одразу переходить у DNS-over-HTTPS. Перша версія правила не запустилася, бо для REDIRECT --to-ports не вказав протокол. Додав -p udp, закріпив обидва виправлення тестами й повторно зібрав контейнер. Усі 49 тестів пройшли.
Після цього вже навмисно перезавантажив tg-quickstart-01. Цього разу нічого вручну не запускав: systemd підняв шість контейнерів, дочекався активного пулу, а клієнтська перевірка пройшла повністю. Тепер зникнення світла більше не є частиною процедури запуску.
Перевіряю маршрут
Проксі вже з’явився в активному пулі. Тепер проводжу через gateway запит від окремої клієнтської машини. Так перевіряю не лише сам проксі, а й перехоплення та пересилання клієнтського трафіку.
Спочатку передаю клієнту перевірочний скрипт із репозиторію та підключаюся до нього:
Bash-код
cd ~/TransparentGateway
scp scripts/client-check.py [email protected]:/tmp/tg-client-check.py
ssh [email protected]Адресу 192.168.88.115 використовую лише для входу в термінал tg-client-01. Наступні команди виконую вже на цій машині.
Спочатку дивлюся, куди клієнт надсилає звичайний інтернет-трафік:
Bash-код
ip -4 route show default
ip -4 route get 1.1.1.1Обидві команди мають указати 192.168.91.1 — адресу Transparent Gateway у клієнтській мережі. Якщо вони покажуть інший шлюз, наступний запит зможе оминути gateway.
Запускаю перевірку:
Bash-код
python3 /tmp/tg-client-check.py --gateway 192.168.91.1Скрипт перевіряє маршрут, знаходить публічний echo-сервіс через DNS і відкриває його через HTTPS без логіна, пароля чи адреси проксі.
Отримав такий результат:
Показати код
CLIENT_ROUTE PASS gateway=192.168.91.1 dev=eth1
CLIENT_DNS PASS addresses=2
CLIENT_HTTPS PASS request_id=61f8a968d5744c25a2a594bb43317362
PUBLIC_EXIT id=0649753926f0CLIENT_ROUTE PASSозначає, що маршрут правильний.gateway=192.168.91.1— наступна машина на шляху до інтернету, тобто Transparent Gateway.dev=eth1— мережевий інтерфейс клієнта, через який піде трафік.CLIENT_DNS PASS addresses=2означає, що DNS повернув дві різні IPv4-адреси публічного echo-сервісу. Самі адреси можуть змінюватися, тому скрипт показує лише їхню кількість. Для цієї перевірки важливо, що отримано хоча б одну.CLIENT_HTTPS PASSозначає, що echo-сервіс відповів через HTTPS і повернув надіслану йому міткуrequest_id. Скрипт створює її випадково перед кожним запитом, щоб не прийняти стару або чужу відповідь за свою.PUBLIC_EXIT id=0649753926f0— скорочений відбиток зовнішньої IP-адреси, яку побачив echo-сервіс. У статусі gateway вже єactive_exit_ids— відбитки зовнішніх адрес активних проксі. Echo-сервіс повернув адресу, з якої отримав клієнтський запит, а скрипт обчислив для неї такий самий відбиток.PUBLIC_EXIT idзбігся з одним ізactive_exit_ids, отже запит вийшов через проксі, який вибрав Transparent Gateway.

Коли потрібно зупинити gateway, на його машині запускаю:
Bash-код
sudo python3 scripts/tgctl.py downКоманда зупиняє службу та контейнери. Gateway прибирає правила перехоплення, але залишає блокування прямого пересилання клієнтського трафіку: після зупинки клієнт не отримає обхідний вихід в інтернет. Для повторного запуску використовую ту саму команду sudo python3 scripts/tgctl.py up --timeout 300.
Зі стенда виходить інструмент
Найприємніший результат цієї роботи — не чотири рядки з PASS. Уперше встановив TransparentGateway так, як це робитиме людина, яка просто знайшла репозиторій на GitHub.
Далі хочу зробити оновлення таким самим простим, як перший запуск. Одна команда має забрати свіжий код, перевірити наявні налаштування та перезапустити сервіси.
Потім займуся діагностикою. Зараз помилка часто означає лише «gateway не працює». Натомість хочу одразу бачити причину: не відповідає DNS, у пулі немає придатних проксі, relay не приймає з’єднання або зовнішній проксі відмовив у запиті.
Далі з’являться метрики й сповіщення про стан пулу. І лише потім є сенс братися за кілька gateway та відмову цілого хоста. Хочеться одразу малювати кластери й високу доступність, але спочатку один екземпляр має стати нудним і передбачуваним. Для інфраструктури це, мабуть, найкращий комплімент.
TransparentGateway ще не перетворився на чарівну кнопку. Зате тепер його може запустити не лише людина, яка пам’ятає всі попередні експерименти. А це вже значно цікавіше, ніж ще один зелений статус контейнера.