Skip to content

Commit 4619f7e

Browse files
authored
Merge pull request kubernetes#120370 from enj/enj/f/kmsv2_default_kdf
kmsv2: add legacy data integration test
2 parents b89f564 + 95121fe commit 4619f7e

File tree

2 files changed

+174
-2
lines changed

2 files changed

+174
-2
lines changed

staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/envelope.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ import (
4444
"k8s.io/utils/clock"
4545
)
4646

47-
// TODO integration test with old AES GCM data recorded and new KDF data recorded
48-
4947
func init() {
5048
value.RegisterMetrics()
5149
metrics.RegisterMetrics()

test/integration/controlplane/transformation/kmsv2_transformation_test.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@ import (
2727
"encoding/binary"
2828
"fmt"
2929
"io"
30+
"path"
3031
"regexp"
3132
"strings"
3233
"testing"
3334
"time"
3435

3536
"github.com/gogo/protobuf/proto"
3637
"github.com/google/go-cmp/cmp"
38+
"github.com/google/go-cmp/cmp/cmpopts"
3739
clientv3 "go.etcd.io/etcd/client/v3"
3840

3941
corev1 "k8s.io/api/core/v1"
@@ -1308,3 +1310,175 @@ resources:
13081310
}
13091311

13101312
func randomBool() bool { return utilrand.Int()%2 == 1 }
1313+
1314+
// TestKMSv2ProviderLegacyData confirms that legacy data recorded from the earliest released commit can still be read.
1315+
func TestKMSv2ProviderLegacyData(t *testing.T) {
1316+
t.Run("regular gcm", func(t *testing.T) {
1317+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv2KDF, false)()
1318+
testKMSv2ProviderLegacyData(t)
1319+
})
1320+
1321+
t.Run("extended nonce gcm", func(t *testing.T) {
1322+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv2KDF, true)()
1323+
testKMSv2ProviderLegacyData(t)
1324+
})
1325+
}
1326+
1327+
func testKMSv2ProviderLegacyData(t *testing.T) {
1328+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv2, true)()
1329+
1330+
encryptionConfig := `
1331+
kind: EncryptionConfiguration
1332+
apiVersion: apiserver.config.k8s.io/v1
1333+
resources:
1334+
- resources:
1335+
- secrets
1336+
providers:
1337+
- kms:
1338+
apiVersion: v2
1339+
name: kms-provider
1340+
endpoint: unix:///@kms-provider.sock
1341+
`
1342+
1343+
_ = kmsv2mock.NewBase64Plugin(t, "@kms-provider.sock")
1344+
1345+
// the value.Context.AuthenticatedData during read is the etcd storage path of the associated resource
1346+
// thus we need to manually construct the storage config so that we can have a static path
1347+
const legacyDataEtcdPrefix = "43da1478-5e9c-4ef3-a92a-2b19d540c8a5"
1348+
1349+
storageConfig := storagebackend.NewDefaultConfig(path.Join(legacyDataEtcdPrefix, "registry"), nil)
1350+
storageConfig.Transport.ServerList = []string{framework.GetEtcdURL()}
1351+
1352+
test, err := newTransformTest(t, encryptionConfig, false, "", storageConfig)
1353+
if err != nil {
1354+
t.Fatalf("failed to start KUBE API Server with encryptionConfig\n %s, error: %v", encryptionConfig, err)
1355+
}
1356+
defer test.cleanUp()
1357+
1358+
const legacyDataNamespace = "kms-v2-legacy-data"
1359+
1360+
if _, err := test.createNamespace(legacyDataNamespace); err != nil {
1361+
t.Fatal(err)
1362+
}
1363+
1364+
// commit: 1b4df30b3cdfeaba6024e81e559a6cd09a089d65
1365+
// tag: v1.27.0
1366+
const dekSourceAESGCMKeyName = "dek-source-aes-gcm-key"
1367+
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"
1368+
dekSourceAESGCMKeyPath := test.getETCDPathForResource(test.storageConfig.Prefix, "", "secrets", dekSourceAESGCMKeyName, legacyDataNamespace)
1369+
if _, err := test.writeRawRecordToETCD(dekSourceAESGCMKeyPath, []byte(dekSourceAESGCMKeyData)); err != nil {
1370+
t.Fatal(err)
1371+
}
1372+
1373+
expectedDEKSourceAESGCMKeyObject := &kmstypes.EncryptedObject{
1374+
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"),
1375+
KeyID: "1",
1376+
EncryptedDEKSource: []byte("gafIvwT0ASoKdZ/D1L2SlH73LUMj5qa3hroljfS51wc="),
1377+
Annotations: map[string][]byte{"local-kek.kms.kubernetes.io": []byte("encrypted-local-kek")},
1378+
EncryptedDEKSourceType: kmstypes.EncryptedDEKSourceType_AES_GCM_KEY,
1379+
}
1380+
legacyDEKSourceAESGCMKeyObject := &kmstypes.EncryptedObject{}
1381+
if err := proto.Unmarshal([]byte(strings.TrimPrefix(dekSourceAESGCMKeyData, "k8s:enc:kms:v2:kms-provider:")), legacyDEKSourceAESGCMKeyObject); err != nil {
1382+
t.Fatal(err)
1383+
}
1384+
if err := kmsv2.ValidateEncryptedObject(legacyDEKSourceAESGCMKeyObject); err != nil {
1385+
t.Fatal(err)
1386+
}
1387+
if diff := cmp.Diff(expectedDEKSourceAESGCMKeyObject, legacyDEKSourceAESGCMKeyObject); len(diff) > 0 {
1388+
t.Errorf("kms v2 legacy encrypted object diff (-want, +got):\n%s", diff)
1389+
}
1390+
1391+
// commit: 855e7c48de7388eb330da0f8d9d2394ee818fb8d
1392+
// tag: v1.28.0
1393+
const dekSourceHKDFSHA256XNonceAESGCMSeedName = "dek-source-hkdf-sha256-xnonce-aes-gcm-seed"
1394+
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\xdd\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"
1395+
dekSourceHKDFSHA256XNonceAESGCMSeedPath := test.getETCDPathForResource(test.storageConfig.Prefix, "", "secrets", dekSourceHKDFSHA256XNonceAESGCMSeedName, legacyDataNamespace)
1396+
if _, err := test.writeRawRecordToETCD(dekSourceHKDFSHA256XNonceAESGCMSeedPath, []byte(dekSourceHKDFSHA256XNonceAESGCMSeedData)); err != nil {
1397+
t.Fatal(err)
1398+
}
1399+
1400+
expectedDEKSourceHKDFSHA256XNonceAESGCMSeedObject := &kmstypes.EncryptedObject{
1401+
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\xdd\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"),
1402+
KeyID: "1",
1403+
EncryptedDEKSource: []byte("/+WnKXQEM/AhXICYRNBeGk+WSuB+7OBuSYJTbP66Zyc="),
1404+
Annotations: map[string][]byte{"local-kek.kms.kubernetes.io": []byte("encrypted-local-kek")},
1405+
EncryptedDEKSourceType: kmstypes.EncryptedDEKSourceType_HKDF_SHA256_XNONCE_AES_GCM_SEED,
1406+
}
1407+
legacyDEKSourceHKDFSHA256XNonceAESGCMSeedObject := &kmstypes.EncryptedObject{}
1408+
if err := proto.Unmarshal([]byte(strings.TrimPrefix(dekSourceHKDFSHA256XNonceAESGCMSeedData, "k8s:enc:kms:v2:kms-provider:")), legacyDEKSourceHKDFSHA256XNonceAESGCMSeedObject); err != nil {
1409+
t.Fatal(err)
1410+
}
1411+
if err := kmsv2.ValidateEncryptedObject(legacyDEKSourceHKDFSHA256XNonceAESGCMSeedObject); err != nil {
1412+
t.Fatal(err)
1413+
}
1414+
if diff := cmp.Diff(expectedDEKSourceHKDFSHA256XNonceAESGCMSeedObject, legacyDEKSourceHKDFSHA256XNonceAESGCMSeedObject); len(diff) > 0 {
1415+
t.Errorf("kms v2 legacy encrypted object diff (-want, +got):\n%s", diff)
1416+
}
1417+
1418+
ctx := testContext(t)
1419+
1420+
legacySecrets, err := test.restClient.CoreV1().Secrets(legacyDataNamespace).List(ctx, metav1.ListOptions{})
1421+
if err != nil {
1422+
t.Fatal(err)
1423+
}
1424+
1425+
dekSourceAESGCMKeyTime := metav1.NewTime(time.Date(2023, 9, 1, 11, 56, 49, 0, time.FixedZone("EDT", -4*60*60)))
1426+
dekSourceHKDFSHA256XNonceAESGCMSeedTime := metav1.NewTime(time.Date(2023, 9, 1, 10, 23, 13, 0, time.FixedZone("EDT", -4*60*60)))
1427+
1428+
expectedLegacySecrets := &corev1.SecretList{
1429+
Items: []corev1.Secret{
1430+
{
1431+
ObjectMeta: metav1.ObjectMeta{
1432+
Name: dekSourceAESGCMKeyName,
1433+
Namespace: legacyDataNamespace,
1434+
UID: "1f4a8f7b-01b4-49d1-b898-751eb56937f1",
1435+
CreationTimestamp: dekSourceAESGCMKeyTime,
1436+
ManagedFields: []metav1.ManagedFieldsEntry{
1437+
{
1438+
Manager: "___TestKMSv2Provider_in_k8s_io_kubernetes_test_integration_controlplane_transformation.test",
1439+
Operation: "Update",
1440+
APIVersion: "v1",
1441+
Time: &dekSourceAESGCMKeyTime,
1442+
FieldsType: "FieldsV1",
1443+
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:data":{".":{},"f:api_key":{}},"f:type":{}}`)},
1444+
},
1445+
},
1446+
},
1447+
Data: map[string][]byte{
1448+
secretKey: []byte(secretVal),
1449+
},
1450+
Type: corev1.SecretTypeOpaque,
1451+
},
1452+
{
1453+
ObjectMeta: metav1.ObjectMeta{
1454+
Name: dekSourceHKDFSHA256XNonceAESGCMSeedName,
1455+
Namespace: legacyDataNamespace,
1456+
UID: "87c514b4-9c26-4041-ad0d-0d07dca557ed",
1457+
CreationTimestamp: dekSourceHKDFSHA256XNonceAESGCMSeedTime,
1458+
ManagedFields: []metav1.ManagedFieldsEntry{
1459+
{
1460+
Manager: "___TestKMSv2Provider_extended_nonce_gcm_in_k8s_io_kubernetes_test_integration_controlplane_transformation.test",
1461+
Operation: "Update",
1462+
APIVersion: "v1",
1463+
Time: &dekSourceHKDFSHA256XNonceAESGCMSeedTime,
1464+
FieldsType: "FieldsV1",
1465+
FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:data":{".":{},"f:api_key":{}},"f:type":{}}`)},
1466+
},
1467+
},
1468+
},
1469+
Data: map[string][]byte{
1470+
secretKey: []byte(secretVal),
1471+
},
1472+
Type: corev1.SecretTypeOpaque,
1473+
},
1474+
},
1475+
}
1476+
1477+
if diff := cmp.Diff(expectedLegacySecrets, legacySecrets,
1478+
// resource version is set after decoding based on etcd state - it is not stored in the etcd value
1479+
cmpopts.IgnoreFields(corev1.Secret{}, "ResourceVersion"),
1480+
cmpopts.IgnoreFields(metav1.ListMeta{}, "ResourceVersion"),
1481+
); len(diff) > 0 {
1482+
t.Errorf("kms v2 legacy secret data diff (-want, +got):\n%s", diff)
1483+
}
1484+
}

0 commit comments

Comments
 (0)