mirror of https://github.com/k3s-io/k3s
181 lines
5.8 KiB
Go
181 lines
5.8 KiB
Go
|
/*
|
||
|
Copyright 2018 The Kubernetes Authors.
|
||
|
|
||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
you may not use this file except in compliance with the License.
|
||
|
You may obtain a copy of the License at
|
||
|
|
||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
||
|
Unless required by applicable law or agreed to in writing, software
|
||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
See the License for the specific language governing permissions and
|
||
|
limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package serviceaccount
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"time"
|
||
|
|
||
|
"gopkg.in/square/go-jose.v2/jwt"
|
||
|
"k8s.io/klog"
|
||
|
|
||
|
apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
|
||
|
"k8s.io/kubernetes/pkg/apis/core"
|
||
|
)
|
||
|
|
||
|
// time.Now stubbed out to allow testing
|
||
|
var now = time.Now
|
||
|
|
||
|
type privateClaims struct {
|
||
|
Kubernetes kubernetes `json:"kubernetes.io,omitempty"`
|
||
|
}
|
||
|
|
||
|
type kubernetes struct {
|
||
|
Namespace string `json:"namespace,omitempty"`
|
||
|
Svcacct ref `json:"serviceaccount,omitempty"`
|
||
|
Pod *ref `json:"pod,omitempty"`
|
||
|
Secret *ref `json:"secret,omitempty"`
|
||
|
}
|
||
|
|
||
|
type ref struct {
|
||
|
Name string `json:"name,omitempty"`
|
||
|
UID string `json:"uid,omitempty"`
|
||
|
}
|
||
|
|
||
|
func Claims(sa core.ServiceAccount, pod *core.Pod, secret *core.Secret, expirationSeconds int64, audience []string) (*jwt.Claims, interface{}) {
|
||
|
now := now()
|
||
|
sc := &jwt.Claims{
|
||
|
Subject: apiserverserviceaccount.MakeUsername(sa.Namespace, sa.Name),
|
||
|
Audience: jwt.Audience(audience),
|
||
|
IssuedAt: jwt.NewNumericDate(now),
|
||
|
NotBefore: jwt.NewNumericDate(now),
|
||
|
Expiry: jwt.NewNumericDate(now.Add(time.Duration(expirationSeconds) * time.Second)),
|
||
|
}
|
||
|
pc := &privateClaims{
|
||
|
Kubernetes: kubernetes{
|
||
|
Namespace: sa.Namespace,
|
||
|
Svcacct: ref{
|
||
|
Name: sa.Name,
|
||
|
UID: string(sa.UID),
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
switch {
|
||
|
case pod != nil:
|
||
|
pc.Kubernetes.Pod = &ref{
|
||
|
Name: pod.Name,
|
||
|
UID: string(pod.UID),
|
||
|
}
|
||
|
case secret != nil:
|
||
|
pc.Kubernetes.Secret = &ref{
|
||
|
Name: secret.Name,
|
||
|
UID: string(secret.UID),
|
||
|
}
|
||
|
}
|
||
|
return sc, pc
|
||
|
}
|
||
|
|
||
|
func NewValidator(getter ServiceAccountTokenGetter) Validator {
|
||
|
return &validator{
|
||
|
getter: getter,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type validator struct {
|
||
|
getter ServiceAccountTokenGetter
|
||
|
}
|
||
|
|
||
|
var _ = Validator(&validator{})
|
||
|
|
||
|
func (v *validator) Validate(_ string, public *jwt.Claims, privateObj interface{}) (*ServiceAccountInfo, error) {
|
||
|
private, ok := privateObj.(*privateClaims)
|
||
|
if !ok {
|
||
|
klog.Errorf("jwt validator expected private claim of type *privateClaims but got: %T", privateObj)
|
||
|
return nil, errors.New("Token could not be validated.")
|
||
|
}
|
||
|
err := public.Validate(jwt.Expected{
|
||
|
Time: now(),
|
||
|
})
|
||
|
switch {
|
||
|
case err == nil:
|
||
|
case err == jwt.ErrExpired:
|
||
|
return nil, errors.New("Token has expired.")
|
||
|
default:
|
||
|
klog.Errorf("unexpected validation error: %T", err)
|
||
|
return nil, errors.New("Token could not be validated.")
|
||
|
}
|
||
|
|
||
|
namespace := private.Kubernetes.Namespace
|
||
|
saref := private.Kubernetes.Svcacct
|
||
|
podref := private.Kubernetes.Pod
|
||
|
secref := private.Kubernetes.Secret
|
||
|
// Make sure service account still exists (name and UID)
|
||
|
serviceAccount, err := v.getter.GetServiceAccount(namespace, saref.Name)
|
||
|
if err != nil {
|
||
|
klog.V(4).Infof("Could not retrieve service account %s/%s: %v", namespace, saref.Name, err)
|
||
|
return nil, err
|
||
|
}
|
||
|
if serviceAccount.DeletionTimestamp != nil {
|
||
|
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)
|
||
|
}
|
||
|
if string(serviceAccount.UID) != saref.UID {
|
||
|
klog.V(4).Infof("Service account UID no longer matches %s/%s: %q != %q", namespace, saref.Name, string(serviceAccount.UID), saref.UID)
|
||
|
return nil, fmt.Errorf("ServiceAccount UID (%s) does not match claim (%s)", serviceAccount.UID, saref.UID)
|
||
|
}
|
||
|
|
||
|
if secref != nil {
|
||
|
// Make sure token hasn't been invalidated by deletion of the secret
|
||
|
secret, err := v.getter.GetSecret(namespace, secref.Name)
|
||
|
if err != nil {
|
||
|
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")
|
||
|
}
|
||
|
if secret.DeletionTimestamp != nil {
|
||
|
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")
|
||
|
}
|
||
|
if secref.UID != string(secret.UID) {
|
||
|
klog.V(4).Infof("Secret UID no longer matches %s/%s: %q != %q", namespace, secref.Name, string(secret.UID), secref.UID)
|
||
|
return nil, fmt.Errorf("Secret UID (%s) does not match claim (%s)", secret.UID, secref.UID)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var podName, podUID string
|
||
|
if podref != nil {
|
||
|
// Make sure token hasn't been invalidated by deletion of the pod
|
||
|
pod, err := v.getter.GetPod(namespace, podref.Name)
|
||
|
if err != nil {
|
||
|
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")
|
||
|
}
|
||
|
if pod.DeletionTimestamp != nil {
|
||
|
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")
|
||
|
}
|
||
|
if podref.UID != string(pod.UID) {
|
||
|
klog.V(4).Infof("Pod UID no longer matches %s/%s: %q != %q", namespace, podref.Name, string(pod.UID), podref.UID)
|
||
|
return nil, fmt.Errorf("Pod UID (%s) does not match claim (%s)", pod.UID, podref.UID)
|
||
|
}
|
||
|
podName = podref.Name
|
||
|
podUID = podref.UID
|
||
|
}
|
||
|
|
||
|
return &ServiceAccountInfo{
|
||
|
Namespace: private.Kubernetes.Namespace,
|
||
|
Name: private.Kubernetes.Svcacct.Name,
|
||
|
UID: private.Kubernetes.Svcacct.UID,
|
||
|
PodName: podName,
|
||
|
PodUID: podUID,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (v *validator) NewPrivateClaims() interface{} {
|
||
|
return &privateClaims{}
|
||
|
}
|