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

Блог ·

Межа належить ядру

Відкликання розміщеного агента відчіплювало його від замовлення і лишало запущену машину Proxmox, до якої ніхто не міг дістатися, а платити не переставали.

Автор: Provibr

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

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

Для Cloud Agent це не є правдою, і ніщо цьому не заважало.

Що ми зміряли, перш ніж будь-що змінювати

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

Він відзвітував mode: "hard". Рядок агента зник. Рядок замовлення досі був там, досі active, досі ніс vmid 5998, а його agent_id тихо став NULL, бо зовнішній ключ від замовлення до агента є ON DELETE SET NULL.

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

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

Дві наявні сторожі, обидві дивляться не туди

Видалення агента вже мало дві відмови, і варто бути точним щодо того, чому не спрацювала жодна.

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

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

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

Межа йде в ядро, а не в кнопку

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

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

Тож межа сидить усередині самих revokeAgentCore() та deleteAgentCore(), у вузькому місці, крізь яке проходять усі сім викликів. Одна функція, спитана двічі.

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

Присудком є чи це ще можна скасувати, а не чи це живе

У спільному пакеті є список під назвою CLOUD_AGENT_LIVE_STATUSES: стани, у яких машина існує і коштує грошей. Він має чотири члени і є саме тим списком, який тут не слід перевикористовувати.

Присудком, яким ми користуємося, є cloudAgentActionAllowed("cancel", status), що є правдивим для семи з восьми станів замовлення, для всіх, крім замовлення, яке вже скасовано.

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

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

Два шляхи запису, яким дозволено її оминути

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

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

Два факти на екрані, а не один

Панельний бік потребував двох окремих булевих значень, і ми ледь не випустили одне.

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

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

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

  1. Схожість в інтерфейсі не є схожістю в наслідках. Два агенти, одна кнопка, один з них коштує грошей після того, як ви її натиснули.
  2. ON DELETE SET NULL може бути правильним і все одно лишити вас із сиротою. Схема не може поставити те питання.
  3. Сторожі про те, що видалення руйнує, не побачать того, що видалення полишає.
  4. Полічіть місця виклику, перш ніж обирати, куди йде перевірка. Наших було сім, у трьох файлах, два з них маршрути API.
  5. Ставте межу у вузькому місці, а не на кнопці. Кнопка є одним із входів.
  6. Ставте її після ідемпотентної гілки. Відмова на виклику, що нічого не зробив би, є брехнею.
  7. Перевикористання списку, що означає майже те саме, є повільною невдачею. Коштує грошей і ще можна скасувати різняться трьома станами, і саме в різниці лежить шкода.
  8. Стан, що не коштує нічого, все одно міг щось по собі лишити. Невдале розгортання є небезпечним.
  9. Розділяйте факт про обʼєкт і факт про глядача. Злиті, вони вкладають руйнівну кнопку в руки обмеженого учасника.
  10. Хай сторожа читає дерево. Тест на кожен відомий вхід не побачить входу, який хтось додасть наступного місяця.

Для ясності про те, чим це є: дефект відтворено проти бази даних розробки перед будь-якою зміною, наскрізна перевірка завершилася на 26 зелених і перед тим була доведена червоною на 16 зелених та 10 невдачах, браузерна перевірка на 29 зелених і доведена червоною на 18 та 11 з вимкненим прапорцем замовлення, девʼять мов на 178 зелених, а перевірка злиття пройшла без рядка невдачі. Не доведено: жодної справжньої клієнтської машини задіяно не було, а бік рахунків радше оглянуто, ніж випробувано. Три пастки вимірювання коштували часу і варті згадки: небезпечна зона живе за параметром вкладки, тож перевірка, що його оминає, міряє нуль кнопок і читає це як успіх; зворотний слеш усередині шаблонного літерала, переданого в обчислення на сторінці, не є екрануванням; а один із трьох каталогових скриптів сплющив нерозривний пробіл, який двоє інших зберегли.

Із платформної роботи над Provibr. Чому машину можна продати рівно один раз, описано в Два замовлення, одна машина, чому ярлик є посиланням і ніколи не дозволом, описано в Ярлик є посиланням, а не правом, а слово стану, позичене з не тієї таблиці, описано в Сканування, що закривало власне вікно.

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

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