2015-05-01 16:02:38 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2014 The Kubernetes Authors .
2015-05-01 16:02:38 +00:00
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"
2016-09-27 05:44:33 +00:00
"crypto/ecdsa"
"crypto/elliptic"
2015-05-01 16:02:38 +00:00
"crypto/rsa"
"errors"
"fmt"
2017-06-22 18:24:23 +00:00
"k8s.io/api/core/v1"
2017-01-04 15:39:05 +00:00
"k8s.io/apiserver/pkg/authentication/authenticator"
2017-01-16 11:43:56 +00:00
apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
2017-01-04 14:31:53 +00:00
"k8s.io/apiserver/pkg/authentication/user"
2015-06-18 14:41:35 +00:00
jwt "github.com/dgrijalva/jwt-go"
"github.com/golang/glog"
2015-05-01 16:02:38 +00:00
)
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"
)
2015-12-24 21:54:40 +00:00
// ServiceAccountTokenGetter defines functions to retrieve a named service account and secret
type ServiceAccountTokenGetter interface {
2016-11-18 21:26:53 +00:00
GetServiceAccount ( namespace , name string ) ( * v1 . ServiceAccount , error )
GetSecret ( namespace , name string ) ( * v1 . Secret , error )
2015-12-24 21:54:40 +00:00
}
2015-05-01 16:02:38 +00:00
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.
2016-11-18 21:26:53 +00:00
GenerateToken ( serviceAccount v1 . ServiceAccount , secret v1 . Secret ) ( string , error )
2015-05-01 16:02:38 +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()
2016-09-27 05:44:33 +00:00
func JWTTokenGenerator ( privateKey interface { } ) TokenGenerator {
return & jwtTokenGenerator { privateKey }
2015-05-01 16:02:38 +00:00
}
type jwtTokenGenerator struct {
2016-09-27 05:44:33 +00:00
privateKey interface { }
2015-05-01 16:02:38 +00:00
}
2016-11-18 21:26:53 +00:00
func ( j * jwtTokenGenerator ) GenerateToken ( serviceAccount v1 . ServiceAccount , secret v1 . Secret ) ( string , error ) {
2016-09-27 05:44:33 +00:00
var method jwt . SigningMethod
switch privateKey := j . privateKey . ( type ) {
case * rsa . PrivateKey :
method = jwt . SigningMethodRS256
case * ecdsa . PrivateKey :
switch privateKey . Curve {
case elliptic . P256 ( ) :
method = jwt . SigningMethodES256
case elliptic . P384 ( ) :
method = jwt . SigningMethodES384
case elliptic . P521 ( ) :
method = jwt . SigningMethodES512
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 )
}
token := jwt . New ( method )
2015-05-01 16:02:38 +00:00
2016-07-06 18:25:11 +00:00
claims , _ := token . Claims . ( jwt . MapClaims )
2015-05-01 16:02:38 +00:00
// Identify the issuer
2016-07-06 18:25:11 +00:00
claims [ IssuerClaim ] = Issuer
2015-05-01 16:02:38 +00:00
2015-05-21 02:19:27 +00:00
// Username
2017-01-16 11:43:56 +00:00
claims [ SubjectClaim ] = apiserverserviceaccount . MakeUsername ( serviceAccount . Namespace , serviceAccount . Name )
2015-05-01 16:02:38 +00:00
// Persist enough structured info for the authenticator to be able to look up the service account and secret
2016-07-06 18:25:11 +00:00
claims [ NamespaceClaim ] = serviceAccount . Namespace
claims [ ServiceAccountNameClaim ] = serviceAccount . Name
claims [ ServiceAccountUIDClaim ] = serviceAccount . UID
claims [ SecretNameClaim ] = secret . Name
2015-05-01 16:02:38 +00:00
// Sign and get the complete encoded token as a string
2016-09-27 05:44:33 +00:00
return token . SignedString ( j . privateKey )
2015-05-01 16:02:38 +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)
2015-05-17 03:29:18 +00:00
// If lookup is true, the service account and secret referenced as claims inside the token are retrieved and verified with the provided ServiceAccountTokenGetter
2016-09-27 05:44:33 +00:00
func JWTTokenAuthenticator ( keys [ ] interface { } , lookup bool , getter ServiceAccountTokenGetter ) authenticator . Token {
2015-05-17 03:29:18 +00:00
return & jwtTokenAuthenticator { keys , lookup , getter }
2015-05-01 16:02:38 +00:00
}
type jwtTokenAuthenticator struct {
2016-09-27 05:44:33 +00:00
keys [ ] interface { }
2015-05-01 16:02:38 +00:00
lookup bool
2015-05-17 03:29:18 +00:00
getter ServiceAccountTokenGetter
2015-05-01 16:02:38 +00:00
}
2016-09-27 05:44:33 +00:00
var errMismatchedSigningMethod = errors . New ( "invalid signing method" )
2015-05-01 16:02:38 +00:00
func ( j * jwtTokenAuthenticator ) AuthenticateToken ( token string ) ( user . Info , bool , error ) {
var validationError error
2015-06-18 14:41:35 +00:00
for i , key := range j . keys {
2015-05-01 16:02:38 +00:00
// Attempt to verify with each key until we find one that works
parsedToken , err := jwt . Parse ( token , func ( token * jwt . Token ) ( interface { } , error ) {
2016-09-27 05:44:33 +00:00
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 :
2015-05-01 16:02:38 +00:00
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
2015-06-18 14:41:35 +00:00
glog . V ( 4 ) . Infof ( "Signature error (key %d): %v" , i , err )
2015-05-01 16:02:38 +00:00
validationError = err
continue
}
2016-09-27 05:44:33 +00:00
// 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
}
2015-05-01 16:02:38 +00:00
}
// Other errors should just return as errors
return nil , false , err
}
// If we get here, we have a token with a recognized signature
2016-07-06 17:51:25 +00:00
claims , _ := parsedToken . Claims . ( jwt . MapClaims )
2015-05-01 16:02:38 +00:00
// Make sure we issued the token
2016-07-06 18:25:11 +00:00
iss , _ := claims [ IssuerClaim ] . ( string )
2015-05-01 16:02:38 +00:00
if iss != Issuer {
return nil , false , nil
}
// Make sure the claims we need exist
2016-07-06 18:25:11 +00:00
sub , _ := claims [ SubjectClaim ] . ( string )
2015-05-01 16:02:38 +00:00
if len ( sub ) == 0 {
return nil , false , errors . New ( "sub claim is missing" )
}
2016-07-06 18:25:11 +00:00
namespace , _ := claims [ NamespaceClaim ] . ( string )
2015-05-01 16:02:38 +00:00
if len ( namespace ) == 0 {
return nil , false , errors . New ( "namespace claim is missing" )
}
2016-07-06 18:25:11 +00:00
secretName , _ := claims [ SecretNameClaim ] . ( string )
2017-12-15 01:27:52 +00:00
if len ( secretName ) == 0 {
2015-05-01 16:02:38 +00:00
return nil , false , errors . New ( "secretName claim is missing" )
}
2016-07-06 18:25:11 +00:00
serviceAccountName , _ := claims [ ServiceAccountNameClaim ] . ( string )
2015-05-01 16:02:38 +00:00
if len ( serviceAccountName ) == 0 {
return nil , false , errors . New ( "serviceAccountName claim is missing" )
}
2016-07-06 18:25:11 +00:00
serviceAccountUID , _ := claims [ ServiceAccountUIDClaim ] . ( string )
2015-05-01 16:02:38 +00:00
if len ( serviceAccountUID ) == 0 {
return nil , false , errors . New ( "serviceAccountUID claim is missing" )
}
2017-01-16 11:43:56 +00:00
subjectNamespace , subjectName , err := apiserverserviceaccount . SplitUsername ( sub )
2015-05-21 02:19:27 +00:00
if err != nil || subjectNamespace != namespace || subjectName != serviceAccountName {
return nil , false , errors . New ( "sub claim is invalid" )
}
2015-05-01 16:02:38 +00:00
if j . lookup {
// Make sure token hasn't been invalidated by deletion of the secret
2015-05-17 03:29:18 +00:00
secret , err := j . getter . GetSecret ( namespace , secretName )
2015-05-01 16:02:38 +00:00
if err != nil {
2015-06-18 14:41:35 +00:00
glog . V ( 4 ) . Infof ( "Could not retrieve token %s/%s for service account %s/%s: %v" , namespace , secretName , namespace , serviceAccountName , err )
2015-05-01 16:02:38 +00:00
return nil , false , errors . New ( "Token has been invalidated" )
}
2017-06-30 13:19:44 +00:00
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 nil , false , errors . New ( "Token has been invalidated" )
}
2016-11-18 21:26:53 +00:00
if bytes . Compare ( secret . Data [ v1 . ServiceAccountTokenKey ] , [ ] byte ( token ) ) != 0 {
2015-06-18 14:41:35 +00:00
glog . V ( 4 ) . Infof ( "Token contents no longer matches %s/%s for service account %s/%s" , namespace , secretName , namespace , serviceAccountName )
2015-05-01 16:02:38 +00:00
return nil , false , errors . New ( "Token does not match server's copy" )
}
// Make sure service account still exists (name and UID)
2015-05-17 03:29:18 +00:00
serviceAccount , err := j . getter . GetServiceAccount ( namespace , serviceAccountName )
2015-05-01 16:02:38 +00:00
if err != nil {
2015-06-18 14:41:35 +00:00
glog . V ( 4 ) . Infof ( "Could not retrieve service account %s/%s: %v" , namespace , serviceAccountName , err )
2015-05-01 16:02:38 +00:00
return nil , false , err
}
2017-06-30 13:19:44 +00:00
if serviceAccount . DeletionTimestamp != nil {
glog . V ( 4 ) . Infof ( "Service account has been deleted %s/%s" , namespace , serviceAccountName )
return nil , false , fmt . Errorf ( "ServiceAccount %s/%s has been deleted" , namespace , serviceAccountName )
}
2015-05-01 16:02:38 +00:00
if string ( serviceAccount . UID ) != serviceAccountUID {
2015-06-18 14:41:35 +00:00
glog . V ( 4 ) . Infof ( "Service account UID no longer matches %s/%s: %q != %q" , namespace , serviceAccountName , string ( serviceAccount . UID ) , serviceAccountUID )
2015-05-01 16:02:38 +00:00
return nil , false , fmt . Errorf ( "ServiceAccount UID (%s) does not match claim (%s)" , serviceAccount . UID , serviceAccountUID )
}
}
2015-05-21 02:19:27 +00:00
return UserInfo ( namespace , serviceAccountName , serviceAccountUID ) , true , nil
2015-05-01 16:02:38 +00:00
}
return nil , false , validationError
}