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

kola: Add tests for encrypted root disks with TPM PCR binding #521

Merged
merged 2 commits into from
Apr 11, 2024
Merged
Changes from 1 commit
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
178 changes: 174 additions & 4 deletions kola/tests/misc/tpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"strings"
"time"

"github.com/coreos/go-semver/semver"
"github.com/coreos/pkg/capnslog"
Expand All @@ -17,6 +18,12 @@ import (
"github.com/flatcar/mantle/util"
)

const (
VariantDefault string = ""
VariantNoUpdate string = "noupdate"
VariantWithUpdate string = "withupdate"
)

var (
// For now Ignition has no systemd-cryptenroll support and a helper service is used
IgnitionConfigRootCryptenroll = conf.Butane(`---
Expand Down Expand Up @@ -47,6 +54,127 @@ systemd:
ExecStart=rm /etc/luks/rootencrypted
[Install]
WantedBy=multi-user.target
`)
// Note: Keep the two below configs in sync with those
// documented in the Flatcar TPM docs.
// Ideally the reboot wouldn't be needed (or done in the initrd?)
IgnitionConfigRootCryptenrollPcrNoUpdate = conf.Butane(`---
Copy link
Contributor

Choose a reason for hiding this comment

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

off-topic: would be a separate bigger PR: I think it would be nice at some time to have the ignition/butane yaml/jsons in a separate folder/file for each test, to be able to better track changes in the code vs changes in the metadata.

variant: flatcar
version: 1.0.0
storage:
files:
- path: /etc/flatcar/update.conf
overwrite: true
contents:
inline: |
SERVER=disabled
luks:
- name: rootencrypted
wipe_volume: true
device: "/dev/disk/by-partlabel/ROOT"
pothos marked this conversation as resolved.
Show resolved Hide resolved
filesystems:
- device: /dev/mapper/rootencrypted
format: ext4
label: ROOT
systemd:
units:
- name: cryptenroll-helper-first.service
enabled: true
contents: |
[Unit]
ConditionFirstBoot=true
OnFailure=emergency.target
OnFailureJobMode=isolate
After=first-boot-complete.target multi-user.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=systemd-cryptenroll --tpm2-device=auto --unlock-key-file=/etc/luks/rootencrypted --tpm2-pcrs= /dev/disk/by-partlabel/ROOT
ExecStart=mv /etc/luks/rootencrypted /etc/luks/rootencrypted-bind
ExecStart=sleep 10
ExecStart=systemctl reboot
[Install]
WantedBy=multi-user.target
- name: cryptenroll-helper-bind.service
enabled: true
contents: |
[Unit]
ConditionFirstBoot=false
ConditionPathExists=/etc/luks/rootencrypted-bind
OnFailure=emergency.target
OnFailureJobMode=isolate
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=systemd-cryptenroll --tpm2-device=auto --unlock-key-file=/etc/luks/rootencrypted-bind --tpm2-pcrs=4+7+8+9+11+12+13 --wipe-slot=tpm2 /dev/disk/by-partlabel/ROOT
ExecStart=mv /etc/luks/rootencrypted-bind /etc/luks/rootencrypted
[Install]
WantedBy=multi-user.target
`)
// The rebinding for the update is due to how GRUB measures things and
// we can only make this work without rebinding if we switch to sd-boot
IgnitionConfigRootCryptenrollPcrWithUpdate = conf.Butane(`---
variant: flatcar
version: 1.0.0
storage:
files:
- path: /oem/bin/oem-postinst
overwrite: true
mode: 0755
contents:
inline: |
#!/bin/bash
set -euo pipefail
# When the update fails to correctly apply, this runs again
if [ -e /etc/luks/rootencrypted-bound ]; then
mv /etc/luks/rootencrypted-bound /etc/luks/rootencrypted-bind
fi
# But since a reboot inbetween could have bound it again,
# remove the PCR binding for every run
systemd-cryptenroll --tpm2-device=auto --unlock-key-file=/etc/luks/rootencrypted-bind --wipe-slot=tpm2 --tpm2-pcrs= /dev/disk/by-partlabel/ROOT
luks:
- name: rootencrypted
wipe_volume: true
device: "/dev/disk/by-partlabel/ROOT"
pothos marked this conversation as resolved.
Show resolved Hide resolved
filesystems:
- device: /dev/mapper/rootencrypted
format: ext4
label: ROOT
systemd:
units:
- name: cryptenroll-helper-first.service
enabled: true
contents: |
[Unit]
ConditionFirstBoot=true
OnFailure=emergency.target
OnFailureJobMode=isolate
After=first-boot-complete.target multi-user.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=systemd-cryptenroll --tpm2-device=auto --unlock-key-file=/etc/luks/rootencrypted --tpm2-pcrs= /dev/disk/by-partlabel/ROOT
ExecStart=mv /etc/luks/rootencrypted /etc/luks/rootencrypted-bind
ExecStart=sleep 10
ExecStart=systemctl reboot
[Install]
WantedBy=multi-user.target
- name: cryptenroll-helper-bind.service
enabled: true
contents: |
[Unit]
ConditionFirstBoot=false
ConditionPathExists=/etc/luks/rootencrypted-bind
OnFailure=emergency.target
OnFailureJobMode=isolate
Before=update-engine.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=systemd-cryptenroll --tpm2-device=auto --unlock-key-file=/etc/luks/rootencrypted-bind --tpm2-pcrs=4+7+8+9+11+12+13 --wipe-slot=tpm2 /dev/disk/by-partlabel/ROOT
ExecStart=mv /etc/luks/rootencrypted-bind /etc/luks/rootencrypted-bound
[Install]
WantedBy=multi-user.target
`)
IgnitionConfigRootTPM = `{
"ignition": {
Expand Down Expand Up @@ -124,7 +252,7 @@ systemd:

func init() {
runRootTPMCryptenroll := func(c cluster.TestCluster) {
tpmTest(c, IgnitionConfigRootCryptenroll, "/")
tpmTest(c, IgnitionConfigRootCryptenroll, "/", VariantDefault)
}
register.Register(&register.Test{
Run: runRootTPMCryptenroll,
Expand All @@ -134,9 +262,31 @@ func init() {
Distros: []string{"cl"},
MinVersion: semver.Version{Major: 3913, Minor: 0, Patch: 1},
})
runRootTPMCryptenrollPcrNoUpdate := func(c cluster.TestCluster) {
tpmTest(c, IgnitionConfigRootCryptenrollPcrNoUpdate, "/", VariantNoUpdate)
}
register.Register(&register.Test{
Run: runRootTPMCryptenrollPcrNoUpdate,
ClusterSize: 0,
Platforms: []string{"qemu"},
Name: "cl.tpm.root-cryptenroll-pcr-noupdate",
Distros: []string{"cl"},
MinVersion: semver.Version{Major: 3913, Minor: 0, Patch: 1},
})
runRootTPMCryptenrollPcrWithUpdate := func(c cluster.TestCluster) {
tpmTest(c, IgnitionConfigRootCryptenrollPcrWithUpdate, "/", VariantWithUpdate)
}
register.Register(&register.Test{
Run: runRootTPMCryptenrollPcrWithUpdate,
ClusterSize: 0,
Platforms: []string{"qemu"},
Name: "cl.tpm.root-cryptenroll-pcr-withupdate",
Distros: []string{"cl"},
MinVersion: semver.Version{Major: 3913, Minor: 0, Patch: 1},
})

runRootTPM := func(c cluster.TestCluster) {
tpmTest(c, conf.Ignition(IgnitionConfigRootTPM), "/")
tpmTest(c, conf.Ignition(IgnitionConfigRootTPM), "/", VariantDefault)
}
register.Register(&register.Test{
Run: runRootTPM,
Expand All @@ -148,7 +298,7 @@ func init() {
})

runNonRootTPM := func(c cluster.TestCluster) {
tpmTest(c, conf.Ignition(IgnitionConfigNonRootTPM), "/mnt/data")
tpmTest(c, conf.Ignition(IgnitionConfigNonRootTPM), "/mnt/data", VariantDefault)
}
register.Register(&register.Test{
Run: runNonRootTPM,
Expand All @@ -160,7 +310,7 @@ func init() {
})
}

func tpmTest(c cluster.TestCluster, userData *conf.UserData, mountpoint string) {
func tpmTest(c cluster.TestCluster, userData *conf.UserData, mountpoint string, variant string) {
swtpm, err := startSwtpm()
if err != nil {
c.Fatalf("could not start software TPM emulation: %v", err)
Expand Down Expand Up @@ -188,6 +338,13 @@ func tpmTest(c cluster.TestCluster, userData *conf.UserData, mountpoint string)
c.Fatal(err)
}

if variant == VariantNoUpdate || variant == VariantWithUpdate {
// Wait for the first reboot
time.Sleep(1 * time.Minute)
// Verify that the machine rebooted
_ = c.MustSSH(m, "grep -v flatcar.first_boot /proc/cmdline")
}

checkIfMountpointIsEncrypted(c, m, mountpoint)

// Make sure the change is reboot-safe. This is especially important for the case of an encrypted root disk because the
Expand All @@ -199,6 +356,19 @@ func tpmTest(c cluster.TestCluster, userData *conf.UserData, mountpoint string)
}

checkIfMountpointIsEncrypted(c, m, mountpoint)

if variant == VariantWithUpdate {
// Simulate update effect by
// affecting the GRUB PCR values for the next boot
_ = c.MustSSH(m, "echo 'set linux_append=\"flatcar.autologin console=ttyS0,115200 quiet\"' | sudo tee -a /oem/grub.cfg")
// and calling the OEM hook we set up
_ = c.MustSSH(m, "sudo /oem/bin/oem-postinst")
err := m.Reboot()
if err != nil {
c.Fatalf("could not reboot machine: %v", err)
}
checkIfMountpointIsEncrypted(c, m, "/")
}
}

type softwareTPM struct {
Expand Down