Skip to content

Commit 32072b4

Browse files
committed
[RFC 0164] Bootspec v2
This introduces the second revision of RFC-0125, Bootspec, addressing the feedback we received in NixOS#125 and building on our experience of Bootspec v1.
1 parent c8569f6 commit 32072b4

File tree

1 file changed

+300
-0
lines changed

1 file changed

+300
-0
lines changed

rfcs/0164-bootspecv2.md

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
---
2+
feature: bootspec_v2
3+
start-date: 2022-11-01
4+
author: Ryan Lahfa
5+
co-authors:
6+
shepherd-team:
7+
shepherd-leader:
8+
---
9+
10+
# Summary
11+
[summary]: #summary
12+
13+
Bootspec v2 is the second revision of the Bootspec document, introduced in RFC-0125.
14+
15+
These facts are used as the primary input for bootloader backends like systemd-boot and grub, for creating files in `/boot/loader/entries/` and `grub.cfg`.
16+
17+
In this proposal, we aim to tackle known weaknesses of Bootspec v1, namely:
18+
19+
- Lack of devicetree
20+
- Multiple initrds support
21+
- Rework of initrd secrets mechanism
22+
- Extra metadata that was discovered to be useful during the year of usage of Bootspec v1
23+
24+
This document describes **Bootspec v2**.
25+
26+
# Motivation
27+
[motivation]: #motivation
28+
29+
The motivation of Bootspec v1 remains but we address the v1 weaknesses and
30+
includes our experience of running with it for a while.
31+
32+
## Multiple initrds
33+
34+
The Boot Loader Specification, developed by the Userspace API group, allows the
35+
specification of multiple initrd entries [1], with the purpose of merging them into
36+
CPIO archives. This functionality is valuable in the systemd-stub ecosystem,
37+
where various types of initrds are combined, including specific credential
38+
initrds, global credential initrds, system extension initrds, PCR signature
39+
initrds, and PCR public key initrds. Some initrds, such as credential initrds,
40+
are dynamically generated from an EFI System Partition (ESP) location. [2]
41+
42+
Moreover, an additional initrd is frequently used to store CPU microcode. To
43+
ensure compatibility and flexibility, it is essential to rework the initrd
44+
support in the Boot Loader Specification. The proposed changes aim to treat
45+
initrds as a set, allowing bootloaders to handle a list of initrds or a single
46+
initrd in cases where multiple initrds are not supported.
47+
48+
[1]: https://uapi-group.org/specifications/specs/boot_loader_specification/#type-1-boot-loader-entry-keys
49+
[2]: https://github.com/systemd/systemd/blob/main/src/boot/efi/stub.c#L778-L793
50+
51+
## Initrd secrets
52+
53+
Initrd secrets play a crucial role in the NixOS ecosystem, but they have raised
54+
concerns, including security vulnerabilities (e.g., [GHSA-3rvf-24q2-24ww](https://github.com/NixOS/calamares-nixos-extensions/security/advisories/GHSA-3rvf-24q2-24ww
55+
)) and
56+
issues with booting in the Heads platform (e.g., [Issue #1348](https://github.com/linuxboot/heads/issues/1348.
57+
)).
58+
59+
Initrd secrets serve to protect boot-time secrets from exposure within the Nix
60+
store. This is achieved by using runtime scripts that append CPIO archives to
61+
the generation's initrd during bootloader installation. However, the term
62+
"initrd secrets" can be misleading, as the secrets are plaintext and can be
63+
accessed from the ESP partition, offering limited confidentiality and no
64+
integrity.
65+
66+
In practice, initrd secrets are often employed to establish stable fingerprints
67+
for SSH servers within the initrd, aiding in remote disk decryption on servers.
68+
To address these issues, this RFC proposes moving away from the "appender
69+
script" model in Bootspec v1 and instead adopting a hash map format to
70+
represent secrets and their corresponding values.
71+
72+
This change allows for greater flexibility and enhanced security. For users in
73+
the NixOS ecosystem relying on systemd and its semantics, the proposal suggests
74+
offering a systemd-credentials approach for handling initrd secrets,
75+
potentially enabling encryption using TPM2 if available, or a straightforward
76+
key if TPM2 is not present. Additionally, this approach provides the means to
77+
build using the `LoadEncryptedCredential` abstraction.
78+
79+
For those not using systemd, the hash map format offers full flexibility to
80+
bootloader implementations to determine how to add the secrets, such as
81+
appending to the initrd or other methods.
82+
83+
**Key Takeaway**: This RFC recommends changing the format of Bootspec v2 to support
84+
`initrdSecrets` as a hashmap with string keys and values. The key represents a
85+
name used to organize secrets, and the value denotes an accessible path during
86+
bootloader installation.
87+
88+
_Example_
89+
90+
```
91+
"initrdSecrets": {
92+
"my-private-key": "/etc/nixos/secrets/wireguard-key"
93+
}
94+
```
95+
96+
## Device Tree and Device Tree Overlays
97+
98+
Non-x86 systems often rely on device trees to inform firmware or bootloader about the available devices and hardware support.
99+
100+
There are two distinct requirements for device trees:
101+
102+
- generic hardware support, where firmware or bootloader selects the appropriate device tree
103+
- device-specific support, where a hardcoded device tree is required.
104+
105+
The latter indicates a potentially problematic, non-upstreamed, or in-development platform.
106+
107+
The current Bootspec v1 does not formally encode information about hardcoded device trees or the folder containing available device trees. As a result, unformalized extensions are needed to address these fundamental use cases.
108+
109+
Regarding overlays in NixOS, any expressed overlay is incorporated into the final device tree. This eliminates the necessity to formalize an additional overlays field list, as overlays can be transformed into device trees as needed.
110+
111+
# Goals
112+
[goals]: #goals
113+
114+
- Improve non-x86 support in Bootspec, emphasizing the importance of
115+
devicetrees.
116+
- Address and reduce the risks associated with initrd secrets, providing a
117+
default "secure" implementation within the systemd ecosystem and offering
118+
flexibility for other ecosystems.
119+
- Enhance initrd flexibility, allowing developers to optimize their systems by
120+
supporting multiple initrds, including one for microcode and other specific
121+
purposes.
122+
123+
### Non-Goals
124+
[non-goals]: #non-goals
125+
126+
- Store TPM2-related information (hashes)
127+
We believe that Bootspec is still too immature for this and pcrlock
128+
(https://github.com/systemd/systemd/pull/28891) offers a more reliable and
129+
robust solution for generating signed PCR policies.
130+
- Supporting SecureBoot.
131+
Secure Boot has one maintained implementation that is being upstreamed: https://github.com/nix-community/lanzaboote
132+
which was enabled by Bootspec v1.
133+
- Specifying how to discover generations. This is desirable, but should not be tied to bootspec directly since bootspec may be useful with diverse discovery mechanisms.
134+
135+
# Proposed Solution
136+
[proposed-solution]: #proposed-solution
137+
138+
- `initrd` will be removed from the v2
139+
- `initrds` will be introduced as a list of initrd (compressed or uncompressed
140+
CPIO archives), this list can be empty, but the field is **required**
141+
nonetheless.
142+
- `initrdSecrets` will now be a hashmap of strings (key) and strings (value)
143+
where the key is the "name" of a secret and the value is the "path" towards a
144+
secret.
145+
- `fdtdir` will be introduced as an opaque string to a directory in a shape as
146+
expected by U-Boot extlinux `FDTDIR` directive — it is **optional**
147+
- `devicetree` will be introduced as a path to a single devicetree that will be
148+
hardcoded — it is **optional**
149+
150+
All the Bootspec ecosystem will be updated as part of this specification.
151+
152+
### Bootspec Format v2
153+
[format-v2]: #format-v2
154+
155+
156+
Using the following JSON:
157+
158+
```json5
159+
{
160+
// Toplevel key describing the version of the specification used in the document
161+
"org.nixos.bootspec.v2": {
162+
// (Required) System type the bootspec is intended for (e.g. `x86_64-linux`, `aarch64-linux`)
163+
"system": "x86_64-linux",
164+
165+
// (Required) Path to the stage-2 init, executed by the initrd (if present)
166+
"init": "/nix/store/xxx-nixos-system-xxx/init",
167+
168+
// (Required) List of paths to the initrd, can be empty
169+
"initrds": [ "/nix/store/xxx-initrd-linux/initrd" ],
170+
171+
// (Required) Hash map of desired secrets for that generation inside of the initrd.
172+
// Implementors of a bootloader installation procedure should examine their options
173+
// to securely make available the secret inside the initrd phase.
174+
// This may involve leveraging TPM2 via systemd-credentials or any measure you deem
175+
// to be reasonable in the context.
176+
// The legacy behavior is to prepare a CPIO archive for each file and
177+
// extend the `initrds` fields with those CPIO archives.
178+
// Make sure the location where the secrets are dropped in the initrd are visible
179+
// for the user.
180+
"initrdSecrets": {
181+
"my-private-key": "/etc/nixos/secrets/wireguard-private-key",
182+
}
183+
184+
// (Required) Path to the kernel image
185+
"kernel": "/nix/store/xxx-linux/bzImage",
186+
187+
// (Required) Kernel commandline options
188+
"kernelParams": [
189+
"amd_iommu=on",
190+
"amd_iommu=pt",
191+
"iommu=pt",
192+
"kvm.ignore_msrs=1",
193+
"kvm.report_ignored_msrs=0",
194+
"udev.log_priority=3",
195+
"systemd.unified_cgroup_hierarchy=1",
196+
"loglevel=4"
197+
],
198+
199+
// (Required) The label of the system. It should contain the operating system, kernel version,
200+
// and other user-relevant information to identify the system. This corresponds
201+
// loosely to `config.system.nixos.label`.
202+
"label": "NixOS 21.11.20210810.dirty (Linux 5.15.30)",
203+
204+
// (Required) Top level path of the closure, in case some spelunking is required
205+
"toplevel": "/nix/store/xxx-nixos-system-xxx",
206+
207+
// (Optional) FDTDIR is assumed to be a path to a directory in the shape
208+
// of what `FDTDIR` in U-Boot extlinux would expect.
209+
// At the time of writing, it is assumed to follow the kernel output shape.
210+
"fdtdir": "/nix/store/xxx-uboot-fdtdir",
211+
212+
// (Optional) devicetree is assumed to be path to a single devicetree file
213+
// which will be hardcoded for that generation.
214+
"devicetree": "/nix/store/xxx-arm64-machine/my-device.dtb"
215+
},
216+
// The top-level object may contain arbitrary further keys ("extensions"), whose semantics may be defined by third parties.
217+
// The use of reverse-domain-name namespacing is recommended in order to avoid name collisions.
218+
219+
// (Optional) Specialisations are an extension to the specification which allows bundling multiple variants of a NixOS configuration with a single parent.
220+
// These are shaped like the top level; to be precise:
221+
// - Each entry in the toplevel "org.nixos.specialisation.v2" object represents a specialisation.
222+
// - In order for the top-level document to be a valid v2 bootspec, each specialisation must have a valid "org.nixos.bootspec.v2" key whose value conforms to the same schema as the toplevel "org.nixos.bootspec.v2" object.
223+
// - The behaviour of nested specialisations (i.e. entries in "org.nixos.specialisation.v2" which themselves contain the "org.nixos.specialisation.v2" key) is not defined.
224+
// - In particular, there is no expectation that such nested specialisations will be handled by consumers of bootspec documents.
225+
// - Each specialisation document may contain arbitrary further keys (extensions), like the top-level document.
226+
// - The semantics of these should be the same as when these keys are used at the top level, but only apply for the given specialisation.
227+
"org.nixos.specialisation.v2": {
228+
// Each key in this object corresponds to a specialisation as defined by the `specialisation.<name>` NixOS option.
229+
"<name>": {
230+
"org.nixos.bootspec.v2": {
231+
// See above
232+
}
233+
}
234+
},
235+
}
236+
```
237+
238+
An *optional* field means: a field that is either missing or present, but **never `null`**.
239+
240+
### Risks
241+
[risks]: #risks
242+
243+
- Some of the bootloader backends are quite complicated, and in many cases have
244+
inadequate tests. We could accidentally break corner cases.
245+
- The bootloader backends are inherently a weak point for NixOS, as it is our
246+
last option for rolling back. We cannot roll back a broken bootloader. This
247+
and the previous point are risks, but also help demonstrate the value of
248+
reducing the amount of code and complexity in the generator.
249+
250+
### Milestones
251+
[milestones]: #milestones
252+
253+
- Update Bootspec with the version 2 of that specification.
254+
- Implement changes inside at least one bootloader backend.
255+
256+
# FAQ
257+
[faq]: #faq
258+
259+
Familiarize yourself with Bootspec v1 which may already contain answers.
260+
261+
## How will I be able to express complicated logic for initrd secrets, e.g. dynamic secrets?
262+
263+
Some users may have used the appender script to provide dynamic logic that
264+
provides a secret at activation time rather than storing it on the long run.
265+
266+
While this usecase is interesting, it is very advanced and the footgun that
267+
initrd secrets represent cannot be made up by our only goal of supporting that
268+
usecase.
269+
270+
Authors may propose to think about how a filesystem could implement the dynamic
271+
fetching at activation time, e.g. a FUSE secretfs that will dynamically query
272+
the secret engine for a secret and make it available for short time.
273+
274+
If any logic requires user interaction, it is preferable to invest in a custom
275+
bootloader installer logic and use the static fields to refer to secrets that
276+
will be requested. We never defined "path" on purpose as it could be a network
277+
path or an application-specific path.
278+
279+
It could even be a UEFI device path!
280+
281+
# Open Questions
282+
[open-questions]: #open-questions
283+
284+
- Should the `initrdSecrets` work itself with systemd-credentials to load
285+
further credential and have a closed loop of credentials, this would require
286+
the activation to run inside systemd:
287+
https://github.com/NixOS/nixpkgs/pull/258571 !
288+
- What are lessons we can learn from
289+
https://github.com/aarch64-laptops/edk2/tree/dtbloader-app#dtbloader for
290+
devicetrees manipulation?
291+
292+
# Future Work
293+
[future]: #future-work
294+
295+
- Continue the migration from filesystem-spelunking into using the bootspec
296+
data.
297+
- Implement a systemd-credentials based of `initrdSecrets`
298+
- Implement an [Verified Boot for
299+
Embedded](https://u-boot.readthedocs.io/en/latest/develop/vbe.html)
300+
bootloader installation script using bootspec data

0 commit comments

Comments
 (0)