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 }