k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy_test.go (about) 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 }