Merge pull request #142051 from antcybersec/remove-kep4193-serviceaccount-token-gates

serviceaccount: remove GA'd KEP-4193 token feature gates
This commit is contained in:
kubernetes-prow[bot] authored and GitHub committed 2026-10-21 20:21:54 +00:00
commit d2c4b85bdb
8 files changed
+4 -158

No files matched your search

-43
View File
@@ -1047,25 +1047,6 @@ const (
// kubelet can request a service account token for.
ServiceAccountNodeAudienceRestriction featuregate.Feature = "ServiceAccountNodeAudienceRestriction"
// owner: @munnerz
// kep: http://kep.k8s.io/4193
//
// Controls whether JTIs (UUIDs) are embedded into generated service account tokens, and whether these JTIs are
// recorded into the audit log for future requests made by these tokens.
ServiceAccountTokenJTI featuregate.Feature = "ServiceAccountTokenJTI"
// owner: @munnerz
// kep: http://kep.k8s.io/4193
//
// Controls whether the apiserver supports binding service account tokens to Node objects.
ServiceAccountTokenNodeBinding featuregate.Feature = "ServiceAccountTokenNodeBinding"
// owner: @munnerz
// kep: http://kep.k8s.io/4193
//
// Controls whether the apiserver will validate Node claims in service account tokens.
ServiceAccountTokenNodeBindingValidation featuregate.Feature = "ServiceAccountTokenNodeBindingValidation"
// owner: @jpbetz
//
// When enabled, the apiserver ignores changes to status fields in writes to the ServiceCIDR root resource.
@@ -2023,24 +2004,6 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
{Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.Beta},
},
ServiceAccountTokenJTI: {
{Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Alpha},
{Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.Beta},
{Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true},
},
ServiceAccountTokenNodeBinding: {
{Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Alpha},
{Version: version.MustParse("1.31"), Default: true, PreRelease: featuregate.Beta},
{Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.GA, LockToDefault: true},
},
ServiceAccountTokenNodeBindingValidation: {
{Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Alpha},
{Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.Beta},
{Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true},
},
ServiceCIDRStatusFieldWiping: {
{Version: version.MustParse("1.0"), Default: false, PreRelease: featuregate.GA},
{Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Deprecated},
@@ -2724,12 +2687,6 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature
ServiceAccountNodeAudienceRestriction: {},
ServiceAccountTokenJTI: {},
ServiceAccountTokenNodeBinding: {ServiceAccountTokenNodeBindingValidation},
ServiceAccountTokenNodeBindingValidation: {},
ServiceCIDRStatusFieldWiping: {},
StaleControllerConsistencyDaemonSet: {featuregate.Feature(clientfeatures.AtomicFIFO)},
+1 -9
View File
@@ -45,19 +45,16 @@ import (
"k8s.io/apiserver/pkg/server/egressselector"
genericoptions "k8s.io/apiserver/pkg/server/options"
authenticationconfigmetrics "k8s.io/apiserver/pkg/server/options/authenticationconfig/metrics"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/apiserver/pkg/util/filesystem"
"k8s.io/apiserver/plugin/pkg/authenticator/token/oidc"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
v1listers "k8s.io/client-go/listers/core/v1"
certutil "k8s.io/client-go/util/cert"
"k8s.io/client-go/util/keyutil"
cliflag "k8s.io/component-base/cli/flag"
"k8s.io/klog/v2"
openapicommon "k8s.io/kube-openapi/pkg/common"
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
"k8s.io/kubernetes/pkg/features"
kubeauthenticator "k8s.io/kubernetes/pkg/kubeapiserver/authenticator"
authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
"k8s.io/kubernetes/pkg/serviceaccount"
@@ -711,17 +708,12 @@ func (o *BuiltInAuthenticationOptions) ApplyTo(
if o.ServiceAccounts != nil && o.ServiceAccounts.OptionalTokenGetter != nil {
authenticatorConfig.ServiceAccountTokenGetter = o.ServiceAccounts.OptionalTokenGetter(versionedInformer)
} else {
var nodeLister v1listers.NodeLister
if utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenNodeBindingValidation) {
nodeLister = versionedInformer.Core().V1().Nodes().Lister()
}
authenticatorConfig.ServiceAccountTokenGetter = serviceaccountcontroller.NewGetterFromClient(
extclient,
versionedInformer.Core().V1().Secrets().Lister(),
versionedInformer.Core().V1().ServiceAccounts().Lister(),
versionedInformer.Core().V1().Pods().Lister(),
nodeLister,
versionedInformer.Core().V1().Nodes().Lister(),
versionedInformer.Admissionregistration().V1().ValidatingWebhookConfigurations().Lister(),
versionedInformer.Admissionregistration().V1().MutatingWebhookConfigurations().Lister(),
)
@@ -33,11 +33,8 @@ import (
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
"k8s.io/apiserver/pkg/registry/rest"
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
authenticationapi "k8s.io/kubernetes/pkg/apis/authentication"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/registry/registrytest"
token "k8s.io/kubernetes/pkg/serviceaccount"
)
@@ -116,9 +113,6 @@ func TestCreate_Token_SetsCredentialIDAuditAnnotation(t *testing.T) {
defer server.Terminate(t)
defer storage.Store.DestroyFunc()
// Enable JTI feature
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenJTI, true)
serviceAccount := validNewServiceAccount("foo")
ctx := genericregistrytest.NewNamespaceScopeContext(storage.Store, serviceAccount.Namespace)
_, err := storage.Store.Create(ctx, serviceAccount, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
@@ -47,7 +47,6 @@ import (
authenticationapi "k8s.io/kubernetes/pkg/apis/authentication"
authenticationvalidation "k8s.io/kubernetes/pkg/apis/authentication/validation"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
token "k8s.io/kubernetes/pkg/serviceaccount"
"k8s.io/utils/ptr"
)
@@ -208,9 +207,6 @@ func (r *TokenREST) Create(ctx context.Context, name string, obj runtime.Object,
}
}
case gvk.Group == "" && gvk.Kind == "Node":
if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenNodeBinding) {
return nil, errors.NewBadRequest(fmt.Sprintf("cannot bind token to a Node object as the %q feature-gate is disabled", features.ServiceAccountTokenNodeBinding))
}
newCtx := newContext(ctx, "nodes", ref.Name, "", gvk)
nodeObj, err := r.nodes.Get(newCtx, ref.Name, &metav1.GetOptions{})
if err != nil {
@@ -303,7 +299,7 @@ func (r *TokenREST) Create(ctx context.Context, name string, obj runtime.Object,
Token: tokdata,
ExpirationTimestamp: metav1.Time{Time: nowTime.Add(time.Duration(ptr.Deref(out.Spec.ExpirationSeconds, 0)) * time.Second)},
}
if utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenJTI) && len(sc.ID) > 0 {
if len(sc.ID) > 0 {
audit.AddAuditAnnotation(ctx, serviceaccount.IssuedCredentialIDAuditAnnotationKey, authenticationtokenjwt.CredentialIDForJTI(sc.ID))
}
return out, nil
+2 -17
View File
@@ -35,7 +35,6 @@ import (
"k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/apis/authentication"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
)
const (
@@ -83,9 +82,7 @@ func Claims(sa core.ServiceAccount, pod *core.Pod, secret *core.Secret, node *co
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Duration(expirationSeconds) * time.Second)),
}
if utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenJTI) {
sc.ID = newUUID()
}
sc.ID = newUUID()
pc := &privateClaims{
Kubernetes: kubernetes{
Namespace: sa.Namespace,
@@ -119,9 +116,6 @@ func Claims(sa core.ServiceAccount, pod *core.Pod, secret *core.Secret, node *co
UID: string(secret.UID),
}
case node != nil:
if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenNodeBinding) {
return nil, nil, fmt.Errorf("token bound to Node object requested, but %q feature gate is disabled", features.ServiceAccountTokenNodeBinding)
}
pc.Kubernetes.Node = &ref{
Name: node.Name,
UID: string(node.UID),
@@ -255,11 +249,6 @@ func (v *validator) Validate(ctx context.Context, _ string, public *jwt.Claims,
nodeName = noderef.Name
nodeUID = noderef.UID
case podref == nil:
if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenNodeBindingValidation) {
klog.V(4).Infof("ServiceAccount token is bound to a Node object, but the node bound token validation feature is disabled")
return nil, fmt.Errorf("token is bound to a Node object but the %s feature gate is disabled", features.ServiceAccountTokenNodeBindingValidation)
}
node, err := v.getter.GetNode(ctx, noderef.Name)
if err != nil {
klog.V(4).Infof("Could not retrieve node object %q for service account %s/%s: %v", noderef.Name, namespace, saref.Name, err)
@@ -320,10 +309,6 @@ func (v *validator) Validate(ctx context.Context, _ string, public *jwt.Claims,
}
}
var jti string
if utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenJTI) {
jti = public.ID
}
return &apiserverserviceaccount.ServiceAccountInfo{
Namespace: private.Kubernetes.Namespace,
Name: private.Kubernetes.Svcacct.Name,
@@ -337,7 +322,7 @@ func (v *validator) Validate(ctx context.Context, _ string, public *jwt.Claims,
ValidatingWebhookConfigurationUID: validatingUID,
MutatingWebhookConfigurationName: mutatingName,
MutatingWebhookConfigurationUID: mutatingUID,
CredentialID: authenticationtokenjwt.CredentialIDForJTI(jti),
CredentialID: authenticationtokenjwt.CredentialIDForJTI(public.ID),
}, nil
}
@@ -198,9 +198,6 @@
| SchedulerPreQueueingHints | | | 1.37– | | | | | [code](https://cs.k8s.io/?q=%5CbSchedulerPreQueueingHints%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbSchedulerPreQueueingHints%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| SeparateCacheWatchRPC | :ballot_box_with_check: 1.28+ | :closed_lock_with_key: 1.36+ | | 1.28–1.32 | | 1.33– | | [code](https://cs.k8s.io/?q=%5CbSeparateCacheWatchRPC%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbSeparateCacheWatchRPC%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| ServiceAccountNodeAudienceRestriction | :ballot_box_with_check: 1.33+ | | | 1.32– | | | | [code](https://cs.k8s.io/?q=%5CbServiceAccountNodeAudienceRestriction%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbServiceAccountNodeAudienceRestriction%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| ServiceAccountTokenJTI | :ballot_box_with_check: 1.30+ | :closed_lock_with_key: 1.32+ | 1.29 | 1.30–1.31 | 1.32– | | | [code](https://cs.k8s.io/?q=%5CbServiceAccountTokenJTI%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbServiceAccountTokenJTI%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| ServiceAccountTokenNodeBinding | :ballot_box_with_check: 1.31+ | :closed_lock_with_key: 1.33+ | 1.29–1.30 | 1.31–1.32 | 1.33– | | ServiceAccountTokenNodeBindingValidation | [code](https://cs.k8s.io/?q=%5CbServiceAccountTokenNodeBinding%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbServiceAccountTokenNodeBinding%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| ServiceAccountTokenNodeBindingValidation | :ballot_box_with_check: 1.30+ | :closed_lock_with_key: 1.32+ | 1.29 | 1.30–1.31 | 1.32– | | | [code](https://cs.k8s.io/?q=%5CbServiceAccountTokenNodeBindingValidation%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbServiceAccountTokenNodeBindingValidation%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| ServiceCIDRStatusFieldWiping | :ballot_box_with_check: 1.36+ | | | | 1.0–1.35 | 1.36– | | [code](https://cs.k8s.io/?q=%5CbServiceCIDRStatusFieldWiping%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbServiceCIDRStatusFieldWiping%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| ShardedListAndWatch | | | 1.36– | | | | | [code](https://cs.k8s.io/?q=%5CbShardedListAndWatch%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbShardedListAndWatch%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| SizeBasedListCostEstimate | :ballot_box_with_check: 1.34+ | | | 1.34– | | | | [code](https://cs.k8s.io/?q=%5CbSizeBasedListCostEstimate%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbSizeBasedListCostEstimate%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
@@ -1830,48 +1830,6 @@
lockToDefault: false
preRelease: Beta
version: "1.33"
- name: ServiceAccountTokenJTI
versionedSpecs:
- default: false
lockToDefault: false
preRelease: Alpha
version: "1.29"
- default: true
lockToDefault: false
preRelease: Beta
version: "1.30"
- default: true
lockToDefault: true
preRelease: GA
version: "1.32"
- name: ServiceAccountTokenNodeBinding
versionedSpecs:
- default: false
lockToDefault: false
preRelease: Alpha
version: "1.29"
- default: true
lockToDefault: false
preRelease: Beta
version: "1.31"
- default: true
lockToDefault: true
preRelease: GA
version: "1.33"
- name: ServiceAccountTokenNodeBindingValidation
versionedSpecs:
- default: false
lockToDefault: false
preRelease: Alpha
version: "1.29"
- default: true
lockToDefault: false
preRelease: Beta
version: "1.30"
- default: true
lockToDefault: true
preRelease: GA
version: "1.32"
- name: ServiceCIDRStatusFieldWiping
versionedSpecs:
- default: false
@@ -44,7 +44,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/version"
"k8s.io/apiserver/pkg/authentication/authenticator"
apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
"k8s.io/apiserver/pkg/authentication/user"
@@ -58,7 +57,6 @@ import (
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/controlplane"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/serviceaccount"
svcacct "k8s.io/kubernetes/plugin/pkg/admission/serviceaccount"
"k8s.io/kubernetes/test/integration/authutil"
@@ -564,37 +562,6 @@ func TestServiceAccountTokenCreate(t *testing.T) {
t.Run("bound to service account and a pod with an assigned nodeName that does not exist", testPodWithAssignedNode(nil))
t.Run("bound to service account and a pod with an assigned nodeName", testPodWithAssignedNode(node))
t.Run("fails to bind to a Node if the feature gate is disabled", func(t *testing.T) {
// Disable node binding, emulating 1.32
featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParseMajorMinor("1.32"))
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenNodeBinding, false)
// Create ServiceAccount and Node objects
sa, del := createDeleteSvcAcct(t, cs, sa)
defer del()
node, delNode := createDeleteNode(t, cs, node)
defer delNode()
treq := &authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{
Audiences: []string{"api"},
BoundObjectRef: &authenticationv1.BoundObjectReference{
Kind: "Node",
APIVersion: "v1",
Name: node.Name,
UID: node.UID,
},
},
}
warningHandler.clear()
if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(tCtx, sa.Name, treq, metav1.CreateOptions{}); err == nil {
t.Fatalf("expected err creating token with featuregate disabled but got: %#v", resp)
} else if err.Error() != "cannot bind token to a Node object as the \"ServiceAccountTokenNodeBinding\" feature-gate is disabled" {
t.Fatalf("expected error due to feature gate being disabled, but got: %s", err.Error())
}
warningHandler.assertEqual(t, nil)
})
t.Run("bound to service account and node", func(t *testing.T) {
treq := &authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{