Skip to content

Commit ebf46ce

Browse files
authored
Merge pull request kubernetes#121485 from ritazh/kmsv2-ga
[KMSv2] promote KMSv2 and KMSv2KDF to GA
2 parents 0712728 + a9b1adb commit ebf46ce

File tree

11 files changed

+265
-177
lines changed

11 files changed

+265
-177
lines changed

staging/src/k8s.io/apiserver/pkg/features/kube_features.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,15 @@ const (
122122
// kep: https://kep.k8s.io/3299
123123
// alpha: v1.25
124124
// beta: v1.27
125+
// stable: v1.29
125126
//
126127
// Enables KMS v2 API for encryption at rest.
127128
KMSv2 featuregate.Feature = "KMSv2"
128129

129130
// owner: @enj
130131
// kep: https://kep.k8s.io/3299
131132
// beta: v1.28
133+
// stable: v1.29
132134
//
133135
// Enables the use of derived encryption keys with KMS v2.
134136
KMSv2KDF featuregate.Feature = "KMSv2KDF"
@@ -279,11 +281,11 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
279281

280282
EfficientWatchResumption: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
281283

282-
KMSv1: {Default: true, PreRelease: featuregate.Deprecated},
284+
KMSv1: {Default: false, PreRelease: featuregate.Deprecated},
283285

284-
KMSv2: {Default: true, PreRelease: featuregate.Beta},
286+
KMSv2: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.31
285287

286-
KMSv2KDF: {Default: true, PreRelease: featuregate.Beta}, // lock to true in 1.29 once KMSv2 is GA, remove in 1.31
288+
KMSv2KDF: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.31
287289

288290
OpenAPIEnums: {Default: true, PreRelease: featuregate.Beta},
289291

staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,26 @@ const (
107107

108108
var codecs serializer.CodecFactory
109109

110+
// this atomic bool allows us to swap enablement of the KMSv2KDF feature in tests
111+
// as the feature gate is now locked to true starting with v1.29
112+
// Note: it cannot be set by an end user
113+
var kdfDisabled atomic.Bool
114+
115+
// this function should only be called in tests to swap enablement of the KMSv2KDF feature
116+
func SetKDFForTests(b bool) func() {
117+
kdfDisabled.Store(!b)
118+
return func() {
119+
kdfDisabled.Store(false)
120+
}
121+
}
122+
123+
// this function should be used to determine enablement of the KMSv2KDF feature
124+
// instead of getting it from DefaultFeatureGate as the feature gate is now locked
125+
// to true starting with v1.29
126+
func GetKDF() bool {
127+
return !kdfDisabled.Load()
128+
}
129+
110130
func init() {
111131
configScheme := runtime.NewScheme()
112132
utilruntime.Must(apiserverconfig.AddToScheme(configScheme))
@@ -138,6 +158,7 @@ type kmsv2PluginProbe struct {
138158
lastResponse *kmsPluginHealthzResponse
139159
l *sync.Mutex
140160
apiServerID string
161+
version string
141162
}
142163

143164
type kmsHealthChecker []healthz.HealthChecker
@@ -369,7 +390,7 @@ func (h *kmsv2PluginProbe) rotateDEKOnKeyIDChange(ctx context.Context, statusKey
369390
// this gate can only change during tests, but the check is cheap enough to always make
370391
// this allows us to easily exercise both modes without restarting the API server
371392
// TODO integration test that this dynamically takes effect
372-
useSeed := utilfeature.DefaultFeatureGate.Enabled(features.KMSv2KDF)
393+
useSeed := GetKDF()
373394
stateUseSeed := state.EncryptedObject.EncryptedDEKSourceType == kmstypes.EncryptedDEKSourceType_HKDF_SHA256_XNONCE_AES_GCM_SEED
374395

375396
// state is valid and status keyID is unchanged from when we generated this DEK/seed so there is no need to rotate it
@@ -454,8 +475,16 @@ func (h *kmsv2PluginProbe) isKMSv2ProviderHealthyAndMaybeRotateDEK(ctx context.C
454475
if response.Healthz != "ok" {
455476
errs = append(errs, fmt.Errorf("got unexpected healthz status: %s", response.Healthz))
456477
}
457-
if response.Version != envelopekmsv2.KMSAPIVersion {
458-
errs = append(errs, fmt.Errorf("expected KMSv2 API version %s, got %s", envelopekmsv2.KMSAPIVersion, response.Version))
478+
if response.Version != envelopekmsv2.KMSAPIVersionv2 && response.Version != envelopekmsv2.KMSAPIVersionv2beta1 {
479+
errs = append(errs, fmt.Errorf("expected KMSv2 API version %s, got %s", envelopekmsv2.KMSAPIVersionv2, response.Version))
480+
} else {
481+
// set version for the first status response
482+
if len(h.version) == 0 {
483+
h.version = response.Version
484+
}
485+
if h.version != response.Version {
486+
errs = append(errs, fmt.Errorf("KMSv2 API version should not change after the initial status response version %s, got %s", h.version, response.Version))
487+
}
459488
}
460489

461490
if errCode, err := envelopekmsv2.ValidateKeyID(response.KeyID); err != nil {

staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config_test.go

Lines changed: 72 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ func TestLegacyConfig(t *testing.T) {
187187
}
188188

189189
func TestEncryptionProviderConfigCorrect(t *testing.T) {
190-
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv2, true)()
190+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv1, true)()
191191

192192
// Set factory for mock envelope service
193193
factory := envelopeServiceFactory
@@ -353,42 +353,33 @@ func TestKMSv1Deprecation(t *testing.T) {
353353

354354
func TestKMSvsEnablement(t *testing.T) {
355355
testCases := []struct {
356-
name string
357-
kmsv2Enabled bool
358-
filePath string
359-
expectedErr string
356+
name string
357+
filePath string
358+
expectedErr string
360359
}{
361360
{
362-
name: "config with kmsv2 and kmsv1, KMSv2=false",
363-
kmsv2Enabled: false,
364-
filePath: "testdata/valid-configs/kms/multiple-providers-kmsv2.yaml",
365-
expectedErr: "KMSv2 feature is not enabled",
366-
},
367-
{
368-
name: "config with kmsv2 and kmsv1, KMSv2=true",
369-
kmsv2Enabled: true,
370-
filePath: "testdata/valid-configs/kms/multiple-providers-kmsv2.yaml",
371-
expectedErr: "",
361+
name: "config with kmsv2 and kmsv1, KMSv2=true, KMSv1=false, should fail when feature is disabled",
362+
filePath: "testdata/valid-configs/kms/multiple-providers-mixed.yaml",
363+
expectedErr: "KMSv1 is deprecated and will only receive security updates going forward. Use KMSv2 instead",
372364
},
373365
{
374-
name: "config with kmsv1, KMSv2=false",
375-
kmsv2Enabled: false,
376-
filePath: "testdata/valid-configs/kms/multiple-providers.yaml",
377-
expectedErr: "",
366+
name: "config with kmsv2, KMSv2=true, KMSv1=false",
367+
filePath: "testdata/valid-configs/kms/multiple-providers-kmsv2.yaml",
368+
expectedErr: "",
378369
},
379370
}
380371

381372
for _, testCase := range testCases {
382373
t.Run(testCase.name, func(t *testing.T) {
383-
// Just testing KMSv2 feature flag
384-
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv1, true)()
385-
386-
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv2, testCase.kmsv2Enabled)()
374+
// only the KMSv2 feature flag is enabled
387375
_, err := LoadEncryptionConfig(testContext(t), testCase.filePath, false, "")
388376

389-
if !strings.Contains(errString(err), testCase.expectedErr) {
377+
if len(testCase.expectedErr) > 0 && !strings.Contains(errString(err), testCase.expectedErr) {
390378
t.Fatalf("expected error %q, got %q", testCase.expectedErr, errString(err))
391379
}
380+
if len(testCase.expectedErr) == 0 && err != nil {
381+
t.Fatalf("unexpected error %q", errString(err))
382+
}
392383

393384
})
394385
}
@@ -400,43 +391,6 @@ func TestKMSvsEnablement(t *testing.T) {
400391
config apiserverconfig.EncryptionConfiguration
401392
wantV2Used bool
402393
}{
403-
{
404-
name: "with kmsv1 and kmsv2, KMSv2=false",
405-
kmsv2Enabled: false,
406-
config: apiserverconfig.EncryptionConfiguration{
407-
Resources: []apiserverconfig.ResourceConfiguration{
408-
{
409-
Resources: []string{"secrets"},
410-
Providers: []apiserverconfig.ProviderConfiguration{
411-
{
412-
KMS: &apiserverconfig.KMSConfiguration{
413-
Name: "kms",
414-
APIVersion: "v1",
415-
Timeout: &metav1.Duration{
416-
Duration: 1 * time.Second,
417-
},
418-
Endpoint: "unix:///tmp/testprovider.sock",
419-
CacheSize: pointer.Int32(1000),
420-
},
421-
},
422-
{
423-
KMS: &apiserverconfig.KMSConfiguration{
424-
Name: "another-kms",
425-
APIVersion: "v2",
426-
Timeout: &metav1.Duration{
427-
Duration: 1 * time.Second,
428-
},
429-
Endpoint: "unix:///tmp/anothertestprovider.sock",
430-
CacheSize: pointer.Int32(1000),
431-
},
432-
},
433-
},
434-
},
435-
},
436-
},
437-
expectedErr: "KMSv2 feature is not enabled",
438-
wantV2Used: false,
439-
},
440394
{
441395
name: "with kmsv1 and kmsv2, KMSv2=true",
442396
kmsv2Enabled: true,
@@ -501,7 +455,7 @@ func TestKMSvsEnablement(t *testing.T) {
501455
}
502456

503457
func TestKMSMaxTimeout(t *testing.T) {
504-
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv2, true)()
458+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv1, true)()
505459

506460
testCases := []struct {
507461
name string
@@ -749,7 +703,7 @@ func TestKMSMaxTimeout(t *testing.T) {
749703
}
750704

751705
func TestKMSPluginHealthz(t *testing.T) {
752-
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv2, true)()
706+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv1, true)()
753707

754708
kmsv2Probe := &kmsv2PluginProbe{
755709
name: "foo",
@@ -823,7 +777,7 @@ func TestKMSPluginHealthz(t *testing.T) {
823777
},
824778
{
825779
desc: "Install multiple healthz with v1 and v2",
826-
config: "testdata/valid-configs/kms/multiple-providers-kmsv2.yaml",
780+
config: "testdata/valid-configs/kms/multiple-providers-mixed.yaml",
827781
want: []healthChecker{
828782
kmsv2Probe,
829783
&kmsPluginProbe{
@@ -900,6 +854,7 @@ func TestKMSPluginHealthz(t *testing.T) {
900854

901855
// tests for masking rules
902856
func TestWildcardMasking(t *testing.T) {
857+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv1, true)()
903858

904859
testCases := []struct {
905860
desc string
@@ -1308,7 +1263,7 @@ func TestWildcardMasking(t *testing.T) {
13081263
}
13091264

13101265
func TestWildcardStructure(t *testing.T) {
1311-
1266+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv1, true)()
13121267
testCases := []struct {
13131268
desc string
13141269
expectedResourceTransformers map[string]string
@@ -1752,19 +1707,19 @@ func TestIsKMSv2ProviderHealthyError(t *testing.T) {
17521707
statusResponse: &kmsservice.StatusResponse{
17531708
Healthz: "unhealthy",
17541709
},
1755-
expectedErr: "got unexpected healthz status: unhealthy, expected KMSv2 API version v2beta1, got , got invalid KMSv2 KeyID ",
1710+
expectedErr: "got unexpected healthz status: unhealthy, expected KMSv2 API version v2, got , got invalid KMSv2 KeyID ",
17561711
wantMetrics: `
17571712
# HELP apiserver_envelope_encryption_invalid_key_id_from_status_total [ALPHA] Number of times an invalid keyID is returned by the Status RPC call split by error.
17581713
# TYPE apiserver_envelope_encryption_invalid_key_id_from_status_total counter
17591714
apiserver_envelope_encryption_invalid_key_id_from_status_total{error="empty",provider_name="testplugin"} 1
17601715
`,
17611716
},
17621717
{
1763-
desc: "version is not v2beta1",
1718+
desc: "version is not v2",
17641719
statusResponse: &kmsservice.StatusResponse{
17651720
Version: "v1beta1",
17661721
},
1767-
expectedErr: "got unexpected healthz status: , expected KMSv2 API version v2beta1, got v1beta1, got invalid KMSv2 KeyID ",
1722+
expectedErr: "got unexpected healthz status: , expected KMSv2 API version v2, got v1beta1, got invalid KMSv2 KeyID ",
17681723
wantMetrics: `
17691724
# HELP apiserver_envelope_encryption_invalid_key_id_from_status_total [ALPHA] Number of times an invalid keyID is returned by the Status RPC call split by error.
17701725
# TYPE apiserver_envelope_encryption_invalid_key_id_from_status_total counter
@@ -1788,7 +1743,7 @@ func TestIsKMSv2ProviderHealthyError(t *testing.T) {
17881743
desc: "invalid long keyID",
17891744
statusResponse: &kmsservice.StatusResponse{
17901745
Healthz: "ok",
1791-
Version: "v2beta1",
1746+
Version: "v2",
17921747
KeyID: sampleInvalidKeyID,
17931748
},
17941749
expectedErr: "got invalid KMSv2 KeyID ",
@@ -1816,6 +1771,52 @@ func TestIsKMSv2ProviderHealthyError(t *testing.T) {
18161771
}
18171772
}
18181773

1774+
// test to ensure KMSv2 API version is not changed after the first status response
1775+
func TestKMSv2SameVersionFromStatus(t *testing.T) {
1776+
probe := &kmsv2PluginProbe{name: "testplugin"}
1777+
service, _ := newMockEnvelopeKMSv2Service(testContext(t), "unix:///tmp/testprovider.sock", "providerName", 3*time.Second)
1778+
probe.l = &sync.Mutex{}
1779+
probe.state.Store(&envelopekmsv2.State{})
1780+
probe.service = service
1781+
1782+
testCases := []struct {
1783+
desc string
1784+
expectedErr string
1785+
newVersion string
1786+
}{
1787+
{
1788+
desc: "version changed",
1789+
newVersion: "v2",
1790+
expectedErr: "KMSv2 API version should not change",
1791+
},
1792+
{
1793+
desc: "version unchanged",
1794+
newVersion: "v2beta1",
1795+
expectedErr: "",
1796+
},
1797+
}
1798+
for _, tt := range testCases {
1799+
t.Run(tt.desc, func(t *testing.T) {
1800+
statusResponse := &kmsservice.StatusResponse{
1801+
Healthz: "ok",
1802+
Version: "v2beta1",
1803+
KeyID: "1",
1804+
}
1805+
if err := probe.isKMSv2ProviderHealthyAndMaybeRotateDEK(testContext(t), statusResponse); err != nil {
1806+
t.Fatal(err)
1807+
}
1808+
statusResponse.Version = tt.newVersion
1809+
err := probe.isKMSv2ProviderHealthyAndMaybeRotateDEK(testContext(t), statusResponse)
1810+
if len(tt.expectedErr) > 0 && !strings.Contains(errString(err), tt.expectedErr) {
1811+
t.Errorf("expected err %q, got %q", tt.expectedErr, errString(err))
1812+
}
1813+
if len(tt.expectedErr) == 0 && err != nil {
1814+
t.Fatal(err)
1815+
}
1816+
})
1817+
}
1818+
}
1819+
18191820
func testContext(t *testing.T) context.Context {
18201821
ctx, cancel := context.WithCancel(context.Background())
18211822
t.Cleanup(cancel)
@@ -1840,7 +1841,7 @@ func TestComputeEncryptionConfigHash(t *testing.T) {
18401841
}
18411842

18421843
func Test_kmsv2PluginProbe_rotateDEKOnKeyIDChange(t *testing.T) {
1843-
defaultUseSeed := utilfeature.DefaultFeatureGate.Enabled(features.KMSv2KDF)
1844+
defaultUseSeed := GetKDF()
18441845

18451846
origNowFunc := envelopekmsv2.NowFunc
18461847
now := origNowFunc() // freeze time
@@ -2065,7 +2066,7 @@ func Test_kmsv2PluginProbe_rotateDEKOnKeyIDChange(t *testing.T) {
20652066
}
20662067
for _, tt := range tests {
20672068
t.Run(tt.name, func(t *testing.T) {
2068-
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv2KDF, tt.useSeed)()
2069+
defer SetKDFForTests(tt.useSeed)()
20692070

20702071
var buf bytes.Buffer
20712072
klog.SetOutput(&buf)

staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/testdata/valid-configs/kms/multiple-providers-kmsv2.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ resources:
1010
endpoint: unix:///tmp/testprovider.sock
1111
timeout: 15s
1212
- kms:
13+
apiVersion: v2
1314
name: bar
1415
endpoint: unix:///tmp/testprovider.sock
1516
timeout: 15s
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
kind: EncryptionConfiguration
2+
apiVersion: apiserver.config.k8s.io/v1
3+
resources:
4+
- resources:
5+
- secrets
6+
providers:
7+
- kms:
8+
apiVersion: v2
9+
name: foo
10+
endpoint: unix:///tmp/testprovider.sock
11+
timeout: 15s
12+
- kms:
13+
name: bar
14+
endpoint: unix:///tmp/testprovider.sock
15+
timeout: 15s

staging/src/k8s.io/apiserver/pkg/server/options/etcd_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ func TestParseWatchCacheSizes(t *testing.T) {
263263
}
264264

265265
func TestKMSHealthzEndpoint(t *testing.T) {
266-
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv2, true)()
266+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KMSv1, true)()
267267

268268
testCases := []struct {
269269
name string

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ func init() {
5252
}
5353

5454
const (
55-
// KMSAPIVersion is the version of the KMS API.
56-
KMSAPIVersion = "v2beta1"
55+
// KMSAPIVersionv2 is a version of the KMS API.
56+
KMSAPIVersionv2 = "v2"
57+
// KMSAPIVersionv2beta1 is a version of the KMS API.
58+
KMSAPIVersionv2beta1 = "v2beta1"
5759
// annotationsMaxSize is the maximum size of the annotations.
5860
annotationsMaxSize = 32 * 1024 // 32 kB
5961
// KeyIDMaxSize is the maximum size of the keyID.

0 commit comments

Comments
 (0)