Блог ·
Непривілейованість вимірюють, а не задають
Відновлення контейнера з резервної копії починається з читання конфігурації всередині архіву, бо безпечне значення одного прапорця не можна надсилати наосліп.
Автор: Provibr
Відновлення контейнера в Proxmox це той самий виклик API, що й створення. Ви робите POST на endpoint контейнерів на вузлі, віддаєте йому архів і додаєте прапорець, який перетворює створення на відновлення. У шляху віртуальної машини такого прапорця навіть немає: там сам архів і є відновленням. Ця симетрія зручна, і вона ховає пастку, бо одне з полів у тому виклику описує властивість безпеки, яка вже має значення всередині архіву.
Надішліть безпечне значення, і ви можете зламати контейнер. Пропустіть поле, і ви можете завести привілейований контейнер у парк, у якому таких не має бути. Єдиний правильний хід це спершу прочитати архів, а потім діяти за прочитаним.
Два endpoint, які мають схожий вигляд
Шлях віртуальної машини й шлях контейнера різняться в більшій кількості місць, ніж підказує назва:
| Віртуальна машина | Контейнер | |
|---|---|---|
| Endpoint | POST /nodes/{node}/qemu | POST /nodes/{node}/lxc |
| Архів | archive=<volid> | ostemplate=<volid> плюс restore=1 |
| Назва | name= | hostname= |
| Цільове сховище мусить приймати | images | rootdir |
| Тип завдання в ідентифікаторі завдання | qmrestore | vzrestore |
Два з тих рядків легко зіпсувати так, що вийде заплутана помилка. 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 назад і не робить жодного власного висновку, бо рішення про те, що ці три джерела дають разом, потребує запуску й наміру, з якого він почався, поруч, а обидва є лише в платформи.
Ніщо не повторюють вічно. Є лічильник зі стелею у вісім спроб, і при усталеному інтервалі в шість годин це виходить приблизно на два дні. Спроби, у яких завдання доказово ще виконувалося, проти нього не рахують, бо велика копія повільною мережею має право забрати ніч. Коли лічильник вичерпується, запуск стає невідомим і ніколи невдалим. Невдалий був би твердженням, якого ніхто не перевіряв, а завершений стверджував би архів, якого ніхто не бачив.
Що ми записали б
- Не надсилайте значення для властивості, яка вже існує у вхідних даних. Прочитайте її, а потім або підтвердьте, або відмовте.
- Зʼясуйте, що означає відсутній ключ, перш ніж писати парсер. Тут він означає небезпечне значення, а це протилежне до звичної конвенції.
- Падайте закрито, коли вимірювання недоступне, і скажіть уголос, чого це коштує, бо хтось на це натрапить.
- Хай одне читання відповість на кожне питання, на яке може. Два виклики можуть описувати два різні стани.
- Ставте судження туди, де контекст. Спостерігач, який не вміє висловити вердикт, не може його переплутати.
- Дайте вичерпаним повторам власне кінцеве слово. Невідомо є чесним; невдало й завершено обидва є твердженнями, яких ніхто не робив.
Заради ясності, чим це є: резервні копії клієнтів лежать за прапорцем розгортання, який типово вимкнено, тож нічого з цього сьогодні не купити. І важлива межа доказів: жодного справжнього резервного копіювання чи відновлення не проганяли проти живого гіпервізора. Поведінку параметрів вище прочитано з власного джерельного коду й документації Proxmox, а код випробувано проти сервера-замінника, який відповідає на ті самі endpoint. Виклик видобування також не пробували проти архіву на виділеному сервері резервних копій, лише проти сховища на файлах.
Із роботи над резервними копіями в Provibr. Чому архів, якого ніхто не відновлював, ще не є резервною копією, є в копії без відновлення, що саме гарантують різні режими копіювання, є в режимах vzdump у Proxmox, а чому перевстановлення контейнера є знищенням і побудовою заново, є в перевстановленні контейнера як заміні.