istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/authorization_test.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package model 16 17 import ( 18 "fmt" 19 "reflect" 20 "testing" 21 22 "google.golang.org/protobuf/proto" 23 24 meshconfig "istio.io/api/mesh/v1alpha1" 25 authpb "istio.io/api/security/v1beta1" 26 selectorpb "istio.io/api/type/v1beta1" 27 "istio.io/istio/pkg/config" 28 "istio.io/istio/pkg/config/constants" 29 "istio.io/istio/pkg/config/labels" 30 "istio.io/istio/pkg/config/mesh" 31 "istio.io/istio/pkg/config/schema/collection" 32 "istio.io/istio/pkg/config/schema/gvk" 33 ) 34 35 func TestAuthorizationPolicies_ListAuthorizationPolicies(t *testing.T) { 36 policy := &authpb.AuthorizationPolicy{ 37 Rules: []*authpb.Rule{ 38 { 39 From: []*authpb.Rule_From{ 40 { 41 Source: &authpb.Source{ 42 Principals: []string{"sleep"}, 43 }, 44 }, 45 }, 46 To: []*authpb.Rule_To{ 47 { 48 Operation: &authpb.Operation{ 49 Methods: []string{"GET"}, 50 }, 51 }, 52 }, 53 }, 54 }, 55 } 56 policyWithSelector := proto.Clone(policy).(*authpb.AuthorizationPolicy) 57 policyWithSelector.Selector = &selectorpb.WorkloadSelector{ 58 MatchLabels: labels.Instance{ 59 "app": "httpbin", 60 "version": "v1", 61 }, 62 } 63 policyWithTargetRef := proto.Clone(policy).(*authpb.AuthorizationPolicy) 64 policyWithTargetRef.TargetRef = &selectorpb.PolicyTargetReference{ 65 Group: gvk.KubernetesGateway.Group, 66 Kind: gvk.KubernetesGateway.Kind, 67 Name: "my-gateway", 68 Namespace: "bar", 69 } 70 71 policyWithServiceRef := proto.Clone(policy).(*authpb.AuthorizationPolicy) 72 policyWithServiceRef.TargetRef = &selectorpb.PolicyTargetReference{ 73 Group: gvk.Service.Group, 74 Kind: gvk.Service.Kind, 75 Name: "foo-svc", 76 Namespace: "foo", 77 } 78 79 denyPolicy := proto.Clone(policy).(*authpb.AuthorizationPolicy) 80 denyPolicy.Action = authpb.AuthorizationPolicy_DENY 81 82 auditPolicy := proto.Clone(policy).(*authpb.AuthorizationPolicy) 83 auditPolicy.Action = authpb.AuthorizationPolicy_AUDIT 84 85 customPolicy := proto.Clone(policy).(*authpb.AuthorizationPolicy) 86 customPolicy.Action = authpb.AuthorizationPolicy_CUSTOM 87 88 cases := []struct { 89 name string 90 selectionOpts WorkloadPolicyMatcher 91 configs []config.Config 92 wantDeny []AuthorizationPolicy 93 wantAllow []AuthorizationPolicy 94 wantAudit []AuthorizationPolicy 95 wantCustom []AuthorizationPolicy 96 }{ 97 { 98 name: "no policies", 99 selectionOpts: WorkloadPolicyMatcher{ 100 Namespace: "foo", 101 }, 102 wantAllow: nil, 103 }, 104 { 105 name: "no policies in namespace foo", 106 selectionOpts: WorkloadPolicyMatcher{ 107 Namespace: "foo", 108 }, 109 configs: []config.Config{ 110 newConfig("authz-1", "bar", policy), 111 newConfig("authz-2", "bar", policy), 112 }, 113 wantAllow: nil, 114 }, 115 { 116 name: "no policies with a targetRef in namespace foo", 117 selectionOpts: WorkloadPolicyMatcher{ 118 Namespace: "foo", 119 WorkloadLabels: labels.Instance{ 120 constants.GatewayNameLabel: "my-gateway", 121 }, 122 }, 123 configs: []config.Config{ 124 newConfig("authz-1", "bar", policyWithTargetRef), 125 }, 126 wantAllow: nil, 127 }, 128 { 129 name: "one allow policy", 130 selectionOpts: WorkloadPolicyMatcher{ 131 Namespace: "bar", 132 }, 133 configs: []config.Config{ 134 newConfig("authz-1", "bar", policy), 135 }, 136 wantAllow: []AuthorizationPolicy{ 137 { 138 Name: "authz-1", 139 Namespace: "bar", 140 Spec: policy, 141 }, 142 }, 143 }, 144 { 145 name: "one deny policy", 146 selectionOpts: WorkloadPolicyMatcher{ 147 Namespace: "bar", 148 }, 149 configs: []config.Config{ 150 newConfig("authz-1", "bar", denyPolicy), 151 }, 152 wantDeny: []AuthorizationPolicy{ 153 { 154 Name: "authz-1", 155 Namespace: "bar", 156 Spec: denyPolicy, 157 }, 158 }, 159 }, 160 { 161 name: "one audit policy", 162 selectionOpts: WorkloadPolicyMatcher{ 163 Namespace: "bar", 164 }, 165 configs: []config.Config{ 166 newConfig("authz-1", "bar", auditPolicy), 167 }, 168 wantAudit: []AuthorizationPolicy{ 169 { 170 Name: "authz-1", 171 Namespace: "bar", 172 Spec: auditPolicy, 173 }, 174 }, 175 }, 176 { 177 name: "one custom policy", 178 selectionOpts: WorkloadPolicyMatcher{ 179 Namespace: "bar", 180 }, 181 configs: []config.Config{ 182 newConfig("authz-1", "bar", customPolicy), 183 }, 184 wantCustom: []AuthorizationPolicy{ 185 { 186 Name: "authz-1", 187 Namespace: "bar", 188 Spec: customPolicy, 189 }, 190 }, 191 }, 192 { 193 name: "two policies", 194 selectionOpts: WorkloadPolicyMatcher{ 195 Namespace: "bar", 196 }, 197 configs: []config.Config{ 198 newConfig("authz-1", "foo", policy), 199 newConfig("authz-1", "bar", policy), 200 newConfig("authz-2", "bar", policy), 201 }, 202 wantAllow: []AuthorizationPolicy{ 203 { 204 Name: "authz-1", 205 Namespace: "bar", 206 Spec: policy, 207 }, 208 { 209 Name: "authz-2", 210 Namespace: "bar", 211 Spec: policy, 212 }, 213 }, 214 }, 215 { 216 name: "mixing allow, deny, and audit policies", 217 selectionOpts: WorkloadPolicyMatcher{ 218 Namespace: "bar", 219 }, 220 configs: []config.Config{ 221 newConfig("authz-1", "bar", policy), 222 newConfig("authz-2", "bar", denyPolicy), 223 newConfig("authz-3", "bar", auditPolicy), 224 newConfig("authz-4", "bar", auditPolicy), 225 }, 226 wantDeny: []AuthorizationPolicy{ 227 { 228 Name: "authz-2", 229 Namespace: "bar", 230 Spec: denyPolicy, 231 }, 232 }, 233 wantAllow: []AuthorizationPolicy{ 234 { 235 Name: "authz-1", 236 Namespace: "bar", 237 Spec: policy, 238 }, 239 }, 240 wantAudit: []AuthorizationPolicy{ 241 { 242 Name: "authz-3", 243 Namespace: "bar", 244 Spec: auditPolicy, 245 }, 246 { 247 Name: "authz-4", 248 Namespace: "bar", 249 Spec: auditPolicy, 250 }, 251 }, 252 }, 253 { 254 name: "targetRef is an exact match", 255 selectionOpts: WorkloadPolicyMatcher{ 256 Namespace: "bar", 257 WorkloadLabels: labels.Instance{ 258 constants.GatewayNameLabel: "my-gateway", 259 }, 260 }, 261 configs: []config.Config{ 262 newConfig("authz-1", "bar", policyWithTargetRef), 263 }, 264 wantAllow: []AuthorizationPolicy{ 265 { 266 Name: "authz-1", 267 Namespace: "bar", 268 Spec: policyWithTargetRef, 269 }, 270 }, 271 }, 272 { 273 name: "selector exact match", 274 selectionOpts: WorkloadPolicyMatcher{ 275 Namespace: "bar", 276 WorkloadLabels: labels.Instance{ 277 "app": "httpbin", 278 "version": "v1", 279 }, 280 }, 281 configs: []config.Config{ 282 newConfig("authz-1", "bar", policyWithSelector), 283 }, 284 wantAllow: []AuthorizationPolicy{ 285 { 286 Name: "authz-1", 287 Namespace: "bar", 288 Spec: policyWithSelector, 289 }, 290 }, 291 }, 292 { 293 name: "selector subset match", 294 selectionOpts: WorkloadPolicyMatcher{ 295 Namespace: "bar", 296 WorkloadLabels: labels.Instance{ 297 "app": "httpbin", 298 "version": "v1", 299 "env": "dev", 300 }, 301 }, 302 configs: []config.Config{ 303 newConfig("authz-1", "bar", policyWithSelector), 304 }, 305 wantAllow: []AuthorizationPolicy{ 306 { 307 Name: "authz-1", 308 Namespace: "bar", 309 Spec: policyWithSelector, 310 }, 311 }, 312 }, 313 { 314 name: "targetRef is not a match", 315 selectionOpts: WorkloadPolicyMatcher{ 316 Namespace: "bar", 317 WorkloadLabels: labels.Instance{ 318 constants.GatewayNameLabel: "my-gateway2", 319 }, 320 }, 321 configs: []config.Config{ 322 newConfig("authz-1", "bar", policyWithTargetRef), 323 }, 324 wantAllow: nil, 325 }, 326 { 327 name: "selector not match", 328 selectionOpts: WorkloadPolicyMatcher{ 329 Namespace: "bar", 330 WorkloadLabels: labels.Instance{ 331 "app": "httpbin", 332 "version": "v2", 333 }, 334 }, 335 configs: []config.Config{ 336 newConfig("authz-1", "bar", policyWithSelector), 337 }, 338 wantAllow: nil, 339 }, 340 { 341 name: "namespace not match", 342 selectionOpts: WorkloadPolicyMatcher{ 343 Namespace: "foo", 344 WorkloadLabels: labels.Instance{ 345 "app": "httpbin", 346 "version": "v1", 347 }, 348 }, 349 configs: []config.Config{ 350 newConfig("authz-1", "bar", policyWithSelector), 351 }, 352 wantAllow: nil, 353 }, 354 { 355 name: "root namespace", 356 selectionOpts: WorkloadPolicyMatcher{ 357 Namespace: "bar", 358 }, 359 configs: []config.Config{ 360 newConfig("authz-1", "istio-config", policy), 361 }, 362 wantAllow: []AuthorizationPolicy{ 363 { 364 Name: "authz-1", 365 Namespace: "istio-config", 366 Spec: policy, 367 }, 368 }, 369 }, 370 { 371 name: "root namespace equals config namespace", 372 selectionOpts: WorkloadPolicyMatcher{ 373 Namespace: "istio-config", 374 }, 375 configs: []config.Config{ 376 newConfig("authz-1", "istio-config", policy), 377 }, 378 wantAllow: []AuthorizationPolicy{ 379 { 380 Name: "authz-1", 381 Namespace: "istio-config", 382 Spec: policy, 383 }, 384 }, 385 }, 386 { 387 name: "root namespace and config namespace", 388 selectionOpts: WorkloadPolicyMatcher{ 389 Namespace: "bar", 390 }, 391 configs: []config.Config{ 392 newConfig("authz-1", "istio-config", policy), 393 newConfig("authz-2", "bar", policy), 394 }, 395 wantAllow: []AuthorizationPolicy{ 396 { 397 Name: "authz-1", 398 Namespace: "istio-config", 399 Spec: policy, 400 }, 401 { 402 Name: "authz-2", 403 Namespace: "bar", 404 Spec: policy, 405 }, 406 }, 407 }, 408 { 409 name: "waypoint service attached", 410 selectionOpts: WorkloadPolicyMatcher{ 411 IsWaypoint: true, 412 Service: "foo-svc", 413 Namespace: "foo", 414 WorkloadLabels: labels.Instance{ 415 constants.GatewayNameLabel: "foo-waypoint", 416 // labels match in selector policy but ignore them for waypoint 417 "app": "httpbin", 418 "version": "v1", 419 }, 420 }, 421 configs: []config.Config{ 422 newConfig("authz-1", "foo", policyWithServiceRef), 423 newConfig("authz-2", "foo", policyWithSelector), 424 }, 425 wantAllow: []AuthorizationPolicy{ 426 {Name: "authz-1", Namespace: "foo", Spec: policyWithServiceRef}, 427 }, 428 }, 429 } 430 431 for _, tc := range cases { 432 t.Run(tc.name, func(t *testing.T) { 433 authzPolicies := createFakeAuthorizationPolicies(tc.configs) 434 435 result := authzPolicies.ListAuthorizationPolicies(tc.selectionOpts) 436 if !reflect.DeepEqual(tc.wantAllow, result.Allow) { 437 t.Errorf("wantAllow:%v\n but got: %v\n", tc.wantAllow, result.Allow) 438 } 439 if !reflect.DeepEqual(tc.wantDeny, result.Deny) { 440 t.Errorf("wantDeny:%v\n but got: %v\n", tc.wantDeny, result.Deny) 441 } 442 if !reflect.DeepEqual(tc.wantAudit, result.Audit) { 443 t.Errorf("wantAudit:%v\n but got: %v\n", tc.wantAudit, result.Audit) 444 } 445 if !reflect.DeepEqual(tc.wantCustom, result.Custom) { 446 t.Errorf("wantCustom:%v\n but got: %v\n", tc.wantCustom, result.Custom) 447 } 448 }) 449 } 450 } 451 452 func createFakeAuthorizationPolicies(configs []config.Config) *AuthorizationPolicies { 453 store := &authzFakeStore{} 454 for _, cfg := range configs { 455 store.add(cfg) 456 } 457 environment := &Environment{ 458 ConfigStore: store, 459 Watcher: mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "istio-config"}), 460 } 461 authzPolicies := GetAuthorizationPolicies(environment) 462 return authzPolicies 463 } 464 465 func newConfig(name, ns string, spec config.Spec) config.Config { 466 return config.Config{ 467 Meta: config.Meta{ 468 GroupVersionKind: gvk.AuthorizationPolicy, 469 Name: name, 470 Namespace: ns, 471 }, 472 Spec: spec, 473 } 474 } 475 476 type authzFakeStore struct { 477 data []struct { 478 typ config.GroupVersionKind 479 ns string 480 cfg config.Config 481 } 482 } 483 484 func (fs *authzFakeStore) add(cfg config.Config) { 485 fs.data = append(fs.data, struct { 486 typ config.GroupVersionKind 487 ns string 488 cfg config.Config 489 }{ 490 typ: cfg.GroupVersionKind, 491 ns: cfg.Namespace, 492 cfg: cfg, 493 }) 494 } 495 496 func (fs *authzFakeStore) Schemas() collection.Schemas { 497 return collection.SchemasFor() 498 } 499 500 func (fs *authzFakeStore) Get(_ config.GroupVersionKind, _, _ string) *config.Config { 501 return nil 502 } 503 504 func (fs *authzFakeStore) List(typ config.GroupVersionKind, namespace string) []config.Config { 505 var configs []config.Config 506 for _, data := range fs.data { 507 if data.typ == typ { 508 if namespace != "" && data.ns == namespace { 509 continue 510 } 511 configs = append(configs, data.cfg) 512 } 513 } 514 return configs 515 } 516 517 func (fs *authzFakeStore) Delete(_ config.GroupVersionKind, _, _ string, _ *string) error { 518 return fmt.Errorf("not implemented") 519 } 520 521 func (fs *authzFakeStore) Create(config.Config) (string, error) { 522 return "not implemented", nil 523 } 524 525 func (fs *authzFakeStore) Update(config.Config) (string, error) { 526 return "not implemented", nil 527 } 528 529 func (fs *authzFakeStore) UpdateStatus(config.Config) (string, error) { 530 return "not implemented", nil 531 } 532 533 func (fs *authzFakeStore) Patch(orig config.Config, patchFn config.PatchFunc) (string, error) { 534 return "not implemented", nil 535 }