Merge pull request #69916 from mikedanese/trev9

create audience unaware authenticator wrappers
pull/58/head
k8s-ci-robot 2018-11-01 13:43:56 -07:00 committed by GitHub
commit 1165d661f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 363 additions and 36 deletions

View File

@ -38,11 +38,10 @@ import (
"k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth"
"k8s.io/apiserver/plugin/pkg/authenticator/token/oidc"
"k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
_ "k8s.io/client-go/plugin/pkg/client/auth"
certutil "k8s.io/client-go/util/cert"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/serviceaccount"
_ "k8s.io/client-go/plugin/pkg/client/auth"
)
type AuthenticatorConfig struct {
@ -97,7 +96,7 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe
if err != nil {
return nil, nil, err
}
authenticators = append(authenticators, requestHeaderAuthenticator)
authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, requestHeaderAuthenticator))
}
// basic auth
@ -106,7 +105,7 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe
if err != nil {
return nil, nil, err
}
authenticators = append(authenticators, basicAuth)
authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, basicAuth))
securityDefinitions["HTTPBasic"] = &spec.SecurityScheme{
SecuritySchemeProps: spec.SecuritySchemeProps{
@ -131,14 +130,14 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe
if err != nil {
return nil, nil, err
}
tokenAuthenticators = append(tokenAuthenticators, tokenAuth)
tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, tokenAuth))
}
if len(config.ServiceAccountKeyFiles) > 0 {
serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.ServiceAccountTokenGetter)
if err != nil {
return nil, nil, err
}
tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, serviceAccountAuth))
}
if utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) && config.ServiceAccountIssuer != "" {
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuer, config.APIAudiences, config.ServiceAccountKeyFiles, config.ServiceAccountTokenGetter)
@ -150,7 +149,7 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe
if config.BootstrapToken {
if config.BootstrapTokenAuthenticator != nil {
// TODO: This can sometimes be nil because of
tokenAuthenticators = append(tokenAuthenticators, config.BootstrapTokenAuthenticator)
tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, config.BootstrapTokenAuthenticator))
}
}
// NOTE(ericchiang): Keep the OpenID Connect after Service Accounts.

View File

@ -5,7 +5,8 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"helpers.go",
"audagnostic.go",
"audiences.go",
"interfaces.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/authentication/authenticator",
@ -28,6 +29,13 @@ filegroup(
go_test(
name = "go_default_test",
srcs = ["helpers_test.go"],
srcs = [
"audagnostic_test.go",
"audiences_test.go",
],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library",
],
)

View File

@ -0,0 +1,90 @@
/*
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 authenticator
import (
"context"
"fmt"
"net/http"
)
func authenticate(ctx context.Context, implicitAuds Audiences, authenticate func() (*Response, bool, error)) (*Response, bool, error) {
targetAuds, ok := AudiencesFrom(ctx)
// We can remove this once api audiences is never empty. That will probably
// be N releases after TokenRequest is GA.
if !ok {
return authenticate()
}
auds := implicitAuds.Intersect(targetAuds)
if len(auds) == 0 {
return nil, false, nil
}
resp, ok, err := authenticate()
if err != nil || !ok {
return nil, false, err
}
if len(resp.Audiences) > 0 {
// maybe the authenticator was audience aware after all.
return nil, false, fmt.Errorf("audience agnostic authenticator wrapped an authenticator that returned audiences: %q", resp.Audiences)
}
resp.Audiences = auds
return resp, true, nil
}
type audAgnosticRequestAuthenticator struct {
implicit Audiences
delegate Request
}
var _ = Request(&audAgnosticRequestAuthenticator{})
func (a *audAgnosticRequestAuthenticator) AuthenticateRequest(req *http.Request) (*Response, bool, error) {
return authenticate(req.Context(), a.implicit, func() (*Response, bool, error) {
return a.delegate.AuthenticateRequest(req)
})
}
// WrapAudienceAgnosticRequest wraps an audience agnostic request authenticator
// to restrict its accepted audiences to a set of implicit audiences.
func WrapAudienceAgnosticRequest(implicit Audiences, delegate Request) Request {
return &audAgnosticRequestAuthenticator{
implicit: implicit,
delegate: delegate,
}
}
type audAgnosticTokenAuthenticator struct {
implicit Audiences
delegate Token
}
var _ = Token(&audAgnosticTokenAuthenticator{})
func (a *audAgnosticTokenAuthenticator) AuthenticateToken(ctx context.Context, tok string) (*Response, bool, error) {
return authenticate(ctx, a.implicit, func() (*Response, bool, error) {
return a.delegate.AuthenticateToken(ctx, tok)
})
}
// WrapAudienceAgnosticToken wraps an audience agnostic token authenticator to
// restrict its accepted audiences to a set of implicit audiences.
func WrapAudienceAgnosticToken(implicit Audiences, delegate Token) Token {
return &audAgnosticTokenAuthenticator{
implicit: implicit,
delegate: delegate,
}
}

View File

@ -0,0 +1,229 @@
/*
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 authenticator
import (
"context"
"errors"
"fmt"
"reflect"
"testing"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apiserver/pkg/authentication/user"
)
func TestAuthenticate(t *testing.T) {
type treq struct {
resp *Response
authenticated bool
err error
wantResp *Response
wantAuthenticated bool
wantErr bool
}
type taudcfg struct {
auds Audiences
implicitAuds Audiences
}
cs := []struct {
name string
taudcfgs []taudcfg
treqs []treq
}{
{
name: "good audience",
taudcfgs: []taudcfg{
{
implicitAuds: Audiences{"api"},
auds: Audiences{"api"},
},
{
implicitAuds: Audiences{"api", "other"},
auds: Audiences{"api"},
},
{
implicitAuds: Audiences{"api"},
auds: Audiences{"api", "other"},
},
{
implicitAuds: Audiences{"api", "other"},
auds: Audiences{"api", "other_other"},
},
},
treqs: []treq{
{
resp: &Response{
User: &user.DefaultInfo{
Name: "test_user",
},
},
authenticated: true,
wantResp: &Response{
User: &user.DefaultInfo{
Name: "test_user",
},
Audiences: Audiences{"api"},
},
wantAuthenticated: true,
},
{
err: errors.New("uhoh"),
wantErr: true,
},
{
authenticated: false,
wantAuthenticated: false,
},
},
},
{
name: "multiple good audiences",
taudcfgs: []taudcfg{
{
implicitAuds: Audiences{"api", "other_api"},
auds: Audiences{"api", "other_api"},
},
{
implicitAuds: Audiences{"api", "other_api", "other"},
auds: Audiences{"api", "other_api"},
},
{
implicitAuds: Audiences{"api", "other_api"},
auds: Audiences{"api", "other_api", "other"},
},
{
implicitAuds: Audiences{"api", "other_api", "other"},
auds: Audiences{"api", "other_api", "other_other"},
},
},
treqs: []treq{
{
resp: &Response{
User: &user.DefaultInfo{
Name: "test_user",
},
},
authenticated: true,
wantResp: &Response{
User: &user.DefaultInfo{
Name: "test_user",
},
Audiences: Audiences{"api", "other_api"},
},
wantAuthenticated: true,
},
{
err: errors.New("uhoh"),
wantErr: true,
},
{
authenticated: false,
wantAuthenticated: false,
},
},
},
{
name: "bad audience(s)",
taudcfgs: []taudcfg{
{
implicitAuds: Audiences{"api"},
auds: Audiences{"other_api"},
},
{
implicitAuds: Audiences{},
auds: Audiences{"other_api"},
},
{
implicitAuds: Audiences{"api"},
auds: Audiences{},
},
{
implicitAuds: Audiences{"api", "other"},
auds: Audiences{},
},
{
implicitAuds: Audiences{},
auds: Audiences{"api", "other"},
},
},
treqs: []treq{
{
resp: &Response{
User: &user.DefaultInfo{
Name: "test_user",
},
},
authenticated: true,
wantAuthenticated: false,
},
{
err: errors.New("uhoh"),
wantAuthenticated: false,
},
{
authenticated: false,
wantAuthenticated: false,
},
},
},
}
for _, c := range cs {
t.Run(c.name, func(t *testing.T) {
for _, taudcfg := range c.taudcfgs {
for _, treq := range c.treqs {
t.Run(fmt.Sprintf("auds=%q,implicit=%q", taudcfg.auds, taudcfg.implicitAuds), func(t *testing.T) {
ctx := context.Background()
ctx = WithAudiences(ctx, taudcfg.auds)
resp, ok, err := authenticate(ctx, taudcfg.implicitAuds, func() (*Response, bool, error) {
if treq.resp != nil {
resp := *treq.resp
return &resp, treq.authenticated, treq.err
}
return nil, treq.authenticated, treq.err
})
if got, want := (err != nil), treq.wantErr; got != want {
t.Errorf("Unexpected error. got=%v, want=%v, err=%v", got, want, err)
}
if got, want := ok, treq.wantAuthenticated; got != want {
t.Errorf("Unexpected authentication. got=%v, want=%v", got, want)
}
if got, want := resp, treq.wantResp; !reflect.DeepEqual(got, want) {
t.Errorf("Unexpected response. diff:\n%v", diff.ObjectGoPrintDiff(got, want))
}
})
}
}
})
}
}

View File

@ -16,9 +16,30 @@ limitations under the License.
package authenticator
import "context"
// Audiences is a container for the Audiences of a token.
type Audiences []string
// The key type is unexported to prevent collisions
type key int
const (
// audiencesKey is the context key for request audiences.
audiencesKey key = iota
)
// WithAudiences returns a context that stores a request's expected audiences.
func WithAudiences(ctx context.Context, auds Audiences) context.Context {
return context.WithValue(ctx, audiencesKey, auds)
}
// AudiencesFrom returns a request's expected audiences stored in the request context.
func AudiencesFrom(ctx context.Context) (Audiences, bool) {
auds, ok := ctx.Value(audiencesKey).(Audiences)
return auds, ok
}
// Has checks if Audiences contains a specific audiences.
func (a Audiences) Has(taud string) bool {
for _, aud := range a {

View File

@ -25,7 +25,6 @@ go_library(
deps = [
"//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
],
)

View File

@ -21,7 +21,6 @@ import (
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/request"
)
const (
@ -32,7 +31,7 @@ const (
func NewAuthenticator() authenticator.Request {
return authenticator.RequestFunc(func(req *http.Request) (*authenticator.Response, bool, error) {
auds, _ := request.AudiencesFrom(req.Context())
auds, _ := authenticator.AudiencesFrom(req.Context())
return &authenticator.Response{
User: &user.DefaultInfo{
Name: anonymousUser,

View File

@ -17,7 +17,6 @@ go_test(
"//staging/src/k8s.io/apimachinery/pkg/util/clock:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/github.com/pborman/uuid:go_default_library",
],
)
@ -35,7 +34,6 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/util/cache:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/clock:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
],
)

View File

@ -23,7 +23,6 @@ import (
utilclock "k8s.io/apimachinery/pkg/util/clock"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/endpoints/request"
)
// cacheRecord holds the three return values of the authenticator.Token AuthenticateToken method
@ -67,7 +66,7 @@ func newWithClock(authenticator authenticator.Token, successTTL, failureTTL time
// AuthenticateToken implements authenticator.Token
func (a *cachedTokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
auds, _ := request.AudiencesFrom(ctx)
auds, _ := authenticator.AudiencesFrom(ctx)
key := keyFunc(auds, token)
if record, ok := a.cache.get(key); ok {

View File

@ -25,7 +25,6 @@ import (
utilclock "k8s.io/apimachinery/pkg/util/clock"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/request"
)
func TestCachedTokenAuthenticator(t *testing.T) {
@ -109,7 +108,7 @@ func TestCachedTokenAuthenticator(t *testing.T) {
func TestCachedTokenAuthenticatorWithAudiences(t *testing.T) {
resultUsers := make(map[string]user.Info)
fakeAuth := authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
auds, _ := request.AudiencesFrom(ctx)
auds, _ := authenticator.AudiencesFrom(ctx)
return &authenticator.Response{User: resultUsers[auds[0]+token]}, true, nil
})
fakeClock := utilclock.NewFakeClock(time.Now())
@ -119,10 +118,10 @@ func TestCachedTokenAuthenticatorWithAudiences(t *testing.T) {
resultUsers["audAusertoken1"] = &user.DefaultInfo{Name: "user1"}
resultUsers["audBusertoken1"] = &user.DefaultInfo{Name: "user1-different"}
if u, ok, _ := a.AuthenticateToken(request.WithAudiences(context.Background(), []string{"audA"}), "usertoken1"); !ok || u.User.GetName() != "user1" {
if u, ok, _ := a.AuthenticateToken(authenticator.WithAudiences(context.Background(), []string{"audA"}), "usertoken1"); !ok || u.User.GetName() != "user1" {
t.Errorf("Expected user1")
}
if u, ok, _ := a.AuthenticateToken(request.WithAudiences(context.Background(), []string{"audB"}), "usertoken1"); !ok || u.User.GetName() != "user1-different" {
if u, ok, _ := a.AuthenticateToken(authenticator.WithAudiences(context.Background(), []string{"audB"}), "usertoken1"); !ok || u.User.GetName() != "user1-different" {
t.Errorf("Expected user1-different")
}
}

View File

@ -57,7 +57,7 @@ func WithAuthentication(handler http.Handler, auth authenticator.Request, failed
}
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if len(apiAuds) > 0 {
req = req.WithContext(genericapirequest.WithAudiences(req.Context(), apiAuds))
req = req.WithContext(authenticator.WithAudiences(req.Context(), apiAuds))
}
resp, ok, err := auth.AuthenticateRequest(req)
if err != nil || !ok {

View File

@ -35,7 +35,6 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/apis/audit:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
],

View File

@ -21,7 +21,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/apis/audit"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
)
@ -95,14 +94,3 @@ func AuditEventFrom(ctx context.Context) *audit.Event {
ev, _ := ctx.Value(auditKey).(*audit.Event)
return ev
}
// WithAudiences returns a context that stores a request's expected audiences.
func WithAudiences(ctx context.Context, auds authenticator.Audiences) context.Context {
return context.WithValue(ctx, audiencesKey, auds)
}
// AudiencesFrom returns a request's expected audiences stored in the request context.
func AudiencesFrom(ctx context.Context) (authenticator.Audiences, bool) {
auds, ok := ctx.Value(audiencesKey).(authenticator.Audiences)
return auds, ok
}

View File

@ -25,7 +25,6 @@ import (
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/client-go/rest"
)
@ -80,7 +79,7 @@ func (s *DeprecatedInsecureServingInfo) NewLoopbackClientConfig() (*rest.Config,
type InsecureSuperuser struct{}
func (InsecureSuperuser) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
auds, _ := request.AudiencesFrom(req.Context())
auds, _ := authenticator.AudiencesFrom(req.Context())
return &authenticator.Response{
User: &user.DefaultInfo{
Name: "system:unsecured",