Блог ·
Зонд, який неможливо ні на що вмовити
Моніторинг ззовні мережі клієнта означає власні машини в публічному інтернеті. Ось як платформа не дає жодній із них робити щось, крім вимірювань.
Автор: 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/10 | Carrier 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 спершу розгортають, інакше в кожного правила вище є написання, яке проходить повз нього.
Перевірка виконується у трьох місцях, і потрібні всі три. Платформа відмовляє перед надсиланням, тож погана ціль ніколи не доходить до машини, яка могла б її виконати. Зонд відмовляє під час виконання, бо лише там відомо, у що імʼя насправді розвʼязалося, а це може відрізнятися від запуску до запуску. І платформа читає повідомлену адресу ще раз, коли приходить результат, перетворюючи зонд, який виміряв щось інше, на недійсну відповідь, а не на точку даних. Приватне призначення є помилкою конфігурації й ніколи не збоєм: нічого не виміряно, тож про ціль нема чого сказати.
Що ми записали б
- Ніколи не дозволяйте клієнтові називати власний вид. Виводьте його з того, що видали ви, і тримайте обидві відмови однаковими, щоб помилка не була оракулом.
- Перелічуйте весь протокол, а не дозволену частину. Хай тест читає згенерований код і вимагає вердикт на кожне поле в обох напрямках, щоб новий тип повідомлення був червоним тестом, а не прогалиною.
- Ставте справжню межу там, де про неї не можна забути. Окремий реєстр без ідентифікатора агента означає, що маршруту немає, хай там що каже таблиця.
- Перевіряйте навантаження так само, як конверт. «Команди дозволені» не є тим самим твердженням, що й «ця команда дозволена».
- Тримайте ключ призначення вільним від годинника, і хай перемикання на резерв буде додатковою спробою, а не додатковим заміром, щоб зміну в даних ніколи не можна було сплутати зі зміною в інструменті.
Заради ясності: хостований моніторинг лежить за прапорцем розгортання, який типово вимкнено, під ним немає шару прав, і сьогодні нічого з цього не купити. Те, що не доведено, теж заслуговує на імʼя. Підкоманда зонда ніколи не працювала наскрізь, бо один із її мережевих компонентів не збирається на машині, де це писалося; політики локацій випробувано лише в найпростішій формі; IPv6 ніде не міряно. Сам дріт проганяли по-справжньому, від зарахування через сесію до перевірки, яка повернулася вгору, і заборонене повідомлення на тому зʼєднанні було відхилено.
Із роботи над моніторингом у Provibr. Чому імʼя хоста треба довести, перш ніж воно стане ціллю, є в доведенні права на імʼя хоста, а чому інцидент не закривається лише тому, що вимірювання припинилися, є в інциденті, який не закривається на мовчанні.