Skip to content

Commit 3f57e07

Browse files
jackhodgkissAlex-Welsh
authored andcommitted
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 <jack@stackhpc.com>
1 parent 3a27968 commit 3f57e07

3 files changed

Lines changed: 261 additions & 0 deletions

File tree

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
---
2+
- name: Disable KVM nested virtualisation
3+
hosts: compute
4+
gather_facts: true
5+
vars:
6+
venv: "{{ virtualenv_path }}/openstack"
7+
pre_tasks:
8+
- name: Assert host is capable of virtualisation
9+
ansible.builtin.assert:
10+
that:
11+
- ansible_facts.virtualization_role == 'host'
12+
fail_msg: "Host is not capable of virtualisation"
13+
14+
- name: Set up openstack cli virtualenv
15+
ansible.builtin.pip:
16+
virtualenv: "{{ venv }}"
17+
virtualenv_command: /usr/bin/python3 -m venv
18+
name:
19+
- python-openstackclient
20+
state: latest
21+
extra_args: "{% if pip_upper_constraints_file %}-c {{ pip_upper_constraints_file }}{% endif %}"
22+
run_once: true
23+
delegate_to: "{{ groups['controllers'][0] }}"
24+
vars:
25+
ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}"
26+
27+
- name: Check compute service is disabled for maintenance
28+
ansible.builtin.command: >
29+
{{ venv }}/bin/openstack compute service list
30+
--service nova-compute
31+
--host {{ ansible_facts.nodename }}
32+
--format json
33+
register: compute_service
34+
environment: "{{ openstack_auth_env }}"
35+
delegate_to: "{{ groups['controllers'][0] }}"
36+
vars:
37+
ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}"
38+
39+
- name: Assert hypervisor is disabled for maintenance
40+
ansible.builtin.assert:
41+
that:
42+
- compute_service.stdout | from_json | length > 0
43+
- (compute_service.stdout | from_json | first).Status == "disabled"
44+
fail_msg: >
45+
Hypervisor {{ ansible_facts.nodename }} is not disabled for maintenance.
46+
Current service state: {{ compute_service.stdout | from_json }}
47+
delegate_to: "{{ groups['controllers'][0] }}"
48+
vars:
49+
ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}"
50+
51+
- name: Query instances
52+
ansible.builtin.command: >
53+
{{ venv }}/bin/openstack
54+
server list --host {{ ansible_facts.nodename }}
55+
--all-projects
56+
--status ACTIVE
57+
--format json
58+
register: instances
59+
environment: "{{ openstack_auth_env }}"
60+
delegate_to: "{{ groups['controllers'][0] }}"
61+
vars:
62+
ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}"
63+
64+
- name: Fail if there are instances still on the host
65+
ansible.builtin.fail:
66+
msg: >
67+
Instances still on {{ inventory_hostname }}: {{ instances.stdout | from_json }}
68+
when:
69+
- instances.stdout | from_json | length > 0
70+
delegate_to: "{{ groups['controllers'][0] }}"
71+
vars:
72+
ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}"
73+
tasks:
74+
- name: Set KVM module name
75+
ansible.builtin.set_fact:
76+
kvm_module_name: >-
77+
{{ 'kvm_intel'
78+
if 'GenuineIntel' in (ansible_facts.processor | default([]))
79+
else 'kvm_amd'
80+
if 'AuthenticAMD' in (ansible_facts.processor | default([]))
81+
else 'unknown' }}
82+
83+
- name: Assert supported CPU is present
84+
ansible.builtin.assert:
85+
that:
86+
- kvm_module_name != 'unknown'
87+
fail_msg: "Cannot detect either GenuineIntel or AuthenticAMD processor"
88+
89+
- name: Verify KVM module is loaded
90+
ansible.builtin.stat:
91+
path: "/sys/module/{{ kvm_module_name }}"
92+
register: kvm_module_path
93+
become: true
94+
95+
- name: Check if nested virtualization is supported
96+
ansible.builtin.command: "cat /sys/module/{{ kvm_module_name }}/parameters/nested"
97+
register: nested
98+
changed_when: false
99+
when: kvm_module_path.stat.exists
100+
become: true
101+
102+
- name: Disable nested virtualisation and restart nova_libvirt
103+
when: "not kvm_module_path.stat.exists or ('N' not in nested.stdout_lines | default([]) and '0' not in nested.stdout_lines | default([]))"
104+
become: true
105+
block:
106+
- name: Unload the KVM module
107+
community.general.modprobe:
108+
name: "{{ kvm_module_name }}"
109+
state: absent
110+
111+
- name: Ensure KVM module is enabled and nested virtualisation is disabled
112+
community.general.modprobe:
113+
name: "{{ kvm_module_name }}"
114+
params: 'nested=0'
115+
persistent: present
116+
117+
- name: Check again if nested virtualization is not supported
118+
ansible.builtin.command: "cat /sys/module/{{ kvm_module_name }}/parameters/nested"
119+
changed_when: false
120+
register: result
121+
failed_when: "'N' not in result.stdout_lines and '0' not in result.stdout_lines"
122+
123+
- name: Restart nova_libvirt container
124+
ansible.builtin.systemd_service:
125+
name: kolla-nova_libvirt-container
126+
state: restarted
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
---
2+
- name: Enable KVM nested virtualisation
3+
hosts: compute
4+
gather_facts: true
5+
vars:
6+
venv: "{{ virtualenv_path }}/openstack"
7+
pre_tasks:
8+
- name: Assert host is capable of virtualisation
9+
ansible.builtin.assert:
10+
that:
11+
- ansible_facts.virtualization_role == 'host'
12+
fail_msg: "Host is not capable of virtualisation"
13+
14+
- name: Set up openstack cli virtualenv
15+
ansible.builtin.pip:
16+
virtualenv: "{{ venv }}"
17+
virtualenv_command: /usr/bin/python3 -m venv
18+
name:
19+
- python-openstackclient
20+
state: latest
21+
extra_args: "{% if pip_upper_constraints_file %}-c {{ pip_upper_constraints_file }}{% endif %}"
22+
run_once: true
23+
delegate_to: "{{ groups['controllers'][0] }}"
24+
vars:
25+
ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}"
26+
27+
- name: Check compute service is disabled for maintenance
28+
ansible.builtin.command: >
29+
{{ venv }}/bin/openstack compute service list
30+
--service nova-compute
31+
--host {{ ansible_facts.nodename }}
32+
--format json
33+
register: compute_service
34+
environment: "{{ openstack_auth_env }}"
35+
delegate_to: "{{ groups['controllers'][0] }}"
36+
vars:
37+
ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}"
38+
39+
- name: Assert hypervisor is disabled for maintenance
40+
ansible.builtin.assert:
41+
that:
42+
- compute_service.stdout | from_json | length > 0
43+
- (compute_service.stdout | from_json | first).Status == "disabled"
44+
fail_msg: >
45+
Hypervisor {{ ansible_facts.nodename }} is not disabled for maintenance.
46+
Current service state: {{ compute_service.stdout | from_json }}
47+
delegate_to: "{{ groups['controllers'][0] }}"
48+
vars:
49+
ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}"
50+
51+
- name: Query instances
52+
ansible.builtin.command: >
53+
{{ venv }}/bin/openstack
54+
server list --host {{ ansible_facts.nodename }}
55+
--all-projects
56+
--status ACTIVE
57+
--format json
58+
register: instances
59+
environment: "{{ openstack_auth_env }}"
60+
delegate_to: "{{ groups['controllers'][0] }}"
61+
vars:
62+
ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}"
63+
64+
- name: Fail if there are instances still on the host
65+
ansible.builtin.fail:
66+
msg: >
67+
Instances still on {{ inventory_hostname }}: {{ instances.stdout | from_json }}
68+
when:
69+
- instances.stdout | from_json | length > 0
70+
delegate_to: "{{ groups['controllers'][0] }}"
71+
vars:
72+
ansible_host: "{{ hostvars[groups['controllers'][0]].ansible_host }}"
73+
tasks:
74+
- name: Set KVM module name
75+
ansible.builtin.set_fact:
76+
kvm_module_name: >-
77+
{{ 'kvm_intel'
78+
if 'GenuineIntel' in (ansible_facts.processor | default([]))
79+
else 'kvm_amd'
80+
if 'AuthenticAMD' in (ansible_facts.processor | default([]))
81+
else 'unknown' }}
82+
83+
- name: Assert supported CPU is present
84+
ansible.builtin.assert:
85+
that:
86+
- kvm_module_name != 'unknown'
87+
fail_msg: "Cannot detect either GenuineIntel or AuthenticAMD processor"
88+
89+
- name: Verify KVM module is loaded
90+
ansible.builtin.stat:
91+
path: "/sys/module/{{ kvm_module_name }}"
92+
register: kvm_module_path
93+
become: true
94+
95+
- name: Check if nested virtualization is supported
96+
ansible.builtin.command: "cat /sys/module/{{ kvm_module_name }}/parameters/nested"
97+
register: nested
98+
changed_when: false
99+
when: kvm_module_path.stat.exists
100+
become: true
101+
102+
- name: Enable nested virtualisation and restart nova_libvirt
103+
when: "not kvm_module_path.stat.exists or ('Y' not in nested.stdout_lines | default([]) and '1' not in nested.stdout_lines | default([]))"
104+
become: true
105+
block:
106+
- name: Unload the KVM module
107+
community.general.modprobe:
108+
name: "{{ kvm_module_name }}"
109+
state: absent
110+
111+
- name: Ensure KVM module is enabled and nested virtualisation is enabled
112+
community.general.modprobe:
113+
name: "{{ kvm_module_name }}"
114+
params: 'nested=1'
115+
persistent: present
116+
117+
- name: Check again if nested virtualization is supported
118+
ansible.builtin.command: "cat /sys/module/{{ kvm_module_name }}/parameters/nested"
119+
changed_when: false
120+
register: result
121+
failed_when: "'Y' not in result.stdout_lines and '1' not in result.stdout_lines"
122+
123+
- name: Restart nova_libvirt container
124+
ansible.builtin.systemd_service:
125+
name: kolla-nova_libvirt-container
126+
state: restarted
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
features:
3+
- |
4+
Add two new playbooks to support enabling or disabling of KVM nested virtualisation.
5+
security:
6+
- |
7+
Playbook added to support disabling of KVM nested virtualisation in response to
8+
`CVE-2026-53359 <https://www.openwall.com/lists/oss-security/2026/07/06/7>`__. This
9+
playbook acts as early mitigation to the vulnerability in lieu of kernel patches.

0 commit comments

Comments
 (0)