Перейти до вмісту

Блог ·

Чому не варто прокидати порт до панелі

Агенту, що дзвонить назовні, потрібні два вихідні правила і жодного вхідного порту. Що це дає, чого коштує і які чотири проблеми лише переміщує.

Автор: Provibr

Якщо платформа керування збирається керувати вашими гіпервізорами, щось має перетнути межу між їхньою мережею та вашою. Напрямків на вибір два, і цей вибір визначає майже все інше в моделі безпеки.

Традиційний напрямок це всередину. Платформа тримає облікові дані до вашого кластера Proxmox, вашої машини з DirectAdmin і вашої ігрової панелі та зʼєднується з кожною з них через інтернет. Це означає API, що відповідає в інтернет, правило фаєрвола чи VPN і площину керування, доступну кожному, хто зможе її знайти.

Інший напрямок це назовні. Ви запускаєте маленького агента всередині власної мережі. Він відкриває одне зʼєднання до платформи й тримає його відкритим, команди їдуть униз цим зʼєднанням, а результати повертаються вгору ним. На вашому боці ніщо не слухає.

Ця стаття про те, чого другий варіант коштує і що він дає, з досвіду побудови такого.

Зміна у фаєрволі це два правила, обидва вихідні

Уся мережева вимога вихідного агента вміщується в таблицю:

НапрямокПравилоКоли
ВихіднийTCP 50051 до шлюзу платформиОдин раз, під час реєстрації
ВихіднийTCP 50052 до того самого хостаПостійно, це сесія
ВхіднийНемає

Два порти замість одного це свідомий поділ. Реєстрація відбувається до того, як в агента зʼявиться клієнтський сертифікат, тож той порт може запропонувати лише TLS на боці сервера. Порт сесії вимагає взаємного TLS і відхиляє все без дійсного клієнтського сертифіката. Один слухач на обидва означав би слухача, у якого клієнтський сертифікат необовʼязковий, а рішення про авторизацію умовне. Два роблять межу структурною: на 50052 немає шляху в коді, на якому неавтентифікований вузол пройшов би рукостискання.

Якщо у вас сувора фільтрація вихідного трафіку, ті два правила це весь запит. Немає вхідного правила, яке треба написати, немає запису NAT, немає концентратора VPN і немає нічого, що треба поновлювати, коли змінюється ваша публічна адреса.

Що означає «назовні», коли мережа ворожа

Вихідне TCP-зʼєднання крізь NAT не безкоштовне: стан, який змушує його працювати, живе в чужій таблиці, а в тієї таблиці є тайм-аут. Операторський NAT, корпоративний фаєрвол і хмарний балансувальник усі тихо забувають бездіяльне зʼєднання, і режим відмови тут найгіршого штибу, бо обидва кінці досі вважають, що із сокетом усе гаразд.

З цим справляються два механізми, і потрібні обидва.

Перший це keepalive HTTP/2, з інтервалом 30 секунд і тайм-аутом 10 секунд. Це той шар, що перемагає тайм-аути NAT і балансувальників, і єдиний, який помічає TCP-зʼєднання, що тихо померло під потоком.

Другий це прикладне серцебиття кожні 20 секунд зі сторожовим таймером, який завершує сесію, якщо підтвердження не приходить протягом 45 секунд. Keepalive доводить, що сокет живий; серцебиття доводить, що інший кінець досі обробляє.

Коли сесія все ж завершується, агент перепідключається з експоненційною витримкою і повним джитером: очікування береться рівномірно з проміжку від нуля до поточної межі, яка подвоюється від однієї секунди до шістдесяти. Джитер тут не оздоблення. Без нього кожен агент кожного клієнта перепідключається в ту саму мить після перезапуску шлюзу, і ваше відновлення перетворюється на тисняву, яку ви влаштували собі самі.

Одна деталь, що проявляється лише в продакшні: лічильник витримки скидається аж після того, як сесія була здоровою цілу хвилину. Без цього правила агент із наполовину зламаним зʼєднанням підключається, помирає, скидає лічильник до однієї секунди й гатить у шлюз раз на секунду вічно.

Ідентичність: сертифікат, а не пароль

Вхідна модель автентифікує платформу перед вашою панеллю, зазвичай ключем API, який ви вставляєте у форму. Вихідна модель має автентифікувати агента перед платформою, і вона може зробити це чимось кращим за спільний секрет.

Агент реєструється один раз одноразовим токеном: 36 випадкових байтів, 48 символів base64url, що зберігається на боці сервера лише як хеш SHA-256 і дійсний 72 години. Його споживають усередині транзакції, яка перевіряє наново умови, за якими його обрали, бо саме між читанням і записом пролізає подвійна реєстрація.

Назад приходить клієнтський сертифікат. Відтоді сесія автентифікована взаємно, а шлюз пінує точний вузол: він бере відбиток SHA-256 наданого сертифіката й шукає агента за цим відбитком ще до того, як агент сказав, ким він себе вважає. Ідентичність зафіксовано сертифікатом; перше повідомлення в потоці може її лише підтвердити.

Ротація йде за власним годинником агента: сертифікати живуть 90 днів, а поновлення починається на половині строку, на 45-й день. Це лишає 45 днів на повторні спроби, перш ніж щось опиниться під загрозою, а панель попереджає людину аж коли лишається 21 день, тож поновлення має падати три тижні, перш ніж когось потурбують. Кожна ротація породжує нову пару ключів: поновлення на тому самому ключі продовжує життя чомусь, що вже могло витекти.

Відкликання заслуговує на власне речення, бо чесна версія менш елегантна за схему. Немає ні CRL, ні OCSP. Відкликати агента це оновлення в базі даних, і підключену сесію звіряють із ним раз на хвилину. Та перевірка fail-closed, з бюджетом у три послідовні збої, тож гикавка бази даних не може тихо тримати відкликаного агента живим, а найгірший випадок обмежений і обчислюваний: шістдесят секунд помножити на три.

Рівно один раз, крізь зʼєднання, яке відпаде

Щойно ви приймаєте, що канал це довгоживучий потік ненадійним шляхом, семантика доставки перестає бути теоретичним клопотом. Платформа доставить команду ще раз після перепідключення, бо з її боку різниця між «агент її не отримав» і «результат не повернувся» невидима.

Тож агент памʼятає. Завершені команди тримаються на диску разом із результатами, за ключем ідентифікатора команди, в обмеженому LRU. Повтор завершеної команди повертає закешований результат і не виконує нічого. Повтор команди, яка ще виконується, ігнорується, бо відповідь уже в дорозі.

Саме на диску, і в цьому суть: кеш у памʼяті порожній рівно тоді, коли платформа пропонує все наново, а це одразу після перезапуску.

Зворотний напрямок дзеркальний. Події виходять через послідовну вихідну чергу; на перепідключенні платформа називає номер послідовності, який у неї є, і агент відтворює звідти. Черга обмежена, і коли з неї щось випадає, агент каже про це на наступному підключенні, оголошуючи найнижчу послідовність, яку ще тримає, мінус один, тож платформа перестає чекати на події, яких більше немає.

В обіцянці є одна чесна тріщина, і її варто назвати, а не ховати: якщо запис дедуплікаційного рядка на диск падає, результат усе одно надсилається. Альтернативою було б викинути результат завершеної команди заради збереження гарантії, а в найгіршому разі пізніша повторна доставка виконає її вдруге.

Облікові дані лишаються на вашому боці

Це та частина, що змінює форму злому.

У вхідній моделі платформа зберігає облікові дані, які дають контроль над вашою інфраструктурою, і саме те сховище є призом. У вихідній моделі облікові дані до ваших гіпервізорів і панелей живуть у зашифрованому сховищі на вашому власному хості, куди їх надіслали один раз, коли ви додавали інтеграцію.

Наше сховище використовує XChaCha20-Poly1305 з ключем, виведеним через HKDF-SHA256 з локального файла ключа та ідентифікатора машини, з ідентифікатором агента, вмішаним у контекст виведення. Наслідок варто проговорити: копіювання vault.enc на іншу машину дає вам файл, який не розшифрується, бо машина, до якої його привʼязано, є частиною ключа.

А межа, яку ми записали у вихідний код поруч із реалізацією, полягає в тому, що це не захищає від root на хості. Йому й не треба. Хост належить клієнтові, і облікові дані належать клієнтові. Сховище захищає від файла, що витік, а не від власника.

Слід аудиту сформовано тим самим правилом. Кожна команда це рядок, який ніколи не видаляється назавжди, а вміст надсилання облікових даних зберігається відредагованим. Таблиця аудиту, що містить секрети, які вона аудитує, це друга копія проблеми.

Чотири речі, яких це не розвʼязує

Стаття про архітектуру, що перелічує самі лише переваги, це маркетинг. Ось що переміщується, а не зникає.

Програмне забезпечення на вашому хості. Обмін реальний: ви більше не виставляєте порт, а натомість запускаєте бінарник, який треба встановити, оновлювати і якому треба довіряти. Це переносить питання з «хто може дістатися моєї панелі» на «хто підписує код, який я запускаю», і це краще питання, але не безкоштовне.

Широкі привілеї. Агент, що керує гіпервізором, тримає облікові дані, які керують гіпервізором. Напрямок зʼєднання нічого не змінює в тому, що ці дані вміють, лише в тому, де вони живуть і хто ще може до них дістатися.

Вихідне це все одно вихід. Зʼєднання, що залишає вашу мережу, може винести з неї дані. Призначення зафіксовано, транспорт автентифіковано взаємно, а протокол вузький, але «два вихідні правила» це менша заява, ніж «нічого не виходить».

Відповіді в реальному часі. Ця річ проявляється в API одразу. Оскільки платформа не може подзвонити агентові, запис не може відповісти своїм результатом: правильна відповідь на «створіть цю машину» це підтвердження й щось, за чим треба стежити. На читання це не впливає, бо воно приходить із власної бази даних платформи. Кожен, хто проєктує API поверх вихідного агента, має прийняти цю асиметрію, а не замазувати її.

Є також рівно один випадок, коли агент таки слухає, і вдавати інакше було б нечесно. Встановлення заліза через мережу означає відповідати на DHCP і роздавати файли, а цього не зробити з вихідного сокета. Тож агент піднімає ці служби, на одному названому інтерфейсі, лише для машин із завданням у роботі й лише поки є робота. «Увімкнено» означає «дозволено тут», а не «слухає зараз».

Коли вхідне все ще правильна відповідь

Якщо у вас один гіпервізор і ви керуєте ним самі, ніщо з цього вас не стосується: агент не дає нічого понад правило фаєрвола й надійний пароль.

Аргумент загострюється з відстанню й кількістю. Кілька майданчиків, мережі, якими ви не володієте, адреси, що змінюються, клієнти, чиї фаєрволи ви не можете редагувати, і площина керування, якої ви взагалі воліли б не мати в публічному інтернеті. У цій точці агент уже не додатковий компонент; це той компонент, який дозволяє видалити решту.

Саме так працює Provibr: одне вихідне зʼєднання на агента, жодного вхідного порту й облікові дані, що ніколи не залишають мережу клієнта. Документація агента описує встановлення, розмір і відкликання, початок роботи проводить перше зʼєднання від початку до кінця, а API документує форму «підтвердити й опитувати», яку навʼязує вихідний дизайн.

Почніть з одного хоста й одного агента

Створіть обліковий запис, встановіть агента однією командою та підключіть свій перший хост Proxmox. Пробний період триває 14 днів і не потребує платіжних даних.