summaryrefslogtreecommitdiff
path: root/vendor/k8s.io/kubernetes/pkg/serviceaccount
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/k8s.io/kubernetes/pkg/serviceaccount')
-rw-r--r--vendor/k8s.io/kubernetes/pkg/serviceaccount/claims.go186
-rw-r--r--vendor/k8s.io/kubernetes/pkg/serviceaccount/jwt.go354
-rw-r--r--vendor/k8s.io/kubernetes/pkg/serviceaccount/legacy.go135
-rw-r--r--vendor/k8s.io/kubernetes/pkg/serviceaccount/util.go6
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),
}
}