svcacct: move claim generation out of TokenGenerator

pull/6/head
Mike Danese 2018-02-05 20:07:50 -08:00
parent c02b784b76
commit 7d74c35963
7 changed files with 71 additions and 36 deletions

View File

@ -58,6 +58,7 @@ go_test(
"//pkg/controller:go_default_library", "//pkg/controller:go_default_library",
"//vendor/github.com/davecgh/go-spew/spew:go_default_library", "//vendor/github.com/davecgh/go-spew/spew:go_default_library",
"//vendor/github.com/golang/glog: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/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",

View File

@ -395,7 +395,7 @@ func (e *TokensController) ensureReferencedToken(serviceAccount *v1.ServiceAccou
} }
// Generate the token // Generate the token
token, err := e.token.GenerateToken(*serviceAccount, *secret) token, err := e.token.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *secret))
if err != nil { if err != nil {
// retriable error // retriable error
return true, err return true, err
@ -551,7 +551,7 @@ func (e *TokensController) generateTokenIfNeeded(serviceAccount *v1.ServiceAccou
// Generate the token // Generate the token
if needsToken { if needsToken {
token, err := e.token.GenerateToken(*serviceAccount, *liveSecret) token, err := e.token.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *liveSecret))
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@ -24,6 +24,7 @@ import (
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/golang/glog" "github.com/golang/glog"
"gopkg.in/square/go-jose.v2/jwt"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
@ -39,15 +40,11 @@ import (
) )
type testGenerator struct { type testGenerator struct {
GeneratedServiceAccounts []v1.ServiceAccount Token string
GeneratedSecrets []v1.Secret Err error
Token string
Err error
} }
func (t *testGenerator) GenerateToken(serviceAccount v1.ServiceAccount, secret v1.Secret) (string, error) { func (t *testGenerator) GenerateToken(sc *jwt.Claims, pc interface{}) (string, error) {
t.GeneratedSecrets = append(t.GeneratedSecrets, secret)
t.GeneratedServiceAccounts = append(t.GeneratedServiceAccounts, serviceAccount)
return t.Token, t.Err return t.Token, t.Err
} }

View File

@ -10,6 +10,7 @@ go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"jwt.go", "jwt.go",
"legacy.go",
"util.go", "util.go",
], ],
importpath = "k8s.io/kubernetes/pkg/serviceaccount", importpath = "k8s.io/kubernetes/pkg/serviceaccount",

View File

@ -38,15 +38,6 @@ import (
"gopkg.in/square/go-jose.v2/jwt" "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 // ServiceAccountTokenGetter defines functions to retrieve a named service account and secret
type ServiceAccountTokenGetter interface { type ServiceAccountTokenGetter interface {
GetServiceAccount(namespace, name string) (*v1.ServiceAccount, error) GetServiceAccount(namespace, name string) (*v1.ServiceAccount, error)
@ -54,9 +45,13 @@ type ServiceAccountTokenGetter interface {
} }
type TokenGenerator interface { type TokenGenerator interface {
// GenerateToken generates a token which will identify the given ServiceAccount. // GenerateToken generates a token which will identify the given
// The returned token will be stored in the given (and yet-unpersisted) Secret. // ServiceAccount. privateClaims is an interface that will be
GenerateToken(serviceAccount v1.ServiceAccount, secret v1.Secret) (string, error) // 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. // JWTTokenGenerator returns a TokenGenerator that generates signed JWT tokens, using the given privateKey.
@ -74,7 +69,7 @@ type jwtTokenGenerator struct {
privateKey interface{} 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 var alg jose.SignatureAlgorithm
switch privateKey := j.privateKey.(type) { switch privateKey := j.privateKey.(type) {
case *rsa.PrivateKey: case *rsa.PrivateKey:
@ -105,17 +100,14 @@ func (j *jwtTokenGenerator) GenerateToken(serviceAccount v1.ServiceAccount, secr
return "", err return "", err
} }
// claims are applied in reverse precedence
return jwt.Signed(signer). return jwt.Signed(signer).
Claims(privateClaims).
Claims(claims).
Claims(&jwt.Claims{ Claims(&jwt.Claims{
Issuer: j.iss, Issuer: j.iss,
Subject: apiserverserviceaccount.MakeUsername(serviceAccount.Namespace, serviceAccount.Name),
}). }).
Claims(&privateClaims{ CompactSerialize()
Namespace: serviceAccount.Namespace,
ServiceAccountName: serviceAccount.Name,
ServiceAccountUID: string(serviceAccount.UID),
SecretName: secret.Name,
}).CompactSerialize()
} }
// JWTTokenAuthenticator authenticates tokens as JWT tokens produced by JWTTokenGenerator // JWTTokenAuthenticator authenticates tokens as JWT tokens produced by JWTTokenGenerator
@ -139,7 +131,7 @@ type jwtTokenAuthenticator struct {
} }
type Validator interface { 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") var errMismatchedSigningMethod = errors.New("invalid signing method")
@ -155,7 +147,7 @@ func (j *jwtTokenAuthenticator) AuthenticateToken(tokenData string) (user.Info,
} }
public := &jwt.Claims{} public := &jwt.Claims{}
private := &privateClaims{} private := &legacyPrivateClaims{}
var ( var (
found bool found bool
@ -218,7 +210,7 @@ type legacyValidator struct {
getter ServiceAccountTokenGetter 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 // Make sure the claims we need exist
if len(public.Subject) == 0 { if len(public.Subject) == 0 {

View File

@ -129,7 +129,7 @@ func TestTokenGenerateAndValidate(t *testing.T) {
// Generate the RSA token // Generate the RSA token
rsaGenerator := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(rsaPrivateKey)) rsaGenerator := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(rsaPrivateKey))
rsaToken, err := rsaGenerator.GenerateToken(*serviceAccount, *rsaSecret) rsaToken, err := rsaGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *rsaSecret))
if err != nil { if err != nil {
t.Fatalf("error generating token: %v", err) t.Fatalf("error generating token: %v", err)
} }
@ -142,7 +142,7 @@ func TestTokenGenerateAndValidate(t *testing.T) {
// Generate the ECDSA token // Generate the ECDSA token
ecdsaGenerator := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(ecdsaPrivateKey)) ecdsaGenerator := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(ecdsaPrivateKey))
ecdsaToken, err := ecdsaGenerator.GenerateToken(*serviceAccount, *ecdsaSecret) ecdsaToken, err := ecdsaGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *ecdsaSecret))
if err != nil { if err != nil {
t.Fatalf("error generating token: %v", err) 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 // Generate signer with same keys as RSA signer but different issuer
badIssuerGenerator := serviceaccount.JWTTokenGenerator("foo", getPrivateKey(rsaPrivateKey)) badIssuerGenerator := serviceaccount.JWTTokenGenerator("foo", getPrivateKey(rsaPrivateKey))
badIssuerToken, err := badIssuerGenerator.GenerateToken(*serviceAccount, *rsaSecret) badIssuerToken, err := badIssuerGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *rsaSecret))
if err != nil { if err != nil {
t.Fatalf("error generating token: %v", err) t.Fatalf("error generating token: %v", err)
} }

View File

@ -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"`
}