k8s.io/apiserver@v0.31.1/pkg/admission/plugin/policy/validating/caching_authorizer.go (about)

     1  /*
     2  Copyright 2023 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 validating
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"sort"
    23  	"strings"
    24  
    25  	"k8s.io/apimachinery/pkg/fields"
    26  	"k8s.io/apimachinery/pkg/labels"
    27  	"k8s.io/apiserver/pkg/authentication/user"
    28  	"k8s.io/apiserver/pkg/authorization/authorizer"
    29  )
    30  
    31  type authzResult struct {
    32  	authorized authorizer.Decision
    33  	reason     string
    34  	err        error
    35  }
    36  
    37  type cachingAuthorizer struct {
    38  	authorizer authorizer.Authorizer
    39  	decisions  map[string]authzResult
    40  }
    41  
    42  func newCachingAuthorizer(in authorizer.Authorizer) authorizer.Authorizer {
    43  	return &cachingAuthorizer{
    44  		authorizer: in,
    45  		decisions:  make(map[string]authzResult),
    46  	}
    47  }
    48  
    49  // The attribute accessors known to cache key construction. If this fails to compile, the cache
    50  // implementation may need to be updated.
    51  var _ authorizer.Attributes = (interface {
    52  	GetUser() user.Info
    53  	GetVerb() string
    54  	IsReadOnly() bool
    55  	GetNamespace() string
    56  	GetResource() string
    57  	GetSubresource() string
    58  	GetName() string
    59  	GetAPIGroup() string
    60  	GetAPIVersion() string
    61  	IsResourceRequest() bool
    62  	GetPath() string
    63  	GetFieldSelector() (fields.Requirements, error)
    64  	GetLabelSelector() (labels.Requirements, error)
    65  })(nil)
    66  
    67  // The user info accessors known to cache key construction. If this fails to compile, the cache
    68  // implementation may need to be updated.
    69  var _ user.Info = (interface {
    70  	GetName() string
    71  	GetUID() string
    72  	GetGroups() []string
    73  	GetExtra() map[string][]string
    74  })(nil)
    75  
    76  // Authorize returns an authorization decision by delegating to another Authorizer. If an equivalent
    77  // check has already been performed, a cached result is returned. Not safe for concurrent use.
    78  func (ca *cachingAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
    79  	type SerializableAttributes struct {
    80  		authorizer.AttributesRecord
    81  		LabelSelector string
    82  	}
    83  
    84  	serializableAttributes := SerializableAttributes{
    85  		AttributesRecord: authorizer.AttributesRecord{
    86  			Verb:            a.GetVerb(),
    87  			Namespace:       a.GetNamespace(),
    88  			APIGroup:        a.GetAPIGroup(),
    89  			APIVersion:      a.GetAPIVersion(),
    90  			Resource:        a.GetResource(),
    91  			Subresource:     a.GetSubresource(),
    92  			Name:            a.GetName(),
    93  			ResourceRequest: a.IsResourceRequest(),
    94  			Path:            a.GetPath(),
    95  		},
    96  	}
    97  	// in the error case, we won't honor this field selector, so the cache doesn't need it.
    98  	if fieldSelector, err := a.GetFieldSelector(); len(fieldSelector) > 0 {
    99  		serializableAttributes.FieldSelectorRequirements, serializableAttributes.FieldSelectorParsingErr = fieldSelector, err
   100  	}
   101  	if labelSelector, _ := a.GetLabelSelector(); len(labelSelector) > 0 {
   102  		// the labels requirements have private elements so those don't help us serialize to a unique key
   103  		serializableAttributes.LabelSelector = labelSelector.String()
   104  	}
   105  
   106  	if u := a.GetUser(); u != nil {
   107  		di := &user.DefaultInfo{
   108  			Name: u.GetName(),
   109  			UID:  u.GetUID(),
   110  		}
   111  
   112  		// Differently-ordered groups or extras could cause otherwise-equivalent checks to
   113  		// have distinct cache keys.
   114  		if groups := u.GetGroups(); len(groups) > 0 {
   115  			di.Groups = make([]string, len(groups))
   116  			copy(di.Groups, groups)
   117  			sort.Strings(di.Groups)
   118  		}
   119  
   120  		if extra := u.GetExtra(); len(extra) > 0 {
   121  			di.Extra = make(map[string][]string, len(extra))
   122  			for k, vs := range extra {
   123  				vdupe := make([]string, len(vs))
   124  				copy(vdupe, vs)
   125  				sort.Strings(vdupe)
   126  				di.Extra[k] = vdupe
   127  			}
   128  		}
   129  
   130  		serializableAttributes.User = di
   131  	}
   132  
   133  	var b strings.Builder
   134  	if err := json.NewEncoder(&b).Encode(serializableAttributes); err != nil {
   135  		return authorizer.DecisionNoOpinion, "", err
   136  	}
   137  	key := b.String()
   138  
   139  	if cached, ok := ca.decisions[key]; ok {
   140  		return cached.authorized, cached.reason, cached.err
   141  	}
   142  
   143  	authorized, reason, err := ca.authorizer.Authorize(ctx, a)
   144  
   145  	ca.decisions[key] = authzResult{
   146  		authorized: authorized,
   147  		reason:     reason,
   148  		err:        err,
   149  	}
   150  
   151  	return authorized, reason, err
   152  }