mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-10-28 13:45:38 +08:00
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:
8 files changed
+4
-158
No files matched your search
@@ -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)},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
Reference in new issue
Block a user