k8s.io/apiserver@v0.31.1/pkg/audit/policy/checker_test.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 "testing" 22 23 "github.com/stretchr/testify/assert" 24 "github.com/stretchr/testify/require" 25 26 "k8s.io/apiserver/pkg/apis/audit" 27 "k8s.io/apiserver/pkg/authentication/user" 28 "k8s.io/apiserver/pkg/authorization/authorizer" 29 ) 30 31 var ( 32 tim = &user.DefaultInfo{ 33 Name: "tim@k8s.io", 34 Groups: []string{"humans", "developers"}, 35 } 36 attrs = map[string]authorizer.Attributes{ 37 "namespaced": &authorizer.AttributesRecord{ 38 User: tim, 39 Verb: "get", 40 Namespace: "default", 41 APIGroup: "", // Core 42 APIVersion: "v1", 43 Resource: "pods", 44 Name: "busybox", 45 ResourceRequest: true, 46 Path: "/api/v1/namespaces/default/pods/busybox", 47 }, 48 "cluster": &authorizer.AttributesRecord{ 49 User: tim, 50 Verb: "get", 51 APIGroup: "rbac.authorization.k8s.io", // Core 52 APIVersion: "v1beta1", 53 Resource: "clusterroles", 54 Name: "edit", 55 ResourceRequest: true, 56 Path: "/apis/rbac.authorization.k8s.io/v1beta1/clusterroles/edit", 57 }, 58 "nonResource": &authorizer.AttributesRecord{ 59 User: tim, 60 Verb: "get", 61 ResourceRequest: false, 62 Path: "/logs/kubelet.log", 63 }, 64 "subresource": &authorizer.AttributesRecord{ 65 User: tim, 66 Verb: "get", 67 Namespace: "default", 68 APIGroup: "", // Core 69 APIVersion: "v1", 70 Resource: "pods", 71 Subresource: "log", 72 Name: "busybox", 73 ResourceRequest: true, 74 Path: "/api/v1/namespaces/default/pods/busybox", 75 }, 76 "Unauthorized": &authorizer.AttributesRecord{ 77 Verb: "get", 78 Namespace: "default", 79 APIGroup: "", // Core 80 APIVersion: "v1", 81 Resource: "pods", 82 Name: "busybox", 83 ResourceRequest: true, 84 Path: "/api/v1/namespaces/default/pods/busybox", 85 }, 86 } 87 88 rules = map[string]audit.PolicyRule{ 89 "default": { 90 Level: audit.LevelMetadata, 91 }, 92 "create": { 93 Level: audit.LevelRequest, 94 Verbs: []string{"create"}, 95 }, 96 "tims": { 97 Level: audit.LevelMetadata, 98 Users: []string{"tim@k8s.io"}, 99 }, 100 "humans": { 101 Level: audit.LevelMetadata, 102 UserGroups: []string{"humans"}, 103 }, 104 "serviceAccounts": { 105 Level: audit.LevelRequest, 106 UserGroups: []string{"system:serviceaccounts"}, 107 }, 108 "getPods": { 109 Level: audit.LevelRequestResponse, 110 Verbs: []string{"get"}, 111 Resources: []audit.GroupResources{{Resources: []string{"pods"}}}, 112 }, 113 "getPodLogs": { 114 Level: audit.LevelRequest, 115 Verbs: []string{"get"}, 116 Resources: []audit.GroupResources{{Resources: []string{"pods/log"}}}, 117 }, 118 "getPodWildcardMatching": { 119 Level: audit.LevelRequest, 120 Verbs: []string{"get"}, 121 Resources: []audit.GroupResources{{Resources: []string{"*"}}}, 122 }, 123 "getPodResourceWildcardMatching": { 124 Level: audit.LevelRequest, 125 Verbs: []string{"get"}, 126 Resources: []audit.GroupResources{{Resources: []string{"*/log"}}}, 127 }, 128 "getPodSubResourceWildcardMatching": { 129 Level: audit.LevelRequest, 130 Verbs: []string{"get"}, 131 Resources: []audit.GroupResources{{Resources: []string{"pods/*"}}}, 132 }, 133 "getClusterRoles": { 134 Level: audit.LevelRequestResponse, 135 Verbs: []string{"get"}, 136 Resources: []audit.GroupResources{{ 137 Group: "rbac.authorization.k8s.io", 138 Resources: []string{"clusterroles"}, 139 }}, 140 Namespaces: []string{""}, 141 }, 142 "getLogs": { 143 Level: audit.LevelRequestResponse, 144 Verbs: []string{"get"}, 145 NonResourceURLs: []string{ 146 "/logs*", 147 }, 148 }, 149 "getMetrics": { 150 Level: audit.LevelRequest, 151 Verbs: []string{"get"}, 152 NonResourceURLs: []string{ 153 "/metrics", 154 }, 155 }, 156 "clusterRoleEdit": { 157 Level: audit.LevelRequest, 158 Resources: []audit.GroupResources{{ 159 Group: "rbac.authorization.k8s.io", 160 Resources: []string{"clusterroles"}, 161 ResourceNames: []string{"edit"}, 162 }}, 163 }, 164 "omit RequestReceived": { 165 Level: audit.LevelRequest, 166 OmitStages: []audit.Stage{ 167 audit.StageRequestReceived, 168 }, 169 }, 170 "only audit panic": { 171 Level: audit.LevelRequest, 172 OmitStages: []audit.Stage{ 173 audit.StageRequestReceived, 174 audit.StageResponseStarted, 175 audit.StageResponseComplete, 176 }, 177 }, 178 } 179 ) 180 181 func test(t *testing.T, req string, expLevel audit.Level, policyStages, expOmitStages []audit.Stage, ruleNames ...string) { 182 policy := audit.Policy{OmitStages: policyStages} 183 for _, rule := range ruleNames { 184 require.Contains(t, rules, rule) 185 policy.Rules = append(policy.Rules, rules[rule]) 186 } 187 require.Contains(t, attrs, req) 188 auditConfig := NewPolicyRuleEvaluator(&policy).EvaluatePolicyRule(attrs[req]) 189 assert.Equal(t, expLevel, auditConfig.Level, "request:%s rules:%s", req, strings.Join(ruleNames, ",")) 190 assert.True(t, stageEqual(expOmitStages, auditConfig.OmitStages), "request:%s rules:%s, expected stages: %v, actual stages: %v", 191 req, strings.Join(ruleNames, ","), expOmitStages, auditConfig.OmitStages) 192 } 193 194 func testAuditLevel(t *testing.T, stages []audit.Stage) { 195 test(t, "namespaced", audit.LevelMetadata, stages, stages, "default") 196 test(t, "namespaced", audit.LevelNone, stages, stages, "create") 197 test(t, "namespaced", audit.LevelMetadata, stages, stages, "tims") 198 test(t, "namespaced", audit.LevelMetadata, stages, stages, "humans") 199 test(t, "namespaced", audit.LevelNone, stages, stages, "serviceAccounts") 200 test(t, "namespaced", audit.LevelRequestResponse, stages, stages, "getPods") 201 test(t, "namespaced", audit.LevelNone, stages, stages, "getClusterRoles") 202 test(t, "namespaced", audit.LevelNone, stages, stages, "getLogs") 203 test(t, "namespaced", audit.LevelNone, stages, stages, "getMetrics") 204 test(t, "namespaced", audit.LevelMetadata, stages, stages, "getMetrics", "serviceAccounts", "default") 205 test(t, "namespaced", audit.LevelRequestResponse, stages, stages, "getMetrics", "getPods", "default") 206 test(t, "namespaced", audit.LevelRequestResponse, stages, stages, "getPodLogs", "getPods") 207 208 test(t, "cluster", audit.LevelMetadata, stages, stages, "default") 209 test(t, "cluster", audit.LevelNone, stages, stages, "create") 210 test(t, "cluster", audit.LevelMetadata, stages, stages, "tims") 211 test(t, "cluster", audit.LevelMetadata, stages, stages, "humans") 212 test(t, "cluster", audit.LevelNone, stages, stages, "serviceAccounts") 213 test(t, "cluster", audit.LevelNone, stages, stages, "getPods") 214 test(t, "cluster", audit.LevelRequestResponse, stages, stages, "getClusterRoles") 215 test(t, "cluster", audit.LevelRequest, stages, stages, "clusterRoleEdit", "getClusterRoles") 216 test(t, "cluster", audit.LevelNone, stages, stages, "getLogs") 217 test(t, "cluster", audit.LevelNone, stages, stages, "getMetrics") 218 test(t, "cluster", audit.LevelMetadata, stages, stages, "getMetrics", "serviceAccounts", "default") 219 test(t, "cluster", audit.LevelRequestResponse, stages, stages, "getMetrics", "getClusterRoles", "default") 220 test(t, "cluster", audit.LevelNone, stages, stages, "getPodLogs", "getPods") 221 222 test(t, "nonResource", audit.LevelMetadata, stages, stages, "default") 223 test(t, "nonResource", audit.LevelNone, stages, stages, "create") 224 test(t, "nonResource", audit.LevelMetadata, stages, stages, "tims") 225 test(t, "nonResource", audit.LevelMetadata, stages, stages, "humans") 226 test(t, "nonResource", audit.LevelNone, stages, stages, "serviceAccounts") 227 test(t, "nonResource", audit.LevelNone, stages, stages, "getPods") 228 test(t, "nonResource", audit.LevelNone, stages, stages, "getClusterRoles") 229 test(t, "nonResource", audit.LevelRequestResponse, stages, stages, "getLogs") 230 test(t, "nonResource", audit.LevelNone, stages, stages, "getMetrics") 231 test(t, "nonResource", audit.LevelMetadata, stages, stages, "getMetrics", "serviceAccounts", "default") 232 test(t, "nonResource", audit.LevelRequestResponse, stages, stages, "getLogs", "getClusterRoles", "default") 233 test(t, "nonResource", audit.LevelNone, stages, stages, "getPodLogs", "getPods") 234 235 test(t, "subresource", audit.LevelRequest, stages, stages, "getPodLogs", "getPods") 236 test(t, "subresource", audit.LevelRequest, stages, stages, "getPodWildcardMatching") 237 test(t, "subresource", audit.LevelRequest, stages, stages, "getPodResourceWildcardMatching") 238 test(t, "subresource", audit.LevelRequest, stages, stages, "getPodSubResourceWildcardMatching") 239 240 test(t, "Unauthorized", audit.LevelNone, stages, stages, "tims") 241 test(t, "Unauthorized", audit.LevelMetadata, stages, stages, "tims", "default") 242 test(t, "Unauthorized", audit.LevelNone, stages, stages, "humans") 243 test(t, "Unauthorized", audit.LevelMetadata, stages, stages, "humans", "default") 244 } 245 246 func TestChecker(t *testing.T) { 247 testAuditLevel(t, nil) 248 249 // test omitStages pre rule 250 test(t, "namespaced", audit.LevelRequest, nil, []audit.Stage{audit.StageRequestReceived}, "omit RequestReceived", "getPods", "default") 251 test(t, "namespaced", audit.LevelRequest, nil, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default") 252 test(t, "cluster", audit.LevelRequest, nil, []audit.Stage{audit.StageRequestReceived}, "omit RequestReceived", "getPods", "default") 253 test(t, "cluster", audit.LevelRequest, nil, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default") 254 test(t, "nonResource", audit.LevelRequest, nil, []audit.Stage{audit.StageRequestReceived}, "omit RequestReceived", "getPods", "default") 255 test(t, "nonResource", audit.LevelRequest, nil, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default") 256 } 257 258 func TestCheckerPolicyOmitStages(t *testing.T) { 259 policyStages := []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted} 260 testAuditLevel(t, policyStages) 261 262 // test omitStages policy wide 263 test(t, "namespaced", audit.LevelRequest, policyStages, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted}, "omit RequestReceived", "getPods", "default") 264 test(t, "namespaced", audit.LevelRequest, policyStages, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default") 265 test(t, "cluster", audit.LevelRequest, policyStages, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted}, "omit RequestReceived", "getPods", "default") 266 test(t, "cluster", audit.LevelRequest, policyStages, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default") 267 test(t, "nonResource", audit.LevelMetadata, policyStages, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted}, "default", "omit RequestReceived", "getPods") 268 test(t, "nonResource", audit.LevelRequest, policyStages, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default") 269 } 270 271 // stageEqual returns true if s1 and s2 are super set of each other 272 func stageEqual(s1, s2 []audit.Stage) bool { 273 m1 := make(map[audit.Stage]bool) 274 m2 := make(map[audit.Stage]bool) 275 for _, s := range s1 { 276 m1[s] = true 277 } 278 for _, s := range s2 { 279 m2[s] = true 280 } 281 if len(m1) != len(m2) { 282 return false 283 } 284 for key, value := range m1 { 285 if m2[key] != value { 286 return false 287 } 288 } 289 return true 290 } 291 292 func TestUnionStages(t *testing.T) { 293 var testCases = []struct { 294 s1, s2, exp []audit.Stage 295 }{ 296 { 297 []audit.Stage{}, 298 []audit.Stage{}, 299 []audit.Stage{}, 300 }, 301 { 302 []audit.Stage{audit.StageRequestReceived}, 303 []audit.Stage{}, 304 []audit.Stage{audit.StageRequestReceived}, 305 }, 306 { 307 []audit.Stage{audit.StageRequestReceived}, 308 []audit.Stage{audit.StageRequestReceived}, 309 []audit.Stage{audit.StageRequestReceived}, 310 }, 311 { 312 []audit.Stage{audit.StageRequestReceived}, 313 []audit.Stage{audit.StageResponseStarted}, 314 []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted}, 315 }, 316 { 317 []audit.Stage{audit.StageRequestReceived, audit.StageRequestReceived}, 318 []audit.Stage{audit.StageRequestReceived, audit.StageRequestReceived}, 319 []audit.Stage{audit.StageRequestReceived}, 320 }, 321 { 322 []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted}, 323 []audit.Stage{audit.StagePanic, audit.StageRequestReceived}, 324 []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StagePanic}, 325 }, 326 { 327 nil, 328 []audit.Stage{audit.StageRequestReceived}, 329 []audit.Stage{audit.StageRequestReceived}, 330 }, 331 } 332 333 for _, tc := range testCases { 334 result := unionStages(tc.s1, tc.s2) 335 assert.Len(t, result, len(tc.exp)) 336 for _, expStage := range tc.exp { 337 ok := false 338 for _, resultStage := range result { 339 if resultStage == expStage { 340 ok = true 341 break 342 } 343 } 344 assert.True(t, ok) 345 } 346 } 347 } 348 349 func TestOmitManagedFields(t *testing.T) { 350 // this authorizer.Attributes should match all policy rules 351 // specified in this test. 352 attributes := &authorizer.AttributesRecord{ 353 Verb: "get", 354 } 355 matchingPolicyRule := audit.PolicyRule{ 356 Level: audit.LevelRequestResponse, 357 Verbs: []string{ 358 attributes.GetVerb(), 359 }, 360 } 361 362 boolPtr := func(v bool) *bool { 363 return &v 364 } 365 366 tests := []struct { 367 name string 368 policy func() audit.Policy 369 want bool 370 }{ 371 { 372 name: "global policy default is false, rule does not override", 373 policy: func() audit.Policy { 374 return audit.Policy{ 375 OmitManagedFields: false, 376 Rules: []audit.PolicyRule{ 377 *matchingPolicyRule.DeepCopy(), 378 }, 379 } 380 }, 381 }, 382 { 383 name: "global policy default is true, rule does not override", 384 policy: func() audit.Policy { 385 return audit.Policy{ 386 OmitManagedFields: true, 387 Rules: []audit.PolicyRule{ 388 *matchingPolicyRule.DeepCopy(), 389 }, 390 } 391 }, 392 want: true, 393 }, 394 { 395 name: "global policy default is true, rule overrides to false", 396 policy: func() audit.Policy { 397 rule := matchingPolicyRule.DeepCopy() 398 rule.OmitManagedFields = boolPtr(false) 399 return audit.Policy{ 400 OmitManagedFields: true, 401 Rules: []audit.PolicyRule{*rule}, 402 } 403 }, 404 want: false, 405 }, 406 { 407 name: "global policy default is false, rule overrides to true", 408 policy: func() audit.Policy { 409 rule := matchingPolicyRule.DeepCopy() 410 rule.OmitManagedFields = boolPtr(true) 411 return audit.Policy{ 412 OmitManagedFields: false, 413 Rules: []audit.PolicyRule{*rule}, 414 } 415 }, 416 want: true, 417 }, 418 } 419 420 for _, test := range tests { 421 t.Run(test.name, func(t *testing.T) { 422 evaluator := &policyRuleEvaluator{ 423 Policy: test.policy(), 424 } 425 426 got := evaluator.EvaluatePolicyRule(attributes) 427 if test.want != got.OmitManagedFields { 428 t.Errorf("Expected OmitManagedFields to match, want: %t, got: %t", test.want, got.OmitManagedFields) 429 } 430 }) 431 } 432 }