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 }