add delegating authorization flags and options

pull/6/head
deads2k 2016-11-10 09:38:52 -05:00
parent ca2b5f136e
commit 6846855929
10 changed files with 153 additions and 29 deletions

View File

@ -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)
}

View File

@ -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",

View File

@ -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")

View File

@ -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

View File

@ -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",
],
)

View File

@ -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) {

View File

@ -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,
)
}

View File

@ -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",

View File

@ -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
}