diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index 5f033ebf281..d0d014625b7 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -246,8 +246,8 @@ func Run(s *options.ServerRunOptions) error { } sharedInformers := informers.NewSharedInformerFactory(nil, client, 10*time.Minute) - authorizerconfig := s.Authorization.ToAuthorizationConfig(sharedInformers) - apiAuthorizer, err := authorizer.NewAuthorizerFromAuthorizationConfig(authorizerconfig) + authorizationConfig := s.Authorization.ToAuthorizationConfig(sharedInformers) + apiAuthorizer, err := authorizer.NewAuthorizerFromAuthorizationConfig(authorizationConfig) if err != nil { glog.Fatalf("Invalid Authorization Config: %v", err) } diff --git a/cmd/kubelet/app/BUILD b/cmd/kubelet/app/BUILD index e18832368fe..22a3d5494cd 100644 --- a/cmd/kubelet/app/BUILD +++ b/cmd/kubelet/app/BUILD @@ -98,7 +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/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 ba9618d68ac..535f10ed561 100644 --- a/cmd/kubelet/app/auth.go +++ b/cmd/kubelet/app/auth.go @@ -29,9 +29,9 @@ import ( 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" - webhooksar "k8s.io/kubernetes/plugin/pkg/auth/authorizer/webhook" ) func buildAuth(nodeName types.NodeName, client clientset.Interface, config componentconfig.KubeletConfiguration) (server.AuthInterface, error) { @@ -87,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/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index a508872963c..a3a26d3826b 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -36,6 +36,7 @@ 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/genericapiserver/authorizer/BUILD b/pkg/genericapiserver/authorizer/BUILD index 78c9a3dc815..eab84ff054c 100644 --- a/pkg/genericapiserver/authorizer/BUILD +++ b/pkg/genericapiserver/authorizer/BUILD @@ -12,12 +12,16 @@ 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", "//plugin/pkg/auth/authorizer/rbac:go_default_library", "//plugin/pkg/auth/authorizer/webhook:go_default_library", @@ -35,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 100% rename from pkg/genericapiserver/authorizer/authz.go rename to pkg/genericapiserver/authorizer/builtin.go diff --git a/pkg/genericapiserver/authorizer/delegating.go b/pkg/genericapiserver/authorizer/delegating.go new file mode 100644 index 00000000000..311b6131be8 --- /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 authentication 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/options/BUILD b/pkg/genericapiserver/options/BUILD index 342643d1d86..7280966c8e2 100644 --- a/pkg/genericapiserver/options/BUILD +++ b/pkg/genericapiserver/options/BUILD @@ -27,6 +27,7 @@ go_library( "//pkg/apimachinery/registered:go_default_library", "//pkg/apiserver/authenticator: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", diff --git a/pkg/genericapiserver/options/authorization.go b/pkg/genericapiserver/options/authorization.go index 04bdad45306..c864394c351 100644 --- a/pkg/genericapiserver/options/authorization.go +++ b/pkg/genericapiserver/options/authorization.go @@ -22,6 +22,8 @@ import ( "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" ) @@ -39,7 +41,7 @@ type BuiltInAuthorizationOptions struct { func NewBuiltInAuthorizationOptions() *BuiltInAuthorizationOptions { return &BuiltInAuthorizationOptions{ - Mode: "AlwaysAllow", + Mode: authorizer.ModeAlwaysAllow, WebhookCacheAuthorizedTTL: 5 * time.Minute, WebhookCacheUnauthorizedTTL: 30 * time.Second, } @@ -87,3 +89,72 @@ func (s *BuiltInAuthorizationOptions) ToAuthorizationConfig(informerFactory info 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 + // TokenAcessReview.authentication.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) { + tokenClient, err := s.newSubjectAccessReview() + if err != nil { + return authorizer.DelegatingAuthorizerConfig{}, err + } + + ret := authorizer.DelegatingAuthorizerConfig{ + SubjectAccessReviewClient: tokenClient, + 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.NewDefaultClientConfigLoadingRules() + loadingRules.ExplicitPath = s.RemoteKubeConfigFile + loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}) + + clientConfig, err := loader.ClientConfig() + if err != nil { + return nil, err + } + + client, err := authorizationclient.NewForConfig(clientConfig) + if err != nil { + return nil, err + } + + return client.SubjectAccessReviews(), nil +}