Skip to content

Commit 44689c8

Browse files
committed
RKE2ControlPlane: v1beta1 support EncryptionConfig
This commit adds RKE2ControlPlane.spec.serverConfig.secretsEncryption property. This allows to specify provider type (aescbc or secretbox) and load encryption key from a secret. Signed-off-by: Dinar Valeev <[email protected]>
1 parent 8ad2334 commit 44689c8

File tree

7 files changed

+245
-0
lines changed

7 files changed

+245
-0
lines changed

controlplane/api/v1alpha1/zz_generated.conversion.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controlplane/api/v1beta1/rke2controlplane_types.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ type RKE2ServerConfig struct {
211211
//+optional
212212
Etcd EtcdConfig `json:"etcd,omitempty"`
213213

214+
// SecretsEncrytion defines encryption at rest configuration
215+
//+optional
216+
SecretsEncryptionProvider *SecretsEncryption `json:"secretsEncryption,omitempty"`
217+
214218
// KubeAPIServer defines optional custom configuration of the Kube API Server.
215219
//+optional
216220
KubeAPIServer *bootstrapv1.ComponentConfig `json:"kubeAPIServer,omitempty"`
@@ -402,6 +406,15 @@ type EtcdS3 struct {
402406
Folder string `json:"folder,omitempty"`
403407
}
404408

409+
// SecretsEncryption defines encryption configuration.
410+
type SecretsEncryption struct {
411+
// EncyptionKey secret reference
412+
EncryptionKeySecret *corev1.ObjectReference `json:"encryptionKeySecret,omitempty"`
413+
// Encryption provider
414+
// +kubebuilder:validation:Enum=aescbc;secretbox
415+
Provider string `json:"provider,omitempty"`
416+
}
417+
405418
// CNI defines the Cni options for deploying RKE2.
406419
type CNI string
407420

controlplane/api/v1beta1/zz_generated.deepcopy.go

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controlplane/config/crd/bases/controlplane.cluster.x-k8s.io_rke2controlplanes.yaml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2557,6 +2557,59 @@ spec:
25572557
pauseImage:
25582558
description: PauseImage Override image to use for pause.
25592559
type: string
2560+
secretsEncryption:
2561+
description: SecretsEncrytion defines encryption at rest configuration
2562+
properties:
2563+
encryptionKeySecret:
2564+
description: EncyptionKey secret reference
2565+
properties:
2566+
apiVersion:
2567+
description: API version of the referent.
2568+
type: string
2569+
fieldPath:
2570+
description: |-
2571+
If referring to a piece of an object instead of an entire object, this string
2572+
should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2].
2573+
For example, if the object reference is to a container within a pod, this would take on a value like:
2574+
"spec.containers{name}" (where "name" refers to the name of the container that triggered
2575+
the event) or if no container name is specified "spec.containers[2]" (container with
2576+
index 2 in this pod). This syntax is chosen only to have some well-defined way of
2577+
referencing a part of an object.
2578+
type: string
2579+
kind:
2580+
description: |-
2581+
Kind of the referent.
2582+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
2583+
type: string
2584+
name:
2585+
description: |-
2586+
Name of the referent.
2587+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
2588+
type: string
2589+
namespace:
2590+
description: |-
2591+
Namespace of the referent.
2592+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
2593+
type: string
2594+
resourceVersion:
2595+
description: |-
2596+
Specific resourceVersion to which this reference is made, if any.
2597+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
2598+
type: string
2599+
uid:
2600+
description: |-
2601+
UID of the referent.
2602+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids
2603+
type: string
2604+
type: object
2605+
x-kubernetes-map-type: atomic
2606+
provider:
2607+
description: Encryption provider
2608+
enum:
2609+
- aescbc
2610+
- secretbox
2611+
type: string
2612+
type: object
25602613
serviceNodePortRange:
25612614
description: 'ServiceNodePortRange is the port range to reserve
25622615
for services with NodePort visibility (default: "30000-32767").'

controlplane/config/crd/bases/controlplane.cluster.x-k8s.io_rke2controlplanetemplates.yaml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,6 +1414,60 @@ spec:
14141414
pauseImage:
14151415
description: PauseImage Override image to use for pause.
14161416
type: string
1417+
secretsEncryption:
1418+
description: SecretsEncrytion defines encryption at rest
1419+
configuration
1420+
properties:
1421+
encryptionKeySecret:
1422+
description: EncyptionKey secret reference
1423+
properties:
1424+
apiVersion:
1425+
description: API version of the referent.
1426+
type: string
1427+
fieldPath:
1428+
description: |-
1429+
If referring to a piece of an object instead of an entire object, this string
1430+
should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2].
1431+
For example, if the object reference is to a container within a pod, this would take on a value like:
1432+
"spec.containers{name}" (where "name" refers to the name of the container that triggered
1433+
the event) or if no container name is specified "spec.containers[2]" (container with
1434+
index 2 in this pod). This syntax is chosen only to have some well-defined way of
1435+
referencing a part of an object.
1436+
type: string
1437+
kind:
1438+
description: |-
1439+
Kind of the referent.
1440+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
1441+
type: string
1442+
name:
1443+
description: |-
1444+
Name of the referent.
1445+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
1446+
type: string
1447+
namespace:
1448+
description: |-
1449+
Namespace of the referent.
1450+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
1451+
type: string
1452+
resourceVersion:
1453+
description: |-
1454+
Specific resourceVersion to which this reference is made, if any.
1455+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
1456+
type: string
1457+
uid:
1458+
description: |-
1459+
UID of the referent.
1460+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids
1461+
type: string
1462+
type: object
1463+
x-kubernetes-map-type: atomic
1464+
provider:
1465+
description: Encryption provider
1466+
enum:
1467+
- aescbc
1468+
- secretbox
1469+
type: string
1470+
type: object
14171471
serviceNodePortRange:
14181472
description: 'ServiceNodePortRange is the port range to
14191473
reserve for services with NodePort visibility (default:

pkg/rke2/config.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package rke2
1818

1919
import (
2020
"context"
21+
"encoding/base64"
2122
"fmt"
2223
"strings"
2324

@@ -41,6 +42,9 @@ const (
4142
// DefaultRKE2CloudProviderConfigLocation is the default location for the RKE2 cloud provider config file.
4243
DefaultRKE2CloudProviderConfigLocation = "/etc/rancher/rke2/cloud-provider-config"
4344

45+
// EncryptionConfigurationLocation a location where rke2 looks up for encryption config.
46+
EncryptionConfigurationLocation = "/var/lib/rancher/rke2/server/cred/encryption-config.json"
47+
4448
// DefaultRKE2JoinPort is the default port used for joining nodes to the cluster. It is open on the control plane nodes.
4549
DefaultRKE2JoinPort = 9345
4650

@@ -83,6 +87,8 @@ fi
8387
# Applying kernel parameters
8488
sysctl -p /etc/sysctl.d/90-rke2-cis.conf
8589
`
90+
//nolint:lll //intentionally compacted to a single line
91+
encptionConfigTemplate = `{"kind":"EncryptionConfiguration","apiVersion":"apiserver.config.k8s.io/v1","resources":[{"resources":["secrets"],"providers":[{"%s":{"keys":[{"name":"enckey","secret":"%s"}]}},{"identity":{}}]}]}`
8692
)
8793

8894
// ServerConfig is a struct that contains the information needed to generate a RKE2 server config.
@@ -137,6 +143,7 @@ type ServerConfig struct {
137143
DatastoreCAFile string `yaml:"datastore-cafile,omitempty"`
138144
DatastoreCertFile string `yaml:"datastore-certfile,omitempty"`
139145
DatastoreKeyFile string `yaml:"datastore-keyfile,omitempty"`
146+
SecretsEncryptionProvider string `yaml:"secrets-encryption-provider,omitempty"`
140147

141148
// We don't expose these fields in the API
142149
ClusterCIDR string `yaml:"cluster-cidr,omitempty"`
@@ -359,6 +366,29 @@ func newRKE2ServerConfig(opts ServerConfigOpts) (*ServerConfig, []bootstrapv1.Fi
359366
rke2ServerConfig.KubeSchedulerExtraEnv = componentMapToSlice(extraEnv, opts.ServerConfig.KubeScheduler.ExtraEnv)
360367
}
361368

369+
if opts.ServerConfig.SecretsEncryptionProvider != nil {
370+
rke2ServerConfig.SecretsEncryptionProvider = opts.ServerConfig.SecretsEncryptionProvider.Provider
371+
encryptionSecret := &corev1.Secret{}
372+
373+
if err := opts.Client.Get(opts.Ctx, types.NamespacedName{
374+
Name: opts.ServerConfig.SecretsEncryptionProvider.EncryptionKeySecret.Name,
375+
Namespace: opts.ServerConfig.SecretsEncryptionProvider.EncryptionKeySecret.Namespace,
376+
}, encryptionSecret); err != nil {
377+
return nil, nil, fmt.Errorf("failed to get encryptionKey secret: %w", err)
378+
}
379+
380+
key, ok := encryptionSecret.Data["encryptionKey"]
381+
if !ok {
382+
return nil, nil, errors.New("encryptionKey secret missing 'encryptionKey' key")
383+
}
384+
files = append(files, bootstrapv1.File{
385+
Path: EncryptionConfigurationLocation,
386+
Content: generateEncryptionConfig(opts.ServerConfig.SecretsEncryptionProvider.Provider, string(base64.StdEncoding.EncodeToString(key))),
387+
Owner: consts.DefaultFileOwner,
388+
Permissions: "0400",
389+
})
390+
}
391+
362392
if opts.ServerConfig.KubeControllerManager != nil {
363393
rke2ServerConfig.KubeControllerManagerArgs = opts.ServerConfig.KubeControllerManager.ExtraArgs
364394
rke2ServerConfig.KubeControllerManagerImage = opts.ServerConfig.KubeControllerManager.OverrideImage
@@ -727,3 +757,7 @@ func componentMapToSlice(componentType componentType, input map[string]string) [
727757

728758
return result
729759
}
760+
761+
func generateEncryptionConfig(provider, key string) string {
762+
return fmt.Sprintf(encptionConfigTemplate, provider, key)
763+
}

pkg/rke2/config_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ import (
3131
"github.com/rancher/cluster-api-provider-rke2/pkg/consts"
3232
)
3333

34+
const (
35+
expEncryptionConfig = `{"kind":"EncryptionConfiguration","apiVersion":"apiserver.config.k8s.io/v1","resources":[{"resources":["secrets"],"providers":[{"secretbox":{"keys":[{"name":"enckey","secret":"dGVzdF9lbmNyeXB0aW9uX2tleQ=="}]}},{"identity":{}}]}]}`
36+
)
37+
3438
var _ = Describe("RKE2ServerConfig", func() {
3539
var opts *ServerConfigOpts
3640

@@ -274,6 +278,67 @@ var _ = Describe("RKE2ServerConfig", func() {
274278
})
275279
})
276280

281+
var _ = Describe("RKE2 Server Config with secretbox encryption", func() {
282+
var opts *ServerConfigOpts
283+
284+
BeforeEach(func() {
285+
286+
opts = &ServerConfigOpts{
287+
Token: "token",
288+
Cluster: v1beta1.Cluster{
289+
Spec: v1beta1.ClusterSpec{
290+
ClusterNetwork: &v1beta1.ClusterNetwork{
291+
Pods: &v1beta1.NetworkRanges{
292+
CIDRBlocks: []string{
293+
"192.168.0.0/16",
294+
},
295+
},
296+
Services: &v1beta1.NetworkRanges{
297+
CIDRBlocks: []string{
298+
"192.169.0.0/16",
299+
},
300+
},
301+
},
302+
},
303+
},
304+
ControlPlaneEndpoint: "testendpoint",
305+
Ctx: context.Background(),
306+
Client: fake.NewClientBuilder().WithObjects(
307+
&corev1.Secret{
308+
ObjectMeta: metav1.ObjectMeta{
309+
Name: "encryption-key",
310+
Namespace: "test",
311+
},
312+
Data: map[string][]byte{
313+
"encryptionKey": []byte("test_encryption_key"),
314+
},
315+
},
316+
).Build(),
317+
ServerConfig: controlplanev1.RKE2ServerConfig{
318+
BindAddress: "testbindaddress",
319+
CNI: controlplanev1.Cilium,
320+
ClusterDNS: "testdns",
321+
ClusterDomain: "testdomain",
322+
SecretsEncryptionProvider: &controlplanev1.SecretsEncryption{
323+
Provider: "secretbox",
324+
EncryptionKeySecret: &corev1.ObjectReference{
325+
Name: "encryption-key",
326+
Namespace: "test",
327+
},
328+
},
329+
},
330+
}
331+
})
332+
333+
It("should succefully generate a server config with secretbox key provider", func() {
334+
rke2ServerConfig, files, err := GenerateInitControlPlaneConfig(*opts)
335+
Expect(err).ToNot(HaveOccurred())
336+
337+
Expect(rke2ServerConfig.SecretsEncryptionProvider).To(Equal("secretbox"))
338+
Expect(files[0].Content).To(Equal(expEncryptionConfig))
339+
})
340+
})
341+
277342
var _ = Describe("RKE2 Agent Config", func() {
278343
var opts *AgentConfigOpts
279344

0 commit comments

Comments
 (0)