Для улучшения работы сайта мы используем файлы cookies. Оставаясь на сайте, вы соглашаетесь с политикой обработки персональных данных.

Обновление ключей UEFI Secure Boot в виртуальных машинах (zVirt 4.x)

Аннотация

В zVirt 4.x виртуальные машины с включенным UEFI Secure Boot проверяют загрузку ОС, используя набор сертификатов и ключей. Этот набор хранится в эмуляции NVRAM вместе с настройками прошивки и создается для каждой виртуальной машины из шаблона.

24 июня 2026 года истекает срок действия одного из сертификатов Microsoft, используемых в UEFI Secure Boot. Это не повлияет на работу существующих виртуальных машин, однако в дальнейшем могут возникнуть проблемы с запуском новых версий ОС, загрузчики которых подписаны только новыми сертификатами Microsoft.

Для предотвращения подобных ситуаций необходимо обновить пакет edk2-ovmf на хостах, который добавит в шаблон NVRAM новые сертификаты и ключи Microsoft,выпущенные в 2023 году и со сроком действия до 2038 года. После обновления новые виртуальные машины будут автоматически использовать актуальный набор ключей Secure Boot, а для существующих виртуальных машин потребуется дополнительное обновление их NVRAM.

В данной статье описан порядок установки обновления и обновления существующих виртуальных машин.

1. Установка обновления

Установите обновление на хостах через веб-интерфейс, либо с помощью команды:

dnf update edk2-ovmf

Команду необходимо выполнить на всех хостах.

2. Обновление существующих виртуальных машин

Описание процедуры

Для применения обновленного набора ключей Secure Boot к существующим виртуальным машинам необходимо пересоздать NVRAM. Процедура заключается во временном переключении типа прошивки виртуальной машины с Q35 c UEFI Secure Boot на Q35 c UEFI и последующем возврате к исходному значению.

Способ 1. Выполнение процедуры через REST API.
Порядок действий:
  1. Выключите виртуальную машину.

  2. Получите токен доступа.

    curl --location 'https://{fqdn}/ovirt-engine/sso/oauth/token' \
    --header 'Accept: application/json' \
    --header 'Version: 4' \
    --header 'Content-Type: application/x-www-form-urlencoded' \
    --data-urlencode 'grant_type=password' \
    --data-urlencode 'scope=ovirt-app-api' \
    --data-urlencode 'username={user}' \
    --data-urlencode 'password={password}'

    Где:

    • {fqdn} — FQDN Менеджера управления.

    • {user} — имя пользователя, например admin@zvirt@internalsso.

    • {password} — пароль пользователя.

      Сохраните значение поля access_token из полученного ответа.

  3. Получите описание виртуальной машины.

    curl --location --request GET 'https://{fqdn}/ovirt-engine/api/vms/{vmId}' \
    --header 'Accept: application/json' \
    --header 'Version: 4' \
    --header 'Authorization: Bearer {token}'

    Где:

    • {vmId} — идентификатор виртуальной машины.

    • {token} — токен, полученный на предыдущем шаге.

      Сохраните полученный JSON.

  4. Измените значение поля bios.type.

    Исходное значение:

    "bios": {
        "boot_menu": {
            "enabled": "false"
        },
        "type": "q35_secure_boot"
    }

    Измените на:

    "bios": {
        "boot_menu": {
            "enabled": "false"
        },
        "type": "q35_sea_bios"
    }
  5. Примените изменения:

    curl --location --request PUT 'https://{fqdn}/ovirt-engine/api/vms/{vmId}' \
    --header 'Accept: application/json' \
    --header 'Version: 4' \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Bearer {token}' \
    --data '{json}'
  6. Убедитесь, что значение поля bios.type изменено на q35_sea_bios.

  7. Повторите шаги 3–5, изменив значение bios.type обратно на q35_secure_boot.

  8. Убедитесь, что изменение успешно применено.

  9. Запустите виртуальную машину.

Если используется самоподписанный сертификат, добавьте к командам curl параметр -k или --insecure.
Способ 2. Автоматическое выполнение процедуры с помощью скрипта.

Для автоматического выполнения процедуры можно использовать скрипт.

Порядок действий:
  1. Сохраните скрипт в файл change_bios.sh.

    #!/bin/bash
    set -u
    
    # Цвета
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    YELLOW='\033[0;33m'
    BLUE='\033[0;34m'
    CYAN='\033[0;36m'
    MAGENTA='\033[0;35m'
    NC='\033[0m'
    
    info()    { echo -e "${BLUE}[INFO]${NC} $1"; }
    success() { echo -e "${GREEN}[OK]${NC} $1"; }
    error()   { echo -e "${RED}[ERROR]${NC} $1" >&2; }
    warn()    { echo -e "${YELLOW}[WARN]${NC} $1"; }
    step()    { echo -e "${MAGENTA}==>${NC} $1"; }
    status()  { echo -e "${CYAN}  $1${NC}"; }
    
    usage() {
        cat <<EOF
    Использование: $0 --vm-id <ID> --url <URL> --username <USER> [--password <PASS>]
    EOF
        exit 1
    }
    
    VM_ID=""
    ENGINE_URL=""
    USERNAME=""
    PASSWORD=""
    
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --vm-id) VM_ID="$2"; shift 2 ;;
            --url) ENGINE_URL="$2"; shift 2 ;;
            --username) USERNAME="$2"; shift 2 ;;
            --password) PASSWORD="$2"; shift 2 ;;
            *) usage ;;
        esac
    done
    
    if [[ -z "$VM_ID" || -z "$ENGINE_URL" || -z "$USERNAME" ]]; then
        usage
    fi
    
    if [[ -z "$PASSWORD" ]]; then
        read -s -p "Введите пароль для $USERNAME: " PASSWORD
        echo
    fi
    
    ENGINE_URL="${ENGINE_URL%/}"
    
    get_token() {
        curl -k -s -X POST \
            -H "Accept: application/json" \
            -H "Content-Type: application/x-www-form-urlencoded" \
            -d "grant_type=password&scope=ovirt-app-api&username=$USERNAME&password=$PASSWORD" \
            "$ENGINE_URL/ovirt-engine/sso/oauth/token" | jq -r '.access_token'
    }
    
    get_vm_status() {
        local token=$1
        curl -k -s -H "Authorization: Bearer $token" \
            -H "Accept: application/json" -H "Version: 4" \
            "$ENGINE_URL/ovirt-engine/api/vms/$VM_ID" 2>/dev/null | jq -r '.status // empty' 2>/dev/null || echo ""
    }
    
    wait_for_status() {
        local token=$1
        local expected=$2
        local max_attempts=90
        local attempt=0
        while [[ $attempt -lt $max_attempts ]]; do
            local status
            status=$(get_vm_status "$token")
            if [[ -z "$status" ]]; then
                status "Не удалось получить статус (пустой ответ). Повтор через 2 сек..."
            elif [[ "$status" == "$expected" ]]; then
                status "Статус ${GREEN}'$expected'${NC} достигнут."
                return 0
            else
                status "Ожидание ${YELLOW}'$expected'${NC} (сейчас ${CYAN}'$status'${NC})..."
            fi
            sleep 2
            ((attempt++))
        done
        error "Таймаут ожидания статуса '$expected'"
        return 1
    }
    
    stop_vm() {
        local token=$1
        step "Останавливаем ВМ ${YELLOW}$VM_ID${NC}..."
        curl -k -s -X POST "$ENGINE_URL/ovirt-engine/api/vms/$VM_ID/stop" \
            -H "Authorization: Bearer $token" \
            -H "Accept: application/json" -H "Version: 4" \
            -H "Content-Type: application/json" \
            --data '{}' > /dev/null
        wait_for_status "$token" "down"
    }
    
    set_bios() {
        local token=$1
        local bios_type=$2
        step "Меняем BIOS на ${YELLOW}$bios_type${NC}..."
        local vm_json
        vm_json=$(curl -k -s -H "Authorization: Bearer $token" \
            -H "Accept: application/json" -H "Version: 4" \
            "$ENGINE_URL/ovirt-engine/api/vms/$VM_ID")
        local updated_json
        updated_json=$(echo "$vm_json" | jq ".bios.type = \"$bios_type\"")
        curl -k -s -X PUT \
            -H "Authorization: Bearer $token" \
            -H "Accept: application/json" -H "Version: 4" \
            -H "Content-Type: application/json" \
            -d "$updated_json" \
            "$ENGINE_URL/ovirt-engine/api/vms/$VM_ID" > /dev/null
        local new_bios
        new_bios=$(curl -k -s -H "Authorization: Bearer $token" \
            -H "Accept: application/json" -H "Version: 4" \
            "$ENGINE_URL/ovirt-engine/api/vms/$VM_ID" | jq -r '.bios.type')
        if [[ "$new_bios" == "$bios_type" ]]; then
            success "BIOS успешно изменён на ${GREEN}$bios_type${NC}."
        else
            error "BIOS не изменился (остался ${YELLOW}$new_bios${NC})"
            return 1
        fi
    }
    
    start_vm() {
        local token=$1
        step "Запускаем ВМ ${YELLOW}$VM_ID${NC}..."
        curl -k -s -X POST "$ENGINE_URL/ovirt-engine/api/vms/$VM_ID/start" \
            -H "Authorization: Bearer $token" \
            -H "Accept: application/json" -H "Version: 4" \
            -H "Content-Type: application/json" \
            --data '{}' > /dev/null
    
        info "Запуск ВМ инициирован, для завершения работы ожидаем окончания запуска (статус 'up')."
        echo -e "       ${YELLOW}Для немедленного выхода нажмите Ctrl+C.${NC}"
    
        local max_attempts=90
        local attempt=0
        while [[ $attempt -lt $max_attempts ]]; do
            local status
            status=$(get_vm_status "$token")
            if [[ "$status" == "up" ]]; then
                success "ВМ запущена (статус ${GREEN}up${NC})."
                return 0
            elif [[ -n "$status" ]]; then
                status "Текущий статус: ${CYAN}$status${NC}"
            else
                status "Не удалось получить статус"
            fi
            sleep 2
            ((attempt++))
        done
        error "Таймаут ожидания запуска ВМ."
        return 1
    }
    
    # === Основной блок ===
    info "Получение токена..."
    TOKEN=$(get_token)
    if [[ -z "$TOKEN" || "$TOKEN" == "null" ]]; then
        error "Ошибка получения токена"
        exit 1
    fi
    success "Токен получен."
    
    CURRENT_STATUS=$(get_vm_status "$TOKEN")
    info "Текущий статус ВМ: ${CYAN}$CURRENT_STATUS${NC}"
    
    if [[ "$CURRENT_STATUS" != "down" ]]; then
        stop_vm "$TOKEN"
    else
        info "ВМ уже выключена."
    fi
    
    set_bios "$TOKEN" "q35_sea_bios"
    
    step "Ждём 2 секунды..."
    sleep 2
    
    set_bios "$TOKEN" "q35_secure_boot"
    
    start_vm "$TOKEN"
    
    success "✅ Готово: Chipset переключён с UEFI SecureBoot на UEFI и обратно, ВМ запущена."
  2. Назначьте права на выполнение:

    chmod +x change_bios.sh
Пример запуска:
./change_bios.sh \
  --vm-id aae140f5-6348-4781-bccb-7748b4e4baf5 \
  --url https://z1.test.local \
  --username admin@zvirt@internalsso \
  --password 12345678
Таблица 1. Параметры запуска:
Параметр Описание

--vm-id

Идентификатор виртуальной машины.

--url

URL Менеджера управления.

--username

Имя пользователя.

--password

Пароль.

3. Возможные проблемы

В отдельных случаях виртуальная машина может не запускаться. В событиях отображается сообщение:

Failed to run VM …​

Также может не выполняться изменение типа чипсета (после нажатия ОК в окне редактирования виртуальной машины изменения не применяются).

Причиной может быть повреждение NVRAM виртуальной машины.

Для восстановления работоспособности удалите запись NVRAM из базы данных:

DELETE FROM vm_nvram_data
WHERE vm_id = '<vm_id>';