diff --git a/cmd/kube-apiserver/app/BUILD b/cmd/kube-apiserver/app/BUILD index 2faa23f89dd..b17e4ec7f4e 100644 --- a/cmd/kube-apiserver/app/BUILD +++ b/cmd/kube-apiserver/app/BUILD @@ -26,9 +26,8 @@ go_library( "//pkg/apis/extensions:go_default_library", "//pkg/apiserver:go_default_library", "//pkg/apiserver/authenticator:go_default_library", - "//pkg/auth/authorizer/union:go_default_library", - "//pkg/auth/user:go_default_library", "//pkg/capabilities:go_default_library", + "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/cloudprovider:go_default_library", "//pkg/cloudprovider/providers:go_default_library", "//pkg/controller/informers:go_default_library", @@ -36,11 +35,11 @@ go_library( "//pkg/generated/openapi:go_default_library", "//pkg/genericapiserver:go_default_library", "//pkg/genericapiserver/authorizer:go_default_library", - "//pkg/genericapiserver/validation:go_default_library", + "//pkg/genericapiserver/options:go_default_library", "//pkg/master:go_default_library", "//pkg/registry/cachesize:go_default_library", "//pkg/runtime/schema:go_default_library", - "//pkg/serviceaccount:go_default_library", + "//pkg/util/errors:go_default_library", "//pkg/util/net:go_default_library", "//pkg/util/wait:go_default_library", "//pkg/version:go_default_library", @@ -63,7 +62,6 @@ go_library( "//plugin/pkg/admission/securitycontext/scdeny:go_default_library", "//plugin/pkg/admission/serviceaccount:go_default_library", "//plugin/pkg/admission/storageclass/default:go_default_library", - "//plugin/pkg/auth/authenticator/request/union:go_default_library", "//vendor:github.com/golang/glog", "//vendor:github.com/pborman/uuid", "//vendor:github.com/spf13/cobra", diff --git a/cmd/kube-apiserver/app/options/options.go b/cmd/kube-apiserver/app/options/options.go index 56afa61ed91..8a412bb130a 100644 --- a/cmd/kube-apiserver/app/options/options.go +++ b/cmd/kube-apiserver/app/options/options.go @@ -31,24 +31,32 @@ import ( // ServerRunOptions runs a kubernetes api server. type ServerRunOptions struct { - GenericServerRunOptions *genericoptions.ServerRunOptions - AllowPrivileged bool - EventTTL time.Duration - KubeletConfig kubeletclient.KubeletClientConfig - MaxConnectionBytesPerSec int64 - SSHKeyfile string - SSHUser string - ServiceAccountKeyFiles []string - ServiceAccountLookup bool - WebhookTokenAuthnConfigFile string - WebhookTokenAuthnCacheTTL time.Duration + GenericServerRunOptions *genericoptions.ServerRunOptions + Etcd *genericoptions.EtcdOptions + SecureServing *genericoptions.SecureServingOptions + InsecureServing *genericoptions.ServingOptions + Authentication *genericoptions.BuiltInAuthenticationOptions + Authorization *genericoptions.BuiltInAuthorizationOptions + + AllowPrivileged bool + EventTTL time.Duration + KubeletConfig kubeletclient.KubeletClientConfig + MaxConnectionBytesPerSec int64 + SSHKeyfile string + SSHUser string } // NewServerRunOptions creates a new ServerRunOptions object with default parameters func NewServerRunOptions() *ServerRunOptions { s := ServerRunOptions{ - GenericServerRunOptions: genericoptions.NewServerRunOptions().WithEtcdOptions(), - EventTTL: 1 * time.Hour, + GenericServerRunOptions: genericoptions.NewServerRunOptions(), + Etcd: genericoptions.NewEtcdOptions(), + SecureServing: genericoptions.NewSecureServingOptions(), + InsecureServing: genericoptions.NewInsecureServingOptions(), + Authentication: genericoptions.NewBuiltInAuthenticationOptions().WithAll(), + Authorization: genericoptions.NewBuiltInAuthorizationOptions(), + + EventTTL: 1 * time.Hour, KubeletConfig: kubeletclient.KubeletClientConfig{ Port: ports.KubeletPort, PreferredAddressTypes: []string{ @@ -60,7 +68,6 @@ func NewServerRunOptions() *ServerRunOptions { EnableHttps: true, HTTPTimeout: time.Duration(5) * time.Second, }, - WebhookTokenAuthnCacheTTL: 2 * time.Minute, } return &s } @@ -69,29 +76,21 @@ func NewServerRunOptions() *ServerRunOptions { func (s *ServerRunOptions) AddFlags(fs *pflag.FlagSet) { // Add the generic flags. s.GenericServerRunOptions.AddUniversalFlags(fs) - //Add etcd specific flags. - s.GenericServerRunOptions.AddEtcdStorageFlags(fs) + + s.Etcd.AddFlags(fs) + s.SecureServing.AddFlags(fs) + s.SecureServing.AddDeprecatedFlags(fs) + s.InsecureServing.AddFlags(fs) + s.InsecureServing.AddDeprecatedFlags(fs) + s.Authentication.AddFlags(fs) + s.Authorization.AddFlags(fs) + // Note: the weird ""+ in below lines seems to be the only way to get gofmt to // arrange these text blocks sensibly. Grrr. fs.DurationVar(&s.EventTTL, "event-ttl", s.EventTTL, "Amount of time to retain events. Default is 1h.") - fs.StringArrayVar(&s.ServiceAccountKeyFiles, "service-account-key-file", s.ServiceAccountKeyFiles, ""+ - "File containing PEM-encoded x509 RSA or ECDSA private or public keys, used to verify "+ - "ServiceAccount tokens. If unspecified, --tls-private-key-file is used. "+ - "The specified file can contain multiple keys, and the flag can be specified multiple times with different files.") - - fs.BoolVar(&s.ServiceAccountLookup, "service-account-lookup", s.ServiceAccountLookup, - "If true, validate ServiceAccount tokens exist in etcd as part of authentication.") - - fs.StringVar(&s.WebhookTokenAuthnConfigFile, "authentication-token-webhook-config-file", s.WebhookTokenAuthnConfigFile, ""+ - "File with webhook configuration for token authentication in kubeconfig format. "+ - "The API server will query the remote service to determine authentication for bearer tokens.") - - fs.DurationVar(&s.WebhookTokenAuthnCacheTTL, "authentication-token-webhook-cache-ttl", s.WebhookTokenAuthnCacheTTL, - "The duration to cache responses from the webhook token authenticator. Default is 2m.") - fs.BoolVar(&s.AllowPrivileged, "allow-privileged", s.AllowPrivileged, "If true, allow privileged containers.") diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index 703a3632391..e2b233e5137 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -41,24 +41,22 @@ import ( "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apiserver" "k8s.io/kubernetes/pkg/apiserver/authenticator" - authorizerunion "k8s.io/kubernetes/pkg/auth/authorizer/union" - "k8s.io/kubernetes/pkg/auth/user" "k8s.io/kubernetes/pkg/capabilities" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/controller/informers" serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount" generatedopenapi "k8s.io/kubernetes/pkg/generated/openapi" "k8s.io/kubernetes/pkg/genericapiserver" "k8s.io/kubernetes/pkg/genericapiserver/authorizer" - genericvalidation "k8s.io/kubernetes/pkg/genericapiserver/validation" + genericoptions "k8s.io/kubernetes/pkg/genericapiserver/options" "k8s.io/kubernetes/pkg/master" "k8s.io/kubernetes/pkg/registry/cachesize" "k8s.io/kubernetes/pkg/runtime/schema" - "k8s.io/kubernetes/pkg/serviceaccount" + utilerrors "k8s.io/kubernetes/pkg/util/errors" utilnet "k8s.io/kubernetes/pkg/util/net" "k8s.io/kubernetes/pkg/util/wait" "k8s.io/kubernetes/pkg/version" - authenticatorunion "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/union" ) // NewAPIServerCommand creates a *cobra.Command object with default parameters @@ -80,11 +78,20 @@ cluster's shared state through which all other components interact.`, // Run runs the specified APIServer. This should never exit. func Run(s *options.ServerRunOptions) error { - genericvalidation.VerifyEtcdServersList(s.GenericServerRunOptions) + if errs := s.Etcd.Validate(); len(errs) > 0 { + return utilerrors.NewAggregate(errs) + } + if err := s.GenericServerRunOptions.DefaultExternalAddress(s.SecureServing, s.InsecureServing); err != nil { + return err + } + genericapiserver.DefaultAndValidateRunOptions(s.GenericServerRunOptions) genericConfig := genericapiserver.NewConfig(). // create the new config ApplyOptions(s.GenericServerRunOptions). // apply the options selected - Complete() // set default values based on the known values + ApplySecureServingOptions(s.SecureServing). + ApplyInsecureServingOptions(s.InsecureServing). + ApplyAuthenticationOptions(s.Authentication). + ApplyRBACSuperUser(s.Authorization.RBACSuperUser) serviceIPRange, apiServerServiceIP, err := genericapiserver.DefaultServiceIPRange(s.GenericServerRunOptions.ServiceClusterIPRange) if err != nil { @@ -142,7 +149,7 @@ func Run(s *options.ServerRunOptions) error { // Proxying to pods and services is IP-based... don't expect to be able to verify the hostname proxyTLSClientConfig := &tls.Config{InsecureSkipVerify: true} - if s.GenericServerRunOptions.StorageConfig.DeserializationCacheSize == 0 { + if s.Etcd.StorageConfig.DeserializationCacheSize == 0 { // When size of cache is not explicitly set, estimate its size based on // target memory usage. glog.V(2).Infof("Initalizing deserialization cache size based on %dMB limit", s.GenericServerRunOptions.TargetRAMMB) @@ -158,9 +165,9 @@ func Run(s *options.ServerRunOptions) error { // size to compute its size. We may even go further and measure // collective sizes of the objects in the cache. clusterSize := s.GenericServerRunOptions.TargetRAMMB / 60 - s.GenericServerRunOptions.StorageConfig.DeserializationCacheSize = 25 * clusterSize - if s.GenericServerRunOptions.StorageConfig.DeserializationCacheSize < 1000 { - s.GenericServerRunOptions.StorageConfig.DeserializationCacheSize = 1000 + s.Etcd.StorageConfig.DeserializationCacheSize = 25 * clusterSize + if s.Etcd.StorageConfig.DeserializationCacheSize < 1000 { + s.Etcd.StorageConfig.DeserializationCacheSize = 1000 } } @@ -169,7 +176,7 @@ func Run(s *options.ServerRunOptions) error { glog.Fatalf("error generating storage version map: %s", err) } storageFactory, err := genericapiserver.BuildDefaultStorageFactory( - s.GenericServerRunOptions.StorageConfig, s.GenericServerRunOptions.DefaultStorageMediaType, api.Codecs, + s.Etcd.StorageConfig, s.GenericServerRunOptions.DefaultStorageMediaType, api.Codecs, genericapiserver.NewDefaultResourceEncodingConfig(), storageGroupsToEncodingVersion, // FIXME: this GroupVersionResource override should be configurable []schema.GroupVersionResource{batch.Resource("cronjobs").WithVersion("v2alpha1")}, @@ -179,7 +186,7 @@ func Run(s *options.ServerRunOptions) error { } storageFactory.AddCohabitatingResources(batch.Resource("jobs"), extensions.Resource("jobs")) storageFactory.AddCohabitatingResources(autoscaling.Resource("horizontalpodautoscalers"), extensions.Resource("horizontalpodautoscalers")) - for _, override := range s.GenericServerRunOptions.EtcdServersOverrides { + for _, override := range s.Etcd.EtcdServersOverrides { tokens := strings.Split(override, "#") if len(tokens) != 2 { glog.Errorf("invalid value of etcd server overrides: %s", override) @@ -200,96 +207,49 @@ func Run(s *options.ServerRunOptions) error { } // Default to the private server key for service account token signing - if len(s.ServiceAccountKeyFiles) == 0 && s.GenericServerRunOptions.TLSPrivateKeyFile != "" { - if authenticator.IsValidServiceAccountKeyFile(s.GenericServerRunOptions.TLSPrivateKeyFile) { - s.ServiceAccountKeyFiles = []string{s.GenericServerRunOptions.TLSPrivateKeyFile} + if len(s.Authentication.ServiceAccounts.KeyFiles) == 0 && s.SecureServing.ServerCert.CertKey.KeyFile != "" { + if authenticator.IsValidServiceAccountKeyFile(s.SecureServing.ServerCert.CertKey.KeyFile) { + s.Authentication.ServiceAccounts.KeyFiles = []string{s.SecureServing.ServerCert.CertKey.KeyFile} } else { glog.Warning("No TLS key provided, service account token authentication disabled") } } - var serviceAccountGetter serviceaccount.ServiceAccountTokenGetter - if s.ServiceAccountLookup { + authenticatorConfig := s.Authentication.ToAuthenticationConfig(s.SecureServing.ClientCA) + if s.Authentication.ServiceAccounts.Lookup { // If we need to look up service accounts and tokens, // go directly to etcd to avoid recursive auth insanity storageConfig, err := storageFactory.NewConfig(api.Resource("serviceaccounts")) if err != nil { glog.Fatalf("Unable to get serviceaccounts storage: %v", err) } - serviceAccountGetter = serviceaccountcontroller.NewGetterFromStorageInterface(storageConfig, storageFactory.ResourcePrefix(api.Resource("serviceaccounts")), storageFactory.ResourcePrefix(api.Resource("secrets"))) + authenticatorConfig.ServiceAccountTokenGetter = serviceaccountcontroller.NewGetterFromStorageInterface(storageConfig, storageFactory.ResourcePrefix(api.Resource("serviceaccounts")), storageFactory.ResourcePrefix(api.Resource("secrets"))) } - apiAuthenticator, securityDefinitions, err := authenticator.New(authenticator.AuthenticatorConfig{ - Anonymous: s.GenericServerRunOptions.AnonymousAuth, - AnyToken: s.GenericServerRunOptions.EnableAnyToken, - BasicAuthFile: s.GenericServerRunOptions.BasicAuthFile, - ClientCAFile: s.GenericServerRunOptions.ClientCAFile, - TokenAuthFile: s.GenericServerRunOptions.TokenAuthFile, - OIDCIssuerURL: s.GenericServerRunOptions.OIDCIssuerURL, - OIDCClientID: s.GenericServerRunOptions.OIDCClientID, - OIDCCAFile: s.GenericServerRunOptions.OIDCCAFile, - OIDCUsernameClaim: s.GenericServerRunOptions.OIDCUsernameClaim, - OIDCGroupsClaim: s.GenericServerRunOptions.OIDCGroupsClaim, - ServiceAccountKeyFiles: s.ServiceAccountKeyFiles, - ServiceAccountLookup: s.ServiceAccountLookup, - ServiceAccountTokenGetter: serviceAccountGetter, - KeystoneURL: s.GenericServerRunOptions.KeystoneURL, - KeystoneCAFile: s.GenericServerRunOptions.KeystoneCAFile, - WebhookTokenAuthnConfigFile: s.WebhookTokenAuthnConfigFile, - WebhookTokenAuthnCacheTTL: s.WebhookTokenAuthnCacheTTL, - RequestHeaderConfig: s.GenericServerRunOptions.AuthenticationRequestHeaderConfig(), - }) - + apiAuthenticator, securityDefinitions, err := authenticator.New(authenticatorConfig) if err != nil { glog.Fatalf("Invalid Authentication Config: %v", err) } privilegedLoopbackToken := uuid.NewRandom().String() - selfClientConfig, err := s.GenericServerRunOptions.NewSelfClientConfig(privilegedLoopbackToken) + selfClientConfig, err := genericoptions.NewSelfClientConfig(s.SecureServing, s.InsecureServing, privilegedLoopbackToken) if err != nil { glog.Fatalf("Failed to create clientset: %v", err) } - client, err := s.GenericServerRunOptions.NewSelfClient(privilegedLoopbackToken) + client, err := internalclientset.NewForConfig(selfClientConfig) if err != nil { glog.Errorf("Failed to create clientset: %v", err) } sharedInformers := informers.NewSharedInformerFactory(nil, client, 10*time.Minute) - authorizationConfig := authorizer.AuthorizationConfig{ - PolicyFile: s.GenericServerRunOptions.AuthorizationPolicyFile, - WebhookConfigFile: s.GenericServerRunOptions.AuthorizationWebhookConfigFile, - WebhookCacheAuthorizedTTL: s.GenericServerRunOptions.AuthorizationWebhookCacheAuthorizedTTL, - WebhookCacheUnauthorizedTTL: s.GenericServerRunOptions.AuthorizationWebhookCacheUnauthorizedTTL, - RBACSuperUser: s.GenericServerRunOptions.AuthorizationRBACSuperUser, - InformerFactory: sharedInformers, - } - authorizationModeNames := strings.Split(s.GenericServerRunOptions.AuthorizationMode, ",") - apiAuthorizer, err := authorizer.NewAuthorizerFromAuthorizationConfig(authorizationModeNames, authorizationConfig) + authorizationConfig := s.Authorization.ToAuthorizationConfig(sharedInformers) + apiAuthorizer, err := authorizer.NewAuthorizerFromAuthorizationConfig(authorizationConfig) if err != nil { glog.Fatalf("Invalid Authorization Config: %v", err) } admissionControlPluginNames := strings.Split(s.GenericServerRunOptions.AdmissionControl, ",") - - // TODO(dims): We probably need to add an option "EnableLoopbackToken" - if apiAuthenticator != nil { - var uid = uuid.NewRandom().String() - tokens := make(map[string]*user.DefaultInfo) - tokens[privilegedLoopbackToken] = &user.DefaultInfo{ - Name: user.APIServerUser, - UID: uid, - Groups: []string{user.SystemPrivilegedGroup}, - } - - tokenAuthenticator := authenticator.NewAuthenticatorFromTokens(tokens) - apiAuthenticator = authenticatorunion.New(tokenAuthenticator, apiAuthenticator) - - tokenAuthorizer := authorizer.NewPrivilegedGroups(user.SystemPrivilegedGroup) - apiAuthorizer = authorizerunion.New(tokenAuthorizer, apiAuthorizer) - } - pluginInitializer := admission.NewPluginInitializer(sharedInformers, apiAuthorizer) - admissionController, err := admission.NewFromPlugins(client, admissionControlPluginNames, s.GenericServerRunOptions.AdmissionControlConfigFile, pluginInitializer) if err != nil { glog.Fatalf("Failed to initialize plugins: %v", err) @@ -314,7 +274,7 @@ func Run(s *options.ServerRunOptions) error { genericConfig.OpenAPIConfig.SecurityDefinitions = securityDefinitions config := &master.Config{ - GenericConfig: genericConfig.Config, + GenericConfig: genericConfig, StorageFactory: storageFactory, EnableWatchCache: s.GenericServerRunOptions.EnableWatchCache, diff --git a/cmd/kubelet/app/BUILD b/cmd/kubelet/app/BUILD index b681156b224..22a3d5494cd 100644 --- a/cmd/kubelet/app/BUILD +++ b/cmd/kubelet/app/BUILD @@ -26,10 +26,9 @@ go_library( "//pkg/api/v1:go_default_library", "//pkg/apis/componentconfig:go_default_library", "//pkg/apis/componentconfig/v1alpha1:go_default_library", + "//pkg/apiserver/authenticator: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/release_1_5:go_default_library", @@ -99,11 +98,6 @@ 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", diff --git a/cmd/kubelet/app/auth.go b/cmd/kubelet/app/auth.go index aa2a496e8da..535f10ed561 100644 --- a/cmd/kubelet/app/auth.go +++ b/cmd/kubelet/app/auth.go @@ -22,22 +22,16 @@ import ( "reflect" "k8s.io/kubernetes/pkg/apis/componentconfig" + apiserverauthenticator "k8s.io/kubernetes/pkg/apiserver/authenticator" "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" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5" authenticationclient "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5/typed/authentication/v1beta1" authorizationclient "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5/typed/authorization/v1beta1" alwaysallowauthorizer "k8s.io/kubernetes/pkg/genericapiserver/authorizer" + apiserverauthorizer "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 clientset.Interface, config componentconfig.KubeletConfiguration) (server.AuthInterface, error) { @@ -67,43 +61,21 @@ func buildAuth(nodeName types.NodeName, client clientset.Interface, config compo } 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)) + authenticatorConfig := apiserverauthenticator.DelegatingAuthenticatorConfig{ + Anonymous: authn.Anonymous.Enabled, + CacheTTL: authn.Webhook.CacheTTL.Duration, + ClientCAFile: authn.X509.ClientCAFile, } - // 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)) + authenticatorConfig.TokenAccessReviewClient = client } - 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 + authenticator, _, err := authenticatorConfig.New() + return authenticator, err } func buildAuthz(client authorizationclient.SubjectAccessReviewInterface, authz componentconfig.KubeletAuthorization) (authorizer.Authorizer, error) { @@ -115,11 +87,12 @@ func buildAuthz(client authorizationclient.SubjectAccessReviewInterface, authz c 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, - ) + authorizerConfig := apiserverauthorizer.DelegatingAuthorizerConfig{ + SubjectAccessReviewClient: client, + AllowCacheTTL: authz.Webhook.CacheAuthorizedTTL.Duration, + DenyCacheTTL: authz.Webhook.CacheUnauthorizedTTL.Duration, + } + return authorizerConfig.New() case "": return nil, fmt.Errorf("No authorization mode specified") diff --git a/examples/apiserver/apiserver.go b/examples/apiserver/apiserver.go index 3f4f2f82675..cc4e4618dc7 100644 --- a/examples/apiserver/apiserver.go +++ b/examples/apiserver/apiserver.go @@ -32,6 +32,7 @@ import ( "k8s.io/kubernetes/pkg/registry/generic" "k8s.io/kubernetes/pkg/runtime/schema" "k8s.io/kubernetes/pkg/storage/storagebackend" + utilerrors "k8s.io/kubernetes/pkg/util/errors" // Install the testgroup API _ "k8s.io/kubernetes/cmd/libs/go2idl/client-gen/test_apis/testgroup/install" @@ -54,20 +55,51 @@ func newStorageFactory() genericapiserver.StorageFactory { return storageFactory } -func NewServerRunOptions() *genericoptions.ServerRunOptions { - serverOptions := genericoptions.NewServerRunOptions().WithEtcdOptions() - serverOptions.InsecurePort = InsecurePort - return serverOptions +type ServerRunOptions struct { + GenericServerRunOptions *genericoptions.ServerRunOptions + Etcd *genericoptions.EtcdOptions + SecureServing *genericoptions.SecureServingOptions + InsecureServing *genericoptions.ServingOptions + Authentication *genericoptions.BuiltInAuthenticationOptions } -func Run(serverOptions *genericoptions.ServerRunOptions, stopCh <-chan struct{}) error { +func NewServerRunOptions() *ServerRunOptions { + s := ServerRunOptions{ + GenericServerRunOptions: genericoptions.NewServerRunOptions(), + Etcd: genericoptions.NewEtcdOptions(), + SecureServing: genericoptions.NewSecureServingOptions(), + InsecureServing: genericoptions.NewInsecureServingOptions(), + Authentication: genericoptions.NewBuiltInAuthenticationOptions().WithAll(), + } + s.InsecureServing.BindPort = InsecurePort + s.SecureServing.ServingOptions.BindPort = SecurePort + + return &s +} + +func (serverOptions *ServerRunOptions) Run(stopCh <-chan struct{}) error { // Set ServiceClusterIPRange _, serviceClusterIPRange, _ := net.ParseCIDR("10.0.0.0/24") - serverOptions.ServiceClusterIPRange = *serviceClusterIPRange - serverOptions.StorageConfig.ServerList = []string{"http://127.0.0.1:2379"} - genericvalidation.ValidateRunOptions(serverOptions) - genericvalidation.VerifyEtcdServersList(serverOptions) - config := genericapiserver.NewConfig().ApplyOptions(serverOptions).Complete() + serverOptions.GenericServerRunOptions.ServiceClusterIPRange = *serviceClusterIPRange + serverOptions.Etcd.StorageConfig.ServerList = []string{"http://127.0.0.1:2379"} + + genericvalidation.ValidateRunOptions(serverOptions.GenericServerRunOptions) + if errs := serverOptions.Etcd.Validate(); len(errs) > 0 { + return utilerrors.NewAggregate(errs) + } + if errs := serverOptions.SecureServing.Validate(); len(errs) > 0 { + return utilerrors.NewAggregate(errs) + } + if errs := serverOptions.InsecureServing.Validate("insecure-port"); len(errs) > 0 { + return utilerrors.NewAggregate(errs) + } + + config := genericapiserver.NewConfig(). + ApplyOptions(serverOptions.GenericServerRunOptions). + ApplySecureServingOptions(serverOptions.SecureServing). + ApplyInsecureServingOptions(serverOptions.InsecureServing). + ApplyAuthenticationOptions(serverOptions.Authentication). + Complete() if err := config.MaybeGenerateServingCerts(); err != nil { // this wasn't treated as fatal for this process before fmt.Printf("Error creating cert: %v", err) diff --git a/examples/apiserver/server/main.go b/examples/apiserver/server/main.go index 5c69edf708f..5dff01318a7 100644 --- a/examples/apiserver/server/main.go +++ b/examples/apiserver/server/main.go @@ -30,10 +30,14 @@ func main() { // Parse command line flags. serverRunOptions.AddUniversalFlags(pflag.CommandLine) - serverRunOptions.AddEtcdStorageFlags(pflag.CommandLine) + serverRunOptions.Etcd.AddFlags(pflag.CommandLine) + serverRunOptions.SecureServing.AddFlags(pflag.CommandLine) + serverRunOptions.SecureServing.AddDeprecatedFlags(pflag.CommandLine) + serverRunOptions.InsecureServing.AddFlags(pflag.CommandLine) + serverRunOptions.InsecureServing.AddDeprecatedFlags(pflag.CommandLine) flag.InitFlags() - if err := apiserver.Run(serverRunOptions, wait.NeverStop); err != nil { + if err := serverRunOptions.Run(wait.NeverStop); err != nil { glog.Fatalf("Error in bringing up the server: %v", err) } } diff --git a/federation/apis/openapi-spec/swagger.json b/federation/apis/openapi-spec/swagger.json index 456cfce5ea8..85c745a8148 100644 --- a/federation/apis/openapi-spec/swagger.json +++ b/federation/apis/openapi-spec/swagger.json @@ -31,6 +31,9 @@ "schema": { "$ref": "#/definitions/unversioned.APIVersions" } + }, + "401": { + "description": "Unauthorized" } } } @@ -61,6 +64,9 @@ "schema": { "$ref": "#/definitions/unversioned.APIResourceList" } + }, + "401": { + "description": "Unauthorized" } } } @@ -91,6 +97,9 @@ "schema": { "$ref": "#/definitions/v1.ConfigMapList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -165,6 +174,9 @@ "schema": { "$ref": "#/definitions/v1.EventList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -276,6 +288,9 @@ "schema": { "$ref": "#/definitions/v1.NamespaceList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -312,6 +327,9 @@ "schema": { "$ref": "#/definitions/v1.Namespace" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -375,6 +393,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -451,6 +472,9 @@ "schema": { "$ref": "#/definitions/v1.ConfigMapList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -487,6 +511,9 @@ "schema": { "$ref": "#/definitions/v1.ConfigMap" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -550,6 +577,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -611,6 +641,9 @@ "schema": { "$ref": "#/definitions/v1.ConfigMap" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -647,6 +680,9 @@ "schema": { "$ref": "#/definitions/v1.ConfigMap" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -697,6 +733,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -735,6 +774,9 @@ "schema": { "$ref": "#/definitions/v1.ConfigMap" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -827,6 +869,9 @@ "schema": { "$ref": "#/definitions/v1.EventList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -863,6 +908,9 @@ "schema": { "$ref": "#/definitions/v1.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -926,6 +974,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -987,6 +1038,9 @@ "schema": { "$ref": "#/definitions/v1.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1023,6 +1077,9 @@ "schema": { "$ref": "#/definitions/v1.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1073,6 +1130,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1111,6 +1171,9 @@ "schema": { "$ref": "#/definitions/v1.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1203,6 +1266,9 @@ "schema": { "$ref": "#/definitions/v1.SecretList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1239,6 +1305,9 @@ "schema": { "$ref": "#/definitions/v1.Secret" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1302,6 +1371,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1363,6 +1435,9 @@ "schema": { "$ref": "#/definitions/v1.Secret" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1399,6 +1474,9 @@ "schema": { "$ref": "#/definitions/v1.Secret" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1449,6 +1527,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1487,6 +1568,9 @@ "schema": { "$ref": "#/definitions/v1.Secret" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1579,6 +1663,9 @@ "schema": { "$ref": "#/definitions/v1.ServiceList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1615,6 +1702,9 @@ "schema": { "$ref": "#/definitions/v1.Service" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1678,6 +1768,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1739,6 +1832,9 @@ "schema": { "$ref": "#/definitions/v1.Service" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1775,6 +1871,9 @@ "schema": { "$ref": "#/definitions/v1.Service" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1825,6 +1924,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1863,6 +1965,9 @@ "schema": { "$ref": "#/definitions/v1.Service" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1916,6 +2021,9 @@ "schema": { "$ref": "#/definitions/v1.Service" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1952,6 +2060,9 @@ "schema": { "$ref": "#/definitions/v1.Service" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -1990,6 +2101,9 @@ "schema": { "$ref": "#/definitions/v1.Service" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -2059,6 +2173,9 @@ "schema": { "$ref": "#/definitions/v1.Namespace" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -2095,6 +2212,9 @@ "schema": { "$ref": "#/definitions/v1.Namespace" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -2145,6 +2265,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -2183,6 +2306,9 @@ "schema": { "$ref": "#/definitions/v1.Namespace" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -2228,6 +2354,9 @@ "schema": { "$ref": "#/definitions/v1.Namespace" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -2281,6 +2410,9 @@ "schema": { "$ref": "#/definitions/v1.Namespace" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -2317,6 +2449,9 @@ "schema": { "$ref": "#/definitions/v1.Namespace" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -2355,6 +2490,9 @@ "schema": { "$ref": "#/definitions/v1.Namespace" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -2402,6 +2540,9 @@ "schema": { "$ref": "#/definitions/v1.SecretList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -2476,6 +2617,9 @@ "schema": { "$ref": "#/definitions/v1.ServiceList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -2550,6 +2694,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -2624,6 +2771,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -2698,6 +2848,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -2772,6 +2925,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -2854,6 +3010,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -2944,6 +3103,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -3026,6 +3188,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -3116,6 +3281,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -3198,6 +3366,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -3288,6 +3459,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -3370,6 +3544,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -3460,6 +3637,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -3542,6 +3722,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -3616,6 +3799,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -3690,6 +3876,9 @@ "schema": { "$ref": "#/definitions/unversioned.APIGroupList" } + }, + "401": { + "description": "Unauthorized" } } } @@ -3720,6 +3909,9 @@ "schema": { "$ref": "#/definitions/unversioned.APIGroup" } + }, + "401": { + "description": "Unauthorized" } } } @@ -3750,6 +3942,9 @@ "schema": { "$ref": "#/definitions/unversioned.APIResourceList" } + }, + "401": { + "description": "Unauthorized" } } } @@ -3780,6 +3975,9 @@ "schema": { "$ref": "#/definitions/v1beta1.DaemonSetList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -3854,6 +4052,9 @@ "schema": { "$ref": "#/definitions/v1beta1.DeploymentList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -3928,6 +4129,9 @@ "schema": { "$ref": "#/definitions/v1beta1.IngressList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4039,6 +4243,9 @@ "schema": { "$ref": "#/definitions/v1beta1.DaemonSetList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4075,6 +4282,9 @@ "schema": { "$ref": "#/definitions/v1beta1.DaemonSet" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4138,6 +4348,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4199,6 +4412,9 @@ "schema": { "$ref": "#/definitions/v1beta1.DaemonSet" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4235,6 +4451,9 @@ "schema": { "$ref": "#/definitions/v1beta1.DaemonSet" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4285,6 +4504,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4323,6 +4545,9 @@ "schema": { "$ref": "#/definitions/v1beta1.DaemonSet" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4376,6 +4601,9 @@ "schema": { "$ref": "#/definitions/v1beta1.DaemonSet" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4412,6 +4640,9 @@ "schema": { "$ref": "#/definitions/v1beta1.DaemonSet" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4450,6 +4681,9 @@ "schema": { "$ref": "#/definitions/v1beta1.DaemonSet" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4542,6 +4776,9 @@ "schema": { "$ref": "#/definitions/v1beta1.DeploymentList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4578,6 +4815,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Deployment" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4641,6 +4881,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4702,6 +4945,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Deployment" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4738,6 +4984,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Deployment" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4788,6 +5037,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4826,6 +5078,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Deployment" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4879,6 +5134,9 @@ "schema": { "$ref": "#/definitions/v1beta1.DeploymentRollback" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4940,6 +5198,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Scale" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -4976,6 +5237,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Scale" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5014,6 +5278,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Scale" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5067,6 +5334,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Deployment" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5103,6 +5373,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Deployment" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5141,6 +5414,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Deployment" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5233,6 +5509,9 @@ "schema": { "$ref": "#/definitions/v1beta1.IngressList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5269,6 +5548,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Ingress" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5332,6 +5614,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5393,6 +5678,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Ingress" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5429,6 +5717,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Ingress" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5479,6 +5770,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5517,6 +5811,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Ingress" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5570,6 +5867,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Ingress" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5606,6 +5906,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Ingress" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5644,6 +5947,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Ingress" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5736,6 +6042,9 @@ "schema": { "$ref": "#/definitions/v1beta1.ReplicaSetList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5772,6 +6081,9 @@ "schema": { "$ref": "#/definitions/v1beta1.ReplicaSet" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5835,6 +6147,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5896,6 +6211,9 @@ "schema": { "$ref": "#/definitions/v1beta1.ReplicaSet" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5932,6 +6250,9 @@ "schema": { "$ref": "#/definitions/v1beta1.ReplicaSet" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -5982,6 +6303,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -6020,6 +6344,9 @@ "schema": { "$ref": "#/definitions/v1beta1.ReplicaSet" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -6073,6 +6400,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Scale" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -6109,6 +6439,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Scale" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -6147,6 +6480,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Scale" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -6200,6 +6536,9 @@ "schema": { "$ref": "#/definitions/v1beta1.ReplicaSet" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -6236,6 +6575,9 @@ "schema": { "$ref": "#/definitions/v1beta1.ReplicaSet" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -6274,6 +6616,9 @@ "schema": { "$ref": "#/definitions/v1beta1.ReplicaSet" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -6329,6 +6674,9 @@ "schema": { "$ref": "#/definitions/v1beta1.ReplicaSetList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -6403,6 +6751,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -6477,6 +6828,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -6551,6 +6905,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -6625,6 +6982,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -6707,6 +7067,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -6797,6 +7160,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -6879,6 +7245,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -6969,6 +7338,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -7051,6 +7423,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -7141,6 +7516,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -7223,6 +7601,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -7313,6 +7694,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -7387,6 +7771,9 @@ "schema": { "$ref": "#/definitions/unversioned.APIGroup" } + }, + "401": { + "description": "Unauthorized" } } } @@ -7417,6 +7804,9 @@ "schema": { "$ref": "#/definitions/unversioned.APIResourceList" } + }, + "401": { + "description": "Unauthorized" } } } @@ -7484,6 +7874,9 @@ "schema": { "$ref": "#/definitions/v1beta1.ClusterList" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -7520,6 +7913,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Cluster" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -7583,6 +7979,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -7636,6 +8035,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Cluster" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -7672,6 +8074,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Cluster" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -7722,6 +8127,9 @@ "schema": { "$ref": "#/definitions/unversioned.Status" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -7760,6 +8168,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Cluster" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -7805,6 +8216,9 @@ "schema": { "$ref": "#/definitions/v1beta1.Cluster" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -7860,6 +8274,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -7934,6 +8351,9 @@ "schema": { "$ref": "#/definitions/versioned.Event" } + }, + "401": { + "description": "Unauthorized" } } }, @@ -8000,8 +8420,8 @@ ], "operationId": "logFileListHandler", "responses": { - "default": { - "description": "Default Response." + "401": { + "description": "Unauthorized" } } } @@ -8016,8 +8436,8 @@ ], "operationId": "logFileHandler", "responses": { - "default": { - "description": "Default Response." + "401": { + "description": "Unauthorized" } } }, @@ -8054,6 +8474,9 @@ "schema": { "$ref": "#/definitions/version.Info" } + }, + "401": { + "description": "Unauthorized" } } } @@ -10322,5 +10745,10 @@ "name": "authorization", "in": "header" } - } + }, + "security": [ + { + "BearerToken": [] + } + ] } diff --git a/federation/cmd/federation-apiserver/app/BUILD b/federation/cmd/federation-apiserver/app/BUILD index 4e5a16af2ca..46fa167c6f1 100644 --- a/federation/cmd/federation-apiserver/app/BUILD +++ b/federation/cmd/federation-apiserver/app/BUILD @@ -36,14 +36,13 @@ go_library( "//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions/install:go_default_library", "//pkg/apiserver/authenticator:go_default_library", - "//pkg/auth/authorizer/union:go_default_library", - "//pkg/auth/user:go_default_library", + "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/cloudprovider/providers:go_default_library", "//pkg/controller/informers:go_default_library", "//pkg/generated/openapi:go_default_library", "//pkg/genericapiserver:go_default_library", "//pkg/genericapiserver/authorizer:go_default_library", - "//pkg/genericapiserver/validation:go_default_library", + "//pkg/genericapiserver/options:go_default_library", "//pkg/registry/cachesize:go_default_library", "//pkg/registry/core/configmap/etcd:go_default_library", "//pkg/registry/core/event/etcd:go_default_library", @@ -58,13 +57,13 @@ go_library( "//pkg/registry/generic/registry:go_default_library", "//pkg/routes:go_default_library", "//pkg/runtime/schema:go_default_library", + "//pkg/util/errors:go_default_library", "//pkg/util/wait:go_default_library", "//pkg/version:go_default_library", "//plugin/pkg/admission/admit:go_default_library", "//plugin/pkg/admission/deny:go_default_library", "//plugin/pkg/admission/gc:go_default_library", "//plugin/pkg/admission/namespace/lifecycle:go_default_library", - "//plugin/pkg/auth/authenticator/request/union:go_default_library", "//vendor:github.com/golang/glog", "//vendor:github.com/pborman/uuid", "//vendor:github.com/spf13/cobra", diff --git a/federation/cmd/federation-apiserver/app/options/options.go b/federation/cmd/federation-apiserver/app/options/options.go index b1bfafb5071..1c5f7a86fe7 100644 --- a/federation/cmd/federation-apiserver/app/options/options.go +++ b/federation/cmd/federation-apiserver/app/options/options.go @@ -28,14 +28,26 @@ import ( // Runtime options for the federation-apiserver. type ServerRunOptions struct { GenericServerRunOptions *genericoptions.ServerRunOptions - EventTTL time.Duration + Etcd *genericoptions.EtcdOptions + SecureServing *genericoptions.SecureServingOptions + InsecureServing *genericoptions.ServingOptions + Authentication *genericoptions.BuiltInAuthenticationOptions + Authorization *genericoptions.BuiltInAuthorizationOptions + + EventTTL time.Duration } // NewServerRunOptions creates a new ServerRunOptions object with default values. func NewServerRunOptions() *ServerRunOptions { s := ServerRunOptions{ - GenericServerRunOptions: genericoptions.NewServerRunOptions().WithEtcdOptions(), - EventTTL: 1 * time.Hour, + GenericServerRunOptions: genericoptions.NewServerRunOptions(), + Etcd: genericoptions.NewEtcdOptions(), + SecureServing: genericoptions.NewSecureServingOptions(), + InsecureServing: genericoptions.NewInsecureServingOptions(), + Authentication: genericoptions.NewBuiltInAuthenticationOptions().WithAll(), + Authorization: genericoptions.NewBuiltInAuthorizationOptions(), + + EventTTL: 1 * time.Hour, } return &s } @@ -44,8 +56,11 @@ func NewServerRunOptions() *ServerRunOptions { func (s *ServerRunOptions) AddFlags(fs *pflag.FlagSet) { // Add the generic flags. s.GenericServerRunOptions.AddUniversalFlags(fs) - //Add etcd specific flags. - s.GenericServerRunOptions.AddEtcdStorageFlags(fs) + s.Etcd.AddFlags(fs) + s.SecureServing.AddFlags(fs) + s.InsecureServing.AddFlags(fs) + s.Authentication.AddFlags(fs) + s.Authorization.AddFlags(fs) fs.DurationVar(&s.EventTTL, "event-ttl", s.EventTTL, "Amount of time to retain events. Default is 1h.") diff --git a/federation/cmd/federation-apiserver/app/server.go b/federation/cmd/federation-apiserver/app/server.go index a6f8a1ad569..0c4d118268f 100644 --- a/federation/cmd/federation-apiserver/app/server.go +++ b/federation/cmd/federation-apiserver/app/server.go @@ -32,21 +32,20 @@ import ( "k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apiserver/authenticator" - authorizerunion "k8s.io/kubernetes/pkg/auth/authorizer/union" - "k8s.io/kubernetes/pkg/auth/user" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/controller/informers" "k8s.io/kubernetes/pkg/generated/openapi" "k8s.io/kubernetes/pkg/genericapiserver" "k8s.io/kubernetes/pkg/genericapiserver/authorizer" - genericvalidation "k8s.io/kubernetes/pkg/genericapiserver/validation" + genericoptions "k8s.io/kubernetes/pkg/genericapiserver/options" "k8s.io/kubernetes/pkg/registry/cachesize" "k8s.io/kubernetes/pkg/registry/generic" "k8s.io/kubernetes/pkg/registry/generic/registry" "k8s.io/kubernetes/pkg/routes" "k8s.io/kubernetes/pkg/runtime/schema" + utilerrors "k8s.io/kubernetes/pkg/util/errors" "k8s.io/kubernetes/pkg/util/wait" "k8s.io/kubernetes/pkg/version" - authenticatorunion "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/union" ) // NewAPIServerCommand creates a *cobra.Command object with default parameters @@ -67,11 +66,20 @@ cluster's shared state through which all other components interact.`, // Run runs the specified APIServer. This should never exit. func Run(s *options.ServerRunOptions) error { - genericvalidation.VerifyEtcdServersList(s.GenericServerRunOptions) + if errs := s.Etcd.Validate(); len(errs) > 0 { + utilerrors.NewAggregate(errs) + } + if err := s.GenericServerRunOptions.DefaultExternalAddress(s.SecureServing, s.InsecureServing); err != nil { + return err + } + genericapiserver.DefaultAndValidateRunOptions(s.GenericServerRunOptions) genericConfig := genericapiserver.NewConfig(). // create the new config ApplyOptions(s.GenericServerRunOptions). // apply the options selected - Complete() // set default values based on the known values + ApplySecureServingOptions(s.SecureServing). + ApplyInsecureServingOptions(s.InsecureServing). + ApplyAuthenticationOptions(s.Authentication). + ApplyRBACSuperUser(s.Authorization.RBACSuperUser) if err := genericConfig.MaybeGenerateServingCerts(); err != nil { glog.Fatalf("Failed to generate service certificate: %v", err) @@ -80,23 +88,23 @@ func Run(s *options.ServerRunOptions) error { // TODO: register cluster federation resources here. resourceConfig := genericapiserver.NewResourceConfig() - if s.GenericServerRunOptions.StorageConfig.DeserializationCacheSize == 0 { + if s.Etcd.StorageConfig.DeserializationCacheSize == 0 { // When size of cache is not explicitly set, set it to 50000 - s.GenericServerRunOptions.StorageConfig.DeserializationCacheSize = 50000 + s.Etcd.StorageConfig.DeserializationCacheSize = 50000 } storageGroupsToEncodingVersion, err := s.GenericServerRunOptions.StorageGroupsToEncodingVersion() if err != nil { glog.Fatalf("error generating storage version map: %s", err) } storageFactory, err := genericapiserver.BuildDefaultStorageFactory( - s.GenericServerRunOptions.StorageConfig, s.GenericServerRunOptions.DefaultStorageMediaType, api.Codecs, + s.Etcd.StorageConfig, s.GenericServerRunOptions.DefaultStorageMediaType, api.Codecs, genericapiserver.NewDefaultResourceEncodingConfig(), storageGroupsToEncodingVersion, []schema.GroupVersionResource{}, resourceConfig, s.GenericServerRunOptions.RuntimeConfig) if err != nil { glog.Fatalf("error in initializing storage factory: %s", err) } - for _, override := range s.GenericServerRunOptions.EtcdServersOverrides { + for _, override := range s.Etcd.EtcdServersOverrides { tokens := strings.Split(override, "#") if len(tokens) != 2 { glog.Errorf("invalid value of etcd server overrides: %s", override) @@ -116,70 +124,30 @@ func Run(s *options.ServerRunOptions) error { storageFactory.SetEtcdLocation(groupResource, servers) } - apiAuthenticator, securityDefinitions, err := authenticator.New(authenticator.AuthenticatorConfig{ - Anonymous: s.GenericServerRunOptions.AnonymousAuth, - AnyToken: s.GenericServerRunOptions.EnableAnyToken, - BasicAuthFile: s.GenericServerRunOptions.BasicAuthFile, - ClientCAFile: s.GenericServerRunOptions.ClientCAFile, - TokenAuthFile: s.GenericServerRunOptions.TokenAuthFile, - OIDCIssuerURL: s.GenericServerRunOptions.OIDCIssuerURL, - OIDCClientID: s.GenericServerRunOptions.OIDCClientID, - OIDCCAFile: s.GenericServerRunOptions.OIDCCAFile, - OIDCUsernameClaim: s.GenericServerRunOptions.OIDCUsernameClaim, - OIDCGroupsClaim: s.GenericServerRunOptions.OIDCGroupsClaim, - KeystoneURL: s.GenericServerRunOptions.KeystoneURL, - RequestHeaderConfig: s.GenericServerRunOptions.AuthenticationRequestHeaderConfig(), - }) + apiAuthenticator, securityDefinitions, err := authenticator.New(s.Authentication.ToAuthenticationConfig(s.SecureServing.ClientCA)) if err != nil { glog.Fatalf("Invalid Authentication Config: %v", err) } privilegedLoopbackToken := uuid.NewRandom().String() - selfClientConfig, err := s.GenericServerRunOptions.NewSelfClientConfig(privilegedLoopbackToken) + selfClientConfig, err := genericoptions.NewSelfClientConfig(s.SecureServing, s.InsecureServing, privilegedLoopbackToken) if err != nil { glog.Fatalf("Failed to create clientset: %v", err) } - client, err := s.GenericServerRunOptions.NewSelfClient(privilegedLoopbackToken) + client, err := internalclientset.NewForConfig(selfClientConfig) if err != nil { glog.Errorf("Failed to create clientset: %v", err) } sharedInformers := informers.NewSharedInformerFactory(nil, client, 10*time.Minute) - authorizationConfig := authorizer.AuthorizationConfig{ - PolicyFile: s.GenericServerRunOptions.AuthorizationPolicyFile, - WebhookConfigFile: s.GenericServerRunOptions.AuthorizationWebhookConfigFile, - WebhookCacheAuthorizedTTL: s.GenericServerRunOptions.AuthorizationWebhookCacheAuthorizedTTL, - WebhookCacheUnauthorizedTTL: s.GenericServerRunOptions.AuthorizationWebhookCacheUnauthorizedTTL, - RBACSuperUser: s.GenericServerRunOptions.AuthorizationRBACSuperUser, - InformerFactory: sharedInformers, - } - authorizationModeNames := strings.Split(s.GenericServerRunOptions.AuthorizationMode, ",") - apiAuthorizer, err := authorizer.NewAuthorizerFromAuthorizationConfig(authorizationModeNames, authorizationConfig) + authorizerconfig := s.Authorization.ToAuthorizationConfig(sharedInformers) + apiAuthorizer, err := authorizer.NewAuthorizerFromAuthorizationConfig(authorizerconfig) if err != nil { glog.Fatalf("Invalid Authorization Config: %v", err) } admissionControlPluginNames := strings.Split(s.GenericServerRunOptions.AdmissionControl, ",") - - // TODO(dims): We probably need to add an option "EnableLoopbackToken" - if apiAuthenticator != nil { - var uid = uuid.NewRandom().String() - tokens := make(map[string]*user.DefaultInfo) - tokens[privilegedLoopbackToken] = &user.DefaultInfo{ - Name: user.APIServerUser, - UID: uid, - Groups: []string{user.SystemPrivilegedGroup}, - } - - tokenAuthenticator := authenticator.NewAuthenticatorFromTokens(tokens) - apiAuthenticator = authenticatorunion.New(tokenAuthenticator, apiAuthenticator) - - tokenAuthorizer := authorizer.NewPrivilegedGroups(user.SystemPrivilegedGroup) - apiAuthorizer = authorizerunion.New(tokenAuthorizer, apiAuthorizer) - } - pluginInitializer := admission.NewPluginInitializer(sharedInformers, apiAuthorizer) - admissionController, err := admission.NewFromPlugins(client, admissionControlPluginNames, s.GenericServerRunOptions.AdmissionControlConfigFile, pluginInitializer) if err != nil { glog.Fatalf("Failed to initialize plugins: %v", err) @@ -202,7 +170,7 @@ func Run(s *options.ServerRunOptions) error { cachesize.SetWatchCacheSizes(s.GenericServerRunOptions.WatchCacheSizes) } - m, err := genericConfig.New() + m, err := genericConfig.Complete().New() if err != nil { return err } diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index b482d30356c..3e4f162b3d7 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -32,9 +32,11 @@ auth-provider auth-provider auth-provider-arg auth-provider-arg +authentication-kubeconfig authentication-token-webhook authentication-token-webhook-cache-ttl authentication-token-webhook-config-file +authorization-kubeconfig authorization-mode authorization-policy-file authorization-rbac-super-user diff --git a/pkg/apiserver/authenticator/BUILD b/pkg/apiserver/authenticator/BUILD index 79517cdd26f..e4a92917b58 100644 --- a/pkg/apiserver/authenticator/BUILD +++ b/pkg/apiserver/authenticator/BUILD @@ -12,13 +12,17 @@ load( go_library( name = "go_default_library", - srcs = ["authn.go"], + srcs = [ + "builtin.go", + "delegating.go", + ], tags = ["automanaged"], deps = [ "//pkg/auth/authenticator:go_default_library", "//pkg/auth/authenticator/bearertoken:go_default_library", "//pkg/auth/group:go_default_library", "//pkg/auth/user:go_default_library", + "//pkg/client/clientset_generated/release_1_5/typed/authentication/v1beta1:go_default_library", "//pkg/serviceaccount:go_default_library", "//pkg/util/cert:go_default_library", "//plugin/pkg/auth/authenticator/password/keystone:go_default_library", diff --git a/pkg/apiserver/authenticator/authn.go b/pkg/apiserver/authenticator/builtin.go similarity index 98% rename from pkg/apiserver/authenticator/authn.go rename to pkg/apiserver/authenticator/builtin.go index 5719774ede2..65280ae677e 100644 --- a/pkg/apiserver/authenticator/authn.go +++ b/pkg/apiserver/authenticator/builtin.go @@ -62,13 +62,15 @@ type AuthenticatorConfig struct { OIDCGroupsClaim string ServiceAccountKeyFiles []string ServiceAccountLookup bool - ServiceAccountTokenGetter serviceaccount.ServiceAccountTokenGetter KeystoneURL string KeystoneCAFile string WebhookTokenAuthnConfigFile string WebhookTokenAuthnCacheTTL time.Duration RequestHeaderConfig *RequestHeaderConfig + + // TODO, this is the only non-serializable part of the entire config. Factor it out into a clientconfig + ServiceAccountTokenGetter serviceaccount.ServiceAccountTokenGetter } // New returns an authenticator.Request or an error that supports the standard diff --git a/pkg/apiserver/authenticator/delegating.go b/pkg/apiserver/authenticator/delegating.go new file mode 100644 index 00000000000..185323bc869 --- /dev/null +++ b/pkg/apiserver/authenticator/delegating.go @@ -0,0 +1,97 @@ +/* +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 authenticator + +import ( + "errors" + "fmt" + "time" + + "github.com/go-openapi/spec" + + "k8s.io/kubernetes/pkg/auth/authenticator" + "k8s.io/kubernetes/pkg/auth/authenticator/bearertoken" + "k8s.io/kubernetes/pkg/auth/group" + "k8s.io/kubernetes/pkg/auth/user" + authenticationclient "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5/typed/authentication/v1beta1" + "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" +) + +// DelegatingAuthenticatorConfig is the minimal configuration needed to create an authenticator +// built to delegate authentication to a kube API server +type DelegatingAuthenticatorConfig struct { + Anonymous bool + + TokenAccessReviewClient authenticationclient.TokenReviewInterface + + // CacheTTL is the length of time that a token authentication answer will be cached. + CacheTTL time.Duration + + // ClientCAFile is the CA bundle file used to authenticate client certificates + ClientCAFile string +} + +func (c DelegatingAuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDefinitions, error) { + authenticators := []authenticator.Request{} + securityDefinitions := spec.SecurityDefinitions{} + + // x509 client cert auth + if len(c.ClientCAFile) > 0 { + clientCAs, err := cert.NewPool(c.ClientCAFile) + if err != nil { + return nil, nil, fmt.Errorf("unable to load client CA file %s: %v", c.ClientCAFile, err) + } + verifyOpts := x509.DefaultVerifyOptions() + verifyOpts.Roots = clientCAs + authenticators = append(authenticators, x509.New(verifyOpts, x509.CommonNameUserConversion)) + } + + if c.TokenAccessReviewClient != nil { + tokenAuth, err := webhooktoken.NewFromInterface(c.TokenAccessReviewClient, c.CacheTTL) + if err != nil { + return nil, nil, err + } + authenticators = append(authenticators, bearertoken.New(tokenAuth)) + + securityDefinitions["BearerToken"] = &spec.SecurityScheme{ + SecuritySchemeProps: spec.SecuritySchemeProps{ + Type: "apiKey", + Name: "authorization", + In: "header", + Description: "Bearer Token authentication", + }, + } + } + + if len(authenticators) == 0 { + if c.Anonymous { + return anonymous.NewAuthenticator(), &securityDefinitions, nil + } + return nil, nil, errors.New("No authentication method configured") + } + + authenticator := group.NewGroupAdder(unionauth.New(authenticators...), []string{user.AllAuthenticated}) + if c.Anonymous { + authenticator = unionauth.NewFailOnError(authenticator, anonymous.NewAuthenticator()) + } + return authenticator, &securityDefinitions, nil + +} diff --git a/pkg/genericapiserver/BUILD b/pkg/genericapiserver/BUILD index ccc566ad104..4cd42b49129 100644 --- a/pkg/genericapiserver/BUILD +++ b/pkg/genericapiserver/BUILD @@ -38,14 +38,18 @@ go_library( "//pkg/apimachinery:go_default_library", "//pkg/apimachinery/registered:go_default_library", "//pkg/apiserver:go_default_library", + "//pkg/apiserver/authenticator:go_default_library", "//pkg/apiserver/filters:go_default_library", "//pkg/apiserver/openapi:go_default_library", "//pkg/apiserver/request:go_default_library", "//pkg/auth/authenticator:go_default_library", "//pkg/auth/authorizer:go_default_library", + "//pkg/auth/authorizer/union:go_default_library", "//pkg/auth/handlers:go_default_library", + "//pkg/auth/user:go_default_library", "//pkg/client/restclient:go_default_library", "//pkg/cloudprovider:go_default_library", + "//pkg/genericapiserver/authorizer:go_default_library", "//pkg/genericapiserver/filters:go_default_library", "//pkg/genericapiserver/mux:go_default_library", "//pkg/genericapiserver/openapi/common:go_default_library", @@ -70,10 +74,12 @@ go_library( "//pkg/util/validation:go_default_library", "//pkg/util/wait:go_default_library", "//pkg/version:go_default_library", + "//plugin/pkg/auth/authenticator/request/union:go_default_library", "//vendor:github.com/coreos/go-systemd/daemon", "//vendor:github.com/emicklei/go-restful", "//vendor:github.com/go-openapi/spec", "//vendor:github.com/golang/glog", + "//vendor:github.com/pborman/uuid", "//vendor:github.com/pkg/errors", "//vendor:github.com/prometheus/client_golang/prometheus", "//vendor:gopkg.in/natefinch/lumberjack.v2", diff --git a/pkg/genericapiserver/authorizer/BUILD b/pkg/genericapiserver/authorizer/BUILD index 9784de226ae..eab84ff054c 100644 --- a/pkg/genericapiserver/authorizer/BUILD +++ b/pkg/genericapiserver/authorizer/BUILD @@ -12,14 +12,17 @@ load( go_library( name = "go_default_library", - srcs = ["authz.go"], + srcs = [ + "builtin.go", + "delegating.go", + ], tags = ["automanaged"], deps = [ "//pkg/auth/authorizer:go_default_library", "//pkg/auth/authorizer/abac:go_default_library", "//pkg/auth/authorizer/union:go_default_library", + "//pkg/client/clientset_generated/release_1_5/typed/authorization/v1beta1:go_default_library", "//pkg/controller/informers:go_default_library", - "//pkg/genericapiserver/options:go_default_library", "//plugin/pkg/auth/authorizer/rbac:go_default_library", "//plugin/pkg/auth/authorizer/webhook:go_default_library", ], @@ -36,6 +39,5 @@ go_test( deps = [ "//pkg/auth/authorizer:go_default_library", "//pkg/auth/user:go_default_library", - "//pkg/genericapiserver/options:go_default_library", ], ) diff --git a/pkg/genericapiserver/authorizer/authz_test.go b/pkg/genericapiserver/authorizer/authz_test.go index 48b6f19ba9a..ec53e5be412 100644 --- a/pkg/genericapiserver/authorizer/authz_test.go +++ b/pkg/genericapiserver/authorizer/authz_test.go @@ -19,8 +19,6 @@ package authorizer import ( "testing" - "k8s.io/kubernetes/pkg/genericapiserver/options" - "k8s.io/kubernetes/pkg/auth/authorizer" "k8s.io/kubernetes/pkg/auth/user" ) @@ -50,67 +48,71 @@ func TestNewAuthorizerFromAuthorizationConfig(t *testing.T) { examplePolicyFile := "../../auth/authorizer/abac/example_policy_file.jsonl" tests := []struct { - modes []string config AuthorizationConfig wantErr bool msg string }{ { // Unknown modes should return errors - modes: []string{"DoesNotExist"}, + config: AuthorizationConfig{AuthorizationModes: []string{"DoesNotExist"}}, wantErr: true, msg: "using a fake mode should have returned an error", }, { // ModeAlwaysAllow and ModeAlwaysDeny should return without authorizationPolicyFile // but error if one is given - modes: []string{options.ModeAlwaysAllow, options.ModeAlwaysDeny}, - msg: "returned an error for valid config", + config: AuthorizationConfig{AuthorizationModes: []string{ModeAlwaysAllow, ModeAlwaysDeny}}, + msg: "returned an error for valid config", }, { // ModeABAC requires a policy file - modes: []string{options.ModeAlwaysAllow, options.ModeAlwaysDeny, options.ModeABAC}, + config: AuthorizationConfig{AuthorizationModes: []string{ModeAlwaysAllow, ModeAlwaysDeny, ModeABAC}}, wantErr: true, msg: "specifying ABAC with no policy file should return an error", }, { // ModeABAC should not error if a valid policy path is provided - modes: []string{options.ModeAlwaysAllow, options.ModeAlwaysDeny, options.ModeABAC}, - config: AuthorizationConfig{PolicyFile: examplePolicyFile}, - msg: "errored while using a valid policy file", + config: AuthorizationConfig{ + AuthorizationModes: []string{ModeAlwaysAllow, ModeAlwaysDeny, ModeABAC}, + PolicyFile: examplePolicyFile, + }, + msg: "errored while using a valid policy file", }, { // Authorization Policy file cannot be used without ModeABAC - modes: []string{options.ModeAlwaysAllow, options.ModeAlwaysDeny}, - config: AuthorizationConfig{PolicyFile: examplePolicyFile}, + config: AuthorizationConfig{ + AuthorizationModes: []string{ModeAlwaysAllow, ModeAlwaysDeny}, + PolicyFile: examplePolicyFile, + }, wantErr: true, msg: "should have errored when Authorization Policy File is used without ModeABAC", }, { // At least one authorizationMode is necessary - modes: []string{}, config: AuthorizationConfig{PolicyFile: examplePolicyFile}, wantErr: true, msg: "should have errored when no authorization modes are passed", }, { // ModeWebhook requires at minimum a target. - modes: []string{options.ModeWebhook}, + config: AuthorizationConfig{AuthorizationModes: []string{ModeWebhook}}, wantErr: true, msg: "should have errored when config was empty with ModeWebhook", }, { // Cannot provide webhook flags without ModeWebhook - modes: []string{options.ModeAlwaysAllow}, - config: AuthorizationConfig{WebhookConfigFile: "authz_webhook_config.yml"}, + config: AuthorizationConfig{ + AuthorizationModes: []string{ModeAlwaysAllow}, + WebhookConfigFile: "authz_webhook_config.yml", + }, wantErr: true, msg: "should have errored when Webhook config file is used without ModeWebhook", }, } for _, tt := range tests { - _, err := NewAuthorizerFromAuthorizationConfig(tt.modes, tt.config) + _, err := NewAuthorizerFromAuthorizationConfig(tt.config) if tt.wantErr && (err == nil) { t.Errorf("NewAuthorizerFromAuthorizationConfig %s", tt.msg) } else if !tt.wantErr && (err != nil) { diff --git a/pkg/genericapiserver/authorizer/authz.go b/pkg/genericapiserver/authorizer/builtin.go similarity index 88% rename from pkg/genericapiserver/authorizer/authz.go rename to pkg/genericapiserver/authorizer/builtin.go index 7088b155b19..86fc05f2b00 100644 --- a/pkg/genericapiserver/authorizer/authz.go +++ b/pkg/genericapiserver/authorizer/builtin.go @@ -25,11 +25,18 @@ import ( "k8s.io/kubernetes/pkg/auth/authorizer/abac" "k8s.io/kubernetes/pkg/auth/authorizer/union" "k8s.io/kubernetes/pkg/controller/informers" - "k8s.io/kubernetes/pkg/genericapiserver/options" "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac" "k8s.io/kubernetes/plugin/pkg/auth/authorizer/webhook" ) +const ( + ModeAlwaysAllow string = "AlwaysAllow" + ModeAlwaysDeny string = "AlwaysDeny" + ModeABAC string = "ABAC" + ModeWebhook string = "Webhook" + ModeRBAC string = "RBAC" +) + // alwaysAllowAuthorizer is an implementation of authorizer.Attributes // which always says yes to an authorization request. // It is useful in tests and when using kubernetes in an open manner. @@ -95,6 +102,8 @@ func NewPrivilegedGroups(groups ...string) *privilegedGroupAuthorizer { } type AuthorizationConfig struct { + AuthorizationModes []string + // Options for ModeABAC // Path to an ABAC policy file. @@ -118,28 +127,27 @@ type AuthorizationConfig struct { } // NewAuthorizerFromAuthorizationConfig returns the right sort of union of multiple authorizer.Authorizer objects -// based on the authorizationMode or an error. authorizationMode should be a comma separated values -// of options.AuthorizationModeChoices. -func NewAuthorizerFromAuthorizationConfig(authorizationModes []string, config AuthorizationConfig) (authorizer.Authorizer, error) { +// based on the authorizationMode or an error. +func NewAuthorizerFromAuthorizationConfig(config AuthorizationConfig) (authorizer.Authorizer, error) { - if len(authorizationModes) == 0 { + if len(config.AuthorizationModes) == 0 { return nil, errors.New("At least one authorization mode should be passed") } var authorizers []authorizer.Authorizer authorizerMap := make(map[string]bool) - for _, authorizationMode := range authorizationModes { + for _, authorizationMode := range config.AuthorizationModes { if authorizerMap[authorizationMode] { return nil, fmt.Errorf("Authorization mode %s specified more than once", authorizationMode) } // Keep cases in sync with constant list above. switch authorizationMode { - case options.ModeAlwaysAllow: + case ModeAlwaysAllow: authorizers = append(authorizers, NewAlwaysAllowAuthorizer()) - case options.ModeAlwaysDeny: + case ModeAlwaysDeny: authorizers = append(authorizers, NewAlwaysDenyAuthorizer()) - case options.ModeABAC: + case ModeABAC: if config.PolicyFile == "" { return nil, errors.New("ABAC's authorization policy file not passed") } @@ -148,7 +156,7 @@ func NewAuthorizerFromAuthorizationConfig(authorizationModes []string, config Au return nil, err } authorizers = append(authorizers, abacAuthorizer) - case options.ModeWebhook: + case ModeWebhook: if config.WebhookConfigFile == "" { return nil, errors.New("Webhook's configuration file not passed") } @@ -159,7 +167,7 @@ func NewAuthorizerFromAuthorizationConfig(authorizationModes []string, config Au return nil, err } authorizers = append(authorizers, webhookAuthorizer) - case options.ModeRBAC: + case ModeRBAC: rbacAuthorizer := rbac.New( config.InformerFactory.Roles().Lister(), config.InformerFactory.RoleBindings().Lister(), @@ -174,13 +182,13 @@ func NewAuthorizerFromAuthorizationConfig(authorizationModes []string, config Au authorizerMap[authorizationMode] = true } - if !authorizerMap[options.ModeABAC] && config.PolicyFile != "" { + if !authorizerMap[ModeABAC] && config.PolicyFile != "" { return nil, errors.New("Cannot specify --authorization-policy-file without mode ABAC") } - if !authorizerMap[options.ModeWebhook] && config.WebhookConfigFile != "" { + if !authorizerMap[ModeWebhook] && config.WebhookConfigFile != "" { return nil, errors.New("Cannot specify --authorization-webhook-config-file without mode Webhook") } - if !authorizerMap[options.ModeRBAC] && config.RBACSuperUser != "" { + if !authorizerMap[ModeRBAC] && config.RBACSuperUser != "" { return nil, errors.New("Cannot specify --authorization-rbac-super-user without mode RBAC") } diff --git a/pkg/genericapiserver/authorizer/delegating.go b/pkg/genericapiserver/authorizer/delegating.go new file mode 100644 index 00000000000..ff7992b502f --- /dev/null +++ b/pkg/genericapiserver/authorizer/delegating.go @@ -0,0 +1,46 @@ +/* +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 authorizer + +import ( + "time" + + "k8s.io/kubernetes/pkg/auth/authorizer" + authorizationclient "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5/typed/authorization/v1beta1" + webhooksar "k8s.io/kubernetes/plugin/pkg/auth/authorizer/webhook" +) + +// DelegatingAuthorizerConfig is the minimal configuration needed to create an authenticator +// built to delegate authorization to a kube API server +type DelegatingAuthorizerConfig struct { + SubjectAccessReviewClient authorizationclient.SubjectAccessReviewInterface + + // AllowCacheTTL is the length of time that a successful authorization response will be cached + AllowCacheTTL time.Duration + + // DenyCacheTTL is the length of time that an unsuccessful authorization response will be cached. + // You generally want more responsive, "deny, try again" flows. + DenyCacheTTL time.Duration +} + +func (c DelegatingAuthorizerConfig) New() (authorizer.Authorizer, error) { + return webhooksar.NewFromInterface( + c.SubjectAccessReviewClient, + c.AllowCacheTTL, + c.DenyCacheTTL, + ) +} diff --git a/pkg/genericapiserver/config.go b/pkg/genericapiserver/config.go index 3f4eb7ef652..e19f8419a0b 100644 --- a/pkg/genericapiserver/config.go +++ b/pkg/genericapiserver/config.go @@ -32,20 +32,25 @@ import ( "github.com/go-openapi/spec" "github.com/golang/glog" + "github.com/pborman/uuid" "gopkg.in/natefinch/lumberjack.v2" "k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/v1" + apiserverauthenticator "k8s.io/kubernetes/pkg/apiserver/authenticator" apiserverfilters "k8s.io/kubernetes/pkg/apiserver/filters" apiserveropenapi "k8s.io/kubernetes/pkg/apiserver/openapi" "k8s.io/kubernetes/pkg/apiserver/request" "k8s.io/kubernetes/pkg/auth/authenticator" "k8s.io/kubernetes/pkg/auth/authorizer" + authorizerunion "k8s.io/kubernetes/pkg/auth/authorizer/union" authhandlers "k8s.io/kubernetes/pkg/auth/handlers" + "k8s.io/kubernetes/pkg/auth/user" "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/cloudprovider" + apiserverauthorizer "k8s.io/kubernetes/pkg/genericapiserver/authorizer" genericfilters "k8s.io/kubernetes/pkg/genericapiserver/filters" "k8s.io/kubernetes/pkg/genericapiserver/mux" "k8s.io/kubernetes/pkg/genericapiserver/openapi/common" @@ -54,9 +59,9 @@ import ( genericvalidation "k8s.io/kubernetes/pkg/genericapiserver/validation" "k8s.io/kubernetes/pkg/runtime" certutil "k8s.io/kubernetes/pkg/util/cert" - utilnet "k8s.io/kubernetes/pkg/util/net" "k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/version" + authenticatorunion "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/union" ) const ( @@ -226,12 +231,77 @@ func NewConfig() *Config { 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.SecurePort = 0 - defaultOptions.InsecurePort = 0 defaultOptions.AuditLogPath = "" return config.ApplyOptions(defaultOptions) } +func (c *Config) ApplySecureServingOptions(secureServing *options.SecureServingOptions) *Config { + if secureServing == nil || secureServing.ServingOptions.BindPort <= 0 { + return c + } + + secureServingInfo := &SecureServingInfo{ + ServingInfo: ServingInfo{ + BindAddress: net.JoinHostPort(secureServing.ServingOptions.BindAddress.String(), strconv.Itoa(secureServing.ServingOptions.BindPort)), + }, + ServerCert: GeneratableKeyCert{ + CertKey: CertKey{ + CertFile: secureServing.ServerCert.CertKey.CertFile, + KeyFile: secureServing.ServerCert.CertKey.KeyFile, + }, + }, + SNICerts: []NamedCertKey{}, + ClientCA: secureServing.ClientCA, + } + if secureServing.ServerCert.CertKey.CertFile == "" && secureServing.ServerCert.CertKey.KeyFile == "" { + secureServingInfo.ServerCert.Generate = true + secureServingInfo.ServerCert.CertFile = path.Join(secureServing.ServerCert.CertDirectory, secureServing.ServerCert.PairName+".crt") + secureServingInfo.ServerCert.KeyFile = path.Join(secureServing.ServerCert.CertDirectory, secureServing.ServerCert.PairName+".key") + } + + secureServingInfo.SNICerts = nil + for _, nkc := range secureServing.SNICertKeys { + secureServingInfo.SNICerts = append(secureServingInfo.SNICerts, NamedCertKey{ + CertKey: CertKey{ + KeyFile: nkc.KeyFile, + CertFile: nkc.CertFile, + }, + Names: nkc.Names, + }) + } + + c.SecureServingInfo = secureServingInfo + c.ReadWritePort = secureServing.ServingOptions.BindPort + + return c +} + +func (c *Config) ApplyInsecureServingOptions(insecureServing *options.ServingOptions) *Config { + if insecureServing == nil || insecureServing.BindPort <= 0 { + return c + } + + c.InsecureServingInfo = &ServingInfo{ + BindAddress: net.JoinHostPort(insecureServing.BindAddress.String(), strconv.Itoa(insecureServing.BindPort)), + } + + return c +} + +func (c *Config) ApplyAuthenticationOptions(o *options.BuiltInAuthenticationOptions) *Config { + if o == nil || o.PasswordFile == nil { + return c + } + + c.SupportsBasicAuth = len(o.PasswordFile.BasicAuthFile) > 0 + return c +} + +func (c *Config) ApplyRBACSuperUser(rbacSuperUser string) *Config { + c.AuthorizerRBACSuperUser = rbacSuperUser + return c +} + // ApplyOptions applies the run options to the method receiver and returns self func (c *Config) ApplyOptions(options *options.ServerRunOptions) *Config { if len(options.AuditLogPath) != 0 { @@ -243,49 +313,6 @@ func (c *Config) ApplyOptions(options *options.ServerRunOptions) *Config { } } - if options.SecurePort > 0 { - secureServingInfo := &SecureServingInfo{ - ServingInfo: ServingInfo{ - BindAddress: net.JoinHostPort(options.BindAddress.String(), strconv.Itoa(options.SecurePort)), - }, - ServerCert: GeneratableKeyCert{ - CertKey: CertKey{ - CertFile: options.TLSCertFile, - KeyFile: options.TLSPrivateKeyFile, - }, - }, - SNICerts: []NamedCertKey{}, - ClientCA: options.ClientCAFile, - } - if options.TLSCertFile == "" && options.TLSPrivateKeyFile == "" { - secureServingInfo.ServerCert.Generate = true - secureServingInfo.ServerCert.CertFile = path.Join(options.CertDirectory, "apiserver.crt") - secureServingInfo.ServerCert.KeyFile = path.Join(options.CertDirectory, "apiserver.key") - } - - secureServingInfo.SNICerts = nil - for _, nkc := range options.SNICertKeys { - secureServingInfo.SNICerts = append(secureServingInfo.SNICerts, NamedCertKey{ - CertKey: CertKey{ - KeyFile: nkc.KeyFile, - CertFile: nkc.CertFile, - }, - Names: nkc.Names, - }) - } - - c.SecureServingInfo = secureServingInfo - c.ReadWritePort = options.SecurePort - } - - if options.InsecurePort > 0 { - insecureServingInfo := &ServingInfo{ - BindAddress: net.JoinHostPort(options.InsecureBindAddress.String(), strconv.Itoa(options.InsecurePort)), - } - c.InsecureServingInfo = insecureServingInfo - } - - c.AuthorizerRBACSuperUser = options.AuthorizationRBACSuperUser c.CorsAllowedOriginList = options.CorsAllowedOriginList c.EnableGarbageCollection = options.EnableGarbageCollection c.EnableProfiling = options.EnableProfiling @@ -295,7 +322,6 @@ func (c *Config) ApplyOptions(options *options.ServerRunOptions) *Config { c.MaxRequestsInFlight = options.MaxRequestsInFlight c.MinRequestTimeout = options.MinRequestTimeout c.PublicAddress = options.AdvertiseAddress - c.SupportsBasicAuth = len(options.BasicAuthFile) > 0 return c } @@ -340,6 +366,25 @@ func (c *Config) Complete() completedConfig { c.DiscoveryAddresses = DefaultDiscoveryAddresses{DefaultAddress: c.ExternalAddress} } + // If the loopbackclientconfig is specified AND it has a token for use against the API server + // wrap the authenticator and authorizer in loopback authentication logic + if c.Authenticator != nil && c.Authorizer != nil && c.LoopbackClientConfig != nil && len(c.LoopbackClientConfig.BearerToken) > 0 { + privilegedLoopbackToken := c.LoopbackClientConfig.BearerToken + var uid = uuid.NewRandom().String() + tokens := make(map[string]*user.DefaultInfo) + tokens[privilegedLoopbackToken] = &user.DefaultInfo{ + Name: user.APIServerUser, + UID: uid, + Groups: []string{user.SystemPrivilegedGroup}, + } + + tokenAuthenticator := apiserverauthenticator.NewAuthenticatorFromTokens(tokens) + c.Authenticator = authenticatorunion.New(tokenAuthenticator, c.Authenticator) + + tokenAuthorizer := apiserverauthorizer.NewPrivilegedGroups(user.SystemPrivilegedGroup) + c.Authorizer = authorizerunion.New(tokenAuthorizer, c.Authorizer) + } + return completedConfig{c} } @@ -408,7 +453,7 @@ func (c completedConfig) New() (*GenericAPIServer, error) { } // MaybeGenerateServingCerts generates serving certificates if requested and needed. -func (c completedConfig) MaybeGenerateServingCerts(alternateIPs ...net.IP) error { +func (c *Config) MaybeGenerateServingCerts(alternateIPs ...net.IP) error { // It would be nice to set a fqdn subject alt name, but only the kubelets know, the apiserver is clueless // alternateDNS = append(alternateDNS, "kubernetes.default.svc.CLUSTER.DNS.NAME") if c.SecureServingInfo != nil && c.SecureServingInfo.ServerCert.Generate && !certutil.CanReadCertOrKey(c.SecureServingInfo.ServerCert.CertFile, c.SecureServingInfo.ServerCert.KeyFile) { @@ -485,17 +530,6 @@ func (s *GenericAPIServer) installAPI(c *Config) { func DefaultAndValidateRunOptions(options *options.ServerRunOptions) { genericvalidation.ValidateRunOptions(options) - // If advertise-address is not specified, use bind-address. If bind-address - // is not usable (unset, 0.0.0.0, or loopback), we will use the host's default - // interface as valid public addr for master (see: util/net#ValidPublicAddrForMaster) - if options.AdvertiseAddress == nil || options.AdvertiseAddress.IsUnspecified() { - hostIP, err := utilnet.ChooseBindAddress(options.BindAddress) - if err != nil { - glog.Fatalf("Unable to find suitable network address.error='%v' . "+ - "Try to set the AdvertiseAddress directly or provide a valid BindAddress to fix this.", err) - } - options.AdvertiseAddress = hostIP - } glog.Infof("Will report %v as public IP address.", options.AdvertiseAddress) // Set default value for ExternalAddress if not specified. diff --git a/pkg/genericapiserver/options/BUILD b/pkg/genericapiserver/options/BUILD index 81339e7e6d4..7280966c8e2 100644 --- a/pkg/genericapiserver/options/BUILD +++ b/pkg/genericapiserver/options/BUILD @@ -13,10 +13,12 @@ load( go_library( name = "go_default_library", srcs = [ - "authenticator.go", + "authentication.go", + "authorization.go", "doc.go", - "etcd_options.go", + "etcd.go", "server_run_options.go", + "serving.go", ], tags = ["automanaged"], deps = [ @@ -24,8 +26,12 @@ go_library( "//pkg/api:go_default_library", "//pkg/apimachinery/registered:go_default_library", "//pkg/apiserver/authenticator:go_default_library", - "//pkg/client/clientset_generated/internalclientset:go_default_library", + "//pkg/client/clientset_generated/release_1_5/typed/authentication/v1beta1:go_default_library", + "//pkg/client/clientset_generated/release_1_5/typed/authorization/v1beta1:go_default_library", "//pkg/client/restclient:go_default_library", + "//pkg/client/unversioned/clientcmd:go_default_library", + "//pkg/controller/informers:go_default_library", + "//pkg/genericapiserver/authorizer:go_default_library", "//pkg/runtime/schema:go_default_library", "//pkg/storage/storagebackend:go_default_library", "//pkg/util/config:go_default_library", diff --git a/pkg/genericapiserver/options/authentication.go b/pkg/genericapiserver/options/authentication.go new file mode 100644 index 00000000000..ecf09887626 --- /dev/null +++ b/pkg/genericapiserver/options/authentication.go @@ -0,0 +1,377 @@ +/* +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 options + +import ( + "time" + + "github.com/spf13/pflag" + + "k8s.io/kubernetes/pkg/apiserver/authenticator" + authenticationclient "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5/typed/authentication/v1beta1" + "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" +) + +type BuiltInAuthenticationOptions struct { + Anonymous *AnonymousAuthenticationOptions + AnyToken *AnyTokenAuthenticationOptions + Keystone *KeystoneAuthenticationOptions + OIDC *OIDCAuthenticationOptions + PasswordFile *PasswordFileAuthenticationOptions + RequestHeader *RequestHeaderAuthenticationOptions + ServiceAccounts *ServiceAccountAuthenticationOptions + TokenFile *TokenFileAuthenticationOptions + WebHook *WebHookAuthenticationOptions +} + +type AnyTokenAuthenticationOptions struct { + Allow bool +} + +type AnonymousAuthenticationOptions struct { + Allow bool +} + +type KeystoneAuthenticationOptions struct { + URL string + CAFile string +} + +type OIDCAuthenticationOptions struct { + CAFile string + ClientID string + IssuerURL string + UsernameClaim string + GroupsClaim string +} + +type PasswordFileAuthenticationOptions struct { + BasicAuthFile string +} + +type RequestHeaderAuthenticationOptions struct { + UsernameHeaders []string + ClientCAFile string + AllowedNames []string +} + +type ServiceAccountAuthenticationOptions struct { + KeyFiles []string + Lookup bool +} + +type TokenFileAuthenticationOptions struct { + TokenFile string +} + +type WebHookAuthenticationOptions struct { + ConfigFile string + CacheTTL time.Duration +} + +func NewBuiltInAuthenticationOptions() *BuiltInAuthenticationOptions { + return &BuiltInAuthenticationOptions{} +} + +func (s *BuiltInAuthenticationOptions) WithAll() *BuiltInAuthenticationOptions { + return s. + WithAnyonymous(). + WithAnyToken(). + WithKeystone(). + WithOIDC(). + WithPasswordFile(). + WithRequestHeader(). + WithServiceAccounts(). + WithTokenFile(). + WithWebHook() +} + +func (s *BuiltInAuthenticationOptions) WithAnyonymous() *BuiltInAuthenticationOptions { + s.Anonymous = &AnonymousAuthenticationOptions{Allow: true} + return s +} + +func (s *BuiltInAuthenticationOptions) WithAnyToken() *BuiltInAuthenticationOptions { + s.AnyToken = &AnyTokenAuthenticationOptions{} + return s +} + +func (s *BuiltInAuthenticationOptions) WithKeystone() *BuiltInAuthenticationOptions { + s.Keystone = &KeystoneAuthenticationOptions{} + return s +} + +func (s *BuiltInAuthenticationOptions) WithOIDC() *BuiltInAuthenticationOptions { + s.OIDC = &OIDCAuthenticationOptions{} + return s +} + +func (s *BuiltInAuthenticationOptions) WithPasswordFile() *BuiltInAuthenticationOptions { + s.PasswordFile = &PasswordFileAuthenticationOptions{} + return s +} + +func (s *BuiltInAuthenticationOptions) WithRequestHeader() *BuiltInAuthenticationOptions { + s.RequestHeader = &RequestHeaderAuthenticationOptions{} + return s +} + +func (s *BuiltInAuthenticationOptions) WithServiceAccounts() *BuiltInAuthenticationOptions { + s.ServiceAccounts = &ServiceAccountAuthenticationOptions{} + return s +} + +func (s *BuiltInAuthenticationOptions) WithTokenFile() *BuiltInAuthenticationOptions { + s.TokenFile = &TokenFileAuthenticationOptions{} + return s +} + +func (s *BuiltInAuthenticationOptions) WithWebHook() *BuiltInAuthenticationOptions { + s.WebHook = &WebHookAuthenticationOptions{ + CacheTTL: 2 * time.Minute, + } + return s +} + +func (s *BuiltInAuthenticationOptions) Validate() []error { + allErrors := []error{} + return allErrors +} + +func (s *BuiltInAuthenticationOptions) AddFlags(fs *pflag.FlagSet) { + if s.Anonymous != nil { + fs.BoolVar(&s.Anonymous.Allow, "anonymous-auth", s.Anonymous.Allow, ""+ + "Enables anonymous requests to the secure port of the API 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.") + } + + if s.AnyToken != nil { + fs.BoolVar(&s.AnyToken.Allow, "insecure-allow-any-token", s.AnyToken.Allow, ""+ + "If set, your server will be INSECURE. Any token will be allowed and user information will be parsed "+ + "from the token as `username/group1,group2`") + + } + + if s.Keystone != nil { + fs.StringVar(&s.Keystone.URL, "experimental-keystone-url", s.Keystone.URL, + "If passed, activates the keystone authentication plugin.") + + fs.StringVar(&s.Keystone.CAFile, "experimental-keystone-ca-file", s.Keystone.CAFile, ""+ + "If set, the Keystone server's certificate will be verified by one of the authorities "+ + "in the experimental-keystone-ca-file, otherwise the host's root CA set will be used.") + } + + if s.OIDC != nil { + fs.StringVar(&s.OIDC.IssuerURL, "oidc-issuer-url", s.OIDC.IssuerURL, ""+ + "The URL of the OpenID issuer, only HTTPS scheme will be accepted. "+ + "If set, it will be used to verify the OIDC JSON Web Token (JWT).") + + fs.StringVar(&s.OIDC.ClientID, "oidc-client-id", s.OIDC.ClientID, + "The client ID for the OpenID Connect client, must be set if oidc-issuer-url is set.") + + fs.StringVar(&s.OIDC.CAFile, "oidc-ca-file", s.OIDC.CAFile, ""+ + "If set, the OpenID server's certificate will be verified by one of the authorities "+ + "in the oidc-ca-file, otherwise the host's root CA set will be used.") + + fs.StringVar(&s.OIDC.UsernameClaim, "oidc-username-claim", "sub", ""+ + "The OpenID claim to use as the user name. Note that claims other than the default ('sub') "+ + "is not guaranteed to be unique and immutable. This flag is experimental, please see "+ + "the authentication documentation for further details.") + + fs.StringVar(&s.OIDC.GroupsClaim, "oidc-groups-claim", "", ""+ + "If provided, the name of a custom OpenID Connect claim for specifying user groups. "+ + "The claim value is expected to be a string or array of strings. This flag is experimental, "+ + "please see the authentication documentation for further details.") + } + + if s.PasswordFile != nil { + fs.StringVar(&s.PasswordFile.BasicAuthFile, "basic-auth-file", s.PasswordFile.BasicAuthFile, ""+ + "If set, the file that will be used to admit requests to the secure port of the API server "+ + "via http basic authentication.") + } + + if s.RequestHeader != nil { + fs.StringSliceVar(&s.RequestHeader.UsernameHeaders, "requestheader-username-headers", s.RequestHeader.UsernameHeaders, ""+ + "List of request headers to inspect for usernames. X-Remote-User is common.") + + fs.StringVar(&s.RequestHeader.ClientCAFile, "requestheader-client-ca-file", s.RequestHeader.ClientCAFile, ""+ + "Root certificate bundle to use to verify client certificates on incoming requests "+ + "before trusting usernames in headers specified by --requestheader-username-headers") + + fs.StringSliceVar(&s.RequestHeader.AllowedNames, "requestheader-allowed-names", s.RequestHeader.AllowedNames, ""+ + "List of client certificate common names to allow to provide usernames in headers "+ + "specified by --requestheader-username-headers. If empty, any client certificate validated "+ + "by the authorities in --requestheader-client-ca-file is allowed.") + } + + if s.ServiceAccounts != nil { + fs.StringArrayVar(&s.ServiceAccounts.KeyFiles, "service-account-key-file", s.ServiceAccounts.KeyFiles, ""+ + "File containing PEM-encoded x509 RSA or ECDSA private or public keys, used to verify "+ + "ServiceAccount tokens. If unspecified, --tls-private-key-file is used. "+ + "The specified file can contain multiple keys, and the flag can be specified multiple times with different files.") + + fs.BoolVar(&s.ServiceAccounts.Lookup, "service-account-lookup", s.ServiceAccounts.Lookup, + "If true, validate ServiceAccount tokens exist in etcd as part of authentication.") + } + + if s.TokenFile != nil { + fs.StringVar(&s.TokenFile.TokenFile, "token-auth-file", s.TokenFile.TokenFile, ""+ + "If set, the file that will be used to secure the secure port of the API server "+ + "via token authentication.") + } + + if s.WebHook != nil { + fs.StringVar(&s.WebHook.ConfigFile, "authentication-token-webhook-config-file", s.WebHook.ConfigFile, ""+ + "File with webhook configuration for token authentication in kubeconfig format. "+ + "The API server will query the remote service to determine authentication for bearer tokens.") + + fs.DurationVar(&s.WebHook.CacheTTL, "authentication-token-webhook-cache-ttl", s.WebHook.CacheTTL, + "The duration to cache responses from the webhook token authenticator. Default is 2m.") + } +} + +func (s *BuiltInAuthenticationOptions) ToAuthenticationConfig(clientCAFile string) authenticator.AuthenticatorConfig { + ret := authenticator.AuthenticatorConfig{ + ClientCAFile: clientCAFile, + } + if s.Anonymous != nil { + ret.Anonymous = s.Anonymous.Allow + } + + if s.AnyToken != nil { + ret.AnyToken = s.AnyToken.Allow + } + + if s.Keystone != nil { + ret.KeystoneURL = s.Keystone.URL + ret.KeystoneCAFile = s.Keystone.CAFile + } + + if s.OIDC != nil { + ret.OIDCCAFile = s.OIDC.CAFile + ret.OIDCClientID = s.OIDC.ClientID + ret.OIDCGroupsClaim = s.OIDC.GroupsClaim + ret.OIDCIssuerURL = s.OIDC.IssuerURL + ret.OIDCUsernameClaim = s.OIDC.UsernameClaim + } + + if s.PasswordFile != nil { + ret.BasicAuthFile = s.PasswordFile.BasicAuthFile + } + + if s.RequestHeader != nil { + ret.RequestHeaderConfig = s.RequestHeader.AuthenticationRequestHeaderConfig() + } + + if s.ServiceAccounts != nil { + ret.ServiceAccountKeyFiles = s.ServiceAccounts.KeyFiles + ret.ServiceAccountLookup = s.ServiceAccounts.Lookup + } + + if s.TokenFile != nil { + ret.TokenAuthFile = s.TokenFile.TokenFile + } + + if s.WebHook != nil { + ret.WebhookTokenAuthnConfigFile = s.WebHook.ConfigFile + ret.WebhookTokenAuthnCacheTTL = s.WebHook.CacheTTL + } + + return ret +} + +// AuthenticationRequestHeaderConfig returns an authenticator config object for these options +// if necessary. nil otherwise. +func (s *RequestHeaderAuthenticationOptions) AuthenticationRequestHeaderConfig() *authenticator.RequestHeaderConfig { + if len(s.UsernameHeaders) == 0 { + return nil + } + + return &authenticator.RequestHeaderConfig{ + UsernameHeaders: s.UsernameHeaders, + ClientCA: s.ClientCAFile, + AllowedClientNames: s.AllowedNames, + } +} + +// DelegatingAuthenticationOptions provides an easy way for composing API servers to delegate their authentication to +// the root kube API server +type DelegatingAuthenticationOptions struct { + // RemoteKubeConfigFile is the file to use to connect to a "normal" kube API server which hosts the + // TokenAccessReview.authentication.k8s.io endpoint for checking tokens. + RemoteKubeConfigFile string + + // CacheTTL is the length of time that a token authentication answer will be cached. + CacheTTL time.Duration +} + +func NewDelegatingAuthenticationOptions() *DelegatingAuthenticationOptions { + return &DelegatingAuthenticationOptions{ + CacheTTL: 5 * time.Minute, + } +} + +func (s *DelegatingAuthenticationOptions) Validate() []error { + allErrors := []error{} + return allErrors +} + +func (s *DelegatingAuthenticationOptions) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&s.RemoteKubeConfigFile, "authentication-kubeconfig", s.RemoteKubeConfigFile, ""+ + "kubeconfig file pointing at the 'core' kubernetes server with enough rights to create "+ + " tokenaccessreviews.authentication.k8s.io.") +} + +func (s *DelegatingAuthenticationOptions) ToAuthenticationConfig(clientCAFile string) (authenticator.DelegatingAuthenticatorConfig, error) { + tokenClient, err := s.newTokenAccessReview() + if err != nil { + return authenticator.DelegatingAuthenticatorConfig{}, err + } + + ret := authenticator.DelegatingAuthenticatorConfig{ + Anonymous: true, + TokenAccessReviewClient: tokenClient, + CacheTTL: s.CacheTTL, + ClientCAFile: clientCAFile, + } + return ret, nil +} + +func (s *DelegatingAuthenticationOptions) newTokenAccessReview() (authenticationclient.TokenReviewInterface, error) { + if len(s.RemoteKubeConfigFile) == 0 { + return nil, nil + } + + loadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: s.RemoteKubeConfigFile} + loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}) + + clientConfig, err := loader.ClientConfig() + if err != nil { + return nil, err + } + // set high qps/burst limits since this will effectively limit API server responsiveness + clientConfig.QPS = 200 + clientConfig.Burst = 400 + + client, err := authenticationclient.NewForConfig(clientConfig) + if err != nil { + return nil, err + } + + return client.TokenReviews(), nil +} diff --git a/pkg/genericapiserver/options/authenticator.go b/pkg/genericapiserver/options/authenticator.go deleted file mode 100644 index 58f943050e4..00000000000 --- a/pkg/genericapiserver/options/authenticator.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -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 options - -import ( - "k8s.io/kubernetes/pkg/apiserver/authenticator" -) - -// AuthenticationRequestHeaderConfig returns an authenticator config object for these options -// if necessary. nil otherwise. -func (s *ServerRunOptions) AuthenticationRequestHeaderConfig() *authenticator.RequestHeaderConfig { - if len(s.RequestHeaderUsernameHeaders) == 0 { - return nil - } - - return &authenticator.RequestHeaderConfig{ - UsernameHeaders: s.RequestHeaderUsernameHeaders, - ClientCA: s.RequestHeaderClientCAFile, - AllowedClientNames: s.RequestHeaderAllowedNames, - } -} diff --git a/pkg/genericapiserver/options/authorization.go b/pkg/genericapiserver/options/authorization.go new file mode 100644 index 00000000000..869e511b5c4 --- /dev/null +++ b/pkg/genericapiserver/options/authorization.go @@ -0,0 +1,167 @@ +/* +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 options + +import ( + "strings" + "time" + + "github.com/spf13/pflag" + + authorizationclient "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5/typed/authorization/v1beta1" + "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + "k8s.io/kubernetes/pkg/controller/informers" + "k8s.io/kubernetes/pkg/genericapiserver/authorizer" +) + +var AuthorizationModeChoices = []string{authorizer.ModeAlwaysAllow, authorizer.ModeAlwaysDeny, authorizer.ModeABAC, authorizer.ModeWebhook, authorizer.ModeRBAC} + +type BuiltInAuthorizationOptions struct { + Mode string + PolicyFile string + WebhookConfigFile string + WebhookCacheAuthorizedTTL time.Duration + WebhookCacheUnauthorizedTTL time.Duration + RBACSuperUser string +} + +func NewBuiltInAuthorizationOptions() *BuiltInAuthorizationOptions { + return &BuiltInAuthorizationOptions{ + Mode: authorizer.ModeAlwaysAllow, + WebhookCacheAuthorizedTTL: 5 * time.Minute, + WebhookCacheUnauthorizedTTL: 30 * time.Second, + } +} + +func (s *BuiltInAuthorizationOptions) Validate() []error { + allErrors := []error{} + return allErrors +} + +func (s *BuiltInAuthorizationOptions) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&s.Mode, "authorization-mode", s.Mode, ""+ + "Ordered list of plug-ins to do authorization on secure port. Comma-delimited list of: "+ + strings.Join(AuthorizationModeChoices, ",")+".") + + fs.StringVar(&s.PolicyFile, "authorization-policy-file", s.PolicyFile, ""+ + "File with authorization policy in csv format, used with --authorization-mode=ABAC, on the secure port.") + + fs.StringVar(&s.WebhookConfigFile, "authorization-webhook-config-file", s.WebhookConfigFile, ""+ + "File with webhook configuration in kubeconfig format, used with --authorization-mode=Webhook. "+ + "The API server will query the remote service to determine access on the API server's secure port.") + + fs.DurationVar(&s.WebhookCacheAuthorizedTTL, "authorization-webhook-cache-authorized-ttl", + s.WebhookCacheAuthorizedTTL, + "The duration to cache 'authorized' responses from the webhook authorizer. Default is 5m.") + + fs.DurationVar(&s.WebhookCacheUnauthorizedTTL, + "authorization-webhook-cache-unauthorized-ttl", s.WebhookCacheUnauthorizedTTL, + "The duration to cache 'unauthorized' responses from the webhook authorizer. Default is 30s.") + + fs.StringVar(&s.RBACSuperUser, "authorization-rbac-super-user", s.RBACSuperUser, ""+ + "If specified, a username which avoids RBAC authorization checks and role binding "+ + "privilege escalation checks, to be used with --authorization-mode=RBAC.") + +} + +func (s *BuiltInAuthorizationOptions) ToAuthorizationConfig(informerFactory informers.SharedInformerFactory) authorizer.AuthorizationConfig { + modes := []string{} + if len(s.Mode) > 0 { + modes = strings.Split(s.Mode, ",") + } + + return authorizer.AuthorizationConfig{ + AuthorizationModes: modes, + PolicyFile: s.PolicyFile, + WebhookConfigFile: s.WebhookConfigFile, + WebhookCacheAuthorizedTTL: s.WebhookCacheAuthorizedTTL, + WebhookCacheUnauthorizedTTL: s.WebhookCacheUnauthorizedTTL, + RBACSuperUser: s.RBACSuperUser, + InformerFactory: informerFactory, + } +} + +// DelegatingAuthorizationOptions provides an easy way for composing API servers to delegate their authorization to +// the root kube API server +type DelegatingAuthorizationOptions struct { + // RemoteKubeConfigFile is the file to use to connect to a "normal" kube API server which hosts the + // SubjectAccessReview.authorization.k8s.io endpoint for checking tokens. + RemoteKubeConfigFile string + + // AllowCacheTTL is the length of time that a successful authorization response will be cached + AllowCacheTTL time.Duration + + // DenyCacheTTL is the length of time that an unsuccessful authorization response will be cached. + // You generally want more responsive, "deny, try again" flows. + DenyCacheTTL time.Duration +} + +func NewDelegatingAuthorizationOptions() *DelegatingAuthorizationOptions { + return &DelegatingAuthorizationOptions{ + AllowCacheTTL: 5 * time.Minute, + DenyCacheTTL: 30 * time.Second, + } +} + +func (s *DelegatingAuthorizationOptions) Validate() []error { + allErrors := []error{} + return allErrors +} + +func (s *DelegatingAuthorizationOptions) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&s.RemoteKubeConfigFile, "authorization-kubeconfig", s.RemoteKubeConfigFile, ""+ + "kubeconfig file pointing at the 'core' kubernetes server with enough rights to create "+ + " subjectaccessreviews.authorization.k8s.io.") +} + +func (s *DelegatingAuthorizationOptions) ToAuthorizationConfig() (authorizer.DelegatingAuthorizerConfig, error) { + sarClient, err := s.newSubjectAccessReview() + if err != nil { + return authorizer.DelegatingAuthorizerConfig{}, err + } + + ret := authorizer.DelegatingAuthorizerConfig{ + SubjectAccessReviewClient: sarClient, + AllowCacheTTL: s.AllowCacheTTL, + DenyCacheTTL: s.DenyCacheTTL, + } + return ret, nil +} + +func (s *DelegatingAuthorizationOptions) newSubjectAccessReview() (authorizationclient.SubjectAccessReviewInterface, error) { + if len(s.RemoteKubeConfigFile) == 0 { + return nil, nil + } + + loadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: s.RemoteKubeConfigFile} + loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}) + + clientConfig, err := loader.ClientConfig() + if err != nil { + return nil, err + } + // set high qps/burst limits since this will effectively limit API server responsiveness + clientConfig.QPS = 200 + clientConfig.Burst = 400 + + client, err := authorizationclient.NewForConfig(clientConfig) + if err != nil { + return nil, err + } + + return client.SubjectAccessReviews(), nil +} diff --git a/pkg/genericapiserver/options/etcd_options.go b/pkg/genericapiserver/options/etcd.go similarity index 64% rename from pkg/genericapiserver/options/etcd_options.go rename to pkg/genericapiserver/options/etcd.go index c991414b371..4d1b3124505 100644 --- a/pkg/genericapiserver/options/etcd_options.go +++ b/pkg/genericapiserver/options/etcd.go @@ -17,20 +17,55 @@ limitations under the License. package options import ( + "fmt" + "github.com/spf13/pflag" + + "k8s.io/kubernetes/pkg/storage/storagebackend" ) const ( DefaultEtcdPathPrefix = "/registry" ) -// AddEtcdFlags adds flags related to etcd storage for a specific APIServer to the specified FlagSet -func (s *ServerRunOptions) AddEtcdStorageFlags(fs *pflag.FlagSet) { +type EtcdOptions struct { + StorageConfig storagebackend.Config + EtcdServersOverrides []string +} + +func NewEtcdOptions() *EtcdOptions { + return &EtcdOptions{ + StorageConfig: storagebackend.Config{ + Prefix: DefaultEtcdPathPrefix, + // Default cache size to 0 - if unset, its size will be set based on target + // memory usage. + DeserializationCacheSize: 0, + }, + } +} + +func (s *EtcdOptions) Validate() []error { + allErrors := []error{} + if len(s.StorageConfig.ServerList) == 0 { + allErrors = append(allErrors, fmt.Errorf("--etcd-servers must be specified")) + } + + return allErrors +} + +// AddEtcdFlags adds flags related to etcd storage for a specific APIServer to the specified FlagSet +func (s *EtcdOptions) AddFlags(fs *pflag.FlagSet) { fs.StringSliceVar(&s.EtcdServersOverrides, "etcd-servers-overrides", s.EtcdServersOverrides, ""+ "Per-resource etcd servers overrides, comma separated. The individual override "+ "format: group/resource#servers, where servers are http://ip:port, semicolon separated.") + fs.StringVar(&s.StorageConfig.Type, "storage-backend", s.StorageConfig.Type, + "The storage backend for persistence. Options: 'etcd2' (default), 'etcd3'.") + + fs.IntVar(&s.StorageConfig.DeserializationCacheSize, "deserialization-cache-size", s.StorageConfig.DeserializationCacheSize, + "Number of deserialized json objects to cache in memory.") + fs.StringSliceVar(&s.StorageConfig.ServerList, "etcd-servers", s.StorageConfig.ServerList, "List of etcd servers to connect with (scheme://ip:port), comma separated.") diff --git a/pkg/genericapiserver/options/server_run_options.go b/pkg/genericapiserver/options/server_run_options.go index 8505993aa1f..51b96add8fc 100644 --- a/pkg/genericapiserver/options/server_run_options.go +++ b/pkg/genericapiserver/options/server_run_options.go @@ -17,19 +17,14 @@ limitations under the License. package options import ( - "errors" + "fmt" "net" - "strconv" "strings" - "time" "k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apimachinery/registered" - clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/runtime/schema" - "k8s.io/kubernetes/pkg/storage/storagebackend" "k8s.io/kubernetes/pkg/util/config" utilnet "k8s.io/kubernetes/pkg/util/net" @@ -43,127 +38,89 @@ const ( var DefaultServiceNodePortRange = utilnet.PortRange{Base: 30000, Size: 2768} -const ( - ModeAlwaysAllow string = "AlwaysAllow" - ModeAlwaysDeny string = "AlwaysDeny" - ModeABAC string = "ABAC" - ModeWebhook string = "Webhook" - ModeRBAC string = "RBAC" -) - -var AuthorizationModeChoices = []string{ModeAlwaysAllow, ModeAlwaysDeny, ModeABAC, ModeWebhook, ModeRBAC} - // ServerRunOptions contains the options while running a generic api server. type ServerRunOptions struct { AdmissionControl string AdmissionControlConfigFile string AdvertiseAddress net.IP - // Authorization mode and associated flags. - AuthorizationMode string - AuthorizationPolicyFile string - AuthorizationWebhookConfigFile string - AuthorizationWebhookCacheAuthorizedTTL time.Duration - AuthorizationWebhookCacheUnauthorizedTTL time.Duration - AuthorizationRBACSuperUser string - - AnonymousAuth bool - BasicAuthFile string - BindAddress net.IP - CertDirectory string - ClientCAFile string - CloudConfigFile string - CloudProvider string - CorsAllowedOriginList []string - DefaultStorageMediaType string - DeleteCollectionWorkers int - AuditLogPath string - AuditLogMaxAge int - AuditLogMaxBackups int - AuditLogMaxSize int - EnableGarbageCollection bool - EnableProfiling bool - EnableContentionProfiling bool - EnableSwaggerUI bool - EnableWatchCache bool - EtcdServersOverrides []string - StorageConfig storagebackend.Config - ExternalHost string - InsecureBindAddress net.IP - InsecurePort int - KeystoneURL string - KeystoneCAFile string - KubernetesServiceNodePort int - LongRunningRequestRE string - MasterCount int - MasterServiceNamespace string - MaxRequestsInFlight int - MinRequestTimeout int - OIDCCAFile string - OIDCClientID string - OIDCIssuerURL string - OIDCUsernameClaim string - OIDCGroupsClaim string - RequestHeaderUsernameHeaders []string - RequestHeaderClientCAFile string - RequestHeaderAllowedNames []string - RuntimeConfig config.ConfigurationMap - SecurePort int - ServiceClusterIPRange net.IPNet // TODO: make this a list - ServiceNodePortRange utilnet.PortRange - StorageVersions string + CloudConfigFile string + CloudProvider string + CorsAllowedOriginList []string + DefaultStorageMediaType string + DeleteCollectionWorkers int + AuditLogPath string + AuditLogMaxAge int + AuditLogMaxBackups int + AuditLogMaxSize int + EnableGarbageCollection bool + EnableProfiling bool + EnableContentionProfiling bool + EnableSwaggerUI bool + EnableWatchCache bool + ExternalHost string + KubernetesServiceNodePort int + LongRunningRequestRE string + MasterCount int + MasterServiceNamespace string + MaxRequestsInFlight int + MinRequestTimeout int + RuntimeConfig config.ConfigurationMap + ServiceClusterIPRange net.IPNet // TODO: make this a list + ServiceNodePortRange utilnet.PortRange + StorageVersions string // The default values for StorageVersions. StorageVersions overrides // these; you can change this if you want to change the defaults (e.g., // for testing). This is not actually exposed as a flag. DefaultStorageVersions string TargetRAMMB int TLSCAFile string - TLSCertFile string - TLSPrivateKeyFile string - SNICertKeys []config.NamedCertKey - TokenAuthFile string - EnableAnyToken bool WatchCacheSizes []string } func NewServerRunOptions() *ServerRunOptions { return &ServerRunOptions{ - AdmissionControl: "AlwaysAdmit", - AnonymousAuth: true, - AuthorizationMode: "AlwaysAllow", - AuthorizationWebhookCacheAuthorizedTTL: 5 * time.Minute, - AuthorizationWebhookCacheUnauthorizedTTL: 30 * time.Second, - BindAddress: net.ParseIP("0.0.0.0"), - CertDirectory: "/var/run/kubernetes", - DefaultStorageMediaType: "application/json", - DefaultStorageVersions: registered.AllPreferredGroupVersions(), - DeleteCollectionWorkers: 1, - EnableGarbageCollection: true, - EnableProfiling: true, - EnableContentionProfiling: false, - EnableWatchCache: true, - InsecureBindAddress: net.ParseIP("127.0.0.1"), - InsecurePort: 8080, - LongRunningRequestRE: DefaultLongRunningRequestRE, - MasterCount: 1, - MasterServiceNamespace: api.NamespaceDefault, - MaxRequestsInFlight: 400, - MinRequestTimeout: 1800, - RuntimeConfig: make(config.ConfigurationMap), - SecurePort: 6443, - ServiceNodePortRange: DefaultServiceNodePortRange, - StorageVersions: registered.AllPreferredGroupVersions(), + AdmissionControl: "AlwaysAdmit", + DefaultStorageMediaType: "application/json", + DefaultStorageVersions: registered.AllPreferredGroupVersions(), + DeleteCollectionWorkers: 1, + EnableGarbageCollection: true, + EnableProfiling: true, + EnableContentionProfiling: false, + EnableWatchCache: true, + LongRunningRequestRE: DefaultLongRunningRequestRE, + MasterCount: 1, + MasterServiceNamespace: api.NamespaceDefault, + MaxRequestsInFlight: 400, + MinRequestTimeout: 1800, + RuntimeConfig: make(config.ConfigurationMap), + ServiceNodePortRange: DefaultServiceNodePortRange, + StorageVersions: registered.AllPreferredGroupVersions(), } } -func (o *ServerRunOptions) WithEtcdOptions() *ServerRunOptions { - o.StorageConfig = storagebackend.Config{ - Prefix: DefaultEtcdPathPrefix, - // Default cache size to 0 - if unset, its size will be set based on target - // memory usage. - DeserializationCacheSize: 0, +func (s *ServerRunOptions) DefaultExternalAddress(secure *SecureServingOptions, insecure *ServingOptions) error { + if s.AdvertiseAddress == nil || s.AdvertiseAddress.IsUnspecified() { + switch { + case secure != nil: + hostIP, err := secure.ServingOptions.DefaultExternalAddress() + if err != nil { + return fmt.Errorf("Unable to find suitable network address.error='%v'. "+ + "Try to set the AdvertiseAddress directly or provide a valid BindAddress to fix this.", err) + } + s.AdvertiseAddress = hostIP + + case insecure != nil: + hostIP, err := insecure.DefaultExternalAddress() + if err != nil { + return fmt.Errorf("Unable to find suitable network address.error='%v'. "+ + "Try to set the AdvertiseAddress directly or provide a valid BindAddress to fix this.", err) + } + s.AdvertiseAddress = hostIP + } } - return o + + return nil } // StorageGroupsToEncodingVersion returns a map from group name to group version, @@ -212,43 +169,6 @@ func mergeGroupVersionIntoMap(gvList string, dest map[string]schema.GroupVersion return nil } -// Returns a clientset which can be used to talk to this apiserver. -func (s *ServerRunOptions) NewSelfClient(token string) (clientset.Interface, error) { - clientConfig, err := s.NewSelfClientConfig(token) - if err != nil { - return nil, err - } - return clientset.NewForConfig(clientConfig) -} - -// Returns a clientconfig which can be used to talk to this apiserver. -func (s *ServerRunOptions) NewSelfClientConfig(token string) (*restclient.Config, error) { - clientConfig := &restclient.Config{ - // Increase QPS limits. The client is currently passed to all admission plugins, - // and those can be throttled in case of higher load on apiserver - see #22340 and #22422 - // for more details. Once #22422 is fixed, we may want to remove it. - QPS: 50, - Burst: 100, - } - - // Use secure port if the TLSCAFile is specified - if s.SecurePort > 0 && len(s.TLSCAFile) > 0 { - host := s.BindAddress.String() - if host == "0.0.0.0" { - host = "localhost" - } - clientConfig.Host = "https://" + net.JoinHostPort(host, strconv.Itoa(s.SecurePort)) - clientConfig.CAFile = s.TLSCAFile - clientConfig.BearerToken = token - } else if s.InsecurePort > 0 { - clientConfig.Host = net.JoinHostPort(s.InsecureBindAddress.String(), strconv.Itoa(s.InsecurePort)) - } else { - return nil, errors.New("Unable to set url for apiserver local client") - } - - return clientConfig, nil -} - // AddFlags adds flags for a specific APIServer to the specified FlagSet func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) { // Note: the weird ""+ in below lines seems to be the only way to get gofmt to @@ -267,56 +187,6 @@ func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) { "will be used. If --bind-address is unspecified, the host's default interface will "+ "be used.") - fs.StringVar(&s.AuthorizationMode, "authorization-mode", s.AuthorizationMode, ""+ - "Ordered list of plug-ins to do authorization on secure port. Comma-delimited list of: "+ - strings.Join(AuthorizationModeChoices, ",")+".") - - fs.StringVar(&s.AuthorizationPolicyFile, "authorization-policy-file", s.AuthorizationPolicyFile, ""+ - "File with authorization policy in csv format, used with --authorization-mode=ABAC, on the secure port.") - - fs.StringVar(&s.AuthorizationWebhookConfigFile, "authorization-webhook-config-file", s.AuthorizationWebhookConfigFile, ""+ - "File with webhook configuration in kubeconfig format, used with --authorization-mode=Webhook. "+ - "The API server will query the remote service to determine access on the API server's secure port.") - - fs.DurationVar(&s.AuthorizationWebhookCacheAuthorizedTTL, "authorization-webhook-cache-authorized-ttl", - s.AuthorizationWebhookCacheAuthorizedTTL, - "The duration to cache 'authorized' responses from the webhook authorizer. Default is 5m.") - - fs.DurationVar(&s.AuthorizationWebhookCacheUnauthorizedTTL, - "authorization-webhook-cache-unauthorized-ttl", s.AuthorizationWebhookCacheUnauthorizedTTL, - "The duration to cache 'unauthorized' responses from the webhook authorizer. Default is 30s.") - - fs.StringVar(&s.AuthorizationRBACSuperUser, "authorization-rbac-super-user", s.AuthorizationRBACSuperUser, ""+ - "If specified, a username which avoids RBAC authorization checks and role binding "+ - "privilege escalation checks, to be used with --authorization-mode=RBAC.") - - fs.BoolVar(&s.AnonymousAuth, "anonymous-auth", s.AnonymousAuth, ""+ - "Enables anonymous requests to the secure port of the API 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.StringVar(&s.BasicAuthFile, "basic-auth-file", s.BasicAuthFile, ""+ - "If set, the file that will be used to admit requests to the secure port of the API server "+ - "via http basic authentication.") - - fs.IPVar(&s.BindAddress, "public-address-override", s.BindAddress, - "DEPRECATED: see --bind-address instead.") - fs.MarkDeprecated("public-address-override", "see --bind-address instead.") - - fs.IPVar(&s.BindAddress, "bind-address", s.BindAddress, ""+ - "The IP address on which to listen for the --secure-port port. The "+ - "associated interface(s) must be reachable by the rest of the cluster, and by CLI/web "+ - "clients. If blank, all interfaces will be used (0.0.0.0).") - - fs.StringVar(&s.CertDirectory, "cert-dir", s.CertDirectory, ""+ - "The directory where the TLS certs are located (by default /var/run/kubernetes). "+ - "If --tls-cert-file and --tls-private-key-file are provided, this flag will be ignored.") - - fs.StringVar(&s.ClientCAFile, "client-ca-file", s.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.") - fs.StringVar(&s.CloudProvider, "cloud-provider", s.CloudProvider, "The provider for cloud services. Empty string for no provider.") @@ -365,29 +235,6 @@ func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) { fs.StringVar(&s.ExternalHost, "external-hostname", s.ExternalHost, "The hostname to use when generating externalized URLs for this master (e.g. Swagger API Docs).") - fs.IPVar(&s.InsecureBindAddress, "insecure-bind-address", s.InsecureBindAddress, ""+ - "The IP address on which to serve the --insecure-port (set to 0.0.0.0 for all interfaces). "+ - "Defaults to localhost.") - fs.IPVar(&s.InsecureBindAddress, "address", s.InsecureBindAddress, - "DEPRECATED: see --insecure-bind-address instead.") - fs.MarkDeprecated("address", "see --insecure-bind-address instead.") - - fs.IntVar(&s.InsecurePort, "insecure-port", s.InsecurePort, ""+ - "The port on which to serve unsecured, unauthenticated access. Default 8080. It is assumed "+ - "that firewall rules are set up such that this port is not reachable from outside of "+ - "the cluster and that port 443 on the cluster's public address is proxied to this "+ - "port. This is performed by nginx in the default setup.") - - fs.IntVar(&s.InsecurePort, "port", s.InsecurePort, "DEPRECATED: see --insecure-port instead.") - fs.MarkDeprecated("port", "see --insecure-port instead.") - - fs.StringVar(&s.KeystoneURL, "experimental-keystone-url", s.KeystoneURL, - "If passed, activates the keystone authentication plugin.") - - fs.StringVar(&s.KeystoneCAFile, "experimental-keystone-ca-file", s.KeystoneCAFile, ""+ - "If set, the Keystone server's certificate will be verified by one of the authorities "+ - "in the experimental-keystone-ca-file, otherwise the host's root CA set will be used.") - // See #14282 for details on how to test/try this option out. // TODO: remove this comment once this option is tested in CI. fs.IntVar(&s.KubernetesServiceNodePort, "kubernetes-service-node-port", s.KubernetesServiceNodePort, ""+ @@ -415,49 +262,12 @@ func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) { "handler, which picks a randomized value above this number as the connection timeout, "+ "to spread out load.") - fs.StringVar(&s.OIDCIssuerURL, "oidc-issuer-url", s.OIDCIssuerURL, ""+ - "The URL of the OpenID issuer, only HTTPS scheme will be accepted. "+ - "If set, it will be used to verify the OIDC JSON Web Token (JWT).") - - fs.StringVar(&s.OIDCClientID, "oidc-client-id", s.OIDCClientID, - "The client ID for the OpenID Connect client, must be set if oidc-issuer-url is set.") - - fs.StringVar(&s.OIDCCAFile, "oidc-ca-file", s.OIDCCAFile, ""+ - "If set, the OpenID server's certificate will be verified by one of the authorities "+ - "in the oidc-ca-file, otherwise the host's root CA set will be used.") - - fs.StringVar(&s.OIDCUsernameClaim, "oidc-username-claim", "sub", ""+ - "The OpenID claim to use as the user name. Note that claims other than the default ('sub') "+ - "is not guaranteed to be unique and immutable. This flag is experimental, please see "+ - "the authentication documentation for further details.") - - fs.StringVar(&s.OIDCGroupsClaim, "oidc-groups-claim", "", ""+ - "If provided, the name of a custom OpenID Connect claim for specifying user groups. "+ - "The claim value is expected to be a string or array of strings. This flag is experimental, "+ - "please see the authentication documentation for further details.") - - fs.StringSliceVar(&s.RequestHeaderUsernameHeaders, "requestheader-username-headers", s.RequestHeaderUsernameHeaders, ""+ - "List of request headers to inspect for usernames. X-Remote-User is common.") - - fs.StringVar(&s.RequestHeaderClientCAFile, "requestheader-client-ca-file", s.RequestHeaderClientCAFile, ""+ - "Root certificate bundle to use to verify client certificates on incoming requests "+ - "before trusting usernames in headers specified by --requestheader-username-headers") - - fs.StringSliceVar(&s.RequestHeaderAllowedNames, "requestheader-allowed-names", s.RequestHeaderAllowedNames, ""+ - "List of client certificate common names to allow to provide usernames in headers "+ - "specified by --requestheader-username-headers. If empty, any client certificate validated "+ - "by the authorities in --requestheader-client-ca-file is allowed.") - fs.Var(&s.RuntimeConfig, "runtime-config", ""+ "A set of key=value pairs that describe runtime configuration that may be passed "+ "to apiserver. apis/ key can be used to turn on/off specific api versions. "+ "apis// can be used to turn on/off specific resources. api/all and "+ "api/legacy are special keys to control all and legacy api versions respectively.") - fs.IntVar(&s.SecurePort, "secure-port", s.SecurePort, ""+ - "The port on which to serve HTTPS with authentication and authorization. If 0, "+ - "don't serve HTTPS at all.") - fs.IPNetVar(&s.ServiceClusterIPRange, "service-cluster-ip-range", s.ServiceClusterIPRange, ""+ "A CIDR notation IP range from which to assign service cluster IPs. This must not "+ "overlap with any IP ranges assigned to nodes for pods.") @@ -472,12 +282,6 @@ func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) { fs.Var(&s.ServiceNodePortRange, "service-node-ports", "DEPRECATED: see --service-node-port-range instead") fs.MarkDeprecated("service-node-ports", "see --service-node-port-range instead") - fs.StringVar(&s.StorageConfig.Type, "storage-backend", s.StorageConfig.Type, - "The storage backend for persistence. Options: 'etcd2' (default), 'etcd3'.") - - fs.IntVar(&s.StorageConfig.DeserializationCacheSize, "deserialization-cache-size", s.StorageConfig.DeserializationCacheSize, - "Number of deserialized json objects to cache in memory.") - deprecatedStorageVersion := "" fs.StringVar(&deprecatedStorageVersion, "storage-version", deprecatedStorageVersion, "DEPRECATED: the version to store the legacy v1 resources with. Defaults to server preferred.") @@ -493,36 +297,6 @@ func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) { "It defaults to a list of preferred versions of all registered groups, "+ "which is derived from the KUBE_API_VERSIONS environment variable.") - fs.StringVar(&s.TLSCAFile, "tls-ca-file", s.TLSCAFile, "If set, this "+ - "certificate authority will used for secure access from Admission "+ - "Controllers. This must be a valid PEM-encoded CA bundle.") - - fs.StringVar(&s.TLSCertFile, "tls-cert-file", s.TLSCertFile, ""+ - "File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated "+ - "after server cert). If HTTPS serving is enabled, and --tls-cert-file and "+ - "--tls-private-key-file are not provided, a self-signed certificate and key "+ - "are generated for the public address and saved to /var/run/kubernetes.") - - fs.StringVar(&s.TLSPrivateKeyFile, "tls-private-key-file", s.TLSPrivateKeyFile, - "File containing the default x509 private key matching --tls-cert-file.") - - fs.Var(config.NewNamedCertKeyArray(&s.SNICertKeys), "tls-sni-cert-key", ""+ - "A pair of x509 certificate and private key file paths, optionally suffixed with a list of "+ - "domain patterns which are fully qualified domain names, possibly with prefixed wildcard "+ - "segments. If no domain patterns are provided, the names of the certificate are "+ - "extracted. Non-wildcard matches trump over wildcard matches, explicit domain patterns "+ - "trump over extracted names. For multiple key/certificate pairs, use the "+ - "--tls-sni-cert-key multiple times. "+ - "Examples: \"example.key,example.crt\" or \"*.foo.com,foo.com:foo.key,foo.crt\".") - - fs.StringVar(&s.TokenAuthFile, "token-auth-file", s.TokenAuthFile, ""+ - "If set, the file that will be used to secure the secure port of the API server "+ - "via token authentication.") - - fs.BoolVar(&s.EnableAnyToken, "insecure-allow-any-token", s.EnableAnyToken, ""+ - "If set, your server will be INSECURE. Any token will be allowed and user information will be parsed "+ - "from the token as `username/group1,group2`") - fs.StringSliceVar(&s.WatchCacheSizes, "watch-cache-sizes", s.WatchCacheSizes, ""+ "List of watch cache sizes for every resource (pods, nodes, etc.), comma separated. "+ "The individual override format: resource#size, where size is a number. It takes effect "+ diff --git a/pkg/genericapiserver/options/serving.go b/pkg/genericapiserver/options/serving.go new file mode 100644 index 00000000000..8487b9e35af --- /dev/null +++ b/pkg/genericapiserver/options/serving.go @@ -0,0 +1,237 @@ +/* +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 options + +import ( + "errors" + "fmt" + "net" + "strconv" + + "github.com/spf13/pflag" + + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/util/config" + utilnet "k8s.io/kubernetes/pkg/util/net" +) + +type ServingOptions struct { + BindAddress net.IP + BindPort int +} + +type SecureServingOptions struct { + ServingOptions ServingOptions + + // ServerCert is the TLS cert info for serving secure traffic + ServerCert GeneratableKeyCert + // SNICertKeys are named CertKeys for serving secure traffic with SNI support. + SNICertKeys []config.NamedCertKey + // ClientCA is the certificate bundle for all the signers that you'll recognize for incoming client certificates + ClientCA string + + // ServerCA is the certificate bundle for the signer of your serving certificate. Used for building a loopback + // connection to the API server for admission. + ServerCA string +} + +type CertKey struct { + // CertFile is a file containing a PEM-encoded certificate + CertFile string + // KeyFile is a file containing a PEM-encoded private key for the certificate specified by CertFile + KeyFile string +} + +type GeneratableKeyCert struct { + CertKey CertKey + + // CertDirectory is a directory that will contain the certificates. If the cert and key aren't specifically set + // this will be used to derive a match with the "pair-name" + CertDirectory string + // PairName is the name which will be used with CertDirectory to make a cert and key names + // It becomes CertDirector/PairName.crt and CertDirector/PairName.key + PairName string +} + +func NewSecureServingOptions() *SecureServingOptions { + return &SecureServingOptions{ + ServingOptions: ServingOptions{ + BindAddress: net.ParseIP("0.0.0.0"), + BindPort: 6443, + }, + ServerCert: GeneratableKeyCert{ + PairName: "apiserver", + CertDirectory: "/var/run/kubernetes", + }, + } +} + +func (s *SecureServingOptions) NewSelfClientConfig(token string) *restclient.Config { + if s == nil || s.ServingOptions.BindPort <= 0 || len(s.ServerCA) == 0 { + return nil + } + + clientConfig := &restclient.Config{ + // Increase QPS limits. The client is currently passed to all admission plugins, + // and those can be throttled in case of higher load on apiserver - see #22340 and #22422 + // for more details. Once #22422 is fixed, we may want to remove it. + QPS: 50, + Burst: 100, + } + + // Use secure port if the ServerCA is specified + host := s.ServingOptions.BindAddress.String() + if host == "0.0.0.0" { + host = "localhost" + } + clientConfig.Host = "https://" + net.JoinHostPort(host, strconv.Itoa(s.ServingOptions.BindPort)) + clientConfig.CAFile = s.ServerCA + clientConfig.BearerToken = token + + return clientConfig +} + +func (s *SecureServingOptions) Validate() []error { + errors := []error{} + if s == nil { + return errors + } + + errors = append(errors, s.ServingOptions.Validate("secure-port")...) + return errors +} + +func (s *SecureServingOptions) AddFlags(fs *pflag.FlagSet) { + fs.IPVar(&s.ServingOptions.BindAddress, "bind-address", s.ServingOptions.BindAddress, ""+ + "The IP address on which to listen for the --secure-port port. The "+ + "associated interface(s) must be reachable by the rest of the cluster, and by CLI/web "+ + "clients. If blank, all interfaces will be used (0.0.0.0).") + + fs.IntVar(&s.ServingOptions.BindPort, "secure-port", s.ServingOptions.BindPort, ""+ + "The port on which to serve HTTPS with authentication and authorization. If 0, "+ + "don't serve HTTPS at all.") + + fs.StringVar(&s.ServerCert.CertDirectory, "cert-dir", s.ServerCert.CertDirectory, ""+ + "The directory where the TLS certs are located (by default /var/run/kubernetes). "+ + "If --tls-cert-file and --tls-private-key-file are provided, this flag will be ignored.") + + fs.StringVar(&s.ServerCert.CertKey.CertFile, "tls-cert-file", s.ServerCert.CertKey.CertFile, ""+ + "File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated "+ + "after server cert). If HTTPS serving is enabled, and --tls-cert-file and "+ + "--tls-private-key-file are not provided, a self-signed certificate and key "+ + "are generated for the public address and saved to /var/run/kubernetes.") + + fs.StringVar(&s.ServerCert.CertKey.KeyFile, "tls-private-key-file", s.ServerCert.CertKey.KeyFile, + "File containing the default x509 private key matching --tls-cert-file.") + + fs.Var(config.NewNamedCertKeyArray(&s.SNICertKeys), "tls-sni-cert-key", ""+ + "A pair of x509 certificate and private key file paths, optionally suffixed with a list of "+ + "domain patterns which are fully qualified domain names, possibly with prefixed wildcard "+ + "segments. If no domain patterns are provided, the names of the certificate are "+ + "extracted. Non-wildcard matches trump over wildcard matches, explicit domain patterns "+ + "trump over extracted names. For multiple key/certificate pairs, use the "+ + "--tls-sni-cert-key multiple times. "+ + "Examples: \"example.key,example.crt\" or \"*.foo.com,foo.com:foo.key,foo.crt\".") + + fs.StringVar(&s.ClientCA, "client-ca-file", s.ClientCA, ""+ + "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.") + + fs.StringVar(&s.ServerCA, "tls-ca-file", s.ServerCA, "If set, this "+ + "certificate authority will used for secure access from Admission "+ + "Controllers. This must be a valid PEM-encoded CA bundle.") + +} + +func (s *SecureServingOptions) AddDeprecatedFlags(fs *pflag.FlagSet) { + fs.IPVar(&s.ServingOptions.BindAddress, "public-address-override", s.ServingOptions.BindAddress, + "DEPRECATED: see --bind-address instead.") + fs.MarkDeprecated("public-address-override", "see --bind-address instead.") + +} + +func NewInsecureServingOptions() *ServingOptions { + return &ServingOptions{ + BindAddress: net.ParseIP("127.0.0.1"), + BindPort: 8080, + } +} + +func (s ServingOptions) Validate(portArg string) []error { + errors := []error{} + + if s.BindPort < 0 || s.BindPort > 65535 { + errors = append(errors, fmt.Errorf("--%v %v must be between 0 and 65535, inclusive. 0 for turning off secure port.", portArg, s.BindPort)) + } + + return errors +} + +func (s *ServingOptions) NewSelfClientConfig(token string) *restclient.Config { + if s == nil || s.BindPort <= 0 { + return nil + } + clientConfig := &restclient.Config{ + // Increase QPS limits. The client is currently passed to all admission plugins, + // and those can be throttled in case of higher load on apiserver - see #22340 and #22422 + // for more details. Once #22422 is fixed, we may want to remove it. + QPS: 50, + Burst: 100, + } + + clientConfig.Host = net.JoinHostPort(s.BindAddress.String(), strconv.Itoa(s.BindPort)) + + return clientConfig +} + +func (s *ServingOptions) DefaultExternalAddress() (net.IP, error) { + return utilnet.ChooseBindAddress(s.BindAddress) +} + +func (s *ServingOptions) AddFlags(fs *pflag.FlagSet) { + fs.IPVar(&s.BindAddress, "insecure-bind-address", s.BindAddress, ""+ + "The IP address on which to serve the --insecure-port (set to 0.0.0.0 for all interfaces). "+ + "Defaults to localhost.") + + fs.IntVar(&s.BindPort, "insecure-port", s.BindPort, ""+ + "The port on which to serve unsecured, unauthenticated access. Default 8080. It is assumed "+ + "that firewall rules are set up such that this port is not reachable from outside of "+ + "the cluster and that port 443 on the cluster's public address is proxied to this "+ + "port. This is performed by nginx in the default setup.") +} + +func (s *ServingOptions) AddDeprecatedFlags(fs *pflag.FlagSet) { + fs.IPVar(&s.BindAddress, "address", s.BindAddress, + "DEPRECATED: see --insecure-bind-address instead.") + fs.MarkDeprecated("address", "see --insecure-bind-address instead.") + + fs.IntVar(&s.BindPort, "port", s.BindPort, "DEPRECATED: see --insecure-port instead.") + fs.MarkDeprecated("port", "see --insecure-port instead.") +} + +// Returns a clientconfig which can be used to talk to this apiserver. +func NewSelfClientConfig(secureServingOptions *SecureServingOptions, insecureServingOptions *ServingOptions, token string) (*restclient.Config, error) { + if cfg := secureServingOptions.NewSelfClientConfig(token); cfg != nil { + return cfg, nil + } + if cfg := insecureServingOptions.NewSelfClientConfig(token); cfg != nil { + return cfg, nil + } + + return nil, errors.New("Unable to set url for apiserver local client") +} diff --git a/pkg/genericapiserver/validation/BUILD b/pkg/genericapiserver/validation/BUILD index 2842b5f9603..d4be4ef0abc 100644 --- a/pkg/genericapiserver/validation/BUILD +++ b/pkg/genericapiserver/validation/BUILD @@ -12,10 +12,7 @@ load( go_library( name = "go_default_library", - srcs = [ - "etcd_validation.go", - "universal_validation.go", - ], + srcs = ["universal_validation.go"], tags = ["automanaged"], deps = [ "//pkg/genericapiserver/options:go_default_library", diff --git a/pkg/genericapiserver/validation/etcd_validation.go b/pkg/genericapiserver/validation/etcd_validation.go deleted file mode 100644 index 54e4a4fdfb8..00000000000 --- a/pkg/genericapiserver/validation/etcd_validation.go +++ /dev/null @@ -1,28 +0,0 @@ -/* -Copyright 2014 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 validation - -import ( - "github.com/golang/glog" - "k8s.io/kubernetes/pkg/genericapiserver/options" -) - -func VerifyEtcdServersList(options *options.ServerRunOptions) { - if len(options.StorageConfig.ServerList) == 0 { - glog.Fatalf("--etcd-servers must be specified") - } -} diff --git a/pkg/genericapiserver/validation/universal_validation.go b/pkg/genericapiserver/validation/universal_validation.go index 6577500dd8e..72e683acfc3 100644 --- a/pkg/genericapiserver/validation/universal_validation.go +++ b/pkg/genericapiserver/validation/universal_validation.go @@ -49,26 +49,6 @@ func verifyServiceNodePort(options *options.ServerRunOptions) []error { return errors } -func verifySecureAndInsecurePort(options *options.ServerRunOptions) []error { - errors := []error{} - if options.SecurePort < 0 || options.SecurePort > 65535 { - errors = append(errors, fmt.Errorf("--secure-port %v must be between 0 and 65535, inclusive. 0 for turning off secure port.", options.SecurePort)) - } - - if options.InsecurePort < 0 || options.InsecurePort > 65535 { - errors = append(errors, fmt.Errorf("--insecure-port %v must be between 0 and 65535, inclusive. 0 for turning off insecure port.", options.InsecurePort)) - } - - if options.SecurePort == 0 && options.InsecurePort == 0 { - glog.Fatalf("--secure-port and --insecure-port cannot be turned off at the same time.") - } - - if options.SecurePort == options.InsecurePort { - errors = append(errors, fmt.Errorf("--secure-port and --insecure-port cannot use the same port.")) - } - return errors -} - func ValidateRunOptions(options *options.ServerRunOptions) { errors := []error{} if errs := verifyClusterIPFlags(options); len(errs) > 0 { @@ -77,9 +57,6 @@ func ValidateRunOptions(options *options.ServerRunOptions) { if errs := verifyServiceNodePort(options); len(errs) > 0 { errors = append(errors, errs...) } - if errs := verifySecureAndInsecurePort(options); len(errs) > 0 { - errors = append(errors, errs...) - } if err := utilerrors.NewAggregate(errors); err != nil { glog.Fatalf("Validate server run options failed: %v", err) } diff --git a/test/e2e_node/services/apiserver.go b/test/e2e_node/services/apiserver.go index 6b5b66bab26..7e7eeee4982 100644 --- a/test/e2e_node/services/apiserver.go +++ b/test/e2e_node/services/apiserver.go @@ -41,7 +41,7 @@ func NewAPIServer() *APIServer { // Start starts the apiserver, returns when apiserver is ready. func (a *APIServer) Start() error { config := options.NewServerRunOptions() - config.GenericServerRunOptions.StorageConfig.ServerList = []string{getEtcdClientURL()} + config.Etcd.StorageConfig.ServerList = []string{getEtcdClientURL()} _, ipnet, err := net.ParseCIDR(clusterIPRange) if err != nil { return err diff --git a/test/integration/discoverysummarizer/discoverysummarizer_test.go b/test/integration/discoverysummarizer/discoverysummarizer_test.go index 353cd8733f1..a392f8033d5 100644 --- a/test/integration/discoverysummarizer/discoverysummarizer_test.go +++ b/test/integration/discoverysummarizer/discoverysummarizer_test.go @@ -68,15 +68,15 @@ func runDiscoverySummarizer(t *testing.T) string { func runAPIServer(t *testing.T, stopCh <-chan struct{}) string { serverRunOptions := apiserver.NewServerRunOptions() // Change the ports, because otherwise it will fail if examples/apiserver/apiserver_test and this are run in parallel. - serverRunOptions.SecurePort = 6443 + 3 - serverRunOptions.InsecurePort = 8080 + 3 + serverRunOptions.SecureServing.ServingOptions.BindPort = 6443 + 3 + serverRunOptions.InsecureServing.BindPort = 8080 + 3 go func() { - if err := apiserver.Run(serverRunOptions, stopCh); err != nil { + if err := serverRunOptions.Run(stopCh); err != nil { t.Fatalf("Error in bringing up the example apiserver: %v", err) } }() - serverURL := fmt.Sprintf("http://localhost:%d", serverRunOptions.InsecurePort) + serverURL := fmt.Sprintf("http://localhost:%d", serverRunOptions.InsecureServing.BindPort) if err := waitForServerUp(serverURL); err != nil { t.Fatalf("%v", err) } diff --git a/test/integration/examples/apiserver_test.go b/test/integration/examples/apiserver_test.go index 858d2ee390e..4ffce261bcd 100644 --- a/test/integration/examples/apiserver_test.go +++ b/test/integration/examples/apiserver_test.go @@ -44,7 +44,7 @@ func TestRunServer(t *testing.T) { serverIP := fmt.Sprintf("http://localhost:%d", apiserver.InsecurePort) stopCh := make(chan struct{}) go func() { - if err := apiserver.Run(apiserver.NewServerRunOptions(), stopCh); err != nil { + if err := apiserver.NewServerRunOptions().Run(stopCh); err != nil { t.Fatalf("Error in bringing up the server: %v", err) } }() @@ -63,9 +63,9 @@ func TestRunSecureServer(t *testing.T) { stopCh := make(chan struct{}) go func() { options := apiserver.NewServerRunOptions() - options.InsecurePort = 0 - options.SecurePort = apiserver.SecurePort - if err := apiserver.Run(options, stopCh); err != nil { + options.InsecureServing.BindPort = 0 + options.SecureServing.ServingOptions.BindPort = apiserver.SecurePort + if err := options.Run(stopCh); err != nil { t.Fatalf("Error in bringing up the server: %v", err) } }() diff --git a/test/integration/federation/server_test.go b/test/integration/federation/server_test.go index 8747939bd00..d1bfa722587 100644 --- a/test/integration/federation/server_test.go +++ b/test/integration/federation/server_test.go @@ -88,11 +88,11 @@ var groupVersions = []schema.GroupVersion{ func TestRun(t *testing.T) { s := options.NewServerRunOptions() - s.GenericServerRunOptions.SecurePort = securePort - s.GenericServerRunOptions.InsecurePort = insecurePort + s.SecureServing.ServingOptions.BindPort = securePort + s.InsecureServing.BindPort = insecurePort _, ipNet, _ := net.ParseCIDR("10.10.10.0/24") s.GenericServerRunOptions.ServiceClusterIPRange = *ipNet - s.GenericServerRunOptions.StorageConfig.ServerList = []string{"http://localhost:2379"} + s.Etcd.StorageConfig.ServerList = []string{"http://localhost:2379"} go func() { if err := app.Run(s); err != nil { t.Fatalf("Error in bringing up the server: %v", err)