Skip to content

Commit

Permalink
feat: implement secure boot from disk
Browse files Browse the repository at this point in the history
This includes sd-boot handling, EFI variables, etc.

There are some TODOs which need to be addressed to make things smooth.

Install to disk, upgrades work.

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
  • Loading branch information
smira authored and frezbo committed Jun 16, 2023
1 parent 445f5ad commit fe0f469
Show file tree
Hide file tree
Showing 19 changed files with 461 additions and 77 deletions.
10 changes: 10 additions & 0 deletions .drone.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,15 @@ local default_pipeline_steps = [

local integration_qemu = Step('e2e-qemu', privileged=true, depends_on=[load_artifacts], environment={ IMAGE_REGISTRY: local_registry });

local integration_qemu_trusted_boot = Step('e2e-qemu-trusted-boot', target='e2e-qemu', privileged=true, depends_on=[load_artifacts], environment={
// TODO: frezbo: do we need the full suite here?
SHORT_INTEGRATION_TEST: 'yes',
IMAGE_REGISTRY: local_registry,
VIA_MAINTENANCE_MODE: "true",
WITH_TRUSTED_BOOT: "true",
WITH_TEST: "validate_booted_secureboot"
});

local build_race = Step('build-race', target='initramfs installer', depends_on=[load_artifacts], environment={ IMAGE_REGISTRY: local_registry, PUSH: true, TAG_SUFFIX: '-race', WITH_RACE: '1', PLATFORM: 'linux/amd64' });
local integration_qemu_race = Step('e2e-qemu-race', target='e2e-qemu', privileged=true, depends_on=[build_race], environment={ IMAGE_REGISTRY: local_registry, TAG_SUFFIX: '-race' });

Expand Down Expand Up @@ -576,6 +585,7 @@ local integration_trigger(names) = {
local integration_pipelines = [
// regular pipelines, triggered on promote events
Pipeline('integration-qemu', default_pipeline_steps + [integration_qemu, push_edge]) + integration_trigger(['integration-qemu']),
Pipeline('integration-trusted-boot', default_pipeline_steps + [integration_qemu_trusted_boot]) + integration_trigger(['integration-trusted-boot']),
Pipeline('integration-provision-0', default_pipeline_steps + [integration_provision_tests_prepare, integration_provision_tests_track_0]) + integration_trigger(['integration-provision', 'integration-provision-0']),
Pipeline('integration-provision-1', default_pipeline_steps + [integration_provision_tests_prepare, integration_provision_tests_track_1]) + integration_trigger(['integration-provision', 'integration-provision-1']),
Pipeline('integration-provision-2', default_pipeline_steps + [integration_provision_tests_prepare, integration_provision_tests_track_2]) + integration_trigger(['integration-provision', 'integration-provision-2']),
Expand Down
27 changes: 15 additions & 12 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,6 @@ COPY ./hack/structprotogen /go/src/github.com/siderolabs/structprotogen
RUN --mount=type=cache,target=/.cache cd /go/src/github.com/siderolabs/structprotogen \
&& go build -o structprotogen . \
&& mv structprotogen /toolchain/go/bin/
COPY ./hack/ukify /go/src/github.com/siderolabs/ukify
RUN --mount=type=cache,target=/.cache \
--mount=type=bind,source=pkg,target=/go/src/github.com/pkg \
cd /go/src/github.com/siderolabs/ukify \
&& CGO_ENABLED=1 go test ./... \
&& go build -o gen-uki-certs ./gen-certs \
&& CGO_ENABLED=1 go build -o ukify . \
&& mv gen-uki-certs /toolchain/go/bin/ \
&& mv ukify /toolchain/go/bin/

# The build target creates a container that will be used to build Talos source
# code.
Expand Down Expand Up @@ -706,13 +697,25 @@ COPY --from=rootfs / /
LABEL org.opencontainers.image.source https://github.com/siderolabs/talos
ENTRYPOINT ["/sbin/init"]

FROM --platform=${BUILDPLATFORM} tools AS gen-uki-certs
FROM --platform=${BUILDPLATFORM} tools AS ukify-tools
# base has the talos source with the non-abrev version of TAG
COPY --from=base /src/pkg /go/src/github.com/pkg
COPY ./hack/ukify /go/src/github.com/siderolabs/ukify
RUN --mount=type=cache,target=/.cache \
cd /go/src/github.com/siderolabs/ukify \
&& CGO_ENABLED=1 go test ./... \
&& go build -o gen-uki-certs ./gen-certs \
&& CGO_ENABLED=1 go build -o ukify . \
&& mv gen-uki-certs /toolchain/go/bin/ \
&& mv ukify /toolchain/go/bin/

FROM --platform=${BUILDPLATFORM} ukify-tools AS gen-uki-certs
RUN gen-uki-certs

FROM scratch as uki-certs
COPY --from=gen-uki-certs /_out /

FROM --platform=${BUILDPLATFORM} tools AS uki-build-amd64
FROM --platform=${BUILDPLATFORM} ukify-tools AS uki-build-amd64
WORKDIR /build
COPY --from=pkg-sd-stub-amd64 / _out/
COPY --from=pkg-sd-boot-amd64 / _out/
Expand All @@ -725,7 +728,7 @@ FROM scratch AS uki-amd64
COPY --from=uki-build-amd64 /build/_out/systemd-bootx64.efi.signed /systemd-boot.efi.signed
COPY --from=uki-build-amd64 /build/_out/vmlinuz.efi.signed /vmlinuz.efi.signed

FROM --platform=${BUILDPLATFORM} tools AS uki-build-arm64
FROM --platform=${BUILDPLATFORM} ukify-tools AS uki-build-arm64
WORKDIR /build
COPY --from=pkg-sd-stub-arm64 / _out/
COPY --from=pkg-sd-boot-arm64 / _out/
Expand Down
12 changes: 10 additions & 2 deletions cmd/installer/pkg/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func NewInstaller(cmdline *procfs.Cmdline, seq runtime.Sequence, opts *Options)
}
}

i.manifest, err = NewManifest(seq, bootLoaderPresent, i.options)
i.manifest, err = NewManifest(seq, i.bootloader.UEFIBoot(), bootLoaderPresent, i.options)
if err != nil {
return nil, fmt.Errorf("failed to create installation manifest: %w", err)
}
Expand Down Expand Up @@ -158,7 +158,15 @@ func (i *Installer) Install(seq runtime.Sequence) (err error) {
// Mount the partitions.
mountpoints := mount.NewMountPoints()

for _, label := range []string{constants.BootPartitionLabel, constants.EFIPartitionLabel} {
var bootLabels []string

if i.bootloader.UEFIBoot() {
bootLabels = []string{constants.EFIPartitionLabel}
} else {
bootLabels = []string{constants.BootPartitionLabel, constants.EFIPartitionLabel}
}

for _, label := range bootLabels {
err = func() error {
var device string
// searching targets for the device to be used
Expand Down
54 changes: 34 additions & 20 deletions cmd/installer/pkg/install/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,18 @@ type Device struct {
// NewManifest initializes and returns a Manifest.
//
//nolint:gocyclo
func NewManifest(sequence runtime.Sequence, bootLoaderPresent bool, opts *Options) (manifest *Manifest, err error) {
func NewManifest(sequence runtime.Sequence, uefiOnlyBoot bool, bootLoaderPresent bool, opts *Options) (manifest *Manifest, err error) {
manifest = &Manifest{
Devices: map[string]Device{},
Targets: map[string][]*Target{},
LegacyBIOSSupport: opts.LegacyBIOSSupport,
}

if opts.Board != constants.BoardNone {
if uefiOnlyBoot {
return nil, fmt.Errorf("board option can't be used with uefi-only-boot")
}

var b runtime.Board

b, err = board.NewBoard(opts.Board)
Expand Down Expand Up @@ -107,27 +111,37 @@ func NewManifest(sequence runtime.Sequence, bootLoaderPresent bool, opts *Option
manifest.Targets[opts.Disk] = []*Target{}
}

efiTarget := EFITarget(opts.Disk, nil)
biosTarget := BIOSTarget(opts.Disk, nil)

bootTarget := BootTarget(opts.Disk, &Target{
PreserveContents: bootLoaderPresent,
})

metaTarget := MetaTarget(opts.Disk, &Target{
PreserveContents: bootLoaderPresent,
})

stateTarget := StateTarget(opts.Disk, &Target{
PreserveContents: bootLoaderPresent,
FormatOptions: &partition.FormatOptions{
FileSystemType: partition.FilesystemTypeNone,
},
})
targets := []*Target{}

ephemeralTarget := EphemeralTarget(opts.Disk, NoFilesystem)
// create GRUB BIOS+UEFI partitions, or only one big EFI partition if not using GRUB
if !uefiOnlyBoot {
targets = append(targets,
EFITarget(opts.Disk, nil),
BIOSTarget(opts.Disk, nil),
BootTarget(opts.Disk, &Target{
PreserveContents: bootLoaderPresent,
}),
)
} else {
targets = append(targets,
EFITargetUKI(opts.Disk, &Target{
PreserveContents: bootLoaderPresent,
}),
)
}

targets := []*Target{efiTarget, biosTarget, bootTarget, metaTarget, stateTarget, ephemeralTarget}
targets = append(targets,
MetaTarget(opts.Disk, &Target{
PreserveContents: bootLoaderPresent,
}),
StateTarget(opts.Disk, &Target{
PreserveContents: bootLoaderPresent,
FormatOptions: &partition.FormatOptions{
FileSystemType: partition.FilesystemTypeNone,
},
}),
EphemeralTarget(opts.Disk, NoFilesystem),
)

if !opts.Force {
for _, target := range targets {
Expand Down
49 changes: 13 additions & 36 deletions cmd/installer/pkg/install/manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ func (suite *manifestSuite) SetupTest() {
suite.Require().NoError(loopback.Loop(suite.loopbackDevice, suite.disk))

suite.Require().NoError(loopback.LoopSetReadWrite(suite.loopbackDevice))

// set the env vars xfsprogs expects to use Talos STATE partition which is 100Megs
// whereas xfs expects a default minimum size of 300Megs if these are not set.
// Ref: https://git.kernel.org/pub/scm/fs/xfs/xfsprogs-dev.git/tree/mkfs/xfs_mkfs.c?h=v6.3.0#n2582
suite.T().Setenv("TEST_DIR", "true")
suite.T().Setenv("TEST_DEV", "true")
suite.T().Setenv("QA_CHECK_FS", "true")
}

func (suite *manifestSuite) TearDownTest() {
Expand Down Expand Up @@ -132,7 +139,7 @@ func (suite *manifestSuite) verifyBlockdevice(manifest *install.Manifest, curren
suite.Assert().Equal(constants.StatePartitionLabel, part.Name)
suite.Assert().EqualValues(0, part.Attributes)

suite.Assert().EqualValues(extendedStateSize/lbaSize, part.Length())
suite.Assert().EqualValues(partition.StateSize/lbaSize, part.Length())

part = table.Partitions().Items()[5]
suite.Assert().Equal(partition.LinuxFilesystemData, strings.ToUpper(part.Type.String()))
Expand Down Expand Up @@ -215,12 +222,10 @@ func (suite *manifestSuite) verifyBlockdevice(manifest *install.Manifest, curren
suite.Assert().NoError(os.WriteFile(filepath.Join(tempDir, "var", "content"), []byte("data"), 0o600))
}

const extendedStateSize = 300 * 1024 * 1024

func (suite *manifestSuite) TestExecuteManifestClean() {
suite.skipUnderBuildkit()

manifest, err := install.NewManifest(runtime.SequenceInstall, false, &install.Options{
manifest, err := install.NewManifest(runtime.SequenceInstall, false, false, &install.Options{
Disk: suite.loopbackDevice.Name(),
Force: true,
Board: constants.BoardNone,
Expand All @@ -232,13 +237,6 @@ func (suite *manifestSuite) TestExecuteManifestClean() {
dev.SkipOverlayMountsCheck = true
manifest.Devices[suite.loopbackDevice.Name()] = dev

// increase the size of the state partition
for _, t := range manifest.Targets[suite.loopbackDevice.Name()] {
if t.Label == constants.StatePartitionLabel {
t.Size = extendedStateSize
}
}

suite.Assert().NoError(manifest.Execute())

suite.verifyBlockdevice(manifest, "", "A", false, false)
Expand All @@ -247,7 +245,7 @@ func (suite *manifestSuite) TestExecuteManifestClean() {
func (suite *manifestSuite) TestExecuteManifestForce() {
suite.skipUnderBuildkit()

manifest, err := install.NewManifest(runtime.SequenceInstall, false, &install.Options{
manifest, err := install.NewManifest(runtime.SequenceInstall, false, false, &install.Options{
Disk: suite.loopbackDevice.Name(),
Force: true,
Board: constants.BoardNone,
Expand All @@ -259,20 +257,13 @@ func (suite *manifestSuite) TestExecuteManifestForce() {
dev.SkipOverlayMountsCheck = true
manifest.Devices[suite.loopbackDevice.Name()] = dev

// increase the size of the state partition
for _, t := range manifest.Targets[suite.loopbackDevice.Name()] {
if t.Label == constants.StatePartitionLabel {
t.Size = extendedStateSize
}
}

suite.Assert().NoError(manifest.Execute())

suite.verifyBlockdevice(manifest, "", "A", false, false)

// reinstall

manifest, err = install.NewManifest(runtime.SequenceUpgrade, true, &install.Options{
manifest, err = install.NewManifest(runtime.SequenceUpgrade, false, true, &install.Options{
Disk: suite.loopbackDevice.Name(),
Force: true,
Zero: true,
Expand All @@ -285,13 +276,6 @@ func (suite *manifestSuite) TestExecuteManifestForce() {
dev.SkipOverlayMountsCheck = true
manifest.Devices[suite.loopbackDevice.Name()] = dev

// increase the size of the state partition
for _, t := range manifest.Targets[suite.loopbackDevice.Name()] {
if t.Label == constants.StatePartitionLabel {
t.Size = extendedStateSize
}
}

suite.Assert().NoError(manifest.Execute())

suite.verifyBlockdevice(manifest, "A", "B", true, false)
Expand All @@ -300,20 +284,13 @@ func (suite *manifestSuite) TestExecuteManifestForce() {
func (suite *manifestSuite) TestExecuteManifestPreserve() {
suite.skipUnderBuildkit()

manifest, err := install.NewManifest(runtime.SequenceInstall, false, &install.Options{
manifest, err := install.NewManifest(runtime.SequenceInstall, false, false, &install.Options{
Disk: suite.loopbackDevice.Name(),
Force: true,
Board: constants.BoardNone,
})
suite.Require().NoError(err)

// increase the size of the state partition
for _, t := range manifest.Targets[suite.loopbackDevice.Name()] {
if t.Label == constants.StatePartitionLabel {
t.Size = extendedStateSize
}
}

// in the tests overlay mounts should be ignored
dev := manifest.Devices[suite.loopbackDevice.Name()]
dev.SkipOverlayMountsCheck = true
Expand All @@ -325,7 +302,7 @@ func (suite *manifestSuite) TestExecuteManifestPreserve() {

// reinstall

manifest, err = install.NewManifest(runtime.SequenceUpgrade, true, &install.Options{
manifest, err = install.NewManifest(runtime.SequenceUpgrade, false, true, &install.Options{
Disk: suite.loopbackDevice.Name(),
Force: false,
Board: constants.BoardNone,
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ require (
github.com/docker/docker v24.0.2+incompatible
github.com/docker/go-connections v0.4.0
github.com/dustin/go-humanize v1.0.1
github.com/ecks/uefi v0.0.0-20221116212947-caef65d070eb
github.com/emicklei/dot v1.4.2
github.com/fatih/color v1.15.0
github.com/freddierice/go-losetup/v2 v2.0.1
Expand Down Expand Up @@ -128,6 +129,7 @@ require (
golang.org/x/sync v0.2.0
golang.org/x/sys v0.8.0
golang.org/x/term v0.8.0
golang.org/x/text v0.9.0
golang.org/x/time v0.3.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
google.golang.org/grpc v1.55.0
Expand All @@ -143,6 +145,7 @@ require (
cloud.google.com/go/compute v1.19.0 // indirect
cloud.google.com/go/iam v0.13.0 // indirect
cloud.google.com/go/storage v1.28.1 // indirect
github.com/0x5a17ed/itkit v0.6.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
Expand Down Expand Up @@ -280,7 +283,6 @@ require (
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/oauth2 v0.6.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/tools v0.7.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
Expand Down
6 changes: 5 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuW
cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0=
cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/0x5a17ed/itkit v0.6.0 h1:g1SnJQM61e0nAEk0Qu7cGGiL4zOHrk7ta55KoKwRcCs=
github.com/0x5a17ed/itkit v0.6.0/go.mod h1:v22t2Uc3bKewFBwLkY2U1KM7Us8iiEWw3qGqJFU76rI=
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
Expand Down Expand Up @@ -509,6 +511,8 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:Htrtb
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/ecks/uefi v0.0.0-20221116212947-caef65d070eb h1:LZBZtPpqHDydudNAs2sHmo4Zp9bxEyxHdGCk3Fr6tv8=
github.com/ecks/uefi v0.0.0-20221116212947-caef65d070eb/go.mod h1:jP/WitZVr91050NiqxEEp0ynBFbP2eUQC0CnxWPlQTA=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/dot v1.4.2 h1:UbK6gX4yvrpHKlxuUQicwoAis4zl8Dzwit9SnbBAXWw=
github.com/emicklei/dot v1.4.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
Expand Down Expand Up @@ -2041,8 +2045,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 h1:Wobr37noukisGxpKo5jAsLREcpj61RxrWYzD8uwveOY=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
Expand Down
8 changes: 8 additions & 0 deletions hack/test/e2e-qemu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,14 @@ case "${CUSTOM_CNI_NAME:-none}" in
;;
esac

case "${WITH_TRUSTED_BOOT:-false}" in
false)
;;
*)
QEMU_FLAGS="${QEMU_FLAGS} --iso-path=_out/talos-uki-amd64.iso --with-secureboot=true --with-tpm2=true"
;;
esac

function create_cluster {
build_registry_mirrors

Expand Down
4 changes: 4 additions & 0 deletions hack/test/e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,10 @@ function validate_rlimit_nofile {
${KUBECTL} run --rm --restart=Never -it rlimit-test --image=alpine -- /bin/sh -c "ulimit -n" | grep 1048576
}

function validate_booted_secureboot {
${TALOSCTL} dmesg | grep "Secure boot enabled"
}

function install_and_run_cilium_cni_tests {
get_kubeconfig

Expand Down
Loading

0 comments on commit fe0f469

Please sign in to comment.