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

Блог ·

Зонд, який неможливо ні на що вмовити

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

Автор: Provibr

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

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

Один бінарник, і хто ви такий, вирішує токен

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

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

Тож такого поля немає. Є дві таблиці токенів: enrollment_tokens із зовнішнім ключем not null на agents і monitoring_probe_enrollment_tokens із таким самим на monitoring_probes. Токен існує рівно в одній із них, тож те, що саме зараховується, випливає з виданого нами, а не зі сказаного тим, хто викликає. Обидві невдачі повертають ідентичну відмову, бо різні повідомлення сказали б зловмисникові, який саме вид токена він знайшов.

Сертифікат, що повертається, несе загальне імʼя у формі probe:<id> і, як альтернативне імʼя субʼєкта, URI без організації всередині, тоді як в агента таке імʼя називає клієнта. Це для оператора, який читає сертифікат, і це явно не є межею: загальне імʼя є рядком, який записує центр сертифікації. Межа полягає в тому, що відбиток розвʼязується в agents або в monitoring_probes і ніколи в обох, бо це дві таблиці.

Вердикт на кожне повідомлення, а не список дозволених

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

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

ПовідомленняЩо воно несло бЧому зонд не може побачити його ніколи
Надсилання облікових данихЛогіни клієнта до панелі та гіпервізораУ зонда немає ні сховища, ні інтеграції; немає стану, в якому це щось означало б
Стирання облікових данихВказівку стерти одні з нихТа сама причина, у зворотний бік
Директива netbootЩо віддавати машині, яка завантажується з мережіЗонд нічого не провізіонує
Відкриття автоматизаціїПісочницю, що виконує скрипти клієнтаЗонд не виконує коду клієнта

Реєстр, у якому зонда немає

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

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

Вибір зонда й перемикання на наступний

Кожна локація може тримати більш ніж один зонд, тож щось мусить вирішувати. Хеш за модулем кількості зондів є неправильним того дня, коли ця кількість змінюється: майже всі правила переїжджають одночасно, і графіки затримки по всьому продукту стрибають того самого дня без жодної видимої причини. Rendezvous-хешування переносить лише те, що було привʼязане до зонда, який пішов. Ключем є ревізія правила й локація. Ревізія незмінна, тож правило, яке клієнт відредагував, може законно приземлитися деінде, тоді як вибір усередині однієї ревізії ніколи не пливе. Слот навмисно не входить у ключ: із номером тіка всередині послідовні вимірювання приземлялися б на різні зонди, а різниця між двома точками з тим самим успіхом могла б бути різницею між двома машинами. Порівнюваність у часі коштує більше, ніж розподіл навантаження.

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

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

Три місця, які відмовляють приватній адресі

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

ДіапазонЧому він закритий
10/8, 172.16/12, 192.168/16Приватний простір. На хостованому зонді це центр обробки даних, а не клієнт
100.64/10Carrier grade NAT, де провайдери вішають балансувальники навантаження й керівні шляхи
192.0.0/24, 192.0.2/24, 198.18/15, 198.51.100/24, 203.0.113/24, 192.88.99/24Призначення протоколу, тестування продуктивності, документація, застарілий anycast для ретрансляції. Жоден із них не може бути машиною клієнта, тож усе, що відповідає, є ближчим за ціль
fc00::/7, 2001:db8::/32Перший і третій рядки вище, в IPv6: унікальний локальний простір і простір документації

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

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

Що ми записали б

  1. Ніколи не дозволяйте клієнтові називати власний вид. Виводьте його з того, що видали ви, і тримайте обидві відмови однаковими, щоб помилка не була оракулом.
  2. Перелічуйте весь протокол, а не дозволену частину. Хай тест читає згенерований код і вимагає вердикт на кожне поле в обох напрямках, щоб новий тип повідомлення був червоним тестом, а не прогалиною.
  3. Ставте справжню межу там, де про неї не можна забути. Окремий реєстр без ідентифікатора агента означає, що маршруту немає, хай там що каже таблиця.
  4. Перевіряйте навантаження так само, як конверт. «Команди дозволені» не є тим самим твердженням, що й «ця команда дозволена».
  5. Тримайте ключ призначення вільним від годинника, і хай перемикання на резерв буде додатковою спробою, а не додатковим заміром, щоб зміну в даних ніколи не можна було сплутати зі зміною в інструменті.

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

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

Читати далі

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

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