mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 20:53:33 +00:00
KEP-4193: Promote ServiceAccountTokenJTI, ServiceAccountTokenPodNodeInfo, ServiceAccountTokenNodeBindingValidation to stable
This commit is contained in:
parent
632ed16e00
commit
0771f601e1
@ -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: {
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
|
@ -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
|
||||
|
@ -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"},
|
||||
|
Loading…
Reference in New Issue
Block a user