From 6e0211b3d82c5b3b2f69f6b3c7a7840b42e6e000 Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Mon, 27 Apr 2020 17:41:42 +0200 Subject: [PATCH 1/4] provides RequestHeaderAuthRequestController for dynamically filling RequestHeaderConfig struct --- .../headerrequest/requestheader_controller.go | 319 ++++++++++++++++++ .../requestheader_controller_test.go | 284 ++++++++++++++++ 2 files changed, 603 insertions(+) create mode 100644 staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go create mode 100644 staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller_test.go diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go b/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go new file mode 100644 index 00000000000..9ef4057e313 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go @@ -0,0 +1,319 @@ +/* +Copyright 2020 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 headerrequest + +import ( + "context" + "encoding/json" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + coreinformers "k8s.io/client-go/informers/core/v1" + "k8s.io/client-go/kubernetes" + corev1listers "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + "k8s.io/klog" + "sync/atomic" +) + +const ( + authenticationRoleName = "extension-apiserver-authentication-reader" +) + +type requestHeaderBundle struct { + UsernameHeaders []string + GroupHeaders []string + ExtraHeaderPrefixes []string + AllowedClientNames []string +} + +// RequestHeaderAuthRequestController a controller that exposes a set of methods for dynamically filling RequestHeaderConfig struct. +// The methods are sourced from the config map which is being monitored by this controller. +// The controller is primed from the server at the construction time for components that don't want to dynamically react to changes +// in the config map. +type RequestHeaderAuthRequestController struct { + name string + + configmapName string + configmapNamespace string + + configmapLister corev1listers.ConfigMapNamespaceLister + configmapInformerSynced cache.InformerSynced + + queue workqueue.RateLimitingInterface + + // exportedRequestHeaderBundle is a requestHeaderBundle that contains the last read, non-zero length content of the configmap + exportedRequestHeaderBundle atomic.Value + + usernameHeadersKey string + groupHeadersKey string + extraHeaderPrefixesKey string + allowedClientNamesKey string +} + +// NewRequestHeaderAuthRequestController creates a new controller that implements RequestHeaderAuthRequestController +func NewRequestHeaderAuthRequestController( + cmName string, + cmNamespace string, + client kubernetes.Interface, + cmInformer coreinformers.ConfigMapInformer, + usernameHeadersKey, groupHeadersKey, extraHeaderPrefixesKey, allowedClientNamesKey string) (*RequestHeaderAuthRequestController, error) { + c := &RequestHeaderAuthRequestController{ + name: "RequestHeaderAuthRequestController", + + configmapName: cmName, + configmapNamespace: cmNamespace, + + usernameHeadersKey: usernameHeadersKey, + groupHeadersKey: groupHeadersKey, + extraHeaderPrefixesKey: extraHeaderPrefixesKey, + allowedClientNamesKey: allowedClientNamesKey, + + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "RequestHeaderAuthRequestController"), + } + + // use the live client to prime the controller + if err := c.syncOnce(client); err != nil { + return nil, err + } + + cmInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ + FilterFunc: func(obj interface{}) bool { + if cast, ok := obj.(*corev1.ConfigMap); ok { + return cast.Name == c.configmapName && cast.Namespace == c.configmapNamespace + } + if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok { + if cast, ok := tombstone.Obj.(*corev1.ConfigMap); ok { + return cast.Name == c.configmapName && cast.Namespace == c.configmapNamespace + } + } + return true // always return true just in case. The checks are fairly cheap + }, + Handler: cache.ResourceEventHandlerFuncs{ + // we have a filter, so any time we're called, we may as well queue. We only ever check one configmap + // so we don't have to be choosy about our key. + AddFunc: func(obj interface{}) { + c.queue.Add(c.keyFn()) + }, + UpdateFunc: func(oldObj, newObj interface{}) { + c.queue.Add(c.keyFn()) + }, + DeleteFunc: func(obj interface{}) { + c.queue.Add(c.keyFn()) + }, + }, + }) + + c.configmapLister = cmInformer.Lister().ConfigMaps(c.configmapNamespace) + c.configmapInformerSynced = cmInformer.Informer().HasSynced + + return c, nil +} + +func (c *RequestHeaderAuthRequestController) UsernameHeaders() []string { + return c.loadRequestHeaderFor(c.usernameHeadersKey) +} + +func (c *RequestHeaderAuthRequestController) GroupHeaders() []string { + return c.loadRequestHeaderFor(c.groupHeadersKey) +} + +func (c *RequestHeaderAuthRequestController) ExtraHeaderPrefixes() []string { + return c.loadRequestHeaderFor(c.extraHeaderPrefixesKey) +} + +func (c *RequestHeaderAuthRequestController) AllowedClientNames() []string { + return c.loadRequestHeaderFor(c.allowedClientNamesKey) +} + +// Run starts RequestHeaderAuthRequestController controller and blocks until stopCh is closed. +func (c *RequestHeaderAuthRequestController) Run(workers int, stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + defer c.queue.ShutDown() + + klog.Infof("Starting %s", c.name) + defer klog.Infof("Shutting down %s", c.name) + + // wait for caches to fill before starting your work + if !cache.WaitForNamedCacheSync(c.name, stopCh, c.configmapInformerSynced) { + return + } + + // doesn't matter what workers say, only start one. + go wait.Until(c.runWorker, time.Second, stopCh) + + <-stopCh +} + +func (c *RequestHeaderAuthRequestController) runWorker() { + for c.processNextWorkItem() { + } +} + +func (c *RequestHeaderAuthRequestController) processNextWorkItem() bool { + dsKey, quit := c.queue.Get() + if quit { + return false + } + defer c.queue.Done(dsKey) + + err := c.sync() + if err == nil { + c.queue.Forget(dsKey) + return true + } + + utilruntime.HandleError(fmt.Errorf("%v failed with : %v", dsKey, err)) + c.queue.AddRateLimited(dsKey) + + return true +} + +func (c *RequestHeaderAuthRequestController) syncOnce(client kubernetes.Interface) error { + configMap, err := client.CoreV1().ConfigMaps(c.configmapNamespace).Get(context.TODO(), c.configmapName, metav1.GetOptions{}) + switch { + case errors.IsNotFound(err): + // ignore, authConfigMap is nil now + return nil + case errors.IsForbidden(err): + klog.Warningf("Unable to get configmap/%s in %s. Usually fixed by "+ + "'kubectl create rolebinding -n %s ROLEBINDING_NAME --role=%s --serviceaccount=YOUR_NS:YOUR_SA'", + c.configmapName, c.configmapNamespace, c.configmapNamespace, authenticationRoleName) + return err + case err != nil: + return err + } + return c.syncConfigMap(configMap) +} + +// sync reads the config and propagates the changes to exportedRequestHeaderBundle +// which is exposed by the set of methods that are used to fill RequestHeaderConfig struct +func (c *RequestHeaderAuthRequestController) sync() error { + configMap, err := c.configmapLister.Get(c.configmapName) + if err != nil { + return err + } + return c.syncConfigMap(configMap) +} + +func (c *RequestHeaderAuthRequestController) syncConfigMap(configMap *corev1.ConfigMap) error { + hasChanged, newRequestHeaderBundle, err := c.hasRequestHeaderBundleChanged(configMap) + if err != nil { + return err + } + if hasChanged { + c.exportedRequestHeaderBundle.Store(newRequestHeaderBundle) + } + return nil +} + +func (c *RequestHeaderAuthRequestController) hasRequestHeaderBundleChanged(cm *corev1.ConfigMap) (bool, *requestHeaderBundle, error) { + currentHeadersBundle, err := c.getRequestHeaderBundleFromConfigMap(cm) + if err != nil { + return false, nil, err + } + + rawHeaderBundle := c.exportedRequestHeaderBundle.Load() + if rawHeaderBundle == nil { + return true, currentHeadersBundle, nil + } + + // check to see if we have a change. If the values are the same, do nothing. + loadedHeadersBundle, ok := rawHeaderBundle.(*requestHeaderBundle) + if !ok { + return true, currentHeadersBundle, nil + } + + if !equality.Semantic.DeepEqual(loadedHeadersBundle, currentHeadersBundle) { + return true, currentHeadersBundle, nil + } + return false, nil, nil +} + +func (c *RequestHeaderAuthRequestController) getRequestHeaderBundleFromConfigMap(cm *corev1.ConfigMap) (*requestHeaderBundle, error) { + usernameHeaderCurrentValue, err := deserializeStrings(cm.Data[c.usernameHeadersKey]) + if err != nil { + return nil, err + } + + groupHeadersCurrentValue, err := deserializeStrings(cm.Data[c.groupHeadersKey]) + if err != nil { + return nil, err + } + + extraHeaderPrefixesCurrentValue, err := deserializeStrings(cm.Data[c.extraHeaderPrefixesKey]) + if err != nil { + return nil, err + + } + + allowedClientNamesCurrentValue, err := deserializeStrings(cm.Data[c.allowedClientNamesKey]) + if err != nil { + return nil, err + } + + return &requestHeaderBundle{ + UsernameHeaders: usernameHeaderCurrentValue, + GroupHeaders: groupHeadersCurrentValue, + ExtraHeaderPrefixes: extraHeaderPrefixesCurrentValue, + AllowedClientNames: allowedClientNamesCurrentValue, + }, nil +} + +func (c *RequestHeaderAuthRequestController) loadRequestHeaderFor(key string) []string { + rawHeaderBundle := c.exportedRequestHeaderBundle.Load() + if rawHeaderBundle == nil { + return nil // this can happen if we've been unable load data from the apiserver for some reason + } + headerBundle := rawHeaderBundle.(*requestHeaderBundle) + + switch key { + case c.usernameHeadersKey: + return headerBundle.UsernameHeaders + case c.groupHeadersKey: + return headerBundle.GroupHeaders + case c.extraHeaderPrefixesKey: + return headerBundle.ExtraHeaderPrefixes + case c.allowedClientNamesKey: + return headerBundle.AllowedClientNames + default: + return nil + } +} + +func (c *RequestHeaderAuthRequestController) keyFn() string { + // this format matches DeletionHandlingMetaNamespaceKeyFunc for our single key + return c.configmapNamespace + "/" + c.configmapName +} + +func deserializeStrings(in string) ([]string, error) { + if len(in) == 0 { + return nil, nil + } + var ret []string + if err := json.Unmarshal([]byte(in), &ret); err != nil { + return nil, err + } + return ret, nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller_test.go b/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller_test.go new file mode 100644 index 00000000000..07c299398f6 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller_test.go @@ -0,0 +1,284 @@ +/* +Copyright 2020 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 headerrequest + +import ( + "encoding/json" + "k8s.io/apimachinery/pkg/api/equality" + "testing" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + corev1listers "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" +) + +const ( + defConfigMapName = "extension-apiserver-authentication" + defConfigMapNamespace = "kube-system" + + defUsernameHeadersKey = "user-key" + defGroupHeadersKey = "group-key" + defExtraHeaderPrefixesKey = "extra-key" + defAllowedClientNamesKey = "names-key" +) + +type expectedHeadersHolder struct { + usernameHeaders []string + groupHeaders []string + extraHeaderPrefixes []string + allowedClientNames []string +} + +func TestRequestHeaderAuthRequestController(t *testing.T) { + scenarios := []struct { + name string + cm *corev1.ConfigMap + expectedHeader expectedHeadersHolder + expectErr bool + }{ + { + name: "happy-path: headers values are populated form a config map", + cm: defaultConfigMap(t, []string{"user-val"}, []string{"group-val"}, []string{"extra-val"}, []string{"names-val"}), + expectedHeader: expectedHeadersHolder{ + usernameHeaders: []string{"user-val"}, + groupHeaders: []string{"group-val"}, + extraHeaderPrefixes: []string{"extra-val"}, + allowedClientNames: []string{"names-val"}, + }, + }, + { + name: "passing an empty config map doesn't break the controller", + cm: func() *corev1.ConfigMap { + c := defaultConfigMap(t, nil, nil, nil, nil) + c.Data = map[string]string{} + return c + }(), + }, + { + name: "an invalid config map produces an error", + cm: func() *corev1.ConfigMap { + c := defaultConfigMap(t, nil, nil, nil, nil) + c.Data = map[string]string{ + defUsernameHeadersKey: "incorrect-json-array", + } + return c + }(), + expectErr: true, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + // test data + indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) + if err := indexer.Add(scenario.cm); err != nil { + t.Fatal(err.Error()) + } + target := newDefaultTarget() + target.configmapLister = corev1listers.NewConfigMapLister(indexer).ConfigMaps(defConfigMapNamespace) + + // act + err := target.sync() + + if err != nil && !scenario.expectErr { + t.Errorf("got unexpected error %v", err) + } + if err == nil && scenario.expectErr { + t.Error("expected an error but didn't get one") + } + + // validate + validateExpectedHeaders(t, target, scenario.expectedHeader) + }) + } +} + +func TestRequestHeaderAuthRequestControllerPreserveState(t *testing.T) { + scenarios := []struct { + name string + cm *corev1.ConfigMap + expectedHeader expectedHeadersHolder + expectErr bool + }{ + { + name: "scenario 1: headers values are populated form a config map", + cm: defaultConfigMap(t, []string{"user-val"}, []string{"group-val"}, []string{"extra-val"}, []string{"names-val"}), + expectedHeader: expectedHeadersHolder{ + usernameHeaders: []string{"user-val"}, + groupHeaders: []string{"group-val"}, + extraHeaderPrefixes: []string{"extra-val"}, + allowedClientNames: []string{"names-val"}, + }, + }, + { + name: "scenario 2: an invalid config map produces an error but doesn't destroy the state (scenario 1)", + cm: func() *corev1.ConfigMap { + c := defaultConfigMap(t, nil, nil, nil, nil) + c.Data = map[string]string{ + defUsernameHeadersKey: "incorrect-json-array", + } + return c + }(), + expectErr: true, + expectedHeader: expectedHeadersHolder{ + usernameHeaders: []string{"user-val"}, + groupHeaders: []string{"group-val"}, + extraHeaderPrefixes: []string{"extra-val"}, + allowedClientNames: []string{"names-val"}, + }, + }, + { + name: "scenario 3: some headers values have changed (prev set by scenario 1)", + cm: defaultConfigMap(t, []string{"user-val"}, []string{"group-val-scenario-3"}, []string{"extra-val"}, []string{"names-val"}), + expectedHeader: expectedHeadersHolder{ + usernameHeaders: []string{"user-val"}, + groupHeaders: []string{"group-val-scenario-3"}, + extraHeaderPrefixes: []string{"extra-val"}, + allowedClientNames: []string{"names-val"}, + }, + }, + { + name: "scenario 4: all headers values have changed (prev set by scenario 3)", + cm: defaultConfigMap(t, []string{"user-val-scenario-4"}, []string{"group-val-scenario-4"}, []string{"extra-val-scenario-4"}, []string{"names-val-scenario-4"}), + expectedHeader: expectedHeadersHolder{ + usernameHeaders: []string{"user-val-scenario-4"}, + groupHeaders: []string{"group-val-scenario-4"}, + extraHeaderPrefixes: []string{"extra-val-scenario-4"}, + allowedClientNames: []string{"names-val-scenario-4"}, + }, + }, + } + + target := newDefaultTarget() + + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + // test data + if scenario.cm != nil { + indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) + if err := indexer.Add(scenario.cm); err != nil { + t.Fatal(err.Error()) + } + target.configmapLister = corev1listers.NewConfigMapLister(indexer).ConfigMaps(defConfigMapNamespace) + } + + // act + err := target.sync() + + if err != nil && !scenario.expectErr { + t.Errorf("got unexpected error %v", err) + } + if err == nil && scenario.expectErr { + t.Error("expected an error but didn't get one") + } + + // validate + validateExpectedHeaders(t, target, scenario.expectedHeader) + }) + } +} + +func TestRequestHeaderAuthRequestControllerSyncOnce(t *testing.T) { + scenarios := []struct { + name string + cm *corev1.ConfigMap + expectedHeader expectedHeadersHolder + expectErr bool + }{ + { + name: "headers values are populated form a config map", + cm: defaultConfigMap(t, []string{"user-val"}, []string{"group-val"}, []string{"extra-val"}, []string{"names-val"}), + expectedHeader: expectedHeadersHolder{ + usernameHeaders: []string{"user-val"}, + groupHeaders: []string{"group-val"}, + extraHeaderPrefixes: []string{"extra-val"}, + allowedClientNames: []string{"names-val"}, + }, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + // test data + target := newDefaultTarget() + fakeKubeClient := fake.NewSimpleClientset(scenario.cm) + + // act + err := target.syncOnce(fakeKubeClient) + + if err != nil && !scenario.expectErr { + t.Errorf("got unexpected error %v", err) + } + if err == nil && scenario.expectErr { + t.Error("expected an error but didn't get one") + } + + // validate + validateExpectedHeaders(t, target, scenario.expectedHeader) + }) + } +} + +func defaultConfigMap(t *testing.T, usernameHeaderVal, groupHeadersVal, extraHeaderPrefixesVal, allowedClientNamesVal []string) *corev1.ConfigMap { + encode := func(val []string) string { + encodedVal, err := json.Marshal(val) + if err != nil { + t.Fatalf("unable to marshal %q , due to %v", usernameHeaderVal, err) + } + return string(encodedVal) + } + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: defConfigMapName, + Namespace: defConfigMapNamespace, + }, + Data: map[string]string{ + defUsernameHeadersKey: encode(usernameHeaderVal), + defGroupHeadersKey: encode(groupHeadersVal), + defExtraHeaderPrefixesKey: encode(extraHeaderPrefixesVal), + defAllowedClientNamesKey: encode(allowedClientNamesVal), + }, + } +} + +func newDefaultTarget() *RequestHeaderAuthRequestController { + return &RequestHeaderAuthRequestController{ + configmapName: defConfigMapName, + configmapNamespace: defConfigMapNamespace, + usernameHeadersKey: defUsernameHeadersKey, + groupHeadersKey: defGroupHeadersKey, + extraHeaderPrefixesKey: defExtraHeaderPrefixesKey, + allowedClientNamesKey: defAllowedClientNamesKey, + } +} + +func validateExpectedHeaders(t *testing.T, target *RequestHeaderAuthRequestController, expected expectedHeadersHolder) { + if !equality.Semantic.DeepEqual(target.UsernameHeaders(), expected.usernameHeaders) { + t.Fatalf("incorrect usernameHeaders, got %v, wanted %v", target.UsernameHeaders(), expected.usernameHeaders) + } + if !equality.Semantic.DeepEqual(target.GroupHeaders(), expected.groupHeaders) { + t.Fatalf("incorrect groupHeaders, got %v, wanted %v", target.GroupHeaders(), expected.groupHeaders) + } + if !equality.Semantic.DeepEqual(target.ExtraHeaderPrefixes(), expected.extraHeaderPrefixes) { + t.Fatalf("incorrect extraheaderPrefixes, got %v, wanted %v", target.ExtraHeaderPrefixes(), expected.extraHeaderPrefixes) + } + if !equality.Semantic.DeepEqual(target.AllowedClientNames(), expected.allowedClientNames) { + t.Fatalf("incorrect expectedAllowedClientNames, got %v, wanted %v", target.AllowedClientNames(), expected.allowedClientNames) + } +} From cb4b4cb5a6ffdf1c7f199e644a8b5cac2367d504 Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Tue, 28 Apr 2020 12:48:21 +0200 Subject: [PATCH 2/4] provides DynamicRequestHeaderController that combines DynamicCAFromConfigMapController and RequestHeaderAuthRequestController into one controller the unified controller will dynamically fill RequestHeaderConfig struct --- .../headerrequest/requestheader_controller.go | 29 +++++++-- .../pkg/server/options/authentication.go | 58 ++--------------- .../authentication_dynamic_request_header.go | 62 +++++++++++++++++++ 3 files changed, 92 insertions(+), 57 deletions(-) create mode 100644 staging/src/k8s.io/apiserver/pkg/server/options/authentication_dynamic_request_header.go diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go b/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go index 9ef4057e313..889a552f1a8 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go +++ b/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" coreinformers "k8s.io/client-go/informers/core/v1" @@ -41,6 +42,16 @@ const ( authenticationRoleName = "extension-apiserver-authentication-reader" ) +// RequestHeaderAuthRequestProvider a provider that knows how to dynamically fill parts of RequestHeaderConfig struct +type RequestHeaderAuthRequestProvider interface { + UsernameHeaders() []string + GroupHeaders() []string + ExtraHeaderPrefixes() []string + AllowedClientNames() []string +} + +var _ RequestHeaderAuthRequestProvider = &RequestHeaderAuthRequestController{} + type requestHeaderBundle struct { UsernameHeaders []string GroupHeaders []string @@ -48,7 +59,7 @@ type requestHeaderBundle struct { AllowedClientNames []string } -// RequestHeaderAuthRequestController a controller that exposes a set of methods for dynamically filling RequestHeaderConfig struct. +// RequestHeaderAuthRequestController a controller that exposes a set of methods for dynamically filling parts of RequestHeaderConfig struct. // The methods are sourced from the config map which is being monitored by this controller. // The controller is primed from the server at the construction time for components that don't want to dynamically react to changes // in the config map. @@ -59,6 +70,7 @@ type RequestHeaderAuthRequestController struct { configmapNamespace string configmapLister corev1listers.ConfigMapNamespaceLister + configmapInformer cache.SharedIndexInformer configmapInformerSynced cache.InformerSynced queue workqueue.RateLimitingInterface @@ -77,7 +89,6 @@ func NewRequestHeaderAuthRequestController( cmName string, cmNamespace string, client kubernetes.Interface, - cmInformer coreinformers.ConfigMapInformer, usernameHeadersKey, groupHeadersKey, extraHeaderPrefixesKey, allowedClientNamesKey string) (*RequestHeaderAuthRequestController, error) { c := &RequestHeaderAuthRequestController{ name: "RequestHeaderAuthRequestController", @@ -98,7 +109,12 @@ func NewRequestHeaderAuthRequestController( return nil, err } - cmInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ + // we construct our own informer because we need such a small subset of the information available. Just one namespace. + c.configmapInformer = coreinformers.NewFilteredConfigMapInformer(client, c.configmapNamespace, 12*time.Hour, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, func(listOptions *metav1.ListOptions) { + listOptions.FieldSelector = fields.OneTermEqualSelector("metadata.name", c.configmapName).String() + }) + + c.configmapInformer.AddEventHandler(cache.FilteringResourceEventHandler{ FilterFunc: func(obj interface{}) bool { if cast, ok := obj.(*corev1.ConfigMap); ok { return cast.Name == c.configmapName && cast.Namespace == c.configmapNamespace @@ -125,8 +141,8 @@ func NewRequestHeaderAuthRequestController( }, }) - c.configmapLister = cmInformer.Lister().ConfigMaps(c.configmapNamespace) - c.configmapInformerSynced = cmInformer.Informer().HasSynced + c.configmapLister = corev1listers.NewConfigMapLister(c.configmapInformer.GetIndexer()).ConfigMaps(c.configmapNamespace) + c.configmapInformerSynced = c.configmapInformer.HasSynced return c, nil } @@ -155,6 +171,8 @@ func (c *RequestHeaderAuthRequestController) Run(workers int, stopCh <-chan stru klog.Infof("Starting %s", c.name) defer klog.Infof("Shutting down %s", c.name) + go c.configmapInformer.Run(stopCh) + // wait for caches to fill before starting your work if !cache.WaitForNamedCacheSync(c.name, stopCh, c.configmapInformerSynced) { return @@ -224,6 +242,7 @@ func (c *RequestHeaderAuthRequestController) syncConfigMap(configMap *corev1.Con } if hasChanged { c.exportedRequestHeaderBundle.Store(newRequestHeaderBundle) + klog.V(2).Infof("Loaded a new request header values for %v", c.name) } return nil } diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/authentication.go b/staging/src/k8s.io/apiserver/pkg/server/options/authentication.go index 1df0fccf601..86ef6feccd7 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/authentication.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/authentication.go @@ -17,8 +17,6 @@ limitations under the License. package options import ( - "context" - "encoding/json" "fmt" "strings" "time" @@ -27,7 +25,6 @@ import ( "github.com/spf13/pflag" - "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/authentication/authenticatorfactory" "k8s.io/apiserver/pkg/authentication/request/headerrequest" @@ -330,66 +327,23 @@ const ( // by --requestheader-username-headers. This is created in the cluster by the kube-apiserver. // "WARNING: generally do not depend on authorization being already done for incoming requests.") authenticationConfigMapName = "extension-apiserver-authentication" - authenticationRoleName = "extension-apiserver-authentication-reader" ) func (s *DelegatingAuthenticationOptions) createRequestHeaderConfig(client kubernetes.Interface) (*authenticatorfactory.RequestHeaderConfig, error) { - requestHeaderCAProvider, err := dynamiccertificates.NewDynamicCAFromConfigMapController("client-ca", authenticationConfigMapNamespace, authenticationConfigMapName, "requestheader-client-ca-file", client) + dynamicRequestHeaderProvider, err := newDynamicRequestHeaderController(client) if err != nil { return nil, fmt.Errorf("unable to create request header authentication config: %v", err) } - authConfigMap, err := client.CoreV1().ConfigMaps(authenticationConfigMapNamespace).Get(context.TODO(), authenticationConfigMapName, metav1.GetOptions{}) - switch { - case errors.IsNotFound(err): - // ignore, authConfigMap is nil now - return nil, nil - case errors.IsForbidden(err): - klog.Warningf("Unable to get configmap/%s in %s. Usually fixed by "+ - "'kubectl create rolebinding -n %s ROLEBINDING_NAME --role=%s --serviceaccount=YOUR_NS:YOUR_SA'", - authenticationConfigMapName, authenticationConfigMapNamespace, authenticationConfigMapNamespace, authenticationRoleName) - return nil, err - case err != nil: - return nil, err - } - - usernameHeaders, err := deserializeStrings(authConfigMap.Data["requestheader-username-headers"]) - if err != nil { - return nil, err - } - groupHeaders, err := deserializeStrings(authConfigMap.Data["requestheader-group-headers"]) - if err != nil { - return nil, err - } - extraHeaderPrefixes, err := deserializeStrings(authConfigMap.Data["requestheader-extra-headers-prefix"]) - if err != nil { - return nil, err - } - allowedNames, err := deserializeStrings(authConfigMap.Data["requestheader-allowed-names"]) - if err != nil { - return nil, err - } - return &authenticatorfactory.RequestHeaderConfig{ - CAContentProvider: requestHeaderCAProvider, - UsernameHeaders: headerrequest.StaticStringSlice(usernameHeaders), - GroupHeaders: headerrequest.StaticStringSlice(groupHeaders), - ExtraHeaderPrefixes: headerrequest.StaticStringSlice(extraHeaderPrefixes), - AllowedClientNames: headerrequest.StaticStringSlice(allowedNames), + CAContentProvider: dynamicRequestHeaderProvider, + UsernameHeaders: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.UsernameHeaders)), + GroupHeaders: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.GroupHeaders)), + ExtraHeaderPrefixes: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.ExtraHeaderPrefixes)), + AllowedClientNames: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.AllowedClientNames)), }, nil } -func deserializeStrings(in string) ([]string, error) { - if len(in) == 0 { - return nil, nil - } - var ret []string - if err := json.Unmarshal([]byte(in), &ret); err != nil { - return nil, err - } - return ret, nil -} - // getClient returns a Kubernetes clientset. If s.RemoteKubeConfigFileOptional is true, nil will be returned // if no kubeconfig is specified by the user and the in-cluster config is not found. func (s *DelegatingAuthenticationOptions) getClient() (kubernetes.Interface, error) { diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/authentication_dynamic_request_header.go b/staging/src/k8s.io/apiserver/pkg/server/options/authentication_dynamic_request_header.go new file mode 100644 index 00000000000..9f11f728dec --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/options/authentication_dynamic_request_header.go @@ -0,0 +1,62 @@ +package options + +import ( + "fmt" + + "k8s.io/apiserver/pkg/authentication/request/headerrequest" + "k8s.io/apiserver/pkg/server/dynamiccertificates" + "k8s.io/client-go/kubernetes" +) + +var _ dynamiccertificates.ControllerRunner = &DynamicRequestHeaderController{} +var _ dynamiccertificates.Notifier = &DynamicRequestHeaderController{} +var _ dynamiccertificates.CAContentProvider = &DynamicRequestHeaderController{} + +var _ headerrequest.RequestHeaderAuthRequestProvider = &DynamicRequestHeaderController{} + +// DynamicRequestHeaderController combines DynamicCAFromConfigMapController and RequestHeaderAuthRequestController +// into one controller for dynamically filling RequestHeaderConfig struct +type DynamicRequestHeaderController struct { + *dynamiccertificates.ConfigMapCAController + *headerrequest.RequestHeaderAuthRequestController +} + +// newDynamicRequestHeaderController creates a new controller that implements DynamicRequestHeaderController +func newDynamicRequestHeaderController(client kubernetes.Interface) (*DynamicRequestHeaderController, error) { + requestHeaderCAController, err := dynamiccertificates.NewDynamicCAFromConfigMapController( + "client-ca", + authenticationConfigMapNamespace, + authenticationConfigMapName, + "requestheader-client-ca-file", + client) + if err != nil { + return nil, fmt.Errorf("unable to create DynamicCAFromConfigMap controller: %v", err) + } + + requestHeaderAuthRequestController, err := headerrequest.NewRequestHeaderAuthRequestController( + authenticationConfigMapName, + authenticationConfigMapNamespace, + client, + "requestheader-username-headers", + "requestheader-group-headers", + "requestheader-extra-headers-prefix", + "requestheader-allowed-names", + ) + if err != nil { + return nil, fmt.Errorf("unable to create RequestHeaderAuthRequest controller: %v", err) + } + return &DynamicRequestHeaderController{ + ConfigMapCAController: requestHeaderCAController, + RequestHeaderAuthRequestController: requestHeaderAuthRequestController, + }, nil +} + +func (c *DynamicRequestHeaderController) RunOnce() error { + return c.ConfigMapCAController.RunOnce() +} + +func (c *DynamicRequestHeaderController) Run(workers int, stopCh <-chan struct{}) { + go c.ConfigMapCAController.Run(workers, stopCh) + go c.RequestHeaderAuthRequestController.Run(workers, stopCh) + <-stopCh +} From f3a7f057c423caf77b0c5315d7728727c4b35bde Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Tue, 28 Apr 2020 15:35:17 +0200 Subject: [PATCH 3/4] expose RunOnce method on RequestHeaderAuthRequest controller --- .../headerrequest/requestheader_controller.go | 47 +++++++++---------- .../requestheader_controller_test.go | 3 +- .../pkg/server/options/authentication.go | 5 ++ .../authentication_dynamic_request_header.go | 27 +++++++++-- 4 files changed, 52 insertions(+), 30 deletions(-) diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go b/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go index 889a552f1a8..9e8bc8b21d9 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go +++ b/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go @@ -69,6 +69,7 @@ type RequestHeaderAuthRequestController struct { configmapName string configmapNamespace string + client kubernetes.Interface configmapLister corev1listers.ConfigMapNamespaceLister configmapInformer cache.SharedIndexInformer configmapInformerSynced cache.InformerSynced @@ -89,10 +90,12 @@ func NewRequestHeaderAuthRequestController( cmName string, cmNamespace string, client kubernetes.Interface, - usernameHeadersKey, groupHeadersKey, extraHeaderPrefixesKey, allowedClientNamesKey string) (*RequestHeaderAuthRequestController, error) { + usernameHeadersKey, groupHeadersKey, extraHeaderPrefixesKey, allowedClientNamesKey string) *RequestHeaderAuthRequestController { c := &RequestHeaderAuthRequestController{ name: "RequestHeaderAuthRequestController", + client: client, + configmapName: cmName, configmapNamespace: cmNamespace, @@ -104,11 +107,6 @@ func NewRequestHeaderAuthRequestController( queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "RequestHeaderAuthRequestController"), } - // use the live client to prime the controller - if err := c.syncOnce(client); err != nil { - return nil, err - } - // we construct our own informer because we need such a small subset of the information available. Just one namespace. c.configmapInformer = coreinformers.NewFilteredConfigMapInformer(client, c.configmapNamespace, 12*time.Hour, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, func(listOptions *metav1.ListOptions) { listOptions.FieldSelector = fields.OneTermEqualSelector("metadata.name", c.configmapName).String() @@ -144,7 +142,7 @@ func NewRequestHeaderAuthRequestController( c.configmapLister = corev1listers.NewConfigMapLister(c.configmapInformer.GetIndexer()).ConfigMaps(c.configmapNamespace) c.configmapInformerSynced = c.configmapInformer.HasSynced - return c, nil + return c } func (c *RequestHeaderAuthRequestController) UsernameHeaders() []string { @@ -184,6 +182,24 @@ func (c *RequestHeaderAuthRequestController) Run(workers int, stopCh <-chan stru <-stopCh } +// // RunOnce runs a single sync loop +func (c *RequestHeaderAuthRequestController) RunOnce() error { + configMap, err := c.client.CoreV1().ConfigMaps(c.configmapNamespace).Get(context.TODO(), c.configmapName, metav1.GetOptions{}) + switch { + case errors.IsNotFound(err): + // ignore, authConfigMap is nil now + return nil + case errors.IsForbidden(err): + klog.Warningf("Unable to get configmap/%s in %s. Usually fixed by "+ + "'kubectl create rolebinding -n %s ROLEBINDING_NAME --role=%s --serviceaccount=YOUR_NS:YOUR_SA'", + c.configmapName, c.configmapNamespace, c.configmapNamespace, authenticationRoleName) + return err + case err != nil: + return err + } + return c.syncConfigMap(configMap) +} + func (c *RequestHeaderAuthRequestController) runWorker() { for c.processNextWorkItem() { } @@ -208,23 +224,6 @@ func (c *RequestHeaderAuthRequestController) processNextWorkItem() bool { return true } -func (c *RequestHeaderAuthRequestController) syncOnce(client kubernetes.Interface) error { - configMap, err := client.CoreV1().ConfigMaps(c.configmapNamespace).Get(context.TODO(), c.configmapName, metav1.GetOptions{}) - switch { - case errors.IsNotFound(err): - // ignore, authConfigMap is nil now - return nil - case errors.IsForbidden(err): - klog.Warningf("Unable to get configmap/%s in %s. Usually fixed by "+ - "'kubectl create rolebinding -n %s ROLEBINDING_NAME --role=%s --serviceaccount=YOUR_NS:YOUR_SA'", - c.configmapName, c.configmapNamespace, c.configmapNamespace, authenticationRoleName) - return err - case err != nil: - return err - } - return c.syncConfigMap(configMap) -} - // sync reads the config and propagates the changes to exportedRequestHeaderBundle // which is exposed by the set of methods that are used to fill RequestHeaderConfig struct func (c *RequestHeaderAuthRequestController) sync() error { diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller_test.go b/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller_test.go index 07c299398f6..2577d2635b2 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller_test.go +++ b/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller_test.go @@ -218,9 +218,10 @@ func TestRequestHeaderAuthRequestControllerSyncOnce(t *testing.T) { // test data target := newDefaultTarget() fakeKubeClient := fake.NewSimpleClientset(scenario.cm) + target.client = fakeKubeClient // act - err := target.syncOnce(fakeKubeClient) + err := target.RunOnce() if err != nil && !scenario.expectErr { t.Errorf("got unexpected error %v", err) diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/authentication.go b/staging/src/k8s.io/apiserver/pkg/server/options/authentication.go index 86ef6feccd7..e2e2d33451b 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/authentication.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/authentication.go @@ -335,6 +335,11 @@ func (s *DelegatingAuthenticationOptions) createRequestHeaderConfig(client kuber return nil, fmt.Errorf("unable to create request header authentication config: %v", err) } + // look up authentication configuration in the cluster and in case of an err defer to authentication-tolerate-lookup-failure flag + if err := dynamicRequestHeaderProvider.RunOnce(); err != nil { + return nil, err + } + return &authenticatorfactory.RequestHeaderConfig{ CAContentProvider: dynamicRequestHeaderProvider, UsernameHeaders: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.UsernameHeaders)), diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/authentication_dynamic_request_header.go b/staging/src/k8s.io/apiserver/pkg/server/options/authentication_dynamic_request_header.go index 9f11f728dec..5c558b06d68 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/authentication_dynamic_request_header.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/authentication_dynamic_request_header.go @@ -1,8 +1,25 @@ +/* +Copyright 2020 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 ( "fmt" + "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apiserver/pkg/authentication/request/headerrequest" "k8s.io/apiserver/pkg/server/dynamiccertificates" "k8s.io/client-go/kubernetes" @@ -33,7 +50,7 @@ func newDynamicRequestHeaderController(client kubernetes.Interface) (*DynamicReq return nil, fmt.Errorf("unable to create DynamicCAFromConfigMap controller: %v", err) } - requestHeaderAuthRequestController, err := headerrequest.NewRequestHeaderAuthRequestController( + requestHeaderAuthRequestController := headerrequest.NewRequestHeaderAuthRequestController( authenticationConfigMapName, authenticationConfigMapNamespace, client, @@ -42,9 +59,6 @@ func newDynamicRequestHeaderController(client kubernetes.Interface) (*DynamicReq "requestheader-extra-headers-prefix", "requestheader-allowed-names", ) - if err != nil { - return nil, fmt.Errorf("unable to create RequestHeaderAuthRequest controller: %v", err) - } return &DynamicRequestHeaderController{ ConfigMapCAController: requestHeaderCAController, RequestHeaderAuthRequestController: requestHeaderAuthRequestController, @@ -52,7 +66,10 @@ func newDynamicRequestHeaderController(client kubernetes.Interface) (*DynamicReq } func (c *DynamicRequestHeaderController) RunOnce() error { - return c.ConfigMapCAController.RunOnce() + errs := []error{} + errs = append(errs, c.ConfigMapCAController.RunOnce()) + errs = append(errs, c.RequestHeaderAuthRequestController.RunOnce()) + return errors.NewAggregate(errs) } func (c *DynamicRequestHeaderController) Run(workers int, stopCh <-chan struct{}) { From f83f4a873f702728b74ccae7564cb12350728be0 Mon Sep 17 00:00:00 2001 From: Lukasz Szaszkiewicz Date: Wed, 29 Apr 2020 11:38:45 +0200 Subject: [PATCH 4/4] generated --- .../request/headerrequest/BUILD | 33 +++++++++++++++++-- .../k8s.io/apiserver/pkg/server/options/BUILD | 3 +- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/BUILD b/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/BUILD index 9aa48d1caa4..b0d6da11a67 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/BUILD @@ -8,21 +8,48 @@ load( go_test( name = "go_default_test", - srcs = ["requestheader_test.go"], + srcs = [ + "requestheader_controller_test.go", + "requestheader_test.go", + ], embed = [":go_default_library"], - deps = ["//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library"], + deps = [ + "//staging/src/k8s.io/api/core/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library", + "//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library", + "//staging/src/k8s.io/client-go/listers/core/v1:go_default_library", + "//staging/src/k8s.io/client-go/tools/cache:go_default_library", + ], ) go_library( name = "go_default_library", - srcs = ["requestheader.go"], + srcs = [ + "requestheader.go", + "requestheader_controller.go", + ], importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/authentication/request/headerrequest", importpath = "k8s.io/apiserver/pkg/authentication/request/headerrequest", deps = [ + "//staging/src/k8s.io/api/core/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authentication/request/x509:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library", + "//staging/src/k8s.io/client-go/informers/core/v1:go_default_library", + "//staging/src/k8s.io/client-go/kubernetes:go_default_library", + "//staging/src/k8s.io/client-go/listers/core/v1:go_default_library", + "//staging/src/k8s.io/client-go/tools/cache:go_default_library", "//staging/src/k8s.io/client-go/util/cert:go_default_library", + "//staging/src/k8s.io/client-go/util/workqueue:go_default_library", + "//vendor/k8s.io/klog:go_default_library", ], ) diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/BUILD b/staging/src/k8s.io/apiserver/pkg/server/options/BUILD index 10738bbbbc5..09154ec1204 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/server/options/BUILD @@ -7,6 +7,7 @@ go_library( "api_enablement.go", "audit.go", "authentication.go", + "authentication_dynamic_request_header.go", "authorization.go", "coreapi.go", "deprecated_insecure_serving.go", @@ -28,11 +29,11 @@ go_library( visibility = ["//visibility:public"], deps = [ "//staging/src/k8s.io/api/core/v1:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",