...

Source file src/k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go

Documentation: k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy

     1  /*
     2  Copyright 2016 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 bootstrappolicy_test
    18  
    19  import (
    20  	"os"
    21  	"path/filepath"
    22  	"reflect"
    23  	"testing"
    24  
    25  	"github.com/google/go-cmp/cmp"
    26  	"sigs.k8s.io/yaml"
    27  
    28  	v1 "k8s.io/api/core/v1"
    29  	rbacv1 "k8s.io/api/rbac/v1"
    30  	"k8s.io/apimachinery/pkg/api/meta"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/apimachinery/pkg/util/sets"
    33  	"k8s.io/component-helpers/auth/rbac/validation"
    34  	"k8s.io/kubernetes/pkg/api/legacyscheme"
    35  	api "k8s.io/kubernetes/pkg/apis/core"
    36  	_ "k8s.io/kubernetes/pkg/apis/core/install"
    37  	_ "k8s.io/kubernetes/pkg/apis/rbac/install"
    38  	rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
    39  	"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy"
    40  )
    41  
    42  // semanticRoles is a few enumerated roles for which the relationships are well established
    43  // and we want to maintain symmetric roles
    44  type semanticRoles struct {
    45  	admin *rbacv1.ClusterRole
    46  	edit  *rbacv1.ClusterRole
    47  	view  *rbacv1.ClusterRole
    48  }
    49  
    50  func getSemanticRoles(roles []rbacv1.ClusterRole) semanticRoles {
    51  	ret := semanticRoles{}
    52  	for i := range roles {
    53  		role := roles[i]
    54  		switch role.Name {
    55  		case "system:aggregate-to-admin":
    56  			ret.admin = &role
    57  		case "system:aggregate-to-edit":
    58  			ret.edit = &role
    59  		case "system:aggregate-to-view":
    60  			ret.view = &role
    61  		}
    62  	}
    63  	return ret
    64  }
    65  
    66  // viewEscalatingNamespaceResources is the list of rules that would allow privilege escalation attacks based on
    67  // ability to view (GET) them
    68  var viewEscalatingNamespaceResources = []rbacv1.PolicyRule{
    69  	rbacv1helpers.NewRule(bootstrappolicy.Read...).Groups("").Resources("pods/attach").RuleOrDie(),
    70  	rbacv1helpers.NewRule(bootstrappolicy.Read...).Groups("").Resources("pods/proxy").RuleOrDie(),
    71  	rbacv1helpers.NewRule(bootstrappolicy.Read...).Groups("").Resources("pods/exec").RuleOrDie(),
    72  	rbacv1helpers.NewRule(bootstrappolicy.Read...).Groups("").Resources("pods/portforward").RuleOrDie(),
    73  	rbacv1helpers.NewRule(bootstrappolicy.Read...).Groups("").Resources("secrets").RuleOrDie(),
    74  	rbacv1helpers.NewRule(bootstrappolicy.Read...).Groups("").Resources("services/proxy").RuleOrDie(),
    75  }
    76  
    77  // ungettableResources is the list of rules that don't allow to view (GET) them
    78  // this is purposefully separate list to distinguish from escalating privs
    79  var ungettableResources = []rbacv1.PolicyRule{
    80  	rbacv1helpers.NewRule(bootstrappolicy.Read...).Groups("apps", "extensions").Resources("deployments/rollback").RuleOrDie(),
    81  }
    82  
    83  func TestEditViewRelationship(t *testing.T) {
    84  	readVerbs := sets.NewString(bootstrappolicy.Read...)
    85  	semanticRoles := getSemanticRoles(bootstrappolicy.ClusterRoles())
    86  
    87  	// modify the edit role rules to make then read-only for comparison against view role rules
    88  	for i := range semanticRoles.edit.Rules {
    89  		rule := semanticRoles.edit.Rules[i]
    90  		remainingVerbs := []string{}
    91  		for _, verb := range rule.Verbs {
    92  			if readVerbs.Has(verb) {
    93  				remainingVerbs = append(remainingVerbs, verb)
    94  			}
    95  		}
    96  		rule.Verbs = remainingVerbs
    97  		semanticRoles.edit.Rules[i] = rule
    98  	}
    99  
   100  	// confirm that the view role doesn't already have extra powers
   101  	for _, rule := range viewEscalatingNamespaceResources {
   102  		if covers, _ := validation.Covers(semanticRoles.view.Rules, []rbacv1.PolicyRule{rule}); covers {
   103  			t.Errorf("view has extra powers: %#v", rule)
   104  		}
   105  	}
   106  	semanticRoles.view.Rules = append(semanticRoles.view.Rules, viewEscalatingNamespaceResources...)
   107  
   108  	// confirm that the view role doesn't have ungettable resources
   109  	for _, rule := range ungettableResources {
   110  		if covers, _ := validation.Covers(semanticRoles.view.Rules, []rbacv1.PolicyRule{rule}); covers {
   111  			t.Errorf("view has ungettable resource: %#v", rule)
   112  		}
   113  	}
   114  	semanticRoles.view.Rules = append(semanticRoles.view.Rules, ungettableResources...)
   115  }
   116  
   117  func TestBootstrapNamespaceRoles(t *testing.T) {
   118  	list := &api.List{}
   119  	names := sets.NewString()
   120  	roles := map[string]runtime.Object{}
   121  
   122  	namespaceRoles := bootstrappolicy.NamespaceRoles()
   123  	for _, namespace := range sets.StringKeySet(namespaceRoles).List() {
   124  		bootstrapRoles := namespaceRoles[namespace]
   125  		for i := range bootstrapRoles {
   126  			role := bootstrapRoles[i]
   127  			names.Insert(role.Name)
   128  			roles[role.Name] = &role
   129  		}
   130  
   131  		for _, name := range names.List() {
   132  			list.Items = append(list.Items, roles[name])
   133  		}
   134  	}
   135  
   136  	testObjects(t, list, "namespace-roles.yaml")
   137  }
   138  
   139  func TestBootstrapNamespaceRoleBindings(t *testing.T) {
   140  	list := &api.List{}
   141  	names := sets.NewString()
   142  	roleBindings := map[string]runtime.Object{}
   143  
   144  	namespaceRoleBindings := bootstrappolicy.NamespaceRoleBindings()
   145  	for _, namespace := range sets.StringKeySet(namespaceRoleBindings).List() {
   146  		bootstrapRoleBindings := namespaceRoleBindings[namespace]
   147  		for i := range bootstrapRoleBindings {
   148  			roleBinding := bootstrapRoleBindings[i]
   149  			names.Insert(roleBinding.Name)
   150  			roleBindings[roleBinding.Name] = &roleBinding
   151  		}
   152  
   153  		for _, name := range names.List() {
   154  			list.Items = append(list.Items, roleBindings[name])
   155  		}
   156  	}
   157  
   158  	testObjects(t, list, "namespace-role-bindings.yaml")
   159  }
   160  
   161  func TestBootstrapClusterRoles(t *testing.T) {
   162  	list := &api.List{}
   163  	names := sets.NewString()
   164  	roles := map[string]runtime.Object{}
   165  	bootstrapRoles := bootstrappolicy.ClusterRoles()
   166  	for i := range bootstrapRoles {
   167  		role := bootstrapRoles[i]
   168  		names.Insert(role.Name)
   169  		roles[role.Name] = &role
   170  	}
   171  	for _, name := range names.List() {
   172  		list.Items = append(list.Items, roles[name])
   173  	}
   174  	testObjects(t, list, "cluster-roles.yaml")
   175  }
   176  
   177  func TestBootstrapClusterRoleBindings(t *testing.T) {
   178  	list := &api.List{}
   179  	names := sets.NewString()
   180  	roleBindings := map[string]runtime.Object{}
   181  	bootstrapRoleBindings := bootstrappolicy.ClusterRoleBindings()
   182  	for i := range bootstrapRoleBindings {
   183  		role := bootstrapRoleBindings[i]
   184  		names.Insert(role.Name)
   185  		roleBindings[role.Name] = &role
   186  	}
   187  	for _, name := range names.List() {
   188  		list.Items = append(list.Items, roleBindings[name])
   189  	}
   190  	testObjects(t, list, "cluster-role-bindings.yaml")
   191  }
   192  
   193  func TestBootstrapControllerRoles(t *testing.T) {
   194  	list := &api.List{}
   195  	names := sets.NewString()
   196  	roles := map[string]runtime.Object{}
   197  	bootstrapRoles := bootstrappolicy.ControllerRoles()
   198  	for i := range bootstrapRoles {
   199  		role := bootstrapRoles[i]
   200  		names.Insert(role.Name)
   201  		roles[role.Name] = &role
   202  	}
   203  	for _, name := range names.List() {
   204  		list.Items = append(list.Items, roles[name])
   205  	}
   206  	testObjects(t, list, "controller-roles.yaml")
   207  }
   208  
   209  func TestBootstrapControllerRoleBindings(t *testing.T) {
   210  	list := &api.List{}
   211  	names := sets.NewString()
   212  	roleBindings := map[string]runtime.Object{}
   213  	bootstrapRoleBindings := bootstrappolicy.ControllerRoleBindings()
   214  	for i := range bootstrapRoleBindings {
   215  		roleBinding := bootstrapRoleBindings[i]
   216  		names.Insert(roleBinding.Name)
   217  		roleBindings[roleBinding.Name] = &roleBinding
   218  	}
   219  	for _, name := range names.List() {
   220  		list.Items = append(list.Items, roleBindings[name])
   221  	}
   222  	testObjects(t, list, "controller-role-bindings.yaml")
   223  }
   224  
   225  func testObjects(t *testing.T, list *api.List, fixtureFilename string) {
   226  	filename := filepath.Join("testdata", fixtureFilename)
   227  	expectedYAML, err := os.ReadFile(filename)
   228  	if err != nil {
   229  		t.Fatal(err)
   230  	}
   231  
   232  	if err := runtime.EncodeList(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion, rbacv1.SchemeGroupVersion), list.Items); err != nil {
   233  		t.Fatal(err)
   234  	}
   235  
   236  	jsonData, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion, rbacv1.SchemeGroupVersion), list)
   237  	if err != nil {
   238  		t.Fatal(err)
   239  	}
   240  	yamlData, err := yaml.JSONToYAML(jsonData)
   241  	if err != nil {
   242  		t.Fatal(err)
   243  	}
   244  	if string(yamlData) != string(expectedYAML) {
   245  		t.Errorf("Bootstrap policy data does not match the test fixture in %s", filename)
   246  
   247  		const updateEnvVar = "UPDATE_BOOTSTRAP_POLICY_FIXTURE_DATA"
   248  		if os.Getenv(updateEnvVar) == "true" {
   249  			if err := os.WriteFile(filename, []byte(yamlData), os.FileMode(0755)); err == nil {
   250  				t.Logf("Updated data in %s", filename)
   251  				t.Logf("Verify the diff, commit changes, and rerun the tests")
   252  			} else {
   253  				t.Logf("Could not update data in %s: %v", filename, err)
   254  			}
   255  		} else {
   256  			t.Logf("Diff between bootstrap data and fixture data in %s:\n-------------\n%s", filename, cmp.Diff(string(yamlData), string(expectedYAML)))
   257  			t.Logf("If the change is expected, re-run with %s=true to update the fixtures", updateEnvVar)
   258  		}
   259  	}
   260  }
   261  
   262  func TestClusterRoleLabel(t *testing.T) {
   263  	roles := bootstrappolicy.ClusterRoles()
   264  	for i := range roles {
   265  		role := roles[i]
   266  		accessor, err := meta.Accessor(&role)
   267  		if err != nil {
   268  			t.Fatalf("unexpected error: %v", err)
   269  		}
   270  
   271  		if accessor.GetLabels()["kubernetes.io/bootstrapping"] != "rbac-defaults" {
   272  			t.Errorf("ClusterRole: %s GetLabels() = %s, want %s", accessor.GetName(), accessor.GetLabels(), map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"})
   273  		}
   274  	}
   275  
   276  	rolebindings := bootstrappolicy.ClusterRoleBindings()
   277  	for i := range rolebindings {
   278  		rolebinding := rolebindings[i]
   279  		accessor, err := meta.Accessor(&rolebinding)
   280  		if err != nil {
   281  			t.Fatalf("unexpected error: %v", err)
   282  		}
   283  		if got, want := accessor.GetLabels(), map[string]string{"kubernetes.io/bootstrapping": "rbac-defaults"}; !reflect.DeepEqual(got, want) {
   284  			t.Errorf("ClusterRoleBinding: %s GetLabels() = %s, want %s", accessor.GetName(), got, want)
   285  		}
   286  	}
   287  }
   288  

View as plain text