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

Блог ·

Канарка, що працює проти справжніх серверів

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

Автор: Provibr

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

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

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

Чим тут є канарка

Канарка є невеликим набором проб, що працюють проти справжньої речі, а не проти її моделі. Одноразові контейнери на нашому власному сервері, привʼязані до localhost, зі справжніми PostgreSQL 17, Redis 7, MySQL 8.4 та MariaDB 11 усередині. Адаптери викликаються напряму, а не через конверт команд, бо предметом є дріт, а не сантехніка навколо нього. Одинадцять проб, усі типово пропущені, і кожна відмовляється працювати, доки її адреса справді не задана.

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

Що покривають одинадцять:

СерверЩо доведено
PostgreSQL 17SCRAM із правильним і з хибним паролем, md5, trust, вимога TLS проти сервера з вимкненим TLS, повна перевірка сертифіката проти чужого якоря
Redis 7Автентифікація паролем і користувач з ACL, а також дозвільний перелік INFO
MySQL 8.4caching_sha2 на холодному й на теплому кеші, а також хибний пароль
MariaDB 11.8Те саме рукостискання проти іншої його реалізації

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

Два дефекти, яких жодна заглушка показати не могла

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

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

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

Виправленням є два окремі виклики INFO, а не один виклик, що називає обидва розділи, бо Redis приймає кілька розділів в одному INFO лише від версії 7.0. На 6.x це синтаксична помилка, а що невдалий INFO навмисно не робить перевірку червоною, поле просто знову лишилося б порожнім на старіших серверах. Тихе погіршення на заміну тихому погіршенню.

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

MySQL і бібліотека, яка не додалася

Той самий раунд додав MySQL і MariaDB як тип перевірки. За ним немає клієнтської бібліотеки, і це свідоме повторення давнішого рішення, а не нове рішення.

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

MySQL такого кроку не має. Рідна автентифікація паролем є ланцюгом дайджестів SHA1, а швидкий шлях caching_sha2_password є ланцюгом дайджестів SHA256, і в жодному з них сервер не підписує нічого, чию перевірку ми могли б проминути. Лишаються кадрування й два дайджести, а обидві бібліотеки дайджестів уже стояли в дереві. Будівник запитів теж не знадобився: є один запит, SELECT 1, він стоїть сталою, і його очікувана відповідь теж.

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

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

Сторінка стану рахує тіки, а не результати

Той самий раунд поставив перед усім цим публічну сторінку стану, і арифметика на ній вимагала рішення, яке легко зробити хибно.

Кожне належне виконання правила є тіком. Тік може завершитися як up, degraded або down, а ще він може завершитися як source_unavailable (нашого власного джерела вимірювання не було), configuration_error (правило не мало сенсу), unsupported (агент не знав того типу перевірки) або як пропущений. Ці останні чотири нічого не кажуть про службу клієнта.

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

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

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

Чого нам коштував зелений набір тестів

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

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

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

Читати далі

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

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