1
2
3
4
19
20 package transformation
21
22 import (
23 "bytes"
24 "context"
25 "crypto/aes"
26 "encoding/base64"
27 "encoding/binary"
28 "fmt"
29 "math/rand"
30 "os"
31 "path/filepath"
32 "regexp"
33 "strings"
34 "testing"
35 "time"
36
37 "github.com/google/go-cmp/cmp"
38 clientv3 "go.etcd.io/etcd/client/v3"
39 "golang.org/x/crypto/cryptobyte"
40
41 apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
42 "k8s.io/apimachinery/pkg/api/meta"
43 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
44 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme"
45 "k8s.io/apimachinery/pkg/runtime/schema"
46 "k8s.io/apimachinery/pkg/util/sets"
47 "k8s.io/apimachinery/pkg/util/wait"
48 "k8s.io/apiserver/pkg/features"
49 genericapiserver "k8s.io/apiserver/pkg/server"
50 encryptionconfigcontroller "k8s.io/apiserver/pkg/server/options/encryptionconfig/controller"
51 "k8s.io/apiserver/pkg/storage/value"
52 aestransformer "k8s.io/apiserver/pkg/storage/value/encrypt/aes"
53 mock "k8s.io/apiserver/pkg/storage/value/encrypt/envelope/testing/v1beta1"
54 utilfeature "k8s.io/apiserver/pkg/util/feature"
55 "k8s.io/client-go/dynamic"
56 "k8s.io/client-go/rest"
57 featuregatetesting "k8s.io/component-base/featuregate/testing"
58 kmsapi "k8s.io/kms/apis/v1beta1"
59 "k8s.io/kubernetes/test/integration"
60 "k8s.io/kubernetes/test/integration/etcd"
61 "k8s.io/kubernetes/test/integration/framework"
62 )
63
64 const (
65 dekKeySizeLen = 2
66 kmsAPIVersion = "v1beta1"
67 )
68
69 type envelope struct {
70 providerName string
71 rawEnvelope []byte
72 plainTextDEK []byte
73 }
74
75 func (r envelope) prefix() string {
76 return fmt.Sprintf("k8s:enc:kms:v1:%s:", r.providerName)
77 }
78
79 func (r envelope) prefixLen() int {
80 return len(r.prefix())
81 }
82
83 func (r envelope) dekLen() int {
84
85 return int(binary.BigEndian.Uint16(r.rawEnvelope[r.prefixLen() : r.prefixLen()+dekKeySizeLen]))
86 }
87
88 func (r envelope) cipherTextDEK() []byte {
89 return r.rawEnvelope[r.prefixLen()+dekKeySizeLen : r.prefixLen()+dekKeySizeLen+r.dekLen()]
90 }
91
92 func (r envelope) startOfPayload(providerName string) int {
93 return r.prefixLen() + dekKeySizeLen + r.dekLen()
94 }
95
96 func (r envelope) cipherTextPayload() []byte {
97 return r.rawEnvelope[r.startOfPayload(r.providerName):]
98 }
99
100 func (r envelope) plainTextPayload(secretETCDPath string) ([]byte, error) {
101 block, err := aes.NewCipher(r.plainTextDEK)
102 if err != nil {
103 return nil, fmt.Errorf("failed to initialize AES Cipher: %v", err)
104 }
105
106 ctx := context.Background()
107 dataCtx := value.DefaultContext([]byte(secretETCDPath))
108 aesgcmTransformer, err := aestransformer.NewGCMTransformer(block)
109 if err != nil {
110 return nil, fmt.Errorf("failed to create transformer from block: %v", err)
111 }
112 plainSecret, _, err := aesgcmTransformer.TransformFromStorage(ctx, r.cipherTextPayload(), dataCtx)
113 if err != nil {
114 return nil, fmt.Errorf("failed to transform from storage via AESGCM, err: %w", err)
115 }
116
117 return plainSecret, nil
118 }
119
120
121
122
123
124
125
126
127
128
129
130
131 func TestKMSProvider(t *testing.T) {
132 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv1, true)()
133
134 encryptionConfig := `
135 kind: EncryptionConfiguration
136 apiVersion: apiserver.config.k8s.io/v1
137 resources:
138 - resources:
139 - secrets
140 providers:
141 - kms:
142 name: kms-provider
143 cachesize: 1000
144 endpoint: unix:///@kms-provider.sock
145 `
146 providerName := "kms-provider"
147 pluginMock := mock.NewBase64Plugin(t, "@kms-provider.sock")
148 test, err := newTransformTest(t, encryptionConfig, false, "", nil)
149 if err != nil {
150 t.Fatalf("failed to start KUBE API Server with encryptionConfig\n %s, error: %v", encryptionConfig, err)
151 }
152 defer test.cleanUp()
153
154 test.secret, err = test.createSecret(testSecret, testNamespace)
155 if err != nil {
156 t.Fatalf("Failed to create test secret, error: %v", err)
157 }
158
159
160 plainTextDEK := pluginMock.LastEncryptRequest()
161
162 secretETCDPath := test.getETCDPathForResource(test.storageConfig.Prefix, "", "secrets", test.secret.Name, test.secret.Namespace)
163 rawEnvelope, err := test.getRawSecretFromETCD()
164 if err != nil {
165 t.Fatalf("failed to read %s from etcd: %v", secretETCDPath, err)
166 }
167 envelopeData := envelope{
168 providerName: providerName,
169 rawEnvelope: rawEnvelope,
170 plainTextDEK: plainTextDEK,
171 }
172
173 wantPrefix := "k8s:enc:kms:v1:kms-provider:"
174 if !bytes.HasPrefix(rawEnvelope, []byte(wantPrefix)) {
175 t.Fatalf("expected secret to be prefixed with %s, but got %s", wantPrefix, rawEnvelope)
176 }
177
178 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
179 defer cancel()
180 decryptResponse, err := pluginMock.Decrypt(ctx, &kmsapi.DecryptRequest{Version: kmsAPIVersion, Cipher: envelopeData.cipherTextDEK()})
181 if err != nil {
182 t.Fatalf("failed to decrypt DEK, %v", err)
183 }
184 dekPlainAsWouldBeSeenByETCD := decryptResponse.Plain
185
186 if !bytes.Equal(plainTextDEK, dekPlainAsWouldBeSeenByETCD) {
187 t.Fatalf("expected plainTextDEK %v to be passed to KMS Plugin, but got %s",
188 plainTextDEK, dekPlainAsWouldBeSeenByETCD)
189 }
190
191 plainSecret, err := envelopeData.plainTextPayload(secretETCDPath)
192 if err != nil {
193 t.Fatalf("failed to transform from storage via AESCBC, err: %v", err)
194 }
195
196 if !strings.Contains(string(plainSecret), secretVal) {
197 t.Fatalf("expected %q after decryption, but got %q", secretVal, string(plainSecret))
198 }
199
200 secretClient := test.restClient.CoreV1().Secrets(testNamespace)
201
202 s, err := secretClient.Get(ctx, testSecret, metav1.GetOptions{})
203 if err != nil {
204 t.Fatalf("failed to get Secret from %s, err: %v", testNamespace, err)
205 }
206 if secretVal != string(s.Data[secretKey]) {
207 t.Fatalf("expected %s from KubeAPI, but got %s", secretVal, string(s.Data[secretKey]))
208 }
209
210
211 oldSecretBytes, err := base64.StdEncoding.DecodeString(oldSecret)
212 if err != nil {
213 t.Fatalf("failed to base64 decode old secret, err: %v", err)
214 }
215 oldKeyBytes, err := base64.StdEncoding.DecodeString(oldAESCBCKey)
216 if err != nil {
217 t.Fatalf("failed to base64 decode old key, err: %v", err)
218 }
219 block, err := aes.NewCipher(oldKeyBytes)
220 if err != nil {
221 t.Fatalf("invalid key, err: %v", err)
222 }
223
224 oldEncryptedSecretBytes, err := aestransformer.NewCBCTransformer(block).TransformToStorage(ctx, oldSecretBytes, value.DefaultContext(secretETCDPath))
225 if err != nil {
226 t.Fatalf("failed to encrypt old secret, err: %v", err)
227 }
228
229 oldEncryptedSecretBuf := cryptobyte.NewBuilder(nil)
230 oldEncryptedSecretBuf.AddBytes([]byte(wantPrefix))
231 oldEncryptedSecretBuf.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
232 b.AddBytes([]byte(oldAESCBCKey))
233 })
234 oldEncryptedSecretBuf.AddBytes(oldEncryptedSecretBytes)
235
236 _, err = test.writeRawRecordToETCD(secretETCDPath, oldEncryptedSecretBuf.BytesOrPanic())
237 if err != nil {
238 t.Fatalf("failed to write old encrypted secret, err: %v", err)
239 }
240
241
242 failingRawEnvelope, err := test.getRawSecretFromETCD()
243 if err != nil {
244 t.Fatalf("failed to read %s from etcd: %v", secretETCDPath, err)
245 }
246 failingOldEnvelope := envelope{
247 providerName: providerName,
248 rawEnvelope: failingRawEnvelope,
249 plainTextDEK: oldKeyBytes,
250 }
251 failingOldPlainSecret, err := failingOldEnvelope.plainTextPayload(secretETCDPath)
252 if err == nil {
253 t.Fatalf("AESGCM decryption failure not seen, data: %s", string(failingOldPlainSecret))
254 }
255
256
257 oldSecretObj, err := secretClient.Get(ctx, testSecret, metav1.GetOptions{})
258 if err != nil {
259 t.Fatalf("failed to read old secret via Kube API, err: %v", err)
260 }
261 if oldSecretVal != string(oldSecretObj.Data[secretKey]) {
262 t.Fatalf("expected %s from KubeAPI, but got %s", oldSecretVal, string(oldSecretObj.Data[secretKey]))
263 }
264
265
266 oldSecretUpdated, err := secretClient.Update(ctx, oldSecretObj, metav1.UpdateOptions{})
267 if err != nil {
268 t.Fatalf("failed to update old secret via Kube API, err: %v", err)
269 }
270 if oldSecretObj.ResourceVersion == oldSecretUpdated.ResourceVersion {
271 t.Fatalf("old secret not updated on no-op write: %s", oldSecretObj.ResourceVersion)
272 }
273
274
275 oldRawEnvelope, err := test.getRawSecretFromETCD()
276 if err != nil {
277 t.Fatalf("failed to read %s from etcd: %v", secretETCDPath, err)
278 }
279 oldEnvelope := envelope{
280 providerName: providerName,
281 rawEnvelope: oldRawEnvelope,
282 plainTextDEK: pluginMock.LastEncryptRequest(),
283 }
284 if !bytes.HasPrefix(oldRawEnvelope, []byte(wantPrefix)) {
285 t.Fatalf("expected secret to be prefixed with %s, but got %s", wantPrefix, oldRawEnvelope)
286 }
287 oldPlainSecret, err := oldEnvelope.plainTextPayload(secretETCDPath)
288 if err != nil {
289 t.Fatalf("failed to transform from storage via AESGCM, err: %v", err)
290 }
291 if !strings.Contains(string(oldPlainSecret), oldSecretVal) {
292 t.Fatalf("expected %q after decryption, but got %q", oldSecretVal, string(oldPlainSecret))
293 }
294 }
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309 func TestEncryptionConfigHotReload(t *testing.T) {
310 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv1, true)()
311
312
313 encryptionconfigcontroller.EncryptionConfigFileChangePollDuration = time.Second
314
315 storageConfig := framework.SharedEtcd()
316 encryptionConfig := `
317 kind: EncryptionConfiguration
318 apiVersion: apiserver.config.k8s.io/v1
319 resources:
320 - resources:
321 - secrets
322 providers:
323 - kms:
324 name: kms-provider
325 cachesize: 1000
326 endpoint: unix:///@kms-provider.sock
327 `
328
329 genericapiserver.SetHostnameFuncForTests("testAPIServerID")
330 _ = mock.NewBase64Plugin(t, "@kms-provider.sock")
331 var restarted bool
332 test, err := newTransformTest(t, encryptionConfig, true, "", storageConfig)
333 if err != nil {
334 t.Fatalf("failed to start KUBE API Server with encryptionConfig\n %s, error: %v", encryptionConfig, err)
335 }
336 defer func() {
337 if !restarted {
338 test.cleanUp()
339 }
340 }()
341 ctx := testContext(t)
342
343
344 copyConfig := rest.CopyConfig(test.kubeAPIServer.ClientConfig)
345 copyConfig.GroupVersion = &schema.GroupVersion{}
346 copyConfig.NegotiatedSerializer = unstructuredscheme.NewUnstructuredNegotiatedSerializer()
347 rc, err := rest.RESTClientFor(copyConfig)
348 if err != nil {
349 t.Fatal(err)
350 }
351 if err := rc.Delete().AbsPath("/metrics").Do(ctx).Error(); err != nil {
352 t.Fatal(err)
353 }
354
355
356
357 wantMetricStrings := []string{
358 `apiserver_encryption_config_controller_automatic_reload_last_timestamp_seconds{apiserver_id_hash="sha256:3c607df3b2bf22c9d9f01d5314b4bbf411c48ef43ff44ff29b1d55b41367c795",status="success"} FP`,
359 `apiserver_encryption_config_controller_automatic_reload_success_total{apiserver_id_hash="sha256:3c607df3b2bf22c9d9f01d5314b4bbf411c48ef43ff44ff29b1d55b41367c795"} 2`,
360 `apiserver_encryption_config_controller_automatic_reloads_total{apiserver_id_hash="sha256:3c607df3b2bf22c9d9f01d5314b4bbf411c48ef43ff44ff29b1d55b41367c795",status="success"} 2`,
361 }
362
363 test.secret, err = test.createSecret(testSecret, testNamespace)
364 if err != nil {
365 t.Fatalf("Failed to create test secret, error: %v", err)
366 }
367
368
369 _, err = test.createSecret(fmt.Sprintf("%s-%s", testSecret, "1"), "default")
370 if err != nil {
371 t.Fatalf("Failed to create test secret in default namespace, error: %v", err)
372 }
373
374 _, err = test.createConfigMap(testConfigmap, testNamespace)
375 if err != nil {
376 t.Fatalf("Failed to create test configmap, error: %v", err)
377 }
378
379
380 mustBeHealthy(t, "/poststarthook/start-encryption-provider-config-automatic-reload", "ok", test.kubeAPIServer.ClientConfig)
381
382 encryptionConfigWithNewProvider := `
383 kind: EncryptionConfiguration
384 apiVersion: apiserver.config.k8s.io/v1
385 resources:
386 - resources:
387 - secrets
388 providers:
389 - kms:
390 name: new-kms-provider-for-secrets
391 cachesize: 1000
392 endpoint: unix:///@new-kms-provider.sock
393 - kms:
394 name: kms-provider
395 cachesize: 1000
396 endpoint: unix:///@kms-provider.sock
397 - resources:
398 - configmaps
399 providers:
400 - kms:
401 name: new-kms-provider-for-configmaps
402 cachesize: 1000
403 endpoint: unix:///@new-kms-provider.sock
404 - identity: {}
405 `
406
407 _ = mock.NewBase64Plugin(t, "@new-kms-provider.sock")
408
409 updateFile(t, test.configDir, encryptionConfigFileName, []byte(encryptionConfigWithNewProvider))
410
411 wantPrefixForSecrets := "k8s:enc:kms:v1:new-kms-provider-for-secrets:"
412
413
414
415 verifyIfKMSTransformersSwapped(t, wantPrefixForSecrets, "", test)
416
417
418
419
420 secretsList, err := test.restClient.CoreV1().Secrets("").List(
421 ctx,
422 metav1.ListOptions{},
423 )
424 if err != nil {
425 t.Fatalf("failed to list secrets, err: %v", err)
426 }
427
428 for _, secret := range secretsList.Items {
429
430 _, err = test.restClient.CoreV1().Secrets(secret.Namespace).Update(
431 ctx,
432 &secret,
433 metav1.UpdateOptions{},
434 )
435 if err != nil {
436 t.Fatalf("failed to update secret, err: %v", err)
437 }
438 }
439
440
441 configmapsList, err := test.restClient.CoreV1().ConfigMaps("").List(
442 ctx,
443 metav1.ListOptions{},
444 )
445 if err != nil {
446 t.Fatalf("failed to list configmaps, err: %v", err)
447 }
448
449 for _, configmap := range configmapsList.Items {
450
451 _, err = test.restClient.CoreV1().ConfigMaps(configmap.Namespace).Update(
452 ctx,
453 &configmap,
454 metav1.UpdateOptions{},
455 )
456 if err != nil {
457 t.Fatalf("failed to update configmap, err: %v", err)
458 }
459 }
460
461
462 secretETCDPath := test.getETCDPathForResource(test.storageConfig.Prefix, "", "secrets", test.secret.Name, test.secret.Namespace)
463 rawEnvelope, err := test.getRawSecretFromETCD()
464 if err != nil {
465 t.Fatalf("failed to read %s from etcd: %v", secretETCDPath, err)
466 }
467
468
469 if !bytes.HasPrefix(rawEnvelope, []byte(wantPrefixForSecrets)) {
470 t.Fatalf("expected secret to be prefixed with %s, but got %s", wantPrefixForSecrets, rawEnvelope)
471 }
472
473 rawConfigmapEnvelope, err := test.readRawRecordFromETCD(test.getETCDPathForResource(test.storageConfig.Prefix, "", "configmaps", testConfigmap, testNamespace))
474 if err != nil {
475 t.Fatalf("failed to read %s from etcd: %v", test.getETCDPathForResource(test.storageConfig.Prefix, "", "configmaps", testConfigmap, testNamespace), err)
476 }
477
478
479 wantPrefixForConfigmaps := "k8s:enc:kms:v1:new-kms-provider-for-configmaps:"
480 if !bytes.HasPrefix(rawConfigmapEnvelope.Kvs[0].Value, []byte(wantPrefixForConfigmaps)) {
481 t.Fatalf("expected configmap to be prefixed with %s, but got %s", wantPrefixForConfigmaps, rawConfigmapEnvelope.Kvs[0].Value)
482 }
483
484
485
486
487
488
489 encryptionConfigWithoutOldProvider := `
490 kind: EncryptionConfiguration
491 apiVersion: apiserver.config.k8s.io/v1
492 resources:
493 - resources:
494 - secrets
495 providers:
496 - kms:
497 name: new-kms-provider-for-secrets
498 cachesize: 1000
499 endpoint: unix:///@new-kms-provider.sock
500 - resources:
501 - configmaps
502 providers:
503 - kms:
504 name: new-kms-provider-for-configmaps
505 cachesize: 1000
506 endpoint: unix:///@new-kms-provider.sock
507 - resources:
508 - '*.*'
509 providers:
510 - kms:
511 name: kms-provider-to-encrypt-all
512 cachesize: 1000
513 endpoint: unix:///@new-encrypt-all-kms-provider.sock
514 - identity: {}
515 `
516
517 _ = mock.NewBase64Plugin(t, "@new-encrypt-all-kms-provider.sock")
518
519
520 updateFile(t, test.configDir, encryptionConfigFileName, []byte(encryptionConfigWithoutOldProvider))
521
522 wantPrefixForEncryptAll := "k8s:enc:kms:v1:kms-provider-to-encrypt-all:"
523
524
525 verifyIfKMSTransformersSwapped(t, wantPrefixForSecrets, wantPrefixForEncryptAll, test)
526
527
528 _, err = test.restClient.CoreV1().Secrets(testNamespace).Get(
529 ctx,
530 testSecret,
531 metav1.GetOptions{},
532 )
533 if err != nil {
534 t.Fatalf("failed to read secret, err: %v", err)
535 }
536
537
538 _, err = test.restClient.CoreV1().Secrets("").List(ctx, metav1.ListOptions{})
539 if err != nil {
540 t.Fatalf("failed to list secrets, err: %v", err)
541 }
542
543
544 _, err = test.restClient.CoreV1().ConfigMaps("").List(ctx, metav1.ListOptions{})
545 if err != nil {
546 t.Fatalf("failed to list configmaps, err: %v", err)
547 }
548
549
550 previousConfigDir := test.configDir
551 test.shutdownAPIServer()
552 restarted = true
553 test, err = newTransformTest(t, test.transformerConfig, true, previousConfigDir, storageConfig)
554 if err != nil {
555 t.Fatalf("failed to start KUBE API Server with encryptionConfig\n %s, error: %v", encryptionConfig, err)
556 }
557 defer test.cleanUp()
558
559 _, err = test.restClient.CoreV1().Secrets(testNamespace).Get(
560 ctx,
561 testSecret,
562 metav1.GetOptions{},
563 )
564 if err != nil {
565 t.Fatalf("failed to read secret, err: %v", err)
566 }
567
568
569 if _, err = test.restClient.CoreV1().Secrets("").List(ctx, metav1.ListOptions{}); err != nil {
570 t.Fatalf("failed to list secrets, err: %v", err)
571 }
572
573
574 if _, err = test.restClient.CoreV1().ConfigMaps("").List(ctx, metav1.ListOptions{}); err != nil {
575 t.Fatalf("failed to list configmaps, err: %v", err)
576 }
577
578
579 copyConfig = rest.CopyConfig(test.kubeAPIServer.ClientConfig)
580 copyConfig.GroupVersion = &schema.GroupVersion{}
581 copyConfig.NegotiatedSerializer = unstructuredscheme.NewUnstructuredNegotiatedSerializer()
582 rc, err = rest.RESTClientFor(copyConfig)
583 if err != nil {
584 t.Fatal(err)
585 }
586 defer func() {
587 body, err := rc.Get().AbsPath("/metrics").DoRaw(ctx)
588 if err != nil {
589 t.Fatal(err)
590 }
591 var gotMetricStrings []string
592 trimFP := regexp.MustCompile(`(.*)(} \d+\.\d+.*)`)
593 for _, line := range strings.Split(string(body), "\n") {
594 if strings.HasPrefix(line, "apiserver_encryption_config_controller_") {
595 if strings.Contains(line, "_seconds") {
596 line = trimFP.ReplaceAllString(line, `$1`) + "} FP"
597 }
598 gotMetricStrings = append(gotMetricStrings, line)
599 }
600 }
601 if diff := cmp.Diff(wantMetricStrings, gotMetricStrings); diff != "" {
602 t.Errorf("unexpected metrics diff (-want +got): %s", diff)
603 }
604 }()
605 }
606
607 func TestEncryptAll(t *testing.T) {
608 encryptionConfig := `
609 kind: EncryptionConfiguration
610 apiVersion: apiserver.config.k8s.io/v1
611 resources:
612 - resources:
613 - '*.*'
614 providers:
615 - kms:
616 name: encrypt-all-kms-provider
617 cachesize: 1000
618 endpoint: unix:///@encrypt-all-kms-provider.sock
619 `
620
621 t.Run("encrypt all resources", func(t *testing.T) {
622 _ = mock.NewBase64Plugin(t, "@encrypt-all-kms-provider.sock")
623
624 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, "AllAlpha", true)()
625 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, "AllBeta", true)()
626
627 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv1, true)()
628
629 test, err := newTransformTest(t, encryptionConfig, false, "", nil)
630 if err != nil {
631 t.Fatalf("failed to start KUBE API Server with encryptionConfig")
632 }
633 defer test.cleanUp()
634
635 etcd.CreateTestCRDs(t, apiextensionsclientset.NewForConfigOrDie(test.kubeAPIServer.ClientConfig), false, etcd.GetCustomResourceDefinitionData()...)
636
637 _, serverResources, err := test.restClient.Discovery().ServerGroupsAndResources()
638 if err != nil {
639 t.Fatal(err)
640 }
641 resources := etcd.GetResources(t, serverResources)
642 client := dynamic.NewForConfigOrDie(test.kubeAPIServer.ClientConfig)
643
644 etcdStorageData := etcd.GetEtcdStorageDataForNamespace(testNamespace)
645 restResourceSet := sets.New[schema.GroupVersionResource]()
646 stubResourceSet := sets.New[schema.GroupVersionResource]()
647 for _, resource := range resources {
648 gvr := resource.Mapping.Resource
649 stub := etcdStorageData[gvr].Stub
650
651
652 if stub == "" {
653 t.Errorf("skipping resource %s because stub is empty", gvr)
654 continue
655 }
656 restResourceSet.Insert(gvr)
657 dynamicClient, obj, err := etcd.JSONToUnstructured(stub, testNamespace, &meta.RESTMapping{
658 Resource: gvr,
659 GroupVersionKind: gvr.GroupVersion().WithKind(resource.Mapping.GroupVersionKind.Kind),
660 Scope: resource.Mapping.Scope,
661 }, client)
662 if err != nil {
663 t.Fatal(err)
664 }
665
666 _, err = dynamicClient.Create(context.TODO(), obj, metav1.CreateOptions{})
667 if err != nil {
668 t.Fatal(err)
669 }
670 }
671 for gvr, data := range etcdStorageData {
672 if data.Stub == "" {
673 continue
674 }
675 stubResourceSet.Insert(gvr)
676 }
677 if !restResourceSet.Equal(stubResourceSet) {
678 t.Errorf("failed to check all REST resources: %q", restResourceSet.SymmetricDifference(stubResourceSet).UnsortedList())
679 }
680 rawClient, etcdClient, err := integration.GetEtcdClients(test.kubeAPIServer.ServerOpts.Etcd.StorageConfig.Transport)
681 if err != nil {
682 t.Fatalf("failed to create etcd client: %v", err)
683 }
684
685
686 defer rawClient.Close()
687
688 response, err := etcdClient.Get(context.TODO(), "/"+test.kubeAPIServer.ServerOpts.Etcd.StorageConfig.Prefix, clientv3.WithPrefix())
689 if err != nil {
690 t.Fatalf("failed to retrieve secret from etcd %v", err)
691 }
692
693
694 if len(response.Kvs) == 0 {
695 t.Fatalf("expected total number of keys to be greater than 0, but got %d", len(response.Kvs))
696 }
697
698
699 if len(response.Kvs) < len(resources) {
700 t.Fatalf("expected total number of keys to be greater or equal to total resources, but got %d", len(response.Kvs))
701 }
702
703 wantPrefix := "k8s:enc:kms:v1:encrypt-all-kms-provider:"
704 for _, kv := range response.Kvs {
705
706
707 if strings.Contains(kv.String(), "masterleases") ||
708 strings.Contains(kv.String(), "peerserverleases") ||
709 strings.Contains(kv.String(), "serviceips") ||
710 strings.Contains(kv.String(), "servicenodeports") {
711
712 if bytes.HasPrefix(kv.Value, []byte("k8s:enc:")) {
713 t.Errorf("expected resource %s to not be prefixed with %s, but got %s", kv.Key, "k8s:enc:", kv.Value)
714 }
715 continue
716 }
717
718
719 if !bytes.HasPrefix(kv.Value, []byte(wantPrefix)) {
720 t.Errorf("expected resource %s to be prefixed with %s, but got %s", kv.Key, wantPrefix, kv.Value)
721 }
722 }
723 })
724 }
725
726 func TestEncryptAllWithWildcard(t *testing.T) {
727 encryptionConfig := `
728 kind: EncryptionConfiguration
729 apiVersion: apiserver.config.k8s.io/v1
730 resources:
731 - resources:
732 - configmaps
733 providers:
734 - identity: {}
735 - resources:
736 - '*.batch'
737 providers:
738 - kms:
739 name: kms-provider
740 cachesize: 1000
741 endpoint: unix:///@kms-provider.sock
742 - resources:
743 - '*.*'
744 providers:
745 - kms:
746 name: encrypt-all-kms-provider
747 cachesize: 1000
748 endpoint: unix:///@encrypt-all-kms-provider.sock
749 `
750 _ = mock.NewBase64Plugin(t, "@kms-provider.sock")
751 _ = mock.NewBase64Plugin(t, "@encrypt-all-kms-provider.sock")
752
753 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv1, true)()
754
755 test, err := newTransformTest(t, encryptionConfig, false, "", nil)
756 if err != nil {
757 t.Fatalf("failed to start KUBE API Server with encryptionConfig\n %s, error: %v", encryptionConfig, err)
758 }
759 defer test.cleanUp()
760
761 wantPrefix := "k8s:enc:kms:v1:kms-provider:"
762 wantPrefixForEncryptAll := "k8s:enc:kms:v1:encrypt-all-kms-provider:"
763
764 _, err = test.createJob("test-job", "default")
765 if err != nil {
766 t.Fatalf("failed to create job: %v", err)
767 }
768
769 rawJobsEnvelope, err := test.readRawRecordFromETCD(test.getETCDPathForResource(test.storageConfig.Prefix, "", "jobs", "test-job", "default"))
770 if err != nil {
771 t.Fatalf("failed to read %s from etcd: %v", test.getETCDPathForResource(test.storageConfig.Prefix, "", "jobs", "test-job", "default"), err)
772 }
773
774
775 if !bytes.HasPrefix(rawJobsEnvelope.Kvs[0].Value, []byte(wantPrefix)) {
776 t.Fatalf("expected jobs to be prefixed with %s, but got %s", wantPrefix, rawJobsEnvelope.Kvs[0].Value)
777 }
778
779 _, err = test.createDeployment("test-deployment", "default")
780 if err != nil {
781 t.Fatalf("failed to create deployment: %v", err)
782 }
783
784 rawDeploymentsEnvelope, err := test.readRawRecordFromETCD(test.getETCDPathForResource(test.storageConfig.Prefix, "", "deployments", "test-deployment", "default"))
785 if err != nil {
786 t.Fatalf("failed to read %s from etcd: %v", test.getETCDPathForResource(test.storageConfig.Prefix, "", "deployments", "test-deployment", "default"), err)
787 }
788
789
790 if !bytes.HasPrefix(rawDeploymentsEnvelope.Kvs[0].Value, []byte(wantPrefixForEncryptAll)) {
791 t.Fatalf("expected deployments to be prefixed with %s, but got %s", wantPrefixForEncryptAll, rawDeploymentsEnvelope.Kvs[0].Value)
792 }
793
794 test.secret, err = test.createSecret(testSecret, testNamespace)
795 if err != nil {
796 t.Fatalf("Failed to create test secret, error: %v", err)
797 }
798
799 rawSecretEnvelope, err := test.getRawSecretFromETCD()
800 if err != nil {
801 t.Fatalf("failed to read secrets from etcd: %v", err)
802 }
803
804
805 if !bytes.HasPrefix(rawSecretEnvelope, []byte(wantPrefixForEncryptAll)) {
806 t.Fatalf("expected secrets to be prefixed with %s, but got %s", wantPrefixForEncryptAll, rawSecretEnvelope)
807 }
808
809 _, err = test.createConfigMap(testConfigmap, testNamespace)
810 if err != nil {
811 t.Fatalf("Failed to create test configmap, error: %v", err)
812 }
813
814 rawConfigMapEnvelope, err := test.readRawRecordFromETCD(test.getETCDPathForResource(test.storageConfig.Prefix, "", "configmaps", testConfigmap, testNamespace))
815 if err != nil {
816 t.Fatalf("failed to read configmaps from etcd: %v", err)
817 }
818
819
820 if bytes.HasPrefix(rawConfigMapEnvelope.Kvs[0].Value, []byte("k8s:enc:")) {
821 t.Fatalf("expected configmaps to be not encrypted, got %s", rawConfigMapEnvelope.Kvs[0].Value)
822 }
823 }
824
825 func TestEncryptionConfigHotReloadFilePolling(t *testing.T) {
826 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv1, true)()
827
828
829 encryptionconfigcontroller.EncryptionConfigFileChangePollDuration = time.Second
830
831 testCases := []struct {
832 sleep time.Duration
833 name string
834 updateFile func(filePath, fileContent string) error
835 }{
836 {
837 name: "truncate file",
838 updateFile: func(filePath string, fileContent string) error {
839
840 return os.WriteFile(filePath, []byte(fileContent), 0644)
841 },
842
843 sleep: 20 * time.Second,
844 },
845 {
846 name: "delete and create file",
847 updateFile: func(filePath, fileContent string) error {
848
849 if err := os.Remove(filePath); err != nil {
850 return fmt.Errorf("failed to remove encryption config, err: %w", err)
851 }
852
853 file, err := os.Create(filePath)
854 if err != nil {
855 return fmt.Errorf("failed to create encryption config, err: %w", err)
856 }
857 defer file.Close()
858
859 if _, err := file.Write([]byte(fileContent)); err != nil {
860 return fmt.Errorf("failed to write encryption config, err: %w", err)
861 }
862
863 return nil
864 },
865 },
866 {
867 name: "move file",
868 updateFile: func(filePath, fileContent string) error {
869
870 tmpFilePath := filePath + ".tmp"
871 if err := os.WriteFile(tmpFilePath, []byte(fileContent), 0644); err != nil {
872 return fmt.Errorf("failed to write config to tmp file, err: %w", err)
873 }
874
875
876 if err := os.Rename(tmpFilePath, filePath); err != nil {
877 return fmt.Errorf("failed to move encryption config, err: %w", err)
878 }
879
880 return nil
881 },
882 },
883 }
884
885 for _, tc := range testCases {
886 t.Run(tc.name, func(t *testing.T) {
887 encryptionConfig := `
888 kind: EncryptionConfiguration
889 apiVersion: apiserver.config.k8s.io/v1
890 resources:
891 - resources:
892 - secrets
893 providers:
894 - kms:
895 name: kms-provider
896 cachesize: 1000
897 endpoint: unix:///@kms-provider.sock
898 timeout: 1s
899 `
900 _ = mock.NewBase64Plugin(t, "@kms-provider.sock")
901
902 test, err := newTransformTest(t, encryptionConfig, true, "", nil)
903 if err != nil {
904 t.Fatalf("failed to start KUBE API Server with encryptionConfig\n %s, error: %v", encryptionConfig, err)
905 }
906 defer test.cleanUp()
907
908 test.secret, err = test.createSecret(testSecret, testNamespace)
909 if err != nil {
910 t.Fatalf("Failed to create test secret, error: %v", err)
911 }
912
913
914 mustBeHealthy(t, "/poststarthook/start-encryption-provider-config-automatic-reload", "ok", test.kubeAPIServer.ClientConfig)
915
916 encryptionConfigWithNewProvider := `
917 kind: EncryptionConfiguration
918 apiVersion: apiserver.config.k8s.io/v1
919 resources:
920 - resources:
921 - secrets
922 providers:
923 - kms:
924 name: new-kms-provider-for-secrets
925 cachesize: 1000
926 endpoint: unix:///@new-kms-provider.sock
927 timeout: 1s
928 - kms:
929 name: kms-provider
930 cachesize: 1000
931 endpoint: unix:///@kms-provider.sock
932 timeout: 1s
933 - resources:
934 - configmaps
935 providers:
936 - kms:
937 name: new-kms-provider-for-configmaps
938 cachesize: 1000
939 endpoint: unix:///@new-kms-provider.sock
940 timeout: 1s
941 - identity: {}
942 `
943
944 _ = mock.NewBase64Plugin(t, "@new-kms-provider.sock")
945
946 if err := tc.updateFile(filepath.Join(test.configDir, encryptionConfigFileName), encryptionConfigWithNewProvider); err != nil {
947 t.Fatalf("failed to update encryption config, err: %v", err)
948 }
949
950 wantPrefix := "k8s:enc:kms:v1:new-kms-provider-for-secrets:"
951 verifyPrefixOfSecretResource(t, wantPrefix, test)
952
953
954 if tc.sleep != 0 {
955 time.Sleep(tc.sleep)
956 }
957 _, err = test.createSecret(fmt.Sprintf("secret-%d", rand.Intn(100000)), "default")
958 if err != nil {
959 t.Fatalf("Failed to create test secret, error: %v", err)
960 }
961 _, err = test.restClient.CoreV1().Secrets("").List(
962 context.TODO(),
963 metav1.ListOptions{},
964 )
965 if err != nil {
966 t.Fatalf("failed to re-list secrets, err: %v", err)
967 }
968 })
969 }
970 }
971
972 func verifyPrefixOfSecretResource(t *testing.T, wantPrefix string, test *transformTest) {
973
974
975 verifyIfKMSTransformersSwapped(t, wantPrefix, "", test)
976
977
978 secretsList, err := test.restClient.CoreV1().Secrets("").List(
979 context.TODO(),
980 metav1.ListOptions{},
981 )
982 if err != nil {
983 t.Fatalf("failed to list secrets, err: %v", err)
984 }
985
986 for _, secret := range secretsList.Items {
987 _, err = test.restClient.CoreV1().Secrets(secret.Namespace).Update(
988 context.TODO(),
989 &secret,
990 metav1.UpdateOptions{},
991 )
992 if err != nil {
993 t.Fatalf("failed to update secret, err: %v", err)
994 }
995 }
996
997 secretETCDPath := test.getETCDPathForResource(test.storageConfig.Prefix, "", "secrets", test.secret.Name, test.secret.Namespace)
998 rawEnvelope, err := test.getRawSecretFromETCD()
999 if err != nil {
1000 t.Fatalf("failed to read %s from etcd: %v", secretETCDPath, err)
1001 }
1002
1003
1004 if !bytes.HasPrefix(rawEnvelope, []byte(wantPrefix)) {
1005 t.Fatalf("expected secret to be prefixed with %s, but got %s", wantPrefix, rawEnvelope)
1006 }
1007 }
1008
1009 func verifyIfKMSTransformersSwapped(t *testing.T, wantPrefix, wantPrefixForEncryptAll string, test *transformTest) {
1010 t.Helper()
1011
1012 var swapErr error
1013
1014
1015 idx := rand.Intn(100000)
1016
1017 pollErr := wait.PollImmediate(time.Second, wait.ForeverTestTimeout, func() (bool, error) {
1018
1019 secretName := fmt.Sprintf("secret-%d", idx)
1020 _, err := test.createSecret(secretName, "default")
1021 if err != nil {
1022 t.Fatalf("Failed to create test secret, error: %v", err)
1023 }
1024
1025 rawEnvelope, err := test.readRawRecordFromETCD(test.getETCDPathForResource(test.storageConfig.Prefix, "", "secrets", secretName, "default"))
1026 if err != nil {
1027 t.Fatalf("failed to read %s from etcd: %v", test.getETCDPathForResource(test.storageConfig.Prefix, "", "secrets", secretName, "default"), err)
1028 }
1029
1030
1031 if !bytes.HasPrefix(rawEnvelope.Kvs[0].Value, []byte(wantPrefix)) {
1032 idx++
1033
1034 swapErr = fmt.Errorf("expected secret to be prefixed with %s, but got %s", wantPrefix, rawEnvelope.Kvs[0].Value)
1035
1036
1037 return false, nil
1038 }
1039
1040 if wantPrefixForEncryptAll != "" {
1041 deploymentName := fmt.Sprintf("deployment-%d", idx)
1042 _, err := test.createDeployment(deploymentName, "default")
1043 if err != nil {
1044 t.Fatalf("Failed to create test secret, error: %v", err)
1045 }
1046
1047 rawEnvelope, err := test.readRawRecordFromETCD(test.getETCDPathForResource(test.storageConfig.Prefix, "", "deployments", deploymentName, "default"))
1048 if err != nil {
1049 t.Fatalf("failed to read %s from etcd: %v", test.getETCDPathForResource(test.storageConfig.Prefix, "", "deployments", deploymentName, "default"), err)
1050 }
1051
1052
1053 if !bytes.HasPrefix(rawEnvelope.Kvs[0].Value, []byte(wantPrefixForEncryptAll)) {
1054 idx++
1055
1056 swapErr = fmt.Errorf("expected deployment to be prefixed with %s, but got %s", wantPrefixForEncryptAll, rawEnvelope.Kvs[0].Value)
1057
1058
1059 return false, nil
1060 }
1061 }
1062
1063 return true, nil
1064 })
1065 if pollErr == wait.ErrWaitTimeout {
1066 t.Fatalf("failed to verify if kms transformers swapped, err: %v", swapErr)
1067 }
1068 }
1069
1070 func updateFile(t *testing.T, configDir, filename string, newContent []byte) {
1071 t.Helper()
1072
1073
1074 tempFile, err := os.CreateTemp(configDir, "tempfile")
1075 if err != nil {
1076 t.Fatal(err)
1077 }
1078 defer tempFile.Close()
1079
1080
1081 _, err = tempFile.Write(newContent)
1082 if err != nil {
1083 t.Fatal(err)
1084 }
1085
1086
1087 err = os.Rename(tempFile.Name(), filepath.Join(configDir, filename))
1088 if err != nil {
1089 t.Fatal(err)
1090 }
1091 }
1092
1093 func TestKMSHealthz(t *testing.T) {
1094 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv1, true)()
1095
1096 encryptionConfig := `
1097 kind: EncryptionConfiguration
1098 apiVersion: apiserver.config.k8s.io/v1
1099 resources:
1100 - resources:
1101 - secrets
1102 providers:
1103 - kms:
1104 name: provider-1
1105 endpoint: unix:///@kms-provider-1.sock
1106 - kms:
1107 name: provider-2
1108 endpoint: unix:///@kms-provider-2.sock
1109 `
1110
1111 pluginMock1 := mock.NewBase64Plugin(t, "@kms-provider-1.sock")
1112 pluginMock2 := mock.NewBase64Plugin(t, "@kms-provider-2.sock")
1113
1114 test, err := newTransformTest(t, encryptionConfig, false, "", nil)
1115 if err != nil {
1116 t.Fatalf("failed to start kube-apiserver, error: %v", err)
1117 }
1118 defer test.cleanUp()
1119
1120
1121
1122
1123
1124 mustBeHealthy(t, "/kms-provider-0", "ok", test.kubeAPIServer.ClientConfig)
1125 mustBeHealthy(t, "/kms-provider-1", "ok", test.kubeAPIServer.ClientConfig)
1126 mustNotHaveLivez(t, "/kms-provider-0", "404 page not found", test.kubeAPIServer.ClientConfig)
1127 mustNotHaveLivez(t, "/kms-provider-1", "404 page not found", test.kubeAPIServer.ClientConfig)
1128
1129
1130
1131 pluginMock1.EnterFailedState()
1132 mustBeUnHealthy(t, "/kms-provider-0",
1133 "internal server error: rpc error: code = FailedPrecondition desc = failed precondition - key disabled",
1134 test.kubeAPIServer.ClientConfig)
1135
1136 mustNotHaveLivez(t, "/kms-provider-0", "404 page not found", test.kubeAPIServer.ClientConfig)
1137 mustBeHealthy(t, "/kms-provider-1", "ok", test.kubeAPIServer.ClientConfig)
1138 mustNotHaveLivez(t, "/kms-provider-1", "404 page not found", test.kubeAPIServer.ClientConfig)
1139 pluginMock1.ExitFailedState()
1140
1141
1142
1143 pluginMock2.EnterFailedState()
1144 mustBeHealthy(t, "/kms-provider-0", "ok", test.kubeAPIServer.ClientConfig)
1145 mustBeUnHealthy(t, "/kms-provider-1",
1146 "internal server error: rpc error: code = FailedPrecondition desc = failed precondition - key disabled",
1147 test.kubeAPIServer.ClientConfig)
1148 pluginMock2.ExitFailedState()
1149
1150
1151
1152 mustBeHealthy(t, "/kms-provider-0", "ok", test.kubeAPIServer.ClientConfig)
1153 mustBeHealthy(t, "/kms-provider-1", "ok", test.kubeAPIServer.ClientConfig)
1154 }
1155
1156 func TestKMSHealthzWithReload(t *testing.T) {
1157 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv1, true)()
1158
1159 encryptionConfig := `
1160 kind: EncryptionConfiguration
1161 apiVersion: apiserver.config.k8s.io/v1
1162 resources:
1163 - resources:
1164 - secrets
1165 providers:
1166 - kms:
1167 name: provider-1
1168 endpoint: unix:///@kms-provider-1.sock
1169 - kms:
1170 name: provider-2
1171 endpoint: unix:///@kms-provider-2.sock
1172 `
1173
1174 pluginMock1 := mock.NewBase64Plugin(t, "@kms-provider-1.sock")
1175 pluginMock2 := mock.NewBase64Plugin(t, "@kms-provider-2.sock")
1176
1177 test, err := newTransformTest(t, encryptionConfig, true, "", nil)
1178 if err != nil {
1179 t.Fatalf("Failed to start kube-apiserver, error: %v", err)
1180 }
1181 defer test.cleanUp()
1182
1183
1184
1185
1186
1187 mustBeHealthy(t, "/kms-providers", "ok", test.kubeAPIServer.ClientConfig)
1188
1189
1190
1191 pluginMock1.EnterFailedState()
1192 mustBeUnHealthy(t, "/kms-providers",
1193 "internal server error: kms-provider-0: failed to perform encrypt section of the healthz check for KMS Provider provider-1, error: rpc error: code = FailedPrecondition desc = failed precondition - key disabled",
1194 test.kubeAPIServer.ClientConfig)
1195 pluginMock1.ExitFailedState()
1196
1197
1198
1199 pluginMock2.EnterFailedState()
1200 mustBeUnHealthy(t, "/kms-providers",
1201 "internal server error: kms-provider-1: failed to perform encrypt section of the healthz check for KMS Provider provider-2, error: rpc error: code = FailedPrecondition desc = failed precondition - key disabled",
1202 test.kubeAPIServer.ClientConfig)
1203 pluginMock2.ExitFailedState()
1204
1205
1206
1207 mustBeHealthy(t, "/kms-providers", "ok", test.kubeAPIServer.ClientConfig)
1208 }
1209
View as plain text