Skip to content

Commit 506e4fe

Browse files
committed
In TestListCorruptObject corrupt the object in etcd instead of changing encryption key
Changing the encryption key doesn't work with watch cache as it doesn't break decoding newly written objects. A new object will be written using a new key, and decoded using a new key.
1 parent 381ccf0 commit 506e4fe

File tree

1 file changed

+55
-60
lines changed

1 file changed

+55
-60
lines changed

test/integration/controlplane/transformation/secrets_transformation_test.go

Lines changed: 55 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"testing"
3131
"time"
3232

33+
clientv3 "go.etcd.io/etcd/client/v3"
3334
rbacv1 "k8s.io/api/rbac/v1"
3435
apierrors "k8s.io/apimachinery/pkg/api/errors"
3536
"k8s.io/apimachinery/pkg/api/meta"
@@ -47,6 +48,7 @@ import (
4748
"k8s.io/client-go/tools/cache"
4849
featuregatetesting "k8s.io/component-base/featuregate/testing"
4950
"k8s.io/kubernetes/test/integration/authutil"
51+
"k8s.io/kubernetes/test/integration/framework"
5052
"k8s.io/utils/ptr"
5153
)
5254

@@ -431,19 +433,26 @@ func TestListCorruptObjects(t *testing.T) {
431433
// secrets that are created before encryption breaks
432434
secrets []string
433435
// whether encryption broke after the config change
434-
encryptionBrokenFn func(t *testing.T, got apierrors.APIStatus) bool
436+
encryptionBrokenFn func(t *testing.T, got apierrors.APIStatus)
435437
// what we expect for LIST on the corrupt objects after encryption has broken
436438
listAfter verifier
437439
}{
438440
{
439441
secrets: secrets,
440442
featureEnabled: true,
441-
encryptionBrokenFn: func(t *testing.T, got apierrors.APIStatus) bool {
442-
// the new encryption config does not have the old key, so reading of resources
443-
// created before the encryption change will fail with 'no matching prefix found'
444-
return got.Status().Reason == metav1.StatusReasonInternalError &&
445-
strings.Contains(got.Status().Message, "Internal error occurred: StorageError: corrupt object") &&
446-
strings.Contains(got.Status().Message, "data from the storage is not transformable revision=0: no matching prefix found")
443+
encryptionBrokenFn: func(t *testing.T, got apierrors.APIStatus) {
444+
status := got.Status()
445+
if status.Reason != metav1.StatusReasonInternalError {
446+
t.Errorf("Invalid reason, got: %q, want: %q", status.Reason, metav1.StatusReasonInternalError)
447+
}
448+
corruptObjectMsg := "Internal error occurred: StorageError: corrupt object"
449+
if !strings.Contains(status.Message, corruptObjectMsg) {
450+
t.Errorf("Message should include %q, but got: %q", corruptObjectMsg, status.Message)
451+
}
452+
messageAuthenticationFailedMsg := "data from the storage is not transformable revision=0: cipher: message authentication failed"
453+
if !strings.Contains(status.Message, messageAuthenticationFailedMsg) {
454+
t.Errorf("Message should include %q, but got: %q", messageAuthenticationFailedMsg, status.Message)
455+
}
447456
},
448457
listAfter: wantAPIStatusError{
449458
reason: metav1.StatusReasonStoreReadError,
@@ -482,11 +491,15 @@ func TestListCorruptObjects(t *testing.T) {
482491
{
483492
secrets: secrets,
484493
featureEnabled: false,
485-
encryptionBrokenFn: func(t *testing.T, got apierrors.APIStatus) bool {
486-
// the new encryption config does not have the old key, so reading of resources
487-
// created before the encryption change will fail with 'no matching prefix found'
488-
return got.Status().Reason == metav1.StatusReasonInternalError &&
489-
strings.Contains(got.Status().Message, "Internal error occurred: no matching prefix found")
494+
encryptionBrokenFn: func(t *testing.T, got apierrors.APIStatus) {
495+
status := got.Status()
496+
if status.Reason != metav1.StatusReasonInternalError {
497+
t.Errorf("Invalid reason, got: %q, want: %q", status.Reason, metav1.StatusReasonInternalError)
498+
}
499+
noMatchingPrefixFoundMsg := "Internal error occurred: cipher: message authentication failed"
500+
if !strings.Contains(status.Message, noMatchingPrefixFoundMsg) {
501+
t.Errorf("Message should include %q, but got: %q", noMatchingPrefixFoundMsg, status.Message)
502+
}
490503
},
491504
listAfter: wantAPIStatusError{
492505
reason: metav1.StatusReasonInternalError,
@@ -497,12 +510,13 @@ func TestListCorruptObjects(t *testing.T) {
497510
for _, tc := range tests {
498511
t.Run(fmt.Sprintf("%s/%t", string(genericfeatures.AllowUnsafeMalformedObjectDeletion), tc.featureEnabled), func(t *testing.T) {
499512
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.AllowUnsafeMalformedObjectDeletion, tc.featureEnabled)
500-
501-
test, err := newTransformTest(t, transformTestConfig{transformerConfigYAML: aesGCMConfigYAML, reload: true})
513+
storageConfig := framework.SharedEtcd()
514+
test, err := newTransformTest(t, transformTestConfig{transformerConfigYAML: aesGCMConfigYAML, reload: true, storageConfig: storageConfig})
502515
if err != nil {
503516
t.Fatalf("failed to setup test for envelop %s, error was %v", aesGCMPrefix, err)
504517
}
505518
defer test.cleanUp()
519+
ctx := context.Background()
506520

507521
// a) create a number of secrets in the test namespace
508522
for _, name := range tc.secrets {
@@ -523,64 +537,45 @@ func TestListCorruptObjects(t *testing.T) {
523537

524538
// c) override the config and break decryption of the old resources,
525539
// the secret created in step a will be undecryptable
526-
encryptionConf := filepath.Join(test.configDir, encryptionConfigFileName)
527-
body, _ := ioutil.ReadFile(encryptionConf)
528-
t.Logf("file before write: %s", body)
529-
// we replace the existing key with a new key from a different provider
530-
if err := os.WriteFile(encryptionConf, []byte(aesCBCConfigYAML), 0o644); err != nil {
531-
t.Fatalf("failed to write encryption config that's going to make decryption fail")
540+
client, err := clientv3.New(clientv3.Config{Endpoints: storageConfig.Transport.ServerList})
541+
if err != nil {
542+
t.Fatal(err)
532543
}
533-
body, _ = ioutil.ReadFile(encryptionConf)
534-
t.Logf("file after write: %s", body)
535-
536-
// d) wait for the breaking changes to take effect
537-
testCtx, cancel := context.WithCancel(context.Background())
538-
defer cancel()
539-
// TODO: dynamic encryption config reload takes about 1m, so can't use
540-
// wait.ForeverTestTimeout just yet, investigate and reduce the reload time.
541-
err = wait.PollUntilContextTimeout(testCtx, 1*time.Second, 2*time.Minute, true, func(ctx context.Context) (done bool, err error) {
542-
_, err = test.restClient.CoreV1().Secrets(testNamespace).Get(ctx, tc.secrets[0], metav1.GetOptions{})
543-
544+
defer func() {
545+
err := client.Close()
544546
if err != nil {
545-
t.Logf("get returned error: %#v message: %s", err, err.Error())
546-
}
547-
548-
var got apierrors.APIStatus
549-
if !errors.As(err, &got) {
550-
return false, nil
551-
}
552-
if done := tc.encryptionBrokenFn(t, got); done {
553-
return true, nil
547+
t.Fatal(err)
554548
}
555-
return false, nil
556-
})
549+
}()
550+
resp, err := client.Get(ctx, "/"+storageConfig.Prefix+"/secrets/", clientv3.WithPrefix())
557551
if err != nil {
558-
t.Fatalf("encryption never broke: %v", err)
552+
t.Fatal(err)
553+
}
554+
if len(resp.Kvs) != len(tc.secrets) {
555+
t.Fatalf("Expected %d number of keys, got: %d", len(tc.secrets), len(resp.Kvs))
556+
}
557+
for _, kv := range resp.Kvs {
558+
// Remove last byte
559+
_, err = client.Put(ctx, string(kv.Key), string(kv.Value)[:len(kv.Value)-1])
560+
if err != nil {
561+
t.Fatal(err)
562+
}
559563
}
560564

561-
// TODO: ConsistentListFromCache feature returns the list of objects
562-
// from cache even though these objects are not readable from the
563-
// store after encryption has broken; to work around this issue, let's
564-
// create a new secret and retrieve it from the store to get a more
565-
// recent ResourceVersion and invoke the list with:
566-
// ResourceVersionMatch: Exact
567-
newSecretName := "new-a"
568-
_, err = test.createSecret(newSecretName, testNamespace)
565+
_, err = test.restClient.CoreV1().Secrets(testNamespace).Get(ctx, tc.secrets[0], metav1.GetOptions{})
569566
if err != nil {
570-
t.Fatalf("expected no error while creating the new secret, but got: %d", err)
567+
t.Logf("get returned error: %#v message: %s", err, err.Error())
571568
}
572-
newSecret, err := test.restClient.CoreV1().Secrets(testNamespace).Get(context.Background(), newSecretName, metav1.GetOptions{})
573-
if err != nil {
574-
t.Fatalf("expected no error getting the new secret, but got: %d", err)
569+
570+
var got apierrors.APIStatus
571+
if !errors.As(err, &got) {
572+
t.Fatalf("encryption never broke: %v", err)
575573
}
574+
tc.encryptionBrokenFn(t, got)
576575

577576
// e) list should return expected error
578-
_, err = test.restClient.CoreV1().Secrets(testNamespace).List(context.Background(), metav1.ListOptions{
579-
ResourceVersion: newSecret.ResourceVersion,
580-
ResourceVersionMatch: metav1.ResourceVersionMatchExact,
581-
})
577+
_, err = test.restClient.CoreV1().Secrets(testNamespace).List(ctx, metav1.ListOptions{})
582578
tc.listAfter.verify(t, err)
583-
584579
})
585580
}
586581
}

0 commit comments

Comments
 (0)