k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cluster/gce/gci/audit_policy_test.go (about)

     1  /*
     2  Copyright 2019 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 gci
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  	"testing"
    24  
    25  	"k8s.io/apiserver/pkg/apis/audit"
    26  	auditinstall "k8s.io/apiserver/pkg/apis/audit/install"
    27  	auditpkg "k8s.io/apiserver/pkg/audit"
    28  	auditpolicy "k8s.io/apiserver/pkg/audit/policy"
    29  	"k8s.io/apiserver/pkg/authentication/serviceaccount"
    30  	"k8s.io/apiserver/pkg/authentication/user"
    31  	"k8s.io/apiserver/pkg/authorization/authorizer"
    32  
    33  	"github.com/stretchr/testify/assert"
    34  	"github.com/stretchr/testify/require"
    35  )
    36  
    37  func init() {
    38  	// Register audit scheme to parse audit config.
    39  	auditinstall.Install(auditpkg.Scheme)
    40  }
    41  
    42  func TestCreateMasterAuditPolicy(t *testing.T) {
    43  	baseDir, err := os.MkdirTemp("", "configure-helper-test") // cleaned up by c.tearDown()
    44  	require.NoError(t, err, "Failed to create temp directory")
    45  
    46  	policyFile := filepath.Join(baseDir, "audit_policy.yaml")
    47  	c := ManifestTestCase{
    48  		t:                t,
    49  		kubeHome:         baseDir,
    50  		manifestFuncName: fmt.Sprintf("create-master-audit-policy %s", policyFile),
    51  	}
    52  	defer c.tearDown()
    53  
    54  	// Initialize required environment variables.
    55  	c.mustInvokeFunc(
    56  		kubeAPIServerEnv{KubeHome: c.kubeHome},
    57  		[]string{"configure-helper.sh"},
    58  		"base.template",
    59  		"testdata/kube-apiserver/base.template",
    60  	)
    61  
    62  	policy, err := auditpolicy.LoadPolicyFromFile(policyFile)
    63  	require.NoError(t, err, "Failed to load generated policy.")
    64  
    65  	// Users for test cases
    66  	var (
    67  		anonymous           = newUserInfo(user.Anonymous, user.AllUnauthenticated)
    68  		kubeproxy           = newUserInfo(user.KubeProxy, user.AllAuthenticated)
    69  		ingress             = newUserInfo("system:unsecured", user.AllAuthenticated, user.SystemPrivilegedGroup)
    70  		kubelet             = newUserInfo("kubelet", user.AllAuthenticated, user.NodesGroup)
    71  		node                = newUserInfo("system:node:node-123", user.AllAuthenticated, user.NodesGroup)
    72  		controller          = newUserInfo(user.KubeControllerManager, user.AllAuthenticated)
    73  		scheduler           = newUserInfo(user.KubeScheduler, user.AllAuthenticated)
    74  		apiserver           = newUserInfo(user.APIServerUser, user.SystemPrivilegedGroup)
    75  		autoscaler          = newUserInfo("cluster-autoscaler", user.AllAuthenticated)
    76  		npd                 = newUserInfo("system:node-problem-detector", user.AllAuthenticated)
    77  		npdSA               = serviceaccount.UserInfo("kube-system", "node-problem-detector", "")
    78  		namespaceController = serviceaccount.UserInfo("kube-system", "namespace-controller", "")
    79  		endpointController  = serviceaccount.UserInfo("kube-system", "endpoint-controller", "")
    80  		defaultSA           = serviceaccount.UserInfo("default", "default", "")
    81  
    82  		allUsers = []user.Info{anonymous, kubeproxy, ingress, kubelet, node, controller, scheduler, apiserver, autoscaler, npd, npdSA, namespaceController, endpointController, defaultSA}
    83  	)
    84  
    85  	// Resources for test cases
    86  	var (
    87  		nodes           = resource("nodes")
    88  		nodeStatus      = resource("nodes", "", "", "status")
    89  		endpoints       = resource("endpoints", "default")
    90  		sysEndpoints    = resource("endpoints", "kube-system")
    91  		services        = resource("services", "default")
    92  		serviceStatus   = resource("services", "default", "", "status")
    93  		configmaps      = resource("configmaps", "default")
    94  		sysConfigmaps   = resource("configmaps", "kube-system")
    95  		namespaces      = resource("namespaces")
    96  		namespaceStatus = resource("namespaces", "", "", "status")
    97  		namespaceFinal  = resource("namespaces", "", "", "finalize")
    98  		podMetrics      = resource("podmetrics", "default", "metrics.k8s.io")
    99  		nodeMetrics     = resource("nodemetrics", "", "metrics.k8s.io")
   100  		pods            = resource("pods", "default")
   101  		podStatus       = resource("pods", "default", "", "status")
   102  		secrets         = resource("secrets", "default")
   103  		tokenReviews    = resource("tokenreviews", "", "authentication.k8s.io")
   104  		deployments     = resource("deployments", "default", "apps")
   105  		clusterRoles    = resource("clusterroles", "", "rbac.authorization.k8s.io")
   106  		events          = resource("events", "default")
   107  		foobars         = resource("foos", "default", "example.com")
   108  		foobarbaz       = resource("foos", "default", "example.com", "baz")
   109  	)
   110  
   111  	// Aliases
   112  	const (
   113  		none     = audit.LevelNone
   114  		metadata = audit.LevelMetadata
   115  		request  = audit.LevelRequest
   116  		response = audit.LevelRequestResponse
   117  	)
   118  
   119  	at := auditTester{
   120  		T:         t,
   121  		evaluator: auditpolicy.NewPolicyRuleEvaluator(policy),
   122  	}
   123  
   124  	at.testResources(none, kubeproxy, "watch", endpoints, sysEndpoints, services, serviceStatus)
   125  	at.testResources(request, kubeproxy, "watch", nodes, pods)
   126  
   127  	at.testResources(none, ingress, "get", sysConfigmaps)
   128  	at.testResources(metadata, ingress, "get", configmaps)
   129  
   130  	at.testResources(none, kubelet, node, "get", nodes, nodeStatus)
   131  	at.testResources(metadata, kubelet, node, "get", sysConfigmaps, secrets)
   132  	at.testResources(response, kubelet, node, "create", deployments, pods)
   133  
   134  	at.testResources(none, controller, scheduler, endpointController, "get", "update", sysEndpoints)
   135  	at.testResources(request, controller, scheduler, endpointController, "get", endpoints)
   136  	at.testResources(response, controller, scheduler, endpointController, "update", endpoints)
   137  
   138  	at.testResources(none, apiserver, "get", namespaces, namespaceStatus, namespaceFinal)
   139  	at.testResources(metadata, apiserver, "get", "create", "update", sysConfigmaps, secrets)
   140  
   141  	at.testResources(none, autoscaler, "get", "update", sysConfigmaps, sysEndpoints)
   142  	at.testResources(metadata, autoscaler, "get", "update", configmaps)
   143  	at.testResources(response, autoscaler, "update", endpoints)
   144  
   145  	at.testResources(none, controller, "get", "list", podMetrics, nodeMetrics)
   146  
   147  	at.testNonResources(none, allUsers, "/healthz", "/healthz/etcd", "/swagger-2.0.0.json", "/swagger-2.0.0.pb-v1.gz", "/version")
   148  	at.testNonResources(metadata, allUsers, "/logs", "/openapi/v2", "/apis/policy", "/metrics", "/api")
   149  
   150  	at.testResources(none, node, apiserver, defaultSA, anonymous, "get", "list", "create", "patch", "update", "delete", events)
   151  
   152  	at.testResources(request, kubelet, node, npd, npdSA, "update", "patch", nodeStatus, podStatus)
   153  
   154  	at.testResources(request, namespaceController, "deletecollection", pods, namespaces)
   155  
   156  	at.testResources(metadata, defaultSA, anonymous, npd, namespaceController, "get", "create", "update", secrets, configmaps, sysConfigmaps, tokenReviews)
   157  	at.testResources(request, defaultSA, anonymous, npd, namespaceController, "get", "list", "watch", sysEndpoints, podMetrics, pods, clusterRoles, deployments)
   158  	at.testResources(response, defaultSA, anonymous, npd, namespaceController, "create", "update", "patch", "delete", sysEndpoints, podMetrics, pods, clusterRoles, deployments)
   159  
   160  	at.testResources(metadata, defaultSA, anonymous, npd, namespaceController, "get", "list", "watch", "create", "update", "patch", "delete", foobars, foobarbaz)
   161  }
   162  
   163  type auditTester struct {
   164  	*testing.T
   165  	evaluator auditpkg.PolicyRuleEvaluator
   166  }
   167  
   168  func (t *auditTester) testResources(level audit.Level, usrVerbRes ...interface{}) {
   169  	verbs := []string{}
   170  	users := []user.Info{}
   171  	resources := []Resource{}
   172  	for _, arg := range usrVerbRes {
   173  		switch v := arg.(type) {
   174  		case string:
   175  			verbs = append(verbs, v)
   176  		case user.Info:
   177  			users = append(users, v)
   178  		case Resource:
   179  			resources = append(resources, v)
   180  		default:
   181  			t.Fatalf("Invalid test argument: %+v", arg)
   182  		}
   183  	}
   184  	require.NotEmpty(t, verbs, "testcases must have a verb")
   185  	require.NotEmpty(t, users, "testcases must have a user")
   186  	require.NotEmpty(t, resources, "resource testcases must have a resource")
   187  
   188  	for _, usr := range users {
   189  		for _, verb := range verbs {
   190  			for _, res := range resources {
   191  				attrs := &authorizer.AttributesRecord{
   192  					User:            usr,
   193  					Verb:            verb,
   194  					Namespace:       res.Namespace,
   195  					APIGroup:        res.Group,
   196  					APIVersion:      "v1",
   197  					Resource:        res.Resource,
   198  					Subresource:     res.Subresource,
   199  					ResourceRequest: true,
   200  				}
   201  				t.expectLevel(level, attrs)
   202  			}
   203  		}
   204  	}
   205  }
   206  
   207  func (t *auditTester) testNonResources(level audit.Level, users []user.Info, paths ...string) {
   208  	for _, usr := range users {
   209  		for _, verb := range []string{"get", "post"} {
   210  			for _, path := range paths {
   211  				attrs := &authorizer.AttributesRecord{
   212  					User:            usr,
   213  					Verb:            verb,
   214  					ResourceRequest: false,
   215  					Path:            path,
   216  				}
   217  				t.expectLevel(level, attrs)
   218  			}
   219  		}
   220  	}
   221  }
   222  
   223  func (t *auditTester) expectLevel(expected audit.Level, attrs authorizer.Attributes) {
   224  	obj := attrs.GetPath()
   225  	if attrs.IsResourceRequest() {
   226  		obj = attrs.GetResource()
   227  		if attrs.GetNamespace() != "" {
   228  			obj = obj + ":" + attrs.GetNamespace()
   229  		}
   230  	}
   231  	name := fmt.Sprintf("%s.%s.%s", attrs.GetUser().GetName(), attrs.GetVerb(), obj)
   232  	evaluator := t.evaluator
   233  	t.Run(name, func(t *testing.T) {
   234  		auditConfig := evaluator.EvaluatePolicyRule(attrs)
   235  		assert.Equal(t, expected, auditConfig.Level)
   236  		if auditConfig.Level != audit.LevelNone {
   237  			assert.ElementsMatch(t, auditConfig.OmitStages, []audit.Stage{audit.StageRequestReceived})
   238  		}
   239  	})
   240  }
   241  
   242  func newUserInfo(name string, groups ...string) user.Info {
   243  	return &user.DefaultInfo{
   244  		Name:   name,
   245  		Groups: groups,
   246  	}
   247  }
   248  
   249  type Resource struct {
   250  	Group, Resource, Subresource, Namespace string
   251  }
   252  
   253  func resource(kind string, nsGroupSub ...string) Resource {
   254  	res := Resource{Resource: kind}
   255  	if len(nsGroupSub) > 0 {
   256  		res.Namespace = nsGroupSub[0]
   257  	}
   258  	if len(nsGroupSub) > 1 {
   259  		res.Group = nsGroupSub[1]
   260  	}
   261  	if len(nsGroupSub) > 2 {
   262  		res.Subresource = nsGroupSub[2]
   263  	}
   264  	return res
   265  }