Skip to content

Commit 7adcad3

Browse files
authored
Merge pull request kubernetes#128169 from liggitt/4193-ga
KEP-4193: Promote ServiceAccountTokenJTI, ServiceAccountTokenPodNodeInfo, ServiceAccountTokenNodeBindingValidation to stable
2 parents d7bd728 + 0771f60 commit 7adcad3

File tree

7 files changed

+61
-57
lines changed

7 files changed

+61
-57
lines changed

pkg/features/versioned_kube_features.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
641641
ServiceAccountTokenJTI: {
642642
{Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Alpha},
643643
{Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.Beta},
644+
{Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.34
644645
},
645646

646647
ServiceAccountTokenNodeBinding: {
@@ -651,11 +652,13 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
651652
ServiceAccountTokenNodeBindingValidation: {
652653
{Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Alpha},
653654
{Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.Beta},
655+
{Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.34
654656
},
655657

656658
ServiceAccountTokenPodNodeInfo: {
657659
{Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Alpha},
658660
{Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.Beta},
661+
{Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.34
659662
},
660663

661664
ServiceTrafficDistribution: {

pkg/kubeapiserver/options/authentication_test.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import (
4343
"k8s.io/component-base/featuregate"
4444
featuregatetesting "k8s.io/component-base/featuregate/testing"
4545
openapicommon "k8s.io/kube-openapi/pkg/common"
46-
kubefeatures "k8s.io/kubernetes/pkg/features"
4746
kubeauthenticator "k8s.io/kubernetes/pkg/kubeapiserver/authenticator"
4847
"k8s.io/kubernetes/pkg/serviceaccount"
4948
"k8s.io/utils/pointer"
@@ -237,12 +236,6 @@ func TestAuthenticationValidate(t *testing.T) {
237236
},
238237
expectErr: "authentication-config file and oidc-* flags are mutually exclusive",
239238
},
240-
{
241-
name: "fails to validate if ServiceAccountTokenNodeBindingValidation is disabled and ServiceAccountTokenNodeBinding is enabled",
242-
enabledFeatures: []featuregate.Feature{kubefeatures.ServiceAccountTokenNodeBinding},
243-
disabledFeatures: []featuregate.Feature{kubefeatures.ServiceAccountTokenNodeBindingValidation},
244-
expectErr: "the \"ServiceAccountTokenNodeBinding\" feature gate can only be enabled if the \"ServiceAccountTokenNodeBindingValidation\" feature gate is also enabled",
245-
},
246239
{
247240
name: "test when authentication config file and anonymous-auth flags are set AnonymousAuthConfigurableEndpoints disabled",
248241
disabledFeatures: []featuregate.Feature{features.AnonymousAuthConfigurableEndpoints},
@@ -489,7 +482,6 @@ func TestBuiltInAuthenticationOptionsAddFlags(t *testing.T) {
489482
}
490483

491484
func TestWithTokenGetterFunction(t *testing.T) {
492-
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, kubefeatures.ServiceAccountTokenNodeBindingValidation, false)
493485
fakeClientset := fake.NewSimpleClientset()
494486
versionedInformer := informers.NewSharedInformerFactory(fakeClientset, 0)
495487
{

pkg/serviceaccount/claims_test.go

Lines changed: 15 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func TestClaims(t *testing.T) {
8989
sc *jwt.Claims
9090
pc *privateClaims
9191

92-
featureJTI, featurePodNodeInfo, featureNodeBinding bool
92+
featureNodeBinding bool
9393
}{
9494
{
9595
// pod and secret
@@ -115,6 +115,7 @@ func TestClaims(t *testing.T) {
115115
IssuedAt: jwt.NewNumericDate(time.Unix(1514764800, 0)),
116116
NotBefore: jwt.NewNumericDate(time.Unix(1514764800, 0)),
117117
Expiry: jwt.NewNumericDate(time.Unix(1514764800+100, 0)),
118+
ID: "fixed",
118119
},
119120
pc: &privateClaims{
120121
Kubernetes: kubernetes{
@@ -138,6 +139,7 @@ func TestClaims(t *testing.T) {
138139
IssuedAt: jwt.NewNumericDate(time.Unix(1514764800, 0)),
139140
NotBefore: jwt.NewNumericDate(time.Unix(1514764800, 0)),
140141
Expiry: jwt.NewNumericDate(time.Unix(1514764800+100, 0)),
142+
ID: "fixed",
141143
},
142144
pc: &privateClaims{
143145
Kubernetes: kubernetes{
@@ -160,6 +162,7 @@ func TestClaims(t *testing.T) {
160162
IssuedAt: jwt.NewNumericDate(time.Unix(1514764800, 0)),
161163
NotBefore: jwt.NewNumericDate(time.Unix(1514764800, 0)),
162164
Expiry: jwt.NewNumericDate(time.Unix(1514764800+100, 0)),
165+
ID: "fixed",
163166
},
164167
pc: &privateClaims{
165168
Kubernetes: kubernetes{
@@ -182,6 +185,7 @@ func TestClaims(t *testing.T) {
182185
IssuedAt: jwt.NewNumericDate(time.Unix(1514764800, 0)),
183186
NotBefore: jwt.NewNumericDate(time.Unix(1514764800, 0)),
184187
Expiry: jwt.NewNumericDate(time.Unix(1514764800+60*60*24, 0)),
188+
ID: "fixed",
185189
},
186190
pc: &privateClaims{
187191
Kubernetes: kubernetes{
@@ -202,30 +206,6 @@ func TestClaims(t *testing.T) {
202206
aud: nil,
203207
err: "token bound to Node object requested, but \"ServiceAccountTokenNodeBinding\" feature gate is disabled",
204208
},
205-
{
206-
// node & pod with feature gate disabled
207-
sa: sa,
208-
node: node,
209-
pod: pod,
210-
// really fast
211-
exp: 0,
212-
// nil audience
213-
aud: nil,
214-
215-
sc: &jwt.Claims{
216-
Subject: "system:serviceaccount:myns:mysvcacct",
217-
IssuedAt: jwt.NewNumericDate(time.Unix(1514764800, 0)),
218-
NotBefore: jwt.NewNumericDate(time.Unix(1514764800, 0)),
219-
Expiry: jwt.NewNumericDate(time.Unix(1514764800, 0)),
220-
},
221-
pc: &privateClaims{
222-
Kubernetes: kubernetes{
223-
Namespace: "myns",
224-
Pod: &ref{Name: "mypod", UID: "mypod-uid"},
225-
Svcacct: ref{Name: "mysvcacct", UID: "mysvcacct-uid"},
226-
},
227-
},
228-
},
229209
{
230210
// node alone
231211
sa: sa,
@@ -242,6 +222,7 @@ func TestClaims(t *testing.T) {
242222
IssuedAt: jwt.NewNumericDate(time.Unix(1514764800, 0)),
243223
NotBefore: jwt.NewNumericDate(time.Unix(1514764800, 0)),
244224
Expiry: jwt.NewNumericDate(time.Unix(1514764800, 0)),
225+
ID: "fixed",
245226
},
246227
pc: &privateClaims{
247228
Kubernetes: kubernetes{
@@ -256,8 +237,6 @@ func TestClaims(t *testing.T) {
256237
sa: sa,
257238
pod: pod,
258239
node: node,
259-
// enable embedding pod node info feature
260-
featurePodNodeInfo: true,
261240
// really fast
262241
exp: 0,
263242
// nil audience
@@ -268,6 +247,7 @@ func TestClaims(t *testing.T) {
268247
IssuedAt: jwt.NewNumericDate(time.Unix(1514764800, 0)),
269248
NotBefore: jwt.NewNumericDate(time.Unix(1514764800, 0)),
270249
Expiry: jwt.NewNumericDate(time.Unix(1514764800, 0)),
250+
ID: "fixed",
271251
},
272252
pc: &privateClaims{
273253
Kubernetes: kubernetes{
@@ -294,8 +274,6 @@ func TestClaims(t *testing.T) {
294274
{
295275
// ensure JTI is set
296276
sa: sa,
297-
// enable setting JTI feature
298-
featureJTI: true,
299277
// really fast
300278
exp: 0,
301279
// nil audience
@@ -342,9 +320,7 @@ func TestClaims(t *testing.T) {
342320
}
343321

344322
// set feature flags for the duration of the test case
345-
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenJTI, c.featureJTI)
346323
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenNodeBinding, c.featureNodeBinding)
347-
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenPodNodeInfo, c.featurePodNodeInfo)
348324

349325
sc, pc, err := Claims(c.sa, c.pod, c.sec, c.node, c.exp, c.warnafter, c.aud)
350326
if err != nil && err.Error() != c.err {
@@ -376,8 +352,6 @@ type claimTestCase struct {
376352
expiry jwt.NumericDate
377353
notBefore jwt.NumericDate
378354
expectErr string
379-
380-
featureNodeBindingValidation bool
381355
}
382356

383357
func TestValidatePrivateClaims(t *testing.T) {
@@ -458,11 +432,10 @@ func TestValidatePrivateClaims(t *testing.T) {
458432
expectErr: "service account token has been invalidated",
459433
},
460434
{
461-
name: "missing node",
462-
getter: fakeGetter{serviceAccount, nil, nil, nil},
463-
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Node: &ref{Name: "nodename", UID: "nodeuid"}, Namespace: "ns"}},
464-
expectErr: "service account token has been invalidated",
465-
featureNodeBindingValidation: true,
435+
name: "missing node",
436+
getter: fakeGetter{serviceAccount, nil, nil, nil},
437+
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Node: &ref{Name: "nodename", UID: "nodeuid"}, Namespace: "ns"}},
438+
expectErr: "service account token has been invalidated",
466439
},
467440
{
468441
name: "different uid serviceaccount",
@@ -522,11 +495,10 @@ func TestValidatePrivateClaims(t *testing.T) {
522495
expectErr: deletedErr,
523496
},
524497
claimTestCase{
525-
name: deletionTestCase.name + " node",
526-
getter: fakeGetter{serviceAccount, nil, nil, deletedNode},
527-
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Node: &ref{Name: "nodename", UID: "nodeuid"}, Namespace: "ns"}},
528-
expectErr: deletedErr,
529-
featureNodeBindingValidation: true,
498+
name: deletionTestCase.name + " node",
499+
getter: fakeGetter{serviceAccount, nil, nil, deletedNode},
500+
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Node: &ref{Name: "nodename", UID: "nodeuid"}, Namespace: "ns"}},
501+
expectErr: deletedErr,
530502
},
531503
)
532504
}
@@ -539,8 +511,6 @@ func TestValidatePrivateClaims(t *testing.T) {
539511
expiry = tc.expiry
540512
}
541513

542-
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenNodeBindingValidation, tc.featureNodeBindingValidation)
543-
544514
_, err := v.Validate(context.Background(), "", &jwt.Claims{Expiry: &expiry, NotBefore: &tc.notBefore}, tc.private)
545515
if len(tc.expectErr) > 0 {
546516
if errStr := errString(err); tc.expectErr != errStr {

test/e2e/auth/per_node_update.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
g "github.com/onsi/ginkgo/v2"
2626
o "github.com/onsi/gomega"
27+
2728
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
2829
authenticationv1 "k8s.io/api/authentication/v1"
2930
v1 "k8s.io/api/core/v1"
@@ -34,7 +35,6 @@ import (
3435
"k8s.io/client-go/kubernetes"
3536
cgoscheme "k8s.io/client-go/kubernetes/scheme"
3637
"k8s.io/client-go/rest"
37-
"k8s.io/kubernetes/pkg/features"
3838
"k8s.io/kubernetes/test/e2e/framework"
3939
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
4040
imageutils "k8s.io/kubernetes/test/utils/image"
@@ -52,7 +52,7 @@ var (
5252
perNodeCheckValidatingAdmissionPolicyBinding string
5353
)
5454

55-
var _ = SIGDescribe("ValidatingAdmissionPolicy", framework.WithFeatureGate(features.ServiceAccountTokenNodeBindingValidation), func() {
55+
var _ = SIGDescribe("ValidatingAdmissionPolicy", func() {
5656
defer g.GinkgoRecover()
5757
f := framework.NewDefaultFramework("node-authn")
5858
f.NamespacePodSecurityLevel = admissionapi.LevelRestricted

test/e2e/auth/service_accounts.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ var _ = SIGDescribe("ServiceAccounts", func() {
100100
framework.ExpectNoError(err)
101101
framework.ExpectNoError(e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod))
102102

103+
// Read the running pod to get the current node name
104+
pod, err = f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(ctx, pod.Name, metav1.GetOptions{})
105+
framework.ExpectNoError(err)
106+
node, err := f.ClientSet.CoreV1().Nodes().Get(ctx, pod.Spec.NodeName, metav1.GetOptions{})
107+
framework.ExpectNoError(err)
108+
103109
tk := e2ekubectl.NewTestKubeconfig(framework.TestContext.CertDir, framework.TestContext.Host, framework.TestContext.KubeConfig, framework.TestContext.KubeContext, framework.TestContext.KubectlPath, f.Namespace.Name)
104110
mountedToken, err := tk.ReadFileViaContainer(pod.Name, pod.Spec.Containers[0].Name, path.Join(serviceaccount.DefaultAPITokenMountPath, v1.ServiceAccountTokenKey))
105111
framework.ExpectNoError(err)
@@ -133,6 +139,29 @@ var _ = SIGDescribe("ServiceAccounts", func() {
133139
if !groups.Has("system:serviceaccounts:" + f.Namespace.Name) {
134140
framework.Failf("expected system:serviceaccounts:%s group, had %v", f.Namespace.Name, groups.List())
135141
}
142+
143+
credentialID, ok := tokenReview.Status.User.Extra["authentication.kubernetes.io/credential-id"]
144+
if !ok || len(credentialID) != 1 || !strings.HasPrefix(credentialID[0], "JTI=") {
145+
framework.Failf("expected single authentication.kubernetes.io/credential-id extra info item starting with 'JTI=', got %v", credentialID)
146+
}
147+
148+
podName, ok := tokenReview.Status.User.Extra["authentication.kubernetes.io/pod-name"]
149+
if !ok || len(podName) != 1 || podName[0] != pod.Name {
150+
framework.Failf("expected single authentication.kubernetes.io/pod-name extra info item matching %v, got %v", pod.Name, podName)
151+
}
152+
podUID, ok := tokenReview.Status.User.Extra["authentication.kubernetes.io/pod-uid"]
153+
if !ok || len(podUID) != 1 || podUID[0] != string(pod.UID) {
154+
framework.Failf("expected single authentication.kubernetes.io/pod-uid extra info item matching %v, got %v", pod.UID, podUID)
155+
}
156+
157+
nodeName, ok := tokenReview.Status.User.Extra["authentication.kubernetes.io/node-name"]
158+
if !ok || len(nodeName) != 1 || nodeName[0] != node.Name {
159+
framework.Failf("expected single authentication.kubernetes.io/node-name extra info item matching %v, got %v", node.Name, nodeName)
160+
}
161+
nodeUID, ok := tokenReview.Status.User.Extra["authentication.kubernetes.io/node-uid"]
162+
if !ok || len(nodeUID) != 1 || nodeUID[0] != string(node.UID) {
163+
framework.Failf("expected single authentication.kubernetes.io/node-uid extra info item matching %v, got %v", node.UID, nodeUID)
164+
}
136165
})
137166

138167
/*

test/featuregates_linter/test_data/versioned_feature_list.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,10 @@
10441044
lockToDefault: false
10451045
preRelease: Beta
10461046
version: "1.30"
1047+
- default: true
1048+
lockToDefault: true
1049+
preRelease: GA
1050+
version: "1.32"
10471051
- name: ServiceAccountTokenNodeBinding
10481052
versionedSpecs:
10491053
- default: false
@@ -1064,6 +1068,10 @@
10641068
lockToDefault: false
10651069
preRelease: Beta
10661070
version: "1.30"
1071+
- default: true
1072+
lockToDefault: true
1073+
preRelease: GA
1074+
version: "1.32"
10671075
- name: ServiceAccountTokenPodNodeInfo
10681076
versionedSpecs:
10691077
- default: false
@@ -1074,6 +1082,10 @@
10741082
lockToDefault: false
10751083
preRelease: Beta
10761084
version: "1.30"
1085+
- default: true
1086+
lockToDefault: true
1087+
preRelease: GA
1088+
version: "1.32"
10771089
- name: ServiceTrafficDistribution
10781090
versionedSpecs:
10791091
- default: false

test/integration/auth/svcaccttoken_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,6 @@ func TestServiceAccountTokenCreate(t *testing.T) {
248248
})
249249

250250
t.Run("bound to service account and pod", func(t *testing.T) {
251-
// Disable embedding pod's node info
252-
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenPodNodeInfo, false)
253251
treq := &authenticationv1.TokenRequest{
254252
Spec: authenticationv1.TokenRequestSpec{
255253
Audiences: []string{"api"},

0 commit comments

Comments
 (0)