...

Source file src/k8s.io/kubernetes/test/integration/controlplane/transformation/kmsv2_transformation_test.go

Documentation: k8s.io/kubernetes/test/integration/controlplane/transformation

     1  //go:build !windows
     2  // +build !windows
     3  
     4  /*
     5  Copyright 2022 The Kubernetes Authors.
     6  
     7  Licensed under the Apache License, Version 2.0 (the "License");
     8  you may not use this file except in compliance with the License.
     9  You may obtain a copy of the License at
    10  
    11      http://www.apache.org/licenses/LICENSE-2.0
    12  
    13  Unless required by applicable law or agreed to in writing, software
    14  distributed under the License is distributed on an "AS IS" BASIS,
    15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  See the License for the specific language governing permissions and
    17  limitations under the License.
    18  */
    19  
    20  package transformation
    21  
    22  import (
    23  	"bytes"
    24  	"context"
    25  	"crypto/aes"
    26  	"crypto/cipher"
    27  	"encoding/binary"
    28  	"fmt"
    29  	"io"
    30  	"path"
    31  	"regexp"
    32  	"strings"
    33  	"testing"
    34  	"time"
    35  
    36  	"github.com/gogo/protobuf/proto"
    37  	"github.com/google/go-cmp/cmp"
    38  	"github.com/google/go-cmp/cmp/cmpopts"
    39  	clientv3 "go.etcd.io/etcd/client/v3"
    40  
    41  	corev1 "k8s.io/api/core/v1"
    42  	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    43  	"k8s.io/apimachinery/pkg/api/meta"
    44  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    45  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme"
    46  	"k8s.io/apimachinery/pkg/runtime"
    47  	"k8s.io/apimachinery/pkg/runtime/schema"
    48  	utilrand "k8s.io/apimachinery/pkg/util/rand"
    49  	"k8s.io/apimachinery/pkg/util/sets"
    50  	"k8s.io/apimachinery/pkg/util/uuid"
    51  	"k8s.io/apimachinery/pkg/util/wait"
    52  	"k8s.io/apiserver/pkg/endpoints/request"
    53  	"k8s.io/apiserver/pkg/features"
    54  	"k8s.io/apiserver/pkg/registry/generic"
    55  	genericapiserver "k8s.io/apiserver/pkg/server"
    56  	"k8s.io/apiserver/pkg/server/options/encryptionconfig"
    57  	"k8s.io/apiserver/pkg/storage/storagebackend"
    58  	"k8s.io/apiserver/pkg/storage/value"
    59  	aestransformer "k8s.io/apiserver/pkg/storage/value/encrypt/aes"
    60  	"k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2"
    61  	kmstypes "k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/v2"
    62  	kmsv2mock "k8s.io/apiserver/pkg/storage/value/encrypt/envelope/testing/v2"
    63  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    64  	"k8s.io/client-go/dynamic"
    65  	"k8s.io/client-go/kubernetes"
    66  	"k8s.io/client-go/rest"
    67  	"k8s.io/klog/v2"
    68  	kmsv2api "k8s.io/kms/apis/v2"
    69  	kmsv2svc "k8s.io/kms/pkg/service"
    70  	"k8s.io/kubernetes/pkg/api/legacyscheme"
    71  	api "k8s.io/kubernetes/pkg/apis/core"
    72  	"k8s.io/kubernetes/pkg/controlplane"
    73  	"k8s.io/kubernetes/pkg/kubeapiserver"
    74  	secretstore "k8s.io/kubernetes/pkg/registry/core/secret/storage"
    75  	"k8s.io/kubernetes/test/integration"
    76  	"k8s.io/kubernetes/test/integration/etcd"
    77  	"k8s.io/kubernetes/test/integration/framework"
    78  )
    79  
    80  type envelopekmsv2 struct {
    81  	providerName       string
    82  	rawEnvelope        []byte
    83  	plainTextDEKSource []byte
    84  	useSeed            bool
    85  }
    86  
    87  func (r envelopekmsv2) prefix() string {
    88  	return fmt.Sprintf("k8s:enc:kms:v2:%s:", r.providerName)
    89  }
    90  
    91  func (r envelopekmsv2) prefixLen() int {
    92  	return len(r.prefix())
    93  }
    94  
    95  func (r envelopekmsv2) cipherTextDEKSource() ([]byte, error) {
    96  	o := &kmstypes.EncryptedObject{}
    97  	if err := proto.Unmarshal(r.rawEnvelope[r.startOfPayload(r.providerName):], o); err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	if err := kmsv2.ValidateEncryptedObject(o); err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	if r.useSeed && o.EncryptedDEKSourceType != kmstypes.EncryptedDEKSourceType_HKDF_SHA256_XNONCE_AES_GCM_SEED {
   106  		return nil, fmt.Errorf("wrong type used with useSeed=true")
   107  	}
   108  
   109  	if !r.useSeed && o.EncryptedDEKSourceType != kmstypes.EncryptedDEKSourceType_AES_GCM_KEY {
   110  		return nil, fmt.Errorf("wrong type used with useSeed=false")
   111  	}
   112  
   113  	return o.EncryptedDEKSource, nil
   114  }
   115  
   116  func (r envelopekmsv2) startOfPayload(_ string) int {
   117  	return r.prefixLen()
   118  }
   119  
   120  func (r envelopekmsv2) cipherTextPayload() ([]byte, error) {
   121  	o := &kmstypes.EncryptedObject{}
   122  	if err := proto.Unmarshal(r.rawEnvelope[r.startOfPayload(r.providerName):], o); err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	if err := kmsv2.ValidateEncryptedObject(o); err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	return o.EncryptedData, nil
   131  }
   132  
   133  func (r envelopekmsv2) plainTextPayload(secretETCDPath string) ([]byte, error) {
   134  	var transformer value.Read
   135  	var err error
   136  	if r.useSeed {
   137  		transformer, err = aestransformer.NewHKDFExtendedNonceGCMTransformer(r.plainTextDEKSource)
   138  	} else {
   139  		var block cipher.Block
   140  		block, err = aes.NewCipher(r.plainTextDEKSource)
   141  		if err != nil {
   142  			return nil, err
   143  		}
   144  		transformer, err = aestransformer.NewGCMTransformer(block)
   145  	}
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  
   150  	ctx := context.Background()
   151  	dataCtx := value.DefaultContext(secretETCDPath)
   152  
   153  	data, err := r.cipherTextPayload()
   154  	if err != nil {
   155  		return nil, fmt.Errorf("failed to get cipher text payload: %v", err)
   156  	}
   157  	plainSecret, _, err := transformer.TransformFromStorage(ctx, data, dataCtx)
   158  	if err != nil {
   159  		return nil, fmt.Errorf("failed to transform from storage via AESGCM, err: %w", err)
   160  	}
   161  
   162  	return plainSecret, nil
   163  }
   164  
   165  // TestDefaultValues tests default flag values without setting any of the feature flags or
   166  // calling SetKDFForTests, and assert that the data stored in etcd is using KDF
   167  func TestDefaultValues(t *testing.T) {
   168  	if encryptionconfig.GetKDF() != true {
   169  		t.Fatalf("without updating the feature flags, default value of KMSv2KDF should be enabled.")
   170  	}
   171  	if utilfeature.DefaultFeatureGate.Enabled(features.KMSv2) != true {
   172  		t.Fatalf("without updating the feature flags, default value of KMSv2 should be enabled.")
   173  	}
   174  	if utilfeature.DefaultFeatureGate.Enabled(features.KMSv1) != false {
   175  		t.Fatalf("without updating the feature flags, default value of KMSv1 should be disabled.")
   176  	}
   177  	// since encryptionconfig.GetKDF() is true by default, following test should verify if
   178  	// object.EncryptedDEKSourceType == kmstypes.EncryptedDEKSourceType_HKDF_SHA256_XNONCE_AES_GCM_SEED
   179  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
   180  	t.Cleanup(cancel)
   181  
   182  	encryptionConfig := `
   183  kind: EncryptionConfiguration
   184  apiVersion: apiserver.config.k8s.io/v1
   185  resources:
   186    - resources:
   187      - pods
   188      providers:
   189      - kms:
   190         apiVersion: v2
   191         name: kms-provider
   192         endpoint: unix:///@kms-provider.sock
   193  `
   194  	_ = kmsv2mock.NewBase64Plugin(t, "@kms-provider.sock")
   195  
   196  	test, err := newTransformTest(t, encryptionConfig, false, "", nil)
   197  	if err != nil {
   198  		t.Fatalf("failed to start KUBE API Server with encryptionConfig\n %s, error: %v", encryptionConfig, err)
   199  	}
   200  	t.Cleanup(test.cleanUp)
   201  
   202  	client := kubernetes.NewForConfigOrDie(test.kubeAPIServer.ClientConfig)
   203  	if _, err := client.CoreV1().Pods(testNamespace).Create(ctx, &corev1.Pod{
   204  		ObjectMeta: metav1.ObjectMeta{
   205  			Name: "test",
   206  		},
   207  		Spec: corev1.PodSpec{
   208  			Containers: []corev1.Container{
   209  				{
   210  					Name:  "busybox",
   211  					Image: "busybox",
   212  				},
   213  			},
   214  		},
   215  	}, metav1.CreateOptions{}); err != nil {
   216  		t.Fatal(err)
   217  	}
   218  
   219  	config := test.kubeAPIServer.ServerOpts.Etcd.StorageConfig
   220  	rawClient, etcdClient, err := integration.GetEtcdClients(config.Transport)
   221  	if err != nil {
   222  		t.Fatalf("failed to create etcd client: %v", err)
   223  	}
   224  	t.Cleanup(func() { _ = rawClient.Close() })
   225  
   226  	response, err := etcdClient.Get(ctx, "/"+config.Prefix+"/pods/"+testNamespace+"/", clientv3.WithPrefix())
   227  	if err != nil {
   228  		t.Fatal(err)
   229  	}
   230  	if len(response.Kvs) != 1 {
   231  		t.Fatalf("expected 1 KVs, but got %d", len(response.Kvs))
   232  	}
   233  	object := kmstypes.EncryptedObject{}
   234  	v := bytes.TrimPrefix(response.Kvs[0].Value, []byte("k8s:enc:kms:v2:kms-provider:"))
   235  	if err := proto.Unmarshal(v, &object); err != nil {
   236  		t.Fatal(err)
   237  	}
   238  	if object.EncryptedDEKSourceType != kmstypes.EncryptedDEKSourceType_HKDF_SHA256_XNONCE_AES_GCM_SEED {
   239  		t.Errorf("invalid type: %d", object.EncryptedDEKSourceType)
   240  	}
   241  }
   242  
   243  // TestKMSv2Provider is an integration test between KubeAPI, ETCD and KMSv2 Plugin
   244  // Concretely, this test verifies the following integration contracts:
   245  // 1. Raw records in ETCD that were processed by KMSv2 Provider should be prefixed with k8s:enc:kms:v2:<plugin name>:
   246  // 2. Data Encryption Key (DEK) / DEK seed should be generated by envelopeTransformer and passed to KMS gRPC Plugin
   247  // 3. KMS gRPC Plugin should encrypt the DEK/seed with a Key Encryption Key (KEK) and pass it back to envelopeTransformer
   248  // 4. The cipherTextPayload (ex. Secret) should be encrypted via AES GCM transform / extended nonce GCM
   249  // 5. kmstypes.EncryptedObject structure should be serialized and deposited in ETCD
   250  func TestKMSv2Provider(t *testing.T) {
   251  	defaultUseSeed := encryptionconfig.GetKDF()
   252  
   253  	t.Run("regular gcm", func(t *testing.T) {
   254  		defer encryptionconfig.SetKDFForTests(false)()
   255  		testKMSv2Provider(t, !defaultUseSeed)
   256  	})
   257  	t.Run("extended nonce gcm", func(t *testing.T) {
   258  		defer encryptionconfig.SetKDFForTests(true)()
   259  		testKMSv2Provider(t, defaultUseSeed)
   260  	})
   261  }
   262  
   263  func testKMSv2Provider(t *testing.T, useSeed bool) {
   264  	encryptionConfig := `
   265  kind: EncryptionConfiguration
   266  apiVersion: apiserver.config.k8s.io/v1
   267  resources:
   268    - resources:
   269      - secrets
   270      providers:
   271      - kms:
   272         apiVersion: v2
   273         name: kms-provider
   274         endpoint: unix:///@kms-provider.sock
   275  `
   276  	genericapiserver.SetHostnameFuncForTests("testAPIServerID")
   277  	providerName := "kms-provider"
   278  	pluginMock := kmsv2mock.NewBase64Plugin(t, "@kms-provider.sock")
   279  
   280  	test, err := newTransformTest(t, encryptionConfig, false, "", nil)
   281  	if err != nil {
   282  		t.Fatalf("failed to start KUBE API Server with encryptionConfig\n %s, error: %v", encryptionConfig, err)
   283  	}
   284  	defer test.cleanUp()
   285  
   286  	ctx := testContext(t)
   287  
   288  	// the global metrics registry persists across test runs - reset it here so we can make assertions
   289  	copyConfig := rest.CopyConfig(test.kubeAPIServer.ClientConfig)
   290  	copyConfig.GroupVersion = &schema.GroupVersion{}
   291  	copyConfig.NegotiatedSerializer = unstructuredscheme.NewUnstructuredNegotiatedSerializer()
   292  	rc, err := rest.RESTClientFor(copyConfig)
   293  	if err != nil {
   294  		t.Fatal(err)
   295  	}
   296  	if err := rc.Delete().AbsPath("/metrics").Do(ctx).Error(); err != nil {
   297  		t.Fatal(err)
   298  	}
   299  
   300  	// assert that the metrics we collect during the test run match expectations
   301  	wantMetricStrings := []string{
   302  		`apiserver_envelope_encryption_dek_source_cache_size{provider_name="kms-provider"} 1`,
   303  		`apiserver_envelope_encryption_key_id_hash_last_timestamp_seconds{apiserver_id_hash="sha256:3c607df3b2bf22c9d9f01d5314b4bbf411c48ef43ff44ff29b1d55b41367c795",key_id_hash="sha256:6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b",provider_name="kms-provider",transformation_type="from_storage"} FP`,
   304  		`apiserver_envelope_encryption_key_id_hash_last_timestamp_seconds{apiserver_id_hash="sha256:3c607df3b2bf22c9d9f01d5314b4bbf411c48ef43ff44ff29b1d55b41367c795",key_id_hash="sha256:6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b",provider_name="kms-provider",transformation_type="to_storage"} FP`,
   305  		`apiserver_envelope_encryption_key_id_hash_total{apiserver_id_hash="sha256:3c607df3b2bf22c9d9f01d5314b4bbf411c48ef43ff44ff29b1d55b41367c795",key_id_hash="sha256:6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b",provider_name="kms-provider",transformation_type="from_storage"} 2`,
   306  		`apiserver_envelope_encryption_key_id_hash_total{apiserver_id_hash="sha256:3c607df3b2bf22c9d9f01d5314b4bbf411c48ef43ff44ff29b1d55b41367c795",key_id_hash="sha256:6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b",provider_name="kms-provider",transformation_type="to_storage"} 1`,
   307  	}
   308  	defer func() {
   309  		body, err := rc.Get().AbsPath("/metrics").DoRaw(ctx)
   310  		if err != nil {
   311  			t.Fatal(err)
   312  		}
   313  		var gotMetricStrings []string
   314  		trimFP := regexp.MustCompile(`(.*)(} \d+\.\d+.*)`)
   315  		for _, line := range strings.Split(string(body), "\n") {
   316  			if strings.HasPrefix(line, "apiserver_envelope_") {
   317  				if strings.HasPrefix(line, "apiserver_envelope_encryption_dek_cache_fill_percent") {
   318  					continue // this can be ignored as it is KMS v1 only
   319  				}
   320  
   321  				if strings.Contains(line, "_seconds") {
   322  					line = trimFP.ReplaceAllString(line, `$1`) + "} FP" // ignore floating point metric values
   323  				}
   324  
   325  				gotMetricStrings = append(gotMetricStrings, line)
   326  			}
   327  		}
   328  		if diff := cmp.Diff(wantMetricStrings, gotMetricStrings); diff != "" {
   329  			t.Errorf("unexpected metrics diff (-want +got): %s", diff)
   330  		}
   331  	}()
   332  
   333  	test.secret, err = test.createSecret(testSecret, testNamespace)
   334  	if err != nil {
   335  		t.Fatalf("Failed to create test secret, error: %v", err)
   336  	}
   337  
   338  	plainTextDEKSource := pluginMock.LastEncryptRequest()
   339  
   340  	secretETCDPath := test.getETCDPathForResource(test.storageConfig.Prefix, "", "secrets", test.secret.Name, test.secret.Namespace)
   341  	rawEnvelope, err := test.getRawSecretFromETCD()
   342  	if err != nil {
   343  		t.Fatalf("failed to read %s from etcd: %v", secretETCDPath, err)
   344  	}
   345  
   346  	envelopeData := envelopekmsv2{
   347  		providerName:       providerName,
   348  		rawEnvelope:        rawEnvelope,
   349  		plainTextDEKSource: plainTextDEKSource,
   350  		useSeed:            useSeed,
   351  	}
   352  
   353  	wantPrefix := envelopeData.prefix()
   354  	if !bytes.HasPrefix(rawEnvelope, []byte(wantPrefix)) {
   355  		t.Fatalf("expected secret to be prefixed with %s, but got %s", wantPrefix, rawEnvelope)
   356  	}
   357  
   358  	ciphertext, err := envelopeData.cipherTextDEKSource()
   359  	if err != nil {
   360  		t.Fatalf("failed to get ciphertext DEK/seed from KMSv2 Plugin: %v", err)
   361  	}
   362  	decryptResponse, err := pluginMock.Decrypt(ctx, &kmsv2api.DecryptRequest{Uid: string(uuid.NewUUID()), Ciphertext: ciphertext})
   363  	if err != nil {
   364  		t.Fatalf("failed to decrypt DEK, %v", err)
   365  	}
   366  	dekSourcePlainAsWouldBeSeenByETCD := decryptResponse.Plaintext
   367  
   368  	if !bytes.Equal(plainTextDEKSource, dekSourcePlainAsWouldBeSeenByETCD) {
   369  		t.Fatalf("expected plainTextDEKSource %v to be passed to KMS Plugin, but got %s",
   370  			plainTextDEKSource, dekSourcePlainAsWouldBeSeenByETCD)
   371  	}
   372  
   373  	plainSecret, err := envelopeData.plainTextPayload(secretETCDPath)
   374  	if err != nil {
   375  		t.Fatalf("failed to transform from storage via AESGCM, err: %v", err)
   376  	}
   377  
   378  	if !strings.Contains(string(plainSecret), secretVal) {
   379  		t.Fatalf("expected %q after decryption, but got %q", secretVal, string(plainSecret))
   380  	}
   381  
   382  	secretClient := test.restClient.CoreV1().Secrets(testNamespace)
   383  	// Secrets should be un-enveloped on direct reads from Kube API Server.
   384  	s, err := secretClient.Get(ctx, testSecret, metav1.GetOptions{})
   385  	if err != nil {
   386  		t.Fatalf("failed to get Secret from %s, err: %v", testNamespace, err)
   387  	}
   388  	if secretVal != string(s.Data[secretKey]) {
   389  		t.Fatalf("expected %s from KubeAPI, but got %s", secretVal, string(s.Data[secretKey]))
   390  	}
   391  }
   392  
   393  // TestKMSv2ProviderKeyIDStaleness is an integration test between KubeAPI and KMSv2 Plugin
   394  // Concretely, this test verifies the following contracts for no-op updates:
   395  // 1. When the key ID is unchanged, the resource version must not change
   396  // 2. When the key ID changes, the resource version changes (but only once)
   397  // 3. For all subsequent updates, the resource version must not change
   398  // 4. When kms-plugin is down, expect creation of new pod and encryption to succeed while the DEK/seed is valid
   399  // 5. when kms-plugin is down, no-op update for a pod should succeed and not result in RV change while the DEK/seed is valid
   400  // 6. When kms-plugin is down, expect creation of new pod and encryption to fail once the DEK/seed is invalid
   401  // 7. when kms-plugin is down, no-op update for a pod should succeed and not result in RV change even once the DEK/seed is valid
   402  func TestKMSv2ProviderKeyIDStaleness(t *testing.T) {
   403  	t.Run("regular gcm", func(t *testing.T) {
   404  		defer encryptionconfig.SetKDFForTests(false)()
   405  		testKMSv2ProviderKeyIDStaleness(t)
   406  	})
   407  	t.Run("extended nonce gcm", func(t *testing.T) {
   408  		testKMSv2ProviderKeyIDStaleness(t)
   409  	})
   410  }
   411  
   412  func testKMSv2ProviderKeyIDStaleness(t *testing.T) {
   413  	encryptionConfig := `
   414  kind: EncryptionConfiguration
   415  apiVersion: apiserver.config.k8s.io/v1
   416  resources:
   417    - resources:
   418      - pods
   419      - deployments.apps
   420      providers:
   421      - kms:
   422         apiVersion: v2
   423         name: kms-provider
   424         endpoint: unix:///@kms-provider.sock
   425  `
   426  	pluginMock := kmsv2mock.NewBase64Plugin(t, "@kms-provider.sock")
   427  
   428  	test, err := newTransformTest(t, encryptionConfig, false, "", nil)
   429  	if err != nil {
   430  		t.Fatalf("failed to start KUBE API Server with encryptionConfig\n %s, error: %v", encryptionConfig, err)
   431  	}
   432  	defer test.cleanUp()
   433  
   434  	dynamicClient := dynamic.NewForConfigOrDie(test.kubeAPIServer.ClientConfig)
   435  
   436  	testPod, err := test.createPod(testNamespace, dynamicClient)
   437  	if err != nil {
   438  		t.Fatalf("Failed to create test pod, error: %v, ns: %s", err, testNamespace)
   439  	}
   440  	version1 := testPod.GetResourceVersion()
   441  
   442  	// 1. no-op update for the test pod should not result in any RV change
   443  	updatedPod, err := test.inplaceUpdatePod(testNamespace, testPod, dynamicClient)
   444  	if err != nil {
   445  		t.Fatalf("Failed to update test pod, error: %v, ns: %s", err, testNamespace)
   446  	}
   447  	version2 := updatedPod.GetResourceVersion()
   448  	if version1 != version2 {
   449  		t.Fatalf("Resource version should not have changed. old pod: %v, new pod: %v", testPod, updatedPod)
   450  	}
   451  
   452  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
   453  	t.Cleanup(cancel)
   454  
   455  	useSeed := encryptionconfig.GetKDF()
   456  
   457  	var firstEncryptedDEKSource []byte
   458  	var f checkFunc
   459  	if useSeed {
   460  		f = func(_ int, _ uint64, etcdKey string, obj kmstypes.EncryptedObject) {
   461  			firstEncryptedDEKSource = obj.EncryptedDEKSource
   462  
   463  			if obj.KeyID != "1" {
   464  				t.Errorf("key %s: want key ID %s, got %s", etcdKey, "1", obj.KeyID)
   465  			}
   466  		}
   467  	} else {
   468  		f = func(_ int, counter uint64, etcdKey string, obj kmstypes.EncryptedObject) {
   469  			firstEncryptedDEKSource = obj.EncryptedDEKSource
   470  
   471  			if obj.KeyID != "1" {
   472  				t.Errorf("key %s: want key ID %s, got %s", etcdKey, "1", obj.KeyID)
   473  			}
   474  
   475  			// with the first key we perform encryption during the following steps:
   476  			// - create
   477  			const want = 1_000_000_000 + 1 // zero value of counter is one billion
   478  			if want != counter {
   479  				t.Errorf("key %s: counter nonce is invalid: want %d, got %d", etcdKey, want, counter)
   480  			}
   481  		}
   482  	}
   483  	assertPodDEKSources(ctx, t, test.kubeAPIServer.ServerOpts.Etcd.StorageConfig,
   484  		1, 1, "k8s:enc:kms:v2:kms-provider:", f,
   485  	)
   486  	if len(firstEncryptedDEKSource) == 0 {
   487  		t.Fatal("unexpected empty DEK or seed")
   488  	}
   489  
   490  	// 2. no-op update for the test pod with keyID update should result in RV change
   491  	pluginMock.UpdateKeyID()
   492  	if err := kmsv2mock.WaitForBase64PluginToBeUpdated(pluginMock); err != nil {
   493  		t.Fatalf("Failed to update keyID for plugin, err: %v", err)
   494  	}
   495  	// Wait 1 sec (poll interval to check resource version) until a resource version change is detected or timeout at 1 minute.
   496  
   497  	version3 := ""
   498  	err = wait.Poll(time.Second, time.Minute,
   499  		func() (bool, error) {
   500  			t.Log("polling for in-place update rv change")
   501  			updatedPod, err = test.inplaceUpdatePod(testNamespace, updatedPod, dynamicClient)
   502  			if err != nil {
   503  				return false, err
   504  			}
   505  			version3 = updatedPod.GetResourceVersion()
   506  			if version1 != version3 {
   507  				return true, nil
   508  			}
   509  			return false, nil
   510  		})
   511  	if err != nil {
   512  		t.Fatalf("Failed to detect one resource version update within the allotted time after keyID is updated and pod has been inplace updated, err: %v, ns: %s", err, testNamespace)
   513  	}
   514  
   515  	if version1 == version3 {
   516  		t.Fatalf("Resource version should have changed after keyID update. old pod: %v, new pod: %v", testPod, updatedPod)
   517  	}
   518  
   519  	var wantCount uint64 = 1_000_000_000 // zero value of counter is one billion
   520  	wantCount++                          // in place update with RV change
   521  
   522  	// with the second key we perform encryption during the following steps:
   523  	// - in place update with RV change
   524  	// - delete (which does an update to set deletion timestamp)
   525  	// - create
   526  	var checkDEK checkFunc
   527  	if useSeed {
   528  		checkDEK = func(_ int, _ uint64, etcdKey string, obj kmstypes.EncryptedObject) {
   529  			if len(obj.EncryptedDEKSource) == 0 {
   530  				t.Error("unexpected empty DEK source")
   531  			}
   532  
   533  			if bytes.Equal(obj.EncryptedDEKSource, firstEncryptedDEKSource) {
   534  				t.Errorf("key %s: incorrectly has the same ESEED", etcdKey)
   535  			}
   536  
   537  			if obj.KeyID != "2" {
   538  				t.Errorf("key %s: want key ID %s, got %s", etcdKey, "2", obj.KeyID)
   539  			}
   540  		}
   541  	} else {
   542  		checkDEK = func(_ int, counter uint64, etcdKey string, obj kmstypes.EncryptedObject) {
   543  			if len(obj.EncryptedDEKSource) == 0 {
   544  				t.Error("unexpected empty DEK source")
   545  			}
   546  
   547  			if bytes.Equal(obj.EncryptedDEKSource, firstEncryptedDEKSource) {
   548  				t.Errorf("key %s: incorrectly has the same EDEK", etcdKey)
   549  			}
   550  
   551  			if obj.KeyID != "2" {
   552  				t.Errorf("key %s: want key ID %s, got %s", etcdKey, "2", obj.KeyID)
   553  			}
   554  
   555  			if wantCount != counter {
   556  				t.Errorf("key %s: counter nonce is invalid: want %d, got %d", etcdKey, wantCount, counter)
   557  			}
   558  		}
   559  	}
   560  
   561  	// 3. no-op update for the updated pod should not result in RV change
   562  	updatedPod, err = test.inplaceUpdatePod(testNamespace, updatedPod, dynamicClient)
   563  	if err != nil {
   564  		t.Fatalf("Failed to update test pod, error: %v, ns: %s", err, testNamespace)
   565  	}
   566  	version4 := updatedPod.GetResourceVersion()
   567  	if version3 != version4 {
   568  		t.Fatalf("Resource version should not have changed again after the initial version updated as a result of the keyID update. old pod: %v, new pod: %v", testPod, updatedPod)
   569  	}
   570  
   571  	// delete the pod so that it can be recreated
   572  	if err := test.deletePod(testNamespace, dynamicClient); err != nil {
   573  		t.Fatalf("failed to delete test pod: %v", err)
   574  	}
   575  	wantCount++ // we cannot assert against the counter being 2 since the pod gets deleted
   576  
   577  	// 4. when kms-plugin is down, expect creation of new pod and encryption to succeed because the DEK is still valid
   578  	pluginMock.EnterFailedState()
   579  	mustBeUnHealthy(t, "/kms-providers",
   580  		"internal server error: kms-provider-0: rpc error: code = FailedPrecondition desc = failed precondition - key disabled",
   581  		test.kubeAPIServer.ClientConfig)
   582  
   583  	newPod, err := test.createPod(testNamespace, dynamicClient)
   584  	if err != nil {
   585  		t.Fatalf("Create test pod should have succeeded due to valid DEK, ns: %s, got: %v", testNamespace, err)
   586  	}
   587  	wantCount++
   588  	version5 := newPod.GetResourceVersion()
   589  
   590  	// 5. when kms-plugin is down and DEK is valid, no-op update for a pod should succeed and not result in RV change
   591  	updatedPod, err = test.inplaceUpdatePod(testNamespace, newPod, dynamicClient)
   592  	if err != nil {
   593  		t.Fatalf("Failed to perform no-op update on pod when kms-plugin is down, error: %v, ns: %s", err, testNamespace)
   594  	}
   595  	version6 := updatedPod.GetResourceVersion()
   596  	if version5 != version6 {
   597  		t.Fatalf("Resource version should not have changed again after the initial version updated as a result of the keyID update. old pod: %v, new pod: %v", newPod, updatedPod)
   598  	}
   599  
   600  	// Invalidate the DEK by moving the current time forward
   601  	origNowFunc := kmsv2.NowFunc
   602  	t.Cleanup(func() { kmsv2.NowFunc = origNowFunc })
   603  	kmsv2.NowFunc = func() time.Time { return origNowFunc().Add(5 * time.Minute) }
   604  
   605  	// 6. when kms-plugin is down, expect creation of new pod and encryption to fail because the DEK is invalid
   606  	_, err = test.createPod(testNamespace, dynamicClient)
   607  	if err == nil || !strings.Contains(err.Error(), `encryptedDEKSource with keyID hash "sha256:d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35" expired at 2`) {
   608  		t.Fatalf("Create test pod should have failed due to encryption, ns: %s, got: %v", testNamespace, err)
   609  	}
   610  
   611  	// 7. when kms-plugin is down and DEK is invalid, no-op update for a pod should succeed and not result in RV change
   612  	updatedNewPod, err := test.inplaceUpdatePod(testNamespace, newPod, dynamicClient)
   613  	if err != nil {
   614  		t.Fatalf("Failed to perform no-op update on pod when kms-plugin is down, error: %v, ns: %s", err, testNamespace)
   615  	}
   616  	version7 := updatedNewPod.GetResourceVersion()
   617  	if version5 != version7 {
   618  		t.Fatalf("Resource version should not have changed again after the initial version updated as a result of the keyID update. old pod: %v, new pod: %v", newPod, updatedNewPod)
   619  	}
   620  
   621  	assertPodDEKSources(ctx, t, test.kubeAPIServer.ServerOpts.Etcd.StorageConfig,
   622  		1, 1, "k8s:enc:kms:v2:kms-provider:", checkDEK,
   623  	)
   624  
   625  	// fix plugin and wait for new writes to start working again
   626  	kmsv2.NowFunc = origNowFunc
   627  	pluginMock.ExitFailedState()
   628  	err = wait.Poll(time.Second, 3*time.Minute,
   629  		func() (bool, error) {
   630  			t.Log("polling for plugin to be functional")
   631  			_, err = test.createDeployment("panda", testNamespace)
   632  			if err != nil {
   633  				t.Logf("failed to create deployment, plugin is likely still unhealthy: %v", err)
   634  			}
   635  			return err == nil, nil
   636  		})
   637  	if err != nil {
   638  		t.Fatalf("failed to restore plugin health, err: %v, ns: %s", err, testNamespace)
   639  	}
   640  
   641  	// 8. confirm that no-op update for a pod succeeds and still does not result in RV change
   642  	updatedNewPod2, err := test.inplaceUpdatePod(testNamespace, updatedNewPod, dynamicClient)
   643  	if err != nil {
   644  		t.Fatalf("Failed to perform no-op update on pod when kms-plugin is up, error: %v, ns: %s", err, testNamespace)
   645  	}
   646  	version8 := updatedNewPod2.GetResourceVersion()
   647  	if version7 != version8 {
   648  		t.Fatalf("Resource version should not have changed after plugin health is restored. old pod: %v, new pod: %v", updatedNewPod, updatedNewPod2)
   649  	}
   650  	// flip the current config
   651  	defer encryptionconfig.SetKDFForTests(!useSeed)()
   652  
   653  	// 9. confirm that no-op update for a pod results in RV change due to KDF config change
   654  	var version9 string
   655  	err = wait.Poll(time.Second, 3*time.Minute,
   656  		func() (bool, error) {
   657  			t.Log("polling for in-place update rv change due to KDF config change")
   658  			updatedNewPod2, err = test.inplaceUpdatePod(testNamespace, updatedNewPod2, dynamicClient)
   659  			if err != nil {
   660  				return false, err
   661  			}
   662  			version9 = updatedNewPod2.GetResourceVersion()
   663  			if version8 != version9 {
   664  				return true, nil
   665  			}
   666  			return false, nil
   667  		})
   668  	if err != nil {
   669  		t.Fatalf("Failed to detect one resource version update within the allotted time after KDF config change and pod has been inplace updated, err: %v, ns: %s", err, testNamespace)
   670  	}
   671  }
   672  
   673  func TestKMSv2ProviderDEKSourceReuse(t *testing.T) {
   674  	t.Run("regular gcm", func(t *testing.T) {
   675  		defer encryptionconfig.SetKDFForTests(false)()
   676  		testKMSv2ProviderDEKSourceReuse(t,
   677  			func(i int, counter uint64, etcdKey string, obj kmstypes.EncryptedObject) {
   678  				if obj.KeyID != "1" {
   679  					t.Errorf("key %s: want key ID %s, got %s", etcdKey, "1", obj.KeyID)
   680  				}
   681  
   682  				// zero value of counter is one billion so the first value will be one billion plus one
   683  				// hence we add that to our zero based index to calculate the expected nonce
   684  				if uint64(i+1_000_000_000+1) != counter {
   685  					t.Errorf("key %s: counter nonce is invalid: want %d, got %d", etcdKey, i+1, counter)
   686  				}
   687  			},
   688  		)
   689  	})
   690  	t.Run("extended nonce gcm", func(t *testing.T) {
   691  		defer encryptionconfig.SetKDFForTests(true)()
   692  		testKMSv2ProviderDEKSourceReuse(t,
   693  			func(_ int, _ uint64, etcdKey string, obj kmstypes.EncryptedObject) {
   694  				if obj.KeyID != "1" {
   695  					t.Errorf("key %s: want key ID %s, got %s", etcdKey, "1", obj.KeyID)
   696  				}
   697  			},
   698  		)
   699  	})
   700  }
   701  
   702  func testKMSv2ProviderDEKSourceReuse(t *testing.T, f checkFunc) {
   703  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
   704  	t.Cleanup(cancel)
   705  
   706  	encryptionConfig := `
   707  kind: EncryptionConfiguration
   708  apiVersion: apiserver.config.k8s.io/v1
   709  resources:
   710    - resources:
   711      - pods
   712      providers:
   713      - kms:
   714         apiVersion: v2
   715         name: kms-provider
   716         endpoint: unix:///@kms-provider.sock
   717  `
   718  	_ = kmsv2mock.NewBase64Plugin(t, "@kms-provider.sock")
   719  
   720  	test, err := newTransformTest(t, encryptionConfig, false, "", nil)
   721  	if err != nil {
   722  		t.Fatalf("failed to start KUBE API Server with encryptionConfig\n %s, error: %v", encryptionConfig, err)
   723  	}
   724  	t.Cleanup(test.cleanUp)
   725  
   726  	client := kubernetes.NewForConfigOrDie(test.kubeAPIServer.ClientConfig)
   727  
   728  	const podCount = 1_000
   729  
   730  	for i := 0; i < podCount; i++ {
   731  		if _, err := client.CoreV1().Pods(testNamespace).Create(ctx, &corev1.Pod{
   732  			ObjectMeta: metav1.ObjectMeta{
   733  				Name: fmt.Sprintf("dek-reuse-%04d", i+1), // making creation order match returned list order / nonce counter
   734  			},
   735  			Spec: corev1.PodSpec{
   736  				Containers: []corev1.Container{
   737  					{
   738  						Name:  "busybox",
   739  						Image: "busybox",
   740  					},
   741  				},
   742  			},
   743  		}, metav1.CreateOptions{}); err != nil {
   744  			t.Fatal(err)
   745  		}
   746  	}
   747  
   748  	assertPodDEKSources(ctx, t, test.kubeAPIServer.ServerOpts.Etcd.StorageConfig,
   749  		podCount, 1, // key ID does not change during the test so we should only have a single DEK
   750  		"k8s:enc:kms:v2:kms-provider:", f,
   751  	)
   752  }
   753  
   754  type checkFunc func(i int, counter uint64, etcdKey string, obj kmstypes.EncryptedObject)
   755  
   756  func assertPodDEKSources(ctx context.Context, t *testing.T, config storagebackend.Config, podCount, dekSourcesCount int, kmsPrefix string, f checkFunc) {
   757  	t.Helper()
   758  
   759  	rawClient, etcdClient, err := integration.GetEtcdClients(config.Transport)
   760  	if err != nil {
   761  		t.Fatalf("failed to create etcd client: %v", err)
   762  	}
   763  	t.Cleanup(func() { _ = rawClient.Close() })
   764  
   765  	response, err := etcdClient.Get(ctx, "/"+config.Prefix+"/pods/"+testNamespace+"/", clientv3.WithPrefix())
   766  	if err != nil {
   767  		t.Fatal(err)
   768  	}
   769  
   770  	if len(response.Kvs) != podCount {
   771  		t.Fatalf("expected %d KVs, but got %d", podCount, len(response.Kvs))
   772  	}
   773  
   774  	useSeed := encryptionconfig.GetKDF()
   775  
   776  	out := make([]kmstypes.EncryptedObject, len(response.Kvs))
   777  	for i, kv := range response.Kvs {
   778  		v := bytes.TrimPrefix(kv.Value, []byte(kmsPrefix))
   779  		if err := proto.Unmarshal(v, &out[i]); err != nil {
   780  			t.Fatal(err)
   781  		}
   782  
   783  		if err := kmsv2.ValidateEncryptedObject(&out[i]); err != nil {
   784  			t.Fatal(err)
   785  		}
   786  
   787  		var infoLen int
   788  		if useSeed {
   789  			infoLen = 32
   790  		}
   791  
   792  		info := out[i].EncryptedData[:infoLen]
   793  		nonce := out[i].EncryptedData[infoLen : 12+infoLen]
   794  		randN := nonce[:4]
   795  		count := nonce[4:]
   796  
   797  		if bytes.Equal(randN, make([]byte, len(randN))) {
   798  			t.Errorf("key %s: got all zeros for first four bytes", string(kv.Key))
   799  		}
   800  
   801  		if useSeed {
   802  			if bytes.Equal(info, make([]byte, infoLen)) {
   803  				t.Errorf("key %s: got all zeros for info", string(kv.Key))
   804  			}
   805  		}
   806  
   807  		counter := binary.LittleEndian.Uint64(count)
   808  		f(i, counter, string(kv.Key), out[i])
   809  	}
   810  
   811  	uniqueDEKSources := sets.NewString()
   812  	for _, object := range out {
   813  		object := object
   814  		uniqueDEKSources.Insert(string(object.EncryptedDEKSource))
   815  		if useSeed {
   816  			if object.EncryptedDEKSourceType != kmstypes.EncryptedDEKSourceType_HKDF_SHA256_XNONCE_AES_GCM_SEED {
   817  				t.Errorf("invalid type: %d", object.EncryptedDEKSourceType)
   818  			}
   819  		} else {
   820  			if object.EncryptedDEKSourceType != kmstypes.EncryptedDEKSourceType_AES_GCM_KEY {
   821  				t.Errorf("invalid type: %d", object.EncryptedDEKSourceType)
   822  			}
   823  		}
   824  	}
   825  
   826  	if uniqueDEKSources.Has("") {
   827  		t.Error("unexpected empty DEK source seen")
   828  	}
   829  
   830  	if uniqueDEKSources.Len() != dekSourcesCount {
   831  		t.Errorf("expected %d DEK sources, got: %d", dekSourcesCount, uniqueDEKSources.Len())
   832  	}
   833  }
   834  
   835  func TestKMSv2Healthz(t *testing.T) {
   836  	defer encryptionconfig.SetKDFForTests(randomBool())()
   837  
   838  	encryptionConfig := `
   839  kind: EncryptionConfiguration
   840  apiVersion: apiserver.config.k8s.io/v1
   841  resources:
   842    - resources:
   843      - secrets
   844      providers:
   845      - kms:
   846         apiVersion: v2
   847         name: provider-1
   848         endpoint: unix:///@kms-provider-1.sock
   849      - kms:
   850         apiVersion: v2
   851         name: provider-2
   852         endpoint: unix:///@kms-provider-2.sock
   853  `
   854  
   855  	pluginMock1 := kmsv2mock.NewBase64Plugin(t, "@kms-provider-1.sock")
   856  	pluginMock2 := kmsv2mock.NewBase64Plugin(t, "@kms-provider-2.sock")
   857  
   858  	test, err := newTransformTest(t, encryptionConfig, false, "", nil)
   859  	if err != nil {
   860  		t.Fatalf("Failed to start kube-apiserver, error: %v", err)
   861  	}
   862  	defer test.cleanUp()
   863  
   864  	// Name of the healthz check is always "kms-provider-0" and it covers all kms plugins.
   865  
   866  	// Stage 1 - Since all kms-plugins are guaranteed to be up,
   867  	// the healthz check should be OK.
   868  	mustBeHealthy(t, "/kms-providers", "ok", test.kubeAPIServer.ClientConfig)
   869  
   870  	// Stage 2 - kms-plugin for provider-1 is down. Therefore, expect the healthz check
   871  	// to fail and report that provider-1 is down
   872  	pluginMock1.EnterFailedState()
   873  	mustBeUnHealthy(t, "/kms-providers",
   874  		"internal server error: kms-provider-0: rpc error: code = FailedPrecondition desc = failed precondition - key disabled",
   875  		test.kubeAPIServer.ClientConfig)
   876  	pluginMock1.ExitFailedState()
   877  
   878  	// Stage 3 - kms-plugin for provider-1 is now up. Therefore, expect the health check for provider-1
   879  	// to succeed now, but provider-2 is now down.
   880  	pluginMock2.EnterFailedState()
   881  	mustBeUnHealthy(t, "/kms-providers",
   882  		"internal server error: kms-provider-1: rpc error: code = FailedPrecondition desc = failed precondition - key disabled",
   883  		test.kubeAPIServer.ClientConfig)
   884  	pluginMock2.ExitFailedState()
   885  
   886  	// Stage 4 - All kms-plugins are once again up,
   887  	// the healthz check should be OK.
   888  	mustBeHealthy(t, "/kms-providers", "ok", test.kubeAPIServer.ClientConfig)
   889  
   890  	// Stage 5 - All kms-plugins are unhealthy at the same time and we can observe both failures.
   891  	pluginMock1.EnterFailedState()
   892  	pluginMock2.EnterFailedState()
   893  	mustBeUnHealthy(t, "/kms-providers",
   894  		"internal server error: "+
   895  			"[kms-provider-0: failed to perform status section of the healthz check for KMS Provider provider-1, error: rpc error: code = FailedPrecondition desc = failed precondition - key disabled,"+
   896  			" kms-provider-1: failed to perform status section of the healthz check for KMS Provider provider-2, error: rpc error: code = FailedPrecondition desc = failed precondition - key disabled]",
   897  		test.kubeAPIServer.ClientConfig)
   898  }
   899  
   900  func TestKMSv2SingleService(t *testing.T) {
   901  	defer encryptionconfig.SetKDFForTests(randomBool())()
   902  
   903  	var kmsv2Calls int
   904  	origEnvelopeKMSv2ServiceFactory := encryptionconfig.EnvelopeKMSv2ServiceFactory
   905  	encryptionconfig.EnvelopeKMSv2ServiceFactory = func(ctx context.Context, endpoint, providerName string, callTimeout time.Duration) (kmsv2svc.Service, error) {
   906  		kmsv2Calls++
   907  		return origEnvelopeKMSv2ServiceFactory(ctx, endpoint, providerName, callTimeout)
   908  	}
   909  	t.Cleanup(func() {
   910  		encryptionconfig.EnvelopeKMSv2ServiceFactory = origEnvelopeKMSv2ServiceFactory
   911  	})
   912  
   913  	// check resources provided by the three servers that we have wired together
   914  	// - pods and config maps from KAS
   915  	// - CRDs and CRs from API extensions
   916  	// - API services from aggregator
   917  	encryptionConfig := `
   918  kind: EncryptionConfiguration
   919  apiVersion: apiserver.config.k8s.io/v1
   920  resources:
   921    - resources:
   922      - pods
   923      - configmaps
   924      - customresourcedefinitions.apiextensions.k8s.io
   925      - pandas.awesome.bears.com
   926      - apiservices.apiregistration.k8s.io
   927      providers:
   928      - kms:
   929         apiVersion: v2
   930         name: kms-provider
   931         endpoint: unix:///@kms-provider.sock
   932  `
   933  
   934  	_ = kmsv2mock.NewBase64Plugin(t, "@kms-provider.sock")
   935  
   936  	test, err := newTransformTest(t, encryptionConfig, false, "", nil)
   937  	if err != nil {
   938  		t.Fatalf("failed to start KUBE API Server with encryptionConfig\n %s, error: %v", encryptionConfig, err)
   939  	}
   940  	t.Cleanup(test.cleanUp)
   941  
   942  	// the storage registry for CRs is dynamic so create one to exercise the wiring
   943  	etcd.CreateTestCRDs(t, apiextensionsclientset.NewForConfigOrDie(test.kubeAPIServer.ClientConfig), false, etcd.GetCustomResourceDefinitionData()...)
   944  
   945  	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
   946  	t.Cleanup(cancel)
   947  
   948  	gvr := schema.GroupVersionResource{Group: "awesome.bears.com", Version: "v1", Resource: "pandas"}
   949  	stub := etcd.GetEtcdStorageData()[gvr].Stub
   950  	dynamicClient, obj, err := etcd.JSONToUnstructured(stub, "", &meta.RESTMapping{
   951  		Resource:         gvr,
   952  		GroupVersionKind: gvr.GroupVersion().WithKind("Panda"),
   953  		Scope:            meta.RESTScopeRoot,
   954  	}, dynamic.NewForConfigOrDie(test.kubeAPIServer.ClientConfig))
   955  	if err != nil {
   956  		t.Fatal(err)
   957  	}
   958  	_, err = dynamicClient.Create(ctx, obj, metav1.CreateOptions{})
   959  	if err != nil {
   960  		t.Fatal(err)
   961  	}
   962  
   963  	if kmsv2Calls != 1 {
   964  		t.Fatalf("expected a single call to KMS v2 service factory: %v", kmsv2Calls)
   965  	}
   966  }
   967  
   968  // TestKMSv2FeatureFlag is an integration test between KubeAPI and ETCD
   969  // Concretely, this test verifies the following:
   970  // 1. When feature flag is enabled, loading a encryptionConfig with KMSv2 should work
   971  // 2. After a restart, loading a encryptionConfig with the same KMSv2 plugin from 1 should work,
   972  // decryption of data encrypted with v2 should work
   973  func TestKMSv2FeatureFlag(t *testing.T) {
   974  	encryptionConfig := `
   975  kind: EncryptionConfiguration
   976  apiVersion: apiserver.config.k8s.io/v1
   977  resources:
   978    - resources:
   979      - secrets
   980      providers:
   981      - kms:
   982         apiVersion: v2
   983         name: kms-provider
   984         endpoint: unix:///@kms-provider.sock
   985  `
   986  	providerName := "kms-provider"
   987  	pluginMock := kmsv2mock.NewBase64Plugin(t, "@kms-provider.sock")
   988  	storageConfig := framework.SharedEtcd()
   989  
   990  	// KMSv2 is enabled by default. Loading a encryptionConfig with KMSv2 should work
   991  	test, err := newTransformTest(t, encryptionConfig, false, "", storageConfig)
   992  	if err != nil {
   993  		t.Fatalf("failed to start KUBE API Server with encryptionConfig\n %s, error: %v", encryptionConfig, err)
   994  	}
   995  	defer func() {
   996  		test.cleanUp()
   997  	}()
   998  
   999  	test.secret, err = test.createSecret(testSecret, testNamespace)
  1000  	if err != nil {
  1001  		t.Fatalf("Failed to create test secret, error: %v", err)
  1002  	}
  1003  
  1004  	// Since Data Encryption Key (DEK) is randomly generated, we need to ask KMS Mock for it.
  1005  	plainTextDEKSource := pluginMock.LastEncryptRequest()
  1006  
  1007  	secretETCDPath := test.getETCDPathForResource(test.storageConfig.Prefix, "", "secrets", test.secret.Name, test.secret.Namespace)
  1008  	rawEnvelope, err := test.getRawSecretFromETCD()
  1009  	if err != nil {
  1010  		t.Fatalf("failed to read %s from etcd: %v", secretETCDPath, err)
  1011  	}
  1012  
  1013  	envelopeData := envelopekmsv2{
  1014  		providerName:       providerName,
  1015  		rawEnvelope:        rawEnvelope,
  1016  		plainTextDEKSource: plainTextDEKSource,
  1017  		useSeed:            true, // expect KMSv2KDF to be enabled by default for this test
  1018  	}
  1019  
  1020  	wantPrefix := envelopeData.prefix()
  1021  	if !bytes.HasPrefix(rawEnvelope, []byte(wantPrefix)) {
  1022  		t.Fatalf("expected secret to be prefixed with %s, but got %s", wantPrefix, rawEnvelope)
  1023  	}
  1024  
  1025  	ctx := testContext(t)
  1026  	ciphertext, err := envelopeData.cipherTextDEKSource()
  1027  	if err != nil {
  1028  		t.Fatalf("failed to get ciphertext DEK from KMSv2 Plugin: %v", err)
  1029  	}
  1030  	decryptResponse, err := pluginMock.Decrypt(ctx, &kmsv2api.DecryptRequest{Uid: string(uuid.NewUUID()), Ciphertext: ciphertext})
  1031  	if err != nil {
  1032  		t.Fatalf("failed to decrypt DEK, %v", err)
  1033  	}
  1034  	dekPlainAsWouldBeSeenByETCD := decryptResponse.Plaintext
  1035  
  1036  	if !bytes.Equal(plainTextDEKSource, dekPlainAsWouldBeSeenByETCD) {
  1037  		t.Fatalf("expected plainTextDEKSource %v to be passed to KMS Plugin, but got %s",
  1038  			plainTextDEKSource, dekPlainAsWouldBeSeenByETCD)
  1039  	}
  1040  
  1041  	plainSecret, err := envelopeData.plainTextPayload(secretETCDPath)
  1042  	if err != nil {
  1043  		t.Fatalf("failed to transform from storage via AESGCM, err: %v", err)
  1044  	}
  1045  
  1046  	if !strings.Contains(string(plainSecret), secretVal) {
  1047  		t.Fatalf("expected %q after decryption, but got %q", secretVal, string(plainSecret))
  1048  	}
  1049  
  1050  	secretClient := test.restClient.CoreV1().Secrets(testNamespace)
  1051  	// Secrets should be un-enveloped on direct reads from Kube API Server.
  1052  	s, err := secretClient.Get(ctx, testSecret, metav1.GetOptions{})
  1053  	if err != nil {
  1054  		t.Fatalf("failed to get Secret from %s, err: %v", testNamespace, err)
  1055  	}
  1056  	if secretVal != string(s.Data[secretKey]) {
  1057  		t.Fatalf("expected %s from KubeAPI, but got %s", secretVal, string(s.Data[secretKey]))
  1058  	}
  1059  	test.shutdownAPIServer()
  1060  
  1061  	// After a restart, loading a encryptionConfig with the same KMSv2 plugin before the restart should work, decryption of data encrypted with v2 should work
  1062  
  1063  	test, err = newTransformTest(t, encryptionConfig, false, "", storageConfig)
  1064  	if err != nil {
  1065  		t.Fatalf("Failed to restart api server, error: %v", err)
  1066  	}
  1067  
  1068  	// Getting an old secret that was encrypted by the same plugin should not fail.
  1069  	s, err = test.restClient.CoreV1().Secrets(testNamespace).Get(
  1070  		ctx,
  1071  		testSecret,
  1072  		metav1.GetOptions{},
  1073  	)
  1074  	if err != nil {
  1075  		t.Fatalf("failed to read secret, err: %v", err)
  1076  	}
  1077  	if secretVal != string(s.Data[secretKey]) {
  1078  		t.Fatalf("expected %s from KubeAPI, but got %s", secretVal, string(s.Data[secretKey]))
  1079  	}
  1080  }
  1081  
  1082  var benchSecret *api.Secret
  1083  
  1084  func BenchmarkKMSv2KDF(b *testing.B) {
  1085  	b.StopTimer()
  1086  
  1087  	klog.SetOutput(io.Discard)
  1088  	klog.LogToStderr(false)
  1089  
  1090  	defer encryptionconfig.SetKDFForTests(false)()
  1091  
  1092  	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
  1093  	b.Cleanup(cancel)
  1094  
  1095  	ctx = request.WithNamespace(ctx, testNamespace)
  1096  
  1097  	encryptionConfig := `
  1098  kind: EncryptionConfiguration
  1099  apiVersion: apiserver.config.k8s.io/v1
  1100  resources:
  1101    - resources:
  1102      - secrets
  1103      providers:
  1104      - kms:
  1105         apiVersion: v2
  1106         name: kms-provider
  1107         endpoint: unix:///@kms-provider.sock
  1108  `
  1109  	_ = kmsv2mock.NewBase64Plugin(b, "@kms-provider.sock")
  1110  
  1111  	test, err := newTransformTest(b, encryptionConfig, false, "", nil)
  1112  	if err != nil {
  1113  		b.Fatalf("failed to start KUBE API Server with encryptionConfig\n %s, error: %v", encryptionConfig, err)
  1114  	}
  1115  	b.Cleanup(test.cleanUp)
  1116  
  1117  	client := kubernetes.NewForConfigOrDie(test.kubeAPIServer.ClientConfig)
  1118  
  1119  	restOptionsGetter := getRESTOptionsGetterForSecrets(b, test)
  1120  
  1121  	secretStorage, err := secretstore.NewREST(restOptionsGetter)
  1122  	if err != nil {
  1123  		b.Fatal(err)
  1124  	}
  1125  	b.Cleanup(secretStorage.Destroy)
  1126  
  1127  	const dataLen = 1_000
  1128  
  1129  	secrets := make([]*api.Secret, dataLen)
  1130  
  1131  	for i := 0; i < dataLen; i++ {
  1132  		secrets[i] = &api.Secret{
  1133  			ObjectMeta: metav1.ObjectMeta{
  1134  				Name:      fmt.Sprintf("test-secret-%d", i),
  1135  				Namespace: testNamespace,
  1136  			},
  1137  			Data: map[string][]byte{
  1138  				"lots_of_data": bytes.Repeat([]byte{1, 3, 3, 7}, i*dataLen/4),
  1139  			},
  1140  		}
  1141  	}
  1142  
  1143  	b.StartTimer()
  1144  	for i := 0; i < b.N; i++ {
  1145  		_, err := secretStorage.DeleteCollection(ctx, noValidation, &metav1.DeleteOptions{}, nil)
  1146  		if err != nil {
  1147  			b.Fatal(err)
  1148  		}
  1149  
  1150  		for i := 0; i < dataLen; i++ {
  1151  			out, err := secretStorage.Create(ctx, secrets[i], noValidation, &metav1.CreateOptions{})
  1152  			if err != nil {
  1153  				b.Fatal(err)
  1154  			}
  1155  
  1156  			benchSecret = out.(*api.Secret)
  1157  
  1158  			out, err = secretStorage.Get(ctx, benchSecret.Name, &metav1.GetOptions{})
  1159  			if err != nil {
  1160  				b.Fatal(err)
  1161  			}
  1162  
  1163  			benchSecret = out.(*api.Secret)
  1164  		}
  1165  	}
  1166  	b.StopTimer()
  1167  
  1168  	secretList, err := client.CoreV1().Secrets(testNamespace).List(ctx, metav1.ListOptions{})
  1169  	if err != nil {
  1170  		b.Fatal(err)
  1171  	}
  1172  
  1173  	if secretLen := len(secretList.Items); secretLen != dataLen {
  1174  		b.Errorf("unexpected secret len: want %d, got %d", dataLen, secretLen)
  1175  	}
  1176  }
  1177  
  1178  func getRESTOptionsGetterForSecrets(t testing.TB, test *transformTest) generic.RESTOptionsGetter {
  1179  	t.Helper()
  1180  
  1181  	s := test.kubeAPIServer.ServerOpts
  1182  
  1183  	etcdConfigCopy := *s.Etcd
  1184  	etcdConfigCopy.SkipHealthEndpoints = true                     // avoid running health check go routines
  1185  	etcdConfigCopy.EncryptionProviderConfigAutomaticReload = true // hack to use DynamicTransformers in t.Cleanup below
  1186  
  1187  	// mostly copied from BuildGenericConfig
  1188  
  1189  	genericConfig := genericapiserver.NewConfig(legacyscheme.Codecs)
  1190  
  1191  	genericConfig.MergedResourceConfig = controlplane.DefaultAPIResourceConfigSource()
  1192  
  1193  	if err := s.APIEnablement.ApplyTo(genericConfig, controlplane.DefaultAPIResourceConfigSource(), legacyscheme.Scheme); err != nil {
  1194  		t.Fatal(err)
  1195  	}
  1196  
  1197  	storageFactoryConfig := kubeapiserver.NewStorageFactoryConfig()
  1198  	storageFactoryConfig.APIResourceConfig = genericConfig.MergedResourceConfig
  1199  	storageFactory, err := storageFactoryConfig.Complete(&etcdConfigCopy).New()
  1200  	if err != nil {
  1201  		t.Fatal(err)
  1202  	}
  1203  	if err := etcdConfigCopy.ApplyWithStorageFactoryTo(storageFactory, genericConfig); err != nil {
  1204  		t.Fatal(err)
  1205  	}
  1206  
  1207  	transformers, ok := genericConfig.ResourceTransformers.(*encryptionconfig.DynamicTransformers)
  1208  	if !ok {
  1209  		t.Fatalf("incorrect type for ResourceTransformers: %T", genericConfig.ResourceTransformers)
  1210  	}
  1211  
  1212  	t.Cleanup(func() {
  1213  		// this is a hack to cause the existing transformers to shutdown
  1214  		transformers.Set(nil, nil, nil, 0)
  1215  		time.Sleep(10 * time.Second) // block this cleanup for longer than kmsCloseGracePeriod
  1216  	})
  1217  
  1218  	if genericConfig.RESTOptionsGetter == nil {
  1219  		t.Fatal("not REST options found")
  1220  	}
  1221  
  1222  	opts, err := genericConfig.RESTOptionsGetter.GetRESTOptions(schema.GroupResource{Group: "", Resource: "secrets"})
  1223  	if err != nil {
  1224  		t.Fatal(err)
  1225  	}
  1226  
  1227  	if err := runtime.CheckCodec(opts.StorageConfig.Codec, &api.Secret{},
  1228  		schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Secret"}); err != nil {
  1229  		t.Fatal(err)
  1230  	}
  1231  
  1232  	return genericConfig.RESTOptionsGetter
  1233  }
  1234  
  1235  func noValidation(_ context.Context, _ runtime.Object) error { return nil }
  1236  
  1237  var benchRESTSecret *corev1.Secret
  1238  
  1239  func BenchmarkKMSv2REST(b *testing.B) {
  1240  	b.StopTimer()
  1241  
  1242  	klog.SetOutput(io.Discard)
  1243  	klog.LogToStderr(false)
  1244  
  1245  	defer encryptionconfig.SetKDFForTests(true)()
  1246  
  1247  	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
  1248  	b.Cleanup(cancel)
  1249  
  1250  	encryptionConfig := `
  1251  kind: EncryptionConfiguration
  1252  apiVersion: apiserver.config.k8s.io/v1
  1253  resources:
  1254    - resources:
  1255      - secrets
  1256      providers:
  1257      - kms:
  1258         apiVersion: v2
  1259         name: kms-provider
  1260         endpoint: unix:///@kms-provider.sock
  1261  `
  1262  	_ = kmsv2mock.NewBase64Plugin(b, "@kms-provider.sock")
  1263  
  1264  	test, err := newTransformTest(b, encryptionConfig, false, "", nil)
  1265  	if err != nil {
  1266  		b.Fatalf("failed to start KUBE API Server with encryptionConfig\n %s, error: %v", encryptionConfig, err)
  1267  	}
  1268  	b.Cleanup(test.cleanUp)
  1269  
  1270  	client := kubernetes.NewForConfigOrDie(test.kubeAPIServer.ClientConfig)
  1271  
  1272  	const dataLen = 1_000
  1273  
  1274  	secretStorage := client.CoreV1().Secrets(testNamespace)
  1275  
  1276  	secrets := make([]*corev1.Secret, dataLen)
  1277  
  1278  	for i := 0; i < dataLen; i++ {
  1279  		secrets[i] = &corev1.Secret{
  1280  			ObjectMeta: metav1.ObjectMeta{
  1281  				Name:      fmt.Sprintf("test-secret-%d", i),
  1282  				Namespace: testNamespace,
  1283  			},
  1284  			Data: map[string][]byte{
  1285  				"lots_of_data": bytes.Repeat([]byte{1, 3, 3, 7}, i*dataLen/4),
  1286  			},
  1287  		}
  1288  	}
  1289  
  1290  	b.StartTimer()
  1291  	for i := 0; i < b.N; i++ {
  1292  		err := secretStorage.DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{})
  1293  		if err != nil {
  1294  			b.Fatal(err)
  1295  		}
  1296  
  1297  		for i := 0; i < dataLen; i++ {
  1298  			out, err := secretStorage.Create(ctx, secrets[i], metav1.CreateOptions{})
  1299  			if err != nil {
  1300  				b.Fatal(err)
  1301  			}
  1302  
  1303  			benchRESTSecret = out
  1304  
  1305  			out, err = secretStorage.Get(ctx, benchRESTSecret.Name, metav1.GetOptions{})
  1306  			if err != nil {
  1307  				b.Fatal(err)
  1308  			}
  1309  
  1310  			benchRESTSecret = out
  1311  		}
  1312  	}
  1313  	b.StopTimer()
  1314  
  1315  	secretList, err := client.CoreV1().Secrets(testNamespace).List(ctx, metav1.ListOptions{})
  1316  	if err != nil {
  1317  		b.Fatal(err)
  1318  	}
  1319  
  1320  	if secretLen := len(secretList.Items); secretLen != dataLen {
  1321  		b.Errorf("unexpected secret len: want %d, got %d", dataLen, secretLen)
  1322  	}
  1323  }
  1324  
  1325  func randomBool() bool { return utilrand.Int()%2 == 1 }
  1326  
  1327  // TestKMSv2ProviderLegacyData confirms that legacy data recorded from the earliest released commit can still be read.
  1328  func TestKMSv2ProviderLegacyData(t *testing.T) {
  1329  	t.Run("regular gcm", func(t *testing.T) {
  1330  		defer encryptionconfig.SetKDFForTests(false)()
  1331  		testKMSv2ProviderLegacyData(t)
  1332  	})
  1333  	t.Run("extended nonce gcm", func(t *testing.T) {
  1334  		defer encryptionconfig.SetKDFForTests(true)()
  1335  		testKMSv2ProviderLegacyData(t)
  1336  	})
  1337  }
  1338  
  1339  func testKMSv2ProviderLegacyData(t *testing.T) {
  1340  	encryptionConfig := `
  1341  kind: EncryptionConfiguration
  1342  apiVersion: apiserver.config.k8s.io/v1
  1343  resources:
  1344    - resources:
  1345      - secrets
  1346      providers:
  1347      - kms:
  1348         apiVersion: v2
  1349         name: kms-provider
  1350         endpoint: unix:///@kms-provider.sock
  1351  `
  1352  
  1353  	_ = kmsv2mock.NewBase64Plugin(t, "@kms-provider.sock")
  1354  
  1355  	// the value.Context.AuthenticatedData during read is the etcd storage path of the associated resource
  1356  	// thus we need to manually construct the storage config so that we can have a static path
  1357  	const legacyDataEtcdPrefix = "43da1478-5e9c-4ef3-a92a-2b19d540c8a5"
  1358  
  1359  	storageConfig := storagebackend.NewDefaultConfig(path.Join(legacyDataEtcdPrefix, "registry"), nil)
  1360  	storageConfig.Transport.ServerList = []string{framework.GetEtcdURL()}
  1361  
  1362  	test, err := newTransformTest(t, encryptionConfig, false, "", storageConfig)
  1363  	if err != nil {
  1364  		t.Fatalf("failed to start KUBE API Server with encryptionConfig\n %s, error: %v", encryptionConfig, err)
  1365  	}
  1366  	defer test.cleanUp()
  1367  
  1368  	const legacyDataNamespace = "kms-v2-legacy-data"
  1369  
  1370  	if _, err := test.createNamespace(legacyDataNamespace); err != nil {
  1371  		t.Fatal(err)
  1372  	}
  1373  
  1374  	// commit: 1b4df30b3cdfeaba6024e81e559a6cd09a089d65
  1375  	// tag: v1.27.0
  1376  	const dekSourceAESGCMKeyName = "dek-source-aes-gcm-key"
  1377  	const dekSourceAESGCMKeyData = "k8s:enc:kms:v2:kms-provider:\n\x8a\x03\xb0ƙ\xdc\x01ʚ;\x00\x00\x00\x00\x8c\x80\v\xf0\x1dKo{0\v\xb5\xd5Ń\x1a\xb5\x0e\xcf\xd7Ve\xed邸\xdfE\xedMk\xcf!\x15\xc0\v\t\x82Wh \x9e\x8f\x1b\\9\b\xa4\x80\x02m\xf4P\x14z\xee\xf7\x8c\x1a\x84n5\xfdG\x83v#\x0e\xd4\f\x83YwH\xe1\x1c\xbf\x12\xc6\x1b\xba\x8br\b\x82z\xf8\xdb`\xa7]P\xb1\xe6!Lb\x8d\xb8I\x1aEL\xa0\xae+\xbe\x15R\x8e\x9b\x064\xf6P\xb6;\x9f\xa6\x8d\x96\xb2\x01\xa1\x8e\xe4a\xdf/\x90u\xde6\x9a\xc2ͻb\x88+\x16\x98=\xe9\x03\xdd\xd7HvC\n\xe5\x8cv\x05~\n\xabX_N\x9a\x84wp\xa8\x13\x0f\x82Y9x\xed\x89\x15\xb9\xe1ꦐ\xc6`R\n0\x04\xf2\xa6ѥ\x85\nk\xf4\xcf\xe4ul\x1c*[A\x12\xa0\xd9\xf2\xb5!\x82\xe4\x00\xa4L'&\xf5pln\"\xe0=\f[\xe1\xb0U97\x11|\xdaNk\xc3=\xc2\xf2\x85<7\x1e\x01\xb8\xa9\xf4\x89\xdb~\xe1\x8c\xe1\x1f\x05B@WʼS\n聛LY\x86$\xf6\x01ݝ\xcd\x1d\xe9\x02]\xf4i\xda\xfa\xc2\x0eUr\xc5Dʽdb\x13\xbe\xfe\x1c\xc5\xe1\x84\xcc\xdf&\x93j\x1eK\x04\xba\x06\x16\x85\x0e\x1f\xca\b\x90\x06\x11K\x9d[\rV\xe8E\xd5(\x91\fn\xd4\x10\x9cH\x1cܝX\x94ȁ5\x1b\x8c\xbbz\xf9Ho{\x1d\x112\x90F\xe7\xd8h\xa8\xa1\xf6\x8c\x8cvʲ1\xf9#\x82\xa3\xbe7ed\xd9\x14\xf3\x06\xff\xb7߫i\x12\x011\x1a,gafIvwT0ASoKdZ/D1L2SlH73LUMj5qa3hroljfS51wc=\"2\n\x1blocal-kek.kms.kubernetes.io\x12\x13encrypted-local-kek"
  1378  	dekSourceAESGCMKeyPath := test.getETCDPathForResource(test.storageConfig.Prefix, "", "secrets", dekSourceAESGCMKeyName, legacyDataNamespace)
  1379  	if _, err := test.writeRawRecordToETCD(dekSourceAESGCMKeyPath, []byte(dekSourceAESGCMKeyData)); err != nil {
  1380  		t.Fatal(err)
  1381  	}
  1382  
  1383  	expectedDEKSourceAESGCMKeyObject := &kmstypes.EncryptedObject{
  1384  		EncryptedData:          []byte("\xb0ƙ\xdc\x01ʚ;\x00\x00\x00\x00\x8c\x80\v\xf0\x1dKo{0\v\xb5\xd5Ń\x1a\xb5\x0e\xcf\xd7Ve\xed邸\xdfE\xedMk\xcf!\x15\xc0\v\t\x82Wh \x9e\x8f\x1b\\9\b\xa4\x80\x02m\xf4P\x14z\xee\xf7\x8c\x1a\x84n5\xfdG\x83v#\x0e\xd4\f\x83YwH\xe1\x1c\xbf\x12\xc6\x1b\xba\x8br\b\x82z\xf8\xdb`\xa7]P\xb1\xe6!Lb\x8d\xb8I\x1aEL\xa0\xae+\xbe\x15R\x8e\x9b\x064\xf6P\xb6;\x9f\xa6\x8d\x96\xb2\x01\xa1\x8e\xe4a\xdf/\x90u\xde6\x9a\xc2ͻb\x88+\x16\x98=\xe9\x03\xdd\xd7HvC\n\xe5\x8cv\x05~\n\xabX_N\x9a\x84wp\xa8\x13\x0f\x82Y9x\xed\x89\x15\xb9\xe1ꦐ\xc6`R\n0\x04\xf2\xa6ѥ\x85\nk\xf4\xcf\xe4ul\x1c*[A\x12\xa0\xd9\xf2\xb5!\x82\xe4\x00\xa4L'&\xf5pln\"\xe0=\f[\xe1\xb0U97\x11|\xdaNk\xc3=\xc2\xf2\x85<7\x1e\x01\xb8\xa9\xf4\x89\xdb~\xe1\x8c\xe1\x1f\x05B@WʼS\n聛LY\x86$\xf6\x01ݝ\xcd\x1d\xe9\x02]\xf4i\xda\xfa\xc2\x0eUr\xc5Dʽdb\x13\xbe\xfe\x1c\xc5\xe1\x84\xcc\xdf&\x93j\x1eK\x04\xba\x06\x16\x85\x0e\x1f\xca\b\x90\x06\x11K\x9d[\rV\xe8E\xd5(\x91\fn\xd4\x10\x9cH\x1cܝX\x94ȁ5\x1b\x8c\xbbz\xf9Ho{\x1d\x112\x90F\xe7\xd8h\xa8\xa1\xf6\x8c\x8cvʲ1\xf9#\x82\xa3\xbe7ed\xd9\x14\xf3\x06\xff\xb7߫i"),
  1385  		KeyID:                  "1",
  1386  		EncryptedDEKSource:     []byte("gafIvwT0ASoKdZ/D1L2SlH73LUMj5qa3hroljfS51wc="),
  1387  		Annotations:            map[string][]byte{"local-kek.kms.kubernetes.io": []byte("encrypted-local-kek")},
  1388  		EncryptedDEKSourceType: kmstypes.EncryptedDEKSourceType_AES_GCM_KEY,
  1389  	}
  1390  	legacyDEKSourceAESGCMKeyObject := &kmstypes.EncryptedObject{}
  1391  	if err := proto.Unmarshal([]byte(strings.TrimPrefix(dekSourceAESGCMKeyData, "k8s:enc:kms:v2:kms-provider:")), legacyDEKSourceAESGCMKeyObject); err != nil {
  1392  		t.Fatal(err)
  1393  	}
  1394  	if err := kmsv2.ValidateEncryptedObject(legacyDEKSourceAESGCMKeyObject); err != nil {
  1395  		t.Fatal(err)
  1396  	}
  1397  	if diff := cmp.Diff(expectedDEKSourceAESGCMKeyObject, legacyDEKSourceAESGCMKeyObject); len(diff) > 0 {
  1398  		t.Errorf("kms v2 legacy encrypted object diff (-want, +got):\n%s", diff)
  1399  	}
  1400  
  1401  	// commit: 855e7c48de7388eb330da0f8d9d2394ee818fb8d
  1402  	// tag: v1.28.0
  1403  	const dekSourceHKDFSHA256XNonceAESGCMSeedName = "dek-source-hkdf-sha256-xnonce-aes-gcm-seed"
  1404  	const dekSourceHKDFSHA256XNonceAESGCMSeedData = "k8s:enc:kms:v2:kms-provider:\n\xd1\x03\x12\x0f\x87\xae\xa2\xa2\xf5J\x11\x06о\x8a\x81\xb6\x15\xdf4H\xa3\xb7i\x93^)\xe5i\xe2\x7f\xfdo\xee\"\x170\xb7\n\xa0\v\xec\xe0\xfa\t#\tƖ\xf6ǧw\x01\xb9\xab\xb3\xf4\xdf\xec\x9eJ\x95$&Z=\x8awAc\xa5\xb2;\t\xd5\xf9\x05\xc1E)\x8b\xb8\x14\xc9\xda\x14{I\rV?\xe5:\xf0BM\x9b\xad\xaaHn>W/Q\xa3\xf5\xba\xe7˚\n\xe7\"\xa7\\p\x8c\xba2\xf2\xb0x<Ą\x88\x9a\xf1\xb5:d=z\xe3\xc3&\x03\x99m\x96\xe7\\\xe3\xa3\xd7i\xb2\f\x84g\xf94\xd2\f\xd6~\xed\xac\xf8\x1b\xc6(,[\xd1\xff\xba\x89ȇD\x02):wTM12\xb5\xfdl\xd2\xf2\x85\x120\xd3\xd9aak\xce\xddI֥\x0fk2\xf6I\xd0\xf9\xc2\xda\vŗ\xd7\u05fb\x83\xd6\xf7\x1a\xbf\x13iH\x8f\xe4\xb3#\xf3\xdf\xc8$y\rL(F\\Xt\x86\xbb\xe5K\x88=\x92\xe9\x04\xf70\x1e\t>\xac;\x9e\xe6\xf0+ۙ4\x1d\x1aV9Մ-g\xf3\xc7Z\x00\xf73\x0e\xe7\xa6\xcf\xc4\xfc\xe0\xb2\x1f\xa0\xbb\x1a\x81\xb3\xe4צ\x7f\xc6\b\x94͉\xad@\xac\x81\x015\x0f\xe8A\xe9B\xfb2\x81o\x9c?*\f\x8c\x15\xa8)\"\xe8\xff\x8d8\xd5!O\x17\xc5㍀\x83\xd3´\xca1;\xf7\xb0\xf4\x90x\xa6\x01\x95\x85\xc0\xaf\xf6\x82Qk\xab\xc1\x82<D\x93\xcf\r\xdb\xdf\x1c\x94\x17Q\xfaS\xe6\xcb\xd4Xƿ\x80\x1d\xc4\x1c\x9dP74\x82JK\vy\xe9)\xbchY\xbe\xcc|\xe4\x97\xdd<;3\x90J\a\xee#\xb2y\xe3\t`\xef\xef\x1f\"k\x8b\x96\xa0\x98\xd9\xffs\xde&\xb7\xa6\x0e\xf1\x7f2ͅb\xe3\xda5\"c\\K\xe21\xa2\xec`\x1b\xe5R\xe6j\b@\x187\xe1\xdb\x04\xf6bNO\x0e\x12\x011\x1a,/+WnKXQEM/AhXICYRNBeGk+WSuB+7OBuSYJTbP66Zyc=\"2\n\x1blocal-kek.kms.kubernetes.io\x12\x13encrypted-local-kek(\x01"
  1405  	dekSourceHKDFSHA256XNonceAESGCMSeedPath := test.getETCDPathForResource(test.storageConfig.Prefix, "", "secrets", dekSourceHKDFSHA256XNonceAESGCMSeedName, legacyDataNamespace)
  1406  	if _, err := test.writeRawRecordToETCD(dekSourceHKDFSHA256XNonceAESGCMSeedPath, []byte(dekSourceHKDFSHA256XNonceAESGCMSeedData)); err != nil {
  1407  		t.Fatal(err)
  1408  	}
  1409  
  1410  	expectedDEKSourceHKDFSHA256XNonceAESGCMSeedObject := &kmstypes.EncryptedObject{
  1411  		EncryptedData:          []byte("\x12\x0f\x87\xae\xa2\xa2\xf5J\x11\x06о\x8a\x81\xb6\x15\xdf4H\xa3\xb7i\x93^)\xe5i\xe2\x7f\xfdo\xee\"\x170\xb7\n\xa0\v\xec\xe0\xfa\t#\tƖ\xf6ǧw\x01\xb9\xab\xb3\xf4\xdf\xec\x9eJ\x95$&Z=\x8awAc\xa5\xb2;\t\xd5\xf9\x05\xc1E)\x8b\xb8\x14\xc9\xda\x14{I\rV?\xe5:\xf0BM\x9b\xad\xaaHn>W/Q\xa3\xf5\xba\xe7˚\n\xe7\"\xa7\\p\x8c\xba2\xf2\xb0x<Ą\x88\x9a\xf1\xb5:d=z\xe3\xc3&\x03\x99m\x96\xe7\\\xe3\xa3\xd7i\xb2\f\x84g\xf94\xd2\f\xd6~\xed\xac\xf8\x1b\xc6(,[\xd1\xff\xba\x89ȇD\x02):wTM12\xb5\xfdl\xd2\xf2\x85\x120\xd3\xd9aak\xce\xddI֥\x0fk2\xf6I\xd0\xf9\xc2\xda\vŗ\xd7\u05fb\x83\xd6\xf7\x1a\xbf\x13iH\x8f\xe4\xb3#\xf3\xdf\xc8$y\rL(F\\Xt\x86\xbb\xe5K\x88=\x92\xe9\x04\xf70\x1e\t>\xac;\x9e\xe6\xf0+ۙ4\x1d\x1aV9Մ-g\xf3\xc7Z\x00\xf73\x0e\xe7\xa6\xcf\xc4\xfc\xe0\xb2\x1f\xa0\xbb\x1a\x81\xb3\xe4צ\x7f\xc6\b\x94͉\xad@\xac\x81\x015\x0f\xe8A\xe9B\xfb2\x81o\x9c?*\f\x8c\x15\xa8)\"\xe8\xff\x8d8\xd5!O\x17\xc5㍀\x83\xd3´\xca1;\xf7\xb0\xf4\x90x\xa6\x01\x95\x85\xc0\xaf\xf6\x82Qk\xab\xc1\x82<D\x93\xcf\r\xdb\xdf\x1c\x94\x17Q\xfaS\xe6\xcb\xd4Xƿ\x80\x1d\xc4\x1c\x9dP74\x82JK\vy\xe9)\xbchY\xbe\xcc|\xe4\x97\xdd<;3\x90J\a\xee#\xb2y\xe3\t`\xef\xef\x1f\"k\x8b\x96\xa0\x98\xd9\xffs\xde&\xb7\xa6\x0e\xf1\x7f2ͅb\xe3\xda5\"c\\K\xe21\xa2\xec`\x1b\xe5R\xe6j\b@\x187\xe1\xdb\x04\xf6bNO\x0e"),
  1412  		KeyID:                  "1",
  1413  		EncryptedDEKSource:     []byte("/+WnKXQEM/AhXICYRNBeGk+WSuB+7OBuSYJTbP66Zyc="),
  1414  		Annotations:            map[string][]byte{"local-kek.kms.kubernetes.io": []byte("encrypted-local-kek")},
  1415  		EncryptedDEKSourceType: kmstypes.EncryptedDEKSourceType_HKDF_SHA256_XNONCE_AES_GCM_SEED,
  1416  	}
  1417  	legacyDEKSourceHKDFSHA256XNonceAESGCMSeedObject := &kmstypes.EncryptedObject{}
  1418  	if err := proto.Unmarshal([]byte(strings.TrimPrefix(dekSourceHKDFSHA256XNonceAESGCMSeedData, "k8s:enc:kms:v2:kms-provider:")), legacyDEKSourceHKDFSHA256XNonceAESGCMSeedObject); err != nil {
  1419  		t.Fatal(err)
  1420  	}
  1421  	if err := kmsv2.ValidateEncryptedObject(legacyDEKSourceHKDFSHA256XNonceAESGCMSeedObject); err != nil {
  1422  		t.Fatal(err)
  1423  	}
  1424  	if diff := cmp.Diff(expectedDEKSourceHKDFSHA256XNonceAESGCMSeedObject, legacyDEKSourceHKDFSHA256XNonceAESGCMSeedObject); len(diff) > 0 {
  1425  		t.Errorf("kms v2 legacy encrypted object diff (-want, +got):\n%s", diff)
  1426  	}
  1427  
  1428  	ctx := testContext(t)
  1429  
  1430  	legacySecrets, err := test.restClient.CoreV1().Secrets(legacyDataNamespace).List(ctx, metav1.ListOptions{})
  1431  	if err != nil {
  1432  		t.Fatal(err)
  1433  	}
  1434  
  1435  	dekSourceAESGCMKeyTime := metav1.NewTime(time.Date(2023, 9, 1, 11, 56, 49, 0, time.FixedZone("EDT", -4*60*60)))
  1436  	dekSourceHKDFSHA256XNonceAESGCMSeedTime := metav1.NewTime(time.Date(2023, 9, 1, 10, 23, 13, 0, time.FixedZone("EDT", -4*60*60)))
  1437  
  1438  	expectedLegacySecrets := &corev1.SecretList{
  1439  		Items: []corev1.Secret{
  1440  			{
  1441  				ObjectMeta: metav1.ObjectMeta{
  1442  					Name:              dekSourceAESGCMKeyName,
  1443  					Namespace:         legacyDataNamespace,
  1444  					UID:               "1f4a8f7b-01b4-49d1-b898-751eb56937f1",
  1445  					CreationTimestamp: dekSourceAESGCMKeyTime,
  1446  					ManagedFields: []metav1.ManagedFieldsEntry{
  1447  						{
  1448  							Manager:    "___TestKMSv2Provider_in_k8s_io_kubernetes_test_integration_controlplane_transformation.test",
  1449  							Operation:  "Update",
  1450  							APIVersion: "v1",
  1451  							Time:       &dekSourceAESGCMKeyTime,
  1452  							FieldsType: "FieldsV1",
  1453  							FieldsV1:   &metav1.FieldsV1{Raw: []byte(`{"f:data":{".":{},"f:api_key":{}},"f:type":{}}`)},
  1454  						},
  1455  					},
  1456  				},
  1457  				Data: map[string][]byte{
  1458  					secretKey: []byte(secretVal),
  1459  				},
  1460  				Type: corev1.SecretTypeOpaque,
  1461  			},
  1462  			{
  1463  				ObjectMeta: metav1.ObjectMeta{
  1464  					Name:              dekSourceHKDFSHA256XNonceAESGCMSeedName,
  1465  					Namespace:         legacyDataNamespace,
  1466  					UID:               "87c514b4-9c26-4041-ad0d-0d07dca557ed",
  1467  					CreationTimestamp: dekSourceHKDFSHA256XNonceAESGCMSeedTime,
  1468  					ManagedFields: []metav1.ManagedFieldsEntry{
  1469  						{
  1470  							Manager:    "___TestKMSv2Provider_extended_nonce_gcm_in_k8s_io_kubernetes_test_integration_controlplane_transformation.test",
  1471  							Operation:  "Update",
  1472  							APIVersion: "v1",
  1473  							Time:       &dekSourceHKDFSHA256XNonceAESGCMSeedTime,
  1474  							FieldsType: "FieldsV1",
  1475  							FieldsV1:   &metav1.FieldsV1{Raw: []byte(`{"f:data":{".":{},"f:api_key":{}},"f:type":{}}`)},
  1476  						},
  1477  					},
  1478  				},
  1479  				Data: map[string][]byte{
  1480  					secretKey: []byte(secretVal),
  1481  				},
  1482  				Type: corev1.SecretTypeOpaque,
  1483  			},
  1484  		},
  1485  	}
  1486  
  1487  	if diff := cmp.Diff(expectedLegacySecrets, legacySecrets,
  1488  		// resource version is set after decoding based on etcd state - it is not stored in the etcd value
  1489  		cmpopts.IgnoreFields(corev1.Secret{}, "ResourceVersion"),
  1490  		cmpopts.IgnoreFields(metav1.ListMeta{}, "ResourceVersion"),
  1491  	); len(diff) > 0 {
  1492  		t.Errorf("kms v2 legacy secret data diff (-want, +got):\n%s", diff)
  1493  	}
  1494  }
  1495  

View as plain text