Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement secure boot from disk #7375

Merged
merged 1 commit into from
Jun 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: above, on line 106, needs a way to force a specific bootloader based on Options (flags), so that one could create sd-boot metal image for example

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