2018-01-09 18:57:14 +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 (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
2018-03-06 22:33:18 +00:00
"encoding/base64"
"encoding/json"
2018-01-09 18:57:14 +00:00
"fmt"
2018-03-06 22:33:18 +00:00
"strings"
2018-01-09 18:57:14 +00:00
"k8s.io/api/core/v1"
2018-03-06 22:33:18 +00:00
utilerrors "k8s.io/apimachinery/pkg/util/errors"
2018-01-09 18:57:14 +00:00
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
2018-03-06 22:33:18 +00:00
jose "gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
2018-01-09 18:57:14 +00:00
)
// ServiceAccountTokenGetter defines functions to retrieve a named service account and secret
type ServiceAccountTokenGetter interface {
GetServiceAccount ( namespace , name string ) ( * v1 . ServiceAccount , error )
2018-03-06 22:33:18 +00:00
GetPod ( namespace , name string ) ( * v1 . Pod , error )
2018-01-09 18:57:14 +00:00
GetSecret ( namespace , name string ) ( * v1 . Secret , error )
}
type TokenGenerator interface {
2018-03-06 22:33:18 +00:00
// 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 )
2018-01-09 18:57:14 +00:00
}
// 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()
2018-03-06 22:33:18 +00:00
func JWTTokenGenerator ( iss string , privateKey interface { } ) TokenGenerator {
return & jwtTokenGenerator {
iss : iss ,
privateKey : privateKey ,
}
2018-01-09 18:57:14 +00:00
}
type jwtTokenGenerator struct {
2018-03-06 22:33:18 +00:00
iss string
2018-01-09 18:57:14 +00:00
privateKey interface { }
}
2018-03-06 22:33:18 +00:00
func ( j * jwtTokenGenerator ) GenerateToken ( claims * jwt . Claims , privateClaims interface { } ) ( string , error ) {
var alg jose . SignatureAlgorithm
2018-01-09 18:57:14 +00:00
switch privateKey := j . privateKey . ( type ) {
case * rsa . PrivateKey :
2018-03-06 22:33:18 +00:00
alg = jose . RS256
2018-01-09 18:57:14 +00:00
case * ecdsa . PrivateKey :
switch privateKey . Curve {
case elliptic . P256 ( ) :
2018-03-06 22:33:18 +00:00
alg = jose . ES256
2018-01-09 18:57:14 +00:00
case elliptic . P384 ( ) :
2018-03-06 22:33:18 +00:00
alg = jose . ES384
2018-01-09 18:57:14 +00:00
case elliptic . P521 ( ) :
2018-03-06 22:33:18 +00:00
alg = jose . ES512
2018-01-09 18:57:14 +00:00
default :
return "" , fmt . Errorf ( "unknown private key curve, must be 256, 384, or 521" )
}
default :
return "" , fmt . Errorf ( "unknown private key type %T, must be *rsa.PrivateKey or *ecdsa.PrivateKey" , j . privateKey )
}
2018-03-06 22:33:18 +00:00
signer , err := jose . NewSigner (
jose . SigningKey {
Algorithm : alg ,
Key : j . privateKey ,
} ,
nil ,
)
if err != nil {
return "" , err
}
2018-01-09 18:57:14 +00:00
2018-03-06 22:33:18 +00:00
// claims are applied in reverse precedence
return jwt . Signed ( signer ) .
Claims ( privateClaims ) .
Claims ( claims ) .
Claims ( & jwt . Claims {
Issuer : j . iss ,
} ) .
CompactSerialize ( )
2018-01-09 18:57:14 +00:00
}
// 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
2018-03-06 22:33:18 +00:00
func JWTTokenAuthenticator ( iss string , keys [ ] interface { } , validator Validator ) authenticator . Token {
return & jwtTokenAuthenticator {
iss : iss ,
keys : keys ,
validator : validator ,
}
2018-01-09 18:57:14 +00:00
}
type jwtTokenAuthenticator struct {
2018-03-06 22:33:18 +00:00
iss string
keys [ ] interface { }
validator Validator
2018-01-09 18:57:14 +00:00
}
2018-03-06 22:33:18 +00:00
// 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 { }
}
2018-01-09 18:57:14 +00:00
2018-03-06 22:33:18 +00:00
func ( j * jwtTokenAuthenticator ) AuthenticateToken ( tokenData string ) ( user . Info , bool , error ) {
if ! j . hasCorrectIssuer ( tokenData ) {
return nil , false , nil
}
2018-01-09 18:57:14 +00:00
2018-03-06 22:33:18 +00:00
tok , err := jwt . ParseSigned ( tokenData )
if err != nil {
return nil , false , nil
}
2018-01-09 18:57:14 +00:00
2018-03-06 22:33:18 +00:00
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
2018-01-09 18:57:14 +00:00
}
2018-03-06 22:33:18 +00:00
found = true
break
}
2018-01-09 18:57:14 +00:00
2018-03-06 22:33:18 +00:00
if ! found {
return nil , false , utilerrors . NewAggregate ( errlist )
}
2018-01-09 18:57:14 +00:00
2018-03-06 22:33:18 +00:00
// 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
}
2018-01-09 18:57:14 +00:00
2018-03-06 22:33:18 +00:00
return UserInfo ( ns , name , uid ) , true , nil
}
2018-01-09 18:57:14 +00:00
2018-03-06 22:33:18 +00:00
// 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
2018-01-09 18:57:14 +00:00
}
2018-03-06 22:33:18 +00:00
return true
2018-01-09 18:57:14 +00:00
}