Один лабораторний сервер, три ролі довіри, один домен відмови
Точці входу VPN, приватним сервісам і завданням CI потрібні різні права навіть на одному сервері. Розділіть ролі, ізолюйте мережі й збережіть відновлення через консоль.
Усе працює, доки збирачеві не знадобиться той самий сокет хоста, який використовує гість VPN, і тоді ніхто не може пояснити, чому завдання може читати ключі шлюзу. Усе поставили на одну машину, бо так уміщалося, а кожному гостеві дали широкий доступ, бо це був найшвидший спосіб усе заробити. Результат — хост, у якого межа довіри це кабель живлення. Розділення ролей потребує менше роботи, ніж здається, і починається з їхнього називання.
Три ролі, які зараз ділять усе
Точка входу VPN, набір приватних сервісів і збирач хочуть різного. Першій потрібен публічний порт і вузька мережа на приватному боці. Другому потрібне постійне сховище й секрети застосунку. Третій виконує код, який написав хтось інший.
Саме однаковий доступ для всіх трьох робить симптом заплутаним, бо тоді кожен із них може впливати на інших. Завдання збирання, яке може дістатися сокета гіпервізора, — це завдання, яке може зупинити VPN. Гість із сервісами, у якого є маршрут у мережу керування, може змінити конфігурацію комутатора. Проблема в правах, а список процесів — лише місце, де це видно. Якщо спершу назвати ролі, кожне право отримує своє місце.
Схема нижче — відправна точка для такого розділення, а не обіцянка високої доступності. Сприймайте її як гіпотезу для перевірки, а не як готовий проєкт.
Розділяйте три ролі, а не три процеси
Три гості на одному хості дають три набори прав і три мережеві положення. Вони не дають три домени відмови, і проєкт має сказати це вголос.
Розділення допомагає лише тоді, коли компрометація одного гостя не дає доступу іншим, а це налаштування гіпервізора, а не угода про імена. Назвати віртуальну машину шлюзом не заважає іншому гостеві дістатися хоста. Читайте конфігурацію віртуального комутатора, а не ім'я хоста, і потім підтвердьте, що кожен гість стоїть там, де ви думаєте.
host
├─ vpn-gw 10.60.10.2/24 public 51820/udp only
├─ services 10.60.0.10/24 no route to the management network
└─ ci-runner 10.60.20.2/24 outbound only, no host socket
Запишіть топологію, а потім перевірте її інструментами, які нею володіють. Гість, що з'явився не на тому мосту, — це знахідка, а не подробиця. Виправте мережу, перш ніж змінювати конфігурацію будь-якого сервісу.
Нехай гість VPN відчиняє лише власні двері
Опублікуйте порт тунелю на маршрутизаторі й більше нічого. Порти адміністрування гостей належать до шляху керування, а не до інтернету, і інтерфейс самого гіпервізора теж там.
Мережа на приватному боці має діставатися сервісів, потрібних віддаленим користувачам, і на цьому зупинятися. Автентифікація застосунку лишається ввімкненою, бо тунель — це транспорт, а не вхід. Тримайте таблицю маршрутів гостя VPN настільки малою, щоб описати її одним реченням. Якщо не можете описати її так просто, межа вже розмилася.
virsh net-list --all
virsh net-dumpxml lab-private
sudo nft list ruleset | grep -n -E '51820|10.60'
Усе, що слухає на хості й не потрібне для керування, не повинно слухати взагалі. Це стосується API гіпервізора, який легко залишити відкритим на публічному інтерфейсі.
Завданням збирання потрібна інша межа
Завдання CI виконує команди, якими керує репозиторій, тож його довіра залежить від того, хто може надсилати код. Оболонковий виконавець запускає ці команди від користувача раннера на хості, а це найширша доступна межа.
Контейнерні виконавці вужчі, і привілейований контейнер не з вузьких. Тримайте раннер в окремому гостеві з власною мережевою областю і дайте йому лише доступ до артефактів і розгортання, потрібний завданням. Одноразові працівники зменшують збережений стан; вони не зменшують досяжність чи схильність до викриття секретів. І те, і те досі потребує явних обмежень.
Гість із сервісами володіє даними, тож саме його план резервного копіювання має працювати. Тримайте репозиторії, завантаження й базу даних у одному розкладі. Перевірте відновлення, а не читайте статус завдання.
Плануйте відмову, яку ділять усі троє
Один хост — це один домен відмови. Перезавантаження прибирає тунель і всі приватні сервіси водночас, і саме тоді шлях усередину важить найбільше.
Зберігайте доступ до консолі, що не залежить від гостя VPN, чи то фізична консоль, інтерфейс керування поза смугою або консоль провайдера. Знімок, збережений на тому самому диску, не є незалежною копією, як і резервна копія, що виконується всередині гостя, якого вона захищає. Потім перевірте холодний запуск як послідовність, а не як список: хост, віртуальні мережі, шлях тунелю і один названий застосунок під звичайним обліковим записом.
virsh list --all
systemctl is-active libvirtd
curl -sS -m 5 -o /dev/null -w '%{http_code}\n' https://10.60.0.10/
ss -ltnp | grep -E ':51820|:443'
Порядок запуску процесів нічого не доводить про маршрути й DNS, тож перевіряйте шлях, а не стани юнітів. Шлях — це те, що віддаленому користувачеві справді потрібно.