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  }