From 9d6b52881d835cd76aa5fe2ce92cf578dd5900c9 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Mon, 28 Sep 2015 23:32:20 -0400 Subject: [PATCH] Add authentication/authorization interfaces to kubelet, always include /metrics with /stats --- cmd/kubelet/app/server.go | 18 +- contrib/mesos/pkg/executor/service/service.go | 4 +- docs/admin/kubelet.md | 2 +- pkg/auth/authorizer/interfaces.go | 7 + pkg/kubelet/auth.go | 37 +++ pkg/kubelet/kubelet.go | 4 +- pkg/kubelet/server.go | 90 +++++++- pkg/kubelet/server_test.go | 212 +++++++++++++++++- 8 files changed, 351 insertions(+), 23 deletions(-) create mode 100644 pkg/kubelet/auth.go diff --git a/cmd/kubelet/app/server.go b/cmd/kubelet/app/server.go index cd4d0cd4fc373..0629df0e15c92 100644 --- a/cmd/kubelet/app/server.go +++ b/cmd/kubelet/app/server.go @@ -147,8 +147,8 @@ type KubeletServer struct { type KubeletBootstrap interface { BirthCry() StartGarbageCollection() - ListenAndServe(net.IP, uint, *kubelet.TLSOptions, bool) - ListenAndServeReadOnly(net.IP, uint) + ListenAndServe(address net.IP, port uint, tlsOptions *kubelet.TLSOptions, auth kubelet.AuthInterface, enableDebuggingHandlers bool) + ListenAndServeReadOnly(address net.IP, port uint) Run(<-chan kubeletTypes.PodUpdate) RunOnce(<-chan kubeletTypes.PodUpdate) ([]kubelet.RunPodResult, error) } @@ -216,7 +216,7 @@ func (s *KubeletServer) AddFlags(fs *pflag.FlagSet) { fs.BoolVar(&s.EnableServer, "enable-server", s.EnableServer, "Enable the Kubelet's server") fs.IPVar(&s.Address, "address", s.Address, "The IP address for the Kubelet to serve on (set to 0.0.0.0 for all interfaces)") fs.UintVar(&s.Port, "port", s.Port, "The port for the Kubelet to serve on. Note that \"kubectl logs\" will not work if you set this flag.") // see #9325 - fs.UintVar(&s.ReadOnlyPort, "read-only-port", s.ReadOnlyPort, "The read-only port for the Kubelet to serve on (set to 0 to disable)") + fs.UintVar(&s.ReadOnlyPort, "read-only-port", s.ReadOnlyPort, "The read-only port for the Kubelet to serve on with no authentication/authorization (set to 0 to disable)") fs.StringVar(&s.TLSCertFile, "tls-cert-file", s.TLSCertFile, ""+ "File containing x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert). "+ "If --tls-cert-file and --tls-private-key-file are not provided, a self-signed certificate and key "+ @@ -281,9 +281,9 @@ func (s *KubeletServer) AddFlags(fs *pflag.FlagSet) { fs.Uint64Var(&s.MaxOpenFiles, "max-open-files", 1000000, "Number of files that can be opened by Kubelet process. [default=1000000]") } -// KubeletConfig returns a KubeletConfig suitable for being run, or an error if the server setup -// is not valid. It will not start any background processes. -func (s *KubeletServer) KubeletConfig() (*KubeletConfig, error) { +// UnsecuredKubeletConfig returns a KubeletConfig suitable for being run, or an error if the server setup +// is not valid. It will not start any background processes, and does not include authentication/authorization +func (s *KubeletServer) UnsecuredKubeletConfig() (*KubeletConfig, error) { hostNetworkSources, err := kubeletTypes.GetValidatedSources(strings.Split(s.HostNetworkSources, ",")) if err != nil { return nil, err @@ -345,6 +345,7 @@ func (s *KubeletServer) KubeletConfig() (*KubeletConfig, error) { return &KubeletConfig{ Address: s.Address, AllowPrivileged: s.AllowPrivileged, + Auth: nil, // default does not enforce auth[nz] CAdvisorInterface: nil, // launches background processes, not set here CgroupRoot: s.CgroupRoot, Cloud: nil, // cloud provider might start background processes @@ -413,7 +414,7 @@ func (s *KubeletServer) KubeletConfig() (*KubeletConfig, error) { // will be ignored. func (s *KubeletServer) Run(kcfg *KubeletConfig) error { if kcfg == nil { - cfg, err := s.KubeletConfig() + cfg, err := s.UnsecuredKubeletConfig() if err != nil { return err } @@ -747,7 +748,7 @@ func startKubelet(k KubeletBootstrap, podCfg *config.PodConfig, kc *KubeletConfi // start the kubelet server if kc.EnableServer { go util.Until(func() { - k.ListenAndServe(kc.Address, kc.Port, kc.TLSOptions, kc.EnableDebuggingHandlers) + k.ListenAndServe(kc.Address, kc.Port, kc.TLSOptions, kc.Auth, kc.EnableDebuggingHandlers) }, 0, util.NeverStop) } if kc.ReadOnlyPort > 0 { @@ -784,6 +785,7 @@ func makePodSourceConfig(kc *KubeletConfig) *config.PodConfig { type KubeletConfig struct { Address net.IP AllowPrivileged bool + Auth kubelet.AuthInterface Builder KubeletBuilder CAdvisorInterface cadvisor.Interface CgroupRoot string diff --git a/contrib/mesos/pkg/executor/service/service.go b/contrib/mesos/pkg/executor/service/service.go index c473e0a1b14c9..4e3180b4548cd 100644 --- a/contrib/mesos/pkg/executor/service/service.go +++ b/contrib/mesos/pkg/executor/service/service.go @@ -433,7 +433,7 @@ type kubeletExecutor struct { clientConfig *client.Config } -func (kl *kubeletExecutor) ListenAndServe(address net.IP, port uint, tlsOptions *kubelet.TLSOptions, enableDebuggingHandlers bool) { +func (kl *kubeletExecutor) ListenAndServe(address net.IP, port uint, tlsOptions *kubelet.TLSOptions, auth kubelet.AuthInterface, enableDebuggingHandlers bool) { // this func could be called many times, depending how often the HTTP server crashes, // so only execute certain initialization procs once kl.initialize.Do(func() { @@ -445,7 +445,7 @@ func (kl *kubeletExecutor) ListenAndServe(address net.IP, port uint, tlsOptions }() }) log.Infof("Starting kubelet server...") - kubelet.ListenAndServeKubeletServer(kl, address, port, tlsOptions, enableDebuggingHandlers) + kubelet.ListenAndServeKubeletServer(kl, address, port, tlsOptions, auth, enableDebuggingHandlers) } // runs the main kubelet loop, closing the kubeletFinished chan when the loop exits. diff --git a/docs/admin/kubelet.md b/docs/admin/kubelet.md index 6a7026c1af50e..1580a31539c89 100644 --- a/docs/admin/kubelet.md +++ b/docs/admin/kubelet.md @@ -104,7 +104,7 @@ HTTP server: The kubelet can also listen for HTTP and respond to a simple API --pod-cidr="": The CIDR to use for pod IP addresses, only used in standalone mode. In cluster mode, this is obtained from the master. --pod-infra-container-image="": The image whose network/ipc namespaces containers in each pod will use. --port=0: The port for the Kubelet to serve on. Note that "kubectl logs" will not work if you set this flag. - --read-only-port=0: The read-only port for the Kubelet to serve on (set to 0 to disable) + --read-only-port=0: The read-only port for the Kubelet to serve on with no authentication/authorization (set to 0 to disable) --really-crash-for-testing=false: If true, when panics occur crash. Intended for testing. --register-node=false: Register the node with the apiserver (defaults to true if --api-server is set) --registry-burst=0: Maximum size of a bursty pulls, temporarily allows pulls to burst to this number, while still not exceeding registry-qps. Only used if --registry-qps > 0 diff --git a/pkg/auth/authorizer/interfaces.go b/pkg/auth/authorizer/interfaces.go index 98b6ad2094a22..eadda02bf2834 100644 --- a/pkg/auth/authorizer/interfaces.go +++ b/pkg/auth/authorizer/interfaces.go @@ -17,6 +17,8 @@ limitations under the License. package authorizer import ( + "net/http" + "k8s.io/kubernetes/pkg/auth/user" ) @@ -63,6 +65,11 @@ func (f AuthorizerFunc) Authorize(a Attributes) error { return f(a) } +// RequestAttributesGetter provides a function that extracts Attributes from an http.Request +type RequestAttributesGetter interface { + GetRequestAttributes(user.Info, *http.Request) Attributes +} + // AttributesRecord implements Attributes interface. type AttributesRecord struct { User user.Info diff --git a/pkg/kubelet/auth.go b/pkg/kubelet/auth.go new file mode 100644 index 0000000000000..4dbd0fda21f3a --- /dev/null +++ b/pkg/kubelet/auth.go @@ -0,0 +1,37 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "k8s.io/kubernetes/pkg/auth/authenticator" + "k8s.io/kubernetes/pkg/auth/authorizer" +) + +// KubeletAuth implements AuthInterface +type KubeletAuth struct { + // authenticator identifies the user for requests to the Kubelet API + authenticator.Request + // authorizerAttributeGetter builds authorization.Attributes for a request to the Kubelet API + authorizer.RequestAttributesGetter + // authorizer determines whether a given authorization.Attributes is allowed + authorizer.Authorizer +} + +// NewKubeletAuth returns a kubelet.AuthInterface composed of the given authenticator, attribute getter, and authorizer +func NewKubeletAuth(authenticator authenticator.Request, authorizerAttributeGetter authorizer.RequestAttributesGetter, authorizer authorizer.Authorizer) AuthInterface { + return &KubeletAuth{authenticator, authorizerAttributeGetter, authorizer} +} diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index ff1b3cbca46fe..d1846618fb6c7 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -2786,8 +2786,8 @@ func (kl *Kubelet) GetCachedMachineInfo() (*cadvisorApi.MachineInfo, error) { return kl.machineInfo, nil } -func (kl *Kubelet) ListenAndServe(address net.IP, port uint, tlsOptions *TLSOptions, enableDebuggingHandlers bool) { - ListenAndServeKubeletServer(kl, address, port, tlsOptions, enableDebuggingHandlers) +func (kl *Kubelet) ListenAndServe(address net.IP, port uint, tlsOptions *TLSOptions, auth AuthInterface, enableDebuggingHandlers bool) { + ListenAndServeKubeletServer(kl, address, port, tlsOptions, auth, enableDebuggingHandlers) } func (kl *Kubelet) ListenAndServeReadOnly(address net.IP, port uint) { diff --git a/pkg/kubelet/server.go b/pkg/kubelet/server.go index 32c6cb3dbb9d0..849a24a187f33 100644 --- a/pkg/kubelet/server.go +++ b/pkg/kubelet/server.go @@ -35,12 +35,15 @@ import ( "github.com/golang/glog" cadvisorApi "github.com/google/cadvisor/info/v1" "github.com/prometheus/client_golang/prometheus" + "k8s.io/kubernetes/pkg/api" apierrs "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/latest" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/auth/authenticator" + "k8s.io/kubernetes/pkg/auth/authorizer" "k8s.io/kubernetes/pkg/healthz" "k8s.io/kubernetes/pkg/httplog" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" @@ -54,8 +57,9 @@ import ( // Server is a http.Handler which exposes kubelet functionality over HTTP. type Server struct { + auth AuthInterface host HostInterface - restfulCont *restful.Container + restfulCont containerInterface } type TLSOptions struct { @@ -64,10 +68,38 @@ type TLSOptions struct { KeyFile string } +// containerInterface defines the restful.Container functions used on the root container +type containerInterface interface { + Add(service *restful.WebService) *restful.Container + Handle(path string, handler http.Handler) + Filter(filter restful.FilterFunction) + ServeHTTP(w http.ResponseWriter, r *http.Request) + RegisteredWebServices() []*restful.WebService + + // RegisteredHandlePaths returns the paths of handlers registered directly with the container (non-web-services) + // Used to test filters are being applied on non-web-service handlers + RegisteredHandlePaths() []string +} + +// filteringContainer delegates all Handle(...) calls to Container.HandleWithFilter(...), +// so we can ensure restful.FilterFunctions are used for all handlers +type filteringContainer struct { + *restful.Container + registeredHandlePaths []string +} + +func (a *filteringContainer) Handle(path string, handler http.Handler) { + a.HandleWithFilter(path, handler) + a.registeredHandlePaths = append(a.registeredHandlePaths, path) +} +func (a *filteringContainer) RegisteredHandlePaths() []string { + return a.registeredHandlePaths +} + // ListenAndServeKubeletServer initializes a server to respond to HTTP network requests on the Kubelet. -func ListenAndServeKubeletServer(host HostInterface, address net.IP, port uint, tlsOptions *TLSOptions, enableDebuggingHandlers bool) { +func ListenAndServeKubeletServer(host HostInterface, address net.IP, port uint, tlsOptions *TLSOptions, auth AuthInterface, enableDebuggingHandlers bool) { glog.Infof("Starting to listen on %s:%d", address, port) - handler := NewServer(host, enableDebuggingHandlers) + handler := NewServer(host, auth, enableDebuggingHandlers) s := &http.Server{ Addr: net.JoinHostPort(address.String(), strconv.FormatUint(uint64(port), 10)), Handler: &handler, @@ -84,8 +116,7 @@ func ListenAndServeKubeletServer(host HostInterface, address net.IP, port uint, // ListenAndServeKubeletReadOnlyServer initializes a server to respond to HTTP network requests on the Kubelet. func ListenAndServeKubeletReadOnlyServer(host HostInterface, address net.IP, port uint) { glog.V(1).Infof("Starting to listen read-only on %s:%d", address, port) - s := NewServer(host, false) - s.restfulCont.Handle("/metrics", prometheus.Handler()) + s := NewServer(host, nil, false) server := &http.Server{ Addr: net.JoinHostPort(address.String(), strconv.FormatUint(uint64(port), 10)), @@ -95,6 +126,13 @@ func ListenAndServeKubeletReadOnlyServer(host HostInterface, address net.IP, por glog.Fatal(server.ListenAndServe()) } +// AuthInterface contains all methods required by the auth filters +type AuthInterface interface { + authenticator.Request + authorizer.RequestAttributesGetter + authorizer.Authorizer +} + // HostInterface contains all the kubelet methods required by the server. // For testablitiy. type HostInterface interface { @@ -118,10 +156,14 @@ type HostInterface interface { } // NewServer initializes and configures a kubelet.Server object to handle HTTP requests. -func NewServer(host HostInterface, enableDebuggingHandlers bool) Server { +func NewServer(host HostInterface, auth AuthInterface, enableDebuggingHandlers bool) Server { server := Server{ host: host, - restfulCont: restful.NewContainer(), + auth: auth, + restfulCont: &filteringContainer{Container: restful.NewContainer()}, + } + if auth != nil { + server.InstallAuthFilter() } server.InstallDefaultHandlers() if enableDebuggingHandlers { @@ -130,6 +172,37 @@ func NewServer(host HostInterface, enableDebuggingHandlers bool) Server { return server } +// InstallAuthFilter installs authentication filters with the restful Container. +func (s *Server) InstallAuthFilter() { + s.restfulCont.Filter(func(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { + // Authenticate + u, ok, err := s.auth.AuthenticateRequest(req.Request) + if err != nil { + glog.Errorf("Unable to authenticate the request due to an error: %v", err) + resp.WriteErrorString(http.StatusUnauthorized, "Unauthorized") + return + } + if !ok { + resp.WriteErrorString(http.StatusUnauthorized, "Unauthorized") + return + } + + // Get authorization attributes + attrs := s.auth.GetRequestAttributes(u, req.Request) + + // Authorize + if err := s.auth.Authorize(attrs); err != nil { + msg := fmt.Sprintf("Forbidden (user=%s, verb=%s, namespace=%s, resource=%s)", u.GetName(), attrs.GetVerb(), attrs.GetNamespace(), attrs.GetResource()) + glog.V(2).Info(msg) + resp.WriteErrorString(http.StatusForbidden, msg) + return + } + + // Continue + chain.ProcessFilter(req, resp) + }) +} + // InstallDefaultHandlers registers the default set of supported HTTP request // patterns with the restful Container. func (s *Server) InstallDefaultHandlers() { @@ -149,6 +222,7 @@ func (s *Server) InstallDefaultHandlers() { s.restfulCont.Add(ws) s.restfulCont.Handle("/stats/", &httpHandler{f: s.handleStats}) + s.restfulCont.Handle("/metrics", prometheus.Handler()) ws = new(restful.WebService) ws. @@ -227,8 +301,6 @@ func (s *Server) InstallDebuggingHandlers() { Operation("getContainerLogs")) s.restfulCont.Add(ws) - s.restfulCont.Handle("/metrics", prometheus.Handler()) - handlePprofEndpoint := func(req *restful.Request, resp *restful.Response) { name := strings.TrimPrefix(req.Request.URL.Path, pprofBasePath) switch name { diff --git a/pkg/kubelet/server_test.go b/pkg/kubelet/server_test.go index 01f4e78e5deb5..9e151c01889b7 100644 --- a/pkg/kubelet/server_test.go +++ b/pkg/kubelet/server_test.go @@ -19,6 +19,7 @@ package kubelet import ( "bytes" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -35,12 +36,15 @@ import ( cadvisorApi "github.com/google/cadvisor/info/v1" "k8s.io/kubernetes/pkg/api" apierrs "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/auth/authorizer" + "k8s.io/kubernetes/pkg/auth/user" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/dockertools" kubeletTypes "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/util/httpstream" "k8s.io/kubernetes/pkg/util/httpstream/spdy" + "k8s.io/kubernetes/pkg/util/sets" ) type fakeKubelet struct { @@ -131,15 +135,38 @@ func (fk *fakeKubelet) StreamingConnectionIdleTimeout() time.Duration { return fk.streamingConnectionIdleTimeoutFunc() } +type fakeAuth struct { + authenticateFunc func(*http.Request) (user.Info, bool, error) + attributesFunc func(user.Info, *http.Request) authorizer.Attributes + authorizeFunc func(authorizer.Attributes) (err error) +} + +func (f *fakeAuth) AuthenticateRequest(req *http.Request) (user.Info, bool, error) { + return f.authenticateFunc(req) +} +func (f *fakeAuth) GetRequestAttributes(u user.Info, req *http.Request) authorizer.Attributes { + return f.attributesFunc(u, req) +} +func (f *fakeAuth) Authorize(a authorizer.Attributes) (err error) { + return f.authorizeFunc(a) +} + type serverTestFramework struct { serverUnderTest *Server fakeKubelet *fakeKubelet + fakeAuth *fakeAuth testHTTPServer *httptest.Server } func newServerTest() *serverTestFramework { fw := &serverTestFramework{} fw.fakeKubelet = &fakeKubelet{ + containerVersionFunc: func() (kubecontainer.Version, error) { + return dockertools.NewVersion("1.15") + }, + hostnameFunc: func() string { + return "127.0.0.1" + }, podByNameFunc: func(namespace, name string) (*api.Pod, bool) { return &api.Pod{ ObjectMeta: api.ObjectMeta{ @@ -149,7 +176,18 @@ func newServerTest() *serverTestFramework { }, true }, } - server := NewServer(fw.fakeKubelet, true) + fw.fakeAuth = &fakeAuth{ + authenticateFunc: func(req *http.Request) (user.Info, bool, error) { + return &user.DefaultInfo{Name: "test"}, true, nil + }, + attributesFunc: func(u user.Info, req *http.Request) authorizer.Attributes { + return &authorizer.AttributesRecord{User: u} + }, + authorizeFunc: func(a authorizer.Attributes) (err error) { + return nil + }, + } + server := NewServer(fw.fakeKubelet, fw.fakeAuth, true) fw.serverUnderTest = &server fw.testHTTPServer = httptest.NewServer(fw.serverUnderTest) return fw @@ -502,6 +540,178 @@ func assertHealthFails(t *testing.T, httpURL string, expectedErrorCode int) { } } +type authTestCase struct { + Method string + Path string +} + +func TestAuthFilters(t *testing.T) { + fw := newServerTest() + + testcases := []authTestCase{} + + // This is a sanity check that the Handle->HandleWithFilter() delegation is working + // Ideally, these would move to registered web services and this list would get shorter + expectedPaths := []string{"/healthz", "/stats/", "/metrics"} + paths := sets.NewString(fw.serverUnderTest.restfulCont.RegisteredHandlePaths()...) + for _, expectedPath := range expectedPaths { + if !paths.Has(expectedPath) { + t.Errorf("Expected registered handle path %s was missing", expectedPath) + } + } + + // Test all the non-web-service handlers + for _, path := range fw.serverUnderTest.restfulCont.RegisteredHandlePaths() { + testcases = append(testcases, authTestCase{"GET", path}) + testcases = append(testcases, authTestCase{"POST", path}) + // Test subpaths for directory handlers + if strings.HasSuffix(path, "/") { + testcases = append(testcases, authTestCase{"GET", path + "foo"}) + testcases = append(testcases, authTestCase{"POST", path + "foo"}) + } + } + + // Test all the generated web-service paths + for _, ws := range fw.serverUnderTest.restfulCont.RegisteredWebServices() { + for _, r := range ws.Routes() { + testcases = append(testcases, authTestCase{r.Method, r.Path}) + } + } + + for _, tc := range testcases { + var ( + expectedUser = &user.DefaultInfo{Name: "test"} + expectedAttributes = &authorizer.AttributesRecord{User: expectedUser} + + calledAuthenticate = false + calledAuthorize = false + calledAttributes = false + ) + + fw.fakeAuth.authenticateFunc = func(req *http.Request) (user.Info, bool, error) { + calledAuthenticate = true + return expectedUser, true, nil + } + fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes { + calledAttributes = true + if u != expectedUser { + t.Fatalf("%s: expected user %v, got %v", tc.Path, expectedUser, u) + } + return expectedAttributes + } + fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (err error) { + calledAuthorize = true + if a != expectedAttributes { + t.Fatalf("%s: expected attributes %v, got %v", tc.Path, expectedAttributes, a) + } + return errors.New("Forbidden") + } + + req, err := http.NewRequest(tc.Method, fw.testHTTPServer.URL+tc.Path, nil) + if err != nil { + t.Errorf("%s: unexpected error: %v", tc.Path, err) + continue + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Errorf("%s: unexpected error: %v", tc.Path, err) + continue + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusForbidden { + t.Errorf("%s: unexpected status code %d", tc.Path, resp.StatusCode) + continue + } + + if !calledAuthenticate { + t.Errorf("%s: Authenticate was not called", tc.Path) + continue + } + if !calledAttributes { + t.Errorf("%s: Attributes were not called", tc.Path) + continue + } + if !calledAuthorize { + t.Errorf("%s: Authorize was not called", tc.Path) + continue + } + } +} + +func TestAuthenticationFailure(t *testing.T) { + var ( + expectedUser = &user.DefaultInfo{Name: "test"} + expectedAttributes = &authorizer.AttributesRecord{User: expectedUser} + + calledAuthenticate = false + calledAuthorize = false + calledAttributes = false + ) + + fw := newServerTest() + fw.fakeAuth.authenticateFunc = func(req *http.Request) (user.Info, bool, error) { + calledAuthenticate = true + return nil, false, nil + } + fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes { + calledAttributes = true + return expectedAttributes + } + fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (err error) { + calledAuthorize = true + return errors.New("not allowed") + } + + assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusUnauthorized) + + if !calledAuthenticate { + t.Fatalf("Authenticate was not called") + } + if calledAttributes { + t.Fatalf("Attributes was called unexpectedly") + } + if calledAuthorize { + t.Fatalf("Authorize was called unexpectedly") + } +} + +func TestAuthorizationSuccess(t *testing.T) { + var ( + expectedUser = &user.DefaultInfo{Name: "test"} + expectedAttributes = &authorizer.AttributesRecord{User: expectedUser} + + calledAuthenticate = false + calledAuthorize = false + calledAttributes = false + ) + + fw := newServerTest() + fw.fakeAuth.authenticateFunc = func(req *http.Request) (user.Info, bool, error) { + calledAuthenticate = true + return expectedUser, true, nil + } + fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes { + calledAttributes = true + return expectedAttributes + } + fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (err error) { + calledAuthorize = true + return nil + } + + assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz") + + if !calledAuthenticate { + t.Fatalf("Authenticate was not called") + } + if !calledAttributes { + t.Fatalf("Attributes were not called") + } + if !calledAuthorize { + t.Fatalf("Authorize was not called") + } +} + func TestSyncLoopCheck(t *testing.T) { fw := newServerTest() fw.fakeKubelet.containerVersionFunc = func() (kubecontainer.Version, error) {