Merge pull request #34381 from liggitt/kubelet-auth
Automatic merge from submit-queue kubelet authn/authz Implements https://github.com/kubernetes/kubernetes/blob/master/docs/proposals/kubelet-auth.md Part of [Authenticated/Authorized access to kubelet API](https://github.com/kubernetes/features/issues/89) feature
This commit is contained in:
@@ -13,6 +13,7 @@ load(
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"auth.go",
|
||||
"bootstrap.go",
|
||||
"plugins.go",
|
||||
"server.go",
|
||||
@@ -24,9 +25,15 @@ go_library(
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/apis/componentconfig:go_default_library",
|
||||
"//pkg/apis/componentconfig/v1alpha1:go_default_library",
|
||||
"//pkg/auth/authenticator:go_default_library",
|
||||
"//pkg/auth/authenticator/bearertoken:go_default_library",
|
||||
"//pkg/auth/authorizer:go_default_library",
|
||||
"//pkg/auth/group:go_default_library",
|
||||
"//pkg/capabilities:go_default_library",
|
||||
"//pkg/client/chaosclient:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/typed/authentication/unversioned:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/typed/certificates/unversioned:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/typed/core/unversioned:go_default_library",
|
||||
"//pkg/client/record:go_default_library",
|
||||
@@ -39,6 +46,7 @@ go_library(
|
||||
"//pkg/credentialprovider:go_default_library",
|
||||
"//pkg/credentialprovider/aws:go_default_library",
|
||||
"//pkg/credentialprovider/gcp:go_default_library",
|
||||
"//pkg/genericapiserver/authorizer:go_default_library",
|
||||
"//pkg/healthz:go_default_library",
|
||||
"//pkg/kubelet:go_default_library",
|
||||
"//pkg/kubelet/cadvisor:go_default_library",
|
||||
@@ -89,6 +97,11 @@ go_library(
|
||||
"//pkg/volume/rbd:go_default_library",
|
||||
"//pkg/volume/secret:go_default_library",
|
||||
"//pkg/volume/vsphere_volume:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/request/anonymous:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/request/union:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/request/x509:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/token/webhook:go_default_library",
|
||||
"//plugin/pkg/auth/authorizer/webhook:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:github.com/spf13/cobra",
|
||||
"//vendor:github.com/spf13/pflag",
|
||||
|
131
cmd/kubelet/app/auth.go
Normal file
131
cmd/kubelet/app/auth.go
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
Copyright 2016 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 app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apis/componentconfig"
|
||||
"k8s.io/kubernetes/pkg/auth/authenticator"
|
||||
"k8s.io/kubernetes/pkg/auth/authenticator/bearertoken"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
"k8s.io/kubernetes/pkg/auth/group"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
authenticationclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/authentication/unversioned"
|
||||
authorizationclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/authorization/unversioned"
|
||||
alwaysallowauthorizer "k8s.io/kubernetes/pkg/genericapiserver/authorizer"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server"
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/util/cert"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/anonymous"
|
||||
unionauth "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/union"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/x509"
|
||||
webhooktoken "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/webhook"
|
||||
webhooksar "k8s.io/kubernetes/plugin/pkg/auth/authorizer/webhook"
|
||||
)
|
||||
|
||||
func buildAuth(nodeName types.NodeName, client internalclientset.Interface, config componentconfig.KubeletConfiguration) (server.AuthInterface, error) {
|
||||
// Get clients, if provided
|
||||
var (
|
||||
tokenClient authenticationclient.TokenReviewInterface
|
||||
sarClient authorizationclient.SubjectAccessReviewInterface
|
||||
)
|
||||
if client != nil && !reflect.ValueOf(client).IsNil() {
|
||||
tokenClient = client.Authentication().TokenReviews()
|
||||
sarClient = client.Authorization().SubjectAccessReviews()
|
||||
}
|
||||
|
||||
authenticator, err := buildAuthn(tokenClient, config.Authentication)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attributes := server.NewNodeAuthorizerAttributesGetter(nodeName)
|
||||
|
||||
authorizer, err := buildAuthz(sarClient, config.Authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return server.NewKubeletAuth(authenticator, attributes, authorizer), nil
|
||||
}
|
||||
|
||||
func buildAuthn(client authenticationclient.TokenReviewInterface, authn componentconfig.KubeletAuthentication) (authenticator.Request, error) {
|
||||
authenticators := []authenticator.Request{}
|
||||
|
||||
// x509 client cert auth
|
||||
if len(authn.X509.ClientCAFile) > 0 {
|
||||
clientCAs, err := cert.NewPool(authn.X509.ClientCAFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load client CA file %s: %v", authn.X509.ClientCAFile, err)
|
||||
}
|
||||
verifyOpts := x509.DefaultVerifyOptions()
|
||||
verifyOpts.Roots = clientCAs
|
||||
authenticators = append(authenticators, x509.New(verifyOpts, x509.CommonNameUserConversion))
|
||||
}
|
||||
|
||||
// bearer token auth that uses authentication.k8s.io TokenReview to determine userinfo
|
||||
if authn.Webhook.Enabled {
|
||||
if client == nil {
|
||||
return nil, errors.New("no client provided, cannot use webhook authentication")
|
||||
}
|
||||
tokenAuth, err := webhooktoken.NewFromInterface(client, authn.Webhook.CacheTTL.Duration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authenticators = append(authenticators, bearertoken.New(tokenAuth))
|
||||
}
|
||||
|
||||
if len(authenticators) == 0 {
|
||||
if authn.Anonymous.Enabled {
|
||||
return anonymous.NewAuthenticator(), nil
|
||||
}
|
||||
return nil, errors.New("No authentication method configured")
|
||||
}
|
||||
|
||||
authenticator := group.NewGroupAdder(unionauth.New(authenticators...), []string{"system:authenticated"})
|
||||
if authn.Anonymous.Enabled {
|
||||
authenticator = unionauth.NewFailOnError(authenticator, anonymous.NewAuthenticator())
|
||||
}
|
||||
return authenticator, nil
|
||||
}
|
||||
|
||||
func buildAuthz(client authorizationclient.SubjectAccessReviewInterface, authz componentconfig.KubeletAuthorization) (authorizer.Authorizer, error) {
|
||||
switch authz.Mode {
|
||||
case componentconfig.KubeletAuthorizationModeAlwaysAllow:
|
||||
return alwaysallowauthorizer.NewAlwaysAllowAuthorizer(), nil
|
||||
|
||||
case componentconfig.KubeletAuthorizationModeWebhook:
|
||||
if client == nil {
|
||||
return nil, errors.New("no client provided, cannot use webhook authorization")
|
||||
}
|
||||
return webhooksar.NewFromInterface(
|
||||
client,
|
||||
authz.Webhook.CacheAuthorizedTTL.Duration,
|
||||
authz.Webhook.CacheUnauthorizedTTL.Duration,
|
||||
)
|
||||
|
||||
case "":
|
||||
return nil, fmt.Errorf("No authorization mode specified")
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown authorization mode %s", authz.Mode)
|
||||
|
||||
}
|
||||
}
|
@@ -98,6 +98,29 @@ func (s *KubeletServer) AddFlags(fs *pflag.FlagSet) {
|
||||
fs.Var(componentconfig.IPVar{Val: &s.Address}, "address", "The IP address for the Kubelet to serve on (set to 0.0.0.0 for all interfaces)")
|
||||
fs.Int32Var(&s.Port, "port", s.Port, "The port for the Kubelet to serve on.")
|
||||
fs.Int32Var(&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)")
|
||||
|
||||
// Authentication
|
||||
fs.BoolVar(&s.Authentication.Anonymous.Enabled, "anonymous-auth", s.Authentication.Anonymous.Enabled, ""+
|
||||
"Enables anonymous requests to the Kubelet server. Requests that are not rejected by another "+
|
||||
"authentication method are treated as anonymous requests. Anonymous requests have a username "+
|
||||
"of system:anonymous, and a group name of system:unauthenticated.")
|
||||
fs.BoolVar(&s.Authentication.Webhook.Enabled, "authentication-token-webhook", s.Authentication.Webhook.Enabled, ""+
|
||||
"Use the TokenReview API to determine authentication for bearer tokens.")
|
||||
fs.DurationVar(&s.Authentication.Webhook.CacheTTL.Duration, "authentication-token-webhook-cache-ttl", s.Authentication.Webhook.CacheTTL.Duration, ""+
|
||||
"The duration to cache responses from the webhook token authenticator.")
|
||||
fs.StringVar(&s.Authentication.X509.ClientCAFile, "client-ca-file", s.Authentication.X509.ClientCAFile, ""+
|
||||
"If set, any request presenting a client certificate signed by one of the authorities in the client-ca-file "+
|
||||
"is authenticated with an identity corresponding to the CommonName of the client certificate.")
|
||||
|
||||
// Authorization
|
||||
fs.StringVar((*string)(&s.Authorization.Mode), "authorization-mode", string(s.Authorization.Mode), ""+
|
||||
"Authorization mode for Kubelet server. Valid options are AlwaysAllow or Webhook. "+
|
||||
"Webhook mode uses the SubjectAccessReview API to determine authorization.")
|
||||
fs.DurationVar(&s.Authorization.Webhook.CacheAuthorizedTTL.Duration, "authorization-webhook-cache-authorized-ttl", s.Authorization.Webhook.CacheAuthorizedTTL.Duration, ""+
|
||||
"The duration to cache 'authorized' responses from the webhook authorizer.")
|
||||
fs.DurationVar(&s.Authorization.Webhook.CacheUnauthorizedTTL.Duration, "authorization-webhook-cache-unauthorized-ttl", s.Authorization.Webhook.CacheUnauthorizedTTL.Duration, ""+
|
||||
"The duration to cache 'unauthorized' responses from the webhook authorizer.")
|
||||
|
||||
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 "+
|
||||
|
@@ -62,6 +62,7 @@ import (
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/util/cert"
|
||||
certutil "k8s.io/kubernetes/pkg/util/cert"
|
||||
utilconfig "k8s.io/kubernetes/pkg/util/config"
|
||||
"k8s.io/kubernetes/pkg/util/configz"
|
||||
@@ -399,6 +400,18 @@ func run(s *options.KubeletServer, kubeDeps *kubelet.KubeletDeps) (err error) {
|
||||
kubeDeps.EventClient = eventClient
|
||||
}
|
||||
|
||||
if kubeDeps.Auth == nil {
|
||||
nodeName, err := getNodeName(kubeDeps.Cloud, nodeutil.GetHostname(s.HostnameOverride))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
auth, err := buildAuth(nodeName, kubeDeps.KubeClient, s.KubeletConfiguration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kubeDeps.Auth = auth
|
||||
}
|
||||
|
||||
if kubeDeps.CAdvisorInterface == nil {
|
||||
kubeDeps.CAdvisorInterface, err = cadvisor.New(uint(s.CAdvisorPort), s.ContainerRuntime)
|
||||
if err != nil {
|
||||
@@ -501,12 +514,22 @@ func InitializeTLS(kc *componentconfig.KubeletConfiguration) (*server.TLSOptions
|
||||
// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
|
||||
// Can't use TLSv1.1 because of RC4 cipher usage
|
||||
MinVersion: tls.VersionTLS12,
|
||||
// Populate PeerCertificates in requests, but don't yet reject connections without certificates.
|
||||
ClientAuth: tls.RequestClientCert,
|
||||
},
|
||||
CertFile: kc.TLSCertFile,
|
||||
KeyFile: kc.TLSPrivateKeyFile,
|
||||
}
|
||||
|
||||
if len(kc.Authentication.X509.ClientCAFile) > 0 {
|
||||
clientCAs, err := cert.NewPool(kc.Authentication.X509.ClientCAFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load client CA file %s: %v", kc.Authentication.X509.ClientCAFile, err)
|
||||
}
|
||||
// Specify allowed CAs for client certificates
|
||||
tlsOptions.Config.ClientCAs = clientCAs
|
||||
// Populate PeerCertificates in requests, but don't reject connections without verified certificates
|
||||
tlsOptions.Config.ClientAuth = tls.RequestClientCert
|
||||
}
|
||||
|
||||
return tlsOptions, nil
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user