Skip to content

Commit

Permalink
Merge pull request kubernetes#14700 from liggitt/kubelet_authz
Browse files Browse the repository at this point in the history
Auto commit by PR queue bot
  • Loading branch information
k8s-merge-robot committed Oct 9, 2015
2 parents a4ac5ef + 9d6b528 commit b793c3e
Show file tree
Hide file tree
Showing 8 changed files with 351 additions and 23 deletions.
18 changes: 10 additions & 8 deletions cmd/kubelet/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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 "+
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions contrib/mesos/pkg/executor/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion docs/admin/kubelet.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions pkg/auth/authorizer/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package authorizer

import (
"net/http"

"k8s.io/kubernetes/pkg/auth/user"
)

Expand Down Expand Up @@ -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
Expand Down
37 changes: 37 additions & 0 deletions pkg/kubelet/auth.go
Original file line number Diff line number Diff line change
@@ -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}
}
4 changes: 2 additions & 2 deletions pkg/kubelet/kubelet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
90 changes: 81 additions & 9 deletions pkg/kubelet/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 {
Expand All @@ -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,
Expand All @@ -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)),
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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() {
Expand All @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit b793c3e

Please sign in to comment.