From ae94377d15a3b70248fbb446d13d7ae96bb04e82 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Thu, 17 Apr 2025 19:51:49 +0400 Subject: [PATCH] feat: support encryption config for user volumes No big changes, mostly wiring things together: * implement encryption config (identical to existing v1alpha1 one) for user volume configuration * provide validation, some small fixes * add support for encrypted user volumes in `talosctl cluster create` Signed-off-by: Andrey Smirnov --- cmd/talosctl/cmd/mgmt/cluster/create.go | 178 +++++++---- hack/test/e2e-qemu.sh | 4 +- .../controllers/block/user_volume_config.go | 4 + .../block/user_volume_config_test.go | 144 +++++++++ .../pkg/controllers/block/volume_config.go | 17 +- pkg/machinery/config/config/machine.go | 9 +- pkg/machinery/config/config/volume.go | 1 + .../config/schemas/config.schema.json | 159 ++++++++++ pkg/machinery/config/types/block/block.go | 2 +- pkg/machinery/config/types/block/block_doc.go | 239 +++++++++++++++ .../config/types/block/deep_copy.generated.go | 30 ++ .../config/types/block/encryption.go | 289 ++++++++++++++++++ .../testdata/uservolumeconfig_encrypted.yaml | 16 + .../config/types/block/user_volume_config.go | 31 ++ .../types/block/user_volume_config_test.go | 114 +++++++ .../types/v1alpha1/v1alpha1_provider.go | 13 +- pkg/machinery/go.mod | 1 - pkg/machinery/go.sum | 2 - website/content/v1.10/reference/cli.md | 1 + .../configuration/block/uservolumeconfig.md | 174 +++++++++++ .../content/v1.10/schemas/config.schema.json | 159 ++++++++++ .../configuration/disk-management.md | 4 + 22 files changed, 1505 insertions(+), 86 deletions(-) create mode 100644 internal/app/machined/pkg/controllers/block/user_volume_config_test.go create mode 100644 pkg/machinery/config/types/block/encryption.go create mode 100644 pkg/machinery/config/types/block/testdata/uservolumeconfig_encrypted.yaml diff --git a/cmd/talosctl/cmd/mgmt/cluster/create.go b/cmd/talosctl/cmd/mgmt/cluster/create.go index 0fcf4d9a6..033dfd758 100644 --- a/cmd/talosctl/cmd/mgmt/cluster/create.go +++ b/cmd/talosctl/cmd/mgmt/cluster/create.go @@ -61,6 +61,7 @@ import ( "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/siderolabs/talos/pkg/machinery/imager/quirks" "github.com/siderolabs/talos/pkg/machinery/nethelpers" + blockres "github.com/siderolabs/talos/pkg/machinery/resources/block" "github.com/siderolabs/talos/pkg/machinery/version" "github.com/siderolabs/talos/pkg/provision" "github.com/siderolabs/talos/pkg/provision/access" @@ -110,6 +111,7 @@ const ( talosVersionFlag = "talos-version" encryptStatePartitionFlag = "encrypt-state" encryptEphemeralPartitionFlag = "encrypt-ephemeral" + encryptUserVolumeFlag = "encrypt-user-volumes" enableKubeSpanFlag = "with-kubespan" forceEndpointFlag = "endpoint" kubePrismFlag = "kubeprism-port" @@ -176,6 +178,7 @@ var ( talosVersion string encryptStatePartition bool encryptEphemeralPartition bool + encryptUserVolumes bool useVIP bool enableKubeSpan bool enableClusterDiscovery bool @@ -314,6 +317,58 @@ func downloadBootAssets(ctx context.Context) error { return nil } +func getEncryptionKeys(cidr4 netip.Prefix, versionContract *config.VersionContract, provisionOptions *[]provision.Option) ([]*v1alpha1.EncryptionKey, error) { + var keys []*v1alpha1.EncryptionKey + + for i, key := range diskEncryptionKeyTypes { + switch key { + case "uuid": + keys = append(keys, &v1alpha1.EncryptionKey{ + KeyNodeID: &v1alpha1.EncryptionKeyNodeID{}, + KeySlot: i, + }) + case "kms": + var ip netip.Addr + + // get bridge IP + ip, err := sideronet.NthIPInNetwork(cidr4, 1) + if err != nil { + return nil, err + } + + const port = 4050 + + keys = append(keys, &v1alpha1.EncryptionKey{ + KeyKMS: &v1alpha1.EncryptionKeyKMS{ + KMSEndpoint: "grpc://" + nethelpers.JoinHostPort(ip.String(), port), + }, + KeySlot: i, + }) + + *provisionOptions = append(*provisionOptions, provision.WithKMS(nethelpers.JoinHostPort("0.0.0.0", port))) + case "tpm": + keyTPM := &v1alpha1.EncryptionKeyTPM{} + + if versionContract.SecureBootEnrollEnforcementSupported() { + keyTPM.TPMCheckSecurebootStatusOnEnroll = pointer.To(true) + } + + keys = append(keys, &v1alpha1.EncryptionKey{ + KeyTPM: keyTPM, + KeySlot: i, + }) + default: + return nil, fmt.Errorf("unknown key type %q", key) + } + } + + if len(keys) == 0 { + return nil, errors.New("no disk encryption key types enabled") + } + + return keys, nil +} + //nolint:gocyclo,cyclop func create(ctx context.Context) error { if err := downloadBootAssets(ctx); err != nil { @@ -517,9 +572,14 @@ func create(ctx context.Context) error { provisionOptions = append(provisionOptions, provision.WithDockerPorts(portList)) } - disks, userVolumePatches, err := getDisks(provisioner) - if err != nil { - return err + // should have at least a single primary disk + disks := []*provision.Disk{ + { + Size: uint64(clusterDiskSize) * 1024 * 1024, + SkipPreallocate: !clusterDiskPreallocate, + Driver: "virtio", + BlockSize: diskBlockSize, + }, } if inputDir != "" { @@ -577,55 +637,19 @@ func create(ctx context.Context) error { genOptions = append(genOptions, generate.WithVersionContract(versionContract)) } + extraDisks, userVolumePatches, err := getDisks(provisioner, cidr4, versionContract, &provisionOptions) + if err != nil { + return err + } + + disks = slices.Concat(disks, extraDisks) + if encryptStatePartition || encryptEphemeralPartition { diskEncryptionConfig := &v1alpha1.SystemDiskEncryptionConfig{} - var keys []*v1alpha1.EncryptionKey - - for i, key := range diskEncryptionKeyTypes { - switch key { - case "uuid": - keys = append(keys, &v1alpha1.EncryptionKey{ - KeyNodeID: &v1alpha1.EncryptionKeyNodeID{}, - KeySlot: i, - }) - case "kms": - var ip netip.Addr - - // get bridge IP - ip, err = sideronet.NthIPInNetwork(cidr4, 1) - if err != nil { - return err - } - - const port = 4050 - - keys = append(keys, &v1alpha1.EncryptionKey{ - KeyKMS: &v1alpha1.EncryptionKeyKMS{ - KMSEndpoint: "grpc://" + nethelpers.JoinHostPort(ip.String(), port), - }, - KeySlot: i, - }) - - provisionOptions = append(provisionOptions, provision.WithKMS(nethelpers.JoinHostPort("0.0.0.0", port))) - case "tpm": - keyTPM := &v1alpha1.EncryptionKeyTPM{} - - if versionContract.SecureBootEnrollEnforcementSupported() { - keyTPM.TPMCheckSecurebootStatusOnEnroll = pointer.To(true) - } - - keys = append(keys, &v1alpha1.EncryptionKey{ - KeyTPM: keyTPM, - KeySlot: i, - }) - default: - return fmt.Errorf("unknown key type %q", key) - } - } - - if len(keys) == 0 { - return errors.New("no disk encryption key types enabled") + keys, err := getEncryptionKeys(cidr4, versionContract, &provisionOptions) + if err != nil { + return err } if encryptStatePartition { @@ -1165,20 +1189,53 @@ func parseCPUShare(cpus string) (int64, error) { return nano.Num().Int64(), nil } -func getDisks(provisioner provision.Provisioner) ([]*provision.Disk, []configpatcher.Patch, error) { +//nolint:gocyclo +func getDisks(provisioner provision.Provisioner, cidr4 netip.Prefix, versionContract *config.VersionContract, provisionOptions *[]provision.Option) ([]*provision.Disk, []configpatcher.Patch, error) { const GPTAlignment = 2 * 1024 * 1024 // 2 MB - // should have at least a single primary disk - disks := []*provision.Disk{ - { - Size: uint64(clusterDiskSize) * 1024 * 1024, - SkipPreallocate: !clusterDiskPreallocate, - Driver: "virtio", - BlockSize: diskBlockSize, - }, + var ( + userVolumes []*block.UserVolumeConfigV1Alpha1 + encryptionSpec block.EncryptionSpec + ) + + if encryptUserVolumes { + encryptionSpec.EncryptionProvider = blockres.EncryptionProviderLUKS2 + + keys, err := getEncryptionKeys( + cidr4, + versionContract, + provisionOptions, + ) + if err != nil { + return nil, nil, err + } + + encryptionSpec.EncryptionKeys = xslices.Map(keys, func(k *v1alpha1.EncryptionKey) block.EncryptionKey { + r := block.EncryptionKey{ + KeySlot: k.KeySlot, + } + + if k.KeyKMS != nil { + r.KeyKMS = pointer.To(block.EncryptionKeyKMS(*k.KeyKMS)) + } + + if k.KeyTPM != nil { + r.KeyTPM = pointer.To(block.EncryptionKeyTPM(*k.KeyTPM)) + } + + if k.KeyNodeID != nil { + r.KeyNodeID = pointer.To(block.EncryptionKeyNodeID(*k.KeyNodeID)) + } + + if k.KeyStatic != nil { + r.KeyStatic = pointer.To(block.EncryptionKeyStatic(*k.KeyStatic)) + } + + return r + }) } - var userVolumes []*block.UserVolumeConfigV1Alpha1 + disks := make([]*provision.Disk, 0, len(clusterUserVolumes)) for diskID, disk := range clusterUserVolumes { var ( @@ -1203,6 +1260,7 @@ func getDisks(provisioner provision.Provisioner) ([]*provision.Disk, []configpat ProvisioningMinSize: block.MustByteSize(volumeSize), ProvisioningMaxSize: block.MustByteSize(volumeSize), } + userVolume.EncryptionSpec = encryptionSpec userVolumes = append(userVolumes, userVolume) diskSize += userVolume.ProvisioningSpec.ProvisioningMaxSize.Value() @@ -1308,6 +1366,7 @@ func init() { createCmd.Flags().BoolVar(&skipInjectingConfig, "skip-injecting-config", false, "skip injecting config from embedded metadata server, write config files to current directory") createCmd.Flags().BoolVar(&encryptStatePartition, encryptStatePartitionFlag, false, "enable state partition encryption") createCmd.Flags().BoolVar(&encryptEphemeralPartition, encryptEphemeralPartitionFlag, false, "enable ephemeral partition encryption") + createCmd.Flags().BoolVar(&encryptUserVolumes, encryptUserVolumeFlag, false, "enable ephemeral partition encryption") createCmd.Flags().StringArrayVar(&diskEncryptionKeyTypes, diskEncryptionKeyTypesFlag, []string{"uuid"}, "encryption key types to use for disk encryption (uuid, kms)") createCmd.Flags().StringVar(&talosVersion, talosVersionFlag, "", "the desired Talos version to generate config for (if not set, defaults to image version)") createCmd.Flags().BoolVar(&useVIP, useVIPFlag, false, "use a virtual IP for the controlplane endpoint instead of the loadbalancer") @@ -1349,6 +1408,7 @@ func init() { createCmd.MarkFlagsMutuallyExclusive(inputDirFlag, talosVersionFlag) createCmd.MarkFlagsMutuallyExclusive(inputDirFlag, encryptStatePartitionFlag) createCmd.MarkFlagsMutuallyExclusive(inputDirFlag, encryptEphemeralPartitionFlag) + createCmd.MarkFlagsMutuallyExclusive(inputDirFlag, encryptUserVolumeFlag) createCmd.MarkFlagsMutuallyExclusive(inputDirFlag, enableKubeSpanFlag) createCmd.MarkFlagsMutuallyExclusive(inputDirFlag, forceEndpointFlag) createCmd.MarkFlagsMutuallyExclusive(inputDirFlag, kubePrismFlag) diff --git a/hack/test/e2e-qemu.sh b/hack/test/e2e-qemu.sh index 1577f8808..345469254 100755 --- a/hack/test/e2e-qemu.sh +++ b/hack/test/e2e-qemu.sh @@ -115,7 +115,7 @@ case "${WITH_DISK_ENCRYPTION:-false}" in false) ;; *) - QEMU_FLAGS+=("--encrypt-ephemeral" "--encrypt-state" "--disk-encryption-key-types=kms") + QEMU_FLAGS+=("--encrypt-ephemeral" "--encrypt-state" "--encrypt-user-volumes" "--disk-encryption-key-types=kms") ;; esac @@ -184,7 +184,7 @@ case "${WITH_TRUSTED_BOOT_ISO:-false}" in ;; *) INSTALLER_IMAGE=${INSTALLER_IMAGE}-amd64-secureboot - QEMU_FLAGS+=("--iso-path=_out/metal-amd64-secureboot.iso" "--with-tpm2" "--encrypt-ephemeral" "--encrypt-state" "--disk-encryption-key-types=tpm") + QEMU_FLAGS+=("--iso-path=_out/metal-amd64-secureboot.iso" "--with-tpm2" "--encrypt-ephemeral" "--encrypt-state" "--encrypt-user-volumes" "--disk-encryption-key-types=tpm") ;; esac diff --git a/internal/app/machined/pkg/controllers/block/user_volume_config.go b/internal/app/machined/pkg/controllers/block/user_volume_config.go index f9ca9afde..3785aa146 100644 --- a/internal/app/machined/pkg/controllers/block/user_volume_config.go +++ b/internal/app/machined/pkg/controllers/block/user_volume_config.go @@ -168,6 +168,10 @@ func (ctrl *UserVolumeConfigController) Run(ctx context.Context, r controller.Ru GID: 0, } + if err := convertEncryptionConfiguration(userVolumeConfig.Encryption(), v.TypedSpec()); err != nil { + return fmt.Errorf("error apply encryption configuration: %w", err) + } + return nil }, ); err != nil { diff --git a/internal/app/machined/pkg/controllers/block/user_volume_config_test.go b/internal/app/machined/pkg/controllers/block/user_volume_config_test.go new file mode 100644 index 000000000..dbfbb8ca5 --- /dev/null +++ b/internal/app/machined/pkg/controllers/block/user_volume_config_test.go @@ -0,0 +1,144 @@ +// 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 block_test + +import ( + "testing" + "time" + + "github.com/cosi-project/runtime/pkg/resource" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + + blockctrls "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/block" + "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/ctest" + "github.com/siderolabs/talos/pkg/machinery/config/container" + blockcfg "github.com/siderolabs/talos/pkg/machinery/config/types/block" + "github.com/siderolabs/talos/pkg/machinery/constants" + "github.com/siderolabs/talos/pkg/machinery/resources/block" + "github.com/siderolabs/talos/pkg/machinery/resources/config" +) + +type UserVolumeConfigSuite struct { + ctest.DefaultSuite +} + +func TestUserVolumeConfigSuite(t *testing.T) { + t.Parallel() + + suite.Run(t, &UserVolumeConfigSuite{ + DefaultSuite: ctest.DefaultSuite{ + Timeout: 3 * time.Second, + AfterSetup: func(suite *ctest.DefaultSuite) { + suite.Require().NoError(suite.Runtime().RegisterController(&blockctrls.UserVolumeConfigController{})) + }, + }, + }) +} + +func (suite *UserVolumeConfigSuite) TestReconcile() { + uv1 := blockcfg.NewUserVolumeConfigV1Alpha1() + uv1.MetaName = "data1" + suite.Require().NoError(uv1.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`system_disk`))) + uv1.ProvisioningSpec.ProvisioningMinSize = blockcfg.MustByteSize("10GiB") + uv1.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustByteSize("100GiB") + uv1.FilesystemSpec.FilesystemType = block.FilesystemTypeXFS + + uv2 := blockcfg.NewUserVolumeConfigV1Alpha1() + uv2.MetaName = "data2" + suite.Require().NoError(uv2.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`!system_disk`))) + uv2.ProvisioningSpec.ProvisioningMaxSize = blockcfg.MustByteSize("1TiB") + uv2.EncryptionSpec = blockcfg.EncryptionSpec{ + EncryptionProvider: block.EncryptionProviderLUKS2, + EncryptionKeys: []blockcfg.EncryptionKey{ + { + KeySlot: 0, + KeyTPM: &blockcfg.EncryptionKeyTPM{}, + }, + { + KeySlot: 1, + KeyStatic: &blockcfg.EncryptionKeyStatic{KeyData: "secret"}, + }, + }, + } + + ctr, err := container.New(uv1, uv2) + suite.Require().NoError(err) + + cfg := config.NewMachineConfig(ctr) + suite.Create(cfg) + + userVolumes := []string{ + constants.UserVolumePrefix + "data1", + constants.UserVolumePrefix + "data2", + } + + ctest.AssertResources(suite, userVolumes, func(vc *block.VolumeConfig, asrt *assert.Assertions) { + asrt.Contains(vc.Metadata().Labels().Raw(), block.UserVolumeLabel) + + asrt.Equal(block.VolumeTypePartition, vc.TypedSpec().Type) + asrt.Contains(userVolumes, vc.TypedSpec().Provisioning.PartitionSpec.Label) + + locator, err := vc.TypedSpec().Locator.Match.MarshalText() + asrt.NoError(err) + + asrt.Contains(string(locator), vc.TypedSpec().Provisioning.PartitionSpec.Label) + + asrt.Contains([]string{"data1", "data2"}, vc.TypedSpec().Mount.TargetPath) + asrt.Equal(constants.UserVolumeMountPoint, vc.TypedSpec().Mount.ParentID) + }) + + ctest.AssertResources(suite, userVolumes, func(vmr *block.VolumeMountRequest, asrt *assert.Assertions) { + asrt.Contains(vmr.Metadata().Labels().Raw(), block.UserVolumeLabel) + }) + + // simulate other controllers working - add finalizers for volume config & mount request + for _, volumeID := range userVolumes { + suite.AddFinalizer(block.NewVolumeConfig(block.NamespaceName, volumeID).Metadata(), "test") + suite.AddFinalizer(block.NewVolumeMountRequest(block.NamespaceName, volumeID).Metadata(), "test") + } + + // drop the first volume + ctr, err = container.New(uv2) + suite.Require().NoError(err) + + newCfg := config.NewMachineConfig(ctr) + newCfg.Metadata().SetVersion(cfg.Metadata().Version()) + suite.Update(newCfg) + + // controller should tear down removed volumes + ctest.AssertResources(suite, userVolumes, func(vc *block.VolumeConfig, asrt *assert.Assertions) { + if vc.Metadata().ID() == userVolumes[0] { + asrt.Equal(resource.PhaseTearingDown, vc.Metadata().Phase()) + } else { + asrt.Equal(resource.PhaseRunning, vc.Metadata().Phase()) + } + }) + + // controller should tear down removed volume resources + ctest.AssertResources(suite, userVolumes, func(vc *block.VolumeConfig, asrt *assert.Assertions) { + if vc.Metadata().ID() == userVolumes[0] { + asrt.Equal(resource.PhaseTearingDown, vc.Metadata().Phase()) + } else { + asrt.Equal(resource.PhaseRunning, vc.Metadata().Phase()) + } + }) + + ctest.AssertResources(suite, userVolumes, func(vmr *block.VolumeMountRequest, asrt *assert.Assertions) { + if vmr.Metadata().ID() == userVolumes[0] { + asrt.Equal(resource.PhaseTearingDown, vmr.Metadata().Phase()) + } else { + asrt.Equal(resource.PhaseRunning, vmr.Metadata().Phase()) + } + }) + + // remove finalizers + suite.RemoveFinalizer(block.NewVolumeConfig(block.NamespaceName, userVolumes[0]).Metadata(), "test") + suite.RemoveFinalizer(block.NewVolumeMountRequest(block.NamespaceName, userVolumes[0]).Metadata(), "test") + + // now the resources should be removed + ctest.AssertNoResource[*block.VolumeConfig](suite, userVolumes[0]) + ctest.AssertNoResource[*block.VolumeMountRequest](suite, userVolumes[0]) +} diff --git a/internal/app/machined/pkg/controllers/block/volume_config.go b/internal/app/machined/pkg/controllers/block/volume_config.go index 590b9dfa7..6f96c1c86 100644 --- a/internal/app/machined/pkg/controllers/block/volume_config.go +++ b/internal/app/machined/pkg/controllers/block/volume_config.go @@ -15,7 +15,6 @@ import ( "github.com/cosi-project/runtime/pkg/safe" "github.com/cosi-project/runtime/pkg/state" "github.com/siderolabs/gen/optional" - "github.com/siderolabs/go-blockdevice/v2/encryption" "go.uber.org/zap" machinedruntime "github.com/siderolabs/talos/internal/app/machined/pkg/runtime" @@ -87,20 +86,14 @@ func systemDiskMatch() cel.Expression { return cel.MustExpression(cel.ParseBooleanExpression("system_disk", celenv.DiskLocator())) } -func (ctrl *VolumeConfigController) convertEncryption(in cfg.Encryption, out *block.VolumeConfigSpec) error { +func convertEncryptionConfiguration(in cfg.EncryptionConfig, out *block.VolumeConfigSpec) error { if in == nil { out.Encryption = block.EncryptionSpec{} return nil } - switch in.Provider() { - case encryption.LUKS2: - out.Encryption.Provider = block.EncryptionProviderLUKS2 - default: - return fmt.Errorf("unsupported encryption provider: %s", in.Provider()) - } - + out.Encryption.Provider = in.Provider() out.Encryption.Cipher = in.Cipher() out.Encryption.KeySize = in.KeySize() out.Encryption.BlockSize = in.BlockSize() @@ -271,7 +264,7 @@ func (ctrl *VolumeConfigController) manageEphemeral(config cfg.Config) func(vc * Match: labelVolumeMatch(constants.EphemeralPartitionLabel), } - if err := ctrl.convertEncryption( + if err := convertEncryptionConfiguration( config.Machine().SystemDiskEncryption().Get(constants.EphemeralPartitionLabel), vc.TypedSpec(), ); err != nil { @@ -331,7 +324,7 @@ func (ctrl *VolumeConfigController) manageStateConfigPresent(config cfg.Config) Match: labelVolumeMatch(constants.StatePartitionLabel), } - if err := ctrl.convertEncryption( + if err := convertEncryptionConfiguration( config.Machine().SystemDiskEncryption().Get(constants.StatePartitionLabel), vc.TypedSpec(), ); err != nil { @@ -374,7 +367,7 @@ func (ctrl *VolumeConfigController) manageStateNoConfig(encryptionMeta *runtime. return fmt.Errorf("error unmarshalling state encryption meta key: %w", err) } - if err := ctrl.convertEncryption( + if err := convertEncryptionConfiguration( encryptionFromMeta, vc.TypedSpec(), ); err != nil { diff --git a/pkg/machinery/config/config/machine.go b/pkg/machinery/config/config/machine.go index bb7cd683b..c237dc1f7 100644 --- a/pkg/machinery/config/config/machine.go +++ b/pkg/machinery/config/config/machine.go @@ -16,6 +16,7 @@ import ( "github.com/siderolabs/talos/pkg/machinery/cel" "github.com/siderolabs/talos/pkg/machinery/config/machine" "github.com/siderolabs/talos/pkg/machinery/nethelpers" + "github.com/siderolabs/talos/pkg/machinery/resources/block" ) // MachineConfig defines the requirements for a config that pertains to machine @@ -431,9 +432,9 @@ type EncryptionKeyTPM interface { String() string } -// Encryption defines settings for the partition encryption. -type Encryption interface { - Provider() string +// EncryptionConfig defines settings for the partition encryption. +type EncryptionConfig interface { + Provider() block.EncryptionProviderType Cipher() string KeySize() uint BlockSize() uint64 @@ -443,7 +444,7 @@ type Encryption interface { // SystemDiskEncryption accumulates settings for all system partitions encryption. type SystemDiskEncryption interface { - Get(label string) Encryption + Get(label string) EncryptionConfig } // Features describe individual Talos features that can be switched on or off. diff --git a/pkg/machinery/config/config/volume.go b/pkg/machinery/config/config/volume.go index b8839e9b8..1e76d8ce3 100644 --- a/pkg/machinery/config/config/volume.go +++ b/pkg/machinery/config/config/volume.go @@ -87,6 +87,7 @@ type UserVolumeConfig interface { UserVolumeConfigSignal() Provisioning() VolumeProvisioningConfig Filesystem() FilesystemConfig + Encryption() EncryptionConfig } // FilesystemConfig defines the interface to access filesystem configuration. diff --git a/pkg/machinery/config/schemas/config.schema.json b/pkg/machinery/config/schemas/config.schema.json index dc77b616d..e9c2e4aeb 100644 --- a/pkg/machinery/config/schemas/config.schema.json +++ b/pkg/machinery/config/schemas/config.schema.json @@ -16,6 +16,158 @@ "type": "object", "description": "DiskSelector selects a disk for the volume." }, + "block.EncryptionKey": { + "properties": { + "slot": { + "type": "integer", + "title": "slot", + "description": "Key slot number for LUKS2 encryption.\n", + "markdownDescription": "Key slot number for LUKS2 encryption.", + "x-intellij-html-description": "\u003cp\u003eKey slot number for LUKS2 encryption.\u003c/p\u003e\n" + }, + "static": { + "$ref": "#/$defs/block.EncryptionKeyStatic", + "title": "static", + "description": "Key which value is stored in the configuration file.\n", + "markdownDescription": "Key which value is stored in the configuration file.", + "x-intellij-html-description": "\u003cp\u003eKey which value is stored in the configuration file.\u003c/p\u003e\n" + }, + "nodeID": { + "$ref": "#/$defs/block.EncryptionKeyNodeID", + "title": "nodeID", + "description": "Deterministically generated key from the node UUID and PartitionLabel.\n", + "markdownDescription": "Deterministically generated key from the node UUID and PartitionLabel.", + "x-intellij-html-description": "\u003cp\u003eDeterministically generated key from the node UUID and PartitionLabel.\u003c/p\u003e\n" + }, + "kms": { + "$ref": "#/$defs/block.EncryptionKeyKMS", + "title": "kms", + "description": "KMS managed encryption key.\n", + "markdownDescription": "KMS managed encryption key.", + "x-intellij-html-description": "\u003cp\u003eKMS managed encryption key.\u003c/p\u003e\n" + }, + "tpm": { + "$ref": "#/$defs/block.EncryptionKeyTPM", + "title": "tpm", + "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" + } + }, + "additionalProperties": false, + "type": "object", + "description": "EncryptionKey represents configuration for disk encryption key." + }, + "block.EncryptionKeyKMS": { + "properties": { + "endpoint": { + "type": "string", + "title": "endpoint", + "description": "KMS endpoint to Seal/Unseal the key.\n", + "markdownDescription": "KMS endpoint to Seal/Unseal the key.", + "x-intellij-html-description": "\u003cp\u003eKMS endpoint to Seal/Unseal the key.\u003c/p\u003e\n" + } + }, + "additionalProperties": false, + "type": "object", + "description": "EncryptionKeyKMS represents a key that is generated and then sealed/unsealed by the KMS server." + }, + "block.EncryptionKeyNodeID": { + "properties": {}, + "additionalProperties": false, + "type": "object", + "description": "EncryptionKeyNodeID represents deterministically generated key from the node UUID and PartitionLabel." + }, + "block.EncryptionKeyStatic": { + "properties": { + "passphrase": { + "type": "string", + "title": "passphrase", + "description": "Defines the static passphrase value.\n", + "markdownDescription": "Defines the static passphrase value.", + "x-intellij-html-description": "\u003cp\u003eDefines the static passphrase value.\u003c/p\u003e\n" + } + }, + "additionalProperties": false, + "type": "object", + "description": "EncryptionKeyStatic represents throw away key type." + }, + "block.EncryptionKeyTPM": { + "properties": { + "checkSecurebootStatusOnEnroll": { + "type": "boolean", + "title": "checkSecurebootStatusOnEnroll", + "description": "Check that Secureboot is enabled in the EFI firmware.\nIf Secureboot is not enabled, the enrollment of the key will fail. As the TPM key is anyways bound to the value of PCR 7, changing Secureboot status or configuration after the initial enrollment will make the key unusable.\n", + "markdownDescription": "Check that Secureboot is enabled in the EFI firmware.\nIf Secureboot is not enabled, the enrollment of the key will fail. As the TPM key is anyways bound to the value of PCR 7, changing Secureboot status or configuration after the initial enrollment will make the key unusable.", + "x-intellij-html-description": "\u003cp\u003eCheck that Secureboot is enabled in the EFI firmware.\nIf Secureboot is not enabled, the enrollment of the key will fail. As the TPM key is anyways bound to the value of PCR 7, changing Secureboot status or configuration after the initial enrollment will make the key unusable.\u003c/p\u003e\n" + } + }, + "additionalProperties": false, + "type": "object", + "description": "EncryptionKeyTPM represents a key that is generated and then sealed/unsealed by the TPM." + }, + "block.EncryptionSpec": { + "properties": { + "provider": { + "enum": [ + "luks2" + ], + "title": "provider", + "description": "Encryption provider to use for the encryption.\n", + "markdownDescription": "Encryption provider to use for the encryption.", + "x-intellij-html-description": "\u003cp\u003eEncryption provider to use for the encryption.\u003c/p\u003e\n" + }, + "keys": { + "items": { + "$ref": "#/$defs/block.EncryptionKey" + }, + "type": "array", + "title": "keys", + "description": "Defines the encryption keys generation and storage method.\n", + "markdownDescription": "Defines the encryption keys generation and storage method.", + "x-intellij-html-description": "\u003cp\u003eDefines the encryption keys generation and storage method.\u003c/p\u003e\n" + }, + "cipher": { + "enum": [ + "aes-xts-plain64", + "xchacha12,aes-adiantum-plain64", + "xchacha20,aes-adiantum-plain64" + ], + "title": "cipher", + "description": "Cipher to use for the encryption. Depends on the encryption provider.\n", + "markdownDescription": "Cipher to use for the encryption. Depends on the encryption provider.", + "x-intellij-html-description": "\u003cp\u003eCipher to use for the encryption. Depends on the encryption provider.\u003c/p\u003e\n" + }, + "keySize": { + "type": "integer", + "title": "keySize", + "description": "Defines the encryption key length.\n", + "markdownDescription": "Defines the encryption key length.", + "x-intellij-html-description": "\u003cp\u003eDefines the encryption key length.\u003c/p\u003e\n" + }, + "blockSize": { + "type": "integer", + "title": "blockSize", + "description": "Defines the encryption sector size.\n", + "markdownDescription": "Defines the encryption sector size.", + "x-intellij-html-description": "\u003cp\u003eDefines the encryption sector size.\u003c/p\u003e\n" + }, + "options": { + "enum": [ + "no_read_workqueue", + "no_write_workqueue", + "same_cpu_crypt" + ], + "title": "options", + "description": "Additional –perf parameters for the LUKS2 encryption.\n", + "markdownDescription": "Additional --perf parameters for the LUKS2 encryption.", + "x-intellij-html-description": "\u003cp\u003eAdditional \u0026ndash;perf parameters for the LUKS2 encryption.\u003c/p\u003e\n" + } + }, + "additionalProperties": false, + "type": "object", + "description": "EncryptionSpec represents volume encryption settings." + }, "block.FilesystemSpec": { "properties": { "type": { @@ -108,6 +260,13 @@ "description": "The filesystem describes how the volume is formatted.\n", "markdownDescription": "The filesystem describes how the volume is formatted.", "x-intellij-html-description": "\u003cp\u003eThe filesystem describes how the volume is formatted.\u003c/p\u003e\n" + }, + "encryption": { + "$ref": "#/$defs/block.EncryptionSpec", + "title": "encryption", + "description": "The encryption describes how the volume is encrypted.\n", + "markdownDescription": "The encryption describes how the volume is encrypted.", + "x-intellij-html-description": "\u003cp\u003eThe encryption describes how the volume is encrypted.\u003c/p\u003e\n" } }, "additionalProperties": false, diff --git a/pkg/machinery/config/types/block/block.go b/pkg/machinery/config/types/block/block.go index c05ed4216..684f4c8d5 100644 --- a/pkg/machinery/config/types/block/block.go +++ b/pkg/machinery/config/types/block/block.go @@ -5,6 +5,6 @@ // Package block provides block device and volume configuration documents. package block -//go:generate docgen -output block_doc.go block.go volume_config.go user_volume_config.go +//go:generate docgen -output block_doc.go block.go encryption.go volume_config.go user_volume_config.go //go:generate deep-copy -type UserVolumeConfigV1Alpha1 -type VolumeConfigV1Alpha1 -pointer-receiver -header-file ../../../../../hack/boilerplate.txt -o deep_copy.generated.go . diff --git a/pkg/machinery/config/types/block/block_doc.go b/pkg/machinery/config/types/block/block_doc.go index 5f771c8d9..a1b567769 100644 --- a/pkg/machinery/config/types/block/block_doc.go +++ b/pkg/machinery/config/types/block/block_doc.go @@ -10,6 +10,232 @@ import ( "github.com/siderolabs/talos/pkg/machinery/config/encoder" ) +func (EncryptionSpec) Doc() *encoder.Doc { + doc := &encoder.Doc{ + Type: "EncryptionSpec", + Comments: [3]string{"" /* encoder.HeadComment */, "EncryptionSpec represents volume encryption settings." /* encoder.LineComment */, "" /* encoder.FootComment */}, + Description: "EncryptionSpec represents volume encryption settings.", + AppearsIn: []encoder.Appearance{ + { + TypeName: "UserVolumeConfigV1Alpha1", + FieldName: "encryption", + }, + }, + Fields: []encoder.Doc{ + { + Name: "provider", + Type: "EncryptionProviderType", + Note: "", + Description: "Encryption provider to use for the encryption.", + Comments: [3]string{"" /* encoder.HeadComment */, "Encryption provider to use for the encryption." /* encoder.LineComment */, "" /* encoder.FootComment */}, + Values: []string{ + "luks2", + }, + }, + { + Name: "keys", + Type: "[]EncryptionKey", + Note: "", + Description: "Defines the encryption keys generation and storage method.", + Comments: [3]string{"" /* encoder.HeadComment */, "Defines the encryption keys generation and storage method." /* encoder.LineComment */, "" /* encoder.FootComment */}, + }, + { + Name: "cipher", + Type: "string", + Note: "", + Description: "Cipher to use for the encryption. Depends on the encryption provider.", + Comments: [3]string{"" /* encoder.HeadComment */, "Cipher to use for the encryption. Depends on the encryption provider." /* encoder.LineComment */, "" /* encoder.FootComment */}, + Values: []string{ + "aes-xts-plain64", + "xchacha12,aes-adiantum-plain64", + "xchacha20,aes-adiantum-plain64", + }, + }, + { + Name: "keySize", + Type: "uint", + Note: "", + Description: "Defines the encryption key length.", + Comments: [3]string{"" /* encoder.HeadComment */, "Defines the encryption key length." /* encoder.LineComment */, "" /* encoder.FootComment */}, + }, + { + Name: "blockSize", + Type: "uint64", + Note: "", + Description: "Defines the encryption sector size.", + Comments: [3]string{"" /* encoder.HeadComment */, "Defines the encryption sector size." /* encoder.LineComment */, "" /* encoder.FootComment */}, + }, + { + Name: "options", + Type: "[]string", + Note: "", + Description: "Additional --perf parameters for the LUKS2 encryption.", + Comments: [3]string{"" /* encoder.HeadComment */, "Additional --perf parameters for the LUKS2 encryption." /* encoder.LineComment */, "" /* encoder.FootComment */}, + Values: []string{ + "no_read_workqueue", + "no_write_workqueue", + "same_cpu_crypt", + }, + }, + }, + } + + doc.AddExample("", exampleEncryptionSpec()) + + doc.Fields[2].AddExample("", "aes-xts-plain64") + doc.Fields[4].AddExample("", 4096) + doc.Fields[5].AddExample("", []string{"no_read_workqueue", "no_write_workqueue"}) + + return doc +} + +func (EncryptionKey) Doc() *encoder.Doc { + doc := &encoder.Doc{ + Type: "EncryptionKey", + Comments: [3]string{"" /* encoder.HeadComment */, "EncryptionKey represents configuration for disk encryption key." /* encoder.LineComment */, "" /* encoder.FootComment */}, + Description: "EncryptionKey represents configuration for disk encryption key.", + AppearsIn: []encoder.Appearance{ + { + TypeName: "EncryptionSpec", + FieldName: "keys", + }, + }, + Fields: []encoder.Doc{ + { + Name: "slot", + Type: "int", + Note: "", + Description: "Key slot number for LUKS2 encryption.", + Comments: [3]string{"" /* encoder.HeadComment */, "Key slot number for LUKS2 encryption." /* encoder.LineComment */, "" /* encoder.FootComment */}, + }, + { + Name: "static", + Type: "EncryptionKeyStatic", + Note: "", + Description: "Key which value is stored in the configuration file.", + Comments: [3]string{"" /* encoder.HeadComment */, "Key which value is stored in the configuration file." /* encoder.LineComment */, "" /* encoder.FootComment */}, + }, + { + Name: "nodeID", + Type: "EncryptionKeyNodeID", + Note: "", + Description: "Deterministically generated key from the node UUID and PartitionLabel.", + Comments: [3]string{"" /* encoder.HeadComment */, "Deterministically generated key from the node UUID and PartitionLabel." /* encoder.LineComment */, "" /* encoder.FootComment */}, + }, + { + Name: "kms", + Type: "EncryptionKeyKMS", + Note: "", + Description: "KMS managed encryption key.", + Comments: [3]string{"" /* encoder.HeadComment */, "KMS managed encryption key." /* encoder.LineComment */, "" /* encoder.FootComment */}, + }, + { + Name: "tpm", + Type: "EncryptionKeyTPM", + Note: "", + Description: "Enable TPM based disk encryption.", + Comments: [3]string{"" /* encoder.HeadComment */, "Enable TPM based disk encryption." /* encoder.LineComment */, "" /* encoder.FootComment */}, + }, + }, + } + + return doc +} + +func (EncryptionKeyStatic) Doc() *encoder.Doc { + doc := &encoder.Doc{ + Type: "EncryptionKeyStatic", + Comments: [3]string{"" /* encoder.HeadComment */, "EncryptionKeyStatic represents throw away key type." /* encoder.LineComment */, "" /* encoder.FootComment */}, + Description: "EncryptionKeyStatic represents throw away key type.", + AppearsIn: []encoder.Appearance{ + { + TypeName: "EncryptionKey", + FieldName: "static", + }, + }, + Fields: []encoder.Doc{ + { + Name: "passphrase", + Type: "string", + Note: "", + Description: "Defines the static passphrase value.", + Comments: [3]string{"" /* encoder.HeadComment */, "Defines the static passphrase value." /* encoder.LineComment */, "" /* encoder.FootComment */}, + }, + }, + } + + return doc +} + +func (EncryptionKeyKMS) Doc() *encoder.Doc { + doc := &encoder.Doc{ + Type: "EncryptionKeyKMS", + Comments: [3]string{"" /* encoder.HeadComment */, "EncryptionKeyKMS represents a key that is generated and then sealed/unsealed by the KMS server." /* encoder.LineComment */, "" /* encoder.FootComment */}, + Description: "EncryptionKeyKMS represents a key that is generated and then sealed/unsealed by the KMS server.", + AppearsIn: []encoder.Appearance{ + { + TypeName: "EncryptionKey", + FieldName: "kms", + }, + }, + Fields: []encoder.Doc{ + { + Name: "endpoint", + Type: "string", + Note: "", + Description: "KMS endpoint to Seal/Unseal the key.", + Comments: [3]string{"" /* encoder.HeadComment */, "KMS endpoint to Seal/Unseal the key." /* encoder.LineComment */, "" /* encoder.FootComment */}, + }, + }, + } + + doc.AddExample("", exampleKMSKey()) + + return doc +} + +func (EncryptionKeyTPM) Doc() *encoder.Doc { + doc := &encoder.Doc{ + Type: "EncryptionKeyTPM", + Comments: [3]string{"" /* encoder.HeadComment */, "EncryptionKeyTPM represents a key that is generated and then sealed/unsealed by the TPM." /* encoder.LineComment */, "" /* encoder.FootComment */}, + Description: "EncryptionKeyTPM represents a key that is generated and then sealed/unsealed by the TPM.", + AppearsIn: []encoder.Appearance{ + { + TypeName: "EncryptionKey", + FieldName: "tpm", + }, + }, + Fields: []encoder.Doc{ + { + Name: "checkSecurebootStatusOnEnroll", + Type: "bool", + Note: "", + Description: "Check that Secureboot is enabled in the EFI firmware.\nIf Secureboot is not enabled, the enrollment of the key will fail. As the TPM key is anyways bound to the value of PCR 7, changing Secureboot status or configuration after the initial enrollment will make the key unusable.", + Comments: [3]string{"" /* encoder.HeadComment */, "Check that Secureboot is enabled in the EFI firmware." /* encoder.LineComment */, "" /* encoder.FootComment */}, + }, + }, + } + + return doc +} + +func (EncryptionKeyNodeID) Doc() *encoder.Doc { + doc := &encoder.Doc{ + Type: "EncryptionKeyNodeID", + Comments: [3]string{"" /* encoder.HeadComment */, "EncryptionKeyNodeID represents deterministically generated key from the node UUID and PartitionLabel." /* encoder.LineComment */, "" /* encoder.FootComment */}, + Description: "EncryptionKeyNodeID represents deterministically generated key from the node UUID and PartitionLabel.", + AppearsIn: []encoder.Appearance{ + { + TypeName: "EncryptionKey", + FieldName: "nodeID", + }, + }, + Fields: []encoder.Doc{}, + } + + return doc +} + func (VolumeConfigV1Alpha1) Doc() *encoder.Doc { doc := &encoder.Doc{ Type: "VolumeConfig", @@ -148,6 +374,13 @@ func (UserVolumeConfigV1Alpha1) Doc() *encoder.Doc { Description: "The filesystem describes how the volume is formatted.", Comments: [3]string{"" /* encoder.HeadComment */, "The filesystem describes how the volume is formatted." /* encoder.LineComment */, "" /* encoder.FootComment */}, }, + { + Name: "encryption", + Type: "EncryptionSpec", + Note: "", + Description: "The encryption describes how the volume is encrypted.", + Comments: [3]string{"" /* encoder.HeadComment */, "The encryption describes how the volume is encrypted." /* encoder.LineComment */, "" /* encoder.FootComment */}, + }, }, } @@ -191,6 +424,12 @@ func GetFileDoc() *encoder.FileDoc { Name: "block", Description: "Package block provides block device and volume configuration documents.\n", Structs: []*encoder.Doc{ + EncryptionSpec{}.Doc(), + EncryptionKey{}.Doc(), + EncryptionKeyStatic{}.Doc(), + EncryptionKeyKMS{}.Doc(), + EncryptionKeyTPM{}.Doc(), + EncryptionKeyNodeID{}.Doc(), VolumeConfigV1Alpha1{}.Doc(), ProvisioningSpec{}.Doc(), DiskSelector{}.Doc(), diff --git a/pkg/machinery/config/types/block/deep_copy.generated.go b/pkg/machinery/config/types/block/deep_copy.generated.go index cb9f5146e..d7a6710cb 100644 --- a/pkg/machinery/config/types/block/deep_copy.generated.go +++ b/pkg/machinery/config/types/block/deep_copy.generated.go @@ -29,6 +29,36 @@ func (o *UserVolumeConfigV1Alpha1) DeepCopy() *UserVolumeConfigV1Alpha1 { cp.ProvisioningSpec.ProvisioningMaxSize.raw = make([]byte, len(o.ProvisioningSpec.ProvisioningMaxSize.raw)) copy(cp.ProvisioningSpec.ProvisioningMaxSize.raw, o.ProvisioningSpec.ProvisioningMaxSize.raw) } + if o.EncryptionSpec.EncryptionKeys != nil { + cp.EncryptionSpec.EncryptionKeys = make([]EncryptionKey, len(o.EncryptionSpec.EncryptionKeys)) + copy(cp.EncryptionSpec.EncryptionKeys, o.EncryptionSpec.EncryptionKeys) + for i3 := range o.EncryptionSpec.EncryptionKeys { + if o.EncryptionSpec.EncryptionKeys[i3].KeyStatic != nil { + cp.EncryptionSpec.EncryptionKeys[i3].KeyStatic = new(EncryptionKeyStatic) + *cp.EncryptionSpec.EncryptionKeys[i3].KeyStatic = *o.EncryptionSpec.EncryptionKeys[i3].KeyStatic + } + if o.EncryptionSpec.EncryptionKeys[i3].KeyNodeID != nil { + cp.EncryptionSpec.EncryptionKeys[i3].KeyNodeID = new(EncryptionKeyNodeID) + *cp.EncryptionSpec.EncryptionKeys[i3].KeyNodeID = *o.EncryptionSpec.EncryptionKeys[i3].KeyNodeID + } + if o.EncryptionSpec.EncryptionKeys[i3].KeyKMS != nil { + cp.EncryptionSpec.EncryptionKeys[i3].KeyKMS = new(EncryptionKeyKMS) + *cp.EncryptionSpec.EncryptionKeys[i3].KeyKMS = *o.EncryptionSpec.EncryptionKeys[i3].KeyKMS + } + if o.EncryptionSpec.EncryptionKeys[i3].KeyTPM != nil { + cp.EncryptionSpec.EncryptionKeys[i3].KeyTPM = new(EncryptionKeyTPM) + *cp.EncryptionSpec.EncryptionKeys[i3].KeyTPM = *o.EncryptionSpec.EncryptionKeys[i3].KeyTPM + if o.EncryptionSpec.EncryptionKeys[i3].KeyTPM.TPMCheckSecurebootStatusOnEnroll != nil { + cp.EncryptionSpec.EncryptionKeys[i3].KeyTPM.TPMCheckSecurebootStatusOnEnroll = new(bool) + *cp.EncryptionSpec.EncryptionKeys[i3].KeyTPM.TPMCheckSecurebootStatusOnEnroll = *o.EncryptionSpec.EncryptionKeys[i3].KeyTPM.TPMCheckSecurebootStatusOnEnroll + } + } + } + } + if o.EncryptionSpec.EncryptionPerfOptions != nil { + cp.EncryptionSpec.EncryptionPerfOptions = make([]string, len(o.EncryptionSpec.EncryptionPerfOptions)) + copy(cp.EncryptionSpec.EncryptionPerfOptions, o.EncryptionSpec.EncryptionPerfOptions) + } return &cp } diff --git a/pkg/machinery/config/types/block/encryption.go b/pkg/machinery/config/types/block/encryption.go new file mode 100644 index 000000000..17f213bab --- /dev/null +++ b/pkg/machinery/config/types/block/encryption.go @@ -0,0 +1,289 @@ +// 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 block + +import ( + "errors" + "fmt" + + "github.com/siderolabs/gen/xslices" + "github.com/siderolabs/go-pointer" + + "github.com/siderolabs/talos/pkg/machinery/config/config" + "github.com/siderolabs/talos/pkg/machinery/resources/block" +) + +//docgen:jsonschema + +// EncryptionSpec represents volume encryption settings. +// +// examples: +// - value: exampleEncryptionSpec() +type EncryptionSpec struct { + // description: > + // Encryption provider to use for the encryption. + // values: + // - luks2 + EncryptionProvider block.EncryptionProviderType `yaml:"provider"` + // description: > + // Defines the encryption keys generation and storage method. + EncryptionKeys []EncryptionKey `yaml:"keys"` + // description: > + // Cipher to use for the encryption. + // Depends on the encryption provider. + // values: + // - aes-xts-plain64 + // - xchacha12,aes-adiantum-plain64 + // - xchacha20,aes-adiantum-plain64 + // examples: + // - value: '"aes-xts-plain64"' + EncryptionCipher string `yaml:"cipher,omitempty"` + // description: > + // Defines the encryption key length. + EncryptionKeySize uint `yaml:"keySize,omitempty"` + // description: > + // Defines the encryption sector size. + // examples: + // - value: '4096' + EncryptionBlockSize uint64 `yaml:"blockSize,omitempty"` + // description: > + // Additional --perf parameters for the LUKS2 encryption. + // values: + // - no_read_workqueue + // - no_write_workqueue + // - same_cpu_crypt + // examples: + // - value: > + // []string{"no_read_workqueue","no_write_workqueue"} + EncryptionPerfOptions []string `yaml:"options,omitempty"` +} + +func exampleEncryptionSpec() *EncryptionSpec { + return &EncryptionSpec{ + EncryptionProvider: block.EncryptionProviderLUKS2, + EncryptionKeys: []EncryptionKey{ + { + KeySlot: 0, + KeyStatic: &EncryptionKeyStatic{ + KeyData: "exampleKey", + }, + }, + { + KeySlot: 1, + KeyKMS: &EncryptionKeyKMS{ + KMSEndpoint: "https://example-kms-endpoint.com", + }, + }, + }, + EncryptionCipher: "aes-xts-plain64", + EncryptionBlockSize: 4096, + } +} + +// EncryptionKey represents configuration for disk encryption key. +type EncryptionKey struct { + // description: > + // Key slot number for LUKS2 encryption. + KeySlot int `yaml:"slot"` + // description: > + // Key which value is stored in the configuration file. + KeyStatic *EncryptionKeyStatic `yaml:"static,omitempty"` + // description: > + // Deterministically generated key from the node UUID and PartitionLabel. + KeyNodeID *EncryptionKeyNodeID `yaml:"nodeID,omitempty"` + // description: > + // KMS managed encryption key. + KeyKMS *EncryptionKeyKMS `yaml:"kms,omitempty"` + // description: > + // Enable TPM based disk encryption. + KeyTPM *EncryptionKeyTPM `yaml:"tpm,omitempty"` +} + +// EncryptionKeyStatic represents throw away key type. +type EncryptionKeyStatic struct { + // description: > + // Defines the static passphrase value. + KeyData string `yaml:"passphrase,omitempty"` +} + +// EncryptionKeyKMS represents a key that is generated and then sealed/unsealed by the KMS server. +// +// examples: +// - value: exampleKMSKey() +type EncryptionKeyKMS struct { + // description: > + // KMS endpoint to Seal/Unseal the key. + KMSEndpoint string `yaml:"endpoint"` +} + +// EncryptionKeyTPM represents a key that is generated and then sealed/unsealed by the TPM. +type EncryptionKeyTPM struct { + // description: > + // Check that Secureboot is enabled in the EFI firmware. + // + // If Secureboot is not enabled, the enrollment of the key will fail. + // As the TPM key is anyways bound to the value of PCR 7, + // changing Secureboot status or configuration + // after the initial enrollment will make the key unusable. + TPMCheckSecurebootStatusOnEnroll *bool `yaml:"checkSecurebootStatusOnEnroll,omitempty"` +} + +// EncryptionKeyNodeID represents deterministically generated key from the node UUID and PartitionLabel. +type EncryptionKeyNodeID struct{} + +func exampleKMSKey() *EncryptionKeyKMS { + return &EncryptionKeyKMS{ + KMSEndpoint: "https://192.168.88.21:4443", + } +} + +// Validate implements config.Validator interface. +// +//nolint:gocyclo +func (s EncryptionSpec) Validate() ([]string, error) { + if s.EncryptionProvider == block.EncryptionProviderNone && len(s.EncryptionKeys) == 0 { + return nil, nil + } + + var errs error + + switch s.EncryptionProvider { + case block.EncryptionProviderLUKS2: + case block.EncryptionProviderNone: + fallthrough + default: + errs = errors.Join(errs, fmt.Errorf("unsupported encryption provider: %s", s.EncryptionProvider)) + } + + if len(s.EncryptionKeys) == 0 { + errs = errors.Join(errs, errors.New("encryption keys are required")) + } + + slotsInUse := make(map[int]struct{}, len(s.EncryptionKeys)) + + for _, key := range s.EncryptionKeys { + if _, ok := slotsInUse[key.KeySlot]; ok { + errs = errors.Join(errs, fmt.Errorf("duplicate key slot %d", key.KeySlot)) + } + + slotsInUse[key.KeySlot] = struct{}{} + + if key.KeyStatic == nil && key.KeyNodeID == nil && key.KeyKMS == nil && key.KeyTPM == nil { + errs = errors.Join(errs, fmt.Errorf("at least one encryption key type must be specified for slot %d", key.KeySlot)) + } + } + + return nil, errs +} + +// Provider implements the config.Provider interface. +func (s EncryptionSpec) Provider() block.EncryptionProviderType { + return s.EncryptionProvider +} + +// Cipher implements the config.Provider interface. +func (s EncryptionSpec) Cipher() string { + return s.EncryptionCipher +} + +// KeySize implements the config.Provider interface. +func (s EncryptionSpec) KeySize() uint { + return s.EncryptionKeySize +} + +// BlockSize implements the config.Provider interface. +func (s EncryptionSpec) BlockSize() uint64 { + return s.EncryptionBlockSize +} + +// Options implements the config.Provider interface. +func (s EncryptionSpec) Options() []string { + return s.EncryptionPerfOptions +} + +// Keys implements the config.Provider interface. +func (s EncryptionSpec) Keys() []config.EncryptionKey { + return xslices.Map(s.EncryptionKeys, func(k EncryptionKey) config.EncryptionKey { return k }) +} + +// Slot implements the config.Provider interface. +func (k EncryptionKey) Slot() int { + return k.KeySlot +} + +// Static implements the config.Provider interface. +func (k EncryptionKey) Static() config.EncryptionKeyStatic { + if k.KeyStatic == nil { + return nil + } + + return k.KeyStatic +} + +// NodeID implements the config.Provider interface. +func (k EncryptionKey) NodeID() config.EncryptionKeyNodeID { + if k.KeyNodeID == nil { + return nil + } + + return k.KeyNodeID +} + +// KMS implements the config.Provider interface. +func (k EncryptionKey) KMS() config.EncryptionKeyKMS { + if k.KeyKMS == nil { + return nil + } + + return k.KeyKMS +} + +// TPM implements the config.Provider interface. +func (k EncryptionKey) TPM() config.EncryptionKeyTPM { + if k.KeyTPM == nil { + return nil + } + + return k.KeyTPM +} + +// String implements the config.Provider interface. +func (e *EncryptionKeyNodeID) String() string { + return "nodeid" +} + +// String implements the config.Provider interface. +func (e *EncryptionKeyTPM) String() string { + return "tpm" +} + +// CheckSecurebootOnEnroll implements the config.Provider interface. +func (e *EncryptionKeyTPM) CheckSecurebootOnEnroll() bool { + if e == nil { + return false + } + + return pointer.SafeDeref(e.TPMCheckSecurebootStatusOnEnroll) +} + +// Key implements the config.Provider interface. +func (e *EncryptionKeyStatic) Key() []byte { + return []byte(e.KeyData) +} + +// String implements the config.Provider interface. +func (e *EncryptionKeyStatic) String() string { + return "static" +} + +// Endpoint implements the config.Provider interface. +func (e *EncryptionKeyKMS) Endpoint() string { + return e.KMSEndpoint +} + +// String implements the config.Provider interface. +func (e *EncryptionKeyKMS) String() string { + return "kms" +} diff --git a/pkg/machinery/config/types/block/testdata/uservolumeconfig_encrypted.yaml b/pkg/machinery/config/types/block/testdata/uservolumeconfig_encrypted.yaml new file mode 100644 index 000000000..149a7d893 --- /dev/null +++ b/pkg/machinery/config/types/block/testdata/uservolumeconfig_encrypted.yaml @@ -0,0 +1,16 @@ +apiVersion: v1alpha1 +kind: UserVolumeConfig +name: secret-store +provisioning: + diskSelector: + match: '!system_disk' + minSize: 10GiB +encryption: + provider: luks2 + keys: + - slot: 0 + tpm: {} + - slot: 1 + static: + passphrase: topsecret + cipher: aes-xts-plain64 diff --git a/pkg/machinery/config/types/block/user_volume_config.go b/pkg/machinery/config/types/block/user_volume_config.go index c726bfb63..7ecb462fc 100644 --- a/pkg/machinery/config/types/block/user_volume_config.go +++ b/pkg/machinery/config/types/block/user_volume_config.go @@ -69,6 +69,9 @@ type UserVolumeConfigV1Alpha1 struct { // description: | // The filesystem describes how the volume is formatted. FilesystemSpec FilesystemSpec `yaml:"filesystem,omitempty"` + // description: | + // The encryption describes how the volume is encrypted. + EncryptionSpec EncryptionSpec `yaml:"encryption,omitempty"` } // NewUserVolumeConfigV1Alpha1 creates a new user volume config document. @@ -93,6 +96,21 @@ func exampleUserVolumeConfigV1Alpha1() *UserVolumeConfigV1Alpha1 { cfg.FilesystemSpec = FilesystemSpec{ FilesystemType: block.FilesystemTypeXFS, } + cfg.EncryptionSpec = EncryptionSpec{ + EncryptionProvider: block.EncryptionProviderLUKS2, + EncryptionKeys: []EncryptionKey{ + { + KeySlot: 0, + KeyTPM: &EncryptionKeyTPM{}, + }, + { + KeySlot: 1, + KeyStatic: &EncryptionKeyStatic{ + KeyData: "topsecret", + }, + }, + }, + } return cfg } @@ -150,6 +168,10 @@ func (s *UserVolumeConfigV1Alpha1) Validate(validation.RuntimeMode, ...validatio warnings = append(warnings, extraWarnings...) validationErrors = errors.Join(validationErrors, extraErrors) + extraWarnings, extraErrors = s.EncryptionSpec.Validate() + warnings = append(warnings, extraWarnings...) + validationErrors = errors.Join(validationErrors, extraErrors) + return warnings, validationErrors } @@ -166,6 +188,15 @@ func (s *UserVolumeConfigV1Alpha1) Filesystem() config.FilesystemConfig { return s.FilesystemSpec } +// Encryption implements config.UserVolumeConfig interface. +func (s *UserVolumeConfigV1Alpha1) Encryption() config.EncryptionConfig { + if s.EncryptionSpec.EncryptionProvider == block.EncryptionProviderNone { + return nil + } + + return s.EncryptionSpec +} + // FilesystemSpec configures the filesystem for the volume. type FilesystemSpec struct { // description: | diff --git a/pkg/machinery/config/types/block/user_volume_config_test.go b/pkg/machinery/config/types/block/user_volume_config_test.go index 8faf64869..b1c8c8a77 100644 --- a/pkg/machinery/config/types/block/user_volume_config_test.go +++ b/pkg/machinery/config/types/block/user_volume_config_test.go @@ -41,6 +41,33 @@ func TestUserVolumeConfigMarshalUnmarshal(t *testing.T) { c.ProvisioningSpec.ProvisioningMaxSize = block.MustByteSize("100GiB") c.FilesystemSpec.FilesystemType = blockres.FilesystemTypeXFS + return c + }, + }, + { + name: "encrypted", + filename: "uservolumeconfig_encrypted.yaml", + cfg: func(t *testing.T) *block.UserVolumeConfigV1Alpha1 { + c := block.NewUserVolumeConfigV1Alpha1() + c.MetaName = "secret-store" + + require.NoError(t, c.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`!system_disk`))) + c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB") + c.EncryptionSpec.EncryptionProvider = blockres.EncryptionProviderLUKS2 + c.EncryptionSpec.EncryptionCipher = "aes-xts-plain64" + c.EncryptionSpec.EncryptionKeys = []block.EncryptionKey{ + { + KeySlot: 0, + KeyTPM: &block.EncryptionKeyTPM{}, + }, + { + KeySlot: 1, + KeyStatic: &block.EncryptionKeyStatic{ + KeyData: "topsecret", + }, + }, + } + return c }, }, @@ -175,6 +202,72 @@ func TestUserVolumeConfigValidate(t *testing.T) { expectedErrors: "unsupported filesystem type: iso9660", }, + { + name: "no encryption provider", + + cfg: func(t *testing.T) *block.UserVolumeConfigV1Alpha1 { + c := block.NewUserVolumeConfigV1Alpha1() + c.MetaName = constants.EphemeralPartitionLabel + + require.NoError(t, c.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`system_disk`))) + c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB") + c.EncryptionSpec.EncryptionKeys = []block.EncryptionKey{ + { + KeySlot: 0, + KeyTPM: &block.EncryptionKeyTPM{}, + }, + } + + return c + }, + + expectedErrors: "unsupported encryption provider: none", + }, + { + name: "no encryption keys", + + cfg: func(t *testing.T) *block.UserVolumeConfigV1Alpha1 { + c := block.NewUserVolumeConfigV1Alpha1() + c.MetaName = constants.EphemeralPartitionLabel + + require.NoError(t, c.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`system_disk`))) + c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB") + c.EncryptionSpec.EncryptionProvider = blockres.EncryptionProviderLUKS2 + + return c + }, + + expectedErrors: "encryption keys are required", + }, + { + name: "invalid encryption key slots", + + cfg: func(t *testing.T) *block.UserVolumeConfigV1Alpha1 { + c := block.NewUserVolumeConfigV1Alpha1() + c.MetaName = constants.EphemeralPartitionLabel + + require.NoError(t, c.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`system_disk`))) + c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB") + c.EncryptionSpec.EncryptionProvider = blockres.EncryptionProviderLUKS2 + c.EncryptionSpec.EncryptionKeys = []block.EncryptionKey{ + { + KeySlot: 1, + KeyTPM: &block.EncryptionKeyTPM{}, + }, + { + KeySlot: 0, + }, + { + KeySlot: 1, + KeyTPM: &block.EncryptionKeyTPM{}, + }, + } + + return c + }, + + expectedErrors: "at least one encryption key type must be specified for slot 0\nduplicate key slot 1", + }, { name: "valid", @@ -187,6 +280,27 @@ func TestUserVolumeConfigValidate(t *testing.T) { c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB") c.FilesystemSpec.FilesystemType = blockres.FilesystemTypeEXT4 + return c + }, + }, + { + name: "valid encrypted", + + cfg: func(t *testing.T) *block.UserVolumeConfigV1Alpha1 { + c := block.NewUserVolumeConfigV1Alpha1() + c.MetaName = constants.EphemeralPartitionLabel + + require.NoError(t, c.ProvisioningSpec.DiskSelectorSpec.Match.UnmarshalText([]byte(`system_disk`))) + c.ProvisioningSpec.ProvisioningMinSize = block.MustByteSize("10GiB") + c.EncryptionSpec.EncryptionProvider = blockres.EncryptionProviderLUKS2 + c.EncryptionSpec.EncryptionCipher = "aes-xts-plain64" + c.EncryptionSpec.EncryptionKeys = []block.EncryptionKey{ + { + KeySlot: 0, + KeyTPM: &block.EncryptionKeyTPM{}, + }, + } + return c }, }, diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go index f2dc3e3b5..1a0176760 100644 --- a/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go @@ -19,7 +19,6 @@ import ( "github.com/opencontainers/runtime-spec/specs-go" "github.com/siderolabs/crypto/x509" "github.com/siderolabs/gen/xslices" - "github.com/siderolabs/go-blockdevice/v2/encryption" "github.com/siderolabs/go-pointer" "github.com/siderolabs/talos/pkg/machinery/cel" @@ -27,6 +26,7 @@ import ( "github.com/siderolabs/talos/pkg/machinery/config/config" "github.com/siderolabs/talos/pkg/machinery/config/machine" "github.com/siderolabs/talos/pkg/machinery/constants" + "github.com/siderolabs/talos/pkg/machinery/resources/block" ) // Verify interfaces. @@ -1532,12 +1532,15 @@ func (p *DiskPartition) MountPoint() string { } // Provider implements the config.Provider interface. -func (e *EncryptionConfig) Provider() string { +func (e *EncryptionConfig) Provider() block.EncryptionProviderType { if e.EncryptionProvider == "" { - return encryption.LUKS2 + return block.EncryptionProviderLUKS2 } - return e.EncryptionProvider + // the provider is validated in the machine config validation + provider, _ := block.EncryptionProviderTypeString(e.EncryptionProvider) //nolint:errcheck + + return provider } // Cipher implements the config.Provider interface. @@ -1646,7 +1649,7 @@ func (e *EncryptionKeyKMS) String() string { } // Get implements the config.Provider interface. -func (e *SystemDiskEncryptionConfig) Get(label string) config.Encryption { +func (e *SystemDiskEncryptionConfig) Get(label string) config.EncryptionConfig { switch label { case constants.StatePartitionLabel: if e.StatePartition == nil { diff --git a/pkg/machinery/go.mod b/pkg/machinery/go.mod index a773a2a85..a15ab344f 100644 --- a/pkg/machinery/go.mod +++ b/pkg/machinery/go.mod @@ -32,7 +32,6 @@ require ( github.com/siderolabs/crypto v0.5.1 github.com/siderolabs/gen v0.8.0 github.com/siderolabs/go-api-signature v0.3.6 - github.com/siderolabs/go-blockdevice/v2 v2.0.16 github.com/siderolabs/go-pointer v1.0.1 github.com/siderolabs/net v0.4.0 github.com/siderolabs/protoenc v0.2.2 diff --git a/pkg/machinery/go.sum b/pkg/machinery/go.sum index bfc2bc476..d402f2af1 100644 --- a/pkg/machinery/go.sum +++ b/pkg/machinery/go.sum @@ -117,8 +117,6 @@ github.com/siderolabs/gen v0.8.0 h1:Pj93+hexkk5hQ7izjJ6YXnEWc8vlzOmDwFz13/VzS7o= github.com/siderolabs/gen v0.8.0/go.mod h1:an3a2Y53O7kUjnnK8Bfu3gewtvnIOu5RTU6HalFtXQQ= github.com/siderolabs/go-api-signature v0.3.6 h1:wDIsXbpl7Oa/FXvxB6uz4VL9INA9fmr3EbmjEZYFJrU= github.com/siderolabs/go-api-signature v0.3.6/go.mod h1:hoH13AfunHflxbXfh+NoploqV13ZTDfQ1mQJWNVSW9U= -github.com/siderolabs/go-blockdevice/v2 v2.0.16 h1:QeQ72S7M/rwXV1nah/uzyBPeF/PLCEwuSqj1hFeZYQU= -github.com/siderolabs/go-blockdevice/v2 v2.0.16/go.mod h1:74htzCV913UzaLZ4H+NBXkwWlYnBJIq5m/379ZEcu8w= github.com/siderolabs/go-pointer v1.0.1 h1:f7Yi4IK1jptS8yrT9GEbwhmGcVxvPQgBUG/weH3V3DM= github.com/siderolabs/go-pointer v1.0.1/go.mod h1:C8Q/3pNHT4RE9e4rYR9PHeS6KPMlStRBgYrJQJNy/vA= github.com/siderolabs/go-retry v0.3.3 h1:zKV+S1vumtO72E6sYsLlmIdV/G/GcYSBLiEx/c9oCEg= diff --git a/website/content/v1.10/reference/cli.md b/website/content/v1.10/reference/cli.md index 40fa2b172..552a98be9 100644 --- a/website/content/v1.10/reference/cli.md +++ b/website/content/v1.10/reference/cli.md @@ -167,6 +167,7 @@ talosctl cluster create [flags] --docker-host-ip string Host IP to forward exposed ports to (Docker provisioner only) (default "0.0.0.0") --encrypt-ephemeral enable ephemeral partition encryption --encrypt-state enable state partition encryption + --encrypt-user-volumes enable ephemeral partition encryption --endpoint string use endpoint instead of provider defaults -p, --exposed-ports string Comma-separated list of ports/protocols to expose on init node. Ex -p :/ (Docker provisioner only) --extra-boot-kernel-args string add extra kernel args to the initial boot from vmlinuz and initramfs (QEMU only) diff --git a/website/content/v1.10/reference/configuration/block/uservolumeconfig.md b/website/content/v1.10/reference/configuration/block/uservolumeconfig.md index 7cc752b9e..9cb778d63 100644 --- a/website/content/v1.10/reference/configuration/block/uservolumeconfig.md +++ b/website/content/v1.10/reference/configuration/block/uservolumeconfig.md @@ -33,6 +33,37 @@ provisioning: # The filesystem describes how the volume is formatted. filesystem: type: xfs # Filesystem type. Default is `xfs`. +# The encryption describes how the volume is encrypted. +encryption: + provider: luks2 # Encryption provider to use for the encryption. + # Defines the encryption keys generation and storage method. + keys: + - slot: 0 # Key slot number for LUKS2 encryption. + # Enable TPM based disk encryption. + tpm: {} + + # # KMS managed encryption key. + # kms: + # endpoint: https://192.168.88.21:4443 # KMS endpoint to Seal/Unseal the key. + - slot: 1 # Key slot number for LUKS2 encryption. + # Key which value is stored in the configuration file. + static: + passphrase: topsecret # Defines the static passphrase value. + + # # KMS managed encryption key. + # kms: + # endpoint: https://192.168.88.21:4443 # KMS endpoint to Seal/Unseal the key. + + # # Cipher to use for the encryption. Depends on the encryption provider. + # cipher: aes-xts-plain64 + + # # Defines the encryption sector size. + # blockSize: 4096 + + # # Additional --perf parameters for the LUKS2 encryption. + # options: + # - no_read_workqueue + # - no_write_workqueue {{< /highlight >}} @@ -41,6 +72,7 @@ filesystem: |`name` |string |
Name of the volume.
Name might be between 1 and 34 characters long and can only contain:
lowercase and uppercase ASCII letters, digits, and hyphens.
| | |`provisioning` |ProvisioningSpec |The provisioning describes how the volume is provisioned. | | |`filesystem` |FilesystemSpec |The filesystem describes how the volume is formatted. | | +|`encryption` |EncryptionSpec |The encryption describes how the volume is encrypted. | | @@ -104,5 +136,147 @@ FilesystemSpec configures the filesystem for the volume. +## encryption {#UserVolumeConfig.encryption} + +EncryptionSpec represents volume encryption settings. + + + +{{< highlight yaml >}} +encryption: + provider: luks2 # Encryption provider to use for the encryption. + # Defines the encryption keys generation and storage method. + keys: + - slot: 0 # Key slot number for LUKS2 encryption. + # Key which value is stored in the configuration file. + static: + passphrase: exampleKey # Defines the static passphrase value. + + # # KMS managed encryption key. + # kms: + # endpoint: https://192.168.88.21:4443 # KMS endpoint to Seal/Unseal the key. + - slot: 1 # Key slot number for LUKS2 encryption. + # KMS managed encryption key. + kms: + endpoint: https://example-kms-endpoint.com # KMS endpoint to Seal/Unseal the key. + cipher: aes-xts-plain64 # Cipher to use for the encryption. Depends on the encryption provider. + blockSize: 4096 # Defines the encryption sector size. + + # # Additional --perf parameters for the LUKS2 encryption. + # options: + # - no_read_workqueue + # - no_write_workqueue +{{< /highlight >}} + + +| Field | Type | Description | Value(s) | +|-------|------|-------------|----------| +|`provider` |EncryptionProviderType |Encryption provider to use for the encryption. |`luks2`
| +|`keys` |[]EncryptionKey |Defines the encryption keys generation and storage method. | | +|`cipher` |string |Cipher to use for the encryption. Depends on the encryption provider.
Show example(s){{< highlight yaml >}} +cipher: aes-xts-plain64 +{{< /highlight >}}
|`aes-xts-plain64`
`xchacha12,aes-adiantum-plain64`
`xchacha20,aes-adiantum-plain64`
| +|`keySize` |uint |Defines the encryption key length. | | +|`blockSize` |uint64 |Defines the encryption sector size.
Show example(s){{< highlight yaml >}} +blockSize: 4096 +{{< /highlight >}}
| | +|`options` |[]string |Additional --perf parameters for the LUKS2 encryption.
Show example(s){{< highlight yaml >}} +options: + - no_read_workqueue + - no_write_workqueue +{{< /highlight >}}
|`no_read_workqueue`
`no_write_workqueue`
`same_cpu_crypt`
| + + + + +### keys[] {#UserVolumeConfig.encryption.keys.} + +EncryptionKey represents configuration for disk encryption key. + + + + +| Field | Type | Description | Value(s) | +|-------|------|-------------|----------| +|`slot` |int |Key slot number for LUKS2 encryption. | | +|`static` |EncryptionKeyStatic |Key which value is stored in the configuration file. | | +|`nodeID` |EncryptionKeyNodeID |Deterministically generated key from the node UUID and PartitionLabel. | | +|`kms` |EncryptionKeyKMS |KMS managed encryption key. | | +|`tpm` |EncryptionKeyTPM |Enable TPM based disk encryption. | | + + + + +#### static {#UserVolumeConfig.encryption.keys..static} + +EncryptionKeyStatic represents throw away key type. + + + + +| Field | Type | Description | Value(s) | +|-------|------|-------------|----------| +|`passphrase` |string |Defines the static passphrase value. | | + + + + + + +#### nodeID {#UserVolumeConfig.encryption.keys..nodeID} + +EncryptionKeyNodeID represents deterministically generated key from the node UUID and PartitionLabel. + + + + + + + + + +#### kms {#UserVolumeConfig.encryption.keys..kms} + +EncryptionKeyKMS represents a key that is generated and then sealed/unsealed by the KMS server. + + + +{{< highlight yaml >}} +encryption: + keys: + - kms: + endpoint: https://192.168.88.21:4443 # KMS endpoint to Seal/Unseal the key. +{{< /highlight >}} + + +| Field | Type | Description | Value(s) | +|-------|------|-------------|----------| +|`endpoint` |string |KMS endpoint to Seal/Unseal the key. | | + + + + + + +#### tpm {#UserVolumeConfig.encryption.keys..tpm} + +EncryptionKeyTPM represents a key that is generated and then sealed/unsealed by the TPM. + + + + +| Field | Type | Description | Value(s) | +|-------|------|-------------|----------| +|`checkSecurebootStatusOnEnroll` |bool |
Check that Secureboot is enabled in the EFI firmware.If Secureboot is not enabled, the enrollment of the key will fail. As the TPM key is anyways bound to the value of PCR 7, changing Secureboot status or configuration after the initial enrollment will make the key unusable.
| | + + + + + + + + + + diff --git a/website/content/v1.10/schemas/config.schema.json b/website/content/v1.10/schemas/config.schema.json index dc77b616d..e9c2e4aeb 100644 --- a/website/content/v1.10/schemas/config.schema.json +++ b/website/content/v1.10/schemas/config.schema.json @@ -16,6 +16,158 @@ "type": "object", "description": "DiskSelector selects a disk for the volume." }, + "block.EncryptionKey": { + "properties": { + "slot": { + "type": "integer", + "title": "slot", + "description": "Key slot number for LUKS2 encryption.\n", + "markdownDescription": "Key slot number for LUKS2 encryption.", + "x-intellij-html-description": "\u003cp\u003eKey slot number for LUKS2 encryption.\u003c/p\u003e\n" + }, + "static": { + "$ref": "#/$defs/block.EncryptionKeyStatic", + "title": "static", + "description": "Key which value is stored in the configuration file.\n", + "markdownDescription": "Key which value is stored in the configuration file.", + "x-intellij-html-description": "\u003cp\u003eKey which value is stored in the configuration file.\u003c/p\u003e\n" + }, + "nodeID": { + "$ref": "#/$defs/block.EncryptionKeyNodeID", + "title": "nodeID", + "description": "Deterministically generated key from the node UUID and PartitionLabel.\n", + "markdownDescription": "Deterministically generated key from the node UUID and PartitionLabel.", + "x-intellij-html-description": "\u003cp\u003eDeterministically generated key from the node UUID and PartitionLabel.\u003c/p\u003e\n" + }, + "kms": { + "$ref": "#/$defs/block.EncryptionKeyKMS", + "title": "kms", + "description": "KMS managed encryption key.\n", + "markdownDescription": "KMS managed encryption key.", + "x-intellij-html-description": "\u003cp\u003eKMS managed encryption key.\u003c/p\u003e\n" + }, + "tpm": { + "$ref": "#/$defs/block.EncryptionKeyTPM", + "title": "tpm", + "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" + } + }, + "additionalProperties": false, + "type": "object", + "description": "EncryptionKey represents configuration for disk encryption key." + }, + "block.EncryptionKeyKMS": { + "properties": { + "endpoint": { + "type": "string", + "title": "endpoint", + "description": "KMS endpoint to Seal/Unseal the key.\n", + "markdownDescription": "KMS endpoint to Seal/Unseal the key.", + "x-intellij-html-description": "\u003cp\u003eKMS endpoint to Seal/Unseal the key.\u003c/p\u003e\n" + } + }, + "additionalProperties": false, + "type": "object", + "description": "EncryptionKeyKMS represents a key that is generated and then sealed/unsealed by the KMS server." + }, + "block.EncryptionKeyNodeID": { + "properties": {}, + "additionalProperties": false, + "type": "object", + "description": "EncryptionKeyNodeID represents deterministically generated key from the node UUID and PartitionLabel." + }, + "block.EncryptionKeyStatic": { + "properties": { + "passphrase": { + "type": "string", + "title": "passphrase", + "description": "Defines the static passphrase value.\n", + "markdownDescription": "Defines the static passphrase value.", + "x-intellij-html-description": "\u003cp\u003eDefines the static passphrase value.\u003c/p\u003e\n" + } + }, + "additionalProperties": false, + "type": "object", + "description": "EncryptionKeyStatic represents throw away key type." + }, + "block.EncryptionKeyTPM": { + "properties": { + "checkSecurebootStatusOnEnroll": { + "type": "boolean", + "title": "checkSecurebootStatusOnEnroll", + "description": "Check that Secureboot is enabled in the EFI firmware.\nIf Secureboot is not enabled, the enrollment of the key will fail. As the TPM key is anyways bound to the value of PCR 7, changing Secureboot status or configuration after the initial enrollment will make the key unusable.\n", + "markdownDescription": "Check that Secureboot is enabled in the EFI firmware.\nIf Secureboot is not enabled, the enrollment of the key will fail. As the TPM key is anyways bound to the value of PCR 7, changing Secureboot status or configuration after the initial enrollment will make the key unusable.", + "x-intellij-html-description": "\u003cp\u003eCheck that Secureboot is enabled in the EFI firmware.\nIf Secureboot is not enabled, the enrollment of the key will fail. As the TPM key is anyways bound to the value of PCR 7, changing Secureboot status or configuration after the initial enrollment will make the key unusable.\u003c/p\u003e\n" + } + }, + "additionalProperties": false, + "type": "object", + "description": "EncryptionKeyTPM represents a key that is generated and then sealed/unsealed by the TPM." + }, + "block.EncryptionSpec": { + "properties": { + "provider": { + "enum": [ + "luks2" + ], + "title": "provider", + "description": "Encryption provider to use for the encryption.\n", + "markdownDescription": "Encryption provider to use for the encryption.", + "x-intellij-html-description": "\u003cp\u003eEncryption provider to use for the encryption.\u003c/p\u003e\n" + }, + "keys": { + "items": { + "$ref": "#/$defs/block.EncryptionKey" + }, + "type": "array", + "title": "keys", + "description": "Defines the encryption keys generation and storage method.\n", + "markdownDescription": "Defines the encryption keys generation and storage method.", + "x-intellij-html-description": "\u003cp\u003eDefines the encryption keys generation and storage method.\u003c/p\u003e\n" + }, + "cipher": { + "enum": [ + "aes-xts-plain64", + "xchacha12,aes-adiantum-plain64", + "xchacha20,aes-adiantum-plain64" + ], + "title": "cipher", + "description": "Cipher to use for the encryption. Depends on the encryption provider.\n", + "markdownDescription": "Cipher to use for the encryption. Depends on the encryption provider.", + "x-intellij-html-description": "\u003cp\u003eCipher to use for the encryption. Depends on the encryption provider.\u003c/p\u003e\n" + }, + "keySize": { + "type": "integer", + "title": "keySize", + "description": "Defines the encryption key length.\n", + "markdownDescription": "Defines the encryption key length.", + "x-intellij-html-description": "\u003cp\u003eDefines the encryption key length.\u003c/p\u003e\n" + }, + "blockSize": { + "type": "integer", + "title": "blockSize", + "description": "Defines the encryption sector size.\n", + "markdownDescription": "Defines the encryption sector size.", + "x-intellij-html-description": "\u003cp\u003eDefines the encryption sector size.\u003c/p\u003e\n" + }, + "options": { + "enum": [ + "no_read_workqueue", + "no_write_workqueue", + "same_cpu_crypt" + ], + "title": "options", + "description": "Additional –perf parameters for the LUKS2 encryption.\n", + "markdownDescription": "Additional --perf parameters for the LUKS2 encryption.", + "x-intellij-html-description": "\u003cp\u003eAdditional \u0026ndash;perf parameters for the LUKS2 encryption.\u003c/p\u003e\n" + } + }, + "additionalProperties": false, + "type": "object", + "description": "EncryptionSpec represents volume encryption settings." + }, "block.FilesystemSpec": { "properties": { "type": { @@ -108,6 +260,13 @@ "description": "The filesystem describes how the volume is formatted.\n", "markdownDescription": "The filesystem describes how the volume is formatted.", "x-intellij-html-description": "\u003cp\u003eThe filesystem describes how the volume is formatted.\u003c/p\u003e\n" + }, + "encryption": { + "$ref": "#/$defs/block.EncryptionSpec", + "title": "encryption", + "description": "The encryption describes how the volume is encrypted.\n", + "markdownDescription": "The encryption describes how the volume is encrypted.", + "x-intellij-html-description": "\u003cp\u003eThe encryption describes how the volume is encrypted.\u003c/p\u003e\n" } }, "additionalProperties": false, diff --git a/website/content/v1.10/talos-guides/configuration/disk-management.md b/website/content/v1.10/talos-guides/configuration/disk-management.md index fa9556616..4c5fc8483 100644 --- a/website/content/v1.10/talos-guides/configuration/disk-management.md +++ b/website/content/v1.10/talos-guides/configuration/disk-management.md @@ -225,6 +225,8 @@ provisioning: match: disk.transport == 'nvme' && !system_disk ``` +> Note: Currently, encryption for `EPHEMERAL` and `STATE` volumes is configured using [another config document]({{< relref "../../reference/configuration/v1alpha1/config#Config.machine.systemDiskEncryption" >}}). + ### `IMAGECACHE` Volume This system volume is not provisioned by default, and it only gets created if the [Image Cache]({{< relref "image-cache" >}}) feature is enabled. @@ -245,6 +247,8 @@ The volume name must be unique across all user volumes, and it should be between The volume label is derived from the volume name as `u-`, and it is used to identify the volume on the disk after initial provisioning. The volume mount location is `/var/mnt/`, and it gets automatically propagated into the `kubelet` container to provide additional features like `subPath` mounts. +Disk encryption can be optionally enabled for user volumes. + ### Creating User Volumes To create a user volume, append the following [document]({{< relref "../../reference/configuration/block/uservolumeconfig" >}}) to the machine configuration: