Skip to content

Commit 0e27962

Browse files
authored
Merge secureboot certificate expiry feature to 26.1-lcm (#7094)
2 parents c224ee1 + 537930d commit 0e27962

29 files changed

Lines changed: 661 additions & 15 deletions
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
---
2+
title: Handling Microsoft Secure Boot Certificate Expiry
3+
layout: default
4+
design_doc: true
5+
revision: 1
6+
status: draft
7+
---
8+
9+
## 1. Background
10+
11+
Microsoft Secure Boot certificates from 2011 are reaching end-of-life, and legacy VMs may still contain only the old certificate set. XenServer needs an out-of-band mechanism to update per-VM UEFI Secure Boot variables safely and at scale.
12+
13+
Scope of this design:
14+
15+
- Update certificate state tracking and update flow for VMs, snapshots, and templates
16+
- Provide API support for scheduling certificate updates on VM boot
17+
- Integrate xapi and varstored behavior for consistent state handling
18+
19+
## 2. System Overview
20+
21+
### 2.1 Out-of-band Update Mechanism
22+
23+
Certificate update is implemented as a dedicated API-driven workflow (not a plugin), so that:
24+
25+
- The interface is documented and SDK-generated
26+
- RBAC can be assigned precisely
27+
- xapi can route requests and coordinate host-side behavior consistently
28+
29+
### 2.2 Certificate State Tracking
30+
31+
A new VM field is introduced:
32+
33+
- `VM.secureboot_certificates_state` (enum, readonly)
34+
35+
States:
36+
37+
- `ok`: No update required (including non-applicable VM types)
38+
- `update_available`: Update required
39+
- `update_on_boot`: Update scheduled for next boot
40+
41+
~~~mermaid
42+
43+
stateDiagram
44+
update_available --> update_on_boot : Admin marks VM for update
45+
update_on_boot --> ok : VM boots, update succeeds
46+
update_on_boot --> update_on_boot : VM boots, update fails(retain state)
47+
ok --> update_available : recompute state(e.g. legacy VM import)
48+
49+
~~~
50+
51+
### 2.3 RBAC
52+
53+
The new update API follows VM-admin-level access, aligned with existing NVRAM-related VM operations.
54+
55+
## 3. Design for Components
56+
57+
### 3.1 VM Certificate State Model
58+
59+
`VM.secureboot_certificates_state` applies to these VM-class objects,
60+
61+
- VMs
62+
- Snapshots
63+
- Templates
64+
65+
Transition intent:
66+
67+
- Admin marks a VM for update: `update_available -> update_on_boot`
68+
- VM boots and update succeeds: `update_on_boot -> ok`
69+
- VM boots and update fails: remains `update_on_boot` or is reset to `update_available` based on update result handling
70+
71+
### 3.2 API: Mark/Unmark Update-on-Boot
72+
73+
New API:
74+
75+
- `VM.update_secureboot_certificates_on_boot(session, vm, mark)`
76+
77+
Behavior:
78+
79+
- `mark=true`: require current state `update_available`, then set `update_on_boot`
80+
- `mark=false`: require current state `update_on_boot`, then set `update_available`
81+
82+
Validation:
83+
84+
- Reject invalid transitions with `OPERATION_NOT_ALLOWED`
85+
86+
### 3.3 DB Upgrade and Import Handling
87+
88+
On toolstack restart after upgrade:
89+
90+
- Initialize `secureboot_certificates_state` for all VM records to `ok`
91+
- Re-evaluate NVRAM and set `update_available` where needed
92+
93+
Applied to:
94+
95+
- VMs
96+
- Snapshots
97+
- Non-default templates
98+
99+
Default templates remain `ok`.
100+
101+
For VM import and cross-pool migration:
102+
103+
- If imported metadata lacks `secureboot_certificates_state`, determine state from NVRAM and set it during import
104+
- If imported metadata contains `secureboot_certificates_state`, reserve the state during import
105+
106+
### 3.4 NVRAM and State Consistency
107+
108+
The certificate state must stay consistent with actual NVRAM content.
109+
110+
Key interface change:
111+
112+
- Extend `VM.set_NVRAM_EFI_variables` with optional parameter `update`, we call it `VM.set_NVRAM_EFI_variables_V2`
113+
114+
Rules:
115+
116+
- `update=yes` -> set state `ok`
117+
- `update=no` -> do not update state
118+
- omitted -> xapi runs certificate check helper and derives state
119+
120+
This ensures compatibility when old varstored instances are still running during rolling update windows.
121+
122+
### 3.5 Certificate Check Helper
123+
124+
A standalone program will be introduced, which xapi calls to determine the SecureBoot cert state
125+
126+
Inputs:
127+
128+
- `temp file path` which contains NVRAM EFI-variables data
129+
130+
Behavior:
131+
132+
- This program comes to use some common functions shared with varstored.
133+
- This program is launched by xapi, it is executed in a sandboxed and reduced privileges environment.
134+
- Xapi retrieves VM's NVRAM content from database and passes it to this program via command-line arguments.
135+
- If this program outputs `update_required`, xapi sets `VM.secureboot_certificates_state` to be `update_available`.
136+
- If this program outputs `update_ok`, xapi sets `VM.secureboot_certificates_state` to be `ok`.
137+
- On toolstack restart, during DB upgrade, this program is invoked to compute `VM.secureboot_certificates_state`. Since xapi process has not completed initialization at that point, this program cannot call any services of xapi.
138+
139+
### 3.6 Boot-time Automatic Update Path
140+
141+
When varstored initializes a VM and sees `secureboot_certificates_state=update_on_boot`, varstored does,
142+
143+
- Perform certificate update flow during boot-time initialization
144+
- Write updated NVRAM and synchronize state via `VM.set_NVRAM_EFI_variables_V2`
145+
146+
The `VM.set_NVRAM_EFI_variables_V2` interface performs same as `VM.set_NVRAM_EFI_variables`, uses the existing varstored-guard process to make calls to xapi.
147+
148+
If `VM.set_NVRAM_EFI_variables_V2` runs into error (e.g. there is something wrong with the communication with xapi),
149+
150+
- xapi does not update VM NVRAM and `VM.secureboot_certificates_state`
151+
- VM boot gets stuck at the firmware initialization stage, if the issue is not fixed, rebooting the VM will still encounter the same problem
152+
- Once the issue is fixed, admin can continue the secureboot certificate upgrade by VM reboot
153+
154+
### 3.7 End-to-end Workflow
155+
156+
1. Upgrade packages (`xapi-core`, `varstored`, related components)
157+
2. Restart toolstack
158+
3. xapi DB upgrade initializes and recalculates `secureboot_certificates_state`
159+
4. Admin marks selected VMs via `VM.update_secureboot_certificates_on_boot`
160+
5. VM reboot triggers varstored certificate update
161+
6. xapi updates state to reflect post-update NVRAM content
162+
163+
## 4. Out of Scope
164+
165+
- User-notification mechanism for certificate expiry
166+
- Custom certificate workflow
167+
- Template/snapshot feature expansion beyond state tracking and conversion behavior
168+
- OS-specific test-process guidance
169+
- VM with Secure Boot PCR7 binding (e.g. Windows bitlocker), provide customer documentation to guide how to resolve such issues

dune-project

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050

5151
(package
5252
(name tgroup)
53+
(synopsis "Thread group management library")
5354
(depends xapi-log xapi-stdext-unix))
5455

5556
(package

ocaml/idl/datamodel_common.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ open Datamodel_roles
1010
to leave a gap for potential hotfixes needing to increment the schema version.*)
1111
let schema_major_vsn = 5
1212

13-
let schema_minor_vsn = 794
13+
let schema_minor_vsn = 795
1414

1515
(* Historical schema versions just in case this is useful later *)
1616
let rio_schema_major_vsn = 5

ocaml/idl/datamodel_lifecycle.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ let prototyped_of_field = function
139139
Some "25.15.0"
140140
| "VM_guest_metrics", "netbios_name" ->
141141
Some "24.28.0"
142+
| "VM", "secureboot_certificates_state" ->
143+
Some "26.1.12-next"
142144
| "VM", "groups" ->
143145
Some "24.19.1"
144146
| "VM", "pending_guidances_full" ->
@@ -281,6 +283,8 @@ let prototyped_of_message = function
281283
Some "24.0.0"
282284
| "VM", "sysprep" ->
283285
Some "25.24.0"
286+
| "VM", "update_secureboot_certificates_on_boot" ->
287+
Some "26.1.12-next"
284288
| "VM", "get_secureboot_readiness" ->
285289
Some "24.17.0"
286290
| "VM", "set_uefi_mode" ->

ocaml/idl/datamodel_vm.ml

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2352,7 +2352,45 @@ let set_HVM_boot_policy =
23522352
let set_NVRAM_EFI_variables =
23532353
call ~flags:[`Session] ~name:"set_NVRAM_EFI_variables"
23542354
~lifecycle:[(Published, rel_naples, "")]
2355-
~params:[(Ref _vm, "self", "The VM"); (String, "value", "The value")]
2355+
~versioned_params:
2356+
[
2357+
{
2358+
param_type= Ref _vm
2359+
; param_name= "self"
2360+
; param_doc= "The VM"
2361+
; param_release= naples_release
2362+
; param_default= None
2363+
}
2364+
; {
2365+
param_type= String
2366+
; param_name= "value"
2367+
; param_doc= "The EFI-variables value"
2368+
; param_release= naples_release
2369+
; param_default= None
2370+
}
2371+
; {
2372+
param_type=
2373+
Enum
2374+
( "update_status"
2375+
, [
2376+
("yes", "Set secureboot_certificates_state to ok")
2377+
; ("no", "Leave secureboot_certificates_state unchanged")
2378+
; ( "unspecified"
2379+
, "Check certificates and update \
2380+
secureboot_certificates_state accordingly"
2381+
)
2382+
]
2383+
)
2384+
; param_name= "update"
2385+
; param_doc=
2386+
"If 'yes', set secureboot_certificates_state to ok. If 'no', keep \
2387+
the current secureboot_certificates_state unchanged. If omitted \
2388+
(defaults to 'unspecified'), run certificate check to determine \
2389+
the state."
2390+
; param_release= numbered_release "26.1.12-next"
2391+
; param_default= Some (VEnum "unspecified")
2392+
}
2393+
]
23562394
~hide_from_docs:true ~allowed_roles:_R_LOCAL_ROOT_ONLY ()
23572395

23582396
let restart_device_models =
@@ -2507,6 +2545,40 @@ let set_uefi_mode =
25072545
~result:(String, "Result from the varstore-sb-state call")
25082546
~doc:"Set the UEFI mode of a VM" ~allowed_roles:_R_POOL_ADMIN ()
25092547

2548+
let vm_secureboot_certificates_state =
2549+
Enum
2550+
( "vm_secureboot_certificates_state"
2551+
, [
2552+
( "ok"
2553+
, "The VM's certificates do not need to be updated (including the case \
2554+
where Secure Boot does not apply to this VM, e.g. BIOS VM)."
2555+
)
2556+
; ( "update_available"
2557+
, "The Secure Boot certificates are due to expire or have already \
2558+
expired."
2559+
)
2560+
; ( "update_on_boot"
2561+
, "An update of the certificates will be triggered whenever the VM \
2562+
boots. This includes VM.start, VM.reboot and a guest-triggered \
2563+
reboot."
2564+
)
2565+
]
2566+
)
2567+
2568+
let update_secureboot_certificates_on_boot =
2569+
call ~name:"update_secureboot_certificates_on_boot" ~lifecycle:[]
2570+
~params:
2571+
[
2572+
(Ref _vm, "self", "The VM")
2573+
; ( Bool
2574+
, "mark"
2575+
, "If true: mark certificates for update on next boot. If false: \
2576+
remove the mark"
2577+
)
2578+
]
2579+
~doc:"Mark or unmark secure boot certificate update on VM boot"
2580+
~allowed_roles:_R_VM_ADMIN ()
2581+
25102582
let vm_secureboot_readiness =
25112583
Enum
25122584
( "vm_secureboot_readiness"
@@ -2681,6 +2753,7 @@ let t =
26812753
; restart_device_models
26822754
; set_uefi_mode
26832755
; get_secureboot_readiness
2756+
; update_secureboot_certificates_on_boot
26842757
; set_blocked_operations
26852758
; add_to_blocked_operations
26862759
; remove_from_blocked_operations
@@ -3291,6 +3364,11 @@ let t =
32913364
doesn't need to"
32923365
; field ~qualifier:DynamicRO ~lifecycle:[] ~ty:(Set (Ref _vm_group))
32933366
"groups" "VM groups associated with the VM"
3367+
; field ~qualifier:DynamicRO ~lifecycle:[]
3368+
~ty:vm_secureboot_certificates_state
3369+
~default_value:(Some (VEnum "ok")) "secureboot_certificates_state"
3370+
"The state of the Secure Boot certificates, showing whether an \
3371+
update is available, already scheduled, or not needed."
32943372
]
32953373
)
32963374
()

ocaml/idl/schematest.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ let hash x = Digest.string x |> Digest.to_hex
33
(* BEWARE: if this changes, check that schema has been bumped accordingly in
44
ocaml/idl/datamodel_common.ml, usually schema_minor_vsn *)
55

6-
let last_known_schema_hash = "51e48060a7bc8427039d56bca269db49"
6+
let last_known_schema_hash = "87dce17b30693b57292d1168002b856f"
77

88
let current_schema_hash : string =
99
let open Datamodel_types in

0 commit comments

Comments
 (0)