diff --git a/pkg/controller/serviceaccount/BUILD b/pkg/controller/serviceaccount/BUILD index dc5bc8fd5f..32a8a79355 100644 --- a/pkg/controller/serviceaccount/BUILD +++ b/pkg/controller/serviceaccount/BUILD @@ -58,6 +58,7 @@ go_test( "//pkg/controller:go_default_library", "//vendor/github.com/davecgh/go-spew/spew:go_default_library", "//vendor/github.com/golang/glog:go_default_library", + "//vendor/gopkg.in/square/go-jose.v2/jwt:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/controller/serviceaccount/tokens_controller.go b/pkg/controller/serviceaccount/tokens_controller.go index c34e600ef1..7b7f80ce43 100644 --- a/pkg/controller/serviceaccount/tokens_controller.go +++ b/pkg/controller/serviceaccount/tokens_controller.go @@ -395,7 +395,7 @@ func (e *TokensController) ensureReferencedToken(serviceAccount *v1.ServiceAccou } // Generate the token - token, err := e.token.GenerateToken(*serviceAccount, *secret) + token, err := e.token.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *secret)) if err != nil { // retriable error return true, err @@ -551,7 +551,7 @@ func (e *TokensController) generateTokenIfNeeded(serviceAccount *v1.ServiceAccou // Generate the token if needsToken { - token, err := e.token.GenerateToken(*serviceAccount, *liveSecret) + token, err := e.token.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *liveSecret)) if err != nil { return false, err } diff --git a/pkg/controller/serviceaccount/tokens_controller_test.go b/pkg/controller/serviceaccount/tokens_controller_test.go index bacf60500c..ca6a14f239 100644 --- a/pkg/controller/serviceaccount/tokens_controller_test.go +++ b/pkg/controller/serviceaccount/tokens_controller_test.go @@ -24,6 +24,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/golang/glog" + "gopkg.in/square/go-jose.v2/jwt" "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -39,15 +40,11 @@ import ( ) type testGenerator struct { - GeneratedServiceAccounts []v1.ServiceAccount - GeneratedSecrets []v1.Secret - Token string - Err error + Token string + Err error } -func (t *testGenerator) GenerateToken(serviceAccount v1.ServiceAccount, secret v1.Secret) (string, error) { - t.GeneratedSecrets = append(t.GeneratedSecrets, secret) - t.GeneratedServiceAccounts = append(t.GeneratedServiceAccounts, serviceAccount) +func (t *testGenerator) GenerateToken(sc *jwt.Claims, pc interface{}) (string, error) { return t.Token, t.Err } diff --git a/pkg/serviceaccount/BUILD b/pkg/serviceaccount/BUILD index 102b4cb7fb..f2d0bd0e99 100644 --- a/pkg/serviceaccount/BUILD +++ b/pkg/serviceaccount/BUILD @@ -10,6 +10,7 @@ go_library( name = "go_default_library", srcs = [ "jwt.go", + "legacy.go", "util.go", ], importpath = "k8s.io/kubernetes/pkg/serviceaccount", diff --git a/pkg/serviceaccount/jwt.go b/pkg/serviceaccount/jwt.go index 5c1f5b7be4..22cc76f61e 100644 --- a/pkg/serviceaccount/jwt.go +++ b/pkg/serviceaccount/jwt.go @@ -38,15 +38,6 @@ import ( "gopkg.in/square/go-jose.v2/jwt" ) -const LegacyIssuer = "kubernetes/serviceaccount" - -type privateClaims struct { - ServiceAccountName string `json:"kubernetes.io/serviceaccount/service-account.name"` - ServiceAccountUID string `json:"kubernetes.io/serviceaccount/service-account.uid"` - SecretName string `json:"kubernetes.io/serviceaccount/secret.name"` - Namespace string `json:"kubernetes.io/serviceaccount/namespace"` -} - // ServiceAccountTokenGetter defines functions to retrieve a named service account and secret type ServiceAccountTokenGetter interface { GetServiceAccount(namespace, name string) (*v1.ServiceAccount, error) @@ -54,9 +45,13 @@ type ServiceAccountTokenGetter interface { } 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 v1.ServiceAccount, secret v1.Secret) (string, error) + // 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. @@ -74,7 +69,7 @@ type jwtTokenGenerator struct { privateKey interface{} } -func (j *jwtTokenGenerator) GenerateToken(serviceAccount v1.ServiceAccount, secret v1.Secret) (string, error) { +func (j *jwtTokenGenerator) GenerateToken(claims *jwt.Claims, privateClaims interface{}) (string, error) { var alg jose.SignatureAlgorithm switch privateKey := j.privateKey.(type) { case *rsa.PrivateKey: @@ -105,17 +100,14 @@ func (j *jwtTokenGenerator) GenerateToken(serviceAccount v1.ServiceAccount, secr return "", err } + // claims are applied in reverse precedence return jwt.Signed(signer). + Claims(privateClaims). + Claims(claims). Claims(&jwt.Claims{ - Issuer: j.iss, - Subject: apiserverserviceaccount.MakeUsername(serviceAccount.Namespace, serviceAccount.Name), + Issuer: j.iss, }). - Claims(&privateClaims{ - Namespace: serviceAccount.Namespace, - ServiceAccountName: serviceAccount.Name, - ServiceAccountUID: string(serviceAccount.UID), - SecretName: secret.Name, - }).CompactSerialize() + CompactSerialize() } // JWTTokenAuthenticator authenticates tokens as JWT tokens produced by JWTTokenGenerator @@ -139,7 +131,7 @@ type jwtTokenAuthenticator struct { } type Validator interface { - Validate(tokenData string, public *jwt.Claims, private *privateClaims) error + Validate(tokenData string, public *jwt.Claims, private *legacyPrivateClaims) error } var errMismatchedSigningMethod = errors.New("invalid signing method") @@ -155,7 +147,7 @@ func (j *jwtTokenAuthenticator) AuthenticateToken(tokenData string) (user.Info, } public := &jwt.Claims{} - private := &privateClaims{} + private := &legacyPrivateClaims{} var ( found bool @@ -218,7 +210,7 @@ type legacyValidator struct { getter ServiceAccountTokenGetter } -func (v *legacyValidator) Validate(tokenData string, public *jwt.Claims, private *privateClaims) error { +func (v *legacyValidator) Validate(tokenData string, public *jwt.Claims, private *legacyPrivateClaims) error { // Make sure the claims we need exist if len(public.Subject) == 0 { diff --git a/pkg/serviceaccount/jwt_test.go b/pkg/serviceaccount/jwt_test.go index d8b326a50c..668be00c41 100644 --- a/pkg/serviceaccount/jwt_test.go +++ b/pkg/serviceaccount/jwt_test.go @@ -129,7 +129,7 @@ func TestTokenGenerateAndValidate(t *testing.T) { // Generate the RSA token rsaGenerator := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(rsaPrivateKey)) - rsaToken, err := rsaGenerator.GenerateToken(*serviceAccount, *rsaSecret) + rsaToken, err := rsaGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *rsaSecret)) if err != nil { t.Fatalf("error generating token: %v", err) } @@ -142,7 +142,7 @@ func TestTokenGenerateAndValidate(t *testing.T) { // Generate the ECDSA token ecdsaGenerator := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(ecdsaPrivateKey)) - ecdsaToken, err := ecdsaGenerator.GenerateToken(*serviceAccount, *ecdsaSecret) + ecdsaToken, err := ecdsaGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *ecdsaSecret)) if err != nil { t.Fatalf("error generating token: %v", err) } @@ -155,7 +155,7 @@ func TestTokenGenerateAndValidate(t *testing.T) { // Generate signer with same keys as RSA signer but different issuer badIssuerGenerator := serviceaccount.JWTTokenGenerator("foo", getPrivateKey(rsaPrivateKey)) - badIssuerToken, err := badIssuerGenerator.GenerateToken(*serviceAccount, *rsaSecret) + badIssuerToken, err := badIssuerGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *rsaSecret)) if err != nil { t.Fatalf("error generating token: %v", err) } diff --git a/pkg/serviceaccount/legacy.go b/pkg/serviceaccount/legacy.go new file mode 100644 index 0000000000..f5fa96eb43 --- /dev/null +++ b/pkg/serviceaccount/legacy.go @@ -0,0 +1,44 @@ +/* +Copyright 2018 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 ( + "k8s.io/api/core/v1" + apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount" + + "gopkg.in/square/go-jose.v2/jwt" +) + +func LegacyClaims(serviceAccount v1.ServiceAccount, secret v1.Secret) (*jwt.Claims, interface{}) { + return &jwt.Claims{ + Subject: apiserverserviceaccount.MakeUsername(serviceAccount.Namespace, serviceAccount.Name), + }, &legacyPrivateClaims{ + Namespace: serviceAccount.Namespace, + ServiceAccountName: serviceAccount.Name, + ServiceAccountUID: string(serviceAccount.UID), + SecretName: secret.Name, + } +} + +const LegacyIssuer = "kubernetes/serviceaccount" + +type legacyPrivateClaims struct { + ServiceAccountName string `json:"kubernetes.io/serviceaccount/service-account.name"` + ServiceAccountUID string `json:"kubernetes.io/serviceaccount/service-account.uid"` + SecretName string `json:"kubernetes.io/serviceaccount/secret.name"` + Namespace string `json:"kubernetes.io/serviceaccount/namespace"` +}