Управляем виртуальной машиной через QEMU Guest Agent
Имея гипервизор и запущенные на нём виртуальные машины, скорее всего, рано или поздно, возникнет необходимость что-то пнуть, взять или запустить внутри гостевой системы, не прибегая к доступу по SSH. Как? С помощью qemu-guest-agent. Посмотрим как всё это работает.
Собираем стенд
Окружение
Собственно, нам много не надо - сервер с qemu-kvm и какая-нибудь виртуалка с не самым маргинальным дистрибутивом. В моём случае qemu-kvm развёрнут прямо на лаптопе, в виртуальной машине крутится Ubuntu Jammy:
andrey@ionice:~$ virsh list Id Name State ----------------------------------------- 7 ubuntu22.04-guest-agent running
Установка qemu-guest-agent
Установка агента тривиальна и скучна - всё есть в репозиториях, устанавливаем и стартуем службу
andrey@ionice:~$ virsh console ubuntu22.04-guest-agent qga login: server Password: Welcome to Ubuntu 22.04.1 LTS (GNU/Linux 5.15.0-71-generic x86_64) server@qga:~$ sudo apt install qemu-guest-agent server@qga:~$ sudo systemctl enable --now qemu-guest-agent server@qga:~$ sudo systemctl status qemu-guest-agent ● qemu-guest-agent.service - QEMU Guest Agent Loaded: loaded (/lib/systemd/system/qemu-guest-agent.service; static) Active: active (running) since Thu 2023-04-27 18:03:54 UTC; 1min 19s ago Main PID: 1039 (qemu-ga) Tasks: 2 (limit: 4572) Memory: 380.0K CPU: 1ms CGroup: /system.slice/qemu-guest-agent.service └─1039 /usr/sbin/qemu-ga Apr 27 18:03:54 qga systemd[1]: Started QEMU Guest Agent. ^[
Всё, основную часть задачи выполнили. Теперь убедимся что наша виртуалка имеет устройство qemu-ga:
andrey@ionice:~$ virsh dumpxml ubuntu22.04-guest-agent ... <channel type='unix'> <target type='virtio' name='org.qemu.guest_agent.0'/> <address type='virtio-serial' controller='0' bus='0' port='1'/> </channel> ...
Если такой секции нет, можем дописать с помощью команды virsh edit ubuntu22.04-guest-agent
. Желательно перезагрузить виртуалку после таких манипуляций командой virsh reboot ubuntu22.04-guest-agent
.
Проверка связи
Теперь самое интересное - обратимся к виртуальной машине через агента:
andrey@ionice:~$ virsh qemu-agent-command ubuntu22.04-guest-agent '{"execute": "guest-ping"}' {"return":{}}
Ну... как-то не впечатляем. Дубль два:
andrey@ionice:~$ virsh qemu-agent-command ubuntu22.04-guest-agent '{"execute": "guest-get-time"}' --pretty { "return": 1682620143771430000 }
andrey@ionice:~$ virsh qemu-agent-command ubuntu22.04-guest-agent '{"execute": "guest-get-time"}' --pretty | jq .return | date -d - Чт 27 апр 2023 00:00:00 UTC
Более-менее информация о дате соответствует действительности, но почему-то таймштамп не вернул нам время. Где-то нас обворовали. Тем не менее, самое главное - информацию мы получили не прибегая к протоколам удалённого доступа. Разбираемся дальше.
Обзор протокола QMP
Общение с виртаульной машиной осуществляется по протоколу QMP (QEMU Machine Protocol) через локальный сокет на гипервизоре (то самое виртуальное устройство, которое мы настраивали ранее). Протокол поддерживает весьма обширный набор команд как GET, так и POST. Формат данных протокола - JSON.
Посмотрим, какие команды поддерживает наш qemu-guest-agent в виртуалке:
andrey@ionice:~$ virsh qemu-agent-command ubuntu22.04-guest-agent '{"execute": "guest-info"}' --pretty | jq -r '.return.supported_commands[] | select(.enabled==true) | .name' guest-ssh-remove-authorized-keys guest-ssh-add-authorized-keys guest-ssh-get-authorized-keys guest-get-osinfo guest-get-timezone guest-get-users guest-get-host-name guest-exec guest-exec-status guest-get-memory-block-info guest-set-memory-blocks guest-get-memory-blocks guest-set-user-password guest-get-fsinfo guest-get-disks guest-set-vcpus guest-get-vcpus guest-network-get-interfaces guest-suspend-hybrid guest-suspend-ram guest-suspend-disk guest-fstrim guest-fsfreeze-thaw guest-fsfreeze-freeze-list guest-fsfreeze-freeze guest-fsfreeze-status guest-file-flush guest-file-seek guest-file-write guest-file-read guest-file-close guest-file-open guest-shutdown guest-info guest-set-time guest-get-time guest-ping guest-sync guest-sync-delimited
Впечатляет? Ваш внутренний безопасник уже истерично лупит по кнопке "Запретить"? Мозг рисует безграничные возможности управления инфраструктурой? Чтобы понимать что к чему, сначала рекомендую почитать референс протокола и двигаться дальше.
Примеры выполнения команд
andrey@ionice:~$ virsh qemu-agent-command ubuntu22.04-guest-agent '{"execute": "guest-get-osinfo"}' --pretty | jq { "return": { "name": "Ubuntu", "kernel-release": "5.15.0-71-generic", "version": "22.04.1 LTS (Jammy Jellyfish)", "pretty-name": "Ubuntu 22.04.1 LTS", "version-id": "22.04", "kernel-version": "#78-Ubuntu SMP Tue Apr 18 09:00:29 UTC 2023", "machine": "x86_64", "id": "ubuntu" } }
andrey@ionice:~$ virsh qemu-agent-command ubuntu22.04-guest-agent '{"execute": "guest-network-get-interfaces"}' --pretty | jq -r '.return[]."ip-addresses"[] | select(."ip-address-type"=="ipv4" and ."ip-address"!="127.0.0.1") | ."ip-address"' 192.168.122.17
andrey@ionice:~$ virsh qemu-agent-command ubuntu22.04-guest-agent '{"execute": "guest-get-users"}' --pretty | jq -r .return[].user server
andrey@ionice:~$ virsh qemu-agent-command ubuntu22.04-guest-agent '{"execute": "guest-ssh-add-authorized-keys", "arguments": {"username": "server", "keys": [ "ssh-rsa xxxxx" ] }}' --pretty
andrey@ionice:~$ virsh qemu-agent-command ubuntu22.04-guest-agent '{"execute": "guest-ssh-get-authorized-keys", "arguments": {"username": "server" }}' --pretty { "return": { "keys": [ "ssh-rsa xxxxx" ] } }
- Выполним какую-нибудь shell-команду и получим её выхлоп. Для этого нам потребуются две команды - guest-exec, чтобы запустить процесс и получить его PID, и guest-exec-status, чтобы узнать чем завершилось выполнение процесса. Выхлоп будет в формате base64
andrey@ionice:~$ virsh qemu-agent-command ubuntu22.04-guest-agent '{"execute": "guest-exec", "arguments": { "path": "/usr/bin/lsb_release", "arg": [ "-a" ], "capture-output": true }}' --pretty | jq ".return.pid" 832 andrey@ionice:~$ virsh qemu-agent-command ubuntu22.04-guest-agent '{"execute": "guest-exec-status", "arguments": { "pid": 832 }}' --pretty | jq { "return": { "exitcode": 0, "err-data": "Tm8gTFNCIG1vZHVsZXMgYXJlIGF2YWlsYWJsZS4K", "out-data": "RGlzdHJpYnV0b3IgSUQ6CVVidW50dQpEZXNjcmlwdGlvbjoJVWJ1bnR1IDIyLjA0LjEgTFRTClJlbGVhc2U6CTIyLjA0CkNvZGVuYW1lOglqYW1teQo=", "exited": true } } andrey@ionice:~$ echo RGlzdHJpYnV0b3IgSUQ6CVVidW50dQpEZXNjcmlwdGlvbjoJVWJ1bnR1IDIyLjA0LjEgTFRTClJlbGVhc2U6CTIyLjA0CkNvZGVuYW1lOglqYW1teQo= | base64 --decode Distributor ID: Ubuntu Description: Ubuntu 22.04.1 LTS Release: 22.04 Codename: jammy andrey@ionice:~$ echo Tm8gTFNCIG1vZHVsZXMgYXJlIGF2YWlsYWJsZS4K | base64 --decode No LSB modules are available.
- Прочитаем что-нибудь из файла. Для этого сначала откроем его на чтение, получив его ID, затем прочитаем данные из файла (они будут в base64), а потом закроем файл, чтобы не держать лишние процессы и дескрипторы:
andrey@ionice:~$ virsh qemu-agent-command ubuntu22.04-guest-agent '{"execute": "guest-file-open", "arguments": { "path": "/etc/hosts", "mode" : "r" }}' --pretty { "return": 1000 } andrey@ionice:~$ virsh qemu-agent-command ubuntu22.04-guest-agent '{"execute": "guest-file-read", "arguments": { "handle": 1000 }}' --pretty { "return": { "count": 218, "buf-b64": "MTI3LjAuMC4xIGxvY2FsaG9zdAoxMjcuMC4xLjEgcWdhCgojIFRoZSBmb2xsb3dpbmcgbGluZXMgYXJlIGRlc2lyYWJsZSBmb3IgSVB2NiBjYXBhYmxlIGhvc3RzCjo6MSAgICAgaXA2LWxvY2FsaG9zdCBpcDYtbG9vcGJhY2sKZmUwMDo6MCBpcDYtbG9jYWxuZXQKZmYwMDo6MCBpcDYtbWNhc3RwcmVmaXgKZmYwMjo6MSBpcDYtYWxsbm9kZXMKZmYwMjo6MiBpcDYtYWxscm91dGVycwo=", "eof": true } } andrey@ionice:~$ echo MTI3LjAuMC4xIGxvY2FsaG9zdAoxMjcuMC4xLjEgcWdhCgojIFRoZSBmb2xsb3dpbmcgbGluZXMgYXJlIGRlc2lyYWJsZSBmb3IgSVB2NiBjYXBhYmxlIGhvc3RzCjo6MSAgICAgaXA2LWxvY2FsaG9zdCBpcDYtbG9vcGJhY2sKZmUwMDo6MCBpcDYtbG9jYWxuZXQKZmYwMDo6MCBpcDYtbWNhc3RwcmVmaXgKZmYwMjo6MSBpcDYtYWxsbm9kZXMKZmYwMjo6MiBpcDYtYWxscm91dGVycwo= | base64 --decode 127.0.0.1 localhost 127.0.1.1 qga # The following lines are desirable for IPv6 capable hosts ::1 ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters andrey@ionice:~$ virsh qemu-agent-command ubuntu22.04-guest-agent '{"execute": "guest-file-close", "arguments": { "handle": 1000 }}' --pretty { "return": { } }
Следы
Хоть мой внутренний автоматизатор ликует, параноик всё равно немного параноит. Как минимум, ему хотелось бы понимать как пользователю гостевой ОС контролировать происходящее с его системой. Посмотрим логи с виртуалки:
root@qga:~# journalctl -u qemu-guest-agent Apr 27 18:22:54 qga systemd[1]: Started QEMU Guest Agent. Apr 27 18:28:40 qga qemu-ga[624]: info: guest-ping called Apr 27 18:29:17 qga qemu-ga[624]: info: guest-ping called Apr 27 19:55:41 qga qemu-ga[624]: info: guest-exec called: "/usr/bin/lsb_release -a" Apr 27 19:56:45 qga qemu-ga[624]: info: guest-exec-status called, pid: 832 Apr 27 20:07:06 qga qemu-ga[624]: info: guest-file-open called, filepath: /etc/hosts, mode: r Apr 27 20:07:06 qga qemu-ga[624]: info: guest-file-open, handle: 1000 Apr 27 20:14:12 qga qemu-ga[624]: info: guest-file-close called, handle: 1000
Как видим, ключевые команды в логе отображаются. Можно выдохнуть.
Выводы
QEMU Guest Agent - очень интересный способ контроля и управления виртуальными машинами, который уже давно используется в продуктовых средах. Если вы заранее подготовите образы ОС, напишете скрипты или сервисы управления, то сможете облегчить себе обслуживание инфраструктуры "без рук". Следует однако помнить, что применение инструмента потребует пересмотра политик безопасности. Ну и классическое - with great power comes great responsibility