istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/security/authz/model/generator_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 "testing" 19 20 rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3" 21 "github.com/google/go-cmp/cmp" 22 "google.golang.org/protobuf/proto" 23 "google.golang.org/protobuf/testing/protocmp" 24 25 "istio.io/istio/pkg/util/protomarshal" 26 ) 27 28 func TestRequestPrincipal(t *testing.T) { 29 cases := []struct { 30 in string 31 want string 32 }{ 33 { 34 in: "*", 35 want: ` 36 and_ids: 37 ids: 38 - metadata: 39 filter: envoy.filters.http.jwt_authn 40 path: 41 - key: payload 42 - key: iss 43 value: 44 string_match: 45 safe_regex: {regex: .+} 46 - metadata: 47 filter: envoy.filters.http.jwt_authn 48 path: 49 - key: payload 50 - key: sub 51 value: 52 string_match: 53 safe_regex: {regex: .+} 54 `, 55 }, 56 { 57 in: "foo*", 58 want: ` 59 and_ids: 60 ids: 61 - metadata: 62 filter: envoy.filters.http.jwt_authn 63 path: 64 - key: payload 65 - key: iss 66 value: 67 string_match: 68 prefix: foo 69 - metadata: 70 filter: envoy.filters.http.jwt_authn 71 path: 72 - key: payload 73 - key: sub 74 value: 75 string_match: 76 safe_regex: {regex: .+} 77 `, 78 }, 79 { 80 in: "foo/*", 81 want: ` 82 and_ids: 83 ids: 84 - metadata: 85 filter: envoy.filters.http.jwt_authn 86 path: 87 - key: payload 88 - key: iss 89 value: 90 string_match: 91 exact: foo 92 - metadata: 93 filter: envoy.filters.http.jwt_authn 94 path: 95 - key: payload 96 - key: sub 97 value: 98 string_match: 99 safe_regex: {regex: .+} 100 `, 101 }, 102 { 103 in: "foo/bar*", 104 want: ` 105 and_ids: 106 ids: 107 - metadata: 108 filter: envoy.filters.http.jwt_authn 109 path: 110 - key: payload 111 - key: iss 112 value: 113 string_match: 114 exact: foo 115 - metadata: 116 filter: envoy.filters.http.jwt_authn 117 path: 118 - key: payload 119 - key: sub 120 value: 121 string_match: 122 prefix: bar 123 `, 124 }, 125 { 126 in: "*foo", 127 want: ` 128 and_ids: 129 ids: 130 - metadata: 131 filter: envoy.filters.http.jwt_authn 132 path: 133 - key: payload 134 - key: iss 135 value: 136 string_match: 137 safe_regex: {regex: .+} 138 - metadata: 139 filter: envoy.filters.http.jwt_authn 140 path: 141 - key: payload 142 - key: sub 143 value: 144 string_match: 145 suffix: foo 146 `, 147 }, 148 { 149 in: "*/foo", 150 want: ` 151 and_ids: 152 ids: 153 - metadata: 154 filter: envoy.filters.http.jwt_authn 155 path: 156 - key: payload 157 - key: iss 158 value: 159 string_match: 160 safe_regex: {regex: .+} 161 - metadata: 162 filter: envoy.filters.http.jwt_authn 163 path: 164 - key: payload 165 - key: sub 166 value: 167 string_match: 168 exact: foo 169 `, 170 }, 171 { 172 in: "*bar/foo", 173 want: ` 174 and_ids: 175 ids: 176 - metadata: 177 filter: envoy.filters.http.jwt_authn 178 path: 179 - key: payload 180 - key: iss 181 value: 182 string_match: 183 suffix: bar 184 - metadata: 185 filter: envoy.filters.http.jwt_authn 186 path: 187 - key: payload 188 - key: sub 189 value: 190 string_match: 191 exact: foo 192 `, 193 }, 194 { 195 in: "foo/bar", 196 want: ` 197 and_ids: 198 ids: 199 - metadata: 200 filter: envoy.filters.http.jwt_authn 201 path: 202 - key: payload 203 - key: iss 204 value: 205 string_match: 206 exact: foo 207 - metadata: 208 filter: envoy.filters.http.jwt_authn 209 path: 210 - key: payload 211 - key: sub 212 value: 213 string_match: 214 exact: bar 215 `, 216 }, 217 } 218 rpg := requestPrincipalGenerator{} 219 for _, tc := range cases { 220 t.Run(tc.in, func(t *testing.T) { 221 got, err := rpg.extendedPrincipal("", []string{tc.in}, false) 222 if err != nil { 223 t.Fatal(err) 224 } 225 principal := yamlPrincipal(t, tc.want) 226 if diff := cmp.Diff(got, principal, protocmp.Transform()); diff != "" { 227 t.Errorf("diff detected: %v", diff) 228 } 229 }) 230 } 231 } 232 233 func TestGenerator(t *testing.T) { 234 cases := []struct { 235 name string 236 g generator 237 key string 238 value string 239 forTCP bool 240 want any 241 }{ 242 { 243 name: "destIPGenerator", 244 g: destIPGenerator{}, 245 value: "1.2.3.4", 246 want: yamlPermission(t, ` 247 destinationIp: 248 addressPrefix: 1.2.3.4 249 prefixLen: 32`), 250 }, 251 { 252 name: "destPortGenerator", 253 g: destPortGenerator{}, 254 value: "80", 255 want: yamlPermission(t, ` 256 destinationPort: 80`), 257 }, 258 { 259 name: "connSNIGenerator", 260 g: connSNIGenerator{}, 261 value: "exact.com", 262 want: yamlPermission(t, ` 263 requestedServerName: 264 exact: exact.com`), 265 }, 266 { 267 name: "envoyFilterGenerator-string", 268 g: envoyFilterGenerator{}, 269 key: "experimental.a.b.c[d]", 270 value: "val", 271 want: yamlPermission(t, ` 272 metadata: 273 filter: a.b.c 274 path: 275 - key: d 276 value: 277 stringMatch: 278 exact: val`), 279 }, 280 { 281 name: "envoyFilterGenerator-invalid", 282 g: envoyFilterGenerator{}, 283 key: "experimental.a.b.c]", 284 value: "val", 285 }, 286 { 287 name: "envoyFilterGenerator-list", 288 g: envoyFilterGenerator{}, 289 key: "experimental.a.b.c[d]", 290 value: "[v1, v2]", 291 want: yamlPermission(t, ` 292 metadata: 293 filter: a.b.c 294 path: 295 - key: d 296 value: 297 listMatch: 298 oneOf: 299 stringMatch: 300 exact: v1, v2`), 301 }, 302 { 303 name: "srcIPGenerator", 304 g: srcIPGenerator{}, 305 value: "1.2.3.4", 306 want: yamlPrincipal(t, ` 307 directRemoteIp: 308 addressPrefix: 1.2.3.4 309 prefixLen: 32`), 310 }, 311 { 312 name: "remoteIPGenerator", 313 g: remoteIPGenerator{}, 314 value: "1.2.3.4", 315 want: yamlPrincipal(t, ` 316 remoteIp: 317 addressPrefix: 1.2.3.4 318 prefixLen: 32`), 319 }, 320 { 321 name: "srcNamespaceGenerator-http", 322 g: srcNamespaceGenerator{}, 323 value: "foo", 324 want: yamlPrincipal(t, ` 325 filter_state: 326 key: io.istio.peer_principal 327 string_match: 328 safeRegex: 329 regex: .*/ns/foo/.*`), 330 }, 331 { 332 name: "srcNamespaceGenerator-tcp", 333 g: srcNamespaceGenerator{}, 334 value: "foo", 335 forTCP: true, 336 want: yamlPrincipal(t, ` 337 filter_state: 338 key: io.istio.peer_principal 339 string_match: 340 safeRegex: 341 regex: .*/ns/foo/.*`), 342 }, 343 { 344 name: "srcPrincipalGenerator-http", 345 g: srcPrincipalGenerator{}, 346 key: "source.principal", 347 value: "foo", 348 want: yamlPrincipal(t, ` 349 filter_state: 350 key: io.istio.peer_principal 351 string_match: 352 exact: spiffe://foo`), 353 }, 354 { 355 name: "srcPrincipalGenerator-tcp", 356 g: srcPrincipalGenerator{}, 357 key: "source.principal", 358 value: "foo", 359 forTCP: true, 360 want: yamlPrincipal(t, ` 361 filter_state: 362 key: io.istio.peer_principal 363 string_match: 364 exact: spiffe://foo`), 365 }, 366 { 367 name: "requestPrincipalGenerator", 368 g: requestPrincipalGenerator{}, 369 key: "request.auth.principal", 370 value: "foo", 371 want: yamlPrincipal(t, ` 372 metadata: 373 filter: istio_authn 374 path: 375 - key: request.auth.principal 376 value: 377 stringMatch: 378 exact: foo`), 379 }, 380 { 381 name: "requestAudiencesGenerator", 382 g: requestAudiencesGenerator{}, 383 key: "request.auth.audiences", 384 value: "foo", 385 want: yamlPrincipal(t, ` 386 metadata: 387 filter: istio_authn 388 path: 389 - key: request.auth.audiences 390 value: 391 stringMatch: 392 exact: foo`), 393 }, 394 { 395 name: "requestPresenterGenerator", 396 g: requestPresenterGenerator{}, 397 key: "request.auth.presenter", 398 value: "foo", 399 want: yamlPrincipal(t, ` 400 metadata: 401 filter: istio_authn 402 path: 403 - key: request.auth.presenter 404 value: 405 stringMatch: 406 exact: foo`), 407 }, 408 { 409 name: "requestHeaderGenerator", 410 g: requestHeaderGenerator{}, 411 key: "request.headers[x-foo]", 412 value: "foo", 413 want: yamlPrincipal(t, ` 414 header: 415 name: x-foo 416 stringMatch: 417 exact: foo`), 418 }, 419 { 420 name: "requestClaimGenerator", 421 g: requestClaimGenerator{}, 422 key: "request.auth.claims[bar]", 423 value: "foo", 424 want: yamlPrincipal(t, ` 425 metadata: 426 filter: istio_authn 427 path: 428 - key: request.auth.claims 429 - key: bar 430 value: 431 listMatch: 432 oneOf: 433 stringMatch: 434 exact: foo`), 435 }, 436 { 437 name: "requestNestedClaimsGenerator", 438 g: requestClaimGenerator{}, 439 key: "request.auth.claims[bar][baz]", 440 value: "foo", 441 want: yamlPrincipal(t, ` 442 metadata: 443 filter: istio_authn 444 path: 445 - key: request.auth.claims 446 - key: bar 447 - key: baz 448 value: 449 listMatch: 450 oneOf: 451 stringMatch: 452 exact: foo`), 453 }, 454 { 455 name: "hostGenerator", 456 g: hostGenerator{}, 457 value: "foo", 458 want: yamlPermission(t, ` 459 header: 460 stringMatch: 461 exact: foo 462 ignoreCase: true 463 name: :authority`), 464 }, 465 { 466 name: "pathGenerator", 467 g: pathGenerator{}, 468 value: "/abc", 469 want: yamlPermission(t, ` 470 urlPath: 471 path: 472 exact: /abc`), 473 }, 474 { 475 name: "pathGenerator-template", 476 g: pathGenerator{}, 477 value: "/abc/{*}", 478 want: yamlPermission(t, ` 479 uriTemplate: 480 name: uri-template 481 typedConfig: 482 '@type': type.googleapis.com/envoy.extensions.path.match.uri_template.v3.UriTemplateMatchConfig 483 pathTemplate: /abc/*`), 484 }, 485 { 486 name: "methodGenerator", 487 g: methodGenerator{}, 488 value: "GET", 489 want: yamlPermission(t, ` 490 header: 491 name: :method 492 stringMatch: 493 exact: GET`), 494 }, 495 } 496 497 for _, tc := range cases { 498 t.Run(tc.name, func(t *testing.T) { 499 var got any 500 var err error 501 // nolint: gocritic 502 if _, ok := tc.want.(*rbacpb.Permission); ok { 503 got, err = tc.g.permission(tc.key, tc.value, tc.forTCP) 504 if err != nil { 505 t.Errorf("both permission and principal returned error") 506 } 507 } else if _, ok := tc.want.(*rbacpb.Principal); ok { 508 got, err = tc.g.principal(tc.key, tc.value, tc.forTCP, false) 509 if err != nil { 510 t.Errorf("both permission and principal returned error") 511 } 512 } else { 513 _, err1 := tc.g.principal(tc.key, tc.value, tc.forTCP, false) 514 _, err2 := tc.g.permission(tc.key, tc.value, tc.forTCP) 515 if err1 == nil || err2 == nil { 516 t.Fatalf("wanted error") 517 } 518 return 519 } 520 if diff := cmp.Diff(got, tc.want, protocmp.Transform()); diff != "" { 521 var gotYaml string 522 gotProto, ok := got.(proto.Message) 523 if !ok { 524 t.Fatal("failed to extract proto") 525 } 526 if gotYaml, err = protomarshal.ToYAML(gotProto); err != nil { 527 t.Fatalf("%s: failed to parse yaml: %s", tc.name, err) 528 } 529 t.Errorf("got:\n %v\n but want:\n %v", gotYaml, tc.want) 530 } 531 }) 532 } 533 } 534 535 func yamlPermission(t *testing.T, yaml string) *rbacpb.Permission { 536 t.Helper() 537 p := &rbacpb.Permission{} 538 if err := protomarshal.ApplyYAML(yaml, p); err != nil { 539 t.Fatalf("failed to parse yaml: %s", err) 540 } 541 return p 542 } 543 544 func yamlPrincipal(t *testing.T, yaml string) *rbacpb.Principal { 545 t.Helper() 546 p := &rbacpb.Principal{} 547 if err := protomarshal.ApplyYAML(yaml, p); err != nil { 548 t.Fatalf("failed to parse yaml: %s", err) 549 } 550 return p 551 }