diff options
Diffstat (limited to 'vendor/k8s.io/kubernetes/pkg/serviceaccount')
-rw-r--r-- | vendor/k8s.io/kubernetes/pkg/serviceaccount/claims.go | 186 | ||||
-rw-r--r-- | vendor/k8s.io/kubernetes/pkg/serviceaccount/jwt.go | 354 | ||||
-rw-r--r-- | vendor/k8s.io/kubernetes/pkg/serviceaccount/legacy.go | 135 | ||||
-rw-r--r-- | vendor/k8s.io/kubernetes/pkg/serviceaccount/util.go | 6 |
4 files changed, 450 insertions, 231 deletions
diff --git a/vendor/k8s.io/kubernetes/pkg/serviceaccount/claims.go b/vendor/k8s.io/kubernetes/pkg/serviceaccount/claims.go new file mode 100644 index 000000000..c3ed29c4c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/serviceaccount/claims.go @@ -0,0 +1,186 @@ +/* +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" + + "github.com/golang/glog" + apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount" + "k8s.io/kubernetes/pkg/apis/core" + + "gopkg.in/square/go-jose.v2/jwt" +) + +// 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(audiences []string, getter ServiceAccountTokenGetter) Validator { + return &validator{ + auds: audiences, + getter: getter, + } +} + +type validator struct { + auds []string + getter ServiceAccountTokenGetter +} + +var _ = Validator(&validator{}) + +func (v *validator) Validate(_ string, public *jwt.Claims, privateObj interface{}) (string, string, string, error) { + private, ok := privateObj.(*privateClaims) + if !ok { + glog.Errorf("jwt validator expected private claim of type *privateClaims but got: %T", privateObj) + return "", "", "", errors.New("Token could not be validated.") + } + err := public.Validate(jwt.Expected{ + Time: now(), + }) + switch { + case err == nil: + case err == jwt.ErrExpired: + return "", "", "", errors.New("Token has expired.") + default: + glog.Errorf("unexpected validation error: %T", err) + return "", "", "", errors.New("Token could not be validated.") + } + + var audValid bool + + for _, aud := range v.auds { + audValid = public.Audience.Contains(aud) + if audValid { + break + } + } + + if !audValid { + return "", "", "", errors.New("Token is invalid for this audience.") + } + + 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 { + glog.V(4).Infof("Could not retrieve service account %s/%s: %v", namespace, saref.Name, err) + return "", "", "", err + } + if serviceAccount.DeletionTimestamp != nil { + glog.V(4).Infof("Service account has been deleted %s/%s", namespace, saref.Name) + return "", "", "", fmt.Errorf("ServiceAccount %s/%s has been deleted", namespace, saref.Name) + } + if string(serviceAccount.UID) != saref.UID { + glog.V(4).Infof("Service account UID no longer matches %s/%s: %q != %q", namespace, saref.Name, string(serviceAccount.UID), saref.UID) + return "", "", "", 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 { + glog.V(4).Infof("Could not retrieve bound secret %s/%s for service account %s/%s: %v", namespace, secref.Name, namespace, saref.Name, err) + return "", "", "", errors.New("Token has been invalidated") + } + if secret.DeletionTimestamp != nil { + glog.V(4).Infof("Bound secret is deleted and awaiting removal: %s/%s for service account %s/%s", namespace, secref.Name, namespace, saref.Name) + return "", "", "", errors.New("Token has been invalidated") + } + if string(secref.UID) != secref.UID { + glog.V(4).Infof("Secret UID no longer matches %s/%s: %q != %q", namespace, secref.Name, string(serviceAccount.UID), secref.UID) + return "", "", "", fmt.Errorf("Secret UID (%s) does not match claim (%s)", secret.UID, secref.UID) + } + } + + 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 { + glog.V(4).Infof("Could not retrieve bound secret %s/%s for service account %s/%s: %v", namespace, podref.Name, namespace, saref.Name, err) + return "", "", "", errors.New("Token has been invalidated") + } + if pod.DeletionTimestamp != nil { + glog.V(4).Infof("Bound pod is deleted and awaiting removal: %s/%s for service account %s/%s", namespace, podref.Name, namespace, saref.Name) + return "", "", "", errors.New("Token has been invalidated") + } + if string(podref.UID) != podref.UID { + glog.V(4).Infof("Pod UID no longer matches %s/%s: %q != %q", namespace, podref.Name, string(serviceAccount.UID), podref.UID) + return "", "", "", fmt.Errorf("Pod UID (%s) does not match claim (%s)", pod.UID, podref.UID) + } + } + + return private.Kubernetes.Namespace, private.Kubernetes.Svcacct.Name, private.Kubernetes.Svcacct.UID, nil +} + +func (v *validator) NewPrivateClaims() interface{} { + return &privateClaims{} +} diff --git a/vendor/k8s.io/kubernetes/pkg/serviceaccount/jwt.go b/vendor/k8s.io/kubernetes/pkg/serviceaccount/jwt.go index 38c063253..01c369a31 100644 --- a/vendor/k8s.io/kubernetes/pkg/serviceaccount/jwt.go +++ b/vendor/k8s.io/kubernetes/pkg/serviceaccount/jwt.go @@ -17,141 +17,69 @@ limitations under the License. package serviceaccount import ( - "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" - "encoding/pem" + "encoding/base64" + "encoding/json" "errors" "fmt" - "io/ioutil" + "strings" + "k8s.io/api/core/v1" + utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apiserver/pkg/authentication/authenticator" - apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount" "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/client-go/util/cert" - "k8s.io/kubernetes/pkg/api/v1" - jwt "github.com/dgrijalva/jwt-go" - "github.com/golang/glog" -) - -const ( - Issuer = "kubernetes/serviceaccount" - - SubjectClaim = "sub" - IssuerClaim = "iss" - ServiceAccountNameClaim = "kubernetes.io/serviceaccount/service-account.name" - ServiceAccountUIDClaim = "kubernetes.io/serviceaccount/service-account.uid" - SecretNameClaim = "kubernetes.io/serviceaccount/secret.name" - NamespaceClaim = "kubernetes.io/serviceaccount/namespace" + jose "gopkg.in/square/go-jose.v2" + "gopkg.in/square/go-jose.v2/jwt" ) // ServiceAccountTokenGetter defines functions to retrieve a named service account and secret type ServiceAccountTokenGetter interface { GetServiceAccount(namespace, name string) (*v1.ServiceAccount, error) + GetPod(namespace, name string) (*v1.Pod, error) GetSecret(namespace, name string) (*v1.Secret, error) } type TokenGenerator interface { - // GenerateToken generates a token which will identify the given ServiceAccount. - // The returned token will be stored in the given (and yet-unpersisted) Secret. - GenerateToken(serviceAccount v1.ServiceAccount, secret v1.Secret) (string, error) -} - -// ReadPrivateKey is a helper function for reading a private key from a PEM-encoded file -func ReadPrivateKey(file string) (interface{}, error) { - data, err := ioutil.ReadFile(file) - if err != nil { - return nil, err - } - key, err := cert.ParsePrivateKeyPEM(data) - if err != nil { - return nil, fmt.Errorf("error reading private key file %s: %v", file, err) - } - return key, nil -} - -// ReadPublicKeys is a helper function for reading an array of rsa.PublicKey or ecdsa.PublicKey from a PEM-encoded file. -// Reads public keys from both public and private key files. -func ReadPublicKeys(file string) ([]interface{}, error) { - data, err := ioutil.ReadFile(file) - if err != nil { - return nil, err - } - keys, err := ReadPublicKeysFromPEM(data) - if err != nil { - return nil, fmt.Errorf("error reading public key file %s: %v", file, err) - } - return keys, nil -} - -// ReadPublicKeysFromPEM is a helper function for reading an array of rsa.PublicKey or ecdsa.PublicKey from a PEM-encoded byte array. -// Reads public keys from both public and private key files. -func ReadPublicKeysFromPEM(data []byte) ([]interface{}, error) { - var block *pem.Block - keys := []interface{}{} - for { - // read the next block - block, data = pem.Decode(data) - if block == nil { - break - } - - // get PEM bytes for just this block - blockData := pem.EncodeToMemory(block) - if privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(blockData); err == nil { - keys = append(keys, &privateKey.PublicKey) - continue - } - if publicKey, err := jwt.ParseRSAPublicKeyFromPEM(blockData); err == nil { - keys = append(keys, publicKey) - continue - } - - if privateKey, err := jwt.ParseECPrivateKeyFromPEM(blockData); err == nil { - keys = append(keys, &privateKey.PublicKey) - continue - } - if publicKey, err := jwt.ParseECPublicKeyFromPEM(blockData); err == nil { - keys = append(keys, publicKey) - continue - } - - // tolerate non-key PEM blocks for backwards compatibility - // originally, only the first PEM block was parsed and expected to be a key block - } - - if len(keys) == 0 { - return nil, fmt.Errorf("data does not contain a valid RSA or ECDSA key") - } - return keys, nil + // GenerateToken generates a token which will identify the given + // ServiceAccount. privateClaims is an interface that will be + // serialized into the JWT payload JSON encoding at the root level of + // the payload object. Public claims take precedent over private + // claims i.e. if both claims and privateClaims have an "exp" field, + // the value in claims will be used. + GenerateToken(claims *jwt.Claims, privateClaims interface{}) (string, error) } // JWTTokenGenerator returns a TokenGenerator that generates signed JWT tokens, using the given privateKey. // privateKey is a PEM-encoded byte array of a private RSA key. // JWTTokenAuthenticator() -func JWTTokenGenerator(privateKey interface{}) TokenGenerator { - return &jwtTokenGenerator{privateKey} +func JWTTokenGenerator(iss string, privateKey interface{}) TokenGenerator { + return &jwtTokenGenerator{ + iss: iss, + privateKey: privateKey, + } } type jwtTokenGenerator struct { + iss string privateKey interface{} } -func (j *jwtTokenGenerator) GenerateToken(serviceAccount v1.ServiceAccount, secret v1.Secret) (string, error) { - var method jwt.SigningMethod +func (j *jwtTokenGenerator) GenerateToken(claims *jwt.Claims, privateClaims interface{}) (string, error) { + var alg jose.SignatureAlgorithm switch privateKey := j.privateKey.(type) { case *rsa.PrivateKey: - method = jwt.SigningMethodRS256 + alg = jose.RS256 case *ecdsa.PrivateKey: switch privateKey.Curve { case elliptic.P256(): - method = jwt.SigningMethodES256 + alg = jose.ES256 case elliptic.P384(): - method = jwt.SigningMethodES384 + alg = jose.ES384 case elliptic.P521(): - method = jwt.SigningMethodES512 + alg = jose.ES512 default: return "", fmt.Errorf("unknown private key curve, must be 256, 384, or 521") } @@ -159,156 +87,126 @@ func (j *jwtTokenGenerator) GenerateToken(serviceAccount v1.ServiceAccount, secr return "", fmt.Errorf("unknown private key type %T, must be *rsa.PrivateKey or *ecdsa.PrivateKey", j.privateKey) } - token := jwt.New(method) - - claims, _ := token.Claims.(jwt.MapClaims) - - // Identify the issuer - claims[IssuerClaim] = Issuer - - // Username - claims[SubjectClaim] = apiserverserviceaccount.MakeUsername(serviceAccount.Namespace, serviceAccount.Name) - - // Persist enough structured info for the authenticator to be able to look up the service account and secret - claims[NamespaceClaim] = serviceAccount.Namespace - claims[ServiceAccountNameClaim] = serviceAccount.Name - claims[ServiceAccountUIDClaim] = serviceAccount.UID - claims[SecretNameClaim] = secret.Name + signer, err := jose.NewSigner( + jose.SigningKey{ + Algorithm: alg, + Key: j.privateKey, + }, + nil, + ) + if err != nil { + return "", err + } - // Sign and get the complete encoded token as a string - return token.SignedString(j.privateKey) + // claims are applied in reverse precedence + return jwt.Signed(signer). + Claims(privateClaims). + Claims(claims). + Claims(&jwt.Claims{ + Issuer: j.iss, + }). + CompactSerialize() } // JWTTokenAuthenticator authenticates tokens as JWT tokens produced by JWTTokenGenerator // Token signatures are verified using each of the given public keys until one works (allowing key rotation) // If lookup is true, the service account and secret referenced as claims inside the token are retrieved and verified with the provided ServiceAccountTokenGetter -func JWTTokenAuthenticator(keys []interface{}, lookup bool, getter ServiceAccountTokenGetter) authenticator.Token { - return &jwtTokenAuthenticator{keys, lookup, getter} +func JWTTokenAuthenticator(iss string, keys []interface{}, validator Validator) authenticator.Token { + return &jwtTokenAuthenticator{ + iss: iss, + keys: keys, + validator: validator, + } } type jwtTokenAuthenticator struct { - keys []interface{} - lookup bool - getter ServiceAccountTokenGetter + iss string + keys []interface{} + validator Validator } -var errMismatchedSigningMethod = errors.New("invalid signing method") - -func (j *jwtTokenAuthenticator) AuthenticateToken(token string) (user.Info, bool, error) { - var validationError error - - for i, key := range j.keys { - // Attempt to verify with each key until we find one that works - parsedToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - switch token.Method.(type) { - case *jwt.SigningMethodRSA: - if _, ok := key.(*rsa.PublicKey); ok { - return key, nil - } - return nil, errMismatchedSigningMethod - case *jwt.SigningMethodECDSA: - if _, ok := key.(*ecdsa.PublicKey); ok { - return key, nil - } - return nil, errMismatchedSigningMethod - default: - return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) - } - }) - - if err != nil { - switch err := err.(type) { - case *jwt.ValidationError: - if (err.Errors & jwt.ValidationErrorMalformed) != 0 { - // Not a JWT, no point in continuing - return nil, false, nil - } - - if (err.Errors & jwt.ValidationErrorSignatureInvalid) != 0 { - // Signature error, perhaps one of the other keys will verify the signature - // If not, we want to return this error - glog.V(4).Infof("Signature error (key %d): %v", i, err) - validationError = err - continue - } - - // This key doesn't apply to the given signature type - // Perhaps one of the other keys will verify the signature - // If not, we want to return this error - if err.Inner == errMismatchedSigningMethod { - glog.V(4).Infof("Mismatched key type (key %d): %v", i, err) - validationError = err - continue - } - } +// Validator is called by the JWT token authentictaor to apply domain specific +// validation to a token and extract user information. +type Validator interface { + // Validate validates a token and returns user information or an error. + // Validator can assume that the issuer and signature of a token are already + // verified when this function is called. + Validate(tokenData string, public *jwt.Claims, private interface{}) (namespace, name, uid string, err error) + // NewPrivateClaims returns a struct that the authenticator should + // deserialize the JWT payload into. The authenticator may then pass this + // struct back to the Validator as the 'private' argument to a Validate() + // call. This struct should contain fields for any private claims that the + // Validator requires to validate the JWT. + NewPrivateClaims() interface{} +} - // Other errors should just return as errors - return nil, false, err - } +var errMismatchedSigningMethod = errors.New("invalid signing method") - // If we get here, we have a token with a recognized signature +func (j *jwtTokenAuthenticator) AuthenticateToken(tokenData string) (user.Info, bool, error) { + if !j.hasCorrectIssuer(tokenData) { + return nil, false, nil + } - claims, _ := parsedToken.Claims.(jwt.MapClaims) + tok, err := jwt.ParseSigned(tokenData) + if err != nil { + return nil, false, nil + } - // Make sure we issued the token - iss, _ := claims[IssuerClaim].(string) - if iss != Issuer { - return nil, false, nil - } + public := &jwt.Claims{} + private := j.validator.NewPrivateClaims() - // Make sure the claims we need exist - sub, _ := claims[SubjectClaim].(string) - if len(sub) == 0 { - return nil, false, errors.New("sub claim is missing") - } - namespace, _ := claims[NamespaceClaim].(string) - if len(namespace) == 0 { - return nil, false, errors.New("namespace claim is missing") - } - secretName, _ := claims[SecretNameClaim].(string) - if len(namespace) == 0 { - return nil, false, errors.New("secretName claim is missing") - } - serviceAccountName, _ := claims[ServiceAccountNameClaim].(string) - if len(serviceAccountName) == 0 { - return nil, false, errors.New("serviceAccountName claim is missing") - } - serviceAccountUID, _ := claims[ServiceAccountUIDClaim].(string) - if len(serviceAccountUID) == 0 { - return nil, false, errors.New("serviceAccountUID claim is missing") + var ( + found bool + errlist []error + ) + for _, key := range j.keys { + if err := tok.Claims(key, public, private); err != nil { + errlist = append(errlist, err) + continue } + found = true + break + } - subjectNamespace, subjectName, err := apiserverserviceaccount.SplitUsername(sub) - if err != nil || subjectNamespace != namespace || subjectName != serviceAccountName { - return nil, false, errors.New("sub claim is invalid") - } + if !found { + return nil, false, utilerrors.NewAggregate(errlist) + } - if j.lookup { - // Make sure token hasn't been invalidated by deletion of the secret - secret, err := j.getter.GetSecret(namespace, secretName) - if err != nil { - glog.V(4).Infof("Could not retrieve token %s/%s for service account %s/%s: %v", namespace, secretName, namespace, serviceAccountName, err) - return nil, false, errors.New("Token has been invalidated") - } - if bytes.Compare(secret.Data[v1.ServiceAccountTokenKey], []byte(token)) != 0 { - glog.V(4).Infof("Token contents no longer matches %s/%s for service account %s/%s", namespace, secretName, namespace, serviceAccountName) - return nil, false, errors.New("Token does not match server's copy") - } + // If we get here, we have a token with a recognized signature and + // issuer string. + ns, name, uid, err := j.validator.Validate(tokenData, public, private) + if err != nil { + return nil, false, err + } - // Make sure service account still exists (name and UID) - serviceAccount, err := j.getter.GetServiceAccount(namespace, serviceAccountName) - if err != nil { - glog.V(4).Infof("Could not retrieve service account %s/%s: %v", namespace, serviceAccountName, err) - return nil, false, err - } - if string(serviceAccount.UID) != serviceAccountUID { - glog.V(4).Infof("Service account UID no longer matches %s/%s: %q != %q", namespace, serviceAccountName, string(serviceAccount.UID), serviceAccountUID) - return nil, false, fmt.Errorf("ServiceAccount UID (%s) does not match claim (%s)", serviceAccount.UID, serviceAccountUID) - } - } + return UserInfo(ns, name, uid), true, nil +} - return UserInfo(namespace, serviceAccountName, serviceAccountUID), true, nil +// hasCorrectIssuer returns true if tokenData is a valid JWT in compact +// serialization format and the "iss" claim matches the iss field of this token +// authenticator, and otherwise returns false. +// +// Note: go-jose currently does not allow access to unverified JWS payloads. +// See https://github.com/square/go-jose/issues/169 +func (j *jwtTokenAuthenticator) hasCorrectIssuer(tokenData string) bool { + parts := strings.Split(tokenData, ".") + if len(parts) != 3 { + return false + } + payload, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return false + } + claims := struct { + // WARNING: this JWT is not verified. Do not trust these claims. + Issuer string `json:"iss"` + }{} + if err := json.Unmarshal(payload, &claims); err != nil { + return false + } + if claims.Issuer != j.iss { + return false } + return true - return nil, false, validationError } diff --git a/vendor/k8s.io/kubernetes/pkg/serviceaccount/legacy.go b/vendor/k8s.io/kubernetes/pkg/serviceaccount/legacy.go new file mode 100644 index 000000000..5055db7cb --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/serviceaccount/legacy.go @@ -0,0 +1,135 @@ +/* +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 ( + "bytes" + "errors" + "fmt" + + "k8s.io/api/core/v1" + apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount" + + "github.com/golang/glog" + "gopkg.in/square/go-jose.v2/jwt" +) + +func LegacyClaims(serviceAccount v1.ServiceAccount, secret v1.Secret) (*jwt.Claims, interface{}) { + return &jwt.Claims{ + Subject: apiserverserviceaccount.MakeUsername(serviceAccount.Namespace, serviceAccount.Name), + }, &legacyPrivateClaims{ + Namespace: serviceAccount.Namespace, + ServiceAccountName: serviceAccount.Name, + ServiceAccountUID: string(serviceAccount.UID), + SecretName: secret.Name, + } +} + +const LegacyIssuer = "kubernetes/serviceaccount" + +type legacyPrivateClaims struct { + ServiceAccountName string `json:"kubernetes.io/serviceaccount/service-account.name"` + ServiceAccountUID string `json:"kubernetes.io/serviceaccount/service-account.uid"` + SecretName string `json:"kubernetes.io/serviceaccount/secret.name"` + Namespace string `json:"kubernetes.io/serviceaccount/namespace"` +} + +func NewLegacyValidator(lookup bool, getter ServiceAccountTokenGetter) Validator { + return &legacyValidator{ + lookup: lookup, + getter: getter, + } +} + +type legacyValidator struct { + lookup bool + getter ServiceAccountTokenGetter +} + +var _ = Validator(&legacyValidator{}) + +func (v *legacyValidator) Validate(tokenData string, public *jwt.Claims, privateObj interface{}) (string, string, string, error) { + private, ok := privateObj.(*legacyPrivateClaims) + if !ok { + glog.Errorf("jwt validator expected private claim of type *legacyPrivateClaims but got: %T", privateObj) + return "", "", "", errors.New("Token could not be validated.") + } + + // Make sure the claims we need exist + if len(public.Subject) == 0 { + return "", "", "", errors.New("sub claim is missing") + } + namespace := private.Namespace + if len(namespace) == 0 { + return "", "", "", errors.New("namespace claim is missing") + } + secretName := private.SecretName + if len(secretName) == 0 { + return "", "", "", errors.New("secretName claim is missing") + } + serviceAccountName := private.ServiceAccountName + if len(serviceAccountName) == 0 { + return "", "", "", errors.New("serviceAccountName claim is missing") + } + serviceAccountUID := private.ServiceAccountUID + if len(serviceAccountUID) == 0 { + return "", "", "", errors.New("serviceAccountUID claim is missing") + } + + subjectNamespace, subjectName, err := apiserverserviceaccount.SplitUsername(public.Subject) + if err != nil || subjectNamespace != namespace || subjectName != serviceAccountName { + return "", "", "", errors.New("sub claim is invalid") + } + + if v.lookup { + // Make sure token hasn't been invalidated by deletion of the secret + secret, err := v.getter.GetSecret(namespace, secretName) + if err != nil { + glog.V(4).Infof("Could not retrieve token %s/%s for service account %s/%s: %v", namespace, secretName, namespace, serviceAccountName, err) + return "", "", "", errors.New("Token has been invalidated") + } + if secret.DeletionTimestamp != nil { + glog.V(4).Infof("Token is deleted and awaiting removal: %s/%s for service account %s/%s", namespace, secretName, namespace, serviceAccountName) + return "", "", "", errors.New("Token has been invalidated") + } + if bytes.Compare(secret.Data[v1.ServiceAccountTokenKey], []byte(tokenData)) != 0 { + glog.V(4).Infof("Token contents no longer matches %s/%s for service account %s/%s", namespace, secretName, namespace, serviceAccountName) + return "", "", "", errors.New("Token does not match server's copy") + } + + // Make sure service account still exists (name and UID) + serviceAccount, err := v.getter.GetServiceAccount(namespace, serviceAccountName) + if err != nil { + glog.V(4).Infof("Could not retrieve service account %s/%s: %v", namespace, serviceAccountName, err) + return "", "", "", err + } + if serviceAccount.DeletionTimestamp != nil { + glog.V(4).Infof("Service account has been deleted %s/%s", namespace, serviceAccountName) + return "", "", "", fmt.Errorf("ServiceAccount %s/%s has been deleted", namespace, serviceAccountName) + } + if string(serviceAccount.UID) != serviceAccountUID { + glog.V(4).Infof("Service account UID no longer matches %s/%s: %q != %q", namespace, serviceAccountName, string(serviceAccount.UID), serviceAccountUID) + return "", "", "", fmt.Errorf("ServiceAccount UID (%s) does not match claim (%s)", serviceAccount.UID, serviceAccountUID) + } + } + + return private.Namespace, private.ServiceAccountName, private.ServiceAccountUID, nil +} + +func (v *legacyValidator) NewPrivateClaims() interface{} { + return &legacyPrivateClaims{} +} diff --git a/vendor/k8s.io/kubernetes/pkg/serviceaccount/util.go b/vendor/k8s.io/kubernetes/pkg/serviceaccount/util.go index 1fd5bd899..0503c1513 100644 --- a/vendor/k8s.io/kubernetes/pkg/serviceaccount/util.go +++ b/vendor/k8s.io/kubernetes/pkg/serviceaccount/util.go @@ -17,10 +17,10 @@ limitations under the License. package serviceaccount import ( + "k8s.io/api/core/v1" apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount" "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/v1" + api "k8s.io/kubernetes/pkg/apis/core" ) // UserInfo returns a user.Info interface for the given namespace, service account name and UID @@ -28,7 +28,7 @@ func UserInfo(namespace, name, uid string) user.Info { return &user.DefaultInfo{ Name: apiserverserviceaccount.MakeUsername(namespace, name), UID: uid, - Groups: apiserverserviceaccount.MakeGroupNames(namespace, name), + Groups: apiserverserviceaccount.MakeGroupNames(namespace), } } |