# 0123. SecureBoot, UKI and kernel lockdown are typed options that pick the installer, check each node and never change one in place

Status: Accepted; built; hardware-verified 2026-11-05 (the --wipe-mode ISO reinstall awaits) · Date: 2026-11-04 · Design section: Typed hardening options for managed clusters · Touches: 0023, 0076, 0077, 0108

Decided: `TalosCluster.spec.security` gains `secureBoot` (`mode: Off | Preferred | Required`) and `kernelLockdown` (`integrity | confidentiality`), both unset by default. (1) The installer image variant follows the mode: `Preferred` and `Required` make Ichor use `factory.talos.dev/metal-installer-secureboot/<schematic>:<version>` wherever Ichor chooses the installer itself (a Talos upgrade, and the default of an approval that names no `--install-image`); a node that already runs with SecureBoot always gets the SecureBoot installer, whatever the mode says, because an unsigned UKI would not boot there. (2) The only machine-config effect is that `requireSecureBoot` of `security.diskEncryption` defaults to true for a TPM key when `mode` is `Required`; the UKI command line, and so the lockdown level, is baked into the boot asset and cannot be set from the machine config on a v1.14 contract, so `kernelLockdown` is checked, not rendered. (3) The controller reads each node's `SecurityState` and `KernelCmdline` resources, records them in `TalosMachine.status.security`, sets the conditions `SecureBoot` and `KernelLockdown` on the machine, and a cluster-level condition of the same name summarises the machines. (4) `Preferred` and `kernelLockdown` only warn. `Required` refuses at two places where refusing is possible: the admission webhook refuses enabling `Required` on a cluster whose approved machines are not all running with SecureBoot unless the cluster carries `ichor.maesh.dev/confirm-secureboot-reinstall: "true"`, and the controller does not push the first configuration to a maintenance-mode machine that reports SecureBoot off. (5) Ichor never converts an installed node: Talos cannot move a GRUB install to UKI/SecureBoot, and the firmware setting and key enrolment are outside Talos, so the answer for an installed node is reset and add one node at a time (the same path as disk encryption), documented in the hardening guide.

## Context

ADR 0108 left "SecureBoot and UKI, kernel lockdown" as Not modelled and tied `diskEncryption.requireSecureBoot` to the TPM key without a way to ask for SecureBoot itself. The user decided on 2026-11-04 that these ship in v0.1.0. What Talos v1.14 says (docs.siderolabs.com/talos/v1.14, read for this ADR; the SecureBoot page is under Bare Metal Platforms):

- **Boot chain.** UEFI firmware loads systemd-boot, which loads the Unified Kernel Image; the UKI bundles kernel, initramfs and command line, and both are signed with a key enrolled in the firmware. With SecureBoot on, the UKI ignores command-line arguments passed at boot and uses the embedded ones; changing them means rebuilding the UKI. SecureBoot is not supported in BIOS mode. "There is no support at the moment to upgrade non-UKI (GRUB-based) Talos installation to use UKI/SecureBoot, so a fresh installation is required."
- **Images.** Image Factory serves signed SecureBoot assets for Talos 1.5.0 and later: the ISO `metal-amd64-secureboot.iso` and the installer `factory.talos.dev/metal-installer-secureboot/<schematic>:<version>`, which is what the machine configuration must name. The stock `metal-installer` image installs an unsigned UKI. The docs do not say what happens when a SecureBoot node is upgraded with the plain installer; the signature check would fail at the next boot, so Ichor treats the mix as unsafe.
- **Enrolment.** The firmware must be in setup mode on the first boot; the keys are enrolled automatically (the `if-safe` default), on bare metal the operator chooses "Enroll Secure Boot keys: auto" from the boot menu. This is outside Talos's API and Ichor's reach.
- **State.** `talosctl get securitystate` (`--insecure` in maintenance mode) shows the `SecurityState` resource (`runtime` namespace, id `securitystate`). In machinery v1.14.5 its spec has `secureBoot`, `bootedWithUKI`, `moduleSignatureEnforced`, `ukiSigningKeyFingerprint`, `pcrSigningKeyFingerprint`, `selinuxState` and `fipsState`. The kernel command line is the `KernelCmdline` resource (`cmdline`).
- **Lockdown.** What's new in Talos 1.14: SecureBoot images no longer enable `lockdown=confidentiality` by default; "they now run with `lockdown=integrity` enabled implicitly, which is the recommended setting for most users. Add `lockdown=confidentiality` to the kernel command line through the Image Factory if it is required." (`pkg/machinery/kernel` forces confidentiality only below 1.14.) With UKI the command line is embedded, and `machine.install.extraKernelArgs` is rejected together with `grubUseUKICmdline`, which v1.14 sets for `UnattendedInstallConfig`. Nothing in `SecurityState` carries the lockdown level.
- **TPM.** TPM disk encryption seals to PCR 7 (SecureBoot state and keys) and a signed PCR 11 policy; "the disk will only unlock if SecureBoot remains enabled with the same key set". Switching SecureBoot on an encrypted node therefore also breaks unlock.

## Decision

**Fields (append-only).** `spec.security.secureBoot.mode` (required inside the struct, enum `Off | Preferred | Required`) and `spec.security.kernelLockdown` (enum `integrity | confidentiality`, optional). Unset or `Off` renders and behaves exactly as before; the render test proves byte equality for a control plane and a worker.

**Installer variant.** One function, `upgrade.InstallerImage(schematic, version, secureBoot)`, builds the reference. The Talos upgrade (`installerImage` in `talos_upgrade.go`) passes `secureBoot` true when the node reports SecureBoot, or when the mode is not `Off` and the node booted from a UKI (`bootedWithUKI`); a GRUB-installed node keeps the stock installer and its machine condition says it needs a reinstall to get SecureBoot. A mode of `Off` with a node that runs SecureBoot is reported as a mismatch (the condition), not obeyed, for the safety reason above. The approval default (`ApproveMachine`, so the CLI and UI alike) fills `install.image` with the SecureBoot installer for the cluster's schematic and `spec.talosVersion` when the mode is not `Off`, the machine names no image and a disk is given; an explicit `--install-image` is kept, and the TalosMachine webhook warns when `Required` meets an explicit image whose path does not contain `installer-secureboot` (Ichor cannot tell a mirror of a custom-key installer from a stock one, so it is a warning, not a refusal). `spec.schematic` stays the cluster-wide schematic; Ichor builds no schematics and has no Image Factory client, so a lockdown of `confidentiality` needs the user to create a schematic with `extraKernelArgs: [lockdown=confidentiality]` and set `spec.schematic.id`.

**Rendered config.** `diskEncryption.type: tpm` with `requireSecureBoot` unset renders `checkSecurebootStatusOnEnroll: true` when the mode is `Required` (an explicit false stays). Nothing else is rendered: no `extraKernelArgs`, no `grubUseUKICmdline`. Validation adds: `kernelLockdown` and `secureBoot` need nothing from the version contract (the options are about the boot asset), and `diskEncryption.requireSecureBoot: true` with a `secureBoot.mode` of `Off` is admitted with a warning (it will fail on enrolment unless the node is already SecureBoot).

**Verification.** `TalosClient.SecurityState` reads the two resources (a missing `SecurityState` is an error to the caller, a missing `KernelCmdline` leaves the command line empty). `status.security` holds `secureBoot`, `bootedWithUKI`, `moduleSignatureEnforced`, `ukiSigningKeyFingerprint` and `kernelLockdown`, where the level is derived: the last `lockdown=` word of the command line, else `integrity` when SecureBoot is on (the implicit level of the docs above), else `none`. The derived level has to be confirmed on hardware against `/sys/kernel/security/lockdown`. It is written on every reconcile that reaches the node, as the schematic is. The machine conditions exist only while the matching option is set. `SecureBoot`: True when the node runs SecureBoot; False with reason `SecureBootOff`, `NotUKI` or `ModeOffButSecureBoot` otherwise, the message saying what to do. `KernelLockdown`: True when the level is at least the expected one, False with the observed level otherwise, Unknown while unread. The cluster-level `SecureBoot` and `KernelLockdown` conditions are True when every approved machine's condition is True, False naming the machines that are not, Unknown while none has been read; the cluster reconciler recomputes them when a TalosMachine changes.

**Refusing and warning.** Per the rule that Ichor hardens itself strictly and only warns about the managed cluster, `Preferred` and `kernelLockdown` never refuse. For `Required`: (a) the TalosCluster webhook, when `mode` becomes `Required` on a cluster with approved machines that are not all known to run SecureBoot, refuses unless `ichor.maesh.dev/confirm-secureboot-reinstall: "true"` is set, the message listing the machines and the path; (b) the machine reconciler, before the first configuration of a machine in maintenance mode, reads `SecurityState` over the insecure API and, when SecureBoot is off, holds the machine with `Ready=False` reason `SecureBootRequired` and pushes nothing (the controller cannot make the node SecureBoot; the message says to boot the SecureBoot ISO with the firmware in setup mode). A node that is already installed and without SecureBoot is only reported (`SecureBoot=False`), never reconfigured or wiped.

**Reinstall, honestly.** Changing the SecureBoot mode never touches an installed node: the installer image of a running node cannot be changed by configuration (`machine.install` and `UnattendedInstallConfig` are install-time-only in `pkg/fieldmeta`), the firmware keys are not Talos's, and the TPM key is bound to the SecureBoot state. The guide describes the path per node: set the mode and the annotation, `ichor node reset NODE --override --confirm NODE`, boot the machine from the SecureBoot ISO of the same schematic and Talos version (firmware in setup mode, SecureBoot enabled), approve it again (the default image is now the SecureBoot installer), wait for `SecureBoot=True`, next node; control planes one at a time with etcd quorum intact and a fresh etcd backup.

**Surfaces.** The UI's Talos details (System section) gains the SecureBoot, UKI, module-signature and lockdown fields from the same two resources (live read); the machine and cluster detail pages and `ichor machine get` / `ichor cluster get` show the conditions as they do every condition. No new CLI command.

## Rejected

- **Rendering `lockdown=` through `machine.install.extraKernelArgs`.** Rejected twice: it is refused with `grubUseUKICmdline`, which the v1.14 install document sets, and with SecureBoot the UKI ignores boot-time arguments, so the node would not obey it. Only the boot asset can carry it.
- **Calling Image Factory to create a schematic with the lockdown argument.** Ichor has no Image Factory client or egress, the controller does not pull installers (ADR 0115), and a schematic is a supply-chain input the user should name. `spec.schematic.id` already carries it.
- **`mode` as a boolean.** `Preferred` is the useful state for a mixed fleet and for a first rollout; a boolean would force either nothing or a refusal.
- **Making `Required` block every reconcile of a non-SecureBoot node.** The node cannot be fixed by configuration, and blocking would stop security patches (firewall, audit) reaching exactly the machines that need attention. It is reported and refused at the two points where refusing prevents a mistake.
- **Obeying `Off` on a SecureBoot node.** It would install an unsigned UKI that the firmware refuses at the next boot.
- **Reading `/sys/kernel/security/lockdown` through the file API.** Precise, but a file read per node per reconcile needs a broader Talos role than the COSI reads Ichor already does, and whether the path is readable through `Read` is unconfirmed. Kept as the hardware check of the derived level.

## Risks

- The derived lockdown level assumes the kernel honours an embedded `lockdown=` and applies `integrity` under SecureBoot as the v1.14 notes say; hardware must confirm it.
- Whether the secureboot installer honours `extraKernelArgs` of its schematic is not documented (the Image Factory page says kernel arguments are ignored for `installer` images and apply to ISO and PXE assets); if it does not, `confidentiality` has to come from a custom-key imager build and `KernelLockdown=False` will say so.
- `Preferred` on a cluster of GRUB-installed nodes changes nothing for them (by design) and the approval default changes the installer for new nodes only.

## Needs hardware

Steps, in order of cost:

1. **Negative path on the existing harness (no firmware change, cheap).** On a BIOS node of the harness, set `spec.security.secureBoot.mode: Required` on a cluster without machines, add a machine booted from the stock ISO and approve it: expect `Ready=False reason SecureBootRequired` and no configuration stored (`status.configStoredAt` empty). Set `Preferred`: it is configured, `status.security` has `secureBoot: false, bootedWithUKI: false` (a BIOS install is GRUB) and the machine condition `SecureBoot=False reason NotUKI`; the cluster condition summarises it. With `kernelLockdown: integrity`, `KernelLockdown=False` (level `none`) and the `status.security.kernelLockdown` value agree with `talosctl read /sys/kernel/security/lockdown` (to confirm the derived level and that the path is readable). The webhook: with the machine up, `Required` without the annotation is refused naming the machine.
2. **Positive path (needs a different node spec).** Cluster-wide `Preferred` and `kernelLockdown: integrity`, one node with `firmware.bootloader.efi.secureBoot: false` (setup mode), `features.smm.enabled: true`, persistent EFI variables and `devices.tpm.persistent: true`, booted from `metal-amd64-secureboot.iso` of the cluster's schematic and version, approved through `ichor machine approve --install-disk ...` (check `spec.install.image` is the `metal-installer-secureboot` reference), expect `secureBoot: true, bootedWithUKI: true`, `SecureBoot=True`, `KernelLockdown=True` (integrity). Then an `ichor upgrade talos` to the next patch: the Upgrade call must carry the SecureBoot installer (`talosctl` events or the machine's installer image) and the node must boot again. With `diskEncryption.type: tpm` and `Required` the STATE volume must show the TPM key and enrolment must have checked SecureBoot.
3. **Lockdown `confidentiality`.** An Image Factory schematic with `extraKernelArgs: [lockdown=confidentiality]`, used for the ISO and as `spec.schematic`: confirm the secureboot installer and the booted UKI both carry it (the risk above) and `KernelLockdown=True`.

## As built

- `pkg/secureboot` (the shared rules: installer reference, lockdown level and comparison, machine and cluster conditions), `api/v1alpha1` (`TalosClusterSecurity.SecureBoot`, `KernelLockdown`, `TalosMachineStatus.Security`, the condition types and the confirm annotation), `pkg/render/security.go` (the TPM default), `internal/controller/security_state.go` (`ReadSecurityState`, `recordSecurity`, the first-configuration gate), `internal/controller/cluster_security.go` (`BootSecurityReconciler`, its own controller so a machine's status change does not start a discovery sweep), `internal/controller/talos_upgrade.go` (the variant), `internal/webhook/v1alpha1/security_options.go` and `talosmachine_webhook.go`, `internal/uiapi` (`ApproveMachine`'s default and the details fields, `talosdetails.proto` fields 13 to 17), `ui/src/components/TalosDetails.tsx`, `docs/guides/hardening.md`.
- Deviations from the text above: the first-configuration hold sets `Ready=False` with reason `SecureBootRequired` (there is no separate `Provisioned` write for a refusal); the CLI has no new command, `ichor machine get` and `ichor cluster get` print the conditions and `ichor machine approve --install-image` documents the default.
- `status.security` is recorded on every reconcile that reaches the node, for every cluster, so enabling `Required` later can be checked against it; the conditions exist only while the option is set (and a `SecureBoot` condition also appears under mode `Off` when a node runs SecureBoot).
- Tests: `pkg/secureboot` (the rules), `pkg/render` (`TestSecureBootRender`: the options alone render the same bytes, `Required` and the TPM key), `api/v1alpha1` (enums), `internal/controller` (`TestReadSecurityState` against an in-memory COSI state, the machine reconcile, the maintenance gate, the upgrade variant, the cluster conditions), `internal/webhook/v1alpha1` (confirmation, warnings, installer image), `internal/uiapi` (approval default, details mapping). The hardware steps above were not run.

## Amendment 2026-11-05: hardware findings of lane C

Four findings, fixed in the same batch:

- **The fingerprint field.** Talos v1.14.5 fills `pcrSigningKeyFingerprint` of `SecurityState` and leaves `ukiSigningKeyFingerprint` empty (both exist in the machinery type, the first one is the one a node reports), so `status.security` and the UI showed no key. `status.security.pcrSigningKeyFingerprint` (and `talosdetails.proto` field 18) now carry it, and the UI row reads "PCR signing key". `ukiSigningKeyFingerprint` stays, filled if a node ever reports it (CRD fields are append-only); it is documented as normally empty.
- **Reset and add from the ISO.** `ichor node reset` wipes only STATE and EPHEMERAL (ADR 0042), so a machine whose system disk comes first in the boot order boots its installed UKI again and the SecureBoot ISO reinstalls nothing. The reset operation gained `spec.node.wipeMode` (`system-disk` or `all`, `ichor node reset --wipe-mode`, field 7 of `NodeActionRequest`): the removal flow then sends Talos a `ResetRequest` with that `Mode` and no partitions named, which erases the whole system disk (`all` also the user disks). Same guards as any reset: `--override`, the typed machine name. The hardening guide's "Moving an installed cluster" uses `--wipe-mode system-disk`. Not offered: `user-disks` (it cannot make a node installable).
- **Rediscovery after a reset** is covered in ADR 0108's amendment of the same date.
- **Approving with no sign-in** is covered there too.

Needs hardware: `ichor node reset --wipe-mode system-disk` on the SecureBoot node with the ISO attached, then approval, expecting a fresh install from the ISO (`bootedWithUKI`, `secureBoot` true, `pcrSigningKeyFingerprint` set).
