Skip to content

Commit 41ca30d

Browse files
committed
feat: add secureboot enrollKeys schematic option
Add a `secureboot.enrollKeys` field to the schematic, mapping to the imager's `SDBootEnrollKeys` profile option (off / manual / if-safe / force) for the ISO and disk image outputs. It controls systemd-boot's `secure-boot-enroll` setting in loader.conf. The default (if-safe) auto-enrolls SecureBoot keys only inside a virtual machine, so on bare-metal keys are never enrolled unattended. Setting force enables unattended enrollment when the UEFI firmware is in setup mode, which network provisioning flows with no console operator require. The corresponding imager flag was added in siderolabs/talos#13571; see siderolabs/talos#12568 for the discussion. Signed-off-by: Mickaël Canévet <mickael.canevet@proton.ch>
1 parent 805c51c commit 41ca30d

4 files changed

Lines changed: 101 additions & 0 deletions

File tree

docs/api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ customization:
6464
secureboot: # optional, only applies to SecureBoot images
6565
# optional, include well-known UEFI certificates into auto-enrollment database (SecureBoot ISO only)
6666
includeWellKnownCertificates: true
67+
# optional, how systemd-boot enrolls SecureBoot keys on first boot: off, manual, if-safe, force
68+
# defaults to if-safe (auto-enrolls only in a VM); use force for unattended bare-metal enrollment in setup mode
69+
enrollKeys: force
6770
bootloader: sd-boot # optional, defaults to auto (bootloader chosen by imager), other options: dual-boot, grub
6871
embeddedMachineConfiguration: | # optional, embedded machine configuration (YAML-encoded)
6972
apiVersion: v1alpha1

internal/profile/profile.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,30 @@ func EnhanceFromSchematic(
359359
}
360360

361361
prof.Input.SecureBoot = &secureBootAssetsCopy
362+
363+
if enroll := schematic.Customization.SecureBoot.EnrollKeys; enroll != "" {
364+
enrollKeys, err := profile.SDBootEnrollKeysString(enroll)
365+
if err != nil {
366+
return prof, xerrors.NewTaggedf[InvalidErrorTag]("invalid secureboot enrollKeys value %q: %w", enroll, err)
367+
}
368+
369+
// secure-boot-enroll only applies to the ESP loader.conf, i.e. the ISO and
370+
// disk image outputs; it is meaningless for installer/UKI outputs.
371+
switch prof.Output.Kind { //nolint:exhaustive
372+
case profile.OutKindISO:
373+
if prof.Output.ISOOptions == nil {
374+
prof.Output.ISOOptions = &profile.ISOOptions{}
375+
}
376+
377+
prof.Output.ISOOptions.SDBootEnrollKeys = enrollKeys
378+
case profile.OutKindImage:
379+
if prof.Output.ImageOptions == nil {
380+
prof.Output.ImageOptions = &profile.ImageOptions{}
381+
}
382+
383+
prof.Output.ImageOptions.SDBootEnrollKeys = enrollKeys
384+
}
385+
}
362386
}
363387

364388
if schematic.Overlay.Name != "" && !quirks.New(versionTag).SupportsOverlay() {

internal/profile/profile_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,3 +973,68 @@ func TestInstallerProfile(t *testing.T) {
973973
})
974974
}
975975
}
976+
977+
func TestEnhanceFromSchematicEnrollKeys(t *testing.T) {
978+
t.Parallel()
979+
980+
ctx := t.Context()
981+
982+
secureBootService, err := secureboot.NewService(secureboot.Options{
983+
Enabled: true,
984+
SigningKeyPath: "sign-key.pem",
985+
SigningCertPath: "sign-cert.pem",
986+
PCRKeyPath: "pcr-key.pem",
987+
})
988+
require.NoError(t, err)
989+
990+
base := func(name string) profile.Profile {
991+
p := profile.Default[name]
992+
p.Arch = "amd64"
993+
994+
return p.DeepCopy()
995+
}
996+
997+
const version = "v1.13.0"
998+
999+
enrollKeysSchematic := func(value string) *schematic.Schematic {
1000+
return &schematic.Schematic{
1001+
Customization: schematic.Customization{
1002+
SecureBoot: schematic.SecureBootCustomization{EnrollKeys: value},
1003+
},
1004+
}
1005+
}
1006+
1007+
t.Run("force on disk image sets image options", func(t *testing.T) {
1008+
t.Parallel()
1009+
1010+
out, err := imageprofile.EnhanceFromSchematic(ctx, base("secureboot-"+constants.PlatformMetal), enrollKeysSchematic("force"), mockArtifactProducer{}, secureBootService, version)
1011+
require.NoError(t, err)
1012+
require.NotNil(t, out.Output.ImageOptions)
1013+
require.Equal(t, profile.SDBootEnrollKeysForce, out.Output.ImageOptions.SDBootEnrollKeys)
1014+
})
1015+
1016+
t.Run("force on ISO sets ISO options", func(t *testing.T) {
1017+
t.Parallel()
1018+
1019+
out, err := imageprofile.EnhanceFromSchematic(ctx, base("secureboot-iso"), enrollKeysSchematic("force"), mockArtifactProducer{}, secureBootService, version)
1020+
require.NoError(t, err)
1021+
require.NotNil(t, out.Output.ISOOptions)
1022+
require.Equal(t, profile.SDBootEnrollKeysForce, out.Output.ISOOptions.SDBootEnrollKeys)
1023+
})
1024+
1025+
t.Run("invalid value is rejected", func(t *testing.T) {
1026+
t.Parallel()
1027+
1028+
_, err := imageprofile.EnhanceFromSchematic(ctx, base("secureboot-"+constants.PlatformMetal), enrollKeysSchematic("bogus"), mockArtifactProducer{}, secureBootService, version)
1029+
require.Error(t, err)
1030+
})
1031+
1032+
t.Run("ignored without secureboot", func(t *testing.T) {
1033+
t.Parallel()
1034+
1035+
out, err := imageprofile.EnhanceFromSchematic(ctx, base(constants.PlatformMetal), enrollKeysSchematic("force"), mockArtifactProducer{}, secureBootService, version)
1036+
require.NoError(t, err)
1037+
require.NotNil(t, out.Output.ImageOptions)
1038+
require.Equal(t, profile.SDBootEnrollKeysIfSafe, out.Output.ImageOptions.SDBootEnrollKeys)
1039+
})
1040+
}

pkg/schematic/schematic.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ type Schematic struct {
3131
}
3232

3333
// Customization represents the Talos image customization.
34+
//
35+
//nolint:govet // field order is part of the stable schematic serialization (see ID()), so it cannot be reordered for alignment
3436
type Customization struct {
3537
// EmbeddedMachineConfiguration is the initial embedded machine configuration.
3638
EmbeddedMachineConfiguration string `yaml:"embeddedMachineConfiguration,omitempty"`
@@ -73,6 +75,13 @@ type Overlay struct { //nolint:govet
7375

7476
// SecureBootCustomization represents the secure boot options for the image.
7577
type SecureBootCustomization struct {
78+
// EnrollKeys controls how systemd-boot enrolls the SecureBoot keys on first boot
79+
// (loader.conf secure-boot-enroll): one of "off", "manual", "if-safe", "force".
80+
//
81+
// Defaults to "if-safe", which auto-enrolls keys only inside a virtual machine.
82+
// Use "force" for unattended enrollment on bare-metal when the UEFI firmware is
83+
// in setup mode.
84+
EnrollKeys string `yaml:"enrollKeys,omitempty"`
7685
// Include well-known UEFI certificates in the auto-enrollment database.
7786
IncludeWellKnownCertificates bool `yaml:"includeWellKnownCertificates,omitempty"`
7887
}

0 commit comments

Comments
 (0)