feat: seed the kernel random pool from the TPM

Use the TPM2 feature to provide high-quality random bytes.

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
This commit is contained in:
Andrey Smirnov 2023-07-07 23:50:22 +04:00
parent c81ce8cfb0
commit d23d04de2a
No known key found for this signature in database
GPG Key ID: 7B26396447AB6DFD
6 changed files with 144 additions and 0 deletions

1
go.mod
View File

@ -54,6 +54,7 @@ require (
github.com/godbus/dbus/v5 v5.1.0
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.5.9
github.com/google/go-tpm v0.9.0
github.com/google/gopacket v1.1.19
github.com/google/nftables v0.1.0
github.com/google/uuid v1.3.0

2
go.sum
View File

@ -687,6 +687,8 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0=
github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=

View File

@ -24,6 +24,7 @@ import (
"github.com/siderolabs/talos/internal/pkg/mount"
"github.com/siderolabs/talos/internal/pkg/mount/switchroot"
"github.com/siderolabs/talos/internal/pkg/rng"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/extensions"
"github.com/siderolabs/talos/pkg/version"
@ -51,6 +52,12 @@ func run() (err error) {
return err
}
// Seed RNG.
if err = rng.TPMSeed(); err != nil {
// not making this fatal error
log.Printf("failed to seed from the TPM: %s", err)
}
log.Printf("booting Talos %s", version.Tag)
// Mount the rootfs.

41
internal/pkg/rng/pool.go Normal file
View File

@ -0,0 +1,41 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package rng
import (
"bytes"
"fmt"
"os"
"strconv"
"golang.org/x/sys/unix"
)
// GetPoolSize returns kernel random pool size.
func GetPoolSize() (int, error) {
contents, err := os.ReadFile("/proc/sys/kernel/random/poolsize")
if err != nil {
return 0, err
}
return strconv.Atoi(string(bytes.TrimSpace(contents)))
}
// WriteEntropy writes entropy data to the pool.
func WriteEntropy(data []byte) error {
fd, err := os.OpenFile("/dev/urandom", os.O_WRONLY|unix.O_CLOEXEC|unix.O_NOCTTY, 0)
if err != nil {
return err
}
defer fd.Close() //nolint:errcheck
_, err = fd.Write(data)
if err != nil {
return fmt.Errorf("error writing entropy: %w", err)
}
return fd.Close()
}

6
internal/pkg/rng/rng.go Normal file
View File

@ -0,0 +1,6 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Package rng handles interaction with kernel random number generator.
package rng

87
internal/pkg/rng/tpm.go Normal file
View File

@ -0,0 +1,87 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package rng
import (
"fmt"
"log"
"os"
"time"
"github.com/google/go-tpm/tpm2"
"github.com/google/go-tpm/tpm2/transport"
)
// TPMSeed seeds the random entropy pool from the TPM.
//
//nolint:gocyclo
func TPMSeed() error {
t, err := transport.OpenTPM()
if err != nil {
if os.IsNotExist(err) {
log.Printf("TPM device is not available")
return nil
}
return fmt.Errorf("error opening TPM device: %w", err)
}
defer t.Close() //nolint:errcheck
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)
}
props, err := caps.CapabilityData.Data.TPMProperties()
if err != nil {
return fmt.Errorf("error getting properties: %w", err)
}
log.Printf("TPM manufacturer ID: %08x", props.TPMProperty[0].Value)
poolSize, err := GetPoolSize()
if err != nil {
return fmt.Errorf("error getting pool size: %w", err)
}
remaining := poolSize
start := time.Now()
for remaining > 0 {
chunk := 32 // default to small chunk (size of AES key)
if remaining < chunk {
chunk = remaining
}
cmd := tpm2.GetRandom{
BytesRequested: uint16(chunk),
}
resp, err := cmd.Execute(t)
if err != nil {
return fmt.Errorf("error getting random data from the TPM: %w", err)
}
if len(resp.RandomBytes.Buffer) == 0 {
return fmt.Errorf("received zero random bytes from the TPM: %w", err)
}
if err = WriteEntropy(resp.RandomBytes.Buffer); err != nil {
return fmt.Errorf("error writing random pool entropy: %w", err)
}
remaining -= len(resp.RandomBytes.Buffer)
}
log.Printf("seeded random pool with %d bytes from the TPM in %s", poolSize, time.Since(start))
return nil
}