k8s.io/apiserver@v0.31.1/pkg/audit/policy/checker.go (about)

     1  /*
     2  Copyright 2017 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 policy
    18  
    19  import (
    20  	"strings"
    21  
    22  	"k8s.io/apiserver/pkg/apis/audit"
    23  	auditinternal "k8s.io/apiserver/pkg/audit"
    24  	"k8s.io/apiserver/pkg/authorization/authorizer"
    25  )
    26  
    27  const (
    28  	// DefaultAuditLevel is the default level to audit at, if no policy rules are matched.
    29  	DefaultAuditLevel = audit.LevelNone
    30  )
    31  
    32  // NewPolicyRuleEvaluator creates a new policy rule evaluator.
    33  func NewPolicyRuleEvaluator(policy *audit.Policy) auditinternal.PolicyRuleEvaluator {
    34  	for i, rule := range policy.Rules {
    35  		policy.Rules[i].OmitStages = unionStages(policy.OmitStages, rule.OmitStages)
    36  	}
    37  	return &policyRuleEvaluator{*policy}
    38  }
    39  
    40  func unionStages(stageLists ...[]audit.Stage) []audit.Stage {
    41  	m := make(map[audit.Stage]bool)
    42  	for _, sl := range stageLists {
    43  		for _, s := range sl {
    44  			m[s] = true
    45  		}
    46  	}
    47  	result := make([]audit.Stage, 0, len(m))
    48  	for key := range m {
    49  		result = append(result, key)
    50  	}
    51  	return result
    52  }
    53  
    54  // NewFakePolicyRuleEvaluator creates a fake policy rule evaluator that returns
    55  // a constant level for all requests (for testing).
    56  func NewFakePolicyRuleEvaluator(level audit.Level, stage []audit.Stage) auditinternal.PolicyRuleEvaluator {
    57  	return &fakePolicyRuleEvaluator{level, stage}
    58  }
    59  
    60  type policyRuleEvaluator struct {
    61  	audit.Policy
    62  }
    63  
    64  func (p *policyRuleEvaluator) EvaluatePolicyRule(attrs authorizer.Attributes) auditinternal.RequestAuditConfig {
    65  	for _, rule := range p.Rules {
    66  		if ruleMatches(&rule, attrs) {
    67  			return auditinternal.RequestAuditConfig{
    68  				Level:             rule.Level,
    69  				OmitStages:        rule.OmitStages,
    70  				OmitManagedFields: isOmitManagedFields(&rule, p.OmitManagedFields),
    71  			}
    72  		}
    73  	}
    74  
    75  	return auditinternal.RequestAuditConfig{
    76  		Level:             DefaultAuditLevel,
    77  		OmitStages:        p.OmitStages,
    78  		OmitManagedFields: p.OmitManagedFields,
    79  	}
    80  }
    81  
    82  // isOmitManagedFields returns whether to omit managed fields from the request
    83  // and response bodies from being written to the API audit log.
    84  // If a user specifies OmitManagedFields inside a policy rule, that overrides
    85  // the global policy default in Policy.OmitManagedFields.
    86  func isOmitManagedFields(policyRule *audit.PolicyRule, policyDefault bool) bool {
    87  	if policyRule.OmitManagedFields == nil {
    88  		return policyDefault
    89  	}
    90  
    91  	return *policyRule.OmitManagedFields
    92  }
    93  
    94  // Check whether the rule matches the request attrs.
    95  func ruleMatches(r *audit.PolicyRule, attrs authorizer.Attributes) bool {
    96  	user := attrs.GetUser()
    97  	if len(r.Users) > 0 {
    98  		if user == nil || !hasString(r.Users, user.GetName()) {
    99  			return false
   100  		}
   101  	}
   102  	if len(r.UserGroups) > 0 {
   103  		if user == nil {
   104  			return false
   105  		}
   106  		matched := false
   107  		for _, group := range user.GetGroups() {
   108  			if hasString(r.UserGroups, group) {
   109  				matched = true
   110  				break
   111  			}
   112  		}
   113  		if !matched {
   114  			return false
   115  		}
   116  	}
   117  	if len(r.Verbs) > 0 {
   118  		if !hasString(r.Verbs, attrs.GetVerb()) {
   119  			return false
   120  		}
   121  	}
   122  
   123  	if len(r.Namespaces) > 0 || len(r.Resources) > 0 {
   124  		return ruleMatchesResource(r, attrs)
   125  	}
   126  
   127  	if len(r.NonResourceURLs) > 0 {
   128  		return ruleMatchesNonResource(r, attrs)
   129  	}
   130  
   131  	return true
   132  }
   133  
   134  // Check whether the rule's non-resource URLs match the request attrs.
   135  func ruleMatchesNonResource(r *audit.PolicyRule, attrs authorizer.Attributes) bool {
   136  	if attrs.IsResourceRequest() {
   137  		return false
   138  	}
   139  
   140  	path := attrs.GetPath()
   141  	for _, spec := range r.NonResourceURLs {
   142  		if pathMatches(path, spec) {
   143  			return true
   144  		}
   145  	}
   146  
   147  	return false
   148  }
   149  
   150  // Check whether the path matches the path specification.
   151  func pathMatches(path, spec string) bool {
   152  	// Allow wildcard match
   153  	if spec == "*" {
   154  		return true
   155  	}
   156  	// Allow exact match
   157  	if spec == path {
   158  		return true
   159  	}
   160  	// Allow a trailing * subpath match
   161  	if strings.HasSuffix(spec, "*") && strings.HasPrefix(path, strings.TrimRight(spec, "*")) {
   162  		return true
   163  	}
   164  	return false
   165  }
   166  
   167  // Check whether the rule's resource fields match the request attrs.
   168  func ruleMatchesResource(r *audit.PolicyRule, attrs authorizer.Attributes) bool {
   169  	if !attrs.IsResourceRequest() {
   170  		return false
   171  	}
   172  
   173  	if len(r.Namespaces) > 0 {
   174  		if !hasString(r.Namespaces, attrs.GetNamespace()) { // Non-namespaced resources use the empty string.
   175  			return false
   176  		}
   177  	}
   178  	if len(r.Resources) == 0 {
   179  		return true
   180  	}
   181  
   182  	apiGroup := attrs.GetAPIGroup()
   183  	resource := attrs.GetResource()
   184  	subresource := attrs.GetSubresource()
   185  	combinedResource := resource
   186  	// If subresource, the resource in the policy must match "(resource)/(subresource)"
   187  	if subresource != "" {
   188  		combinedResource = resource + "/" + subresource
   189  	}
   190  
   191  	name := attrs.GetName()
   192  
   193  	for _, gr := range r.Resources {
   194  		if gr.Group == apiGroup {
   195  			if len(gr.Resources) == 0 {
   196  				return true
   197  			}
   198  			for _, res := range gr.Resources {
   199  				if len(gr.ResourceNames) == 0 || hasString(gr.ResourceNames, name) {
   200  					// match "*"
   201  					if res == combinedResource || res == "*" {
   202  						return true
   203  					}
   204  					// match "*/subresource"
   205  					if len(subresource) > 0 && strings.HasPrefix(res, "*/") && subresource == strings.TrimPrefix(res, "*/") {
   206  						return true
   207  					}
   208  					// match "resource/*"
   209  					if strings.HasSuffix(res, "/*") && resource == strings.TrimSuffix(res, "/*") {
   210  						return true
   211  					}
   212  				}
   213  			}
   214  		}
   215  	}
   216  	return false
   217  }
   218  
   219  // Utility function to check whether a string slice contains a string.
   220  func hasString(slice []string, value string) bool {
   221  	for _, s := range slice {
   222  		if s == value {
   223  			return true
   224  		}
   225  	}
   226  	return false
   227  }
   228  
   229  type fakePolicyRuleEvaluator struct {
   230  	level audit.Level
   231  	stage []audit.Stage
   232  }
   233  
   234  func (f *fakePolicyRuleEvaluator) EvaluatePolicyRule(_ authorizer.Attributes) auditinternal.RequestAuditConfig {
   235  	return auditinternal.RequestAuditConfig{
   236  		Level:      f.level,
   237  		OmitStages: f.stage,
   238  	}
   239  }