k8s.io/apiserver@v0.31.1/pkg/authentication/request/headerrequest/requestheader_controller.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package headerrequest
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"sync/atomic"
    24  	"time"
    25  
    26  	corev1 "k8s.io/api/core/v1"
    27  	"k8s.io/apimachinery/pkg/api/equality"
    28  	"k8s.io/apimachinery/pkg/api/errors"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/fields"
    31  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    32  	"k8s.io/apimachinery/pkg/util/wait"
    33  	coreinformers "k8s.io/client-go/informers/core/v1"
    34  	"k8s.io/client-go/kubernetes"
    35  	corev1listers "k8s.io/client-go/listers/core/v1"
    36  	"k8s.io/client-go/tools/cache"
    37  	"k8s.io/client-go/util/workqueue"
    38  	"k8s.io/klog/v2"
    39  )
    40  
    41  const (
    42  	authenticationRoleName = "extension-apiserver-authentication-reader"
    43  )
    44  
    45  // RequestHeaderAuthRequestProvider a provider that knows how to dynamically fill parts of RequestHeaderConfig struct
    46  type RequestHeaderAuthRequestProvider interface {
    47  	UsernameHeaders() []string
    48  	GroupHeaders() []string
    49  	ExtraHeaderPrefixes() []string
    50  	AllowedClientNames() []string
    51  }
    52  
    53  var _ RequestHeaderAuthRequestProvider = &RequestHeaderAuthRequestController{}
    54  
    55  type requestHeaderBundle struct {
    56  	UsernameHeaders     []string
    57  	GroupHeaders        []string
    58  	ExtraHeaderPrefixes []string
    59  	AllowedClientNames  []string
    60  }
    61  
    62  // RequestHeaderAuthRequestController a controller that exposes a set of methods for dynamically filling parts of RequestHeaderConfig struct.
    63  // The methods are sourced from the config map which is being monitored by this controller.
    64  // The controller is primed from the server at the construction time for components that don't want to dynamically react to changes
    65  // in the config map.
    66  type RequestHeaderAuthRequestController struct {
    67  	name string
    68  
    69  	configmapName      string
    70  	configmapNamespace string
    71  
    72  	client                  kubernetes.Interface
    73  	configmapLister         corev1listers.ConfigMapNamespaceLister
    74  	configmapInformer       cache.SharedIndexInformer
    75  	configmapInformerSynced cache.InformerSynced
    76  
    77  	queue workqueue.TypedRateLimitingInterface[string]
    78  
    79  	// exportedRequestHeaderBundle is a requestHeaderBundle that contains the last read, non-zero length content of the configmap
    80  	exportedRequestHeaderBundle atomic.Value
    81  
    82  	usernameHeadersKey     string
    83  	groupHeadersKey        string
    84  	extraHeaderPrefixesKey string
    85  	allowedClientNamesKey  string
    86  }
    87  
    88  // NewRequestHeaderAuthRequestController creates a new controller that implements RequestHeaderAuthRequestController
    89  func NewRequestHeaderAuthRequestController(
    90  	cmName string,
    91  	cmNamespace string,
    92  	client kubernetes.Interface,
    93  	usernameHeadersKey, groupHeadersKey, extraHeaderPrefixesKey, allowedClientNamesKey string) *RequestHeaderAuthRequestController {
    94  	c := &RequestHeaderAuthRequestController{
    95  		name: "RequestHeaderAuthRequestController",
    96  
    97  		client: client,
    98  
    99  		configmapName:      cmName,
   100  		configmapNamespace: cmNamespace,
   101  
   102  		usernameHeadersKey:     usernameHeadersKey,
   103  		groupHeadersKey:        groupHeadersKey,
   104  		extraHeaderPrefixesKey: extraHeaderPrefixesKey,
   105  		allowedClientNamesKey:  allowedClientNamesKey,
   106  
   107  		queue: workqueue.NewTypedRateLimitingQueueWithConfig(
   108  			workqueue.DefaultTypedControllerRateLimiter[string](),
   109  			workqueue.TypedRateLimitingQueueConfig[string]{Name: "RequestHeaderAuthRequestController"},
   110  		),
   111  	}
   112  
   113  	// we construct our own informer because we need such a small subset of the information available.  Just one namespace.
   114  	c.configmapInformer = coreinformers.NewFilteredConfigMapInformer(client, c.configmapNamespace, 12*time.Hour, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, func(listOptions *metav1.ListOptions) {
   115  		listOptions.FieldSelector = fields.OneTermEqualSelector("metadata.name", c.configmapName).String()
   116  	})
   117  
   118  	c.configmapInformer.AddEventHandler(cache.FilteringResourceEventHandler{
   119  		FilterFunc: func(obj interface{}) bool {
   120  			if cast, ok := obj.(*corev1.ConfigMap); ok {
   121  				return cast.Name == c.configmapName && cast.Namespace == c.configmapNamespace
   122  			}
   123  			if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok {
   124  				if cast, ok := tombstone.Obj.(*corev1.ConfigMap); ok {
   125  					return cast.Name == c.configmapName && cast.Namespace == c.configmapNamespace
   126  				}
   127  			}
   128  			return true // always return true just in case.  The checks are fairly cheap
   129  		},
   130  		Handler: cache.ResourceEventHandlerFuncs{
   131  			// we have a filter, so any time we're called, we may as well queue. We only ever check one configmap
   132  			// so we don't have to be choosy about our key.
   133  			AddFunc: func(obj interface{}) {
   134  				c.queue.Add(c.keyFn())
   135  			},
   136  			UpdateFunc: func(oldObj, newObj interface{}) {
   137  				c.queue.Add(c.keyFn())
   138  			},
   139  			DeleteFunc: func(obj interface{}) {
   140  				c.queue.Add(c.keyFn())
   141  			},
   142  		},
   143  	})
   144  
   145  	c.configmapLister = corev1listers.NewConfigMapLister(c.configmapInformer.GetIndexer()).ConfigMaps(c.configmapNamespace)
   146  	c.configmapInformerSynced = c.configmapInformer.HasSynced
   147  
   148  	return c
   149  }
   150  
   151  func (c *RequestHeaderAuthRequestController) UsernameHeaders() []string {
   152  	return c.loadRequestHeaderFor(c.usernameHeadersKey)
   153  }
   154  
   155  func (c *RequestHeaderAuthRequestController) GroupHeaders() []string {
   156  	return c.loadRequestHeaderFor(c.groupHeadersKey)
   157  }
   158  
   159  func (c *RequestHeaderAuthRequestController) ExtraHeaderPrefixes() []string {
   160  	return c.loadRequestHeaderFor(c.extraHeaderPrefixesKey)
   161  }
   162  
   163  func (c *RequestHeaderAuthRequestController) AllowedClientNames() []string {
   164  	return c.loadRequestHeaderFor(c.allowedClientNamesKey)
   165  }
   166  
   167  // Run starts RequestHeaderAuthRequestController controller and blocks until stopCh is closed.
   168  func (c *RequestHeaderAuthRequestController) Run(ctx context.Context, workers int) {
   169  	defer utilruntime.HandleCrash()
   170  	defer c.queue.ShutDown()
   171  
   172  	klog.Infof("Starting %s", c.name)
   173  	defer klog.Infof("Shutting down %s", c.name)
   174  
   175  	go c.configmapInformer.Run(ctx.Done())
   176  
   177  	// wait for caches to fill before starting your work
   178  	if !cache.WaitForNamedCacheSync(c.name, ctx.Done(), c.configmapInformerSynced) {
   179  		return
   180  	}
   181  
   182  	// doesn't matter what workers say, only start one.
   183  	go wait.Until(c.runWorker, time.Second, ctx.Done())
   184  
   185  	<-ctx.Done()
   186  }
   187  
   188  // // RunOnce runs a single sync loop
   189  func (c *RequestHeaderAuthRequestController) RunOnce(ctx context.Context) error {
   190  	configMap, err := c.client.CoreV1().ConfigMaps(c.configmapNamespace).Get(ctx, c.configmapName, metav1.GetOptions{})
   191  	switch {
   192  	case errors.IsNotFound(err):
   193  		// ignore, authConfigMap is nil now
   194  		return nil
   195  	case errors.IsForbidden(err):
   196  		klog.Warningf("Unable to get configmap/%s in %s.  Usually fixed by "+
   197  			"'kubectl create rolebinding -n %s ROLEBINDING_NAME --role=%s --serviceaccount=YOUR_NS:YOUR_SA'",
   198  			c.configmapName, c.configmapNamespace, c.configmapNamespace, authenticationRoleName)
   199  		return err
   200  	case err != nil:
   201  		return err
   202  	}
   203  	return c.syncConfigMap(configMap)
   204  }
   205  
   206  func (c *RequestHeaderAuthRequestController) runWorker() {
   207  	for c.processNextWorkItem() {
   208  	}
   209  }
   210  
   211  func (c *RequestHeaderAuthRequestController) processNextWorkItem() bool {
   212  	dsKey, quit := c.queue.Get()
   213  	if quit {
   214  		return false
   215  	}
   216  	defer c.queue.Done(dsKey)
   217  
   218  	err := c.sync()
   219  	if err == nil {
   220  		c.queue.Forget(dsKey)
   221  		return true
   222  	}
   223  
   224  	utilruntime.HandleError(fmt.Errorf("%v failed with : %v", dsKey, err))
   225  	c.queue.AddRateLimited(dsKey)
   226  
   227  	return true
   228  }
   229  
   230  // sync reads the config and propagates the changes to exportedRequestHeaderBundle
   231  // which is exposed by the set of methods that are used to fill RequestHeaderConfig struct
   232  func (c *RequestHeaderAuthRequestController) sync() error {
   233  	configMap, err := c.configmapLister.Get(c.configmapName)
   234  	if err != nil {
   235  		return err
   236  	}
   237  	return c.syncConfigMap(configMap)
   238  }
   239  
   240  func (c *RequestHeaderAuthRequestController) syncConfigMap(configMap *corev1.ConfigMap) error {
   241  	hasChanged, newRequestHeaderBundle, err := c.hasRequestHeaderBundleChanged(configMap)
   242  	if err != nil {
   243  		return err
   244  	}
   245  	if hasChanged {
   246  		c.exportedRequestHeaderBundle.Store(newRequestHeaderBundle)
   247  		klog.V(2).Infof("Loaded a new request header values for %v", c.name)
   248  	}
   249  	return nil
   250  }
   251  
   252  func (c *RequestHeaderAuthRequestController) hasRequestHeaderBundleChanged(cm *corev1.ConfigMap) (bool, *requestHeaderBundle, error) {
   253  	currentHeadersBundle, err := c.getRequestHeaderBundleFromConfigMap(cm)
   254  	if err != nil {
   255  		return false, nil, err
   256  	}
   257  
   258  	rawHeaderBundle := c.exportedRequestHeaderBundle.Load()
   259  	if rawHeaderBundle == nil {
   260  		return true, currentHeadersBundle, nil
   261  	}
   262  
   263  	// check to see if we have a change. If the values are the same, do nothing.
   264  	loadedHeadersBundle, ok := rawHeaderBundle.(*requestHeaderBundle)
   265  	if !ok {
   266  		return true, currentHeadersBundle, nil
   267  	}
   268  
   269  	if !equality.Semantic.DeepEqual(loadedHeadersBundle, currentHeadersBundle) {
   270  		return true, currentHeadersBundle, nil
   271  	}
   272  	return false, nil, nil
   273  }
   274  
   275  func (c *RequestHeaderAuthRequestController) getRequestHeaderBundleFromConfigMap(cm *corev1.ConfigMap) (*requestHeaderBundle, error) {
   276  	usernameHeaderCurrentValue, err := deserializeStrings(cm.Data[c.usernameHeadersKey])
   277  	if err != nil {
   278  		return nil, err
   279  	}
   280  
   281  	groupHeadersCurrentValue, err := deserializeStrings(cm.Data[c.groupHeadersKey])
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  
   286  	extraHeaderPrefixesCurrentValue, err := deserializeStrings(cm.Data[c.extraHeaderPrefixesKey])
   287  	if err != nil {
   288  		return nil, err
   289  
   290  	}
   291  
   292  	allowedClientNamesCurrentValue, err := deserializeStrings(cm.Data[c.allowedClientNamesKey])
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  
   297  	return &requestHeaderBundle{
   298  		UsernameHeaders:     usernameHeaderCurrentValue,
   299  		GroupHeaders:        groupHeadersCurrentValue,
   300  		ExtraHeaderPrefixes: extraHeaderPrefixesCurrentValue,
   301  		AllowedClientNames:  allowedClientNamesCurrentValue,
   302  	}, nil
   303  }
   304  
   305  func (c *RequestHeaderAuthRequestController) loadRequestHeaderFor(key string) []string {
   306  	rawHeaderBundle := c.exportedRequestHeaderBundle.Load()
   307  	if rawHeaderBundle == nil {
   308  		return nil // this can happen if we've been unable load data from the apiserver for some reason
   309  	}
   310  	headerBundle := rawHeaderBundle.(*requestHeaderBundle)
   311  
   312  	switch key {
   313  	case c.usernameHeadersKey:
   314  		return headerBundle.UsernameHeaders
   315  	case c.groupHeadersKey:
   316  		return headerBundle.GroupHeaders
   317  	case c.extraHeaderPrefixesKey:
   318  		return headerBundle.ExtraHeaderPrefixes
   319  	case c.allowedClientNamesKey:
   320  		return headerBundle.AllowedClientNames
   321  	default:
   322  		return nil
   323  	}
   324  }
   325  
   326  func (c *RequestHeaderAuthRequestController) keyFn() string {
   327  	// this format matches DeletionHandlingMetaNamespaceKeyFunc for our single key
   328  	return c.configmapNamespace + "/" + c.configmapName
   329  }
   330  
   331  func deserializeStrings(in string) ([]string, error) {
   332  	if len(in) == 0 {
   333  		return nil, nil
   334  	}
   335  	var ret []string
   336  	if err := json.Unmarshal([]byte(in), &ret); err != nil {
   337  		return nil, err
   338  	}
   339  	return ret, nil
   340  }