mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 19:01:49 +00:00
Merge pull request #90548 from p0lyn0mial/wire-ctrl-dynamic-request-header-auth-provider
provides DynamicRequestHeaderController for dynamically filling RequestHeaderConfig struct
This commit is contained in:
commit
d87f38cda8
@ -8,21 +8,48 @@ load(
|
|||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = ["requestheader_test.go"],
|
srcs = [
|
||||||
|
"requestheader_controller_test.go",
|
||||||
|
"requestheader_test.go",
|
||||||
|
],
|
||||||
embed = [":go_default_library"],
|
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(
|
go_library(
|
||||||
name = "go_default_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",
|
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/authentication/request/headerrequest",
|
||||||
importpath = "k8s.io/apiserver/pkg/authentication/request/headerrequest",
|
importpath = "k8s.io/apiserver/pkg/authentication/request/headerrequest",
|
||||||
deps = [
|
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/authenticator:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/authentication/request/x509: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/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/cert:go_default_library",
|
||||||
|
"//staging/src/k8s.io/client-go/util/workqueue:go_default_library",
|
||||||
|
"//vendor/k8s.io/klog:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -0,0 +1,337 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
"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"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
ExtraHeaderPrefixes []string
|
||||||
|
AllowedClientNames []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
type RequestHeaderAuthRequestController struct {
|
||||||
|
name string
|
||||||
|
|
||||||
|
configmapName string
|
||||||
|
configmapNamespace string
|
||||||
|
|
||||||
|
client kubernetes.Interface
|
||||||
|
configmapLister corev1listers.ConfigMapNamespaceLister
|
||||||
|
configmapInformer cache.SharedIndexInformer
|
||||||
|
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,
|
||||||
|
usernameHeadersKey, groupHeadersKey, extraHeaderPrefixesKey, allowedClientNamesKey string) *RequestHeaderAuthRequestController {
|
||||||
|
c := &RequestHeaderAuthRequestController{
|
||||||
|
name: "RequestHeaderAuthRequestController",
|
||||||
|
|
||||||
|
client: client,
|
||||||
|
|
||||||
|
configmapName: cmName,
|
||||||
|
configmapNamespace: cmNamespace,
|
||||||
|
|
||||||
|
usernameHeadersKey: usernameHeadersKey,
|
||||||
|
groupHeadersKey: groupHeadersKey,
|
||||||
|
extraHeaderPrefixesKey: extraHeaderPrefixesKey,
|
||||||
|
allowedClientNamesKey: allowedClientNamesKey,
|
||||||
|
|
||||||
|
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "RequestHeaderAuthRequestController"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
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 = corev1listers.NewConfigMapLister(c.configmapInformer.GetIndexer()).ConfigMaps(c.configmapNamespace)
|
||||||
|
c.configmapInformerSynced = c.configmapInformer.HasSynced
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
go c.configmapInformer.Run(stopCh)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// // 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() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
klog.V(2).Infof("Loaded a new request header values for %v", c.name)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,285 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
target.client = fakeKubeClient
|
||||||
|
|
||||||
|
// act
|
||||||
|
err := target.RunOnce()
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ go_library(
|
|||||||
"api_enablement.go",
|
"api_enablement.go",
|
||||||
"audit.go",
|
"audit.go",
|
||||||
"authentication.go",
|
"authentication.go",
|
||||||
|
"authentication_dynamic_request_header.go",
|
||||||
"authorization.go",
|
"authorization.go",
|
||||||
"coreapi.go",
|
"coreapi.go",
|
||||||
"deprecated_insecure_serving.go",
|
"deprecated_insecure_serving.go",
|
||||||
@ -28,11 +29,11 @@ go_library(
|
|||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
"//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/apis/meta/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime: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/schema:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer: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/net:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/runtime: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",
|
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||||
|
@ -17,8 +17,6 @@ limitations under the License.
|
|||||||
package options
|
package options
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -27,7 +25,6 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
|
"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
|
||||||
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
|
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
|
||||||
@ -330,66 +327,28 @@ const (
|
|||||||
// by --requestheader-username-headers. This is created in the cluster by the kube-apiserver.
|
// 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.")
|
// "WARNING: generally do not depend on authorization being already done for incoming requests.")
|
||||||
authenticationConfigMapName = "extension-apiserver-authentication"
|
authenticationConfigMapName = "extension-apiserver-authentication"
|
||||||
authenticationRoleName = "extension-apiserver-authentication-reader"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *DelegatingAuthenticationOptions) createRequestHeaderConfig(client kubernetes.Interface) (*authenticatorfactory.RequestHeaderConfig, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to create request header authentication config: %v", err)
|
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{})
|
// look up authentication configuration in the cluster and in case of an err defer to authentication-tolerate-lookup-failure flag
|
||||||
switch {
|
if err := dynamicRequestHeaderProvider.RunOnce(); err != nil {
|
||||||
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 nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &authenticatorfactory.RequestHeaderConfig{
|
return &authenticatorfactory.RequestHeaderConfig{
|
||||||
CAContentProvider: requestHeaderCAProvider,
|
CAContentProvider: dynamicRequestHeaderProvider,
|
||||||
UsernameHeaders: headerrequest.StaticStringSlice(usernameHeaders),
|
UsernameHeaders: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.UsernameHeaders)),
|
||||||
GroupHeaders: headerrequest.StaticStringSlice(groupHeaders),
|
GroupHeaders: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.GroupHeaders)),
|
||||||
ExtraHeaderPrefixes: headerrequest.StaticStringSlice(extraHeaderPrefixes),
|
ExtraHeaderPrefixes: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.ExtraHeaderPrefixes)),
|
||||||
AllowedClientNames: headerrequest.StaticStringSlice(allowedNames),
|
AllowedClientNames: headerrequest.StringSliceProvider(headerrequest.StringSliceProviderFunc(dynamicRequestHeaderProvider.AllowedClientNames)),
|
||||||
}, nil
|
}, 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
|
// 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.
|
// if no kubeconfig is specified by the user and the in-cluster config is not found.
|
||||||
func (s *DelegatingAuthenticationOptions) getClient() (kubernetes.Interface, error) {
|
func (s *DelegatingAuthenticationOptions) getClient() (kubernetes.Interface, error) {
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 := headerrequest.NewRequestHeaderAuthRequestController(
|
||||||
|
authenticationConfigMapName,
|
||||||
|
authenticationConfigMapNamespace,
|
||||||
|
client,
|
||||||
|
"requestheader-username-headers",
|
||||||
|
"requestheader-group-headers",
|
||||||
|
"requestheader-extra-headers-prefix",
|
||||||
|
"requestheader-allowed-names",
|
||||||
|
)
|
||||||
|
return &DynamicRequestHeaderController{
|
||||||
|
ConfigMapCAController: requestHeaderCAController,
|
||||||
|
RequestHeaderAuthRequestController: requestHeaderAuthRequestController,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DynamicRequestHeaderController) RunOnce() error {
|
||||||
|
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{}) {
|
||||||
|
go c.ConfigMapCAController.Run(workers, stopCh)
|
||||||
|
go c.RequestHeaderAuthRequestController.Run(workers, stopCh)
|
||||||
|
<-stopCh
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user