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

Блог ·

Непривілейованість вимірюють, а не задають

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

Автор: Provibr

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

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

Два endpoint, які мають схожий вигляд

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

Віртуальна машинаКонтейнер
EndpointPOST /nodes/{node}/qemuPOST /nodes/{node}/lxc
Архівarchive=<volid>ostemplate=<volid> плюс restore=1
Назваname=hostname=
Цільове сховище мусить прийматиimagesrootdir
Тип завдання в ідентифікаторі завданняqmrestorevzrestore

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

Те, який із двох видів це є, вирішують один раз. Його зчитують з імені файла архіву в мить, коли архів потрапляє в перелік, зберігають разом із точкою відновлення й надсилають разом із вказівкою; агент обирає свій endpoint за тим словом і не має запасного шляху. Це мусить працювати саме в такому порядку, бо 404 від Proxmox не відрізняє гостя іншого виду від гостя, якого більше немає. Спроба звернутися до іншого endpoint після 404 дозволила б звертатися до видаленої віртуальної машини як до контейнера, а в кластері, де те число відтоді видали заново, зачепить не того гостя.

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

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

Три відмови, перш ніж щось буде записано

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

Для контейнера є четверта, і саме про неї ця стаття.

Три речі, які можна зробити з привілейованим архівом, і дві з них тихо неправильні

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

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

Другий це пропустити поле. Proxmox тоді бере значення з архіву, і привілейований контейнер приєднується до парку: такий, що ділить ядро хоста з правами root, а це саме те, чому політика має запобігати.

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

Читання конфігурації, що лежить усередині архіву

Вимірюванням є один виклик до endpoint видобування для архіву. Три деталі варто знати.

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

І відсутність ключа означає протилежне тому, що зазвичай означає відсутність. Proxmox записує рядок непривілейованості лише тоді, коли він істинний. Конфігурація без нього описує привілейований контейнер. Будь-який читач, який трактує «не знайдено» як «невідомо, мабуть, усе гаразд», пропускає рівно той випадок, задля якого перевірку й будували, тож порівняння є щедрим щодо того, що воно приймає за істину, і відмовляє всьому іншому.

Якщо конфігурацію взагалі не вдається прочитати, у відновленні теж відмовляють, і цитата з Proxmox їде назад разом із відмовою. Зазвичай це повідомлення про права, бо endpoint видобування хоче права на резервне копіювання гостя й права на розподіл на сховищі копій. Fail-closed тут має справжню ціну: токен API, якому бракує одного дозволу, не може відновити контейнер, і причиною є перевірка, а не обмеження. Це однаково правильний бік, на який падати, бо у відкритого питання за ним є дві тихо неправильні відповіді.

Те саме читання відповідає на друге питання

Конфігурація всередині архіву каже також, які точки монтування були його частиною, і правила Proxmox тут не ті, яких люди очікують:

Що каже конфігураціяВ архіві?Чому
Коренева файлова системаЗавждиУ неї взагалі немає опції резервного копіювання; поля немає в її схемі
Керований том з увімкненою опцією копіюванняТакУвімкнено явно
Керований том без неїНіОпцію треба увімкнути, щоб точка монтування потрапила в копію
Каталог із хостаНіВміст точок монтування bind mount не копіюють
Блоковий пристрій із хостаНіТе саме, для точок монтування пристроїв

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

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

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

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

Коли ніхто не може сказати, що сталося

Відновлення, яке почалося й потім загубило свою відповідь, бо агент перезапустився або зʼєднання обірвалося, не можна вгадувати. Джерел доказів три: перелік архівів, машина, яка мала зʼявитися, і стан завдання Proxmox. Лише третє може встановити невдачу; перші два можуть лише підтвердити успіх, бо відсутній архів і відсутня машина однаково добре узгоджуються з відновленням, яке ще триває.

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

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

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

  1. Не надсилайте значення для властивості, яка вже існує у вхідних даних. Прочитайте її, а потім або підтвердьте, або відмовте.
  2. Зʼясуйте, що означає відсутній ключ, перш ніж писати парсер. Тут він означає небезпечне значення, а це протилежне до звичної конвенції.
  3. Падайте закрито, коли вимірювання недоступне, і скажіть уголос, чого це коштує, бо хтось на це натрапить.
  4. Хай одне читання відповість на кожне питання, на яке може. Два виклики можуть описувати два різні стани.
  5. Ставте судження туди, де контекст. Спостерігач, який не вміє висловити вердикт, не може його переплутати.
  6. Дайте вичерпаним повторам власне кінцеве слово. Невідомо є чесним; невдало й завершено обидва є твердженнями, яких ніхто не робив.

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

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

Читати далі

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

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