Управляем виртуальной машиной через 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.17andrey@ionice:~$ virsh qemu-agent-command ubuntu22.04-guest-agent '{"execute": "guest-get-users"}' --pretty | jq -r .return[].user
serverandrey@ionice:~$ virsh qemu-agent-command ubuntu22.04-guest-agent '{"execute": "guest-ssh-add-authorized-keys", "arguments": {"username": "server", "keys": [ "ssh-rsa xxxxx" ] }}' --prettyandrey@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