2015-05-01 16:02:38 +00:00
/ *
Copyright 2014 The Kubernetes Authors All rights reserved .
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"
"crypto/rsa"
"errors"
"fmt"
"io/ioutil"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/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 {
GetServiceAccount ( namespace , name string ) ( * api . ServiceAccount , error )
GetSecret ( namespace , name string ) ( * api . Secret , error )
}
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.
GenerateToken ( serviceAccount api . ServiceAccount , secret api . Secret ) ( string , error )
}
// ReadPrivateKey is a helper function for reading an rsa.PrivateKey from a PEM-encoded file
func ReadPrivateKey ( file string ) ( * rsa . PrivateKey , error ) {
data , err := ioutil . ReadFile ( file )
if err != nil {
return nil , err
}
return jwt . ParseRSAPrivateKeyFromPEM ( data )
}
// ReadPublicKey is a helper function for reading an rsa.PublicKey from a PEM-encoded file
// Reads public keys from both public and private key files
func ReadPublicKey ( file string ) ( * rsa . PublicKey , error ) {
data , err := ioutil . ReadFile ( file )
if err != nil {
return nil , err
}
if privateKey , err := jwt . ParseRSAPrivateKeyFromPEM ( data ) ; err == nil {
return & privateKey . PublicKey , nil
}
return jwt . ParseRSAPublicKeyFromPEM ( data )
}
// 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 ( key * rsa . PrivateKey ) TokenGenerator {
return & jwtTokenGenerator { key }
}
type jwtTokenGenerator struct {
key * rsa . PrivateKey
}
func ( j * jwtTokenGenerator ) GenerateToken ( serviceAccount api . ServiceAccount , secret api . Secret ) ( string , error ) {
token := jwt . New ( jwt . SigningMethodRS256 )
// Identify the issuer
token . Claims [ IssuerClaim ] = Issuer
2015-05-21 02:19:27 +00:00
// Username
2015-05-01 16:02:38 +00:00
token . Claims [ SubjectClaim ] = MakeUsername ( serviceAccount . Namespace , serviceAccount . Name )
// Persist enough structured info for the authenticator to be able to look up the service account and secret
token . Claims [ NamespaceClaim ] = serviceAccount . Namespace
token . Claims [ ServiceAccountNameClaim ] = serviceAccount . Name
token . Claims [ ServiceAccountUIDClaim ] = serviceAccount . UID
token . Claims [ SecretNameClaim ] = secret . Name
// Sign and get the complete encoded token as a string
return token . SignedString ( j . key )
}
// 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
func JWTTokenAuthenticator ( keys [ ] * rsa . PublicKey , lookup bool , getter ServiceAccountTokenGetter ) authenticator . Token {
return & jwtTokenAuthenticator { keys , lookup , getter }
2015-05-01 16:02:38 +00:00
}
type jwtTokenAuthenticator struct {
keys [ ] * rsa . PublicKey
lookup bool
2015-05-17 03:29:18 +00:00
getter ServiceAccountTokenGetter
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 ) {
if _ , ok := token . Method . ( * jwt . SigningMethodRSA ) ; ! ok {
return nil , fmt . Errorf ( "Unexpected signing method: %v" , token . Header [ "alg" ] )
}
return key , nil
} )
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
}
}
// Other errors should just return as errors
return nil , false , err
}
// If we get here, we have a token with a recognized signature
// Make sure we issued the token
iss , _ := parsedToken . Claims [ IssuerClaim ] . ( string )
if iss != Issuer {
return nil , false , nil
}
// Make sure the claims we need exist
sub , _ := parsedToken . Claims [ SubjectClaim ] . ( string )
if len ( sub ) == 0 {
return nil , false , errors . New ( "sub claim is missing" )
}
namespace , _ := parsedToken . Claims [ NamespaceClaim ] . ( string )
if len ( namespace ) == 0 {
return nil , false , errors . New ( "namespace claim is missing" )
}
secretName , _ := parsedToken . Claims [ SecretNameClaim ] . ( string )
if len ( namespace ) == 0 {
return nil , false , errors . New ( "secretName claim is missing" )
}
serviceAccountName , _ := parsedToken . Claims [ ServiceAccountNameClaim ] . ( string )
if len ( serviceAccountName ) == 0 {
return nil , false , errors . New ( "serviceAccountName claim is missing" )
}
serviceAccountUID , _ := parsedToken . Claims [ ServiceAccountUIDClaim ] . ( string )
if len ( serviceAccountUID ) == 0 {
return nil , false , errors . New ( "serviceAccountUID claim is missing" )
}
2015-05-21 02:19:27 +00:00
subjectNamespace , subjectName , err := SplitUsername ( sub )
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" )
}
if bytes . Compare ( secret . Data [ api . 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
}
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
}