feat: add KMS assisted encryption key handler

Talos now supports new type of encryption keys which rely on Sealing/Unsealing randomly generated bytes with a KMS server:

```
systemDiskEncryption:
  ephemeral:
    keys:
      - kms:
          endpoint: https://1.2.3.4:443
        slot: 0
```
gRPC API definitions and a simple reference implementation of the KMS server can be found in this
[repository](https://github.com/siderolabs/kms-client/blob/main/cmd/kms-server/main.go).

Signed-off-by: Artem Chernyshev <artem.chernyshev@talos-systems.com>
This commit is contained in:
Artem Chernyshev
2023-06-29 23:54:09 +03:00
parent dafbe9debd
commit ce63abb219
62 changed files with 1224 additions and 366 deletions

Binary file not shown.

View File

@ -64,6 +64,7 @@ message MountStatusSpec {
string target = 2;
string filesystem_type = 3;
repeated string options = 4;
bool encrypted = 5;
}
// PlatformMetadataSpec describes platform metadata properties.

View File

@ -36,7 +36,7 @@ var imageCmd = &cobra.Command{
Short: "",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
if err := runImageCmd(); err != nil {
if err := runImageCmd(cmd.Context()); err != nil {
log.Fatal(err)
}
},
@ -49,7 +49,7 @@ func init() {
}
//nolint:gocyclo
func runImageCmd() (err error) {
func runImageCmd(ctx context.Context) (err error) {
p, err := platform.NewPlatform(options.Platform)
if err != nil {
return err
@ -90,7 +90,7 @@ func runImageCmd() (err error) {
}
}
if err = install.Install(p, runtime.SequenceNoop, options); err != nil {
if err = install.Install(ctx, p, runtime.SequenceNoop, options); err != nil {
return err
}

View File

@ -5,6 +5,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"log"
@ -24,7 +25,7 @@ var installCmd = &cobra.Command{
Short: "",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) (err error) {
return runInstallCmd()
return runInstallCmd(cmd.Context())
},
}
@ -32,7 +33,7 @@ func init() {
rootCmd.AddCommand(installCmd)
}
func runInstallCmd() (err error) {
func runInstallCmd(ctx context.Context) (err error) {
log.Printf("running Talos installer %s", version.NewVersion().Tag)
seq := runtime.SequenceInstall
@ -74,5 +75,5 @@ func runInstallCmd() (err error) {
}
}
return install.Install(p, seq, options)
return install.Install(ctx, p, seq, options)
}

View File

@ -40,7 +40,7 @@ type Options struct {
}
// Install installs Talos.
func Install(p runtime.Platform, seq runtime.Sequence, opts *Options) (err error) {
func Install(ctx context.Context, p runtime.Platform, seq runtime.Sequence, opts *Options) (err error) {
cmdline := procfs.NewCmdline("")
cmdline.Append(constants.KernelParamPlatform, p.Name())
@ -63,12 +63,12 @@ func Install(p runtime.Platform, seq runtime.Sequence, opts *Options) (err error
return err
}
i, err := NewInstaller(cmdline, seq, opts)
i, err := NewInstaller(ctx, cmdline, seq, opts)
if err != nil {
return err
}
if err = i.Install(seq); err != nil {
if err = i.Install(ctx, seq); err != nil {
return err
}
@ -87,14 +87,14 @@ type Installer struct {
}
// NewInstaller initializes and returns an Installer.
func NewInstaller(cmdline *procfs.Cmdline, seq runtime.Sequence, opts *Options) (i *Installer, err error) {
func NewInstaller(ctx context.Context, cmdline *procfs.Cmdline, seq runtime.Sequence, opts *Options) (i *Installer, err error) {
i = &Installer{
cmdline: cmdline,
options: opts,
}
if !i.options.Zero {
i.bootloader, err = bootloader.Probe(i.options.Disk)
i.bootloader, err = bootloader.Probe(ctx, i.options.Disk)
if err != nil && !os.IsNotExist(err) {
return nil, fmt.Errorf("failed to probe bootloader: %w", err)
}
@ -121,7 +121,7 @@ func NewInstaller(cmdline *procfs.Cmdline, seq runtime.Sequence, opts *Options)
// to the target locations.
//
//nolint:gocyclo,cyclop
func (i *Installer) Install(seq runtime.Sequence) (err error) {
func (i *Installer) Install(ctx context.Context, seq runtime.Sequence) (err error) {
errataBTF()
if seq == runtime.SequenceUpgrade {
@ -196,7 +196,7 @@ func (i *Installer) Install(seq runtime.Sequence) (err error) {
var mountpoint *mount.Point
mountpoint, err = mount.SystemMountPointForLabel(bd, label)
mountpoint, err = mount.SystemMountPointForLabel(ctx, bd, label)
if err != nil {
return err
}

View File

@ -6,6 +6,7 @@ package install
import (
"bufio"
"context"
"errors"
"fmt"
"log"
@ -496,11 +497,11 @@ func (m *Manifest) restoreContents(targets []*Target) error {
}
// SystemMountpoints returns list of system mountpoints for the manifest.
func (m *Manifest) SystemMountpoints(opts ...mount.Option) (*mount.Points, error) {
func (m *Manifest) SystemMountpoints(ctx context.Context, opts ...mount.Option) (*mount.Points, error) {
mountpoints := mount.NewMountPoints()
for dev := range m.Targets {
mp, err := mount.SystemMountPointsForDevice(dev, opts...)
mp, err := mount.SystemMountPointsForDevice(ctx, dev, opts...)
if err != nil {
return nil, err
}

View File

@ -5,6 +5,7 @@
package install_test
import (
"context"
"fmt"
"io"
"os"
@ -98,6 +99,9 @@ func (suite *manifestSuite) skipIfNotRoot() {
}
func (suite *manifestSuite) verifyBlockdevice(manifest *install.Manifest, current, next string, verifyConfigPersistence, verifyEphemeralPersistence bool) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
bd, err := blockdevice.Open(suite.loopbackDevice.Name())
suite.Require().NoError(err)
@ -151,7 +155,7 @@ func (suite *manifestSuite) verifyBlockdevice(manifest *install.Manifest, curren
// query mount points directly for the device
mountpoints, err := mount.SystemMountPointsForDevice(suite.loopbackDevice.Name())
mountpoints, err := mount.SystemMountPointsForDevice(ctx, suite.loopbackDevice.Name())
suite.Require().NoError(err)
suite.Assert().Equal(4, mountpoints.Len())
@ -160,7 +164,7 @@ func (suite *manifestSuite) verifyBlockdevice(manifest *install.Manifest, curren
tempDir := suite.T().TempDir()
mountpoints, err = manifest.SystemMountpoints()
mountpoints, err = manifest.SystemMountpoints(ctx)
suite.Require().NoError(err)
suite.Assert().Equal(4, mountpoints.Len())

View File

@ -84,6 +84,7 @@ const (
apiServerBalancerPortFlag = "api-server-balancer-port"
tpm2EnabledFlag = "with-tpm2"
secureBootEnabledFlag = "with-secureboot"
diskEncryptionKeyTypesFlag = "disk-encryption-key-types"
)
var (
@ -161,6 +162,7 @@ var (
packetReorder float64
packetCorrupt float64
bandwidth int
diskEncryptionKeyTypes []string
)
// createCmd represents the cluster up command.
@ -431,27 +433,54 @@ func create(ctx context.Context, flags *pflag.FlagSet) (err error) {
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
}
port := 4050
keys = append(keys, &v1alpha1.EncryptionKey{
KeyKMS: &v1alpha1.EncryptionKeyKMS{
KMSEndpoint: "http://" + nethelpers.JoinHostPort(ip.String(), port),
},
KeySlot: i,
})
provisionOptions = append(provisionOptions, provision.WithKMS(nethelpers.JoinHostPort("0.0.0.0", port)))
default:
return fmt.Errorf("unknown key type %q", key)
}
}
if len(keys) == 0 {
return fmt.Errorf("no disk encryption key types enabled")
}
if encryptStatePartition {
diskEncryptionConfig.StatePartition = &v1alpha1.EncryptionConfig{
EncryptionProvider: encryption.LUKS2,
EncryptionKeys: []*v1alpha1.EncryptionKey{
{
KeyNodeID: &v1alpha1.EncryptionKeyNodeID{},
KeySlot: 0,
},
},
EncryptionKeys: keys,
}
}
if encryptEphemeralPartition {
diskEncryptionConfig.EphemeralPartition = &v1alpha1.EncryptionConfig{
EncryptionProvider: encryption.LUKS2,
EncryptionKeys: []*v1alpha1.EncryptionKey{
{
KeyNodeID: &v1alpha1.EncryptionKeyNodeID{},
KeySlot: 0,
},
},
EncryptionKeys: keys,
}
}
@ -958,6 +987,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().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")
createCmd.Flags().BoolVar(&enableClusterDiscovery, withClusterDiscoveryFlag, true, "enable cluster discovery")

View File

@ -0,0 +1,86 @@
// 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 mgmt
import (
"context"
"errors"
"fmt"
"log"
"net"
"github.com/siderolabs/kms-client/api/kms"
"github.com/siderolabs/kms-client/pkg/server"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
grpclog "github.com/siderolabs/talos/pkg/grpc/middleware/log"
)
var kmsLaunchCmdFlags struct {
addr string
key []byte
}
// kmsLaunchCmd represents the kms-launch command.
var kmsLaunchCmd = &cobra.Command{
Use: "kms-launch",
Short: "Internal command used by QEMU provisioner",
Long: ``,
Args: cobra.NoArgs,
Hidden: true,
RunE: func(cmd *cobra.Command, args []string) error {
if kmsLaunchCmdFlags.key == nil {
return fmt.Errorf("no key provided to the KMS server")
}
srv := server.NewServer(func(_ context.Context, nodeUUID string) ([]byte, error) {
return kmsLaunchCmdFlags.key, nil
})
lis, err := net.Listen("tcp", kmsLaunchCmdFlags.addr)
if err != nil {
return err
}
log.Printf("starting KMS server on %s", kmsLaunchCmdFlags.addr)
logMiddleware := grpclog.NewMiddleware(log.New(log.Writer(), "", log.Flags()))
s := grpc.NewServer(
grpc.UnaryInterceptor(logMiddleware.UnaryInterceptor()),
grpc.StreamInterceptor(logMiddleware.StreamInterceptor()),
)
kms.RegisterKMSServiceServer(s, srv)
eg, ctx := errgroup.WithContext(cmd.Context())
eg.Go(func() error {
err := s.Serve(lis)
if errors.Is(err, context.Canceled) {
return nil
}
return err
})
eg.Go(func() error {
<-ctx.Done()
s.Stop()
return nil
})
return s.Serve(lis)
},
}
func init() {
kmsLaunchCmd.Flags().StringVar(&kmsLaunchCmdFlags.addr, "kms-addr", "localhost", "KMS listen address (IP or host)")
kmsLaunchCmd.Flags().BytesBase64Var(&kmsLaunchCmdFlags.key, "kms-key", nil, "KMS key to use")
addCommand(kmsLaunchCmd)
}

3
go.mod
View File

@ -92,7 +92,7 @@ require (
github.com/siderolabs/discovery-api v0.1.3
github.com/siderolabs/discovery-client v0.1.5
github.com/siderolabs/gen v0.4.5
github.com/siderolabs/go-blockdevice v0.4.5
github.com/siderolabs/go-blockdevice v0.4.6
github.com/siderolabs/go-circular v0.1.0
github.com/siderolabs/go-cmd v0.1.1
github.com/siderolabs/go-debug v0.2.2
@ -107,6 +107,7 @@ require (
github.com/siderolabs/go-smbios v0.3.2
github.com/siderolabs/go-tail v0.1.0
github.com/siderolabs/grpc-proxy v0.4.0
github.com/siderolabs/kms-client v0.1.0
github.com/siderolabs/net v0.4.0
github.com/siderolabs/siderolink v0.3.1
github.com/siderolabs/talos/pkg/machinery v1.5.0-alpha.1

6
go.sum
View File

@ -1153,8 +1153,8 @@ github.com/siderolabs/gen v0.4.5 h1:rwXUVJlL7hYza1LrSVXfT905ZC9Rgei37jMKKs/+eP0=
github.com/siderolabs/gen v0.4.5/go.mod h1:wS8tFq7sn5vqKAuyS30vJUig3tX5v6q79VG4KfUnILM=
github.com/siderolabs/go-api-signature v0.2.4 h1:s+K0GtaPHql4LdZzL72QvwPMzffY+KB0mszORDK+5/w=
github.com/siderolabs/go-api-signature v0.2.4/go.mod h1:rLIKzbDhKewI3MdztyntS1apH6EC8ccU2TF5S0/O2yg=
github.com/siderolabs/go-blockdevice v0.4.5 h1:NgpR9XTl/N7WeL59QHBsseDD0Nb8Y2nel+W3u7xHIvY=
github.com/siderolabs/go-blockdevice v0.4.5/go.mod h1:4PeOuk71pReJj1JQEXDE7kIIQJPVe8a+HZQa+qjxSEA=
github.com/siderolabs/go-blockdevice v0.4.6 h1:yfxFYzXezzszB0mSF2ZG8jPPampoNXa9r8W8nM0IoZI=
github.com/siderolabs/go-blockdevice v0.4.6/go.mod h1:4PeOuk71pReJj1JQEXDE7kIIQJPVe8a+HZQa+qjxSEA=
github.com/siderolabs/go-circular v0.1.0 h1:zpBJNUbCZSh0odZxA4Dcj0d3ShLLR2WxKW6hTdAtoiE=
github.com/siderolabs/go-circular v0.1.0/go.mod h1:14XnLf/I3J0VjzTgmwWNGjp58/bdIi4zXppAEx8plfw=
github.com/siderolabs/go-cmd v0.1.1 h1:nTouZUSxLeiiEe7hFexSVvaTsY/3O8k1s08BxPRrsps=
@ -1183,6 +1183,8 @@ github.com/siderolabs/go-tail v0.1.0 h1:U+ZClt7BXLGsxDNU/XQ12sz7lQElfFZBYEPdkW78
github.com/siderolabs/go-tail v0.1.0/go.mod h1:vWxumnRUS3eTZczORCJW3QMjxiTETN31vyuFdaW8rPw=
github.com/siderolabs/grpc-proxy v0.4.0 h1:zYrhqLYs8JlYoLHYeel7/XwXDZ4OJ5XyP9wX7JlbPew=
github.com/siderolabs/grpc-proxy v0.4.0/go.mod h1:QDurYOwQD4H8BKyvCuUxMiuG/etYnb/++xaQB644NdU=
github.com/siderolabs/kms-client v0.1.0 h1:rCDWzcDDsNlp6zdyLngOuuhchVILn+vwUQy3tk6rQps=
github.com/siderolabs/kms-client v0.1.0/go.mod h1:4UQkRhuEh3kaK7VhJxez4YyJLv6lPEff7g3Pa6Y9okg=
github.com/siderolabs/net v0.4.0 h1:1bOgVay/ijPkJz4qct98nHsiB/ysLQU0KLoBC4qLm7I=
github.com/siderolabs/net v0.4.0/go.mod h1:/ibG+Hm9HU27agp5r9Q3eZicEfjquzNzQNux5uEk0kM=
github.com/siderolabs/protoenc v0.2.0 h1:QFxWIAo//12+/bm27GNYoK/TpQGTYsRrrZCu9jSghvU=

View File

@ -102,6 +102,23 @@ Talos now supports generating a custom iso that can be used with SecureBoot. Key
description="""\
Talos now supports setting `environmentFile` for an extension service container spec. Refer: https://www.talos.dev/v1.5/advanced/extension-services/#container
The extension waits for the file to be present before starting the service.
"""
[notes.kms-encryption-keys]
title = "Network KMS Disk Encryption"
description="""\
Talos now supports new type of encryption keys which are sealed/unsealed with an external KMS server:
```
systemDiskEncryption:
ephemeral:
keys:
- kms:
endpoint: https://1.2.3.4:443
slot: 0
```
gRPC API definitions and a simple reference implementation of the KMS server can be found in this
[repository](https://github.com/siderolabs/kms-client/blob/main/cmd/kms-server/main.go).
"""
[make_deps]

View File

@ -100,7 +100,7 @@ case "${WITH_DISK_ENCRYPTION:-false}" in
false)
;;
*)
QEMU_FLAGS+=("--encrypt-ephemeral" "--encrypt-state")
QEMU_FLAGS+=("--encrypt-ephemeral" "--encrypt-state" "--disk-encryption-key-types=kms")
;;
esac

View File

@ -343,12 +343,12 @@ func (s *Server) Rollback(ctx context.Context, in *machine.RollbackRequest) (*ma
}
if err := func() error {
config, err := bootloader.Probe(systemDisk.Device().Name())
config, err := bootloader.Probe(ctx, systemDisk.Device().Name())
if err != nil {
return err
}
return config.Revert()
return config.Revert(ctx)
}(); err != nil {
return nil, err
}

View File

@ -47,7 +47,7 @@ func init() {
http.DefaultClient.Transport = httpdefaults.PatchTransport(cleanhttp.DefaultPooledTransport())
}
func recovery() {
func recovery(ctx context.Context) {
if r := recover(); r != nil {
var (
err error
@ -56,7 +56,7 @@ func recovery() {
err, ok = r.(error)
if ok {
handle(err)
handle(ctx, err)
}
}
}
@ -94,7 +94,7 @@ func syncNonVolatileStorageBuffers() {
}
//nolint:gocyclo
func handle(err error) {
func handle(ctx context.Context, err error) {
rebootCmd := unix.LINUX_REBOOT_CMD_RESTART
var rebootErr runtime.RebootError
@ -108,7 +108,7 @@ func handle(err error) {
if err != nil {
log.Print(err)
revertBootloader()
revertBootloader(ctx)
if p := procfs.ProcCmdline().Get(constants.KernelParamPanic).First(); p != nil {
if *p == "0" {
@ -287,6 +287,9 @@ func run() error {
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
switch filepath.Base(os.Args[0]) {
case "apid":
apid.Main()
@ -313,11 +316,11 @@ func main() {
}
// Setup panic handler.
defer recovery()
defer recovery(ctx)
// Initialize the process reaper.
reaper.Run()
defer reaper.Shutdown()
handle(run())
handle(ctx, run())
}

View File

@ -11,9 +11,7 @@ import (
"errors"
"fmt"
"net/netip"
"net/url"
"os"
"regexp"
"time"
"github.com/cosi-project/runtime/pkg/controller"
@ -31,6 +29,7 @@ import (
"google.golang.org/grpc/credentials/insecure"
networkutils "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/network/utils"
"github.com/siderolabs/talos/internal/pkg/endpoint"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/nethelpers"
"github.com/siderolabs/talos/pkg/machinery/resources/config"
@ -68,44 +67,6 @@ func (ctrl *ManagerController) Outputs() []controller.Output {
}
}
var urlSchemeMatcher = regexp.MustCompile(`[a-zA-z]+://`)
type apiEndpoint struct {
Host string
Insecure bool
JoinToken *string
}
// parseAPIEndpoint parses the siderolink.api kernel parameter.
func parseAPIEndpoint(sideroLinkParam string) (apiEndpoint, error) {
if !urlSchemeMatcher.MatchString(sideroLinkParam) {
sideroLinkParam = "grpc://" + sideroLinkParam
}
u, err := url.Parse(sideroLinkParam)
if err != nil {
return apiEndpoint{}, err
}
result := apiEndpoint{
Host: u.Host,
Insecure: u.Scheme == "grpc",
}
if u.Port() == "" && u.Scheme == "https" {
result.Host += ":443"
}
params := u.Query()
joinTokenStr, ok := params["jointoken"]
if ok && len(joinTokenStr) > 0 {
result.JoinToken = &joinTokenStr[0]
}
return result, nil
}
// Run implements controller.Controller interface.
//
//nolint:gocyclo,cyclop
@ -201,9 +162,9 @@ func (ctrl *ManagerController) Run(ctx context.Context, r controller.Runtime, lo
}
nodeUUID := sysInfo.TypedSpec().UUID
endpoint := cfg.TypedSpec().APIEndpoint
stringEndpoint := cfg.TypedSpec().APIEndpoint
parsedEndpoint, err := parseAPIEndpoint(endpoint)
parsedEndpoint, err := endpoint.Parse(stringEndpoint)
if err != nil {
return fmt.Errorf("failed to parse siderolink endpoint: %w", err)
}
@ -222,7 +183,7 @@ func (ctrl *ManagerController) Run(ctx context.Context, r controller.Runtime, lo
conn, connErr := grpc.DialContext(connCtx, parsedEndpoint.Host, grpc.WithTransportCredentials(transportCredentials))
if connErr != nil {
return nil, fmt.Errorf("error dialing SideroLink endpoint %q: %w", endpoint, connErr)
return nil, fmt.Errorf("error dialing SideroLink endpoint %q: %w", stringEndpoint, connErr)
}
defer func() {
@ -232,12 +193,18 @@ func (ctrl *ManagerController) Run(ctx context.Context, r controller.Runtime, lo
}()
sideroLinkClient := pb.NewProvisionServiceClient(conn)
return sideroLinkClient.Provision(ctx, &pb.ProvisionRequest{
request := &pb.ProvisionRequest{
NodeUuid: nodeUUID,
NodePublicKey: ctrl.nodeKey.PublicKey().String(),
JoinToken: parsedEndpoint.JoinToken,
})
}
token := parsedEndpoint.GetParam("jointoken")
if token != "" {
request.JoinToken = pointer.To(token)
}
return sideroLinkClient.Provision(ctx, request)
}
resp, err := provision()
@ -322,7 +289,7 @@ func (ctrl *ManagerController) Run(ctx context.Context, r controller.Runtime, lo
logger.Info(
"siderolink connection configured",
zap.String("endpoint", endpoint),
zap.String("endpoint", stringEndpoint),
zap.String("node_uuid", nodeUUID),
zap.String("node_address", nodeAddress.String()),
)

View File

@ -17,7 +17,6 @@ import (
"github.com/siderolabs/go-procfs/procfs"
"github.com/siderolabs/go-retry/retry"
pb "github.com/siderolabs/siderolink/api/siderolink"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"google.golang.org/grpc"
@ -169,83 +168,3 @@ func (suite *ManagerSuite) TestReconcile() {
return nil
})
}
func TestParseJoinToken(t *testing.T) {
t.Run("parses a join token from a complete URL without error", func(t *testing.T) {
// when
endpoint, err := siderolinkctrl.ParseAPIEndpoint("grpc://10.5.0.2:3445?jointoken=ttt")
// then
assert.NoError(t, err)
assert.Equal(t, siderolinkctrl.APIEndpoint{
Host: "10.5.0.2:3445",
Insecure: true,
JoinToken: pointer.To("ttt"),
}, endpoint)
})
t.Run("parses a join token from a secure URL without error", func(t *testing.T) {
// when
endpoint, err := siderolinkctrl.ParseAPIEndpoint("https://10.5.0.2:3445?jointoken=ttt&jointoken=xxx")
// then
assert.NoError(t, err)
assert.Equal(t, siderolinkctrl.APIEndpoint{
Host: "10.5.0.2:3445",
Insecure: false,
JoinToken: pointer.To("ttt"),
}, endpoint)
})
t.Run("parses a join token from a secure URL without port", func(t *testing.T) {
// when
endpoint, err := siderolinkctrl.ParseAPIEndpoint("https://10.5.0.2?jointoken=ttt&jointoken=xxx")
// then
assert.NoError(t, err)
assert.Equal(t, siderolinkctrl.APIEndpoint{
Host: "10.5.0.2:443",
Insecure: false,
JoinToken: pointer.To("ttt"),
}, endpoint)
})
t.Run("parses a join token from an URL without a scheme", func(t *testing.T) {
// when
endpoint, err := siderolinkctrl.ParseAPIEndpoint("10.5.0.2:3445?jointoken=ttt")
// then
assert.NoError(t, err)
assert.Equal(t, siderolinkctrl.APIEndpoint{
Host: "10.5.0.2:3445",
Insecure: true,
JoinToken: pointer.To("ttt"),
}, endpoint)
})
t.Run("does not error if there is no join token in a complete URL", func(t *testing.T) {
// when
endpoint, err := siderolinkctrl.ParseAPIEndpoint("grpc://10.5.0.2:3445")
// then
assert.NoError(t, err)
assert.Equal(t, siderolinkctrl.APIEndpoint{
Host: "10.5.0.2:3445",
Insecure: true,
JoinToken: nil,
}, endpoint)
})
t.Run("does not error if there is no join token in an URL without a scheme", func(t *testing.T) {
// when
endpoint, err := siderolinkctrl.ParseAPIEndpoint("10.5.0.2:3445")
// then
assert.NoError(t, err)
assert.Equal(t, siderolinkctrl.APIEndpoint{
Host: "10.5.0.2:3445",
Insecure: true,
JoinToken: nil,
}, endpoint)
})
}

View File

@ -5,9 +5,11 @@
package runtime
import (
"context"
"time"
"github.com/siderolabs/talos/pkg/machinery/config"
"github.com/siderolabs/talos/pkg/machinery/resources/hardware"
)
// Runtime defines the runtime parameters.
@ -24,4 +26,5 @@ type Runtime interface { //nolint:interfacebloat
Logging() LoggingManager
NodeName() (string, error)
IsBootstrapAllowed() bool
GetSystemInformation(ctx context.Context) (*hardware.SystemInformation, error)
}

View File

@ -6,6 +6,7 @@
package bootloader
import (
"context"
"os"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub"
@ -17,7 +18,7 @@ type Bootloader interface {
// Install installs the bootloader
Install(bootDisk, arch, cmdline string) error
// Revert reverts the bootloader entry to the previous state.
Revert() error
Revert(ctx context.Context) error
// PreviousLabel returns the previous bootloader label.
PreviousLabel() string
// UEFIBoot returns true if the bootloader is UEFI-only.
@ -28,8 +29,8 @@ type Bootloader interface {
//
// If 'disk' is empty, it will probe all disks.
// Returns nil if it cannot detect any supported bootloader.
func Probe(disk string) (Bootloader, error) {
grubBootloader, err := grub.Probe(disk)
func Probe(ctx context.Context, disk string) (Bootloader, error) {
grubBootloader, err := grub.Probe(ctx, disk)
if err != nil {
return nil, err
}
@ -38,7 +39,7 @@ func Probe(disk string) (Bootloader, error) {
return grubBootloader, nil
}
sdbootBootloader, err := sdboot.Probe(disk)
sdbootBootloader, err := sdboot.Probe(ctx, disk)
if err != nil {
return nil, err
}

View File

@ -6,6 +6,8 @@
package grub
import (
"context"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/mount"
"github.com/siderolabs/talos/pkg/machinery/constants"
)
@ -15,10 +17,10 @@ import (
// If the 'disk' is passed, search happens on that disk only, otherwise searches all partitions.
//
//nolint:gocyclo
func Probe(disk string) (*Config, error) {
func Probe(ctx context.Context, disk string) (*Config, error) {
var grubConf *Config
if err := mount.PartitionOp(disk, constants.BootPartitionLabel, func() error {
if err := mount.PartitionOp(ctx, disk, constants.BootPartitionLabel, func() error {
var err error
grubConf, err = Read(ConfigPath)

View File

@ -6,6 +6,7 @@
package grub
import (
"context"
"errors"
"fmt"
"os"
@ -19,7 +20,7 @@ import (
// Revert reverts the bootloader to the previous version.
// nolint:gocyclo
func (c *Config) Revert() error {
func (c *Config) Revert(ctx context.Context) error {
if c == nil {
return fmt.Errorf("cannot revert bootloader: %w", bootloaderNotInstalledError{})
}
@ -41,7 +42,7 @@ func (c *Config) Revert() error {
defer dev.Close() //nolint:errcheck
mp, err := mount.SystemMountPointForLabel(dev.BlockDevice, constants.BootPartitionLabel)
mp, err := mount.SystemMountPointForLabel(ctx, dev.BlockDevice, constants.BootPartitionLabel)
if err != nil {
return err
}

View File

@ -6,6 +6,7 @@
package mount
import (
"context"
"errors"
"fmt"
"os"
@ -18,7 +19,7 @@ import (
// PartitionOp mounts a partition with the specified label, executes the operation func, and unmounts the partition.
// nolint:gocyclo
func PartitionOp(disk string, partitionLabel string, opFunc func() error) error {
func PartitionOp(ctx context.Context, disk string, partitionLabel string, opFunc func() error) error {
var probedBlockDevice *blockdevice.BlockDevice
switch {
@ -57,7 +58,7 @@ func PartitionOp(disk string, partitionLabel string, opFunc func() error) error
probedBlockDevice = dev.BlockDevice
}
mp, err := mount.SystemMountPointForLabel(probedBlockDevice, partitionLabel, mount.WithFlags(mount.ReadOnly))
mp, err := mount.SystemMountPointForLabel(ctx, probedBlockDevice, partitionLabel, mount.WithFlags(mount.ReadOnly))
if err != nil {
return err
}

View File

@ -6,6 +6,7 @@
package sdboot
import (
"context"
"fmt"
"log"
"os"
@ -49,7 +50,7 @@ func New() *Config {
// Probe for existing sd-boot bootloader.
// nolint:gocyclo
func Probe(disk string) (*Config, error) {
func Probe(ctx context.Context, disk string) (*Config, error) {
// if not UEFI boot, nothing to do
if !isUEFIBoot() {
return nil, nil
@ -61,7 +62,7 @@ func Probe(disk string) (*Config, error) {
// read /boot/EFI and find if sd-boot is already being used
// this is to make sure sd-boot from Talos is being used and not sd-boot from another distro
if err := mount.PartitionOp(disk, constants.EFIPartitionLabel, func() error {
if err := mount.PartitionOp(ctx, disk, constants.EFIPartitionLabel, func() error {
// list existing boot*.efi files in boot folder
files, err := filepath.Glob(filepath.Join(constants.EFIMountPoint, "EFI", "boot", "BOOT*.efi"))
if err != nil {
@ -91,7 +92,7 @@ func Probe(disk string) (*Config, error) {
log.Printf("booted entry: %q", bootedEntry)
if opErr := mount.PartitionOp(disk, constants.EFIPartitionLabel, func() error {
if opErr := mount.PartitionOp(ctx, disk, constants.EFIPartitionLabel, func() error {
// list existing UKIs, and check if the current one is present
files, err := filepath.Glob(filepath.Join(constants.EFIMountPoint, "EFI", "Linux", "Talos-*.efi"))
if err != nil {
@ -208,8 +209,8 @@ func (c *Config) PreviousLabel() string {
}
// Revert the bootloader to the previous version.
func (c *Config) Revert() error {
if err := mount.PartitionOp("", constants.EFIPartitionLabel, func() error {
func (c *Config) Revert(ctx context.Context) error {
if err := mount.PartitionOp(ctx, "", constants.EFIPartitionLabel, func() error {
// use c.Default as the current entry, list other UKIs, find the one which is not c.Default, and update EFI var
files, err := filepath.Glob(filepath.Join(constants.EFIMountPoint, "EFI", "Linux", "Talos-*.efi"))
if err != nil {

View File

@ -14,6 +14,8 @@ import (
"time"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/cosi-project/runtime/pkg/state"
"github.com/google/go-cmp/cmp"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
@ -22,6 +24,7 @@ import (
"github.com/siderolabs/talos/pkg/machinery/config"
"github.com/siderolabs/talos/pkg/machinery/config/configloader"
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
"github.com/siderolabs/talos/pkg/machinery/resources/hardware"
"github.com/siderolabs/talos/pkg/machinery/resources/k8s"
)
@ -222,6 +225,16 @@ func (r *Runtime) IsBootstrapAllowed() bool {
return true
}
// GetSystemInformation returns system information resource if it exists.
func (r *Runtime) GetSystemInformation(ctx context.Context) (*hardware.SystemInformation, error) {
ctx, cancel := context.WithTimeout(ctx, time.Second*2)
defer cancel()
return safe.StateWatchFor[*hardware.SystemInformation](ctx, r.State().V1Alpha2().Resources(), hardware.NewSystemInformation(hardware.SystemInformationID).Metadata(),
state.WithEventTypes(state.Created, state.Updated),
)
}
// atomicInterface is a typed wrapper around atomic.Value. It's only useful for storing the interfaces, because
// you don't need another layer of indirection (unlike atomic.Pointer[T]) to load the value. For concrete types
// please use atomic.Pointer.

View File

@ -2014,7 +2014,7 @@ func SaveStateEncryptionConfig(runtime.Sequence, any) (runtime.TaskExecutionFunc
// MountEFIPartition mounts the EFI partition.
func MountEFIPartition(runtime.Sequence, any) (runtime.TaskExecutionFunc, string) {
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
return mount.SystemPartitionMount(r, logger, constants.EFIPartitionLabel)
return mount.SystemPartitionMount(ctx, r, logger, constants.EFIPartitionLabel)
}, "mountEFIPartition"
}
@ -2061,7 +2061,7 @@ func MountStatePartition(seq runtime.Sequence, _ any) (runtime.TaskExecutionFunc
opts = append(opts, mount.WithEncryptionConfig(encryption))
}
return mount.SystemPartitionMount(r, logger, constants.StatePartitionLabel, opts...)
return mount.SystemPartitionMount(ctx, r, logger, constants.StatePartitionLabel, opts...)
}, "mountStatePartition"
}
@ -2075,7 +2075,7 @@ func UnmountStatePartition(runtime.Sequence, any) (runtime.TaskExecutionFunc, st
// MountEphemeralPartition mounts the ephemeral partition.
func MountEphemeralPartition(runtime.Sequence, any) (runtime.TaskExecutionFunc, string) {
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) error {
return mount.SystemPartitionMount(r, logger, constants.EphemeralPartitionLabel,
return mount.SystemPartitionMount(ctx, r, logger, constants.EphemeralPartitionLabel,
mount.WithFlags(mount.Resize),
mount.WithProjectQuota(r.Config().Machine().Features().DiskQuotaSupportEnabled()))
}, "mountEphemeralPartition"
@ -2228,7 +2228,7 @@ func KexecPrepare(_ runtime.Sequence, data any) (runtime.TaskExecutionFunc, stri
}
// BOOT partition exists and we can mount it
if err := mount.SystemPartitionMount(r, logger, constants.BootPartitionLabel); err != nil {
if err := mount.SystemPartitionMount(ctx, r, logger, constants.BootPartitionLabel); err != nil {
return err
}

View File

@ -13,15 +13,15 @@ import (
"github.com/siderolabs/talos/internal/pkg/meta"
)
func revertBootloader() {
if err := revertBootloadInternal(); err != nil {
func revertBootloader(ctx context.Context) {
if err := revertBootloadInternal(ctx); err != nil {
log.Printf("failed to revert bootloader: %s", err)
}
}
//nolint:gocyclo
func revertBootloadInternal() error {
metaState, err := meta.New(context.Background(), nil)
func revertBootloadInternal(ctx context.Context) error {
metaState, err := meta.New(ctx, nil)
if err != nil {
if os.IsNotExist(err) {
// no META, no way to revert
@ -37,7 +37,7 @@ func revertBootloadInternal() error {
}
if label == "" {
if _, err = metaState.DeleteTag(context.Background(), meta.Upgrade); err != nil {
if _, err = metaState.DeleteTag(ctx, meta.Upgrade); err != nil {
return err
}
@ -47,7 +47,7 @@ func revertBootloadInternal() error {
log.Printf("reverting failed upgrade, switching to %q", label)
if err = func() error {
config, probeErr := bootloader.Probe("")
config, probeErr := bootloader.Probe(ctx, "")
if probeErr != nil {
if os.IsNotExist(probeErr) {
// no bootloader found, nothing to do
@ -57,12 +57,12 @@ func revertBootloadInternal() error {
return probeErr
}
return config.Revert()
return config.Revert(ctx)
}(); err != nil {
return err
}
if _, err = metaState.DeleteTag(context.Background(), meta.Upgrade); err != nil {
if _, err = metaState.DeleteTag(ctx, meta.Upgrade); err != nil {
return err
}

View File

@ -262,17 +262,17 @@ func (suite *ApplyConfigSuite) TestApplyConfigRotateEncryptionSecrets() {
suite.WaitForBootDone(suite.ctx)
existing := cfg.EncryptionKeys[0]
slot := existing.Slot() + 1
keySets := [][]*v1alpha1.EncryptionKey{
{
{
KeyNodeID: &v1alpha1.EncryptionKeyNodeID{},
KeySlot: 0,
},
existing,
{
KeyStatic: &v1alpha1.EncryptionKeyStatic{
KeyData: "AlO93jayutOpsDxDS=-",
},
KeySlot: 1,
KeySlot: slot,
},
},
{
@ -280,31 +280,29 @@ func (suite *ApplyConfigSuite) TestApplyConfigRotateEncryptionSecrets() {
KeyStatic: &v1alpha1.EncryptionKeyStatic{
KeyData: "AlO93jayutOpsDxDS=-",
},
KeySlot: 1,
KeySlot: slot,
},
},
{
{
KeyNodeID: &v1alpha1.EncryptionKeyNodeID{},
KeySlot: 0,
},
existing,
{
KeyStatic: &v1alpha1.EncryptionKeyStatic{
KeyData: "AlO93jayutOpsDxDS=-",
},
KeySlot: 1,
KeySlot: slot,
},
},
{
existing,
{
KeyNodeID: &v1alpha1.EncryptionKeyNodeID{},
KeySlot: slot,
},
},
{
{
KeyNodeID: &v1alpha1.EncryptionKeyNodeID{},
KeySlot: 0,
},
{
KeyStatic: &v1alpha1.EncryptionKeyStatic{
KeyData: "1js4nfhvneJJsak=GVN4Inf5gh",
},
KeySlot: 1,
KeySlot: slot,
},
},
}

View File

@ -6,27 +6,34 @@
package encryption
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"sort"
"strconv"
"time"
"github.com/hashicorp/go-multierror"
"github.com/siderolabs/go-blockdevice/blockdevice"
"github.com/siderolabs/go-blockdevice/blockdevice/encryption"
"github.com/siderolabs/go-blockdevice/blockdevice/encryption/luks"
"github.com/siderolabs/go-blockdevice/blockdevice/encryption/token"
"github.com/siderolabs/go-blockdevice/blockdevice/partition/gpt"
"github.com/siderolabs/go-retry/retry"
"github.com/siderolabs/talos/internal/pkg/encryption/keys"
"github.com/siderolabs/talos/pkg/machinery/config/config"
)
// NewHandler creates new Handler.
func NewHandler(device *blockdevice.BlockDevice, partition *gpt.Partition, encryptionConfig config.Encryption) (*Handler, error) {
keys, err := getKeys(encryptionConfig, partition)
if err != nil {
return nil, err
}
const (
keyFetchTimeout = time.Minute * 5
keyHandlerTimeout = time.Second * 10
)
// NewHandler creates new Handler.
func NewHandler(device *blockdevice.BlockDevice, partition *gpt.Partition, encryptionConfig config.Encryption, nodeParams NodeParams) (*Handler, error) {
var provider encryption.Provider
switch encryptionConfig.Kind() {
@ -67,8 +74,8 @@ func NewHandler(device *blockdevice.BlockDevice, partition *gpt.Partition, encry
device: device,
partition: partition,
encryptionConfig: encryptionConfig,
keys: keys,
encryptionProvider: provider,
nodeParams: nodeParams,
}, nil
}
@ -78,15 +85,15 @@ type Handler struct {
device *blockdevice.BlockDevice
partition *gpt.Partition
encryptionConfig config.Encryption
keys []*encryption.Key
encryptionProvider encryption.Provider
nodeParams NodeParams
encryptedPath string
}
// Open encrypted partition.
//
//nolint:gocyclo
func (h *Handler) Open() (string, error) {
func (h *Handler) Open(ctx context.Context) (string, error) {
partPath, err := h.partition.Path()
if err != nil {
return "", err
@ -97,11 +104,14 @@ func (h *Handler) Open() (string, error) {
return "", err
}
var path string
handlers, err := h.initKeyHandlers(h.encryptionConfig, h.partition)
if err != nil {
return "", err
}
// encrypt if partition is not encrypted and empty
if sb == nil {
err = h.formatAndEncrypt(partPath)
err = h.formatAndEncrypt(ctx, partPath, handlers)
if err != nil {
return "", err
}
@ -109,28 +119,36 @@ func (h *Handler) Open() (string, error) {
return "", fmt.Errorf("failed to encrypt the partition %s, because it is not empty", partPath)
}
var k *encryption.Key
var (
path string
key *encryption.Key
)
for _, k = range h.keys {
path, err = h.encryptionProvider.Open(partPath, k)
if err = h.tryHandlers(ctx, handlers, func(ctx context.Context, handler keys.Handler) error {
var token token.Token
token, err = h.readToken(partPath, handler.Slot())
if err != nil {
if err == encryption.ErrEncryptionKeyRejected {
continue
}
return "", err
return err
}
break
}
if key, err = handler.GetKey(ctx, token); err != nil {
return err
}
if path == "" {
return "", fmt.Errorf("failed to open encrypted device %s, no key matched", partPath)
path, err = h.encryptionProvider.Open(partPath, key)
if err != nil {
return err
}
return nil
}); err != nil {
return "", fmt.Errorf("failed to open encrypted device %s: %w", partPath, err)
}
log.Printf("mapped encrypted partition %s -> %s", partPath, path)
if err = h.syncKeys(k, partPath); err != nil {
if err = h.syncKeys(ctx, partPath, handlers, key); err != nil {
return "", err
}
@ -154,22 +172,45 @@ func (h *Handler) Close() error {
return nil
}
func (h *Handler) formatAndEncrypt(path string) error {
func (h *Handler) formatAndEncrypt(ctx context.Context, path string, handlers []keys.Handler) error {
log.Printf("encrypting the partition %s (%s)", path, h.partition.Name)
if len(h.keys) == 0 {
if len(handlers) == 0 {
return fmt.Errorf("no encryption keys found")
}
key := h.keys[0]
var (
key *encryption.Key
token token.Token
err error
)
err := h.encryptionProvider.Encrypt(path, key)
if err != nil {
if err = h.tryHandlers(ctx, handlers, func(ctx context.Context, h keys.Handler) error {
if key, token, err = h.NewKey(ctx); err != nil {
return err
}
return nil
}); err != nil {
return err
}
for _, extraKey := range h.keys[1:] {
if err = h.encryptionProvider.AddKey(path, key, extraKey); err != nil {
if err = h.encryptionProvider.Encrypt(path, key); err != nil {
return err
}
if token != nil {
if err = h.encryptionProvider.SetToken(path, key.Slot, token); err != nil {
return err
}
}
for _, handler := range handlers {
if handler.Slot() == key.Slot {
continue
}
if err := h.addKey(ctx, path, key, handler); err != nil {
return err
}
}
@ -178,7 +219,7 @@ func (h *Handler) formatAndEncrypt(path string) error {
}
//nolint:gocyclo
func (h *Handler) syncKeys(k *encryption.Key, path string) error {
func (h *Handler) syncKeys(ctx context.Context, path string, handlers []keys.Handler, k *encryption.Key) error {
keyslots, err := h.encryptionProvider.ReadKeyslots(path)
if err != nil {
return err
@ -186,28 +227,28 @@ func (h *Handler) syncKeys(k *encryption.Key, path string) error {
visited := map[string]bool{}
for _, key := range h.keys {
slot := fmt.Sprintf("%d", key.Slot)
for _, handler := range handlers {
slot := fmt.Sprintf("%d", handler.Slot())
visited[slot] = true
// no need to update the key which we already detected as unchanged
if k.Slot == key.Slot {
if k.Slot == handler.Slot() {
continue
}
// keyslot exists
if _, ok := keyslots.Keyslots[slot]; ok {
if err = h.updateKey(k, key, path); err != nil {
if err = h.updateKey(ctx, path, k, handler); err != nil {
return err
}
log.Printf("updated encryption key at slot %d", key.Slot)
log.Printf("updated encryption key at slot %d", handler.Slot())
} else {
// keyslot does not exist so just add the key
if err = h.encryptionProvider.AddKey(path, k, key); err != nil {
if err = h.addKey(ctx, path, k, handler); err != nil {
return err
}
log.Printf("added encryption key to slot %d", key.Slot)
log.Printf("added encryption key to slot %d", handler.Slot())
}
}
@ -230,46 +271,138 @@ func (h *Handler) syncKeys(k *encryption.Key, path string) error {
return nil
}
func (h *Handler) updateKey(existingKey, newKey *encryption.Key, path string) error {
if valid, err := h.encryptionProvider.CheckKey(path, newKey); err != nil {
func (h *Handler) updateKey(ctx context.Context, path string, existingKey *encryption.Key, handler keys.Handler) error {
valid, err := h.checkKey(ctx, path, handler)
if err != nil {
return err
} else if !valid {
// re-add the key to the slot
err = h.encryptionProvider.RemoveKey(path, newKey.Slot, existingKey)
if err != nil {
return fmt.Errorf("failed to drop old key during key update %w", err)
}
if valid {
return nil
}
// re-add the key to the slot
err = h.encryptionProvider.RemoveKey(path, handler.Slot(), existingKey)
if err != nil {
return fmt.Errorf("failed to drop old key during key update %w", err)
}
err = h.addKey(ctx, path, existingKey, handler)
if err != nil {
return fmt.Errorf("failed to add new key during key update %w", err)
}
return err
}
func (h *Handler) checkKey(ctx context.Context, path string, handler keys.Handler) (bool, error) {
token, err := h.readToken(path, handler.Slot())
if err != nil {
return false, err
}
key, err := handler.GetKey(ctx, token)
if err != nil {
if errors.Is(err, keys.ErrTokenInvalid) {
return false, nil
}
err = h.encryptionProvider.AddKey(path, existingKey, newKey)
if err != nil {
return fmt.Errorf("failed to add new key during key update %w", err)
}
return false, err
}
return h.encryptionProvider.CheckKey(path, key)
}
func (h *Handler) addKey(ctx context.Context, path string, existingKey *encryption.Key, handler keys.Handler) error {
key, token, err := handler.NewKey(ctx)
if err != nil {
return err
}
if token != nil {
if err = h.encryptionProvider.SetToken(path, key.Slot, token); err != nil {
return err
}
}
err = h.encryptionProvider.AddKey(path, existingKey, key)
if err != nil {
return fmt.Errorf("failed to add new key during key update %w", err)
}
return nil
}
func getKeys(encryptionConfig config.Encryption, partition *gpt.Partition) ([]*encryption.Key, error) {
encryptionKeys := make([]*encryption.Key, len(encryptionConfig.Keys()))
func (h *Handler) initKeyHandlers(encryptionConfig config.Encryption, partition *gpt.Partition) ([]keys.Handler, error) {
handlers := make([]keys.Handler, 0, len(encryptionConfig.Keys()))
for i, cfg := range encryptionConfig.Keys() {
handler, err := keys.NewHandler(cfg)
for _, cfg := range encryptionConfig.Keys() {
handler, err := keys.NewHandler(cfg, keys.WithPartitionLabel(partition.Name), keys.WithNodeUUID(h.nodeParams.UUID))
if err != nil {
return nil, err
}
k, err := handler.GetKey(keys.WithPartitionLabel(partition.Name))
if err != nil {
return nil, err
}
encryptionKeys[i] = encryption.NewKey(cfg.Slot(), k)
handlers = append(handlers, handler)
}
//nolint:scopelint
sort.Slice(encryptionKeys, func(i, j int) bool { return encryptionKeys[i].Slot < encryptionKeys[j].Slot })
sort.Slice(handlers, func(i, j int) bool { return handlers[i].Slot() < handlers[j].Slot() })
return encryptionKeys, nil
return handlers, nil
}
func (h *Handler) tryHandlers(ctx context.Context, handlers []keys.Handler, cb func(ctx context.Context, h keys.Handler) error) error {
callback := func(ctx context.Context, h keys.Handler) error {
ctx, cancel := context.WithTimeout(ctx, keyHandlerTimeout)
defer cancel()
return cb(ctx, h)
}
return retry.Exponential(keyFetchTimeout, retry.WithUnits(time.Second), retry.WithJitter(time.Second)).RetryWithContext(ctx,
func(ctx context.Context) error {
var errs error
for _, h := range handlers {
if err := callback(ctx, h); err != nil {
errs = multierror.Append(errs, err)
log.Printf("failed to call key handler at slot %d: %s", h.Slot(), err)
continue
}
return nil
}
return retry.ExpectedErrorf("no handlers available to get encryption keys from: %w", errs)
})
}
func (h *Handler) readToken(path string, id int) (token.Token, error) {
token := luks.Token[json.RawMessage]{}
err := h.encryptionProvider.ReadToken(path, id, &token)
if err != nil {
if errors.Is(err, encryption.ErrTokenNotFound) {
return nil, nil //nolint:nilnil
}
return nil, err
}
if token.Type == keys.TokenTypeKMS {
kmsData := &keys.KMSToken{}
if err = json.Unmarshal(token.UserData, &kmsData); err != nil {
return nil, err
}
return &luks.Token[*keys.KMSToken]{
Type: token.Type,
UserData: kmsData,
}, nil
}
return &token, nil
}

View File

@ -4,7 +4,9 @@
package encryption_test
import "testing"
import (
"testing"
)
func TestEmpty(t *testing.T) {
// added for accurate coverage estimation

View File

@ -6,29 +6,67 @@
package keys
import (
"context"
"fmt"
"github.com/siderolabs/go-blockdevice/blockdevice/encryption"
"github.com/siderolabs/go-blockdevice/blockdevice/encryption/token"
"github.com/siderolabs/talos/pkg/machinery/config/config"
)
// NewHandler creates a new key handler depending on key handler kind.
func NewHandler(key config.EncryptionKey) (Handler, error) {
var errNoNodeUUID = fmt.Errorf("the node UUID is not set")
// NewHandler key using provided config.
func NewHandler(cfg config.EncryptionKey, options ...KeyOption) (Handler, error) {
opts, err := NewDefaultOptions(options)
if err != nil {
return nil, err
}
key := KeyHandler{slot: cfg.Slot()}
switch {
case key.Static() != nil:
k := key.Static().Key()
case cfg.Static() != nil:
k := cfg.Static().Key()
if k == nil {
return nil, fmt.Errorf("static key must have key data defined")
}
return NewStaticKeyHandler(k)
case key.NodeID() != nil:
return NewNodeIDKeyHandler()
return NewStaticKeyHandler(key, k), nil
case cfg.NodeID() != nil:
if opts.NodeUUID == "" {
return nil, fmt.Errorf("failed to create nodeUUID key handler at slot %d: %w", cfg.Slot(), errNoNodeUUID)
}
return NewNodeIDKeyHandler(key, opts.PartitionLabel, opts.NodeUUID), nil
case cfg.KMS() != nil:
if opts.NodeUUID == "" {
return nil, fmt.Errorf("failed to create KMS key handler at slot %d: %w", cfg.Slot(), errNoNodeUUID)
}
return NewKMSKeyHandler(key, cfg.KMS().Endpoint(), opts.NodeUUID)
}
return nil, fmt.Errorf("failed to create key handler: malformed config")
return nil, fmt.Errorf("malformed config: no key handler can be created")
}
// Handler represents an interface for fetching encryption keys.
// Handler manages key lifecycle.
type Handler interface {
GetKey(options ...KeyOption) ([]byte, error)
NewKey(context.Context) (*encryption.Key, token.Token, error)
GetKey(context.Context, token.Token) (*encryption.Key, error)
Slot() int
}
// KeyHandler is the base class for all key handlers.
type KeyHandler struct {
slot int
}
// Slot implements Handler interface.
func (k *KeyHandler) Slot() int {
return k.slot
}
// ErrTokenInvalid is returned by the keys handler if the supplied token is not valid.
var ErrTokenInvalid = fmt.Errorf("invalid token")

View File

@ -0,0 +1,131 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package keys
import (
"context"
"crypto/rand"
"crypto/tls"
"encoding/base64"
"fmt"
"io"
"time"
"github.com/siderolabs/go-blockdevice/blockdevice/encryption"
"github.com/siderolabs/go-blockdevice/blockdevice/encryption/luks"
"github.com/siderolabs/go-blockdevice/blockdevice/encryption/token"
"github.com/siderolabs/kms-client/api/kms"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"github.com/siderolabs/talos/internal/pkg/endpoint"
"github.com/siderolabs/talos/internal/pkg/smbios"
)
// KMSToken is the userdata stored in the partition token metadata.
type KMSToken struct {
SealedData []byte `json:"sealedData"`
}
// KMSKeyHandler seals token using KMS service.
type KMSKeyHandler struct {
KeyHandler
kmsEndpoint string
nodeUUID string
}
// NewKMSKeyHandler creates new KMSKeyHandler.
func NewKMSKeyHandler(key KeyHandler, kmsEndpoint, nodeUUID string) (*KMSKeyHandler, error) {
return &KMSKeyHandler{
KeyHandler: key,
kmsEndpoint: kmsEndpoint,
}, nil
}
// NewKey implements Handler interface.
func (h *KMSKeyHandler) NewKey(ctx context.Context) (*encryption.Key, token.Token, error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
conn, err := h.getConn(ctx)
if err != nil {
return nil, nil, fmt.Errorf("error dialing KMS endpoint %q: %w", h.kmsEndpoint, err)
}
client := kms.NewKMSServiceClient(conn)
key := make([]byte, 32)
if _, err = io.ReadFull(rand.Reader, key); err != nil {
return nil, nil, err
}
resp, err := client.Seal(ctx, &kms.Request{
NodeUuid: h.nodeUUID,
Data: key,
})
if err != nil {
return nil, nil, fmt.Errorf("failed to seal KMS passphrase, slot %d: %w", h.Slot(), err)
}
token := &luks.Token[*KMSToken]{
Type: TokenTypeKMS,
UserData: &KMSToken{
SealedData: resp.Data,
},
}
return encryption.NewKey(h.slot, []byte(base64.StdEncoding.EncodeToString(key))), token, nil
}
// GetKey implements Handler interface.
func (h *KMSKeyHandler) GetKey(ctx context.Context, t token.Token) (*encryption.Key, error) {
token, ok := t.(*luks.Token[*KMSToken])
if !ok {
return nil, ErrTokenInvalid
}
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
conn, err := h.getConn(ctx)
if err != nil {
return nil, fmt.Errorf("error dialing KMS endpoint %q: %w", h.kmsEndpoint, err)
}
client := kms.NewKMSServiceClient(conn)
s, err := smbios.GetSMBIOSInfo()
if err != nil {
return nil, err
}
resp, err := client.Unseal(ctx, &kms.Request{
NodeUuid: s.SystemInformation.UUID,
Data: token.UserData.SealedData,
})
if err != nil {
return nil, fmt.Errorf("failed to unseal KMS passphrase, slot %d: %w", h.Slot(), err)
}
return encryption.NewKey(h.slot, []byte(base64.StdEncoding.EncodeToString(resp.Data))), nil
}
func (h *KMSKeyHandler) getConn(ctx context.Context) (*grpc.ClientConn, error) {
var transportCredentials credentials.TransportCredentials
endpoint, err := endpoint.Parse(h.kmsEndpoint)
if err != nil {
return nil, err
}
if endpoint.Insecure {
transportCredentials = credentials.NewTLS(&tls.Config{})
} else {
transportCredentials = insecure.NewCredentials()
}
return grpc.DialContext(ctx, endpoint.Host, grpc.WithTransportCredentials(transportCredentials))
}

View File

@ -5,46 +5,50 @@
package keys
import (
"context"
"fmt"
"github.com/siderolabs/talos/internal/pkg/smbios"
"github.com/siderolabs/go-blockdevice/blockdevice/encryption"
"github.com/siderolabs/go-blockdevice/blockdevice/encryption/token"
)
// NodeIDKeyHandler generates the key based on current node information
// and provided template string.
type NodeIDKeyHandler struct{}
// NewNodeIDKeyHandler creates new NodeIDKeyHandler.
func NewNodeIDKeyHandler() (*NodeIDKeyHandler, error) {
return &NodeIDKeyHandler{}, nil
type NodeIDKeyHandler struct {
KeyHandler
partitionLabel string
nodeUUID string
}
// GetKey implements KeyHandler interface.
func (h *NodeIDKeyHandler) GetKey(options ...KeyOption) ([]byte, error) {
opts, err := NewDefaultOptions(options)
if err != nil {
return nil, err
// NewNodeIDKeyHandler creates new NodeIDKeyHandler.
func NewNodeIDKeyHandler(key KeyHandler, partitionLabel, nodeUUID string) *NodeIDKeyHandler {
return &NodeIDKeyHandler{
KeyHandler: key,
partitionLabel: partitionLabel,
}
}
s, err := smbios.GetSMBIOSInfo()
if err != nil {
return nil, err
}
// NewKey implements Handler interface.
func (h *NodeIDKeyHandler) NewKey(ctx context.Context) (*encryption.Key, token.Token, error) {
k, err := h.GetKey(ctx, nil)
machineUUID := s.SystemInformation.UUID
return k, nil, err
}
if machineUUID == "" {
return nil, fmt.Errorf("machine UUID is not populated %s", machineUUID)
// GetKey implements Handler interface.
func (h *NodeIDKeyHandler) GetKey(context.Context, token.Token) (*encryption.Key, error) {
if h.nodeUUID == "" {
return nil, fmt.Errorf("machine UUID is not populated %s", h.nodeUUID)
}
// primitive entropy check
counts := map[rune]int{}
for _, s := range machineUUID {
for _, s := range h.nodeUUID {
counts[s]++
if counts[s] > len(machineUUID)/2 {
return nil, fmt.Errorf("machine UUID %s entropy check failed", machineUUID)
if counts[s] > len(h.nodeUUID)/2 {
return nil, fmt.Errorf("machine UUID %s entropy check failed", h.nodeUUID)
}
}
return []byte(machineUUID + opts.PartitionLabel), nil
return encryption.NewKey(h.slot, []byte(h.nodeUUID+h.partitionLabel)), nil
}

View File

@ -10,9 +10,10 @@ type KeyOption func(o *KeyOptions) error
// KeyOptions set of options to be used in KeyHandler.GetKey func.
type KeyOptions struct {
PartitionLabel string
NodeUUID string
}
// WithPartitionLabel passes the partition label in to GetKey function.
// WithPartitionLabel passes the partition label to the key handler.
func WithPartitionLabel(label string) KeyOption {
return func(o *KeyOptions) error {
o.PartitionLabel = label
@ -21,6 +22,15 @@ func WithPartitionLabel(label string) KeyOption {
}
}
// WithNodeUUID passes the node UUID to the key handler.
func WithNodeUUID(uuid string) KeyOption {
return func(o *KeyOptions) error {
o.NodeUUID = uuid
return nil
}
}
// NewDefaultOptions creates new KeyOptions.
func NewDefaultOptions(options []KeyOption) (*KeyOptions, error) {
var opts KeyOptions

View File

@ -4,19 +4,35 @@
package keys
import (
"context"
"github.com/siderolabs/go-blockdevice/blockdevice/encryption"
"github.com/siderolabs/go-blockdevice/blockdevice/encryption/token"
)
// StaticKeyHandler just handles the static key value all the time.
type StaticKeyHandler struct {
key []byte
KeyHandler
data []byte
}
// NewStaticKeyHandler creates new EphemeralKeyHandler.
func NewStaticKeyHandler(key []byte) (*StaticKeyHandler, error) {
func NewStaticKeyHandler(key KeyHandler, data []byte) *StaticKeyHandler {
return &StaticKeyHandler{
key: key,
}, nil
KeyHandler: key,
data: data,
}
}
// GetKey implements KeyHandler interface.
func (h *StaticKeyHandler) GetKey(options ...KeyOption) ([]byte, error) {
return h.key, nil
// NewKey implements Handler interface.
func (h *StaticKeyHandler) NewKey(ctx context.Context) (*encryption.Key, token.Token, error) {
k, err := h.GetKey(ctx, nil)
return k, nil, err
}
// GetKey implements Handler interface.
func (h *StaticKeyHandler) GetKey(context.Context, token.Token) (*encryption.Key, error) {
return encryption.NewKey(h.slot, h.data), nil
}

View File

@ -2,8 +2,9 @@
// 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 siderolink
package keys
var ParseAPIEndpoint = parseAPIEndpoint
type APIEndpoint = apiEndpoint
const (
// TokenTypeKMS is KMS assisted encryption token.
TokenTypeKMS = "sideroKMS"
)

View File

@ -0,0 +1,10 @@
// 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 encryption
// NodeParams contains node information relevant for the encryption handler.
type NodeParams struct {
UUID string
}

View File

@ -0,0 +1,49 @@
// 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 endpoint has common tools for parsing http API endpoints.
package endpoint
import (
"net/url"
"regexp"
)
var urlSchemeMatcher = regexp.MustCompile(`[a-zA-z]+://`)
// Endpoint defines all params parsed from the API endpoint.
type Endpoint struct {
Host string
Insecure bool
params url.Values
}
// Parse parses the endpoint from string.
func Parse(sideroLinkParam string) (Endpoint, error) {
if !urlSchemeMatcher.MatchString(sideroLinkParam) {
sideroLinkParam = "grpc://" + sideroLinkParam
}
u, err := url.Parse(sideroLinkParam)
if err != nil {
return Endpoint{}, err
}
result := Endpoint{
Host: u.Host,
Insecure: u.Scheme == "grpc",
params: u.Query(),
}
if u.Port() == "" && u.Scheme == "https" {
result.Host += ":443"
}
return result, nil
}
// GetParam reads param from the query.
func (e *Endpoint) GetParam(name string) string {
return e.params.Get(name)
}

View File

@ -0,0 +1,81 @@
// 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 endpoint_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/siderolabs/talos/internal/pkg/endpoint"
)
func TestParseEndpoint(t *testing.T) {
t.Run("parses a join token from a complete URL without error", func(t *testing.T) {
// when
endpoint, err := endpoint.Parse("grpc://10.5.0.2:3445?jointoken=ttt")
// then
assert.NoError(t, err)
assert.Equal(t, "10.5.0.2:3445", endpoint.Host)
assert.True(t, endpoint.Insecure)
assert.Equal(t, "ttt", endpoint.GetParam("jointoken"))
})
t.Run("parses a join token from a secure URL without error", func(t *testing.T) {
// when
endpoint, err := endpoint.Parse("https://10.5.0.2:3445?jointoken=ttt&jointoken=xxx")
// then
assert.NoError(t, err)
assert.Equal(t, "10.5.0.2:3445", endpoint.Host)
assert.False(t, endpoint.Insecure)
assert.Equal(t, "ttt", endpoint.GetParam("jointoken"))
})
t.Run("parses a join token from a secure URL without port", func(t *testing.T) {
// when
endpoint, err := endpoint.Parse("https://10.5.0.2?jointoken=ttt&jointoken=xxx")
// then
assert.NoError(t, err)
assert.Equal(t, "10.5.0.2:443", endpoint.Host)
assert.False(t, endpoint.Insecure)
assert.Equal(t, "ttt", endpoint.GetParam("jointoken"))
})
t.Run("parses a join token from an URL without a scheme", func(t *testing.T) {
// when
endpoint, err := endpoint.Parse("10.5.0.2:3445?jointoken=ttt")
// then
assert.NoError(t, err)
assert.Equal(t, "10.5.0.2:3445", endpoint.Host)
assert.True(t, endpoint.Insecure)
assert.Equal(t, "ttt", endpoint.GetParam("jointoken"))
})
t.Run("does not error if there is no join token in a complete URL", func(t *testing.T) {
// when
endpoint, err := endpoint.Parse("grpc://10.5.0.2:3445")
// then
assert.NoError(t, err)
assert.Equal(t, "10.5.0.2:3445", endpoint.Host)
assert.True(t, endpoint.Insecure)
assert.Equal(t, "", endpoint.GetParam("jointoken"))
})
t.Run("does not error if there is no join token in an URL without a scheme", func(t *testing.T) {
// when
endpoint, err := endpoint.Parse("10.5.0.2:3445")
// then
assert.NoError(t, err)
assert.Equal(t, "10.5.0.2:3445", endpoint.Host)
assert.True(t, endpoint.Insecure)
assert.Equal(t, "", endpoint.GetParam("jointoken"))
})
}

View File

@ -7,6 +7,7 @@ package mount
import (
"log"
"github.com/siderolabs/talos/internal/pkg/encryption"
"github.com/siderolabs/talos/pkg/machinery/config/config"
)
@ -45,6 +46,7 @@ type Options struct {
PreMountHooks []Hook
PostUnmountHooks []Hook
Encryption config.Encryption
NodeParams encryption.NodeParams
Logger *log.Logger
ProjectQuota bool
}
@ -111,6 +113,13 @@ func WithProjectQuota(enable bool) Option {
}
}
// WithNodeParams node info used by the encryption handler.
func WithNodeParams(params encryption.NodeParams) Option {
return func(args *Options) {
args.NodeParams = params
}
}
// Hook represents pre/post mount hook.
type Hook func(p *Point) error

View File

@ -36,7 +36,7 @@ var (
// creation and bare metall installs ). This is why we want to look up
// device by specified disk as well as why we don't want to grow any
// filesystems.
func SystemMountPointsForDevice(devpath string, opts ...Option) (mountpoints *Points, err error) {
func SystemMountPointsForDevice(ctx context.Context, devpath string, opts ...Option) (mountpoints *Points, err error) {
mountpoints = NewMountPoints()
bd, err := blockdevice.Open(devpath)
@ -47,7 +47,7 @@ func SystemMountPointsForDevice(devpath string, opts ...Option) (mountpoints *Po
defer bd.Close() //nolint:errcheck
for _, name := range []string{constants.EphemeralPartitionLabel, constants.BootPartitionLabel, constants.EFIPartitionLabel, constants.StatePartitionLabel} {
mountpoint, err := SystemMountPointForLabel(bd, name, opts...)
mountpoint, err := SystemMountPointForLabel(ctx, bd, name, opts...)
if err != nil {
return nil, err
}
@ -61,7 +61,7 @@ func SystemMountPointsForDevice(devpath string, opts ...Option) (mountpoints *Po
// SystemMountPointForLabel returns a mount point for the specified device and label.
//
//nolint:gocyclo
func SystemMountPointForLabel(device *blockdevice.BlockDevice, label string, opts ...Option) (mountpoint *Point, err error) {
func SystemMountPointForLabel(ctx context.Context, device *blockdevice.BlockDevice, label string, opts ...Option) (mountpoint *Point, err error) {
var target string
switch label {
@ -110,6 +110,7 @@ func SystemMountPointForLabel(device *blockdevice.BlockDevice, label string, opt
device,
part,
o.Encryption,
o.NodeParams,
)
if err != nil {
return nil, err
@ -122,7 +123,7 @@ func SystemMountPointForLabel(device *blockdevice.BlockDevice, label string, opt
path string
)
if path, err = encryptionHandler.Open(); err != nil {
if path, err = encryptionHandler.Open(context.TODO()); err != nil {
return err
}
@ -182,23 +183,40 @@ func SystemMountPointForLabel(device *blockdevice.BlockDevice, label string, opt
// SystemPartitionMount mounts a system partition by the label.
//
//nolint:gocyclo
func SystemPartitionMount(r runtime.Runtime, logger *log.Logger, label string, opts ...Option) (err error) {
func SystemPartitionMount(ctx context.Context, r runtime.Runtime, logger *log.Logger, label string, opts ...Option) (err error) {
device := r.State().Machine().Disk(disk.WithPartitionLabel(label))
if device == nil {
return fmt.Errorf("failed to find device with partition labeled %s", label)
}
systemInformation, err := r.GetSystemInformation(ctx)
if err != nil && !errors.Is(err, context.Canceled) && state.IsNotFoundError(err) {
return err
}
if systemInformation != nil {
opts = append(opts, WithNodeParams(encryption.NodeParams{
UUID: systemInformation.TypedSpec().UUID,
}))
}
var encrypted bool
if r.Config() != nil && r.Config().Machine() != nil {
encryptionConfig := r.Config().Machine().SystemDiskEncryption().Get(label)
if encryptionConfig != nil {
opts = append(opts, WithEncryptionConfig(encryptionConfig))
encrypted = true
opts = append(opts,
WithEncryptionConfig(encryptionConfig),
)
}
}
opts = append(opts, WithLogger(logger))
mountpoint, err := SystemMountPointForLabel(device.BlockDevice, label, opts...)
mountpoint, err := SystemMountPointForLabel(ctx, device.BlockDevice, label, opts...)
if err != nil {
return err
}
@ -226,6 +244,7 @@ func SystemPartitionMount(r runtime.Runtime, logger *log.Logger, label string, o
mountStatus.TypedSpec().Source = mountpoint.Source()
mountStatus.TypedSpec().Target = mountpoint.Target()
mountStatus.TypedSpec().FilesystemType = mountpoint.Fstype()
mountStatus.TypedSpec().Encrypted = encrypted
// ignore the error if the MountStatus already exists, as many mounts are silently skipped with the flag SkipIfMounted
if err = r.State().V1Alpha2().Resources().Create(context.Background(), mountStatus); err != nil && !state.IsConflictError(err) {

View File

@ -514,6 +514,7 @@ type MountStatusSpec struct {
Target string `protobuf:"bytes,2,opt,name=target,proto3" json:"target,omitempty"`
FilesystemType string `protobuf:"bytes,3,opt,name=filesystem_type,json=filesystemType,proto3" json:"filesystem_type,omitempty"`
Options []string `protobuf:"bytes,4,rep,name=options,proto3" json:"options,omitempty"`
Encrypted bool `protobuf:"varint,5,opt,name=encrypted,proto3" json:"encrypted,omitempty"`
}
func (x *MountStatusSpec) Reset() {
@ -576,6 +577,13 @@ func (x *MountStatusSpec) GetOptions() []string {
return nil
}
func (x *MountStatusSpec) GetEncrypted() bool {
if x != nil {
return x.Encrypted
}
return false
}
// PlatformMetadataSpec describes platform metadata properties.
type PlatformMetadataSpec struct {
state protoimpl.MessageState
@ -797,7 +805,7 @@ var file_resource_definitions_runtime_runtime_proto_rawDesc = []byte{
0x75, 0x6e, 0x6d, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22,
0x23, 0x0a, 0x0b, 0x4d, 0x65, 0x74, 0x61, 0x4b, 0x65, 0x79, 0x53, 0x70, 0x65, 0x63, 0x12, 0x14,
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x22, 0x84, 0x01, 0x0a, 0x0f, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x74,
0x61, 0x6c, 0x75, 0x65, 0x22, 0xa2, 0x01, 0x0a, 0x0f, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x53, 0x70, 0x65, 0x63, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
@ -805,32 +813,34 @@ var file_resource_definitions_runtime_runtime_proto_rawDesc = []byte{
0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x79, 0x70,
0x65, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03,
0x28, 0x09, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xf5, 0x01, 0x0a, 0x14,
0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
0x53, 0x70, 0x65, 0x63, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d,
0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06,
0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65,
0x67, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x7a, 0x6f, 0x6e, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73, 0x74,
0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a,
0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1f,
0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12,
0x12, 0x0a, 0x04, 0x73, 0x70, 0x6f, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x73,
0x70, 0x6f, 0x74, 0x22, 0x3c, 0x0a, 0x0e, 0x55, 0x6e, 0x6d, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x64,
0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61,
0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f,
0x6e, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
0x73, 0x69, 0x64, 0x65, 0x72, 0x6f, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, 0x6c, 0x6f, 0x73,
0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x72, 0x79, 0x2f, 0x61,
0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2f, 0x64, 0x65, 0x66, 0x69,
0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x28, 0x09, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x65,
0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x22, 0xf5, 0x01, 0x0a, 0x14, 0x50, 0x6c,
0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x53, 0x70,
0x65, 0x63, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1a,
0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65,
0x67, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69,
0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
0x52, 0x04, 0x7a, 0x6f, 0x6e, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e,
0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x69,
0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x69,
0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b,
0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a,
0x04, 0x73, 0x70, 0x6f, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x70, 0x6f,
0x74, 0x22, 0x3c, 0x0a, 0x0e, 0x55, 0x6e, 0x6d, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74,
0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f,
0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x42,
0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69,
0x64, 0x65, 0x72, 0x6f, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2f, 0x70,
0x6b, 0x67, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x72, 0x79, 0x2f, 0x61, 0x70, 0x69,
0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69,
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

View File

@ -492,6 +492,16 @@ func (m *MountStatusSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if m.Encrypted {
i--
if m.Encrypted {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x28
}
if len(m.Options) > 0 {
for iNdEx := len(m.Options) - 1; iNdEx >= 0; iNdEx-- {
i -= len(m.Options[iNdEx])
@ -856,6 +866,9 @@ func (m *MountStatusSpec) SizeVT() (n int) {
n += 1 + l + sov(uint64(l))
}
}
if m.Encrypted {
n += 2
}
n += len(m.unknownFields)
return n
}
@ -1976,6 +1989,26 @@ func (m *MountStatusSpec) UnmarshalVT(dAtA []byte) error {
}
m.Options = append(m.Options, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
case 5:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Encrypted", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.Encrypted = bool(v != 0)
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])

View File

@ -356,6 +356,7 @@ type RegistryTLSConfig interface {
type EncryptionKey interface {
Static() EncryptionKeyStatic
NodeID() EncryptionKeyNodeID
KMS() EncryptionKeyKMS
Slot() int
}
@ -364,6 +365,11 @@ type EncryptionKeyStatic interface {
Key() []byte
}
// EncryptionKeyKMS encryption key sealed by KMS.
type EncryptionKeyKMS interface {
Endpoint() string
}
// EncryptionKeyNodeID deterministically generated encryption key.
type EncryptionKeyNodeID interface{}

View File

@ -1185,6 +1185,13 @@
"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/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"
},
"slot": {
"type": "integer",
"title": "slot",
@ -1196,6 +1203,19 @@
"additionalProperties": false,
"type": "object"
},
"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"
},
"EncryptionKeyNodeID": {
"properties": {},
"additionalProperties": false,

View File

@ -1361,6 +1361,15 @@ func (e *EncryptionKey) NodeID() config.EncryptionKeyNodeID {
return e.KeyNodeID
}
// KMS implements the config.Provider interface.
func (e *EncryptionKey) KMS() config.EncryptionKeyKMS {
if e.KeyKMS == nil {
return nil
}
return e.KeyKMS
}
// Slot implements the config.Provider interface.
func (e *EncryptionKey) Slot() int {
return e.KeySlot
@ -1371,6 +1380,11 @@ func (e *EncryptionKeyStatic) Key() []byte {
return []byte(e.KeyData)
}
// Endpoint implements the config.Provider interface.
func (e *EncryptionKeyKMS) Endpoint() string {
return e.KMSEndpoint
}
// Get implements the config.Provider interface.
func (e *SystemDiskEncryptionConfig) Get(label string) config.Encryption {
switch label {

View File

@ -607,6 +607,10 @@ metadata:
"kube-system",
},
}
kmsKeyExample = &EncryptionKeyKMS{
KMSEndpoint: "https://192.168.88.21:4443",
}
)
// Config defines the v1alpha1 configuration file.
@ -2070,6 +2074,11 @@ type EncryptionKey struct {
// Deterministically generated key from the node UUID and PartitionLabel.
KeyNodeID *EncryptionKeyNodeID `yaml:"nodeID,omitempty"`
// description: >
// KMS managed encryption key.
// examples:
// - value: kmsKeyExample
KeyKMS *EncryptionKeyKMS `yaml:"kms,omitempty"`
// description: >
// Key slot number for LUKS2 encryption.
KeySlot int `yaml:"slot"`
}
@ -2081,6 +2090,13 @@ type EncryptionKeyStatic struct {
KeyData string `yaml:"passphrase,omitempty"`
}
// EncryptionKeyKMS represents a key that is generated and then sealed/unsealed by the KMS server.
type EncryptionKeyKMS struct {
// description: >
// KMS endpoint to Seal/Unseal the key.
KMSEndpoint string `yaml:"endpoint"`
}
// EncryptionKeyNodeID represents deterministically generated key from the node UUID and PartitionLabel.
type EncryptionKeyNodeID struct{}

View File

@ -49,6 +49,7 @@ var (
EncryptionConfigDoc encoder.Doc
EncryptionKeyDoc encoder.Doc
EncryptionKeyStaticDoc encoder.Doc
EncryptionKeyKMSDoc encoder.Doc
EncryptionKeyNodeIDDoc encoder.Doc
MachineFileDoc encoder.Doc
ExtraHostDoc encoder.Doc
@ -1559,7 +1560,7 @@ func init() {
FieldName: "keys",
},
}
EncryptionKeyDoc.Fields = make([]encoder.Doc, 3)
EncryptionKeyDoc.Fields = make([]encoder.Doc, 4)
EncryptionKeyDoc.Fields[0].Name = "static"
EncryptionKeyDoc.Fields[0].Type = "EncryptionKeyStatic"
EncryptionKeyDoc.Fields[0].Note = ""
@ -1570,11 +1571,18 @@ func init() {
EncryptionKeyDoc.Fields[1].Note = ""
EncryptionKeyDoc.Fields[1].Description = "Deterministically generated key from the node UUID and PartitionLabel."
EncryptionKeyDoc.Fields[1].Comments[encoder.LineComment] = "Deterministically generated key from the node UUID and PartitionLabel."
EncryptionKeyDoc.Fields[2].Name = "slot"
EncryptionKeyDoc.Fields[2].Type = "int"
EncryptionKeyDoc.Fields[2].Name = "kms"
EncryptionKeyDoc.Fields[2].Type = "EncryptionKeyKMS"
EncryptionKeyDoc.Fields[2].Note = ""
EncryptionKeyDoc.Fields[2].Description = "Key slot number for LUKS2 encryption."
EncryptionKeyDoc.Fields[2].Comments[encoder.LineComment] = "Key slot number for LUKS2 encryption."
EncryptionKeyDoc.Fields[2].Description = "KMS managed encryption key."
EncryptionKeyDoc.Fields[2].Comments[encoder.LineComment] = "KMS managed encryption key."
EncryptionKeyDoc.Fields[2].AddExample("", kmsKeyExample)
EncryptionKeyDoc.Fields[3].Name = "slot"
EncryptionKeyDoc.Fields[3].Type = "int"
EncryptionKeyDoc.Fields[3].Note = ""
EncryptionKeyDoc.Fields[3].Description = "Key slot number for LUKS2 encryption."
EncryptionKeyDoc.Fields[3].Comments[encoder.LineComment] = "Key slot number for LUKS2 encryption."
EncryptionKeyStaticDoc.Type = "EncryptionKeyStatic"
EncryptionKeyStaticDoc.Comments[encoder.LineComment] = "EncryptionKeyStatic represents throw away key type."
@ -1592,6 +1600,24 @@ func init() {
EncryptionKeyStaticDoc.Fields[0].Description = "Defines the static passphrase value."
EncryptionKeyStaticDoc.Fields[0].Comments[encoder.LineComment] = "Defines the static passphrase value."
EncryptionKeyKMSDoc.Type = "EncryptionKeyKMS"
EncryptionKeyKMSDoc.Comments[encoder.LineComment] = "EncryptionKeyKMS represents a key that is generated and then sealed/unsealed by the KMS server."
EncryptionKeyKMSDoc.Description = "EncryptionKeyKMS represents a key that is generated and then sealed/unsealed by the KMS server."
EncryptionKeyKMSDoc.AddExample("", kmsKeyExample)
EncryptionKeyKMSDoc.AppearsIn = []encoder.Appearance{
{
TypeName: "EncryptionKey",
FieldName: "kms",
},
}
EncryptionKeyKMSDoc.Fields = make([]encoder.Doc, 1)
EncryptionKeyKMSDoc.Fields[0].Name = "endpoint"
EncryptionKeyKMSDoc.Fields[0].Type = "string"
EncryptionKeyKMSDoc.Fields[0].Note = ""
EncryptionKeyKMSDoc.Fields[0].Description = "KMS endpoint to Seal/Unseal the key."
EncryptionKeyKMSDoc.Fields[0].Comments[encoder.LineComment] = "KMS endpoint to Seal/Unseal the key."
EncryptionKeyNodeIDDoc.Type = "EncryptionKeyNodeID"
EncryptionKeyNodeIDDoc.Comments[encoder.LineComment] = "EncryptionKeyNodeID represents deterministically generated key from the node UUID and PartitionLabel."
EncryptionKeyNodeIDDoc.Description = "EncryptionKeyNodeID represents deterministically generated key from the node UUID and PartitionLabel."
@ -2937,6 +2963,10 @@ func (_ EncryptionKeyStatic) Doc() *encoder.Doc {
return &EncryptionKeyStaticDoc
}
func (_ EncryptionKeyKMS) Doc() *encoder.Doc {
return &EncryptionKeyKMSDoc
}
func (_ EncryptionKeyNodeID) Doc() *encoder.Doc {
return &EncryptionKeyNodeIDDoc
}
@ -3126,6 +3156,7 @@ func GetConfigurationDoc() *encoder.FileDoc {
&EncryptionConfigDoc,
&EncryptionKeyDoc,
&EncryptionKeyStaticDoc,
&EncryptionKeyKMSDoc,
&EncryptionKeyNodeIDDoc,
&MachineFileDoc,
&ExtraHostDoc,

View File

@ -231,7 +231,7 @@ func (c *Config) Validate(mode validation.RuntimeMode, options ...validation.Opt
slotsInUse[key.Slot()] = true
if key.NodeID() == nil && key.Static() == nil {
if key.NodeID() == nil && key.Static() == nil && key.KMS() == nil {
result = multierror.Append(result, fmt.Errorf("encryption key at slot %d doesn't have any settings", key.Slot()))
}
}

View File

@ -827,6 +827,11 @@ func (in *EncryptionKey) DeepCopyInto(out *EncryptionKey) {
*out = new(EncryptionKeyNodeID)
**out = **in
}
if in.KeyKMS != nil {
in, out := &in.KeyKMS, &out.KeyKMS
*out = new(EncryptionKeyKMS)
**out = **in
}
return
}
@ -840,6 +845,22 @@ func (in *EncryptionKey) DeepCopy() *EncryptionKey {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EncryptionKeyKMS) DeepCopyInto(out *EncryptionKeyKMS) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EncryptionKeyKMS.
func (in *EncryptionKeyKMS) DeepCopy() *EncryptionKeyKMS {
if in == nil {
return nil
}
out := new(EncryptionKeyKMS)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EncryptionKeyNodeID) DeepCopyInto(out *EncryptionKeyNodeID) {
*out = *in

View File

@ -21,7 +21,7 @@ require (
github.com/siderolabs/crypto v0.4.0
github.com/siderolabs/gen v0.4.5
github.com/siderolabs/go-api-signature v0.2.4
github.com/siderolabs/go-blockdevice v0.4.5
github.com/siderolabs/go-blockdevice v0.4.6
github.com/siderolabs/go-pointer v1.0.0
github.com/siderolabs/net v0.4.0
github.com/siderolabs/protoenc v0.2.0

View File

@ -113,8 +113,8 @@ github.com/siderolabs/gen v0.4.5 h1:rwXUVJlL7hYza1LrSVXfT905ZC9Rgei37jMKKs/+eP0=
github.com/siderolabs/gen v0.4.5/go.mod h1:wS8tFq7sn5vqKAuyS30vJUig3tX5v6q79VG4KfUnILM=
github.com/siderolabs/go-api-signature v0.2.4 h1:s+K0GtaPHql4LdZzL72QvwPMzffY+KB0mszORDK+5/w=
github.com/siderolabs/go-api-signature v0.2.4/go.mod h1:rLIKzbDhKewI3MdztyntS1apH6EC8ccU2TF5S0/O2yg=
github.com/siderolabs/go-blockdevice v0.4.5 h1:NgpR9XTl/N7WeL59QHBsseDD0Nb8Y2nel+W3u7xHIvY=
github.com/siderolabs/go-blockdevice v0.4.5/go.mod h1:4PeOuk71pReJj1JQEXDE7kIIQJPVe8a+HZQa+qjxSEA=
github.com/siderolabs/go-blockdevice v0.4.6 h1:yfxFYzXezzszB0mSF2ZG8jPPampoNXa9r8W8nM0IoZI=
github.com/siderolabs/go-blockdevice v0.4.6/go.mod h1:4PeOuk71pReJj1JQEXDE7kIIQJPVe8a+HZQa+qjxSEA=
github.com/siderolabs/go-pointer v1.0.0 h1:6TshPKep2doDQJAAtHUuHWXbca8ZfyRySjSBT/4GsMU=
github.com/siderolabs/go-pointer v1.0.0/go.mod h1:HTRFUNYa3R+k0FFKNv11zgkaCLzEkWVzoYZ433P3kHc=
github.com/siderolabs/go-retry v0.3.2 h1:FzWslFm4y8RY1wU0gIskm0oZHOpsSibZqlR8N8/k4Eo=

View File

@ -27,6 +27,7 @@ type MountStatusSpec struct {
Target string `yaml:"target" protobuf:"2"`
FilesystemType string `yaml:"filesystemType" protobuf:"3"`
Options []string `yaml:"options" protobuf:"4"`
Encrypted bool `yaml:"encrypted" protobuf:"5"`
}
// NewMountStatus initializes a MountStatus resource.

View File

@ -133,6 +133,15 @@ func WithDeleteOnErr(v bool) Option {
}
}
// WithKMS inits KMS server in the provisioner.
func WithKMS(endpoint string) Option {
return func(o *Options) error {
o.KMSEndpoint = endpoint
return nil
}
}
// Options describes Provisioner parameters.
type Options struct {
LogWriter io.Writer
@ -157,6 +166,8 @@ type Options struct {
DockerPorts []string
DockerPortsHostIP string
DeleteStateOnErr bool
KMSEndpoint string
}
// DefaultOptions returns default options.

View File

@ -59,6 +59,14 @@ func (p *provisioner) Create(ctx context.Context, request provision.ClusterReque
return nil, fmt.Errorf("error creating loadbalancer: %w", err)
}
if options.KMSEndpoint != "" {
fmt.Fprintln(options.LogWriter, "creating KMS server")
if err = p.CreateKMS(state, request, options); err != nil {
return nil, fmt.Errorf("error creating KMS server: %w", err)
}
}
fmt.Fprintln(options.LogWriter, "creating dhcpd")
if err = p.CreateDHCPd(state, request); err != nil {

View File

@ -70,6 +70,12 @@ func (p *provisioner) Destroy(ctx context.Context, cluster provision.Cluster, op
return fmt.Errorf("error stopping loadbalancer: %w", err)
}
fmt.Fprintln(options.LogWriter, "removing kms")
if err := p.DestroyKMS(state); err != nil {
return err
}
fmt.Fprintln(options.LogWriter, "removing network")
if err := p.DestroyNetwork(state); err != nil {

View File

@ -0,0 +1,70 @@
// 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 vm
import (
"crypto/rand"
"encoding/base64"
"fmt"
"io"
"os"
"os/exec"
"strconv"
"syscall"
"github.com/siderolabs/talos/pkg/provision"
)
const (
kmsPid = "kms.pid"
kmsLog = "kms.log"
)
// CreateKMS creates KMS server.
func (p *Provisioner) CreateKMS(state *State, clusterReq provision.ClusterRequest, options provision.Options) error {
pidPath := state.GetRelativePath(kmsPid)
logFile, err := os.OpenFile(state.GetRelativePath(kmsLog), os.O_APPEND|os.O_CREATE|os.O_RDWR, 0o666)
if err != nil {
return err
}
defer logFile.Close() //nolint:errcheck
key := make([]byte, 32)
if _, err = io.ReadFull(rand.Reader, key); err != nil {
return err
}
args := []string{
"kms-launch",
"--kms-addr", options.KMSEndpoint,
"--kms-key", base64.StdEncoding.EncodeToString(key),
}
cmd := exec.Command(clusterReq.SelfExecutable, args...)
cmd.Stdout = logFile
cmd.Stderr = logFile
cmd.SysProcAttr = &syscall.SysProcAttr{
Setsid: true, // daemonize
}
if err = cmd.Start(); err != nil {
return err
}
if err = os.WriteFile(pidPath, []byte(strconv.Itoa(cmd.Process.Pid)), os.ModePerm); err != nil {
return fmt.Errorf("error writing LB PID file: %w", err)
}
return nil
}
// DestroyKMS destroys KMS server.
func (p *Provisioner) DestroyKMS(state *State) error {
pidPath := state.GetRelativePath(kmsPid)
return StopProcessByPidfile(pidPath)
}

View File

@ -64,7 +64,7 @@ func (p *Provisioner) CreateLoadBalancer(state *State, clusterReq provision.Clus
return nil
}
// DestroyLoadBalancer destoys load balancer.
// DestroyLoadBalancer destroys load balancer.
func (p *Provisioner) DestroyLoadBalancer(state *State) error {
pidPath := state.GetRelativePath(lbPid)

View File

@ -28,6 +28,7 @@ type ClusterRequest struct {
InitramfsPath string
ISOPath string
DiskImagePath string
KMSEndpoint string
// Path to talosctl executable to re-execute itself as needed.
SelfExecutable string

View File

@ -3522,6 +3522,7 @@ MountStatusSpec describes status of the defined sysctls.
| target | [string](#string) | | |
| filesystem_type | [string](#string) | | |
| options | [string](#string) | repeated | |
| encrypted | [bool](#bool) | | |

View File

@ -111,6 +111,7 @@ talosctl cluster create [flags]
--custom-cni-url string install custom CNI from the URL (Talos cluster)
--disable-dhcp-hostname skip announcing hostname via DHCP (QEMU only)
--disk int default limit on disk size in MB (each VM) (default 6144)
--disk-encryption-key-types stringArray encryption key types to use for disk encryption (uuid, kms) (default [uuid])
--disk-image-path string disk image to use
--dns-domain string the dns domain to use for cluster (default "cluster.local")
--docker-disable-ipv6 skip enabling IPv6 in containers (Docker only)

View File

@ -357,6 +357,10 @@ systemDiskEncryption:
nodeID: {}
slot: 0 # Key slot number for LUKS2 encryption.
# # KMS managed encryption key.
# kms:
# endpoint: https://192.168.88.21:4443 # KMS endpoint to Seal/Unseal the key.
# # Cipher kind to use for the encryption. Depends on the encryption provider.
# cipher: aes-xts-plain64
@ -1879,6 +1883,10 @@ Appears in:
|-------|------|-------------|----------|
|`static` |<a href="#encryptionkeystatic">EncryptionKeyStatic</a> |Key which value is stored in the configuration file. | |
|`nodeID` |<a href="#encryptionkeynodeid">EncryptionKeyNodeID</a> |Deterministically generated key from the node UUID and PartitionLabel. | |
|`kms` |<a href="#encryptionkeykms">EncryptionKeyKMS</a> |KMS managed encryption key. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
kms:
endpoint: https://192.168.88.21:4443 # KMS endpoint to Seal/Unseal the key.
{{< /highlight >}}</details> | |
|`slot` |int |Key slot number for LUKS2 encryption. | |
@ -1900,6 +1908,27 @@ Appears in:
---
## EncryptionKeyKMS
EncryptionKeyKMS represents a key that is generated and then sealed/unsealed by the KMS server.
Appears in:
- <code><a href="#encryptionkey">EncryptionKey</a>.kms</code>
{{< highlight yaml >}}
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. | |
---
## EncryptionKeyNodeID
EncryptionKeyNodeID represents deterministically generated key from the node UUID and PartitionLabel.
@ -2627,6 +2656,10 @@ ephemeral:
nodeID: {}
slot: 0 # Key slot number for LUKS2 encryption.
# # KMS managed encryption key.
# kms:
# endpoint: https://192.168.88.21:4443 # KMS endpoint to Seal/Unseal the key.
# # Cipher kind to use for the encryption. Depends on the encryption provider.
# cipher: aes-xts-plain64

View File

@ -1185,6 +1185,13 @@
"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/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"
},
"slot": {
"type": "integer",
"title": "slot",
@ -1196,6 +1203,19 @@
"additionalProperties": false,
"type": "object"
},
"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"
},
"EncryptionKeyNodeID": {
"properties": {},
"additionalProperties": false,