fix: skip PCR extension if TPM1.2 is found

When extending PCR or trying to seed entropy pool from TPM if the found
device is a TPM1.2 device, skip it, since Talos only supports TPM2.0

Fixes: #10847

Signed-off-by: Noel Georgi <git@frezbo.dev>
This commit is contained in:
Noel Georgi 2025-05-05 10:26:24 +05:30
parent 09ef1f8a41
commit ac140324eb
No known key found for this signature in database
GPG Key ID: 21A9F444075C9E36
16 changed files with 120 additions and 43 deletions

View File

@ -1,6 +1,6 @@
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
#
# Generated on 2025-05-04T13:37:33Z by kres 6cbcbd1.
# Generated on 2025-05-05T04:55:03Z by kres 6cbcbd1.
name: default
concurrency:
@ -2392,6 +2392,14 @@ jobs:
WITH_CONFIG_PATCH: '@hack/test/patches/node-address-v2.yaml'
run: |
sudo -E make e2e-qemu
- name: e2e-tpm1_2
env:
GITHUB_STEP_NAME: ${{ github.job}}-e2e-tpm1_2
IMAGE_REGISTRY: registry.dev.siderolabs.io
SHORT_INTEGRATION_TEST: "yes"
WITH_TPM1_2: "true"
run: |
sudo -E make e2e-qemu
- name: save artifacts
if: always()
uses: actions/upload-artifact@v4

View File

@ -1,6 +1,6 @@
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
#
# Generated on 2025-04-04T12:28:10Z by kres d903dae.
# Generated on 2025-05-05T04:55:03Z by kres 6cbcbd1.
name: integration-misc-2-cron
concurrency:
@ -149,6 +149,14 @@ jobs:
WITH_CONFIG_PATCH: '@hack/test/patches/node-address-v2.yaml'
run: |
sudo -E make e2e-qemu
- name: e2e-tpm1_2
env:
GITHUB_STEP_NAME: ${{ github.job}}-e2e-tpm1_2
IMAGE_REGISTRY: registry.dev.siderolabs.io
SHORT_INTEGRATION_TEST: "yes"
WITH_TPM1_2: "true"
run: |
sudo -E make e2e-qemu
- name: save artifacts
if: always()
uses: actions/upload-artifact@v4

View File

@ -1246,6 +1246,14 @@ spec:
SHORT_INTEGRATION_TEST: yes
WITH_CONFIG_PATCH: "@hack/test/patches/node-address-v2.yaml"
IMAGE_REGISTRY: registry.dev.siderolabs.io
- name: e2e-tpm1_2
command: e2e-qemu
withSudo: true
environment:
GITHUB_STEP_NAME: ${{ github.job}}-e2e-tpm1_2
SHORT_INTEGRATION_TEST: yes
WITH_TPM1_2: true
IMAGE_REGISTRY: registry.dev.siderolabs.io
- name: save-talos-logs
conditions:
- always

View File

@ -37,6 +37,7 @@ const (
bootloaderEnabledFlag = "with-bootloader"
controlPlanePortFlag = "control-plane-port"
firewallFlag = "with-firewall"
tpmEnabledFlag = "with-tpm1_2"
tpm2EnabledFlag = "with-tpm2"
withDebugShellFlag = "with-debug-shell"
withIOMMUFlag = "with-iommu"
@ -117,6 +118,7 @@ type qemuOps struct {
nodeIPXEBootScript string
bootloaderEnabled bool
uefiEnabled bool
tpm1_2Enabled bool
tpm2Enabled bool
extraUEFISearchPaths []string
networkNoMasqueradeCIDRs []string
@ -249,7 +251,8 @@ func init() {
createCmd.Flags().StringVar(&ops.qemu.nodeIPXEBootScript, "ipxe-boot-script", "", "iPXE boot script (URL) to use")
createCmd.Flags().BoolVar(&ops.qemu.bootloaderEnabled, bootloaderEnabledFlag, true, "enable bootloader to load kernel and initramfs from disk image after install")
createCmd.Flags().BoolVar(&ops.qemu.uefiEnabled, "with-uefi", true, "enable UEFI on x86_64 architecture")
createCmd.Flags().BoolVar(&ops.qemu.tpm2Enabled, tpm2EnabledFlag, false, "enable TPM2 emulation support using swtpm")
createCmd.Flags().BoolVar(&ops.qemu.tpm1_2Enabled, tpmEnabledFlag, false, "enable TPM 1.2 emulation support using swtpm")
createCmd.Flags().BoolVar(&ops.qemu.tpm2Enabled, tpm2EnabledFlag, false, "enable TPM 2.0 emulation support using swtpm")
createCmd.Flags().BoolVar(&ops.qemu.debugShellEnabled, withDebugShellFlag, false, "drop talos into a maintenance shell on boot, this is for advanced debugging for developers only")
createCmd.Flags().BoolVar(&ops.qemu.withIOMMU, withIOMMUFlag, false, "enable IOMMU support, this also add a new PCI root port and an interface attached to it (qemu only)")
createCmd.Flags().MarkHidden("with-debug-shell") //nolint:errcheck
@ -317,5 +320,7 @@ func init() {
createCmd.MarkFlagsMutuallyExclusive(inputDirFlag, kubePrismFlag)
createCmd.MarkFlagsMutuallyExclusive(inputDirFlag, diskEncryptionKeyTypesFlag)
createCmd.MarkFlagsMutuallyExclusive(tpmEnabledFlag, tpm2EnabledFlag)
clustercmd.Cmd.AddCommand(createCmd)
}

View File

@ -417,6 +417,7 @@ func create(ctx context.Context, ops createOps) error {
provision.WithDockerPortsHostIP(dockerOps.dockerHostIP),
provision.WithBootlader(qOps.bootloaderEnabled),
provision.WithUEFI(qOps.uefiEnabled),
provision.WithTPM1_2(qOps.tpm1_2Enabled),
provision.WithTPM2(qOps.tpm2Enabled),
provision.WithDebugShell(qOps.debugShellEnabled),
provision.WithIOMMU(qOps.withIOMMU),

View File

@ -187,6 +187,14 @@ case "${WITH_TRUSTED_BOOT_ISO:-false}" in
;;
esac
case "${WITH_TPM1_2:-false}" in
false)
;;
*)
QEMU_FLAGS+=("--with-tpm1_2")
;;
esac
case "${WITH_SIDEROLINK_AGENT:-false}" in
false)
;;

View File

@ -8,7 +8,6 @@ import (
"fmt"
"log"
"os"
"strings"
"time"
"github.com/google/go-tpm/tpm2"
@ -22,8 +21,8 @@ import (
func TPMSeed() error {
t, err := tpm.Open()
if err != nil {
// if the TPM is not available or not a TPM 2.0, we can skip the PCR extension
if os.IsNotExist(err) || strings.Contains(err.Error(), "device is not a TPM 2.0") {
// if the TPM is not available we can skip seeding random pool
if os.IsNotExist(err) {
log.Printf("TPM device is not available")
return nil
@ -34,13 +33,18 @@ func TPMSeed() error {
defer t.Close() //nolint:errcheck
// now we need to check if the TPM is a 2.0 device
// we can do this by checking the manufacturer,
// if it fails, we can skip trying to seed the pool
caps, err := tpm2.GetCapability{
Capability: tpm2.TPMCapTPMProperties,
Property: uint32(tpm2.TPMPTManufacturer),
PropertyCount: 1,
}.Execute(t)
if err != nil {
return fmt.Errorf("error getting TPM capabilities: %w", err)
log.Printf("TPM device is not a TPM 2.0, skipping seeding entropy pool from TPM")
return nil
}
props, err := caps.CapabilityData.Data.TPMProperties()

View File

@ -11,7 +11,6 @@ import (
"fmt"
"log"
"os"
"strings"
"github.com/google/go-tpm/tpm2"
"github.com/google/go-tpm/tpm2/transport"
@ -67,8 +66,8 @@ func ReadPCR(t transport.TPM, pcr int) ([]byte, error) {
func PCRExtend(pcr int, data []byte) error {
t, err := tpm.Open()
if err != nil {
// if the TPM is not available or not a TPM 2.0, we can skip the PCR extension
if os.IsNotExist(err) || strings.Contains(err.Error(), "device is not a TPM 2.0") {
// if the TPM is not available we can skip the PCR extension
if os.IsNotExist(err) {
log.Printf("TPM device is not available, skipping PCR extension")
return nil
@ -79,6 +78,20 @@ func PCRExtend(pcr int, data []byte) error {
defer t.Close() //nolint:errcheck
// now we need to check if the TPM is a 2.0 device
// we can do this by checking the manufacturer,
// if it fails, we can skip the PCR extension
_, err = tpm2.GetCapability{
Capability: tpm2.TPMCapTPMProperties,
Property: uint32(tpm2.TPMPTManufacturer),
PropertyCount: 1,
}.Execute(t)
if err != nil {
log.Printf("TPM device is not a TPM 2.0, skipping PCR extension")
return nil
}
// since we are using SHA256, we can assume that the PCR bank is SHA256
digest := sha256.Sum256(data)

View File

@ -70,7 +70,16 @@ func WithUEFI(enabled bool) Option {
}
}
// WithTPM2 enables or disables TPM2 emulation.
// WithTPM1_2 enables or disables TPM1.2 emulation.
func WithTPM1_2(enabled bool) Option {
return func(o *Options) error {
o.TPM1_2Enabled = enabled
return nil
}
}
// WithTPM2 enables or disables TPM2.0 emulation.
func WithTPM2(enabled bool) Option {
return func(o *Options) error {
o.TPM2Enabled = enabled
@ -200,7 +209,9 @@ type Options struct {
// Enable UEFI (for amd64), arm64 can only boot UEFI
UEFIEnabled bool
// Enable TPM2 emulation using swtpm.
// Enable TPM 1.2 emulation using swtpm.
TPM1_2Enabled bool
// Enable TPM 2.0 emulation using swtpm.
TPM2Enabled bool
// Enable debug shell in the bootloader.
WithDebugShell bool

View File

@ -56,7 +56,7 @@ func (p *provisioner) Destroy(ctx context.Context, cluster provision.Cluster, op
return err
}
if err := p.destroyVirtualTPM2s(cluster.Info()); err != nil {
if err := p.destroyVirtualTPMs(cluster.Info()); err != nil {
return err
}

View File

@ -45,7 +45,7 @@ type LaunchConfig struct {
MonitorPath string
DefaultBootOrder string
BootloaderEnabled bool
TPM2Config tpm2Config
TPMConfig tpmConfig
NodeUUID uuid.UUID
BadRTC bool
ArchitectureData Arch
@ -91,9 +91,11 @@ type LaunchConfig struct {
controller *Controller
}
type tpm2Config struct {
type tpmConfig struct {
NodeName string
StateDir string
TPM2 bool
}
// launchVM runs qemu with args built based on config.
@ -235,21 +237,27 @@ func launchVM(config *LaunchConfig) error {
return err
}
if config.TPM2Config.NodeName != "" {
tpm2SocketPath := filepath.Join(config.TPM2Config.StateDir, "swtpm.sock")
if config.TPMConfig.NodeName != "" {
tpm2SocketPath := filepath.Join(config.TPMConfig.StateDir, "swtpm.sock")
cmd := exec.Command("swtpm", []string{
swtpmArgs := []string{
"socket",
"--tpmstate",
fmt.Sprintf("dir=%s,mode=0644", config.TPM2Config.StateDir),
fmt.Sprintf("dir=%s,mode=0644", config.TPMConfig.StateDir),
"--ctrl",
fmt.Sprintf("type=unixio,path=%s", tpm2SocketPath),
"--tpm2",
// "--tpm2",
"--pid",
fmt.Sprintf("file=%s", filepath.Join(config.TPM2Config.StateDir, "swtpm.pid")),
fmt.Sprintf("file=%s", filepath.Join(config.TPMConfig.StateDir, "swtpm.pid")),
"--log",
fmt.Sprintf("file=%s,level=20", filepath.Join(config.TPM2Config.StateDir, "swtpm.log")),
}...)
fmt.Sprintf("file=%s,level=20", filepath.Join(config.TPMConfig.StateDir, "swtpm.log")),
}
if config.TPMConfig.TPM2 {
swtpmArgs = append(swtpmArgs, "--tpm2")
}
cmd := exec.Command("swtpm", swtpmArgs...)
log.Printf("starting swtpm: %s", cmd.String())

View File

@ -210,14 +210,14 @@ func (p *provisioner) createNode(state *vm.State, clusterReq provision.ClusterRe
APIPort: apiPort,
}
if opts.TPM2Enabled {
tpm2, tpm2Err := p.createVirtualTPM2State(state, nodeReq.Name)
if opts.TPM1_2Enabled || opts.TPM2Enabled {
tpmConfig, tpm2Err := p.createVirtualTPMState(state, nodeReq.Name, opts.TPM2Enabled)
if tpm2Err != nil {
return provision.NodeInfo{}, tpm2Err
}
launchConfig.TPM2Config = tpm2
nodeInfo.TPM2StateDir = tpm2.StateDir
launchConfig.TPMConfig = tpmConfig
nodeInfo.TPMStateDir = tpmConfig.StateDir
}
if !clusterReq.Network.DHCPSkipHostname {

View File

@ -125,7 +125,7 @@ func (check *preflightCheckContext) checkIptables(ctx context.Context) error {
}
func (check *preflightCheckContext) swtpmExecutable(ctx context.Context) error {
if check.options.TPM2Enabled {
if check.options.TPM1_2Enabled || check.options.TPM2Enabled {
if _, err := exec.LookPath("swtpm"); err != nil {
return fmt.Errorf("swtpm not found in PATH, please install swtpm-tools with the package manager: %w", err)
}

View File

@ -15,40 +15,42 @@ import (
"github.com/siderolabs/talos/pkg/provision/providers/vm"
)
func (p *provisioner) createVirtualTPM2State(state *vm.State, nodeName string) (tpm2Config, error) {
tpm2StateDir := state.GetRelativePath(fmt.Sprintf("%s-tpm2", nodeName))
func (p *provisioner) createVirtualTPMState(state *vm.State, nodeName string, tpm2Enabled bool) (tpmConfig, error) {
tpmStateDir := state.GetRelativePath(fmt.Sprintf("%s-tpm", nodeName))
if err := os.MkdirAll(tpm2StateDir, 0o755); err != nil {
return tpm2Config{}, err
if err := os.MkdirAll(tpmStateDir, 0o755); err != nil {
return tpmConfig{}, err
}
return tpm2Config{
return tpmConfig{
NodeName: nodeName,
StateDir: tpm2StateDir,
StateDir: tpmStateDir,
TPM2: tpm2Enabled,
}, nil
}
func (p *provisioner) destroyVirtualTPM2s(cluster provision.ClusterInfo) error {
func (p *provisioner) destroyVirtualTPMs(cluster provision.ClusterInfo) error {
errCh := make(chan error)
nodes := append([]provision.NodeInfo{}, cluster.Nodes...)
for _, node := range nodes {
if node.TPM2StateDir == "" {
if node.TPMStateDir == "" {
continue
}
tpm2PidPath := filepath.Join(node.TPM2StateDir, "swtpm.pid")
tpm2PidPath := filepath.Join(node.TPMStateDir, "swtpm.pid")
go func() {
errCh <- p.destroyVirtualTPM2(tpm2PidPath)
errCh <- p.destroyVirtualTPM(tpm2PidPath)
}()
}
var multiErr *multierror.Error
for _, node := range nodes {
if node.TPM2StateDir == "" {
if node.TPMStateDir == "" {
continue
}
@ -58,6 +60,6 @@ func (p *provisioner) destroyVirtualTPM2s(cluster provision.ClusterInfo) error {
return multiErr.ErrorOrNil()
}
func (p *provisioner) destroyVirtualTPM2(pid string) error {
func (p *provisioner) destroyVirtualTPM(pid string) error {
return vm.StopProcessByPidfile(pid)
}

View File

@ -61,6 +61,6 @@ type NodeInfo struct {
IPs []netip.Addr
APIPort int
TPM2StateDir string
APIPort int
TPMStateDir string
}

View File

@ -225,7 +225,8 @@ talosctl cluster create [flags]
--with-network-packet-loss float specify percent of packet loss on the bridge interface when creating a qemu cluster. e.g. 50% = 0.50 (default: 0.0)
--with-network-packet-reorder float specify percent of reordered packets on the bridge interface when creating a qemu cluster. e.g. 50% = 0.50 (default: 0.0)
--with-siderolink true enables the use of siderolink agent as configuration apply mechanism. true or `wireguard` enables the agent, `tunnel` enables the agent with grpc tunneling (default none)
--with-tpm2 enable TPM2 emulation support using swtpm
--with-tpm1_2 enable TPM 1.2 emulation support using swtpm
--with-tpm2 enable TPM 2.0 emulation support using swtpm
--with-uefi enable UEFI on x86_64 architecture (default true)
--with-uuid-hostnames use machine UUIDs as default hostnames (QEMU only)
--workers int the number of workers to create (default 1)