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 <andrey.smirnov@siderolabs.com>
This commit is contained in:
Andrey Smirnov 2025-04-17 19:51:49 +04:00
parent 9616f6e8d2
commit ae94377d15
No known key found for this signature in database
GPG Key ID: FE042E3D4085A811
22 changed files with 1505 additions and 86 deletions

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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])
}

View File

@ -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 {

View File

@ -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.

View File

@ -87,6 +87,7 @@ type UserVolumeConfig interface {
UserVolumeConfigSignal()
Provisioning() VolumeProvisioningConfig
Filesystem() FilesystemConfig
Encryption() EncryptionConfig
}
// FilesystemConfig defines the interface to access filesystem configuration.

View File

@ -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,

View File

@ -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 .

View File

@ -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(),

View File

@ -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
}

View File

@ -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"
}

View File

@ -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

View File

@ -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: |

View File

@ -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
},
},

View File

@ -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 {

View File

@ -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

View File

@ -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=

View File

@ -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 <hostPort>:<containerPort>/<protocol (tcp or udp)> (Docker provisioner only)
--extra-boot-kernel-args string add extra kernel args to the initial boot from vmlinuz and initramfs (QEMU only)

View File

@ -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 |<details><summary>Name of the volume.</summary><br />Name might be between 1 and 34 characters long and can only contain:<br />lowercase and uppercase ASCII letters, digits, and hyphens.</details> | |
|`provisioning` |<a href="#UserVolumeConfig.provisioning">ProvisioningSpec</a> |The provisioning describes how the volume is provisioned. | |
|`filesystem` |<a href="#UserVolumeConfig.filesystem">FilesystemSpec</a> |The filesystem describes how the volume is formatted. | |
|`encryption` |<a href="#UserVolumeConfig.encryption">EncryptionSpec</a> |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`<br /> |
|`keys` |<a href="#UserVolumeConfig.encryption.keys.">[]EncryptionKey</a> |Defines the encryption keys generation and storage method. | |
|`cipher` |string |Cipher to use for the encryption. Depends on the encryption provider. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
cipher: aes-xts-plain64
{{< /highlight >}}</details> |`aes-xts-plain64`<br />`xchacha12,aes-adiantum-plain64`<br />`xchacha20,aes-adiantum-plain64`<br /> |
|`keySize` |uint |Defines the encryption key length. | |
|`blockSize` |uint64 |Defines the encryption sector size. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
blockSize: 4096
{{< /highlight >}}</details> | |
|`options` |[]string |Additional --perf parameters for the LUKS2 encryption. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
options:
- no_read_workqueue
- no_write_workqueue
{{< /highlight >}}</details> |`no_read_workqueue`<br />`no_write_workqueue`<br />`same_cpu_crypt`<br /> |
### 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` |<a href="#UserVolumeConfig.encryption.keys..static">EncryptionKeyStatic</a> |Key which value is stored in the configuration file. | |
|`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. | |
#### 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 |<details><summary>Check that Secureboot is enabled in the EFI firmware.</summary>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.</details> | |

View File

@ -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,

View File

@ -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-<volume-name>`, and it is used to identify the volume on the disk after initial provisioning.
The volume mount location is `/var/mnt/<volume-name>`, 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: