func TestAuthenticateRequestError(t *testing.T) { failed := make(chan struct{}) contextMapper := genericapirequest.NewRequestContextMapper() auth := WithAuthentication( http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) { t.Errorf("unexpected call to handler") }), contextMapper, authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) { return nil, false, errors.New("failure") }), http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { close(failed) }), ) auth.ServeHTTP(httptest.NewRecorder(), &http.Request{}) <-failed empty, err := genericapirequest.IsEmpty(contextMapper) if err != nil { t.Fatalf("unexpected error: %v", err) } if !empty { t.Fatalf("contextMapper should have no stored requests: %v", contextMapper) } }
// setUp is a convience function for setting up for (most) tests. func setUp(t *testing.T) (*etcdtesting.EtcdTestServer, Config, *assert.Assertions) { etcdServer, _ := etcdtesting.NewUnsecuredEtcd3TestClientServer(t) config := NewConfig() config.PublicAddress = net.ParseIP("192.168.10.4") config.RequestContextMapper = genericapirequest.NewRequestContextMapper() config.LegacyAPIGroupPrefixes = sets.NewString("/api") config.OpenAPIConfig = DefaultOpenAPIConfig(openapigen.OpenAPIDefinitions) config.OpenAPIConfig.Info = &spec.Info{ InfoProps: spec.InfoProps{ Title: "Kubernetes", Version: "unversioned", }, } config.SwaggerConfig = DefaultSwaggerConfig() return etcdServer, *config, assert.New(t) }
// setUp is a convience function for setting up for (most) tests. func setUp(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *assert.Assertions) { server, storageConfig := etcdtesting.NewUnsecuredEtcd3TestClientServer(t) config := &Config{ GenericConfig: genericapiserver.NewConfig(), APIResourceConfigSource: DefaultAPIResourceConfigSource(), APIServerServicePort: 443, MasterCount: 1, } resourceEncoding := genericapiserver.NewDefaultResourceEncodingConfig() resourceEncoding.SetVersionEncoding(api.GroupName, api.Registry.GroupOrDie(api.GroupName).GroupVersion, schema.GroupVersion{Group: api.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(autoscaling.GroupName, *testapi.Autoscaling.GroupVersion(), schema.GroupVersion{Group: autoscaling.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(batch.GroupName, *testapi.Batch.GroupVersion(), schema.GroupVersion{Group: batch.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(apps.GroupName, *testapi.Apps.GroupVersion(), schema.GroupVersion{Group: apps.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(extensions.GroupName, *testapi.Extensions.GroupVersion(), schema.GroupVersion{Group: extensions.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(rbac.GroupName, *testapi.Rbac.GroupVersion(), schema.GroupVersion{Group: rbac.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(certificates.GroupName, *testapi.Certificates.GroupVersion(), schema.GroupVersion{Group: certificates.GroupName, Version: runtime.APIVersionInternal}) storageFactory := genericapiserver.NewDefaultStorageFactory(*storageConfig, testapi.StorageMediaType(), api.Codecs, resourceEncoding, DefaultAPIResourceConfigSource()) kubeVersion := kubeversion.Get() config.GenericConfig.Version = &kubeVersion config.StorageFactory = storageFactory config.GenericConfig.LoopbackClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: api.Codecs}} config.GenericConfig.PublicAddress = net.ParseIP("192.168.10.4") config.GenericConfig.LegacyAPIGroupPrefixes = sets.NewString("/api") config.GenericConfig.RequestContextMapper = genericapirequest.NewRequestContextMapper() config.GenericConfig.LoopbackClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: api.Codecs}} config.GenericConfig.EnableMetrics = true config.EnableCoreControllers = false config.KubeletClientConfig = kubeletclient.KubeletClientConfig{Port: 10250} config.ProxyTransport = utilnet.SetTransportDefaults(&http.Transport{ Dial: func(network, addr string) (net.Conn, error) { return nil, nil }, TLSClientConfig: &tls.Config{}, }) master, err := config.Complete().New() if err != nil { t.Fatal(err) } return master, server, *config, assert.New(t) }
func TestAuthenticateRequest(t *testing.T) { success := make(chan struct{}) contextMapper := genericapirequest.NewRequestContextMapper() auth := WithAuthentication( http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) { ctx, ok := contextMapper.Get(req) if ctx == nil || !ok { t.Errorf("no context stored on contextMapper: %#v", contextMapper) } user, ok := genericapirequest.UserFrom(ctx) if user == nil || !ok { t.Errorf("no user stored in context: %#v", ctx) } if req.Header.Get("Authorization") != "" { t.Errorf("Authorization header should be removed from request on success: %#v", req) } close(success) }), contextMapper, authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) { if req.Header.Get("Authorization") == "Something" { return &user.DefaultInfo{Name: "user"}, true, nil } return nil, false, errors.New("Authorization header is missing.") }), http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { t.Errorf("unexpected call to failed") }), ) auth.ServeHTTP(httptest.NewRecorder(), &http.Request{Header: map[string][]string{"Authorization": {"Something"}}}) <-success empty, err := genericapirequest.IsEmpty(contextMapper) if err != nil { t.Fatalf("unexpected error: %v", err) } if !empty { t.Fatalf("contextMapper should have no stored requests: %v", contextMapper) } }
// NewConfig returns a Config struct with the default values func NewConfig() *Config { config := &Config{ Serializer: api.Codecs, ReadWritePort: 6443, RequestContextMapper: apirequest.NewRequestContextMapper(), BuildHandlerChainsFunc: DefaultBuildHandlerChain, LegacyAPIGroupPrefixes: sets.NewString(DefaultLegacyAPIPrefix), HealthzChecks: []healthz.HealthzChecker{healthz.PingHealthz}, EnableIndex: true, // Default to treating watch as a long-running operation // Generic API servers have no inherent long-running subresources LongRunningFunc: genericfilters.BasicLongRunningRequestCheck(sets.NewString("watch"), sets.NewString()), } // this keeps the defaults in sync defaultOptions := options.NewServerRunOptions() // unset fields that can be overridden to avoid setting values so that we won't end up with lingering values. // TODO we probably want to run the defaults the other way. A default here drives it in the CLI flags defaultOptions.AuditLogPath = "" return config.ApplyOptions(defaultOptions) }
func TestImpersonationFilter(t *testing.T) { testCases := []struct { name string user user.Info impersonationUser string impersonationGroups []string impersonationUserExtras map[string][]string expectedUser user.Info expectedCode int }{ { name: "not-impersonating", user: &user.DefaultInfo{ Name: "tester", }, expectedUser: &user.DefaultInfo{ Name: "tester", }, expectedCode: http.StatusOK, }, { name: "impersonating-error", user: &user.DefaultInfo{ Name: "tester", }, impersonationUser: "******", expectedUser: &user.DefaultInfo{ Name: "tester", }, expectedCode: http.StatusForbidden, }, { name: "impersonating-group-without-user", user: &user.DefaultInfo{ Name: "tester", }, impersonationGroups: []string{"some-group"}, expectedUser: &user.DefaultInfo{ Name: "tester", }, expectedCode: http.StatusInternalServerError, }, { name: "impersonating-extra-without-user", user: &user.DefaultInfo{ Name: "tester", }, impersonationUserExtras: map[string][]string{"scopes": {"scope-a"}}, expectedUser: &user.DefaultInfo{ Name: "tester", }, expectedCode: http.StatusInternalServerError, }, { name: "disallowed-group", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel"}, }, impersonationUser: "******", impersonationGroups: []string{"some-group"}, expectedUser: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel"}, }, expectedCode: http.StatusForbidden, }, { name: "allowed-group", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel", "group-impersonater"}, }, impersonationUser: "******", impersonationGroups: []string{"some-group"}, expectedUser: &user.DefaultInfo{ Name: "system:admin", Groups: []string{"some-group"}, Extra: map[string][]string{}, }, expectedCode: http.StatusOK, }, { name: "disallowed-userextra-1", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel"}, }, impersonationUser: "******", impersonationGroups: []string{"some-group"}, impersonationUserExtras: map[string][]string{"scopes": {"scope-a"}}, expectedUser: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel"}, }, expectedCode: http.StatusForbidden, }, { name: "disallowed-userextra-2", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel", "extra-setter-project"}, }, impersonationUser: "******", impersonationGroups: []string{"some-group"}, impersonationUserExtras: map[string][]string{"scopes": {"scope-a"}}, expectedUser: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel", "extra-setter-project"}, }, expectedCode: http.StatusForbidden, }, { name: "disallowed-userextra-3", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel", "extra-setter-particular-scopes"}, }, impersonationUser: "******", impersonationGroups: []string{"some-group"}, impersonationUserExtras: map[string][]string{"scopes": {"scope-a", "scope-b"}}, expectedUser: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel", "extra-setter-particular-scopes"}, }, expectedCode: http.StatusForbidden, }, { name: "allowed-userextras", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel", "extra-setter-scopes"}, }, impersonationUser: "******", impersonationUserExtras: map[string][]string{"scopes": {"scope-a", "scope-b"}}, expectedUser: &user.DefaultInfo{ Name: "system:admin", Groups: []string{}, Extra: map[string][]string{"scopes": {"scope-a", "scope-b"}}, }, expectedCode: http.StatusOK, }, { name: "allowed-users-impersonation", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"regular-impersonater"}, }, impersonationUser: "******", expectedUser: &user.DefaultInfo{ Name: "tester", Groups: []string{}, Extra: map[string][]string{}, }, expectedCode: http.StatusOK, }, { name: "disallowed-impersonating", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"sa-impersonater"}, }, impersonationUser: "******", expectedUser: &user.DefaultInfo{ Name: "dev", Groups: []string{"sa-impersonater"}, }, expectedCode: http.StatusForbidden, }, { name: "allowed-sa-impersonating", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"sa-impersonater"}, Extra: map[string][]string{}, }, impersonationUser: "******", expectedUser: &user.DefaultInfo{ Name: "system:serviceaccount:foo:default", Groups: []string{"system:serviceaccounts", "system:serviceaccounts:foo"}, Extra: map[string][]string{}, }, expectedCode: http.StatusOK, }, } requestContextMapper := request.NewRequestContextMapper() var ctx request.Context var actualUser user.Info var lock sync.Mutex doNothingHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { currentCtx, _ := requestContextMapper.Get(req) user, exists := request.UserFrom(currentCtx) if !exists { actualUser = nil return } actualUser = user }) handler := func(delegate http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { defer func() { if r := recover(); r != nil { t.Errorf("Recovered %v", r) } }() lock.Lock() defer lock.Unlock() requestContextMapper.Update(req, ctx) currentCtx, _ := requestContextMapper.Get(req) user, exists := request.UserFrom(currentCtx) if !exists { actualUser = nil return } else { actualUser = user } delegate.ServeHTTP(w, req) }) }(WithImpersonation(doNothingHandler, requestContextMapper, impersonateAuthorizer{})) handler = request.WithRequestContext(handler, requestContextMapper) server := httptest.NewServer(handler) defer server.Close() for _, tc := range testCases { func() { lock.Lock() defer lock.Unlock() ctx = request.WithUser(request.NewContext(), tc.user) }() req, err := http.NewRequest("GET", server.URL, nil) if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) continue } req.Header.Add(authenticationapi.ImpersonateUserHeader, tc.impersonationUser) for _, group := range tc.impersonationGroups { req.Header.Add(authenticationapi.ImpersonateGroupHeader, group) } for extraKey, values := range tc.impersonationUserExtras { for _, value := range values { req.Header.Add(authenticationapi.ImpersonateUserExtraHeaderPrefix+extraKey, value) } } resp, err := http.DefaultClient.Do(req) if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) continue } if resp.StatusCode != tc.expectedCode { t.Errorf("%s: expected %v, actual %v", tc.name, tc.expectedCode, resp.StatusCode) continue } if !reflect.DeepEqual(actualUser, tc.expectedUser) { t.Errorf("%s: expected %#v, actual %#v", tc.name, tc.expectedUser, actualUser) continue } } }
func TestGetAuthorizerAttributes(t *testing.T) { mapper := request.NewRequestContextMapper() testcases := map[string]struct { Verb string Path string ExpectedAttributes *authorizer.AttributesRecord }{ "non-resource root": { Verb: "POST", Path: "/", ExpectedAttributes: &authorizer.AttributesRecord{ Verb: "post", Path: "/", }, }, "non-resource api prefix": { Verb: "GET", Path: "/api/", ExpectedAttributes: &authorizer.AttributesRecord{ Verb: "get", Path: "/api/", }, }, "non-resource group api prefix": { Verb: "GET", Path: "/apis/extensions/", ExpectedAttributes: &authorizer.AttributesRecord{ Verb: "get", Path: "/apis/extensions/", }, }, "resource": { Verb: "POST", Path: "/api/v1/nodes/mynode", ExpectedAttributes: &authorizer.AttributesRecord{ Verb: "create", Path: "/api/v1/nodes/mynode", ResourceRequest: true, Resource: "nodes", APIVersion: "v1", Name: "mynode", }, }, "namespaced resource": { Verb: "PUT", Path: "/api/v1/namespaces/myns/pods/mypod", ExpectedAttributes: &authorizer.AttributesRecord{ Verb: "update", Path: "/api/v1/namespaces/myns/pods/mypod", ResourceRequest: true, Namespace: "myns", Resource: "pods", APIVersion: "v1", Name: "mypod", }, }, "API group resource": { Verb: "GET", Path: "/apis/batch/v1/namespaces/myns/jobs", ExpectedAttributes: &authorizer.AttributesRecord{ Verb: "list", Path: "/apis/batch/v1/namespaces/myns/jobs", ResourceRequest: true, APIGroup: batch.GroupName, APIVersion: "v1", Namespace: "myns", Resource: "jobs", }, }, } for k, tc := range testcases { req, _ := http.NewRequest(tc.Verb, tc.Path, nil) req.RemoteAddr = "127.0.0.1" var attribs authorizer.Attributes var err error var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ctx, ok := mapper.Get(req) if !ok { responsewriters.InternalError(w, req, errors.New("no context found for request")) return } attribs, err = GetAuthorizerAttributes(ctx) }) handler = WithRequestInfo(handler, newTestRequestInfoResolver(), mapper) handler = request.WithRequestContext(handler, mapper) handler.ServeHTTP(httptest.NewRecorder(), req) if err != nil { t.Errorf("%s: unexpected error: %v", k, err) } else if !reflect.DeepEqual(attribs, tc.ExpectedAttributes) { t.Errorf("%s: expected\n\t%#v\ngot\n\t%#v", k, tc.ExpectedAttributes, attribs) } } }