2019-01-12 04:58:27 +00:00
/ *
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"
2019-09-27 21:51:53 +00:00
"crypto"
2019-01-12 04:58:27 +00:00
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
2019-09-27 21:51:53 +00:00
"crypto/x509"
2019-01-12 04:58:27 +00:00
"encoding/base64"
"encoding/json"
"fmt"
"strings"
jose "gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
2019-09-27 21:51:53 +00:00
v1 "k8s.io/api/core/v1"
2019-01-12 04:58:27 +00:00
utilerrors "k8s.io/apimachinery/pkg/util/errors"
2020-08-10 17:43:49 +00:00
"k8s.io/apiserver/pkg/audit"
2019-01-12 04:58:27 +00:00
"k8s.io/apiserver/pkg/authentication/authenticator"
2020-12-01 01:06:26 +00:00
apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
2019-01-12 04:58:27 +00:00
)
// 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.
func JWTTokenGenerator ( iss string , privateKey interface { } ) ( TokenGenerator , error ) {
2019-09-27 21:51:53 +00:00
var signer jose . Signer
var err error
2019-01-12 04:58:27 +00:00
switch pk := privateKey . ( type ) {
case * rsa . PrivateKey :
2019-09-27 21:51:53 +00:00
signer , err = signerFromRSAPrivateKey ( pk )
if err != nil {
return nil , fmt . Errorf ( "could not generate signer for RSA keypair: %v" , err )
}
2019-01-12 04:58:27 +00:00
case * ecdsa . PrivateKey :
2019-09-27 21:51:53 +00:00
signer , err = signerFromECDSAPrivateKey ( pk )
if err != nil {
return nil , fmt . Errorf ( "could not generate signer for ECDSA keypair: %v" , err )
2019-01-12 04:58:27 +00:00
}
case jose . OpaqueSigner :
2019-09-27 21:51:53 +00:00
signer , err = signerFromOpaqueSigner ( pk )
if err != nil {
return nil , fmt . Errorf ( "could not generate signer for OpaqueSigner: %v" , err )
}
2019-01-12 04:58:27 +00:00
default :
return nil , fmt . Errorf ( "unknown private key type %T, must be *rsa.PrivateKey, *ecdsa.PrivateKey, or jose.OpaqueSigner" , privateKey )
}
2019-09-27 21:51:53 +00:00
return & jwtTokenGenerator {
iss : iss ,
signer : signer ,
} , nil
}
// keyIDFromPublicKey derives a key ID non-reversibly from a public key.
//
// The Key ID is field on a given on JWTs and JWKs that help relying parties
// pick the correct key for verification when the identity party advertises
// multiple keys.
//
// Making the derivation non-reversible makes it impossible for someone to
// accidentally obtain the real key from the key ID and use it for token
// validation.
func keyIDFromPublicKey ( publicKey interface { } ) ( string , error ) {
publicKeyDERBytes , err := x509 . MarshalPKIXPublicKey ( publicKey )
if err != nil {
return "" , fmt . Errorf ( "failed to serialize public key to DER format: %v" , err )
}
hasher := crypto . SHA256 . New ( )
hasher . Write ( publicKeyDERBytes )
publicKeyDERHash := hasher . Sum ( nil )
keyID := base64 . RawURLEncoding . EncodeToString ( publicKeyDERHash )
return keyID , nil
}
func signerFromRSAPrivateKey ( keyPair * rsa . PrivateKey ) ( jose . Signer , error ) {
keyID , err := keyIDFromPublicKey ( & keyPair . PublicKey )
if err != nil {
return nil , fmt . Errorf ( "failed to derive keyID: %v" , err )
}
2020-03-26 21:07:15 +00:00
// IMPORTANT: If this function is updated to support additional key sizes,
// algorithmForPublicKey in serviceaccount/openidmetadata.go must also be
// updated to support the same key sizes. Today we only support RS256.
2019-09-27 21:51:53 +00:00
// Wrap the RSA keypair in a JOSE JWK with the designated key ID.
privateJWK := & jose . JSONWebKey {
Algorithm : string ( jose . RS256 ) ,
Key : keyPair ,
KeyID : keyID ,
Use : "sig" ,
}
signer , err := jose . NewSigner (
jose . SigningKey {
Algorithm : jose . RS256 ,
Key : privateJWK ,
} ,
nil ,
)
if err != nil {
return nil , fmt . Errorf ( "failed to create signer: %v" , err )
}
return signer , nil
}
func signerFromECDSAPrivateKey ( keyPair * ecdsa . PrivateKey ) ( jose . Signer , error ) {
var alg jose . SignatureAlgorithm
switch keyPair . 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" )
}
keyID , err := keyIDFromPublicKey ( & keyPair . PublicKey )
if err != nil {
return nil , fmt . Errorf ( "failed to derive keyID: %v" , err )
}
// Wrap the ECDSA keypair in a JOSE JWK with the designated key ID.
privateJWK := & jose . JSONWebKey {
Algorithm : string ( alg ) ,
Key : keyPair ,
KeyID : keyID ,
Use : "sig" ,
}
2019-01-12 04:58:27 +00:00
signer , err := jose . NewSigner (
jose . SigningKey {
Algorithm : alg ,
2019-09-27 21:51:53 +00:00
Key : privateJWK ,
2019-01-12 04:58:27 +00:00
} ,
nil ,
)
if err != nil {
2019-09-27 21:51:53 +00:00
return nil , fmt . Errorf ( "failed to create signer: %v" , err )
2019-01-12 04:58:27 +00:00
}
2019-09-27 21:51:53 +00:00
return signer , nil
}
func signerFromOpaqueSigner ( opaqueSigner jose . OpaqueSigner ) ( jose . Signer , error ) {
alg := jose . SignatureAlgorithm ( opaqueSigner . Public ( ) . Algorithm )
signer , err := jose . NewSigner (
jose . SigningKey {
Algorithm : alg ,
Key : & jose . JSONWebKey {
Algorithm : string ( alg ) ,
Key : opaqueSigner ,
KeyID : opaqueSigner . Public ( ) . KeyID ,
Use : "sig" ,
} ,
} ,
nil ,
)
if err != nil {
return nil , fmt . Errorf ( "failed to create signer: %v" , err )
}
return signer , nil
2019-01-12 04:58:27 +00:00
}
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.
2020-12-01 01:06:26 +00:00
Validate ( ctx context . Context , tokenData string , public * jwt . Claims , private interface { } ) ( * apiserverserviceaccount . ServiceAccountInfo , error )
2019-01-12 04:58:27 +00:00
// 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 ( )
2019-09-27 21:51:53 +00:00
// TODO: Pick the key that has the same key ID as `tok`, if one exists.
2019-01-12 04:58:27 +00:00
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
2020-08-10 17:43:49 +00:00
audit . AddAuditAnnotation ( ctx , "authentication.k8s.io/legacy-token" , public . Subject )
legacyTokensTotal . Inc ( )
2019-01-12 04:58:27 +00:00
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.
2020-08-10 17:43:49 +00:00
sa , err := j . validator . Validate ( ctx , tokenData , public , private )
2019-01-12 04:58:27 +00:00
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
}