mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 04:33:26 +00:00
Merge pull request #89583 from liggitt/bound-token-grace-period
Consider future deletionTimestamps when validating bound tokens
This commit is contained in:
commit
79bf624746
@ -57,7 +57,9 @@ go_test(
|
|||||||
"//pkg/controller/serviceaccount:go_default_library",
|
"//pkg/controller/serviceaccount:go_default_library",
|
||||||
"//pkg/routes:go_default_library",
|
"//pkg/routes:go_default_library",
|
||||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||||
|
@ -98,8 +98,9 @@ func (v *validator) Validate(_ string, public *jwt.Claims, privateObj interface{
|
|||||||
klog.Errorf("jwt validator expected private claim of type *privateClaims but got: %T", privateObj)
|
klog.Errorf("jwt validator expected private claim of type *privateClaims but got: %T", privateObj)
|
||||||
return nil, errors.New("Token could not be validated.")
|
return nil, errors.New("Token could not be validated.")
|
||||||
}
|
}
|
||||||
|
nowTime := now()
|
||||||
err := public.Validate(jwt.Expected{
|
err := public.Validate(jwt.Expected{
|
||||||
Time: now(),
|
Time: nowTime,
|
||||||
})
|
})
|
||||||
switch {
|
switch {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
@ -110,6 +111,8 @@ func (v *validator) Validate(_ string, public *jwt.Claims, privateObj interface{
|
|||||||
return nil, errors.New("Token could not be validated.")
|
return nil, errors.New("Token could not be validated.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// consider things deleted prior to now()-leeway to be invalid
|
||||||
|
invalidIfDeletedBefore := nowTime.Add(-jwt.DefaultLeeway)
|
||||||
namespace := private.Kubernetes.Namespace
|
namespace := private.Kubernetes.Namespace
|
||||||
saref := private.Kubernetes.Svcacct
|
saref := private.Kubernetes.Svcacct
|
||||||
podref := private.Kubernetes.Pod
|
podref := private.Kubernetes.Pod
|
||||||
@ -120,7 +123,7 @@ func (v *validator) Validate(_ string, public *jwt.Claims, privateObj interface{
|
|||||||
klog.V(4).Infof("Could not retrieve service account %s/%s: %v", namespace, saref.Name, err)
|
klog.V(4).Infof("Could not retrieve service account %s/%s: %v", namespace, saref.Name, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if serviceAccount.DeletionTimestamp != nil {
|
if serviceAccount.DeletionTimestamp != nil && serviceAccount.DeletionTimestamp.Time.Before(invalidIfDeletedBefore) {
|
||||||
klog.V(4).Infof("Service account has been deleted %s/%s", namespace, saref.Name)
|
klog.V(4).Infof("Service account has been deleted %s/%s", namespace, saref.Name)
|
||||||
return nil, fmt.Errorf("ServiceAccount %s/%s has been deleted", namespace, saref.Name)
|
return nil, fmt.Errorf("ServiceAccount %s/%s has been deleted", namespace, saref.Name)
|
||||||
}
|
}
|
||||||
@ -136,7 +139,7 @@ func (v *validator) Validate(_ string, public *jwt.Claims, privateObj interface{
|
|||||||
klog.V(4).Infof("Could not retrieve bound secret %s/%s for service account %s/%s: %v", namespace, secref.Name, namespace, saref.Name, err)
|
klog.V(4).Infof("Could not retrieve bound secret %s/%s for service account %s/%s: %v", namespace, secref.Name, namespace, saref.Name, err)
|
||||||
return nil, errors.New("Token has been invalidated")
|
return nil, errors.New("Token has been invalidated")
|
||||||
}
|
}
|
||||||
if secret.DeletionTimestamp != nil {
|
if secret.DeletionTimestamp != nil && secret.DeletionTimestamp.Time.Before(invalidIfDeletedBefore) {
|
||||||
klog.V(4).Infof("Bound secret is deleted and awaiting removal: %s/%s for service account %s/%s", namespace, secref.Name, namespace, saref.Name)
|
klog.V(4).Infof("Bound secret is deleted and awaiting removal: %s/%s for service account %s/%s", namespace, secref.Name, namespace, saref.Name)
|
||||||
return nil, errors.New("Token has been invalidated")
|
return nil, errors.New("Token has been invalidated")
|
||||||
}
|
}
|
||||||
@ -154,7 +157,7 @@ func (v *validator) Validate(_ string, public *jwt.Claims, privateObj interface{
|
|||||||
klog.V(4).Infof("Could not retrieve bound pod %s/%s for service account %s/%s: %v", namespace, podref.Name, namespace, saref.Name, err)
|
klog.V(4).Infof("Could not retrieve bound pod %s/%s for service account %s/%s: %v", namespace, podref.Name, namespace, saref.Name, err)
|
||||||
return nil, errors.New("Token has been invalidated")
|
return nil, errors.New("Token has been invalidated")
|
||||||
}
|
}
|
||||||
if pod.DeletionTimestamp != nil {
|
if pod.DeletionTimestamp != nil && pod.DeletionTimestamp.Time.Before(invalidIfDeletedBefore) {
|
||||||
klog.V(4).Infof("Bound pod is deleted and awaiting removal: %s/%s for service account %s/%s", namespace, podref.Name, namespace, saref.Name)
|
klog.V(4).Infof("Bound pod is deleted and awaiting removal: %s/%s for service account %s/%s", namespace, podref.Name, namespace, saref.Name)
|
||||||
return nil, errors.New("Token has been invalidated")
|
return nil, errors.New("Token has been invalidated")
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,10 @@ import (
|
|||||||
|
|
||||||
"gopkg.in/square/go-jose.v2/jwt"
|
"gopkg.in/square/go-jose.v2/jwt"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/kubernetes/pkg/apis/core"
|
"k8s.io/kubernetes/pkg/apis/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -182,3 +185,166 @@ func TestClaims(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type deletionTestCase struct {
|
||||||
|
name string
|
||||||
|
time *metav1.Time
|
||||||
|
expectErr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type claimTestCase struct {
|
||||||
|
name string
|
||||||
|
getter ServiceAccountTokenGetter
|
||||||
|
private *privateClaims
|
||||||
|
expectErr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidatePrivateClaims(t *testing.T) {
|
||||||
|
var (
|
||||||
|
nowUnix = int64(1514764800)
|
||||||
|
|
||||||
|
serviceAccount = &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: "saname", Namespace: "ns", UID: "sauid"}}
|
||||||
|
secret = &v1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "secretname", Namespace: "ns", UID: "secretuid"}}
|
||||||
|
pod = &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "podname", Namespace: "ns", UID: "poduid"}}
|
||||||
|
)
|
||||||
|
|
||||||
|
deletionTestCases := []deletionTestCase{
|
||||||
|
{
|
||||||
|
name: "valid",
|
||||||
|
time: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deleted now",
|
||||||
|
time: &metav1.Time{Time: time.Unix(nowUnix, 0)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deleted near past",
|
||||||
|
time: &metav1.Time{Time: time.Unix(nowUnix-1, 0)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deleted near future",
|
||||||
|
time: &metav1.Time{Time: time.Unix(nowUnix+1, 0)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deleted now-leeway",
|
||||||
|
time: &metav1.Time{Time: time.Unix(nowUnix-60, 0)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deleted now-leeway-1",
|
||||||
|
time: &metav1.Time{Time: time.Unix(nowUnix-61, 0)},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testcases := []claimTestCase{
|
||||||
|
{
|
||||||
|
name: "missing serviceaccount",
|
||||||
|
getter: fakeGetter{nil, nil, nil},
|
||||||
|
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Namespace: "ns"}},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing secret",
|
||||||
|
getter: fakeGetter{serviceAccount, nil, nil},
|
||||||
|
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Secret: &ref{Name: "secretname", UID: "secretuid"}, Namespace: "ns"}},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing pod",
|
||||||
|
getter: fakeGetter{serviceAccount, nil, nil},
|
||||||
|
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Pod: &ref{Name: "podname", UID: "poduid"}, Namespace: "ns"}},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "different uid serviceaccount",
|
||||||
|
getter: fakeGetter{serviceAccount, nil, nil},
|
||||||
|
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauidold"}, Namespace: "ns"}},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "different uid secret",
|
||||||
|
getter: fakeGetter{serviceAccount, secret, nil},
|
||||||
|
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Secret: &ref{Name: "secretname", UID: "secretuidold"}, Namespace: "ns"}},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "different uid pod",
|
||||||
|
getter: fakeGetter{serviceAccount, nil, pod},
|
||||||
|
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Pod: &ref{Name: "podname", UID: "poduidold"}, Namespace: "ns"}},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, deletionTestCase := range deletionTestCases {
|
||||||
|
var (
|
||||||
|
deletedServiceAccount = serviceAccount.DeepCopy()
|
||||||
|
deletedPod = pod.DeepCopy()
|
||||||
|
deletedSecret = secret.DeepCopy()
|
||||||
|
)
|
||||||
|
deletedServiceAccount.DeletionTimestamp = deletionTestCase.time
|
||||||
|
deletedPod.DeletionTimestamp = deletionTestCase.time
|
||||||
|
deletedSecret.DeletionTimestamp = deletionTestCase.time
|
||||||
|
|
||||||
|
testcases = append(testcases,
|
||||||
|
claimTestCase{
|
||||||
|
name: deletionTestCase.name + " serviceaccount",
|
||||||
|
getter: fakeGetter{deletedServiceAccount, nil, nil},
|
||||||
|
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Namespace: "ns"}},
|
||||||
|
expectErr: deletionTestCase.expectErr,
|
||||||
|
},
|
||||||
|
claimTestCase{
|
||||||
|
name: deletionTestCase.name + " secret",
|
||||||
|
getter: fakeGetter{serviceAccount, deletedSecret, nil},
|
||||||
|
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Secret: &ref{Name: "secretname", UID: "secretuid"}, Namespace: "ns"}},
|
||||||
|
expectErr: deletionTestCase.expectErr,
|
||||||
|
},
|
||||||
|
claimTestCase{
|
||||||
|
name: deletionTestCase.name + " pod",
|
||||||
|
getter: fakeGetter{serviceAccount, nil, deletedPod},
|
||||||
|
private: &privateClaims{Kubernetes: kubernetes{Svcacct: ref{Name: "saname", UID: "sauid"}, Pod: &ref{Name: "podname", UID: "poduid"}, Namespace: "ns"}},
|
||||||
|
expectErr: deletionTestCase.expectErr,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
v := &validator{tc.getter}
|
||||||
|
_, err := v.Validate("", &jwt.Claims{Expiry: jwt.NumericDate(nowUnix)}, tc.private)
|
||||||
|
if err != nil && !tc.expectErr {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err == nil && tc.expectErr {
|
||||||
|
t.Fatal("expected error, got none")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeGetter struct {
|
||||||
|
serviceAccount *v1.ServiceAccount
|
||||||
|
secret *v1.Secret
|
||||||
|
pod *v1.Pod
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeGetter) GetServiceAccount(namespace, name string) (*v1.ServiceAccount, error) {
|
||||||
|
if f.serviceAccount == nil {
|
||||||
|
return nil, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "serviceaccounts"}, name)
|
||||||
|
}
|
||||||
|
return f.serviceAccount, nil
|
||||||
|
}
|
||||||
|
func (f fakeGetter) GetPod(namespace, name string) (*v1.Pod, error) {
|
||||||
|
if f.pod == nil {
|
||||||
|
return nil, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "pods"}, name)
|
||||||
|
}
|
||||||
|
return f.pod, nil
|
||||||
|
}
|
||||||
|
func (f fakeGetter) GetSecret(namespace, name string) (*v1.Secret, error) {
|
||||||
|
if f.secret == nil {
|
||||||
|
return nil, apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "secrets"}, name)
|
||||||
|
}
|
||||||
|
return f.secret, nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user