mirror of
https://github.com/siderolabs/talos.git
synced 2026-05-05 20:36:18 +02:00
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:
parent
09ef1f8a41
commit
ac140324eb
10
.github/workflows/ci.yaml
vendored
10
.github/workflows/ci.yaml
vendored
@ -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
|
||||
|
||||
10
.github/workflows/integration-misc-2-cron.yaml
vendored
10
.github/workflows/integration-misc-2-cron.yaml
vendored
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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)
|
||||
;;
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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())
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -61,6 +61,6 @@ type NodeInfo struct {
|
||||
|
||||
IPs []netip.Addr
|
||||
|
||||
APIPort int
|
||||
TPM2StateDir string
|
||||
APIPort int
|
||||
TPMStateDir string
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user