From d3bbdd9603579777b306cca5caa8b8fb442a669c Mon Sep 17 00:00:00 2001 From: Jack Hodgkiss Date: Tue, 7 Jul 2026 13:24:17 +0100 Subject: [PATCH] feat: add playbooks for managing `KVM` nested virt In response to CVE-2026-53359 [1] it is possible to disable nested virtualisation whilst appropriate kernel patches are made available. The two playbooks can be used to either enable or disable nested virtualisation. Hosts must have nova-compute disabled and empty prior to running these playbooks. See `nova-compute-disable.yml` and `nova-compute-drain.yml` playbooks to assist with this. Ensure that `admin-openrc.sh` credentials are sourced prior to running the playbooks. [1]: https://www.openwall.com/lists/oss-security/2026/07/06/7 Signed-off-by: Jack Hodgkiss --- .../maintenance/kvm-disable-nested-virt.yml | 126 ++++++++++++++++++ .../maintenance/kvm-enable-nested-virt.yml | 126 ++++++++++++++++++ ...d-kvm-virt-playbooks-cad68c77a4deca40.yaml | 9 ++ 3 files changed, 261 insertions(+) create mode 100644 etc/kayobe/ansible/maintenance/kvm-disable-nested-virt.yml create mode 100644 etc/kayobe/ansible/maintenance/kvm-enable-nested-virt.yml create mode 100644 releasenotes/notes/add-kvm-virt-playbooks-cad68c77a4deca40.yaml diff --git a/etc/kayobe/ansible/maintenance/kvm-disable-nested-virt.yml b/etc/kayobe/ansible/maintenance/kvm-disable-nested-virt.yml new file mode 100644 index 0000000000..c167525358 --- /dev/null +++ b/etc/kayobe/ansible/maintenance/kvm-disable-nested-virt.yml @@ -0,0 +1,126 @@ +--- +- name: Disable KVM nested virtualisation + hosts: compute + gather_facts: true + vars: + venv: "{{ virtualenv_path }}/openstack" + pre_tasks: + - name: Assert host is capable of virtualisation + ansible.builtin.assert: + that: + - ansible_facts.virtualization_role == 'host' + fail_msg: "Host is not capable of virtualisation" + + - name: Set up openstack cli virtualenv + ansible.builtin.pip: + virtualenv: "{{ venv }}" + virtualenv_command: /usr/bin/python3 -m venv + name: + - python-openstackclient + state: latest + extra_args: "{% if pip_upper_constraints_file %}-c {{ pip_upper_constraints_file }}{% endif %}" + run_once: true + delegate_to: "{{ groups['controllers'][0] }}" + vars: + ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}" + + - name: Check compute service is disabled for maintenance + ansible.builtin.command: > + {{ venv }}/bin/openstack compute service list + --service nova-compute + --host {{ ansible_facts.nodename }} + --format json + register: compute_service + environment: "{{ openstack_auth_env }}" + delegate_to: "{{ groups['controllers'][0] }}" + vars: + ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}" + + - name: Assert hypervisor is disabled for maintenance + ansible.builtin.assert: + that: + - compute_service.stdout | from_json | length > 0 + - (compute_service.stdout | from_json | first).Status == "disabled" + fail_msg: > + Hypervisor {{ ansible_facts.nodename }} is not disabled for maintenance. + Current service state: {{ compute_service.stdout | from_json }} + delegate_to: "{{ groups['controllers'][0] }}" + vars: + ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}" + + - name: Query instances + ansible.builtin.command: > + {{ venv }}/bin/openstack + server list --host {{ ansible_facts.nodename }} + --all-projects + --status ACTIVE + --format json + register: instances + environment: "{{ openstack_auth_env }}" + delegate_to: "{{ groups['controllers'][0] }}" + vars: + ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}" + + - name: Fail if there are instances still on the host + ansible.builtin.fail: + msg: > + Instances still on {{ inventory_hostname }}: {{ instances.stdout | from_json }} + when: + - instances.stdout | from_json | length > 0 + delegate_to: "{{ groups['controllers'][0] }}" + vars: + ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}" + tasks: + - name: Set KVM module name + ansible.builtin.set_fact: + kvm_module_name: >- + {{ 'kvm_intel' + if 'GenuineIntel' in (ansible_facts.processor | default([])) + else 'kvm_amd' + if 'AuthenticAMD' in (ansible_facts.processor | default([])) + else 'unknown' }} + + - name: Assert supported CPU is present + ansible.builtin.assert: + that: + - kvm_module_name != '' + fail_msg: "Cannot detect either GenuineIntel or AuthenticAMD processor" + + - name: Verify KVM module is loaded + ansible.builtin.stat: + path: "/sys/module/{{ kvm_module_name }}" + register: kvm_module_path + become: true + + - name: Check if nested virtualization is supported + ansible.builtin.command: "cat /sys/module/{{ kvm_module_name }}/parameters/nested" + register: nested + changed_when: false + when: kvm_module_path.stat.exists + become: true + + - name: Disable nested virtualisation and restart nova_libvirt + when: "not kvm_module_path.stat.exists or ('N' not in nested.stdout_lines | default([]) and '0' not in nested.stdout_lines | default([]))" + become: true + block: + - name: Unload the KVM module + community.general.modprobe: + name: "{{ kvm_module_name}}" + state: absent + + - name: Ensure KVM module is enabled and nested virtualisation is disabled + community.general.modprobe: + name: "{{ kvm_module_name}}" + params: 'nested=0' + persistent: present + + - name: Check again if nested virtualization is not supported + ansible.builtin.command: "cat /sys/module/{{ kvm_module_name }}/parameters/nested" + changed_when: false + register: result + failed_when: "'N' not in result.stdout_lines and '0' not in result.stdout_lines" + + - name: Restart nova_libvirt container + ansible.builtin.systemd_service: + name: kolla-nova_libvirt-container + state: restarted diff --git a/etc/kayobe/ansible/maintenance/kvm-enable-nested-virt.yml b/etc/kayobe/ansible/maintenance/kvm-enable-nested-virt.yml new file mode 100644 index 0000000000..e99396c984 --- /dev/null +++ b/etc/kayobe/ansible/maintenance/kvm-enable-nested-virt.yml @@ -0,0 +1,126 @@ +--- +- name: Enable KVM nested virtualisation + hosts: compute + gather_facts: true + vars: + venv: "{{ virtualenv_path }}/openstack" + pre_tasks: + - name: Assert host is capable of virtualisation + ansible.builtin.assert: + that: + - ansible_facts.virtualization_role == 'host' + fail_msg: "Host is not capable of virtualisation" + + - name: Set up openstack cli virtualenv + ansible.builtin.pip: + virtualenv: "{{ venv }}" + virtualenv_command: /usr/bin/python3 -m venv + name: + - python-openstackclient + state: latest + extra_args: "{% if pip_upper_constraints_file %}-c {{ pip_upper_constraints_file }}{% endif %}" + run_once: true + delegate_to: "{{ groups['controllers'][0] }}" + vars: + ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}" + + - name: Check compute service is disabled for maintenance + ansible.builtin.command: > + {{ venv }}/bin/openstack compute service list + --service nova-compute + --host {{ ansible_facts.nodename }} + --format json + register: compute_service + environment: "{{ openstack_auth_env }}" + delegate_to: "{{ groups['controllers'][0] }}" + vars: + ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}" + + - name: Assert hypervisor is disabled for maintenance + ansible.builtin.assert: + that: + - compute_service.stdout | from_json | length > 0 + - (compute_service.stdout | from_json | first).Status == "disabled" + fail_msg: > + Hypervisor {{ ansible_facts.nodename }} is not disabled for maintenance. + Current service state: {{ compute_service.stdout | from_json }} + delegate_to: "{{ groups['controllers'][0] }}" + vars: + ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}" + + - name: Query instances + ansible.builtin.command: > + {{ venv }}/bin/openstack + server list --host {{ ansible_facts.nodename }} + --all-projects + --status ACTIVE + --format json + register: instances + environment: "{{ openstack_auth_env }}" + delegate_to: "{{ groups['controllers'][0] }}" + vars: + ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}" + + - name: Fail if there are instances still on the host + ansible.builtin.fail: + msg: > + Instances still on {{ inventory_hostname }}: {{ instances.stdout | from_json }} + when: + - instances.stdout | from_json | length > 0 + delegate_to: "{{ groups['controllers'][0] }}" + vars: + ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}" + tasks: + - name: Set KVM module name + ansible.builtin.set_fact: + kvm_module_name: >- + {{ 'kvm_intel' + if 'GenuineIntel' in (ansible_facts.processor | default([])) + else 'kvm_amd' + if 'AuthenticAMD' in (ansible_facts.processor | default([])) + else 'unknown' }} + + - name: Assert supported CPU is present + ansible.builtin.assert: + that: + - kvm_module_name != '' + fail_msg: "Cannot detect either GenuineIntel or AuthenticAMD processor" + + - name: Verify KVM module is loaded + ansible.builtin.stat: + path: "/sys/module/{{ kvm_module_name }}" + register: kvm_module_path + become: true + + - name: Check if nested virtualization is supported + ansible.builtin.command: "cat /sys/module/{{ kvm_module_name }}/parameters/nested" + register: nested + changed_when: false + when: kvm_module_path.stat.exists + become: true + + - name: Enable nested virtualisation and restart nova_libvirt + when: "not kvm_module_path.stat.exists or ('Y' not in nested.stdout_lines | default([]) and '1' not in nested.stdout_lines | default([]))" + become: true + block: + - name: Unload the KVM module + community.general.modprobe: + name: "{{ kvm_module_name}}" + state: absent + + - name: Ensure KVM module is enabled and nested virtualisation is enabled + community.general.modprobe: + name: "{{ kvm_module_name}}" + params: 'nested=1' + persistent: present + + - name: Check again if nested virtualization is supported + ansible.builtin.command: "cat /sys/module/{{ kvm_module_name }}/parameters/nested" + changed_when: false + register: result + failed_when: "'Y' not in result.stdout_lines and '1' not in result.stdout_lines" + + - name: Restart nova_libvirt container + ansible.builtin.systemd_service: + name: kolla-nova_libvirt-container + state: restarted diff --git a/releasenotes/notes/add-kvm-virt-playbooks-cad68c77a4deca40.yaml b/releasenotes/notes/add-kvm-virt-playbooks-cad68c77a4deca40.yaml new file mode 100644 index 0000000000..05d79ee57e --- /dev/null +++ b/releasenotes/notes/add-kvm-virt-playbooks-cad68c77a4deca40.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Add two new playbooks to support enabling or disabling of KVM nested virtualisation. +security: + - | + Playbook added to support disabling of KVM nested virtualisation in response to + `CVE-2026-53359 `__. This + playbook acts as early mitigation to the vulnerability in lieu of kernel patches.