feat: implement encryption locking to STATE

Fixes #10676

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
Andrey Smirnov 2025-07-25 21:04:14 +04:00
parent c1e65a3425
commit a5f3000f2e
No known key found for this signature in database
GPG Key ID: FE042E3D4085A811
51 changed files with 1178 additions and 111 deletions

View File

@ -88,6 +88,7 @@ message EncryptionKey {
bytes static_passphrase = 3;
string kms_endpoint = 4;
bool tpm_check_secureboot_status_on_enroll = 5;
bool lock_to_state = 6;
}
// EncryptionSpec is the spec for volume encryption.

View File

@ -21,6 +21,11 @@ message CertSANSpec {
string fqdn = 3;
}
// EncryptionSaltSpec describes the salt.
message EncryptionSaltSpec {
bytes disk_salt = 1;
}
// EtcdCertsSpec describes etcd certs secrets.
message EtcdCertsSpec {
common.PEMEncodedCertificateAndKey etcd = 1;

View File

@ -582,6 +582,12 @@ func create(ctx context.Context, ops createOps) error {
EncryptionKeys: convertEncryptionKeys(keys),
}
if spec.label != constants.StatePartitionLabel {
for idx := range blockCfg.EncryptionSpec.EncryptionKeys {
blockCfg.EncryptionSpec.EncryptionKeys[idx].KeyLockToSTATE = pointer.To(true)
}
}
ctr, err := container.New(blockCfg)
if err != nil {
return fmt.Errorf("error creating container for %q volume: %w", spec.label, err)

View File

@ -132,6 +132,9 @@ Talos increases the boot partition size to 2 GiB to accommodate larger images (w
description = """\
Disk encryption for system volumes is now managed by the `VolumeConfig` machine configuration document.
Legacy configuration in `valpha1` machine configuration is still supported.
New per-key option `lockToSTATE` is added to the `VolumeConfig` document, which allows to lock the volume encryption key to the secret salt in the `STATE` volume.
So, if the `STATE` volume is wiped or replaced, the volume encryption key will not be usable anymore.
"""
[make_deps]

View File

@ -15,6 +15,8 @@ import (
)
func TestIdentityGenerate(t *testing.T) {
t.Parallel()
var spec1, spec2 cluster.IdentitySpec
require.NoError(t, clusteradapter.IdentitySpec(&spec1).Generate())
@ -29,6 +31,8 @@ func TestIdentityGenerate(t *testing.T) {
}
func TestIdentityConvertMachineID(t *testing.T) {
t.Parallel()
spec := cluster.IdentitySpec{
NodeID: "sou7yy34ykX3n373Zw1DXKb8zD7UnyKT6HT3QDsGH6L",
}

View File

@ -0,0 +1,39 @@
// 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 secrets
import (
"crypto/rand"
"io"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/resources/secrets"
)
// EncryptionSalt adapter provides encryption salt generation.
//
//nolint:revive,golint
func EncryptionSalt(r *secrets.EncryptionSaltSpec) encryptionSalt {
return encryptionSalt{
EncryptionSaltSpec: r,
}
}
type encryptionSalt struct {
*secrets.EncryptionSaltSpec
}
// Generate new encryption salt.
func (a encryptionSalt) Generate() error {
buf := make([]byte, constants.DiskEncryptionSaltSize)
if _, err := io.ReadFull(rand.Reader, buf); err != nil {
return err
}
a.EncryptionSaltSpec.DiskSalt = buf
return nil
}

View File

@ -0,0 +1,29 @@
// 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 secrets_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
secretsadapter "github.com/siderolabs/talos/internal/app/machined/pkg/adapters/secrets"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/resources/secrets"
)
func TestEncryptionSaltGenerate(t *testing.T) {
t.Parallel()
var spec1, spec2 secrets.EncryptionSaltSpec
require.NoError(t, secretsadapter.EncryptionSalt(&spec1).Generate())
require.NoError(t, secretsadapter.EncryptionSalt(&spec2).Generate())
assert.NotEqual(t, spec1, spec2)
assert.Len(t, spec1.DiskSalt, constants.DiskEncryptionSaltSize)
}

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 secrets implements adapters wrapping resources to provide additional functionality.
package secrets

View File

@ -36,7 +36,7 @@ func Close(ctx context.Context, logger *zap.Logger, volumeContext ManagerContext
case block.EncryptionProviderLUKS2:
encryptionConfig := volumeContext.Cfg.TypedSpec().Encryption
handler, err := encryption.NewHandler(encryptionConfig, volumeContext.Cfg.Metadata().ID(), volumeContext.GetSystemInformation, volumeContext.TPMLocker)
handler, err := encryption.NewHandler(encryptionConfig, volumeContext.Cfg.Metadata().ID(), volumeContext.EncryptionHelpers)
if err != nil {
return fmt.Errorf("failed to create encryption handler: %w", err)
}

View File

@ -35,7 +35,7 @@ func HandleEncryption(ctx context.Context, logger *zap.Logger, volumeContext Man
case block.EncryptionProviderLUKS2:
encryptionConfig := volumeContext.Cfg.TypedSpec().Encryption
handler, err := encryption.NewHandler(encryptionConfig, volumeContext.Cfg.Metadata().ID(), volumeContext.GetSystemInformation, volumeContext.TPMLocker)
handler, err := encryption.NewHandler(encryptionConfig, volumeContext.Cfg.Metadata().ID(), volumeContext.EncryptionHelpers)
if err != nil {
return fmt.Errorf("failed to create encryption handler: %w", err)
}

View File

@ -7,14 +7,13 @@ package volumes
import (
"cmp"
"context"
"math"
"github.com/siderolabs/gen/optional"
"github.com/siderolabs/talos/internal/pkg/encryption"
blockpb "github.com/siderolabs/talos/pkg/machinery/api/resource/definitions/block"
"github.com/siderolabs/talos/pkg/machinery/resources/block"
"github.com/siderolabs/talos/pkg/machinery/resources/hardware"
)
// CompareVolumeConfigs compares two volume configs in the proposed order of provisioning.
@ -89,7 +88,6 @@ type ManagerContext struct {
DevicesReady bool
PreviousWaveProvisioned bool
GetSystemInformation func(context.Context) (*hardware.SystemInformation, error)
TPMLocker func(context.Context, func() error) error
EncryptionHelpers encryption.Helpers
ShouldCloseVolume bool
}

View File

@ -112,6 +112,7 @@ func convertEncryptionConfiguration(in cfg.EncryptionConfig, out *block.VolumeCo
for i, key := range in.Keys() {
out.Encryption.Keys[i].Slot = key.Slot()
out.Encryption.Keys[i].LockToSTATE = key.LockToSTATE()
switch {
case key.Static() != nil:

View File

@ -23,12 +23,15 @@ import (
"go.uber.org/zap"
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/block/internal/volumes"
"github.com/siderolabs/talos/internal/pkg/encryption"
"github.com/siderolabs/talos/internal/pkg/encryption/helpers"
blockpb "github.com/siderolabs/talos/pkg/machinery/api/resource/definitions/block"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/proto"
"github.com/siderolabs/talos/pkg/machinery/resources/block"
"github.com/siderolabs/talos/pkg/machinery/resources/hardware"
"github.com/siderolabs/talos/pkg/machinery/resources/runtime"
"github.com/siderolabs/talos/pkg/machinery/resources/secrets"
)
// VolumeManagerController manages volumes in the system, converting VolumeConfig resources to VolumeStatuses.
@ -96,6 +99,12 @@ func (ctrl *VolumeManagerController) Inputs() []controller.Input {
Type: hardware.PCRStatusType,
Kind: controller.InputStrong,
},
{
Namespace: secrets.NamespaceName,
Type: secrets.EncryptionSaltType,
ID: optional.Some(secrets.EncryptionSaltID),
Kind: controller.InputWeak,
},
}
}
@ -374,19 +383,11 @@ func (ctrl *VolumeManagerController) Run(ctx context.Context, r controller.Runti
Disks: diskSpecs,
DevicesReady: devicesReady,
PreviousWaveProvisioned: vc.TypedSpec().Provisioning.Wave <= fullyProvisionedWave,
GetSystemInformation: func(ctx context.Context) (*hardware.SystemInformation, error) {
systemInfo, err := safe.ReaderGetByID[*hardware.SystemInformation](ctx, r, hardware.SystemInformationID)
if err != nil && !state.IsNotFoundError(err) {
return nil, fmt.Errorf("error fetching system information: %w", err)
}
if systemInfo == nil {
return nil, errors.New("system information not available")
}
return systemInfo, nil
EncryptionHelpers: encryption.Helpers{
GetSystemInformation: ctrl.getSystemInformation(r),
TPMLocker: hardware.LockPCRStatus(r, constants.UKIPCR, vc.Metadata().ID()),
SaltGetter: ctrl.getSaltGetter(r),
},
TPMLocker: hardware.LockPCRStatus(r, constants.UKIPCR, vc.Metadata().ID()),
ShouldCloseVolume: shouldCloseVolume,
},
); err != nil {
@ -591,3 +592,33 @@ func (ctrl *VolumeManagerController) processVolumeConfig(ctx context.Context, lo
prevPhase = volumeContext.Status.Phase
}
}
func (ctrl *VolumeManagerController) getSystemInformation(r controller.Reader) helpers.SystemInformationGetter {
return func(ctx context.Context) (*hardware.SystemInformation, error) {
systemInfo, err := safe.ReaderGetByID[*hardware.SystemInformation](ctx, r, hardware.SystemInformationID)
if err != nil && !state.IsNotFoundError(err) {
return nil, fmt.Errorf("error fetching system information: %w", err)
}
if systemInfo == nil {
return nil, errors.New("system information not available")
}
return systemInfo, nil
}
}
func (ctrl *VolumeManagerController) getSaltGetter(r controller.Reader) helpers.SaltGetter {
return func(ctx context.Context) ([]byte, error) {
salt, err := safe.ReaderGetByID[*secrets.EncryptionSalt](ctx, r, secrets.EncryptionSaltID)
if err != nil && !state.IsNotFoundError(err) {
return nil, fmt.Errorf("error fetching encryption salt: %w", err)
}
if salt == nil {
return nil, errors.New("encryption salt not available")
}
return salt.TypedSpec().DiskSalt, nil
}
}

View File

@ -0,0 +1,109 @@
// 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 secrets
import (
"context"
"fmt"
"path/filepath"
"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/safe"
"go.uber.org/zap"
secretsadapter "github.com/siderolabs/talos/internal/app/machined/pkg/adapters/secrets"
"github.com/siderolabs/talos/internal/app/machined/pkg/automaton/blockautomaton"
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/resources/block"
"github.com/siderolabs/talos/pkg/machinery/resources/secrets"
)
// EncryptionSaltController manages secrets.EncryptionSalt in STATE.
type EncryptionSaltController struct {
stateMachine blockautomaton.VolumeMounterAutomaton
}
// Name implements controller.Controller interface.
func (ctrl *EncryptionSaltController) Name() string {
return "secrets.EncryptionSaltController"
}
// Inputs implements controller.Controller interface.
func (ctrl *EncryptionSaltController) Inputs() []controller.Input {
return []controller.Input{
{
Namespace: block.NamespaceName,
Type: block.VolumeMountStatusType,
Kind: controller.InputStrong,
},
{
Namespace: block.NamespaceName,
Type: block.VolumeMountRequestType,
Kind: controller.InputDestroyReady,
},
}
}
// Outputs implements controller.Controller interface.
func (ctrl *EncryptionSaltController) Outputs() []controller.Output {
return []controller.Output{
{
Type: secrets.EncryptionSaltType,
Kind: controller.OutputShared,
},
{
Type: block.VolumeMountRequestType,
Kind: controller.OutputShared,
},
}
}
// Run implements controller.Controller interface.
//
//nolint:gocyclo
func (ctrl *EncryptionSaltController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
for {
select {
case <-ctx.Done():
return nil
case <-r.EventCh():
}
if ctrl.stateMachine == nil {
ctrl.stateMachine = blockautomaton.NewVolumeMounter(ctrl.Name(), constants.StatePartitionLabel, ctrl.establishEncryptionSalt)
}
if err := ctrl.stateMachine.Run(ctx, r, logger); err != nil {
return fmt.Errorf("error running volume mounter machine: %w", err)
}
r.ResetRestartBackoff()
}
}
func (ctrl *EncryptionSaltController) establishEncryptionSalt(ctx context.Context, r controller.ReaderWriter, logger *zap.Logger, mountStatus *block.VolumeMountStatus) error {
rootPath := mountStatus.TypedSpec().Target
var salt secrets.EncryptionSaltSpec
if err := controllers.LoadOrNewFromFile(filepath.Join(rootPath, constants.EncryptionSaltFilename), &salt, func(v *secrets.EncryptionSaltSpec) error {
return secretsadapter.EncryptionSalt(v).Generate()
}); err != nil {
return fmt.Errorf("error caching node identity: %w", err)
}
if err := safe.WriterModify(ctx, r, secrets.NewEncryptionSalt(), func(r *secrets.EncryptionSalt) error {
*r.TypedSpec() = salt
return nil
}); err != nil {
return fmt.Errorf("error modifying resource: %w", err)
}
logger.Info("encryption salt established")
return nil
}

View File

@ -0,0 +1,105 @@
// 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 secrets_test
import (
"log"
"os"
"path/filepath"
"testing"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/ctest"
secretsctrl "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/secrets"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/resources/block"
"github.com/siderolabs/talos/pkg/machinery/resources/secrets"
)
type EncryptionSaltSuite struct {
ctest.DefaultSuite
}
func (suite *EncryptionSaltSuite) TestDefault() {
statePath := suite.T().TempDir()
mountID := (&secretsctrl.EncryptionSaltController{}).Name() + "-" + constants.StatePartitionLabel
ctest.AssertResource(suite, mountID, func(mountRequest *block.VolumeMountRequest, asrt *assert.Assertions) {
asrt.Equal(constants.StatePartitionLabel, mountRequest.TypedSpec().VolumeID)
})
ctest.AssertNoResource[*secrets.EncryptionSalt](suite, secrets.EncryptionSaltID)
volumeMountStatus := block.NewVolumeMountStatus(block.NamespaceName, mountID)
volumeMountStatus.TypedSpec().Target = statePath
suite.Create(volumeMountStatus)
ctest.AssertResource(suite, secrets.EncryptionSaltID, func(*secrets.EncryptionSalt, *assert.Assertions) {})
ctest.AssertResources(suite, []resource.ID{volumeMountStatus.Metadata().ID()}, func(vms *block.VolumeMountStatus, asrt *assert.Assertions) {
asrt.True(vms.Metadata().Finalizers().Empty())
})
suite.Destroy(volumeMountStatus)
ctest.AssertNoResource[*block.VolumeMountRequest](suite, mountID)
suite.Assert().FileExists(filepath.Join(statePath, constants.EncryptionSaltFilename), "encryption salt file should exist")
contents, err := os.ReadFile(filepath.Join(statePath, constants.EncryptionSaltFilename))
suite.Require().NoError(err, "should be able to read encryption salt file")
log.Printf("contents: %q", contents)
}
func (suite *EncryptionSaltSuite) TestLoad() {
statePath := suite.T().TempDir()
mountID := (&secretsctrl.EncryptionSaltController{}).Name() + "-" + constants.StatePartitionLabel
ctest.AssertResource(suite, mountID, func(mountRequest *block.VolumeMountRequest, asrt *assert.Assertions) {
asrt.Equal(constants.StatePartitionLabel, mountRequest.TypedSpec().VolumeID)
})
// using verbatim data here to make sure salt representation is supported in future version fo Talos
suite.Require().NoError(os.WriteFile(filepath.Join(statePath, constants.EncryptionSaltFilename),
[]byte("diskSalt:\n - 240\n - 180\n - 79\n - 128\n - 31\n - 0\n - 19\n - 124\n - 165\n - 74\n - 113\n - 220\n - 27\n - 83\n - 46\n - 74\n - 204\n - 190\n - 217\n - 96\n - 221\n - 2\n - 165\n - 98\n - 245\n - 36\n - 165\n - 151\n - 149\n - 66\n - 113\n - 16\n"), //nolint:lll
0o600))
ctest.AssertNoResource[*secrets.EncryptionSalt](suite, secrets.EncryptionSaltID)
volumeMountStatus := block.NewVolumeMountStatus(block.NamespaceName, mountID)
volumeMountStatus.TypedSpec().Target = statePath
suite.Create(volumeMountStatus)
ctest.AssertResource(suite, secrets.EncryptionSaltID, func(encryptionSalt *secrets.EncryptionSalt, asrt *assert.Assertions) {
asrt.Equal(
[]byte{0xf0, 0xb4, 0x4f, 0x80, 0x1f, 0x0, 0x13, 0x7c, 0xa5, 0x4a, 0x71, 0xdc, 0x1b, 0x53, 0x2e, 0x4a, 0xcc, 0xbe, 0xd9, 0x60, 0xdd, 0x2, 0xa5, 0x62, 0xf5, 0x24, 0xa5, 0x97, 0x95, 0x42, 0x71, 0x10},
encryptionSalt.TypedSpec().DiskSalt,
)
})
ctest.AssertResources(suite, []resource.ID{volumeMountStatus.Metadata().ID()}, func(vms *block.VolumeMountStatus, asrt *assert.Assertions) {
asrt.True(vms.Metadata().Finalizers().Empty())
})
suite.Destroy(volumeMountStatus)
ctest.AssertNoResource[*block.VolumeMountRequest](suite, mountID)
}
func TestEncryptionSaltSuite(t *testing.T) {
t.Parallel()
suite.Run(t, &EncryptionSaltSuite{
DefaultSuite: ctest.DefaultSuite{
AfterSetup: func(suite *ctest.DefaultSuite) {
suite.Require().NoError(suite.Runtime().RegisterController(&secretsctrl.EncryptionSaltController{}))
},
},
})
}

View File

@ -378,6 +378,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
&runtimecontrollers.WatchdogTimerController{},
&secrets.APICertSANsController{},
&secrets.APIController{},
&secrets.EncryptionSaltController{},
&secrets.EtcdController{},
secrets.NewKubeletController(),
&secrets.KubernetesCertSANsController{},

View File

@ -232,6 +232,7 @@ func NewState() (*State, error) {
&runtime.WatchdogTimerStatus{},
&secrets.API{},
&secrets.CertSAN{},
&secrets.EncryptionSalt{},
&secrets.Etcd{},
&secrets.EtcdRoot{},
&secrets.Kubelet{},

View File

@ -128,6 +128,8 @@ func (suite *ResetSuite) TestResetWithSpecEphemeral() {
// TestResetWithSpecStateAndUserDisks resets state partition and user disks on the node.
//
// As ephemeral partition is not reset, so kubelet cert shouldn't change.
//
//nolint:gocyclo
func (suite *ResetSuite) TestResetWithSpecStateAndUserDisks() {
if suite.Capabilities().SecureBooted {
// this is because in secure boot mode, the machine config is only applied and cannot be passed as kernel args
@ -135,8 +137,25 @@ func (suite *ResetSuite) TestResetWithSpecStateAndUserDisks() {
}
node := suite.RandomDiscoveredNodeInternalIP(machine.TypeWorker)
nodeCtx := client.WithNode(suite.ctx, node)
disks, err := suite.Client.Disks(client.WithNode(suite.ctx, node))
suite.T().Logf("resetting STATE + user disk on node %s", node)
config, err := suite.ReadConfigFromNode(nodeCtx)
suite.Require().NoError(err)
// check if EPHEMERAL is encrypted locked to STATE
var lockedToState bool
if volume, ok := config.Volumes().ByName(constants.EphemeralPartitionLabel); ok && volume.Encryption() != nil {
for _, key := range volume.Encryption().Keys() {
if key.LockToSTATE() {
lockedToState = true
}
}
}
disks, err := suite.Client.Disks(nodeCtx)
suite.Require().NoError(err)
suite.Require().NotEmpty(disks.Messages)
@ -160,6 +179,26 @@ func (suite *ResetSuite) TestResetWithSpecStateAndUserDisks() {
},
)
if !lockedToState {
// if not locked to STATE, wipe will be successful
suite.ResetNode(suite.ctx, node, &machineapi.ResetRequest{
Reboot: true,
Graceful: true,
SystemPartitionsToWipe: []*machineapi.ResetPartitionSpec{
{
Label: constants.StatePartitionLabel,
Wipe: true,
},
},
UserDisksToWipe: userDisksToWipe,
}, true)
return
}
suite.T().Logf("verifying that EPHEMERAL partition would fail to unlock after reset, as it is locked to STATE")
// if the EPHEMERAL partition is locked to STATE, it will fail to unlock after reset, so let's verify it
suite.ResetNode(suite.ctx, node, &machineapi.ResetRequest{
Reboot: true,
Graceful: true,
@ -170,6 +209,27 @@ func (suite *ResetSuite) TestResetWithSpecStateAndUserDisks() {
},
},
UserDisksToWipe: userDisksToWipe,
}, false)
// wait for EPHEMERAL failure
rtestutils.AssertResources(nodeCtx, suite.T(), suite.Client.COSI,
[]string{constants.EphemeralPartitionLabel},
func(vs *block.VolumeStatus, asrt *assert.Assertions) {
asrt.Equal(block.VolumePhaseFailed, vs.TypedSpec().Phase)
asrt.Contains(vs.TypedSpec().ErrorMessage, "encryption key rejected")
},
)
// now reset EPHEMERAL
suite.ResetNode(suite.ctx, node, &machineapi.ResetRequest{
Reboot: true,
Graceful: false,
SystemPartitionsToWipe: []*machineapi.ResetPartitionSpec{
{
Label: constants.EphemeralPartitionLabel,
Wipe: true,
},
},
}, true)
}

View File

@ -28,8 +28,15 @@ import (
const keyHandlerTimeout = time.Second * 10
// Helpers provides helper methods for encryption handling.
type Helpers struct {
GetSystemInformation helpers.SystemInformationGetter
TPMLocker helpers.TPMLockFunc
SaltGetter helpers.SaltGetter
}
// NewHandler creates new Handler.
func NewHandler(encryptionConfig block.EncryptionSpec, volumeID string, getSystemInformation helpers.SystemInformationGetter, tpmLocker helpers.TPMLockFunc) (*Handler, error) {
func NewHandler(encryptionConfig block.EncryptionSpec, volumeID string, helpers Helpers) (*Handler, error) {
cipher, err := luks.ParseCipherKind(encryptionConfig.Cipher)
if err != nil {
return nil, fmt.Errorf("failed to parse cipher kind: %w", err)
@ -60,8 +67,9 @@ func NewHandler(encryptionConfig block.EncryptionSpec, volumeID string, getSyste
for _, cfg := range encryptionConfig.Keys {
handler, err := keys.NewHandler(cfg,
keys.WithVolumeID(volumeID),
keys.WithSystemInformationGetter(getSystemInformation),
keys.WithTPMLocker(tpmLocker),
keys.WithSystemInformationGetter(helpers.GetSystemInformation),
keys.WithTPMLocker(helpers.TPMLocker),
keys.WithSaltGetter(helpers.SaltGetter),
)
if err != nil {
return nil, err
@ -81,6 +89,7 @@ func NewHandler(encryptionConfig block.EncryptionSpec, volumeID string, getSyste
return &Handler{
encryptionProvider: provider,
keyHandlers: keyHandlers,
saltGetter: helpers.SaltGetter,
}, nil
}
@ -89,6 +98,7 @@ func NewHandler(encryptionConfig block.EncryptionSpec, volumeID string, getSyste
type Handler struct {
encryptionProvider encryption.Provider
keyHandlers []keys.Handler
saltGetter helpers.SaltGetter
}
// Open encrypted partition.

View File

@ -16,3 +16,6 @@ type SystemInformationGetter func(context.Context) (*hardware.SystemInformation,
// TPMLockFunc is a function that ensures that the TPM is locked and PCR state is as expected.
type TPMLockFunc func(context.Context, func() error) error
// SaltGetter defines the closure which can be used in key handlers to get the encryption salt.
type SaltGetter func(context.Context) ([]byte, error)

View File

@ -19,6 +19,8 @@ import (
var errNoSystemInfoGetter = errors.New("the UUID getter is not set")
// NewHandler key using provided config.
//
//nolint:gocyclo
func NewHandler(cfg block.EncryptionKey, options ...KeyOption) (Handler, error) {
opts, err := NewDefaultOptions(options)
if err != nil {
@ -27,6 +29,8 @@ func NewHandler(cfg block.EncryptionKey, options ...KeyOption) (Handler, error)
key := KeyHandler{slot: cfg.Slot}
var handler Handler
switch cfg.Type {
case block.EncryptionKeyStatic:
k := cfg.StaticPassphrase
@ -34,28 +38,44 @@ func NewHandler(cfg block.EncryptionKey, options ...KeyOption) (Handler, error)
return nil, errors.New("static key must have key data defined")
}
return NewStaticKeyHandler(key, k), nil
handler = NewStaticKeyHandler(key, k)
case block.EncryptionKeyNodeID:
if opts.GetSystemInformation == nil {
return nil, fmt.Errorf("failed to create nodeUUID key handler at slot %d: %w", cfg.Slot, errNoSystemInfoGetter)
}
return NewNodeIDKeyHandler(key, opts.VolumeID, opts.GetSystemInformation), nil
handler = NewNodeIDKeyHandler(key, opts.VolumeID, opts.GetSystemInformation)
case block.EncryptionKeyKMS:
if opts.GetSystemInformation == nil {
return nil, fmt.Errorf("failed to create KMS key handler at slot %d: %w", cfg.Slot, errNoSystemInfoGetter)
}
return NewKMSKeyHandler(key, cfg.KMSEndpoint, opts.GetSystemInformation)
handler, err = NewKMSKeyHandler(key, cfg.KMSEndpoint, opts.GetSystemInformation)
if err != nil {
return nil, err
}
case block.EncryptionKeyTPM:
if opts.TPMLocker == nil {
return nil, fmt.Errorf("failed to create TPM key handler at slot %d: no TPM lock function", cfg.Slot)
}
return NewTPMKeyHandler(key, cfg.TPMCheckSecurebootStatusOnEnroll, opts.TPMLocker)
handler, err = NewTPMKeyHandler(key, cfg.TPMCheckSecurebootStatusOnEnroll, opts.TPMLocker)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported key type: %s", cfg.Type)
}
if cfg.LockToSTATE {
if opts.SaltGetter == nil {
return nil, fmt.Errorf("failed to create state-locked key handler at slot %d: no salt getter", cfg.Slot)
}
handler = NewSaltedHandler(handler, opts.SaltGetter)
}
return handler, nil
}
// Handler manages key lifecycle.

View File

@ -0,0 +1,52 @@
// 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 keys_test
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/siderolabs/talos/internal/pkg/encryption/keys"
"github.com/siderolabs/talos/pkg/machinery/resources/hardware"
)
func TestNodeID(t *testing.T) {
t.Parallel()
handler := keys.NewNodeIDKeyHandler(keys.KeyHandler{}, "test", func(context.Context) (*hardware.SystemInformation, error) {
res := hardware.NewSystemInformation(hardware.SystemInformationID)
res.TypedSpec().UUID = "12345678-1234-5678-1234-567812345678"
return res, nil
})
key, token, err := handler.NewKey(t.Context())
require.NoError(t, err)
require.Nil(t, token)
assert.Equal(t, "12345678-1234-5678-1234-567812345678test", string(key.Value))
key, err = handler.GetKey(t.Context(), nil)
require.NoError(t, err)
assert.Equal(t, "12345678-1234-5678-1234-567812345678test", string(key.Value))
}
func TestNodeIDBadEntropy(t *testing.T) {
t.Parallel()
handler := keys.NewNodeIDKeyHandler(keys.KeyHandler{}, "test", func(context.Context) (*hardware.SystemInformation, error) {
res := hardware.NewSystemInformation(hardware.SystemInformationID)
res.TypedSpec().UUID = "11111111-0000-1111-1111-111111111111" // bad entropy
return res, nil
})
_, _, err := handler.NewKey(t.Context())
require.Error(t, err)
assert.EqualError(t, err, "machine UUID 11111111-0000-1111-1111-111111111111 entropy check failed")
}

View File

@ -14,6 +14,7 @@ type KeyOptions struct {
VolumeID string
GetSystemInformation helpers.SystemInformationGetter
TPMLocker helpers.TPMLockFunc
SaltGetter helpers.SaltGetter
}
// WithVolumeID passes the partition label to the key handler.
@ -43,6 +44,15 @@ func WithTPMLocker(locker helpers.TPMLockFunc) KeyOption {
}
}
// WithSaltGetter passes the salt getter to the key handler.
func WithSaltGetter(getter helpers.SaltGetter) KeyOption {
return func(o *KeyOptions) error {
o.SaltGetter = getter
return nil
}
}
// NewDefaultOptions creates new KeyOptions.
func NewDefaultOptions(options []KeyOption) (*KeyOptions, error) {
var opts KeyOptions

View File

@ -0,0 +1,69 @@
// 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 keys
import (
"context"
"fmt"
"slices"
"github.com/siderolabs/go-blockdevice/v2/encryption"
"github.com/siderolabs/go-blockdevice/v2/encryption/token"
"github.com/siderolabs/talos/internal/pkg/encryption/helpers"
)
// SaltedHandler is a key handler wrapper that salts the key with a provided random salt.
type SaltedHandler struct {
wrapped Handler
saltGetter helpers.SaltGetter
}
// NewSaltedHandler creates a new handler that wraps the provided key handler and uses the provided salt getter.
func NewSaltedHandler(wrapped Handler, saltGetter helpers.SaltGetter) Handler {
return &SaltedHandler{
wrapped: wrapped,
saltGetter: saltGetter,
}
}
// NewKey implements the keys.Handler interface.
func (k *SaltedHandler) NewKey(ctx context.Context) (*encryption.Key, token.Token, error) {
key, token, err := k.wrapped.NewKey(ctx)
if err != nil {
return key, token, err
}
salt, err := k.saltGetter(ctx)
if err != nil {
return nil, nil, fmt.Errorf("failed to get disk encryption key salt: %w", err)
}
key.Value = slices.Concat(key.Value, salt)
return key, token, nil
}
// GetKey implements the keys.Handler interface.
func (k *SaltedHandler) GetKey(ctx context.Context, token token.Token) (*encryption.Key, error) {
key, err := k.wrapped.GetKey(ctx, token)
if err != nil {
return key, err
}
salt, err := k.saltGetter(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get disk encryption key salt: %w", err)
}
key.Value = slices.Concat(key.Value, salt)
return key, nil
}
// Slot implements the keys.Handler interface.
func (k *SaltedHandler) Slot() int {
return k.wrapped.Slot()
}

View File

@ -0,0 +1,40 @@
// 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 keys_test
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/siderolabs/talos/internal/pkg/encryption/keys"
)
func TestSalted(t *testing.T) {
t.Parallel()
const (
secret = "topsecret"
salt = "salted"
)
inner := keys.NewStaticKeyHandler(keys.KeyHandler{}, []byte(secret))
handler := keys.NewSaltedHandler(inner, func(context.Context) ([]byte, error) {
return []byte(salt), nil
})
key, token, err := handler.NewKey(t.Context())
require.NoError(t, err)
require.Nil(t, token)
assert.Equal(t, secret+salt, string(key.Value))
key, err = handler.GetKey(t.Context(), nil)
require.NoError(t, err)
assert.Equal(t, secret+salt, string(key.Value))
}

View File

@ -0,0 +1,32 @@
// 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 keys_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/siderolabs/talos/internal/pkg/encryption/keys"
)
func TestStatic(t *testing.T) {
t.Parallel()
const secret = "topsecret"
handler := keys.NewStaticKeyHandler(keys.KeyHandler{}, []byte(secret))
key, token, err := handler.NewKey(t.Context())
require.NoError(t, err)
require.Nil(t, token)
assert.Equal(t, secret, string(key.Value))
key1, err := handler.GetKey(t.Context(), nil)
require.NoError(t, err)
assert.Equal(t, key, key1)
}

View File

@ -647,6 +647,7 @@ type EncryptionKey struct {
StaticPassphrase []byte `protobuf:"bytes,3,opt,name=static_passphrase,json=staticPassphrase,proto3" json:"static_passphrase,omitempty"`
KmsEndpoint string `protobuf:"bytes,4,opt,name=kms_endpoint,json=kmsEndpoint,proto3" json:"kms_endpoint,omitempty"`
TpmCheckSecurebootStatusOnEnroll bool `protobuf:"varint,5,opt,name=tpm_check_secureboot_status_on_enroll,json=tpmCheckSecurebootStatusOnEnroll,proto3" json:"tpm_check_secureboot_status_on_enroll,omitempty"`
LockToState bool `protobuf:"varint,6,opt,name=lock_to_state,json=lockToState,proto3" json:"lock_to_state,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -716,6 +717,13 @@ func (x *EncryptionKey) GetTpmCheckSecurebootStatusOnEnroll() bool {
return false
}
func (x *EncryptionKey) GetLockToState() bool {
if x != nil {
return x.LockToState
}
return false
}
// EncryptionSpec is the spec for volume encryption.
type EncryptionSpec struct {
state protoimpl.MessageState `protogen:"open.v1"`
@ -2217,13 +2225,14 @@ const file_resource_definitions_block_block_proto_rawDesc = "" +
"prettySize\x12'\n" +
"\x0fsecondary_disks\x18\x10 \x03(\tR\x0esecondaryDisks\x12\x12\n" +
"\x04uuid\x18\x11 \x01(\tR\x04uuid\x12\x1a\n" +
"\bsymlinks\x18\x12 \x03(\tR\bsymlinks\"\x92\x02\n" +
"\bsymlinks\x18\x12 \x03(\tR\bsymlinks\"\xb6\x02\n" +
"\rEncryptionKey\x12\x12\n" +
"\x04slot\x18\x01 \x01(\x03R\x04slot\x12L\n" +
"\x04type\x18\x02 \x01(\x0e28.talos.resource.definitions.enums.BlockEncryptionKeyTypeR\x04type\x12+\n" +
"\x11static_passphrase\x18\x03 \x01(\fR\x10staticPassphrase\x12!\n" +
"\fkms_endpoint\x18\x04 \x01(\tR\vkmsEndpoint\x12O\n" +
"%tpm_check_secureboot_status_on_enroll\x18\x05 \x01(\bR tpmCheckSecurebootStatusOnEnroll\"\xa5\x02\n" +
"%tpm_check_secureboot_status_on_enroll\x18\x05 \x01(\bR tpmCheckSecurebootStatusOnEnroll\x12\"\n" +
"\rlock_to_state\x18\x06 \x01(\bR\vlockToState\"\xa5\x02\n" +
"\x0eEncryptionSpec\x12Y\n" +
"\bprovider\x18\x01 \x01(\x0e2=.talos.resource.definitions.enums.BlockEncryptionProviderTypeR\bprovider\x12C\n" +
"\x04keys\x18\x02 \x03(\v2/.talos.resource.definitions.block.EncryptionKeyR\x04keys\x12\x16\n" +

View File

@ -606,6 +606,16 @@ func (m *EncryptionKey) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if m.LockToState {
i--
if m.LockToState {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x30
}
if m.TpmCheckSecurebootStatusOnEnroll {
i--
if m.TpmCheckSecurebootStatusOnEnroll {
@ -2216,6 +2226,9 @@ func (m *EncryptionKey) SizeVT() (n int) {
if m.TpmCheckSecurebootStatusOnEnroll {
n += 2
}
if m.LockToState {
n += 2
}
n += len(m.unknownFields)
return n
}
@ -4552,6 +4565,26 @@ func (m *EncryptionKey) UnmarshalVT(dAtA []byte) error {
}
}
m.TpmCheckSecurebootStatusOnEnroll = bool(v != 0)
case 6:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field LockToState", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.LockToState = bool(v != 0)
default:
iNdEx = preIndex
skippy, err := protohelpers.Skip(dAtA[iNdEx:])

View File

@ -146,6 +146,51 @@ func (x *CertSANSpec) GetFqdn() string {
return ""
}
// EncryptionSaltSpec describes the salt.
type EncryptionSaltSpec struct {
state protoimpl.MessageState `protogen:"open.v1"`
DiskSalt []byte `protobuf:"bytes,1,opt,name=disk_salt,json=diskSalt,proto3" json:"disk_salt,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *EncryptionSaltSpec) Reset() {
*x = EncryptionSaltSpec{}
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EncryptionSaltSpec) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EncryptionSaltSpec) ProtoMessage() {}
func (x *EncryptionSaltSpec) ProtoReflect() protoreflect.Message {
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EncryptionSaltSpec.ProtoReflect.Descriptor instead.
func (*EncryptionSaltSpec) Descriptor() ([]byte, []int) {
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{2}
}
func (x *EncryptionSaltSpec) GetDiskSalt() []byte {
if x != nil {
return x.DiskSalt
}
return nil
}
// EtcdCertsSpec describes etcd certs secrets.
type EtcdCertsSpec struct {
state protoimpl.MessageState `protogen:"open.v1"`
@ -159,7 +204,7 @@ type EtcdCertsSpec struct {
func (x *EtcdCertsSpec) Reset() {
*x = EtcdCertsSpec{}
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[2]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -171,7 +216,7 @@ func (x *EtcdCertsSpec) String() string {
func (*EtcdCertsSpec) ProtoMessage() {}
func (x *EtcdCertsSpec) ProtoReflect() protoreflect.Message {
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[2]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -184,7 +229,7 @@ func (x *EtcdCertsSpec) ProtoReflect() protoreflect.Message {
// Deprecated: Use EtcdCertsSpec.ProtoReflect.Descriptor instead.
func (*EtcdCertsSpec) Descriptor() ([]byte, []int) {
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{2}
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{3}
}
func (x *EtcdCertsSpec) GetEtcd() *common.PEMEncodedCertificateAndKey {
@ -225,7 +270,7 @@ type EtcdRootSpec struct {
func (x *EtcdRootSpec) Reset() {
*x = EtcdRootSpec{}
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[3]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -237,7 +282,7 @@ func (x *EtcdRootSpec) String() string {
func (*EtcdRootSpec) ProtoMessage() {}
func (x *EtcdRootSpec) ProtoReflect() protoreflect.Message {
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[3]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -250,7 +295,7 @@ func (x *EtcdRootSpec) ProtoReflect() protoreflect.Message {
// Deprecated: Use EtcdRootSpec.ProtoReflect.Descriptor instead.
func (*EtcdRootSpec) Descriptor() ([]byte, []int) {
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{3}
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{4}
}
func (x *EtcdRootSpec) GetEtcdCa() *common.PEMEncodedCertificateAndKey {
@ -273,7 +318,7 @@ type KubeletSpec struct {
func (x *KubeletSpec) Reset() {
*x = KubeletSpec{}
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[4]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -285,7 +330,7 @@ func (x *KubeletSpec) String() string {
func (*KubeletSpec) ProtoMessage() {}
func (x *KubeletSpec) ProtoReflect() protoreflect.Message {
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[4]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -298,7 +343,7 @@ func (x *KubeletSpec) ProtoReflect() protoreflect.Message {
// Deprecated: Use KubeletSpec.ProtoReflect.Descriptor instead.
func (*KubeletSpec) Descriptor() ([]byte, []int) {
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{4}
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{5}
}
func (x *KubeletSpec) GetEndpoint() *common.URL {
@ -342,7 +387,7 @@ type KubernetesCertsSpec struct {
func (x *KubernetesCertsSpec) Reset() {
*x = KubernetesCertsSpec{}
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[5]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -354,7 +399,7 @@ func (x *KubernetesCertsSpec) String() string {
func (*KubernetesCertsSpec) ProtoMessage() {}
func (x *KubernetesCertsSpec) ProtoReflect() protoreflect.Message {
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[5]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -367,7 +412,7 @@ func (x *KubernetesCertsSpec) ProtoReflect() protoreflect.Message {
// Deprecated: Use KubernetesCertsSpec.ProtoReflect.Descriptor instead.
func (*KubernetesCertsSpec) Descriptor() ([]byte, []int) {
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{5}
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{6}
}
func (x *KubernetesCertsSpec) GetSchedulerKubeconfig() string {
@ -410,7 +455,7 @@ type KubernetesDynamicCertsSpec struct {
func (x *KubernetesDynamicCertsSpec) Reset() {
*x = KubernetesDynamicCertsSpec{}
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[6]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -422,7 +467,7 @@ func (x *KubernetesDynamicCertsSpec) String() string {
func (*KubernetesDynamicCertsSpec) ProtoMessage() {}
func (x *KubernetesDynamicCertsSpec) ProtoReflect() protoreflect.Message {
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[6]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -435,7 +480,7 @@ func (x *KubernetesDynamicCertsSpec) ProtoReflect() protoreflect.Message {
// Deprecated: Use KubernetesDynamicCertsSpec.ProtoReflect.Descriptor instead.
func (*KubernetesDynamicCertsSpec) Descriptor() ([]byte, []int) {
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{6}
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{7}
}
func (x *KubernetesDynamicCertsSpec) GetApiServer() *common.PEMEncodedCertificateAndKey {
@ -482,7 +527,7 @@ type KubernetesRootSpec struct {
func (x *KubernetesRootSpec) Reset() {
*x = KubernetesRootSpec{}
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[7]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -494,7 +539,7 @@ func (x *KubernetesRootSpec) String() string {
func (*KubernetesRootSpec) ProtoMessage() {}
func (x *KubernetesRootSpec) ProtoReflect() protoreflect.Message {
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[7]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -507,7 +552,7 @@ func (x *KubernetesRootSpec) ProtoReflect() protoreflect.Message {
// Deprecated: Use KubernetesRootSpec.ProtoReflect.Descriptor instead.
func (*KubernetesRootSpec) Descriptor() ([]byte, []int) {
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{7}
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{8}
}
func (x *KubernetesRootSpec) GetName() string {
@ -618,7 +663,7 @@ type MaintenanceRootSpec struct {
func (x *MaintenanceRootSpec) Reset() {
*x = MaintenanceRootSpec{}
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[8]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -630,7 +675,7 @@ func (x *MaintenanceRootSpec) String() string {
func (*MaintenanceRootSpec) ProtoMessage() {}
func (x *MaintenanceRootSpec) ProtoReflect() protoreflect.Message {
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[8]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -643,7 +688,7 @@ func (x *MaintenanceRootSpec) ProtoReflect() protoreflect.Message {
// Deprecated: Use MaintenanceRootSpec.ProtoReflect.Descriptor instead.
func (*MaintenanceRootSpec) Descriptor() ([]byte, []int) {
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{8}
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{9}
}
func (x *MaintenanceRootSpec) GetCa() *common.PEMEncodedCertificateAndKey {
@ -664,7 +709,7 @@ type MaintenanceServiceCertsSpec struct {
func (x *MaintenanceServiceCertsSpec) Reset() {
*x = MaintenanceServiceCertsSpec{}
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[9]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -676,7 +721,7 @@ func (x *MaintenanceServiceCertsSpec) String() string {
func (*MaintenanceServiceCertsSpec) ProtoMessage() {}
func (x *MaintenanceServiceCertsSpec) ProtoReflect() protoreflect.Message {
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[9]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[10]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -689,7 +734,7 @@ func (x *MaintenanceServiceCertsSpec) ProtoReflect() protoreflect.Message {
// Deprecated: Use MaintenanceServiceCertsSpec.ProtoReflect.Descriptor instead.
func (*MaintenanceServiceCertsSpec) Descriptor() ([]byte, []int) {
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{9}
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{10}
}
func (x *MaintenanceServiceCertsSpec) GetCa() *common.PEMEncodedCertificateAndKey {
@ -720,7 +765,7 @@ type OSRootSpec struct {
func (x *OSRootSpec) Reset() {
*x = OSRootSpec{}
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[10]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -732,7 +777,7 @@ func (x *OSRootSpec) String() string {
func (*OSRootSpec) ProtoMessage() {}
func (x *OSRootSpec) ProtoReflect() protoreflect.Message {
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[10]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[11]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -745,7 +790,7 @@ func (x *OSRootSpec) ProtoReflect() protoreflect.Message {
// Deprecated: Use OSRootSpec.ProtoReflect.Descriptor instead.
func (*OSRootSpec) Descriptor() ([]byte, []int) {
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{10}
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{11}
}
func (x *OSRootSpec) GetIssuingCa() *common.PEMEncodedCertificateAndKey {
@ -794,7 +839,7 @@ type TrustdCertsSpec struct {
func (x *TrustdCertsSpec) Reset() {
*x = TrustdCertsSpec{}
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[11]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -806,7 +851,7 @@ func (x *TrustdCertsSpec) String() string {
func (*TrustdCertsSpec) ProtoMessage() {}
func (x *TrustdCertsSpec) ProtoReflect() protoreflect.Message {
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[11]
mi := &file_resource_definitions_secrets_secrets_proto_msgTypes[12]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -819,7 +864,7 @@ func (x *TrustdCertsSpec) ProtoReflect() protoreflect.Message {
// Deprecated: Use TrustdCertsSpec.ProtoReflect.Descriptor instead.
func (*TrustdCertsSpec) Descriptor() ([]byte, []int) {
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{11}
return file_resource_definitions_secrets_secrets_proto_rawDescGZIP(), []int{12}
}
func (x *TrustdCertsSpec) GetServer() *common.PEMEncodedCertificateAndKey {
@ -848,7 +893,9 @@ const file_resource_definitions_secrets_secrets_proto_rawDesc = "" +
"\vCertSANSpec\x12 \n" +
"\x04i_ps\x18\x01 \x03(\v2\r.common.NetIPR\x03iPs\x12\x1b\n" +
"\tdns_names\x18\x02 \x03(\tR\bdnsNames\x12\x12\n" +
"\x04fqdn\x18\x03 \x01(\tR\x04fqdn\"\x9b\x02\n" +
"\x04fqdn\x18\x03 \x01(\tR\x04fqdn\"1\n" +
"\x12EncryptionSaltSpec\x12\x1b\n" +
"\tdisk_salt\x18\x01 \x01(\fR\bdiskSalt\"\x9b\x02\n" +
"\rEtcdCertsSpec\x127\n" +
"\x04etcd\x18\x01 \x01(\v2#.common.PEMEncodedCertificateAndKeyR\x04etcd\x12@\n" +
"\tetcd_peer\x18\x02 \x01(\v2#.common.PEMEncodedCertificateAndKeyR\betcdPeer\x12B\n" +
@ -923,56 +970,57 @@ func file_resource_definitions_secrets_secrets_proto_rawDescGZIP() []byte {
return file_resource_definitions_secrets_secrets_proto_rawDescData
}
var file_resource_definitions_secrets_secrets_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
var file_resource_definitions_secrets_secrets_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
var file_resource_definitions_secrets_secrets_proto_goTypes = []any{
(*APICertsSpec)(nil), // 0: talos.resource.definitions.secrets.APICertsSpec
(*CertSANSpec)(nil), // 1: talos.resource.definitions.secrets.CertSANSpec
(*EtcdCertsSpec)(nil), // 2: talos.resource.definitions.secrets.EtcdCertsSpec
(*EtcdRootSpec)(nil), // 3: talos.resource.definitions.secrets.EtcdRootSpec
(*KubeletSpec)(nil), // 4: talos.resource.definitions.secrets.KubeletSpec
(*KubernetesCertsSpec)(nil), // 5: talos.resource.definitions.secrets.KubernetesCertsSpec
(*KubernetesDynamicCertsSpec)(nil), // 6: talos.resource.definitions.secrets.KubernetesDynamicCertsSpec
(*KubernetesRootSpec)(nil), // 7: talos.resource.definitions.secrets.KubernetesRootSpec
(*MaintenanceRootSpec)(nil), // 8: talos.resource.definitions.secrets.MaintenanceRootSpec
(*MaintenanceServiceCertsSpec)(nil), // 9: talos.resource.definitions.secrets.MaintenanceServiceCertsSpec
(*OSRootSpec)(nil), // 10: talos.resource.definitions.secrets.OSRootSpec
(*TrustdCertsSpec)(nil), // 11: talos.resource.definitions.secrets.TrustdCertsSpec
(*common.PEMEncodedCertificateAndKey)(nil), // 12: common.PEMEncodedCertificateAndKey
(*common.PEMEncodedCertificate)(nil), // 13: common.PEMEncodedCertificate
(*common.NetIP)(nil), // 14: common.NetIP
(*common.URL)(nil), // 15: common.URL
(*common.PEMEncodedKey)(nil), // 16: common.PEMEncodedKey
(*EncryptionSaltSpec)(nil), // 2: talos.resource.definitions.secrets.EncryptionSaltSpec
(*EtcdCertsSpec)(nil), // 3: talos.resource.definitions.secrets.EtcdCertsSpec
(*EtcdRootSpec)(nil), // 4: talos.resource.definitions.secrets.EtcdRootSpec
(*KubeletSpec)(nil), // 5: talos.resource.definitions.secrets.KubeletSpec
(*KubernetesCertsSpec)(nil), // 6: talos.resource.definitions.secrets.KubernetesCertsSpec
(*KubernetesDynamicCertsSpec)(nil), // 7: talos.resource.definitions.secrets.KubernetesDynamicCertsSpec
(*KubernetesRootSpec)(nil), // 8: talos.resource.definitions.secrets.KubernetesRootSpec
(*MaintenanceRootSpec)(nil), // 9: talos.resource.definitions.secrets.MaintenanceRootSpec
(*MaintenanceServiceCertsSpec)(nil), // 10: talos.resource.definitions.secrets.MaintenanceServiceCertsSpec
(*OSRootSpec)(nil), // 11: talos.resource.definitions.secrets.OSRootSpec
(*TrustdCertsSpec)(nil), // 12: talos.resource.definitions.secrets.TrustdCertsSpec
(*common.PEMEncodedCertificateAndKey)(nil), // 13: common.PEMEncodedCertificateAndKey
(*common.PEMEncodedCertificate)(nil), // 14: common.PEMEncodedCertificate
(*common.NetIP)(nil), // 15: common.NetIP
(*common.URL)(nil), // 16: common.URL
(*common.PEMEncodedKey)(nil), // 17: common.PEMEncodedKey
}
var file_resource_definitions_secrets_secrets_proto_depIdxs = []int32{
12, // 0: talos.resource.definitions.secrets.APICertsSpec.client:type_name -> common.PEMEncodedCertificateAndKey
12, // 1: talos.resource.definitions.secrets.APICertsSpec.server:type_name -> common.PEMEncodedCertificateAndKey
13, // 2: talos.resource.definitions.secrets.APICertsSpec.accepted_c_as:type_name -> common.PEMEncodedCertificate
14, // 3: talos.resource.definitions.secrets.CertSANSpec.i_ps:type_name -> common.NetIP
12, // 4: talos.resource.definitions.secrets.EtcdCertsSpec.etcd:type_name -> common.PEMEncodedCertificateAndKey
12, // 5: talos.resource.definitions.secrets.EtcdCertsSpec.etcd_peer:type_name -> common.PEMEncodedCertificateAndKey
12, // 6: talos.resource.definitions.secrets.EtcdCertsSpec.etcd_admin:type_name -> common.PEMEncodedCertificateAndKey
12, // 7: talos.resource.definitions.secrets.EtcdCertsSpec.etcd_api_server:type_name -> common.PEMEncodedCertificateAndKey
12, // 8: talos.resource.definitions.secrets.EtcdRootSpec.etcd_ca:type_name -> common.PEMEncodedCertificateAndKey
15, // 9: talos.resource.definitions.secrets.KubeletSpec.endpoint:type_name -> common.URL
13, // 10: talos.resource.definitions.secrets.KubeletSpec.accepted_c_as:type_name -> common.PEMEncodedCertificate
12, // 11: talos.resource.definitions.secrets.KubernetesDynamicCertsSpec.api_server:type_name -> common.PEMEncodedCertificateAndKey
12, // 12: talos.resource.definitions.secrets.KubernetesDynamicCertsSpec.api_server_kubelet_client:type_name -> common.PEMEncodedCertificateAndKey
12, // 13: talos.resource.definitions.secrets.KubernetesDynamicCertsSpec.front_proxy:type_name -> common.PEMEncodedCertificateAndKey
15, // 14: talos.resource.definitions.secrets.KubernetesRootSpec.endpoint:type_name -> common.URL
15, // 15: talos.resource.definitions.secrets.KubernetesRootSpec.local_endpoint:type_name -> common.URL
12, // 16: talos.resource.definitions.secrets.KubernetesRootSpec.issuing_ca:type_name -> common.PEMEncodedCertificateAndKey
16, // 17: talos.resource.definitions.secrets.KubernetesRootSpec.service_account:type_name -> common.PEMEncodedKey
12, // 18: talos.resource.definitions.secrets.KubernetesRootSpec.aggregator_ca:type_name -> common.PEMEncodedCertificateAndKey
14, // 19: talos.resource.definitions.secrets.KubernetesRootSpec.api_server_ips:type_name -> common.NetIP
13, // 20: talos.resource.definitions.secrets.KubernetesRootSpec.accepted_c_as:type_name -> common.PEMEncodedCertificate
12, // 21: talos.resource.definitions.secrets.MaintenanceRootSpec.ca:type_name -> common.PEMEncodedCertificateAndKey
12, // 22: talos.resource.definitions.secrets.MaintenanceServiceCertsSpec.ca:type_name -> common.PEMEncodedCertificateAndKey
12, // 23: talos.resource.definitions.secrets.MaintenanceServiceCertsSpec.server:type_name -> common.PEMEncodedCertificateAndKey
12, // 24: talos.resource.definitions.secrets.OSRootSpec.issuing_ca:type_name -> common.PEMEncodedCertificateAndKey
14, // 25: talos.resource.definitions.secrets.OSRootSpec.cert_sani_ps:type_name -> common.NetIP
13, // 26: talos.resource.definitions.secrets.OSRootSpec.accepted_c_as:type_name -> common.PEMEncodedCertificate
12, // 27: talos.resource.definitions.secrets.TrustdCertsSpec.server:type_name -> common.PEMEncodedCertificateAndKey
13, // 28: talos.resource.definitions.secrets.TrustdCertsSpec.accepted_c_as:type_name -> common.PEMEncodedCertificate
13, // 0: talos.resource.definitions.secrets.APICertsSpec.client:type_name -> common.PEMEncodedCertificateAndKey
13, // 1: talos.resource.definitions.secrets.APICertsSpec.server:type_name -> common.PEMEncodedCertificateAndKey
14, // 2: talos.resource.definitions.secrets.APICertsSpec.accepted_c_as:type_name -> common.PEMEncodedCertificate
15, // 3: talos.resource.definitions.secrets.CertSANSpec.i_ps:type_name -> common.NetIP
13, // 4: talos.resource.definitions.secrets.EtcdCertsSpec.etcd:type_name -> common.PEMEncodedCertificateAndKey
13, // 5: talos.resource.definitions.secrets.EtcdCertsSpec.etcd_peer:type_name -> common.PEMEncodedCertificateAndKey
13, // 6: talos.resource.definitions.secrets.EtcdCertsSpec.etcd_admin:type_name -> common.PEMEncodedCertificateAndKey
13, // 7: talos.resource.definitions.secrets.EtcdCertsSpec.etcd_api_server:type_name -> common.PEMEncodedCertificateAndKey
13, // 8: talos.resource.definitions.secrets.EtcdRootSpec.etcd_ca:type_name -> common.PEMEncodedCertificateAndKey
16, // 9: talos.resource.definitions.secrets.KubeletSpec.endpoint:type_name -> common.URL
14, // 10: talos.resource.definitions.secrets.KubeletSpec.accepted_c_as:type_name -> common.PEMEncodedCertificate
13, // 11: talos.resource.definitions.secrets.KubernetesDynamicCertsSpec.api_server:type_name -> common.PEMEncodedCertificateAndKey
13, // 12: talos.resource.definitions.secrets.KubernetesDynamicCertsSpec.api_server_kubelet_client:type_name -> common.PEMEncodedCertificateAndKey
13, // 13: talos.resource.definitions.secrets.KubernetesDynamicCertsSpec.front_proxy:type_name -> common.PEMEncodedCertificateAndKey
16, // 14: talos.resource.definitions.secrets.KubernetesRootSpec.endpoint:type_name -> common.URL
16, // 15: talos.resource.definitions.secrets.KubernetesRootSpec.local_endpoint:type_name -> common.URL
13, // 16: talos.resource.definitions.secrets.KubernetesRootSpec.issuing_ca:type_name -> common.PEMEncodedCertificateAndKey
17, // 17: talos.resource.definitions.secrets.KubernetesRootSpec.service_account:type_name -> common.PEMEncodedKey
13, // 18: talos.resource.definitions.secrets.KubernetesRootSpec.aggregator_ca:type_name -> common.PEMEncodedCertificateAndKey
15, // 19: talos.resource.definitions.secrets.KubernetesRootSpec.api_server_ips:type_name -> common.NetIP
14, // 20: talos.resource.definitions.secrets.KubernetesRootSpec.accepted_c_as:type_name -> common.PEMEncodedCertificate
13, // 21: talos.resource.definitions.secrets.MaintenanceRootSpec.ca:type_name -> common.PEMEncodedCertificateAndKey
13, // 22: talos.resource.definitions.secrets.MaintenanceServiceCertsSpec.ca:type_name -> common.PEMEncodedCertificateAndKey
13, // 23: talos.resource.definitions.secrets.MaintenanceServiceCertsSpec.server:type_name -> common.PEMEncodedCertificateAndKey
13, // 24: talos.resource.definitions.secrets.OSRootSpec.issuing_ca:type_name -> common.PEMEncodedCertificateAndKey
15, // 25: talos.resource.definitions.secrets.OSRootSpec.cert_sani_ps:type_name -> common.NetIP
14, // 26: talos.resource.definitions.secrets.OSRootSpec.accepted_c_as:type_name -> common.PEMEncodedCertificate
13, // 27: talos.resource.definitions.secrets.TrustdCertsSpec.server:type_name -> common.PEMEncodedCertificateAndKey
14, // 28: talos.resource.definitions.secrets.TrustdCertsSpec.accepted_c_as:type_name -> common.PEMEncodedCertificate
29, // [29:29] is the sub-list for method output_type
29, // [29:29] is the sub-list for method input_type
29, // [29:29] is the sub-list for extension type_name
@ -991,7 +1039,7 @@ func file_resource_definitions_secrets_secrets_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_resource_definitions_secrets_secrets_proto_rawDesc), len(file_resource_definitions_secrets_secrets_proto_rawDesc)),
NumEnums: 0,
NumMessages: 12,
NumMessages: 13,
NumExtensions: 0,
NumServices: 0,
},

View File

@ -196,6 +196,46 @@ func (m *CertSANSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
func (m *EncryptionSaltSpec) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *EncryptionSaltSpec) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *EncryptionSaltSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.DiskSalt) > 0 {
i -= len(m.DiskSalt)
copy(dAtA[i:], m.DiskSalt)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.DiskSalt)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *EtcdCertsSpec) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
@ -1271,6 +1311,20 @@ func (m *CertSANSpec) SizeVT() (n int) {
return n
}
func (m *EncryptionSaltSpec) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.DiskSalt)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
n += len(m.unknownFields)
return n
}
func (m *EtcdCertsSpec) SizeVT() (n int) {
if m == nil {
return 0
@ -2035,6 +2089,91 @@ func (m *CertSANSpec) UnmarshalVT(dAtA []byte) error {
}
return nil
}
func (m *EncryptionSaltSpec) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: EncryptionSaltSpec: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: EncryptionSaltSpec: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field DiskSalt", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.DiskSalt = append(m.DiskSalt[:0], dAtA[iNdEx:postIndex]...)
if m.DiskSalt == nil {
m.DiskSalt = []byte{}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return protohelpers.ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *EtcdCertsSpec) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0

View File

@ -407,6 +407,7 @@ type EncryptionKey interface {
KMS() EncryptionKeyKMS
Slot() int
TPM() EncryptionKeyTPM
LockToSTATE() bool
}
// EncryptionKeyStatic ephemeral encryption key.

View File

@ -52,6 +52,13 @@
"description": "Enable TPM based disk encryption.\n",
"markdownDescription": "Enable TPM based disk encryption.",
"x-intellij-html-description": "\u003cp\u003eEnable TPM based disk encryption.\u003c/p\u003e\n"
},
"lockToState": {
"type": "boolean",
"title": "lockToState",
"description": "Lock the disk encryption key to the random salt stored in the STATE partition. This is useful to prevent the volume from being unlocked if STATE partition is compromised or replaced. It is recommended to use this option with TPM disk encryption for non-STATE volumes.\n",
"markdownDescription": "Lock the disk encryption key to the random salt stored in the STATE partition. This is useful to prevent the volume from being unlocked if STATE partition is compromised or replaced. It is recommended to use this option with TPM disk encryption for non-STATE volumes.",
"x-intellij-html-description": "\u003cp\u003eLock the disk encryption key to the random salt stored in the STATE partition. This is useful to prevent the volume from being unlocked if STATE partition is compromised or replaced. It is recommended to use this option with TPM disk encryption for non-STATE volumes.\u003c/p\u003e\n"
}
},
"additionalProperties": false,

View File

@ -148,6 +148,13 @@ func (EncryptionKey) Doc() *encoder.Doc {
Description: "Enable TPM based disk encryption.",
Comments: [3]string{"" /* encoder.HeadComment */, "Enable TPM based disk encryption." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{
Name: "lockToState",
Type: "bool",
Note: "",
Description: "Lock the disk encryption key to the random salt stored in the STATE partition. This is useful to prevent the volume from being unlocked if STATE partition is compromised or replaced. It is recommended to use this option with TPM disk encryption for non-STATE volumes.",
Comments: [3]string{"" /* encoder.HeadComment */, "Lock the disk encryption key to the random salt stored in the STATE partition. This is useful to prevent the volume from being unlocked if STATE partition is compromised or replaced. It is recommended to use this option with TPM disk encryption for non-STATE volumes." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
},
}

View File

@ -63,6 +63,10 @@ func (o *RawVolumeConfigV1Alpha1) DeepCopy() *RawVolumeConfigV1Alpha1 {
*cp.EncryptionSpec.EncryptionKeys[i3].KeyTPM.TPMCheckSecurebootStatusOnEnroll = *o.EncryptionSpec.EncryptionKeys[i3].KeyTPM.TPMCheckSecurebootStatusOnEnroll
}
}
if o.EncryptionSpec.EncryptionKeys[i3].KeyLockToSTATE != nil {
cp.EncryptionSpec.EncryptionKeys[i3].KeyLockToSTATE = new(bool)
*cp.EncryptionSpec.EncryptionKeys[i3].KeyLockToSTATE = *o.EncryptionSpec.EncryptionKeys[i3].KeyLockToSTATE
}
}
}
if o.EncryptionSpec.EncryptionPerfOptions != nil {
@ -119,6 +123,10 @@ func (o *SwapVolumeConfigV1Alpha1) DeepCopy() *SwapVolumeConfigV1Alpha1 {
*cp.EncryptionSpec.EncryptionKeys[i3].KeyTPM.TPMCheckSecurebootStatusOnEnroll = *o.EncryptionSpec.EncryptionKeys[i3].KeyTPM.TPMCheckSecurebootStatusOnEnroll
}
}
if o.EncryptionSpec.EncryptionKeys[i3].KeyLockToSTATE != nil {
cp.EncryptionSpec.EncryptionKeys[i3].KeyLockToSTATE = new(bool)
*cp.EncryptionSpec.EncryptionKeys[i3].KeyLockToSTATE = *o.EncryptionSpec.EncryptionKeys[i3].KeyLockToSTATE
}
}
}
if o.EncryptionSpec.EncryptionPerfOptions != nil {
@ -179,6 +187,10 @@ func (o *UserVolumeConfigV1Alpha1) DeepCopy() *UserVolumeConfigV1Alpha1 {
*cp.EncryptionSpec.EncryptionKeys[i3].KeyTPM.TPMCheckSecurebootStatusOnEnroll = *o.EncryptionSpec.EncryptionKeys[i3].KeyTPM.TPMCheckSecurebootStatusOnEnroll
}
}
if o.EncryptionSpec.EncryptionKeys[i3].KeyLockToSTATE != nil {
cp.EncryptionSpec.EncryptionKeys[i3].KeyLockToSTATE = new(bool)
*cp.EncryptionSpec.EncryptionKeys[i3].KeyLockToSTATE = *o.EncryptionSpec.EncryptionKeys[i3].KeyLockToSTATE
}
}
}
if o.EncryptionSpec.EncryptionPerfOptions != nil {
@ -235,6 +247,10 @@ func (o *VolumeConfigV1Alpha1) DeepCopy() *VolumeConfigV1Alpha1 {
*cp.EncryptionSpec.EncryptionKeys[i3].KeyTPM.TPMCheckSecurebootStatusOnEnroll = *o.EncryptionSpec.EncryptionKeys[i3].KeyTPM.TPMCheckSecurebootStatusOnEnroll
}
}
if o.EncryptionSpec.EncryptionKeys[i3].KeyLockToSTATE != nil {
cp.EncryptionSpec.EncryptionKeys[i3].KeyLockToSTATE = new(bool)
*cp.EncryptionSpec.EncryptionKeys[i3].KeyLockToSTATE = *o.EncryptionSpec.EncryptionKeys[i3].KeyLockToSTATE
}
}
}
if o.EncryptionSpec.EncryptionPerfOptions != nil {

View File

@ -99,6 +99,12 @@ type EncryptionKey struct {
// description: >
// Enable TPM based disk encryption.
KeyTPM *EncryptionKeyTPM `yaml:"tpm,omitempty"`
// description: >
// Lock the disk encryption key to the random salt stored in the STATE partition.
// This is useful to prevent the volume from being unlocked if STATE partition is compromised
// or replaced. It is recommended to use this option with TPM disk encryption for
// non-STATE volumes.
KeyLockToSTATE *bool `yaml:"lockToState,omitempty"`
}
// EncryptionKeyStatic represents throw away key type.
@ -213,6 +219,11 @@ func (k EncryptionKey) Slot() int {
return k.KeySlot
}
// LockToSTATE implements the config.Provider interface.
func (k EncryptionKey) LockToSTATE() bool {
return pointer.SafeDeref(k.KeyLockToSTATE)
}
// Static implements the config.Provider interface.
func (k EncryptionKey) Static() config.EncryptionKeyStatic {
if k.KeyStatic == nil {

View File

@ -12,6 +12,7 @@ import (
"slices"
"github.com/siderolabs/gen/optional"
"github.com/siderolabs/go-pointer"
"github.com/siderolabs/talos/pkg/machinery/cel"
"github.com/siderolabs/talos/pkg/machinery/cel/celenv"
@ -177,6 +178,13 @@ func (s *VolumeConfigV1Alpha1) Validate(validation.RuntimeMode, ...validation.Op
if !s.ProvisioningSpec.IsZero() {
validationErrors = errors.Join(validationErrors, fmt.Errorf("provisioning config is not allowed for the %q volume", s.MetaName))
}
for _, key := range s.EncryptionSpec.EncryptionKeys {
if pointer.SafeDeref(key.KeyLockToSTATE) {
// state-locked keys are not allowed
validationErrors = errors.Join(validationErrors, fmt.Errorf("state-locked key is not allowed for the %q volume", s.MetaName))
}
}
}
extraWarnings, extraErrors := s.ProvisioningSpec.Validate(false)

View File

@ -9,6 +9,7 @@ import (
"path/filepath"
"testing"
"github.com/siderolabs/go-pointer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -185,6 +186,37 @@ func TestVolumeConfigValidate(t *testing.T) {
expectedErrors: "provisioning config is not allowed for the \"STATE\" volume",
},
{
name: "state encryption lock to state",
cfg: func(t *testing.T) *block.VolumeConfigV1Alpha1 {
c := block.NewVolumeConfigV1Alpha1()
c.MetaName = constants.StatePartitionLabel
c.EncryptionSpec = block.EncryptionSpec{
EncryptionProvider: blockres.EncryptionProviderLUKS2,
EncryptionKeys: []block.EncryptionKey{
{
KeySlot: 0,
KeyStatic: &block.EncryptionKeyStatic{
KeyData: "topsecret",
},
},
{
KeySlot: 1,
KeyStatic: &block.EncryptionKeyStatic{
KeyData: "topsecret2",
},
KeyLockToSTATE: pointer.To(true),
},
},
}
return c
},
expectedErrors: "state-locked key is not allowed for the \"STATE\" volume",
},
{
name: "valid",

View File

@ -1604,6 +1604,12 @@ func (e *EncryptionKey) TPM() config.EncryptionKeyTPM {
return e.KeyTPM
}
// LockToSTATE implements the config.Provider interface.
func (e *EncryptionKey) LockToSTATE() bool {
// not supported in v1alpha1
return false
}
// String implements the config.Provider interface.
func (e *EncryptionKeyNodeID) String() string {
return "nodeid"

View File

@ -1270,6 +1270,12 @@ const (
// ExtensionSPDXPath is the path to the SBOM file(s) provided by system extensions.
ExtensionSPDXPath = "/usr/local/share/spdx"
// EncryptionSaltFilename is the filename for the encryption salt file.
EncryptionSaltFilename = "encryption-salt.yaml"
// DiskEncryptionSaltSize is the size of the disk encryption salt in bytes.
DiskEncryptionSaltSize = 32
)
// See https://linux.die.net/man/3/klogctl

View File

@ -135,8 +135,9 @@ type EncryptionSpec struct {
//
//gotagsrewrite:gen
type EncryptionKey struct {
Slot int `yaml:"slot" protobuf:"1"`
Type EncryptionKeyType `yaml:"type" protobuf:"2"`
Slot int `yaml:"slot" protobuf:"1"`
Type EncryptionKeyType `yaml:"type" protobuf:"2"`
LockToSTATE bool `yaml:"lockToState,omitempty" protobuf:"6"`
// Only for Type == "static":
StaticPassphrase yamlutils.StringBytes `yaml:"staticPassphrase,omitempty" protobuf:"3"`

View File

@ -2,7 +2,7 @@
// 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/.
// Code generated by "deep-copy -type APICertsSpec -type CertSANSpec -type EtcdCertsSpec -type EtcdRootSpec -type KubeletSpec -type KubernetesCertsSpec -type KubernetesDynamicCertsSpec -type KubernetesRootSpec -type MaintenanceServiceCertsSpec -type MaintenanceRootSpec -type OSRootSpec -type TrustdCertsSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT.
// Code generated by "deep-copy -type APICertsSpec -type CertSANSpec -type EtcdCertsSpec -type EtcdRootSpec -type EncryptionSaltSpec -type KubeletSpec -type KubernetesCertsSpec -type KubernetesDynamicCertsSpec -type KubernetesRootSpec -type MaintenanceServiceCertsSpec -type MaintenanceRootSpec -type OSRootSpec -type TrustdCertsSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT.
package secrets
@ -75,6 +75,16 @@ func (o EtcdRootSpec) DeepCopy() EtcdRootSpec {
return cp
}
// DeepCopy generates a deep copy of EncryptionSaltSpec.
func (o EncryptionSaltSpec) DeepCopy() EncryptionSaltSpec {
var cp EncryptionSaltSpec = o
if o.DiskSalt != nil {
cp.DiskSalt = make([]byte, len(o.DiskSalt))
copy(cp.DiskSalt, o.DiskSalt)
}
return cp
}
// DeepCopy generates a deep copy of KubeletSpec.
func (o KubeletSpec) DeepCopy() KubeletSpec {
var cp KubeletSpec = o

View File

@ -0,0 +1,59 @@
// 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 secrets
import (
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/resource/meta"
"github.com/cosi-project/runtime/pkg/resource/protobuf"
"github.com/cosi-project/runtime/pkg/resource/typed"
"github.com/siderolabs/talos/pkg/machinery/proto"
)
// EncryptionSaltType is type of EncryptionSalt resource.
const EncryptionSaltType = resource.Type("EncryptionSalts.secrets.talos.dev")
// EncryptionSaltID is a resource ID of singleton instance.
const EncryptionSaltID = resource.ID("salt")
// EncryptionSalt contains salt data used to mix in into disk encryption keys.
type EncryptionSalt = typed.Resource[EncryptionSaltSpec, EncryptionSaltExtension]
// EncryptionSaltSpec describes the salt.
//
//gotagsrewrite:gen
type EncryptionSaltSpec struct {
DiskSalt []byte `yaml:"diskSalt" protobuf:"1"`
}
// NewEncryptionSalt initializes a EncryptionSalt resource.
func NewEncryptionSalt() *EncryptionSalt {
return typed.NewResource[EncryptionSaltSpec, EncryptionSaltExtension](
resource.NewMetadata(NamespaceName, EncryptionSaltType, EncryptionSaltID, resource.VersionUndefined),
EncryptionSaltSpec{},
)
}
// EncryptionSaltExtension provides auxiliary methods for EncryptionSalt.
type EncryptionSaltExtension struct{}
// ResourceDefinition implements meta.ResourceDefinitionProvider interface.
func (EncryptionSaltExtension) ResourceDefinition() meta.ResourceDefinitionSpec {
return meta.ResourceDefinitionSpec{
Type: EncryptionSaltType,
Aliases: []resource.Type{},
DefaultNamespace: NamespaceName,
Sensitivity: meta.Sensitive,
}
}
func init() {
proto.RegisterDefaultTypes()
if err := protobuf.RegisterDynamic[EncryptionSaltSpec](EncryptionSaltType, &EncryptionSalt{}); err != nil {
panic(err)
}
}

View File

@ -10,4 +10,4 @@ import "github.com/cosi-project/runtime/pkg/resource"
// NamespaceName contains resources containing secret material.
const NamespaceName resource.Namespace = "secrets"
//go:generate deep-copy -type APICertsSpec -type CertSANSpec -type EtcdCertsSpec -type EtcdRootSpec -type KubeletSpec -type KubernetesCertsSpec -type KubernetesDynamicCertsSpec -type KubernetesRootSpec -type MaintenanceServiceCertsSpec -type MaintenanceRootSpec -type OSRootSpec -type TrustdCertsSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go .
//go:generate deep-copy -type APICertsSpec -type CertSANSpec -type EtcdCertsSpec -type EtcdRootSpec -type EncryptionSaltSpec -type KubeletSpec -type KubernetesCertsSpec -type KubernetesDynamicCertsSpec -type KubernetesRootSpec -type MaintenanceServiceCertsSpec -type MaintenanceRootSpec -type OSRootSpec -type TrustdCertsSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go .

View File

@ -26,6 +26,7 @@ func TestRegisterResource(t *testing.T) {
for _, resource := range []meta.ResourceWithRD{
&secrets.API{},
&secrets.CertSAN{},
&secrets.EncryptionSalt{},
&secrets.Etcd{},
&secrets.EtcdRoot{},
&secrets.Kubelet{},

View File

@ -302,6 +302,7 @@ description: Talos gRPC API reference.
- [resource/definitions/secrets/secrets.proto](#resource/definitions/secrets/secrets.proto)
- [APICertsSpec](#talos.resource.definitions.secrets.APICertsSpec)
- [CertSANSpec](#talos.resource.definitions.secrets.CertSANSpec)
- [EncryptionSaltSpec](#talos.resource.definitions.secrets.EncryptionSaltSpec)
- [EtcdCertsSpec](#talos.resource.definitions.secrets.EtcdCertsSpec)
- [EtcdRootSpec](#talos.resource.definitions.secrets.EtcdRootSpec)
- [KubeletSpec](#talos.resource.definitions.secrets.KubeletSpec)
@ -989,6 +990,7 @@ EncryptionKey is the spec for volume encryption key.
| static_passphrase | [bytes](#bytes) | | |
| kms_endpoint | [string](#string) | | |
| tpm_check_secureboot_status_on_enroll | [bool](#bool) | | |
| lock_to_state | [bool](#bool) | | |
@ -5546,6 +5548,21 @@ CertSANSpec describes fields of the cert SANs.
<a name="talos.resource.definitions.secrets.EncryptionSaltSpec"></a>
### EncryptionSaltSpec
EncryptionSaltSpec describes the salt.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| disk_salt | [bytes](#bytes) | | |
<a name="talos.resource.definitions.secrets.EtcdCertsSpec"></a>
### EtcdCertsSpec

View File

@ -177,6 +177,7 @@ EncryptionKey represents configuration for disk encryption key.
|`nodeID` |<a href="#RawVolumeConfig.encryption.keys..nodeID">EncryptionKeyNodeID</a> |Deterministically generated key from the node UUID and PartitionLabel. | |
|`kms` |<a href="#RawVolumeConfig.encryption.keys..kms">EncryptionKeyKMS</a> |KMS managed encryption key. | |
|`tpm` |<a href="#RawVolumeConfig.encryption.keys..tpm">EncryptionKeyTPM</a> |Enable TPM based disk encryption. | |
|`lockToState` |bool |Lock the disk encryption key to the random salt stored in the STATE partition. This is useful to prevent the volume from being unlocked if STATE partition is compromised or replaced. It is recommended to use this option with TPM disk encryption for non-STATE volumes. | |

View File

@ -174,6 +174,7 @@ EncryptionKey represents configuration for disk encryption key.
|`nodeID` |<a href="#SwapVolumeConfig.encryption.keys..nodeID">EncryptionKeyNodeID</a> |Deterministically generated key from the node UUID and PartitionLabel. | |
|`kms` |<a href="#SwapVolumeConfig.encryption.keys..kms">EncryptionKeyKMS</a> |KMS managed encryption key. | |
|`tpm` |<a href="#SwapVolumeConfig.encryption.keys..tpm">EncryptionKeyTPM</a> |Enable TPM based disk encryption. | |
|`lockToState` |bool |Lock the disk encryption key to the random salt stored in the STATE partition. This is useful to prevent the volume from being unlocked if STATE partition is compromised or replaced. It is recommended to use this option with TPM disk encryption for non-STATE volumes. | |

View File

@ -204,6 +204,7 @@ EncryptionKey represents configuration for disk encryption key.
|`nodeID` |<a href="#UserVolumeConfig.encryption.keys..nodeID">EncryptionKeyNodeID</a> |Deterministically generated key from the node UUID and PartitionLabel. | |
|`kms` |<a href="#UserVolumeConfig.encryption.keys..kms">EncryptionKeyKMS</a> |KMS managed encryption key. | |
|`tpm` |<a href="#UserVolumeConfig.encryption.keys..tpm">EncryptionKeyTPM</a> |Enable TPM based disk encryption. | |
|`lockToState` |bool |Lock the disk encryption key to the random salt stored in the STATE partition. This is useful to prevent the volume from being unlocked if STATE partition is compromised or replaced. It is recommended to use this option with TPM disk encryption for non-STATE volumes. | |

View File

@ -174,6 +174,7 @@ EncryptionKey represents configuration for disk encryption key.
|`nodeID` |<a href="#VolumeConfig.encryption.keys..nodeID">EncryptionKeyNodeID</a> |Deterministically generated key from the node UUID and PartitionLabel. | |
|`kms` |<a href="#VolumeConfig.encryption.keys..kms">EncryptionKeyKMS</a> |KMS managed encryption key. | |
|`tpm` |<a href="#VolumeConfig.encryption.keys..tpm">EncryptionKeyTPM</a> |Enable TPM based disk encryption. | |
|`lockToState` |bool |Lock the disk encryption key to the random salt stored in the STATE partition. This is useful to prevent the volume from being unlocked if STATE partition is compromised or replaced. It is recommended to use this option with TPM disk encryption for non-STATE volumes. | |

View File

@ -52,6 +52,13 @@
"description": "Enable TPM based disk encryption.\n",
"markdownDescription": "Enable TPM based disk encryption.",
"x-intellij-html-description": "\u003cp\u003eEnable TPM based disk encryption.\u003c/p\u003e\n"
},
"lockToState": {
"type": "boolean",
"title": "lockToState",
"description": "Lock the disk encryption key to the random salt stored in the STATE partition. This is useful to prevent the volume from being unlocked if STATE partition is compromised or replaced. It is recommended to use this option with TPM disk encryption for non-STATE volumes.\n",
"markdownDescription": "Lock the disk encryption key to the random salt stored in the STATE partition. This is useful to prevent the volume from being unlocked if STATE partition is compromised or replaced. It is recommended to use this option with TPM disk encryption for non-STATE volumes.",
"x-intellij-html-description": "\u003cp\u003eLock the disk encryption key to the random salt stored in the STATE partition. This is useful to prevent the volume from being unlocked if STATE partition is compromised or replaced. It is recommended to use this option with TPM disk encryption for non-STATE volumes.\u003c/p\u003e\n"
}
},
"additionalProperties": false,

View File

@ -112,6 +112,7 @@ encryption:
- static:
passphrase: supersecret
slot: 0
lockToSTATE: true
```
Take a note that key order does not play any role on which key slot is used.
@ -126,8 +127,17 @@ Talos supports two kinds of keys:
- `kms` which is sealed with the network KMS.
- `tpm` which is sealed using the TPM and protected with SecureBoot.
> Note: Use static keys only if your STATE partition is encrypted and only for the EPHEMERAL partition.
> For the STATE partition it will be stored in the META partition, which is not encrypted.
> Note: The `STATE` volume encryption configuration will be stored cleartext in `META` volume, so
> it is not secure to use `static` keys for `STATE` volume.
> Other volumes can use `static` keys as long as `STATE` partition itself is encrypted.
Every key kind also supports `lockToSTATE` option, which means that the key will be locked to the contents of the `STATE` partition:
- if the `STATE` partition is wiped/replaced with new contents, locked to `STATE` volumes will not be unlockable anymore.
- Talos Linux generates a random salt, and stores in the `STATE` partition, which will be mixed into the key derivation function.
It is recommended to use `lockToSTATE` for the `EPHEMERAL` partition and user volumes, so that the data on these partitions is not accessible if the `STATE` partition is wiped or replaced.
If you would like non-`STATE` volumes to survive `STATE` partition wipe, do not enable `lockToSTATE` option.
### Key Rotation