mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 15:25:57 +00:00
Merge pull request #14700 from liggitt/kubelet_authz
Auto commit by PR queue bot
This commit is contained in:
commit
b793c3edf1
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
37
pkg/kubelet/auth.go
Normal file
37
pkg/kubelet/auth.go
Normal file
@ -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}
|
||||
}
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user