]>
Commit | Line | Data |
---|---|---|
6538c0ef LP |
1 | --- |
2 | title: Safely Building Images | |
3 | category: Concepts | |
4 | layout: default | |
5 | SPDX-License-Identifier: LGPL-2.1-or-later | |
6 | --- | |
7 | ||
f04aac3d | 8 | # Building Images Safely |
6538c0ef LP |
9 | |
10 | In many scenarios OS installations are shipped as pre-built images, that | |
11 | require no further installation process beyond simple `dd`-ing the image to | |
59652fff | 12 | disk and booting it up. |
13 | When building such "golden" OS images for | |
6538c0ef LP |
14 | `systemd`-based OSes a few points should be taken into account. |
15 | ||
16 | Most of the points described here are implemented by the | |
17 | [`mkosi`](https://github.com/systemd/mkosi) OS image builder developed and | |
59652fff | 18 | maintained by the systemd project. |
19 | If you are using or working on another image | |
6538c0ef LP |
20 | builder it's recommended to keep the following concepts and recommendations in |
21 | mind. | |
22 | ||
23 | ## Resources to Reset | |
24 | ||
25 | Typically the same OS image shall be deployable in multiple instances, and each | |
26 | instance should automatically acquire its own identifying credentials on first | |
27 | boot. For that it's essential to: | |
28 | ||
59652fff | 29 | 1. Remove the [`/etc/machine-id`](https://www.freedesktop.org/software/systemd/man/machine-id.html) |
30 | file or write the string `uninitialized\n` into it. | |
31 | This file is supposed to carry a 128-bit identifier unique to the system. | |
32 | Only when it is reset it will be auto-generated on first boot and thus be truly unique. | |
33 | If this file is not reset, and carries a valid ID every instance of the system will come | |
6538c0ef | 34 | up with the same ID and that will likely lead to problems sooner or later, |
59652fff | 35 | as many network-visible identifiers are commonly derived from the machine ID, |
36 | for example, IPv6 addresses or transient MAC addresses. | |
6538c0ef | 37 | |
59652fff | 38 | 2. Remove the `/var/lib/systemd/random-seed` file(see |
3976da02 | 39 | [`systemd-random-seed(8)`](https://www.freedesktop.org/software/systemd/man/systemd-random-seed.service.html)), |
59652fff | 40 | which is used to seed the kernel's random pool on boot. |
41 | If this file is shipped pre-initialized, every instance will seed its random pool with the | |
6538c0ef | 42 | same random data that is included in the image, and thus possibly generate |
59652fff | 43 | random data that is more similar to other instances booted off the same image than advisable. |
6538c0ef LP |
44 | |
45 | 3. Remove the `/loader/random-seed` file (see | |
13a5ffa4 | 46 | [`systemd-boot(7)`](https://www.freedesktop.org/software/systemd/man/systemd-boot.html)) |
59652fff | 47 | from the UEFI System Partition (ESP), in case the `systemd-boot` boot loader is used in the image. |
6538c0ef | 48 | |
3976da02 LP |
49 | 4. It might also make sense to remove |
50 | [`/etc/hostname`](https://www.freedesktop.org/software/systemd/man/hostname.html) | |
51 | and | |
13a5ffa4 | 52 | [`/etc/machine-info`](https://www.freedesktop.org/software/systemd/man/machine-info.html) |
6538c0ef LP |
53 | which carry additional identifying information about the OS image. |
54 | ||
005b1267 LP |
55 | 5. Remove `/var/lib/systemd/credential.secret` which is used for protecting |
56 | service credentials, see | |
57 | [`systemd.exec(5)`](https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Credentials) | |
58 | and | |
59 | [`systemd-creds(1)`](https://www.freedesktop.org/software/systemd/man/systemd-creds.html) | |
60 | for details. Note that by removing this file access to previously encrypted | |
61 | credentials from this image is lost. The file is automatically generated if | |
62 | a new credential is encrypted and the file does not exist yet. | |
63 | ||
6538c0ef LP |
64 | ## Boot Menu Entry Identifiers |
65 | ||
3976da02 LP |
66 | The |
67 | [`kernel-install(8)`](https://www.freedesktop.org/software/systemd/man/kernel-install.html) | |
5c90c67a | 68 | logic used to generate |
db811444 ZJS |
69 | [Boot Loader Specification Type #1](https://uapi-group.org/specifications/specs/boot_loader_specification/#type-1-boot-loader-specification-entries) |
70 | entries by default uses the machine ID as stored in `/etc/machine-id` for | |
59652fff | 71 | naming boot menu entries and the directories in the ESP to place kernel images in. |
72 | This is done in order to allow multiple installations of the same OS on the | |
db811444 ZJS |
73 | same system without conflicts. However, this is problematic if the machine ID |
74 | shall be generated automatically on first boot: if the ID is not known before | |
75 | the first boot it cannot be used to name the most basic resources required for | |
76 | the boot process to complete. | |
6538c0ef LP |
77 | |
78 | Thus, for images that shall acquire their identity on first boot only, it is | |
59652fff | 79 | required to use a different identifier for naming boot menu entries. |
80 | To allow this the `kernel-install` logic knows the generalized *entry* *token* concept, | |
6538c0ef | 81 | which can be a freely chosen string to use for identifying the boot menu |
59652fff | 82 | resources of the OS. |
83 | If not configured explicitly it defaults to the machineID. | |
84 | The file `/etc/kernel/entry-token` may be used to configure this string explicitly. | |
85 | Thus, golden image builders should write a suitable identifier into | |
e347d53a | 86 | this file, for example, the `IMAGE_ID=` or `ID=` field from |
3976da02 | 87 | [`/etc/os-release`](https://www.freedesktop.org/software/systemd/man/os-release.html) |
59652fff | 88 | (also see below). |
89 | It is recommended to do this before the `kernel-install` functionality is invoked (i.e. before the package manager is used to install | |
6538c0ef LP |
90 | packages into the OS tree being prepared), so that the selected string is |
91 | automatically used for all entries to be generated. | |
92 | ||
93 | ## Booting with Empty `/var/` and/or Empty Root File System | |
94 | ||
95 | `systemd` is designed to be able to come up safely and robustly if the `/var/` | |
96 | file system or even the entire root file system (with exception of `/usr/`, | |
59652fff | 97 | i.e. the vendor OS resources) is empty (i.e. "unpopulated"). |
98 | With this in mind it's relatively easy to build images that only ship a `/usr/` tree, and | |
6538c0ef LP |
99 | otherwise carry no other data, populating the rest of the directory hierarchy |
100 | on first boot as needed. | |
101 | ||
102 | Specifically, the following mechanisms are in place: | |
103 | ||
6f396138 | 104 | 1. The `switch-root` logic in systemd, that is used to switch from the initrd |
59652fff | 105 | phase to the host will create the basic OS hierarchy skeleton if missing. |
106 | It will create a couple of directories strictly necessary to boot up | |
6538c0ef LP |
107 | successfully, plus essential symlinks (such as those necessary for the |
108 | dynamic loader `ld.so` to function). | |
109 | ||
110 | 2. PID 1 will initialize `/etc/machine-id` automatically if not initialized yet | |
111 | (see above). | |
112 | ||
3976da02 LP |
113 | 3. The |
114 | [`nss-systemd(8)`](https://www.freedesktop.org/software/systemd/man/nss-systemd.html) | |
115 | glibc NSS module ensures the `root` and `nobody` users and groups remain | |
116 | resolvable, even without `/etc/passwd` and `/etc/group` around. | |
6538c0ef LP |
117 | |
118 | 4. The | |
3976da02 | 119 | [`systemd-sysusers(8)`](https://www.freedesktop.org/software/systemd/man/systemd-sysusers.service.html) |
d8b67e05 | 120 | component will automatically populate `/etc/passwd` and `/etc/group` on |
6538c0ef LP |
121 | first boot with further necessary system users. |
122 | ||
123 | 5. The | |
3976da02 | 124 | [`systemd-tmpfiles(8)`](https://www.freedesktop.org/software/systemd/man/systemd-tmpfiles-setup.service.html) |
6538c0ef LP |
125 | component ensures that various files and directories below `/etc/`, `/var/` |
126 | and other places are created automatically at boot if missing. Unlike the | |
127 | directories/symlinks created by the `switch-root` logic above this logic is | |
128 | extensible by packages, and can adjust access modes, file ownership and | |
129 | more. Among others this will also link `/etc/os-release` → | |
130 | `/usr/lib/os-release`, ensuring that the OS release information is | |
131 | unconditionally accessible through `/etc/os-release`. | |
132 | ||
3976da02 LP |
133 | 6. The |
134 | [`nss-myhostname(8)`](https://www.freedesktop.org/software/systemd/man/nss-myhostname.html) | |
135 | glibc NSS module will ensure the local host name as well as `localhost` | |
136 | remains resolvable, even without `/etc/hosts` around. | |
6538c0ef LP |
137 | |
138 | With these mechanisms the hierarchies below `/var/` and `/etc/` can be safely | |
59652fff | 139 | and robustly populated on first boot, so that the OS can safely boot up. |
140 | Note that some auxiliary package are not prepared to operate correctly if their | |
6538c0ef | 141 | configuration data in `/etc/` or their state directories in `/var/` are |
59652fff | 142 | missing. |
143 | ||
144 | This can typically be addressed via `systemd-tmpfiles` lines that | |
145 | ensure the missing files and directories are created if missing. | |
146 | In particular, configuration files that are necessary for operation can be automatically | |
6538c0ef | 147 | copied or symlinked from the `/usr/share/factory/etc/` tree via the `C` or `L` |
59652fff | 148 | line types. |
149 | ||
150 | That said, we recommend that all packages safely fall back to | |
6538c0ef LP |
151 | internal defaults if their configuration is missing, making such additional |
152 | steps unnecessary. | |
153 | ||
154 | Note that while `systemd` itself explicitly supports booting up with entirely | |
155 | unpopulated images (`/usr/` being the only required directory to be populated) | |
156 | distributions might not be there yet: depending on your distribution further, | |
157 | manual work might be required to make this scenario work. | |
158 | ||
159 | ## Adapting OS Images to Storage | |
160 | ||
161 | Typically, if an image is `dd`-ed onto a target disk it will be minimal: | |
162 | i.e. only consist of necessary vendor data, and lack "payload" data, that shall | |
59652fff | 163 | be individual to the system, and dependent on host parameters. |
164 | On first boot, the OS should take possession of the backing storage as necessary, dynamically | |
6538c0ef LP |
165 | using available space. Specifically: |
166 | ||
167 | 1. Additional partitions should be created, that make no sense to ship | |
59652fff | 168 | pre-built in the image. |
169 | For example, `/tmp/` or `/home/` partitions, or even `/var/` or the root file system (see above). | |
6538c0ef LP |
170 | |
171 | 2. Additional partitions should be created that shall function as A/B | |
59652fff | 172 | secondaries for partitions shipped in the original image. |
173 | In other words: if the `/usr/` file system shall be updated in an A/B fashion it typically | |
6538c0ef LP |
174 | makes sense to ship the original A file system in the deployed image, but |
175 | create the B partition on first boot. | |
176 | ||
177 | 3. Partitions covering only a part of the disk should be grown to the full | |
178 | extent of the disk. | |
179 | ||
180 | 4. File systems in uninitialized partitions should be formatted with a file | |
181 | system of choice. | |
182 | ||
183 | 5. File systems covering only a part of a partition should be grown to the full | |
184 | extent of the partition. | |
185 | ||
186 | 6. Partitions should be encrypted with cryptographic keys generated locally on | |
187 | the machine the system is first booted on, ensuring these keys remain local | |
188 | and are not shared with any other instance of the OS image. | |
189 | ||
190 | Or any combination of the above: i.e. first create a partition, then encrypt | |
191 | it, then format it. | |
192 | ||
193 | `systemd` provides multiple tools to implement the above logic: | |
194 | ||
195 | 1. The | |
3976da02 | 196 | [`systemd-repart(8)`](https://www.freedesktop.org/software/systemd/man/systemd-repart.service.html) |
6538c0ef | 197 | component may manipulate GPT partition tables automatically on boot, growing |
59652fff | 198 | partitions or adding in partitions taking the backing storage size into account. |
199 | It can also encrypt partitions automatically it creates (even bind | |
200 | to TPM2, automatically) and populate partitions from various sources. | |
201 | It does this all in a robust fashion so that aborted invocations will not leave | |
6538c0ef LP |
202 | incompletely set up partitions around. |
203 | ||
204 | 2. The | |
3976da02 | 205 | [`systemd-growfs@(8).service`](https://www.freedesktop.org/software/systemd/man/systemd-growfs.html) |
6538c0ef | 206 | tool can automatically grow a file system to the partition it is contained |
3976da02 LP |
207 | in. The `x-systemd.growfs` mount option in `/etc/fstab` is sufficient to |
208 | enable this logic for specific mounts. Alternatively appropriately set up | |
209 | partitions can set GPT partition flag 59 to request this behaviour, see the | |
db811444 ZJS |
210 | [Discoverable Partitions Specification](https://uapi-group.org/specifications/specs/discoverable_partitions_specification) |
211 | for details. If the file system is already grown it executes no operation. | |
6538c0ef LP |
212 | |
213 | 3. Similar, the `systemd-makefs@.service` and `systemd-makeswap@.service` | |
214 | services can format file systems and swap spaces before first use, if they | |
215 | carry no file system signature yet. The `x-systemd.makefs` mount option in | |
216 | `/etc/fstab` may be used to request this functionality. | |
217 | ||
218 | ## Provisioning Image Settings | |
219 | ||
220 | While a lot of work has gone into ensuring `systemd` systems can safely boot | |
221 | with unpopulated `/etc/` trees, it sometimes is desirable to set a couple of | |
59652fff | 222 | basic settings *after* `dd`-ing the image to disk, but *before* first boot. |
223 | For this the tool | |
3976da02 | 224 | [`systemd-firstboot(1)`](https://www.freedesktop.org/software/systemd/man/systemd-firstboot.html) |
6538c0ef LP |
225 | can be useful, with its `--image=` switch. It may be used to set very basic |
226 | settings, such as the root password or hostname on an OS disk image or | |
227 | installed block device. | |
228 | ||
229 | ## Distinguishing First Boot | |
230 | ||
231 | For various purposes it's useful to be able to distinguish the first boot-up of | |
59652fff | 232 | the system from later boot-ups (for example, to set up TPM hardware specifically, or register a system somewhere). |
233 | `systemd` provides mechanisms to implement that. | |
234 | Specifically, the `ConditionFirstBoot=` and `AssertFirstBoot=` settings may be used to conditionalize units to only run on first boot. | |
235 | See [`systemd.unit(5)`](https://www.freedesktop.org/software/systemd/man/systemd.unit.html#ConditionFirstBoot=) | |
6538c0ef LP |
236 | for details. |
237 | ||
238 | A special target unit `first-boot-complete.target` may be used as milestone to | |
59652fff | 239 | safely handle first boots where the system is powered off too early: |
240 | if the first boot process is aborted before this target is reached, the following boot | |
241 | process will be considered a first boot, too. | |
242 | Once the target is reached, subsequent boots will not be considered first boots anymore, even if the boot | |
243 | process is aborted immediately after. | |
244 | Thus, services that must complete fully before a system shall be considered fully past the first boot should be ordered before this target unit. | |
6538c0ef LP |
245 | |
246 | Whether a system will come up in first boot state or not is derived from the | |
59652fff | 247 | initialization status of `/etc/machine-id`: |
248 | if the file already carries a valid ID the system is already past the first boot. | |
249 | If it is not initialized yet it is still considered in the first boot state. | |
250 | For details see [`machine-id(5)`](https://www.freedesktop.org/software/systemd/man/machine-id.html). | |
3976da02 LP |
251 | |
252 | ## Image Metadata | |
253 | ||
254 | Typically, when operating with golden disk images it is useful to be able to | |
59652fff | 255 | identify them and their version. |
256 | For this the two fields `IMAGE_ID=` and `IMAGE_VERSION=` have been defined in | |
257 | [`os-release(5)`](https://www.freedesktop.org/software/systemd/man/os-release.html). | |
258 | These fields may be accessed from unit files and similar via the `%M` and `%A` specifiers. | |
3976da02 LP |
259 | |
260 | Depending on how the images are put together it might make sense to leave the | |
261 | OS distribution's `os-release` file as is in `/usr/lib/os-release` but to | |
262 | replace the usual `/etc/os-release` symlink with a regular file that extends | |
263 | the distribution's file with one augmented with these two additional | |
264 | fields. | |
265 | ||
266 | ## Links | |
267 | ||
3f4ead8d LP |
268 | [`machine-id(5)`](https://www.freedesktop.org/software/systemd/man/machine-id.html)<br> |
269 | [`systemd-random-seed(8)`](https://www.freedesktop.org/software/systemd/man/systemd-random-seed.service.html)<br> | |
270 | [`os-release(5)`](https://www.freedesktop.org/software/systemd/man/os-release.html)<br> | |
db811444 ZJS |
271 | [Boot Loader Specification](https://uapi-group.org/specifications/specs/boot_loader_specification)<br> |
272 | [Discoverable Partitions Specification](https://uapi-group.org/specifications/specs/discoverable_partitions_specification)<br> | |
3f4ead8d | 273 | [`mkosi`](https://github.com/systemd/mkosi)<br> |
3976da02 | 274 | [`systemd-boot(7)`](https://www.freedesktop.org/software/systemd/man/systemd-boot.html)<br> |
3f4ead8d LP |
275 | [`systemd-repart(8)`](https://www.freedesktop.org/software/systemd/man/systemd-repart.service.html)<br> |
276 | [`systemd-growfs@(8).service`](https://www.freedesktop.org/software/systemd/man/systemd-growfs.html)<br> |