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:
Binary file not shown.
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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")
|
||||
|
86
cmd/talosctl/cmd/mgmt/kms_launch.go
Normal file
86
cmd/talosctl/cmd/mgmt/kms_launch.go
Normal 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
3
go.mod
@ -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
6
go.sum
@ -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=
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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()),
|
||||
)
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -4,7 +4,9 @@
|
||||
|
||||
package encryption_test
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
// added for accurate coverage estimation
|
||||
|
@ -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")
|
||||
|
131
internal/pkg/encryption/keys/kms.go
Normal file
131
internal/pkg/encryption/keys/kms.go
Normal 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))
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"
|
||||
)
|
10
internal/pkg/encryption/node_params.go
Normal file
10
internal/pkg/encryption/node_params.go
Normal 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
|
||||
}
|
49
internal/pkg/endpoint/endpoint.go
Normal file
49
internal/pkg/endpoint/endpoint.go
Normal 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)
|
||||
}
|
81
internal/pkg/endpoint/endpoint_test.go
Normal file
81
internal/pkg/endpoint/endpoint_test.go
Normal 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"))
|
||||
})
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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 (
|
||||
|
@ -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:])
|
||||
|
@ -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{}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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{}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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=
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
70
pkg/provision/providers/vm/kms.go
Normal file
70
pkg/provision/providers/vm/kms.go
Normal 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)
|
||||
}
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) | | |
|
||||
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
Reference in New Issue
Block a user