KEP-4193: Promote ServiceAccountTokenJTI, ServiceAccountTokenPodNodeInfo, ServiceAccountTokenNodeBindingValidation to stable

This commit is contained in:
Jordan Liggitt 2024-10-17 20:49:15 -04:00
parent 632ed16e00
commit 0771f601e1
No known key found for this signature in database
7 changed files with 61 additions and 57 deletions

View File

@ -639,6 +639,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
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}, // remove in 1.34
},
ServiceAccountTokenNodeBinding: {
@ -649,11 +650,13 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
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}, // remove in 1.34
},
ServiceAccountTokenPodNodeInfo: {
{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}, // remove in 1.34
},
ServiceTrafficDistribution: {

View File

@ -43,7 +43,6 @@ import (
"k8s.io/component-base/featuregate"
featuregatetesting "k8s.io/component-base/featuregate/testing"
openapicommon "k8s.io/kube-openapi/pkg/common"
kubefeatures "k8s.io/kubernetes/pkg/features"
kubeauthenticator "k8s.io/kubernetes/pkg/kubeapiserver/authenticator"
"k8s.io/kubernetes/pkg/serviceaccount"
"k8s.io/utils/pointer"
@ -237,12 +236,6 @@ func TestAuthenticationValidate(t *testing.T) {
},
expectErr: "authentication-config file and oidc-* flags are mutually exclusive",
},
{
name: "fails to validate if ServiceAccountTokenNodeBindingValidation is disabled and ServiceAccountTokenNodeBinding is enabled",
enabledFeatures: []featuregate.Feature{kubefeatures.ServiceAccountTokenNodeBinding},
disabledFeatures: []featuregate.Feature{kubefeatures.ServiceAccountTokenNodeBindingValidation},
expectErr: "the \"ServiceAccountTokenNodeBinding\" feature gate can only be enabled if the \"ServiceAccountTokenNodeBindingValidation\" feature gate is also enabled",
},
{
name: "test when authentication config file and anonymous-auth flags are set AnonymousAuthConfigurableEndpoints disabled",
disabledFeatures: []featuregate.Feature{features.AnonymousAuthConfigurableEndpoints},
@ -489,7 +482,6 @@ func TestBuiltInAuthenticationOptionsAddFlags(t *testing.T) {
}
func TestWithTokenGetterFunction(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, kubefeatures.ServiceAccountTokenNodeBindingValidation, false)
fakeClientset := fake.NewSimpleClientset()
versionedInformer := informers.NewSharedInformerFactory(fakeClientset, 0)
{

View File

@ -89,7 +89,7 @@ func TestClaims(t *testing.T) {
sc *jwt.Claims
pc *privateClaims
featureJTI, featurePodNodeInfo, featureNodeBinding bool
featureNodeBinding bool
}{
{
// pod and secret
@ -115,6 +115,7 @@ func TestClaims(t *testing.T) {
IssuedAt: jwt.NewNumericDate(time.Unix(1514764800, 0)),
NotBefore: jwt.NewNumericDate(time.Unix(1514764800, 0)),
Expiry: jwt.NewNumericDate(time.Unix(1514764800+100, 0)),
ID: "fixed",
},
pc: &privateClaims{
Kubernetes: kubernetes{
@ -138,6 +139,7 @@ func TestClaims(t *testing.T) {
IssuedAt: jwt.NewNumericDate(time.Unix(1514764800, 0)),
NotBefore: jwt.NewNumericDate(time.Unix(1514764800, 0)),
Expiry: jwt.NewNumericDate(time.Unix(1514764800+100, 0)),
ID: "fixed",
},
pc: &privateClaims{
Kubernetes: kubernetes{
@ -160,6 +162,7 @@ func TestClaims(t *testing.T) {
IssuedAt: jwt.NewNumericDate(time.Unix(1514764800, 0)),
NotBefore: jwt.NewNumericDate(time.Unix(1514764800, 0)),
Expiry: jwt.NewNumericDate(time.Unix(1514764800+100, 0)),
ID: "fixed",
},
pc: &privateClaims{
Kubernetes: kubernetes{
@ -182,6 +185,7 @@ func TestClaims(t *testing.T) {
IssuedAt: jwt.NewNumericDate(time.Unix(1514764800, 0)),
NotBefore: jwt.NewNumericDate(time.Unix(1514764800, 0)),
Expiry: jwt.NewNumericDate(time.Unix(1514764800+60*60*24, 0)),
ID: "fixed",
},
pc: &privateClaims{
Kubernetes: kubernetes{
@ -202,30 +206,6 @@ func TestClaims(t *testing.T) {
aud: nil,
err: "token bound to Node object requested, but \"ServiceAccountTokenNodeBinding\" feature gate is disabled",
},
{
// node & pod with feature gate disabled
sa: sa,
node: node,
pod: pod,
// really fast
exp: 0,
// nil audience
aud: nil,
sc: &jwt.Claims{
Subject: "system:serviceaccount:myns:mysvcacct",
IssuedAt: jwt.NewNumericDate(time.Unix(1514764800, 0)),
NotBefore: jwt.NewNumericDate(time.Unix(1514764800, 0)),
Expiry: jwt.NewNumericDate(time.Unix(1514764800, 0)),
},
pc: &privateClaims{
Kubernetes: kubernetes{
Namespace: "myns",
Pod: &ref{Name: "mypod", UID: "mypod-uid"},
Svcacct: ref{Name: "mysvcacct", UID: "mysvcacct-uid"},
},
},
},
{
// node alone
sa: sa,
@ -242,6 +222,7 @@ func TestClaims(t *testing.T) {
IssuedAt: jwt.NewNumericDate(time.Unix(1514764800, 0)),
NotBefore: jwt.NewNumericDate(time.Unix(1514764800, 0)),
Expiry: jwt.NewNumericDate(time.Unix(1514764800, 0)),
ID: "fixed",
},
pc: &privateClaims{
Kubernetes: kubernetes{
@ -256,8 +237,6 @@ func TestClaims(t *testing.T) {
sa: sa,
pod: pod,
node: node,
// enable embedding pod node info feature
featurePodNodeInfo: true,
// really fast
exp: 0,
// nil audience
@ -268,6 +247,7 @@ func TestClaims(t *testing.T) {
IssuedAt: jwt.NewNumericDate(time.Unix(1514764800, 0)),
NotBefore: jwt.NewNumericDate(time.Unix(1514764800, 0)),
Expiry: jwt.NewNumericDate(time.Unix(1514764800, 0)),
ID: "fixed",
},
pc: &privateClaims{
Kubernetes: kubernetes{
@ -294,8 +274,6 @@ func TestClaims(t *testing.T) {
{
// ensure JTI is set
sa: sa,
// enable setting JTI feature
featureJTI: true,
// really fast
exp: 0,
// nil audience
@ -342,9 +320,7 @@ func TestClaims(t *testing.T) {
}
// set feature flags for the duration of the test case
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenJTI, c.featureJTI)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenNodeBinding, c.featureNodeBinding)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenPodNodeInfo, c.featurePodNodeInfo)
sc, pc, err := Claims(c.sa, c.pod, c.sec, c.node, c.exp, c.warnafter, c.aud)
if err != nil && err.Error() != c.err {
@ -376,8 +352,6 @@ type claimTestCase struct {
expiry jwt.NumericDate
notBefore jwt.NumericDate
expectErr string
featureNodeBindingValidation bool
}
func TestValidatePrivateClaims(t *testing.T) {
@ -458,11 +432,10 @@ func TestValidatePrivateClaims(t *testing.T) {
expectErr: "service account token has been invalidated",
},
{
name: "missing node",
getter: fakeGetter{serviceAccount, nil, nil, nil},
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Node: &ref{Name: "nodename", UID: "nodeuid"}, Namespace: "ns"}},
expectErr: "service account token has been invalidated",
featureNodeBindingValidation: true,
name: "missing node",
getter: fakeGetter{serviceAccount, nil, nil, nil},
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Node: &ref{Name: "nodename", UID: "nodeuid"}, Namespace: "ns"}},
expectErr: "service account token has been invalidated",
},
{
name: "different uid serviceaccount",
@ -522,11 +495,10 @@ func TestValidatePrivateClaims(t *testing.T) {
expectErr: deletedErr,
},
claimTestCase{
name: deletionTestCase.name + " node",
getter: fakeGetter{serviceAccount, nil, nil, deletedNode},
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Node: &ref{Name: "nodename", UID: "nodeuid"}, Namespace: "ns"}},
expectErr: deletedErr,
featureNodeBindingValidation: true,
name: deletionTestCase.name + " node",
getter: fakeGetter{serviceAccount, nil, nil, deletedNode},
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Node: &ref{Name: "nodename", UID: "nodeuid"}, Namespace: "ns"}},
expectErr: deletedErr,
},
)
}
@ -539,8 +511,6 @@ func TestValidatePrivateClaims(t *testing.T) {
expiry = tc.expiry
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenNodeBindingValidation, tc.featureNodeBindingValidation)
_, err := v.Validate(context.Background(), "", &jwt.Claims{Expiry: &expiry, NotBefore: &tc.notBefore}, tc.private)
if len(tc.expectErr) > 0 {
if errStr := errString(err); tc.expectErr != errStr {

View File

@ -24,6 +24,7 @@ import (
g "github.com/onsi/ginkgo/v2"
o "github.com/onsi/gomega"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
authenticationv1 "k8s.io/api/authentication/v1"
v1 "k8s.io/api/core/v1"
@ -34,7 +35,6 @@ import (
"k8s.io/client-go/kubernetes"
cgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/test/e2e/framework"
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
imageutils "k8s.io/kubernetes/test/utils/image"
@ -52,7 +52,7 @@ var (
perNodeCheckValidatingAdmissionPolicyBinding string
)
var _ = SIGDescribe("ValidatingAdmissionPolicy", framework.WithFeatureGate(features.ServiceAccountTokenNodeBindingValidation), func() {
var _ = SIGDescribe("ValidatingAdmissionPolicy", func() {
defer g.GinkgoRecover()
f := framework.NewDefaultFramework("node-authn")
f.NamespacePodSecurityLevel = admissionapi.LevelRestricted

View File

@ -100,6 +100,12 @@ var _ = SIGDescribe("ServiceAccounts", func() {
framework.ExpectNoError(err)
framework.ExpectNoError(e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod))
// Read the running pod to get the current node name
pod, err = f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(ctx, pod.Name, metav1.GetOptions{})
framework.ExpectNoError(err)
node, err := f.ClientSet.CoreV1().Nodes().Get(ctx, pod.Spec.NodeName, metav1.GetOptions{})
framework.ExpectNoError(err)
tk := e2ekubectl.NewTestKubeconfig(framework.TestContext.CertDir, framework.TestContext.Host, framework.TestContext.KubeConfig, framework.TestContext.KubeContext, framework.TestContext.KubectlPath, f.Namespace.Name)
mountedToken, err := tk.ReadFileViaContainer(pod.Name, pod.Spec.Containers[0].Name, path.Join(serviceaccount.DefaultAPITokenMountPath, v1.ServiceAccountTokenKey))
framework.ExpectNoError(err)
@ -133,6 +139,29 @@ var _ = SIGDescribe("ServiceAccounts", func() {
if !groups.Has("system:serviceaccounts:" + f.Namespace.Name) {
framework.Failf("expected system:serviceaccounts:%s group, had %v", f.Namespace.Name, groups.List())
}
credentialID, ok := tokenReview.Status.User.Extra["authentication.kubernetes.io/credential-id"]
if !ok || len(credentialID) != 1 || !strings.HasPrefix(credentialID[0], "JTI=") {
framework.Failf("expected single authentication.kubernetes.io/credential-id extra info item starting with 'JTI=', got %v", credentialID)
}
podName, ok := tokenReview.Status.User.Extra["authentication.kubernetes.io/pod-name"]
if !ok || len(podName) != 1 || podName[0] != pod.Name {
framework.Failf("expected single authentication.kubernetes.io/pod-name extra info item matching %v, got %v", pod.Name, podName)
}
podUID, ok := tokenReview.Status.User.Extra["authentication.kubernetes.io/pod-uid"]
if !ok || len(podUID) != 1 || podUID[0] != string(pod.UID) {
framework.Failf("expected single authentication.kubernetes.io/pod-uid extra info item matching %v, got %v", pod.UID, podUID)
}
nodeName, ok := tokenReview.Status.User.Extra["authentication.kubernetes.io/node-name"]
if !ok || len(nodeName) != 1 || nodeName[0] != node.Name {
framework.Failf("expected single authentication.kubernetes.io/node-name extra info item matching %v, got %v", node.Name, nodeName)
}
nodeUID, ok := tokenReview.Status.User.Extra["authentication.kubernetes.io/node-uid"]
if !ok || len(nodeUID) != 1 || nodeUID[0] != string(node.UID) {
framework.Failf("expected single authentication.kubernetes.io/node-uid extra info item matching %v, got %v", node.UID, nodeUID)
}
})
/*

View File

@ -1036,6 +1036,10 @@
lockToDefault: false
preRelease: Beta
version: "1.30"
- default: true
lockToDefault: true
preRelease: GA
version: "1.32"
- name: ServiceAccountTokenNodeBinding
versionedSpecs:
- default: false
@ -1056,6 +1060,10 @@
lockToDefault: false
preRelease: Beta
version: "1.30"
- default: true
lockToDefault: true
preRelease: GA
version: "1.32"
- name: ServiceAccountTokenPodNodeInfo
versionedSpecs:
- default: false
@ -1066,6 +1074,10 @@
lockToDefault: false
preRelease: Beta
version: "1.30"
- default: true
lockToDefault: true
preRelease: GA
version: "1.32"
- name: ServiceTrafficDistribution
versionedSpecs:
- default: false

View File

@ -248,8 +248,6 @@ func TestServiceAccountTokenCreate(t *testing.T) {
})
t.Run("bound to service account and pod", func(t *testing.T) {
// Disable embedding pod's node info
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenPodNodeInfo, false)
treq := &authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{
Audiences: []string{"api"},