123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- /*
- Copyright 2014 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 (
- "context"
- "crypto/ecdsa"
- "crypto/elliptic"
- "crypto/rsa"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "strings"
- jose "gopkg.in/square/go-jose.v2"
- "gopkg.in/square/go-jose.v2/jwt"
- "k8s.io/api/core/v1"
- utilerrors "k8s.io/apimachinery/pkg/util/errors"
- "k8s.io/apiserver/pkg/authentication/authenticator"
- )
- // 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. 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(iss string, privateKey interface{}) (TokenGenerator, error) {
- var alg jose.SignatureAlgorithm
- switch pk := privateKey.(type) {
- case *rsa.PrivateKey:
- alg = jose.RS256
- case *ecdsa.PrivateKey:
- switch pk.Curve {
- case elliptic.P256():
- alg = jose.ES256
- case elliptic.P384():
- alg = jose.ES384
- case elliptic.P521():
- alg = jose.ES512
- default:
- return nil, fmt.Errorf("unknown private key curve, must be 256, 384, or 521")
- }
- case jose.OpaqueSigner:
- alg = jose.SignatureAlgorithm(pk.Public().Algorithm)
- default:
- return nil, fmt.Errorf("unknown private key type %T, must be *rsa.PrivateKey, *ecdsa.PrivateKey, or jose.OpaqueSigner", privateKey)
- }
- signer, err := jose.NewSigner(
- jose.SigningKey{
- Algorithm: alg,
- Key: privateKey,
- },
- nil,
- )
- if err != nil {
- return nil, err
- }
- return &jwtTokenGenerator{
- iss: iss,
- signer: signer,
- }, nil
- }
- type jwtTokenGenerator struct {
- iss string
- signer jose.Signer
- }
- func (j *jwtTokenGenerator) GenerateToken(claims *jwt.Claims, privateClaims interface{}) (string, error) {
- // claims are applied in reverse precedence
- return jwt.Signed(j.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(iss string, keys []interface{}, implicitAuds authenticator.Audiences, validator Validator) authenticator.Token {
- return &jwtTokenAuthenticator{
- iss: iss,
- keys: keys,
- implicitAuds: implicitAuds,
- validator: validator,
- }
- }
- type jwtTokenAuthenticator struct {
- iss string
- keys []interface{}
- validator Validator
- implicitAuds authenticator.Audiences
- }
- // Validator is called by the JWT token authenticator 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{}) (*ServiceAccountInfo, 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{}
- }
- func (j *jwtTokenAuthenticator) AuthenticateToken(ctx context.Context, tokenData string) (*authenticator.Response, bool, error) {
- if !j.hasCorrectIssuer(tokenData) {
- return nil, false, nil
- }
- tok, err := jwt.ParseSigned(tokenData)
- if err != nil {
- return nil, false, nil
- }
- public := &jwt.Claims{}
- private := j.validator.NewPrivateClaims()
- 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
- }
- if !found {
- return nil, false, utilerrors.NewAggregate(errlist)
- }
- tokenAudiences := authenticator.Audiences(public.Audience)
- if len(tokenAudiences) == 0 {
- // only apiserver audiences are allowed for legacy tokens
- tokenAudiences = j.implicitAuds
- }
- requestedAudiences, ok := authenticator.AudiencesFrom(ctx)
- if !ok {
- // default to apiserver audiences
- requestedAudiences = j.implicitAuds
- }
- auds := authenticator.Audiences(tokenAudiences).Intersect(requestedAudiences)
- if len(auds) == 0 && len(j.implicitAuds) != 0 {
- return nil, false, fmt.Errorf("token audiences %q is invalid for the target audiences %q", tokenAudiences, requestedAudiences)
- }
- // If we get here, we have a token with a recognized signature and
- // issuer string.
- sa, err := j.validator.Validate(tokenData, public, private)
- if err != nil {
- return nil, false, err
- }
- return &authenticator.Response{
- User: sa.UserInfo(),
- Audiences: auds,
- }, 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
- }
|