← Усі статті

DevDays

Від GitHub до робочого Transparent Gateway

17 жовтня 2026 р. · 8 хв читання

Ноутбук на робочому столі з’єднаний бірюзовим кабелем із 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.

Debian 13, версії Git і Docker та успішне клонування TransparentGateway з GitHub у терміналі.
Робоча копія проєкту вже на Debian. Для встановлення використовую публічний репозиторій на GitHub.

Готую конфігурацію

Для запуску потрібні параметри клієнтської мережі, адреса дозволеного проксі та, за потреби, логін із паролем. Щоб не розкладати ці значення вручну між .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.

Налаштування SOCKS5-проксі через tgctl.py configure і результат CONFIG_CHECK PASS.
Конфігурація пройшла перевірку. Проксі з кадру після тестування видалив — ці дані вже не працюють.

Запускаю 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 знадобиться для порівняння з клієнтським запитом, але не розкриє адресу проксі в кадрі.

Шість запущених сервісів Transparent Gateway, ACTIVE_POOL ready=1 і відбиток адреси активного проксі.
Проксі вже в активному пулі. Його відбиток порівняю з результатом клієнтського запиту.

Світло перевірило 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=0649753926f0
  • CLIENT_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.
Клієнтський маршрут через 192.168.91.1 та успішні перевірки DNS і HTTPS із PUBLIC_EXIT id.
Відбиток PUBLIC_EXIT збігся з active_exit_ids на попередньому кадрі: запит вийшов через активний проксі.

Коли потрібно зупинити 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 ще не перетворився на чарівну кнопку. Зате тепер його може запустити не лише людина, яка пам’ятає всі попередні експерименти. А це вже значно цікавіше, ніж ще один зелений статус контейнера.