mirror of https://github.com/k3s-io/k3s
retrofit svcacct token authenticator to support audience validation
parent
bfb95290b9
commit
67bbf753cb
|
@ -64,7 +64,7 @@ type Config struct {
|
|||
ServiceAccountKeyFiles []string
|
||||
ServiceAccountLookup bool
|
||||
ServiceAccountIssuer string
|
||||
APIAudiences []string
|
||||
APIAudiences authenticator.Audiences
|
||||
WebhookTokenAuthnConfigFile string
|
||||
WebhookTokenAuthnCacheTTL time.Duration
|
||||
|
||||
|
@ -135,14 +135,14 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
|
|||
tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, tokenAuth))
|
||||
}
|
||||
if len(config.ServiceAccountKeyFiles) > 0 {
|
||||
serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.ServiceAccountTokenGetter)
|
||||
serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.APIAudiences, config.ServiceAccountTokenGetter)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, serviceAccountAuth))
|
||||
tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
|
||||
}
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) && config.ServiceAccountIssuer != "" {
|
||||
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuer, config.APIAudiences, config.ServiceAccountKeyFiles, config.ServiceAccountTokenGetter)
|
||||
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuer, config.ServiceAccountKeyFiles, config.APIAudiences, config.ServiceAccountTokenGetter)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -276,7 +276,7 @@ func newAuthenticatorFromOIDCIssuerURL(opts oidc.Options) (authenticator.Token,
|
|||
}
|
||||
|
||||
// newLegacyServiceAccountAuthenticator returns an authenticator.Token or an error
|
||||
func newLegacyServiceAccountAuthenticator(keyfiles []string, lookup bool, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
|
||||
func newLegacyServiceAccountAuthenticator(keyfiles []string, lookup bool, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
|
||||
allPublicKeys := []interface{}{}
|
||||
for _, keyfile := range keyfiles {
|
||||
publicKeys, err := certutil.PublicKeysFromFile(keyfile)
|
||||
|
@ -286,12 +286,12 @@ func newLegacyServiceAccountAuthenticator(keyfiles []string, lookup bool, servic
|
|||
allPublicKeys = append(allPublicKeys, publicKeys...)
|
||||
}
|
||||
|
||||
tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, allPublicKeys, serviceaccount.NewLegacyValidator(lookup, serviceAccountGetter))
|
||||
tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, allPublicKeys, apiAudiences, serviceaccount.NewLegacyValidator(lookup, serviceAccountGetter))
|
||||
return tokenAuthenticator, nil
|
||||
}
|
||||
|
||||
// newServiceAccountAuthenticator returns an authenticator.Token or an error
|
||||
func newServiceAccountAuthenticator(iss string, audiences []string, keyfiles []string, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
|
||||
func newServiceAccountAuthenticator(iss string, keyfiles []string, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
|
||||
allPublicKeys := []interface{}{}
|
||||
for _, keyfile := range keyfiles {
|
||||
publicKeys, err := certutil.PublicKeysFromFile(keyfile)
|
||||
|
@ -301,7 +301,7 @@ func newServiceAccountAuthenticator(iss string, audiences []string, keyfiles []s
|
|||
allPublicKeys = append(allPublicKeys, publicKeys...)
|
||||
}
|
||||
|
||||
tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(iss, allPublicKeys, serviceaccount.NewValidator(audiences, serviceAccountGetter))
|
||||
tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(iss, allPublicKeys, apiAudiences, serviceaccount.NewValidator(serviceAccountGetter))
|
||||
return tokenAuthenticator, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ go_test(
|
|||
"//pkg/controller/serviceaccount:go_default_library",
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/util/cert:go_default_library",
|
||||
|
|
|
@ -80,15 +80,13 @@ func Claims(sa core.ServiceAccount, pod *core.Pod, secret *core.Secret, expirati
|
|||
return sc, pc
|
||||
}
|
||||
|
||||
func NewValidator(audiences []string, getter ServiceAccountTokenGetter) Validator {
|
||||
func NewValidator(getter ServiceAccountTokenGetter) Validator {
|
||||
return &validator{
|
||||
auds: audiences,
|
||||
getter: getter,
|
||||
}
|
||||
}
|
||||
|
||||
type validator struct {
|
||||
auds []string
|
||||
getter ServiceAccountTokenGetter
|
||||
}
|
||||
|
||||
|
@ -112,19 +110,6 @@ func (v *validator) Validate(_ string, public *jwt.Claims, privateObj interface{
|
|||
return nil, errors.New("Token could not be validated.")
|
||||
}
|
||||
|
||||
var audValid bool
|
||||
|
||||
for _, aud := range v.auds {
|
||||
audValid = public.Audience.Contains(aud)
|
||||
if audValid {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !audValid {
|
||||
return nil, errors.New("Token is invalid for this audience.")
|
||||
}
|
||||
|
||||
namespace := private.Kubernetes.Namespace
|
||||
saref := private.Kubernetes.Svcacct
|
||||
podref := private.Kubernetes.Pod
|
||||
|
|
|
@ -111,18 +111,20 @@ func (j *jwtTokenGenerator) GenerateToken(claims *jwt.Claims, privateClaims inte
|
|||
// 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
|
||||
func JWTTokenAuthenticator(iss string, keys []interface{}, validator Validator) authenticator.Token {
|
||||
func JWTTokenAuthenticator(iss string, keys []interface{}, implicitAuds authenticator.Audiences, validator Validator) authenticator.Token {
|
||||
return &jwtTokenAuthenticator{
|
||||
iss: iss,
|
||||
keys: keys,
|
||||
validator: validator,
|
||||
iss: iss,
|
||||
keys: keys,
|
||||
implicitAuds: implicitAuds,
|
||||
validator: validator,
|
||||
}
|
||||
}
|
||||
|
||||
type jwtTokenAuthenticator struct {
|
||||
iss string
|
||||
keys []interface{}
|
||||
validator Validator
|
||||
iss string
|
||||
keys []interface{}
|
||||
validator Validator
|
||||
implicitAuds authenticator.Audiences
|
||||
}
|
||||
|
||||
// Validator is called by the JWT token authenticator to apply domain specific
|
||||
|
@ -170,6 +172,23 @@ func (j *jwtTokenAuthenticator) AuthenticateToken(ctx context.Context, tokenData
|
|||
return nil, false, utilerrors.NewAggregate(errlist)
|
||||
}
|
||||
|
||||
tokenAudiences := authenticator.Audiences(public.Audience)
|
||||
if len(tokenAudiences) == 0 {
|
||||
// only apiserver audiences are allowed for legacy tokens
|
||||
tokenAudiences = j.implicitAuds
|
||||
}
|
||||
|
||||
requestedAudiences, ok := authenticator.AudiencesFrom(ctx)
|
||||
if !ok {
|
||||
// default to apiserver audiences
|
||||
requestedAudiences = j.implicitAuds
|
||||
}
|
||||
|
||||
auds := authenticator.Audiences(tokenAudiences).Intersect(requestedAudiences)
|
||||
if len(auds) == 0 && len(j.implicitAuds) != 0 {
|
||||
return nil, false, fmt.Errorf("token audiences %q is invalid for the target audiences %q", tokenAudiences, requestedAudiences)
|
||||
}
|
||||
|
||||
// If we get here, we have a token with a recognized signature and
|
||||
// issuer string.
|
||||
sa, err := j.validator.Validate(tokenData, public, private)
|
||||
|
@ -177,7 +196,10 @@ func (j *jwtTokenAuthenticator) AuthenticateToken(ctx context.Context, tokenData
|
|||
return nil, false, err
|
||||
}
|
||||
|
||||
return &authenticator.Response{User: sa.UserInfo()}, true, nil
|
||||
return &authenticator.Response{
|
||||
User: sa.UserInfo(),
|
||||
Audiences: auds,
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
// hasCorrectIssuer returns true if tokenData is a valid JWT in compact
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
|
@ -275,16 +276,18 @@ func TestTokenGenerateAndValidate(t *testing.T) {
|
|||
}
|
||||
|
||||
for k, tc := range testCases {
|
||||
auds := authenticator.Audiences{"api"}
|
||||
getter := serviceaccountcontroller.NewGetterFromClient(tc.Client)
|
||||
authenticator := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, tc.Keys, serviceaccount.NewLegacyValidator(tc.Client != nil, getter))
|
||||
authn := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, tc.Keys, auds, serviceaccount.NewLegacyValidator(tc.Client != nil, getter))
|
||||
|
||||
// An invalid, non-JWT token should always fail
|
||||
if _, ok, err := authenticator.AuthenticateToken(context.Background(), "invalid token"); err != nil || ok {
|
||||
ctx := authenticator.WithAudiences(context.Background(), auds)
|
||||
if _, ok, err := authn.AuthenticateToken(ctx, "invalid token"); err != nil || ok {
|
||||
t.Errorf("%s: Expected err=nil, ok=false for non-JWT token", k)
|
||||
continue
|
||||
}
|
||||
|
||||
resp, ok, err := authenticator.AuthenticateToken(context.Background(), tc.Token)
|
||||
resp, ok, err := authn.AuthenticateToken(ctx, tc.Token)
|
||||
if (err != nil) != tc.ExpectedErr {
|
||||
t.Errorf("%s: Expected error=%v, got %v", k, tc.ExpectedErr, err)
|
||||
continue
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
|
||||
apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
|
||||
|
@ -63,7 +64,7 @@ func TestServiceAccountTokenCreate(t *testing.T) {
|
|||
pk := sk.(*ecdsa.PrivateKey).PublicKey
|
||||
|
||||
const iss = "https://foo.bar.example.com"
|
||||
aud := []string{"api"}
|
||||
aud := authenticator.Audiences{"api"}
|
||||
|
||||
maxExpirationSeconds := int64(60 * 60)
|
||||
maxExpirationDuration, err := time.ParseDuration(fmt.Sprintf("%ds", maxExpirationSeconds))
|
||||
|
@ -76,11 +77,13 @@ func TestServiceAccountTokenCreate(t *testing.T) {
|
|||
// Start the server
|
||||
masterConfig := framework.NewIntegrationTestMasterConfig()
|
||||
masterConfig.GenericConfig.Authorization.Authorizer = authorizerfactory.NewAlwaysAllowAuthorizer()
|
||||
masterConfig.GenericConfig.Authentication.APIAudiences = aud
|
||||
masterConfig.GenericConfig.Authentication.Authenticator = bearertoken.New(
|
||||
serviceaccount.JWTTokenAuthenticator(
|
||||
iss,
|
||||
[]interface{}{&pk},
|
||||
serviceaccount.NewValidator(aud, serviceaccountgetter.NewGetterFromClient(gcs)),
|
||||
aud,
|
||||
serviceaccount.NewValidator(serviceaccountgetter.NewGetterFromClient(gcs)),
|
||||
),
|
||||
)
|
||||
tokenGenerator, err := serviceaccount.JWTTokenGenerator(iss, sk)
|
||||
|
|
|
@ -374,7 +374,7 @@ func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclie
|
|||
})
|
||||
serviceAccountKey, _ := rsa.GenerateKey(rand.Reader, 2048)
|
||||
serviceAccountTokenGetter := serviceaccountcontroller.NewGetterFromClient(rootClientset)
|
||||
serviceAccountTokenAuth := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, []interface{}{&serviceAccountKey.PublicKey}, serviceaccount.NewLegacyValidator(true, serviceAccountTokenGetter))
|
||||
serviceAccountTokenAuth := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, []interface{}{&serviceAccountKey.PublicKey}, nil, serviceaccount.NewLegacyValidator(true, serviceAccountTokenGetter))
|
||||
authenticator := union.New(
|
||||
bearertoken.New(rootTokenAuth),
|
||||
bearertoken.New(serviceAccountTokenAuth),
|
||||
|
|
Loading…
Reference in New Issue