google.golang.org/grpc@v1.62.1/internal/xds/rbac/rbac_engine_test.go (about) 1 /* 2 * Copyright 2021 gRPC 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 rbac 18 19 import ( 20 "context" 21 "crypto/tls" 22 "crypto/x509" 23 "crypto/x509/pkix" 24 "encoding/json" 25 "fmt" 26 "net" 27 "net/url" 28 "reflect" 29 "testing" 30 31 v1xdsudpatypepb "github.com/cncf/xds/go/udpa/type/v1" 32 v3xdsxdstypepb "github.com/cncf/xds/go/xds/type/v3" 33 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 34 v3rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3" 35 v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" 36 v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" 37 v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3" 38 "google.golang.org/grpc" 39 "google.golang.org/grpc/authz/audit" 40 "google.golang.org/grpc/codes" 41 "google.golang.org/grpc/credentials" 42 "google.golang.org/grpc/internal/grpctest" 43 "google.golang.org/grpc/metadata" 44 "google.golang.org/grpc/peer" 45 "google.golang.org/grpc/status" 46 "google.golang.org/protobuf/types/known/anypb" 47 "google.golang.org/protobuf/types/known/structpb" 48 "google.golang.org/protobuf/types/known/wrapperspb" 49 ) 50 51 type s struct { 52 grpctest.Tester 53 } 54 55 func Test(t *testing.T) { 56 grpctest.RunSubTests(t, s{}) 57 } 58 59 type addr struct { 60 ipAddress string 61 } 62 63 func (addr) Network() string { return "" } 64 func (a *addr) String() string { return a.ipAddress } 65 66 // TestNewChainEngine tests the construction of the ChainEngine. Due to some 67 // types of RBAC configuration being logically wrong and returning an error 68 // rather than successfully constructing the RBAC Engine, this test tests both 69 // RBAC Configurations deemed successful and also RBAC Configurations that will 70 // raise errors. 71 func (s) TestNewChainEngine(t *testing.T) { 72 tests := []struct { 73 name string 74 policies []*v3rbacpb.RBAC 75 wantErr bool 76 policyName string 77 }{ 78 { 79 name: "SuccessCaseAnyMatchSingular", 80 policies: []*v3rbacpb.RBAC{ 81 { 82 Action: v3rbacpb.RBAC_ALLOW, 83 Policies: map[string]*v3rbacpb.Policy{ 84 "anyone": { 85 Permissions: []*v3rbacpb.Permission{ 86 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 87 }, 88 Principals: []*v3rbacpb.Principal{ 89 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 90 }, 91 }, 92 }, 93 }, 94 }, 95 }, 96 { 97 name: "SuccessCaseAnyMatchMultiple", 98 policies: []*v3rbacpb.RBAC{ 99 { 100 Action: v3rbacpb.RBAC_ALLOW, 101 Policies: map[string]*v3rbacpb.Policy{ 102 "anyone": { 103 Permissions: []*v3rbacpb.Permission{ 104 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 105 }, 106 Principals: []*v3rbacpb.Principal{ 107 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 108 }, 109 }, 110 }, 111 }, 112 { 113 Action: v3rbacpb.RBAC_DENY, 114 Policies: map[string]*v3rbacpb.Policy{ 115 "anyone": { 116 Permissions: []*v3rbacpb.Permission{ 117 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 118 }, 119 Principals: []*v3rbacpb.Principal{ 120 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 121 }, 122 }, 123 }, 124 }, 125 }, 126 }, 127 { 128 name: "SuccessCaseSimplePolicySingular", 129 policies: []*v3rbacpb.RBAC{ 130 { 131 Action: v3rbacpb.RBAC_ALLOW, 132 Policies: map[string]*v3rbacpb.Policy{ 133 "localhost-fan": { 134 Permissions: []*v3rbacpb.Permission{ 135 {Rule: &v3rbacpb.Permission_DestinationPort{DestinationPort: 8080}}, 136 {Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "localhost-fan-page"}}}}}}, 137 }, 138 Principals: []*v3rbacpb.Principal{ 139 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 140 }, 141 }, 142 }, 143 }, 144 }, 145 }, 146 // SuccessCaseSimplePolicyTwoPolicies tests the construction of the 147 // chained engines in the case where there are two policies in a list, 148 // one with an allow policy and one with a deny policy. A situation 149 // where two policies (allow and deny) is a very common use case for 150 // this API, and should successfully build. 151 { 152 name: "SuccessCaseSimplePolicyTwoPolicies", 153 policies: []*v3rbacpb.RBAC{ 154 { 155 Action: v3rbacpb.RBAC_ALLOW, 156 Policies: map[string]*v3rbacpb.Policy{ 157 "localhost-fan": { 158 Permissions: []*v3rbacpb.Permission{ 159 {Rule: &v3rbacpb.Permission_DestinationPort{DestinationPort: 8080}}, 160 {Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "localhost-fan-page"}}}}}}, 161 }, 162 Principals: []*v3rbacpb.Principal{ 163 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 164 }, 165 }, 166 }, 167 }, 168 { 169 Action: v3rbacpb.RBAC_DENY, 170 Policies: map[string]*v3rbacpb.Policy{ 171 "localhost-fan": { 172 Permissions: []*v3rbacpb.Permission{ 173 {Rule: &v3rbacpb.Permission_DestinationPort{DestinationPort: 8080}}, 174 {Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "localhost-fan-page"}}}}}}, 175 }, 176 Principals: []*v3rbacpb.Principal{ 177 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 178 }, 179 }, 180 }, 181 }, 182 }, 183 }, 184 { 185 name: "SuccessCaseEnvoyExampleSingular", 186 policies: []*v3rbacpb.RBAC{ 187 { 188 Action: v3rbacpb.RBAC_ALLOW, 189 Policies: map[string]*v3rbacpb.Policy{ 190 "service-admin": { 191 Permissions: []*v3rbacpb.Permission{ 192 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 193 }, 194 Principals: []*v3rbacpb.Principal{ 195 {Identifier: &v3rbacpb.Principal_Authenticated_{Authenticated: &v3rbacpb.Principal_Authenticated{PrincipalName: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "cluster.local/ns/default/sa/admin"}}}}}, 196 {Identifier: &v3rbacpb.Principal_Authenticated_{Authenticated: &v3rbacpb.Principal_Authenticated{PrincipalName: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "cluster.local/ns/default/sa/superuser"}}}}}, 197 }, 198 }, 199 "product-viewer": { 200 Permissions: []*v3rbacpb.Permission{ 201 {Rule: &v3rbacpb.Permission_AndRules{AndRules: &v3rbacpb.Permission_Set{ 202 Rules: []*v3rbacpb.Permission{ 203 {Rule: &v3rbacpb.Permission_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_ExactMatch{ExactMatch: "GET"}}}}, 204 {Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: "/products"}}}}}}, 205 {Rule: &v3rbacpb.Permission_OrRules{OrRules: &v3rbacpb.Permission_Set{ 206 Rules: []*v3rbacpb.Permission{ 207 {Rule: &v3rbacpb.Permission_DestinationPort{DestinationPort: 80}}, 208 {Rule: &v3rbacpb.Permission_DestinationPort{DestinationPort: 443}}, 209 }, 210 }, 211 }, 212 }, 213 }, 214 }, 215 }, 216 }, 217 }, 218 Principals: []*v3rbacpb.Principal{ 219 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 220 }, 221 }, 222 }, 223 }, 224 }, 225 }, 226 { 227 name: "SourceIpMatcherSuccessSingular", 228 policies: []*v3rbacpb.RBAC{ 229 { 230 Action: v3rbacpb.RBAC_ALLOW, 231 Policies: map[string]*v3rbacpb.Policy{ 232 "certain-source-ip": { 233 Permissions: []*v3rbacpb.Permission{ 234 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 235 }, 236 Principals: []*v3rbacpb.Principal{ 237 {Identifier: &v3rbacpb.Principal_DirectRemoteIp{DirectRemoteIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}}, 238 }, 239 }, 240 }, 241 }, 242 }, 243 }, 244 { 245 name: "SourceIpMatcherFailureSingular", 246 policies: []*v3rbacpb.RBAC{ 247 { 248 Action: v3rbacpb.RBAC_ALLOW, 249 Policies: map[string]*v3rbacpb.Policy{ 250 "certain-source-ip": { 251 Permissions: []*v3rbacpb.Permission{ 252 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 253 }, 254 Principals: []*v3rbacpb.Principal{ 255 {Identifier: &v3rbacpb.Principal_DirectRemoteIp{DirectRemoteIp: &v3corepb.CidrRange{AddressPrefix: "not a correct address", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}}, 256 }, 257 }, 258 }, 259 }, 260 }, 261 wantErr: true, 262 }, 263 { 264 name: "DestinationIpMatcherSuccess", 265 policies: []*v3rbacpb.RBAC{ 266 { 267 Action: v3rbacpb.RBAC_ALLOW, 268 Policies: map[string]*v3rbacpb.Policy{ 269 "certain-destination-ip": { 270 Permissions: []*v3rbacpb.Permission{ 271 {Rule: &v3rbacpb.Permission_DestinationIp{DestinationIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}}, 272 }, 273 Principals: []*v3rbacpb.Principal{ 274 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 275 }, 276 }, 277 }, 278 }, 279 }, 280 }, 281 { 282 name: "DestinationIpMatcherFailure", 283 policies: []*v3rbacpb.RBAC{ 284 { 285 Action: v3rbacpb.RBAC_ALLOW, 286 Policies: map[string]*v3rbacpb.Policy{ 287 "certain-destination-ip": { 288 Permissions: []*v3rbacpb.Permission{ 289 {Rule: &v3rbacpb.Permission_DestinationIp{DestinationIp: &v3corepb.CidrRange{AddressPrefix: "not a correct address", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}}, 290 }, 291 Principals: []*v3rbacpb.Principal{ 292 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 293 }, 294 }, 295 }, 296 }, 297 }, 298 wantErr: true, 299 }, 300 { 301 name: "MatcherToNotPolicy", 302 policies: []*v3rbacpb.RBAC{ 303 { 304 Action: v3rbacpb.RBAC_ALLOW, 305 Policies: map[string]*v3rbacpb.Policy{ 306 "not-secret-content": { 307 Permissions: []*v3rbacpb.Permission{ 308 {Rule: &v3rbacpb.Permission_NotRule{NotRule: &v3rbacpb.Permission{Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: "/secret-content"}}}}}}}}, 309 }, 310 Principals: []*v3rbacpb.Principal{ 311 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 312 }, 313 }, 314 }, 315 }, 316 }, 317 }, 318 { 319 name: "MatcherToNotPrinicipal", 320 policies: []*v3rbacpb.RBAC{ 321 { 322 Action: v3rbacpb.RBAC_ALLOW, 323 Policies: map[string]*v3rbacpb.Policy{ 324 "not-from-certain-ip": { 325 Permissions: []*v3rbacpb.Permission{ 326 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 327 }, 328 Principals: []*v3rbacpb.Principal{ 329 {Identifier: &v3rbacpb.Principal_NotId{NotId: &v3rbacpb.Principal{Identifier: &v3rbacpb.Principal_DirectRemoteIp{DirectRemoteIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}}}}, 330 }, 331 }, 332 }, 333 }, 334 }, 335 }, 336 // PrinicpalProductViewer tests the construction of a chained engine 337 // with a policy that allows any downstream to send a GET request on a 338 // certain path. 339 { 340 name: "PrincipalProductViewer", 341 policies: []*v3rbacpb.RBAC{ 342 { 343 Action: v3rbacpb.RBAC_ALLOW, 344 Policies: map[string]*v3rbacpb.Policy{ 345 "product-viewer": { 346 Permissions: []*v3rbacpb.Permission{ 347 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 348 }, 349 Principals: []*v3rbacpb.Principal{ 350 { 351 Identifier: &v3rbacpb.Principal_AndIds{AndIds: &v3rbacpb.Principal_Set{Ids: []*v3rbacpb.Principal{ 352 {Identifier: &v3rbacpb.Principal_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_ExactMatch{ExactMatch: "GET"}}}}, 353 {Identifier: &v3rbacpb.Principal_OrIds{OrIds: &v3rbacpb.Principal_Set{ 354 Ids: []*v3rbacpb.Principal{ 355 {Identifier: &v3rbacpb.Principal_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: "/books"}}}}}}, 356 {Identifier: &v3rbacpb.Principal_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: "/cars"}}}}}}, 357 }, 358 }}}, 359 }}}, 360 }, 361 }, 362 }, 363 }, 364 }, 365 }, 366 }, 367 // Certain Headers tests the construction of a chained engine with a 368 // policy that allows any downstream to send an HTTP request with 369 // certain headers. 370 { 371 name: "CertainHeaders", 372 policies: []*v3rbacpb.RBAC{ 373 { 374 Policies: map[string]*v3rbacpb.Policy{ 375 "certain-headers": { 376 Permissions: []*v3rbacpb.Permission{ 377 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 378 }, 379 Principals: []*v3rbacpb.Principal{ 380 { 381 Identifier: &v3rbacpb.Principal_OrIds{OrIds: &v3rbacpb.Principal_Set{Ids: []*v3rbacpb.Principal{ 382 {Identifier: &v3rbacpb.Principal_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_ExactMatch{ExactMatch: "GET"}}}}, 383 {Identifier: &v3rbacpb.Principal_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_SafeRegexMatch{SafeRegexMatch: &v3matcherpb.RegexMatcher{Regex: "GET"}}}}}, 384 {Identifier: &v3rbacpb.Principal_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_RangeMatch{RangeMatch: &v3typepb.Int64Range{ 385 Start: 0, 386 End: 64, 387 }}}}}, 388 {Identifier: &v3rbacpb.Principal_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_PresentMatch{PresentMatch: true}}}}, 389 {Identifier: &v3rbacpb.Principal_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_PrefixMatch{PrefixMatch: "GET"}}}}, 390 {Identifier: &v3rbacpb.Principal_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_SuffixMatch{SuffixMatch: "GET"}}}}, 391 {Identifier: &v3rbacpb.Principal_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_ContainsMatch{ContainsMatch: "GET"}}}}, 392 {Identifier: &v3rbacpb.Principal_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_ContainsMatch{ContainsMatch: "GET"}}}}, 393 }}}, 394 }, 395 }, 396 }, 397 }, 398 }, 399 }, 400 }, 401 { 402 name: "LogAction", 403 policies: []*v3rbacpb.RBAC{ 404 { 405 Action: v3rbacpb.RBAC_LOG, 406 Policies: map[string]*v3rbacpb.Policy{ 407 "anyone": { 408 Permissions: []*v3rbacpb.Permission{ 409 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 410 }, 411 Principals: []*v3rbacpb.Principal{ 412 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 413 }, 414 }, 415 }, 416 }, 417 }, 418 wantErr: true, 419 }, 420 { 421 name: "ActionNotSpecified", 422 policies: []*v3rbacpb.RBAC{ 423 { 424 Policies: map[string]*v3rbacpb.Policy{ 425 "anyone": { 426 Permissions: []*v3rbacpb.Permission{ 427 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 428 }, 429 Principals: []*v3rbacpb.Principal{ 430 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 431 }, 432 }, 433 }, 434 }, 435 }, 436 }, 437 { 438 name: "SimpleAuditLogger", 439 policies: []*v3rbacpb.RBAC{ 440 { 441 Action: v3rbacpb.RBAC_ALLOW, 442 Policies: map[string]*v3rbacpb.Policy{ 443 "anyone": { 444 Permissions: []*v3rbacpb.Permission{ 445 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 446 }, 447 Principals: []*v3rbacpb.Principal{ 448 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 449 }, 450 }, 451 }, 452 AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{ 453 AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_ON_ALLOW, 454 LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{ 455 {AuditLogger: &v3corepb.TypedExtensionConfig{ 456 Name: "TestAuditLoggerBuffer", 457 TypedConfig: createUDPATypedStruct(t, map[string]any{}, "SimpleAuditLogger_TestAuditLoggerBuffer")}, 458 IsOptional: false, 459 }, 460 }, 461 }, 462 }, 463 }, 464 }, 465 { 466 name: "AuditLoggerCustomConfig", 467 policies: []*v3rbacpb.RBAC{ 468 { 469 Action: v3rbacpb.RBAC_ALLOW, 470 Policies: map[string]*v3rbacpb.Policy{ 471 "anyone": { 472 Permissions: []*v3rbacpb.Permission{ 473 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 474 }, 475 Principals: []*v3rbacpb.Principal{ 476 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 477 }, 478 }, 479 }, 480 AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{ 481 AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_ON_ALLOW, 482 LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{ 483 {AuditLogger: &v3corepb.TypedExtensionConfig{ 484 Name: "TestAuditLoggerCustomConfig", 485 TypedConfig: createUDPATypedStruct(t, map[string]any{"abc": 123, "xyz": "123"}, "AuditLoggerCustomConfig_TestAuditLoggerCustomConfig")}, 486 IsOptional: false, 487 }, 488 }, 489 }, 490 }, 491 }, 492 policyName: "test_policy", 493 }, 494 { 495 name: "AuditLoggerCustomConfigXdsTypedStruct", 496 policies: []*v3rbacpb.RBAC{ 497 { 498 Action: v3rbacpb.RBAC_ALLOW, 499 Policies: map[string]*v3rbacpb.Policy{ 500 "anyone": { 501 Permissions: []*v3rbacpb.Permission{ 502 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 503 }, 504 Principals: []*v3rbacpb.Principal{ 505 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 506 }, 507 }, 508 }, 509 AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{ 510 AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_ON_ALLOW, 511 LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{ 512 {AuditLogger: &v3corepb.TypedExtensionConfig{ 513 Name: "TestAuditLoggerCustomConfig", 514 TypedConfig: createXDSTypedStruct(t, map[string]any{"abc": 123, "xyz": "123"}, "AuditLoggerCustomConfigXdsTypedStruct_TestAuditLoggerCustomConfig")}, 515 IsOptional: false, 516 }, 517 }, 518 }, 519 }, 520 }, 521 policyName: "test_policy", 522 }, 523 { 524 name: "Missing Optional AuditLogger doesn't fail", 525 policies: []*v3rbacpb.RBAC{ 526 { 527 Action: v3rbacpb.RBAC_ALLOW, 528 Policies: map[string]*v3rbacpb.Policy{ 529 "anyone": { 530 Permissions: []*v3rbacpb.Permission{ 531 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 532 }, 533 Principals: []*v3rbacpb.Principal{ 534 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 535 }, 536 }, 537 }, 538 AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{ 539 AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_ON_ALLOW, 540 LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{ 541 {AuditLogger: &v3corepb.TypedExtensionConfig{ 542 Name: "UnsupportedLogger", 543 TypedConfig: createUDPATypedStruct(t, map[string]any{}, "Missing Optional AuditLogger doesn't fail_UnsupportedLogger")}, 544 IsOptional: true, 545 }, 546 }, 547 }, 548 }, 549 }, 550 }, 551 { 552 name: "Missing Non-Optional AuditLogger fails", 553 policies: []*v3rbacpb.RBAC{ 554 { 555 Action: v3rbacpb.RBAC_ALLOW, 556 Policies: map[string]*v3rbacpb.Policy{ 557 "anyone": { 558 Permissions: []*v3rbacpb.Permission{ 559 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 560 }, 561 Principals: []*v3rbacpb.Principal{ 562 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 563 }, 564 }, 565 }, 566 AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{ 567 AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_ON_ALLOW, 568 LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{ 569 {AuditLogger: &v3corepb.TypedExtensionConfig{ 570 Name: "UnsupportedLogger", 571 TypedConfig: createUDPATypedStruct(t, map[string]any{}, "Missing Non-Optional AuditLogger fails_UnsupportedLogger")}, 572 IsOptional: false, 573 }, 574 }, 575 }, 576 }, 577 }, 578 wantErr: true, 579 }, 580 { 581 name: "Cannot_parse_missing_CustomConfig", 582 policies: []*v3rbacpb.RBAC{ 583 { 584 Action: v3rbacpb.RBAC_ALLOW, 585 Policies: map[string]*v3rbacpb.Policy{ 586 "anyone": { 587 Permissions: []*v3rbacpb.Permission{ 588 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 589 }, 590 Principals: []*v3rbacpb.Principal{ 591 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 592 }, 593 }, 594 }, 595 AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{ 596 AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_ON_ALLOW, 597 LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{ 598 {AuditLogger: &v3corepb.TypedExtensionConfig{ 599 Name: "TestAuditLoggerCustomConfig", 600 }, 601 IsOptional: false, 602 }, 603 }, 604 }, 605 }, 606 }, 607 wantErr: true, 608 }, 609 { 610 name: "Cannot_parse_bad_CustomConfig", 611 policies: []*v3rbacpb.RBAC{ 612 { 613 Action: v3rbacpb.RBAC_ALLOW, 614 Policies: map[string]*v3rbacpb.Policy{ 615 "anyone": { 616 Permissions: []*v3rbacpb.Permission{ 617 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 618 }, 619 Principals: []*v3rbacpb.Principal{ 620 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 621 }, 622 }, 623 }, 624 AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{ 625 AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_ON_ALLOW, 626 LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{ 627 {AuditLogger: &v3corepb.TypedExtensionConfig{ 628 Name: "TestAuditLoggerCustomConfig", 629 TypedConfig: createUDPATypedStruct(t, map[string]any{"abc": "BADVALUE", "xyz": "123"}, "Cannot_parse_bad_CustomConfig_TestAuditLoggerCustomConfig")}, 630 IsOptional: false, 631 }, 632 }, 633 }, 634 }, 635 }, 636 wantErr: true, 637 }, 638 { 639 name: "Cannot_parse_missing_typedConfig_name", 640 policies: []*v3rbacpb.RBAC{ 641 { 642 Action: v3rbacpb.RBAC_ALLOW, 643 Policies: map[string]*v3rbacpb.Policy{ 644 "anyone": { 645 Permissions: []*v3rbacpb.Permission{ 646 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 647 }, 648 Principals: []*v3rbacpb.Principal{ 649 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 650 }, 651 }, 652 }, 653 AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{ 654 AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_ON_ALLOW, 655 LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{ 656 {AuditLogger: &v3corepb.TypedExtensionConfig{ 657 Name: "TestAuditLoggerCustomConfig", 658 TypedConfig: createUDPATypedStruct(t, map[string]any{"abc": 123, "xyz": "123"}, "")}, 659 IsOptional: false, 660 }, 661 }, 662 }, 663 }, 664 }, 665 wantErr: true, 666 }, 667 } 668 for _, test := range tests { 669 t.Run(test.name, func(t *testing.T) { 670 b := TestAuditLoggerBufferBuilder{testName: test.name} 671 audit.RegisterLoggerBuilder(&b) 672 b2 := TestAuditLoggerCustomConfigBuilder{testName: test.name} 673 audit.RegisterLoggerBuilder(&b2) 674 if _, err := NewChainEngine(test.policies, test.policyName); (err != nil) != test.wantErr { 675 t.Fatalf("NewChainEngine(%+v) returned err: %v, wantErr: %v", test.policies, err, test.wantErr) 676 } 677 }) 678 } 679 } 680 681 type rbacQuery struct { 682 rpcData *rpcData 683 wantStatusCode codes.Code 684 wantAuditEvents []*audit.Event 685 } 686 687 // TestChainEngine tests the chain of RBAC Engines by configuring the chain of 688 // engines in a certain way in different scenarios. After configuring the chain 689 // of engines in a certain way, this test pings the chain of engines with 690 // different types of data representing incoming RPC's (piped into a context), 691 // and verifies that it works as expected. 692 func (s) TestChainEngine(t *testing.T) { 693 defer func(gc func(ctx context.Context) net.Conn) { 694 getConnection = gc 695 }(getConnection) 696 tests := []struct { 697 name string 698 rbacConfigs []*v3rbacpb.RBAC 699 rbacQueries []rbacQuery 700 policyName string 701 }{ 702 // SuccessCaseAnyMatch tests a single RBAC Engine instantiated with 703 // a config with a policy with any rules for both permissions and 704 // principals, meaning that any data about incoming RPC's that the RBAC 705 // Engine is queried with should match that policy. 706 { 707 name: "SuccessCaseAnyMatch", 708 rbacConfigs: []*v3rbacpb.RBAC{ 709 { 710 Policies: map[string]*v3rbacpb.Policy{ 711 "anyone": { 712 Permissions: []*v3rbacpb.Permission{ 713 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 714 }, 715 Principals: []*v3rbacpb.Principal{ 716 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 717 }, 718 }, 719 }, 720 }, 721 }, 722 rbacQueries: []rbacQuery{ 723 { 724 rpcData: &rpcData{ 725 fullMethod: "some method", 726 peerInfo: &peer.Peer{ 727 Addr: &addr{ipAddress: "0.0.0.0"}, 728 }, 729 }, 730 wantStatusCode: codes.OK, 731 }, 732 }, 733 }, 734 // SuccessCaseSimplePolicy is a test that tests a single policy 735 // that only allows an rpc to proceed if the rpc is calling with a certain 736 // path. 737 { 738 name: "SuccessCaseSimplePolicy", 739 rbacConfigs: []*v3rbacpb.RBAC{ 740 { 741 Policies: map[string]*v3rbacpb.Policy{ 742 "localhost-fan": { 743 Permissions: []*v3rbacpb.Permission{ 744 {Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "localhost-fan-page"}}}}}}, 745 }, 746 Principals: []*v3rbacpb.Principal{ 747 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 748 }, 749 }, 750 }, 751 }, 752 }, 753 rbacQueries: []rbacQuery{ 754 // This RPC should match with the local host fan policy. Thus, 755 // this RPC should be allowed to proceed. 756 { 757 rpcData: &rpcData{ 758 fullMethod: "localhost-fan-page", 759 peerInfo: &peer.Peer{ 760 Addr: &addr{ipAddress: "0.0.0.0"}, 761 }, 762 }, 763 wantStatusCode: codes.OK, 764 }, 765 766 // This RPC shouldn't match with the local host fan policy. Thus, 767 // this rpc shouldn't be allowed to proceed. 768 { 769 rpcData: &rpcData{ 770 peerInfo: &peer.Peer{ 771 Addr: &addr{ipAddress: "0.0.0.0"}, 772 }, 773 }, 774 wantStatusCode: codes.PermissionDenied, 775 }, 776 }, 777 }, 778 // SuccessCaseEnvoyExample is a test based on the example provided 779 // in the EnvoyProxy docs. The RBAC Config contains two policies, 780 // service admin and product viewer, that provides an example of a real 781 // RBAC Config that might be configured for a given for a given backend 782 // service. 783 { 784 name: "SuccessCaseEnvoyExample", 785 rbacConfigs: []*v3rbacpb.RBAC{ 786 { 787 Policies: map[string]*v3rbacpb.Policy{ 788 "service-admin": { 789 Permissions: []*v3rbacpb.Permission{ 790 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 791 }, 792 Principals: []*v3rbacpb.Principal{ 793 {Identifier: &v3rbacpb.Principal_Authenticated_{Authenticated: &v3rbacpb.Principal_Authenticated{PrincipalName: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "//cluster.local/ns/default/sa/admin"}}}}}, 794 {Identifier: &v3rbacpb.Principal_Authenticated_{Authenticated: &v3rbacpb.Principal_Authenticated{PrincipalName: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "//cluster.local/ns/default/sa/superuser"}}}}}, 795 }, 796 }, 797 "product-viewer": { 798 Permissions: []*v3rbacpb.Permission{ 799 { 800 Rule: &v3rbacpb.Permission_AndRules{AndRules: &v3rbacpb.Permission_Set{ 801 Rules: []*v3rbacpb.Permission{ 802 {Rule: &v3rbacpb.Permission_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_ExactMatch{ExactMatch: "GET"}}}}, 803 {Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: "/products"}}}}}}, 804 }, 805 }, 806 }, 807 }, 808 }, 809 Principals: []*v3rbacpb.Principal{ 810 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 811 }, 812 }, 813 }, 814 }, 815 }, 816 rbacQueries: []rbacQuery{ 817 // This incoming RPC Call should match with the service admin 818 // policy. 819 { 820 rpcData: &rpcData{ 821 fullMethod: "some method", 822 peerInfo: &peer.Peer{ 823 Addr: &addr{ipAddress: "0.0.0.0"}, 824 AuthInfo: credentials.TLSInfo{ 825 State: tls.ConnectionState{ 826 PeerCertificates: []*x509.Certificate{ 827 { 828 URIs: []*url.URL{ 829 { 830 Host: "cluster.local", 831 Path: "/ns/default/sa/admin", 832 }, 833 }, 834 }, 835 }, 836 }, 837 }, 838 }, 839 }, 840 wantStatusCode: codes.OK, 841 }, 842 // These incoming RPC calls should not match any policy. 843 { 844 rpcData: &rpcData{ 845 peerInfo: &peer.Peer{ 846 Addr: &addr{ipAddress: "0.0.0.0"}, 847 }, 848 }, 849 wantStatusCode: codes.PermissionDenied, 850 }, 851 { 852 rpcData: &rpcData{ 853 fullMethod: "get-product-list", 854 peerInfo: &peer.Peer{ 855 Addr: &addr{ipAddress: "0.0.0.0"}, 856 }, 857 }, 858 wantStatusCode: codes.PermissionDenied, 859 }, 860 { 861 rpcData: &rpcData{ 862 peerInfo: &peer.Peer{ 863 Addr: &addr{ipAddress: "0.0.0.0"}, 864 AuthInfo: credentials.TLSInfo{ 865 State: tls.ConnectionState{ 866 PeerCertificates: []*x509.Certificate{ 867 { 868 Subject: pkix.Name{ 869 CommonName: "localhost", 870 }, 871 }, 872 }, 873 }, 874 }, 875 }, 876 }, 877 wantStatusCode: codes.PermissionDenied, 878 }, 879 }, 880 }, 881 { 882 name: "NotMatcher", 883 rbacConfigs: []*v3rbacpb.RBAC{ 884 { 885 Policies: map[string]*v3rbacpb.Policy{ 886 "not-secret-content": { 887 Permissions: []*v3rbacpb.Permission{ 888 { 889 Rule: &v3rbacpb.Permission_NotRule{ 890 NotRule: &v3rbacpb.Permission{Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: "/secret-content"}}}}}}, 891 }, 892 }, 893 }, 894 Principals: []*v3rbacpb.Principal{ 895 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 896 }, 897 }, 898 }, 899 }, 900 }, 901 rbacQueries: []rbacQuery{ 902 // This incoming RPC Call should match with the not-secret-content policy. 903 { 904 rpcData: &rpcData{ 905 fullMethod: "/regular-content", 906 peerInfo: &peer.Peer{ 907 Addr: &addr{ipAddress: "0.0.0.0"}, 908 }, 909 }, 910 wantStatusCode: codes.OK, 911 }, 912 // This incoming RPC Call shouldn't match with the not-secret-content-policy. 913 { 914 rpcData: &rpcData{ 915 fullMethod: "/secret-content", 916 peerInfo: &peer.Peer{ 917 Addr: &addr{ipAddress: "0.0.0.0"}, 918 }, 919 }, 920 wantStatusCode: codes.PermissionDenied, 921 }, 922 }, 923 }, 924 { 925 name: "DirectRemoteIpMatcher", 926 rbacConfigs: []*v3rbacpb.RBAC{ 927 { 928 Policies: map[string]*v3rbacpb.Policy{ 929 "certain-direct-remote-ip": { 930 Permissions: []*v3rbacpb.Permission{ 931 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 932 }, 933 Principals: []*v3rbacpb.Principal{ 934 {Identifier: &v3rbacpb.Principal_DirectRemoteIp{DirectRemoteIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}}, 935 }, 936 }, 937 }, 938 }, 939 }, 940 rbacQueries: []rbacQuery{ 941 // This incoming RPC Call should match with the certain-direct-remote-ip policy. 942 { 943 rpcData: &rpcData{ 944 peerInfo: &peer.Peer{ 945 Addr: &addr{ipAddress: "0.0.0.0"}, 946 }, 947 }, 948 wantStatusCode: codes.OK, 949 }, 950 // This incoming RPC Call shouldn't match with the certain-direct-remote-ip policy. 951 { 952 rpcData: &rpcData{ 953 peerInfo: &peer.Peer{ 954 Addr: &addr{ipAddress: "10.0.0.0"}, 955 }, 956 }, 957 wantStatusCode: codes.PermissionDenied, 958 }, 959 }, 960 }, 961 // This test tests a RBAC policy configured with a remote-ip policy. 962 // This should be logically equivalent to configuring a Engine with a 963 // direct-remote-ip policy, as per A41 - "allow equating RBAC's 964 // direct_remote_ip and remote_ip." 965 { 966 name: "RemoteIpMatcher", 967 rbacConfigs: []*v3rbacpb.RBAC{ 968 { 969 Policies: map[string]*v3rbacpb.Policy{ 970 "certain-remote-ip": { 971 Permissions: []*v3rbacpb.Permission{ 972 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 973 }, 974 Principals: []*v3rbacpb.Principal{ 975 {Identifier: &v3rbacpb.Principal_RemoteIp{RemoteIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}}, 976 }, 977 }, 978 }, 979 }, 980 }, 981 rbacQueries: []rbacQuery{ 982 // This incoming RPC Call should match with the certain-remote-ip policy. 983 { 984 rpcData: &rpcData{ 985 peerInfo: &peer.Peer{ 986 Addr: &addr{ipAddress: "0.0.0.0"}, 987 }, 988 }, 989 wantStatusCode: codes.OK, 990 }, 991 // This incoming RPC Call shouldn't match with the certain-remote-ip policy. 992 { 993 rpcData: &rpcData{ 994 peerInfo: &peer.Peer{ 995 Addr: &addr{ipAddress: "10.0.0.0"}, 996 }, 997 }, 998 wantStatusCode: codes.PermissionDenied, 999 }, 1000 }, 1001 }, 1002 { 1003 name: "DestinationIpMatcher", 1004 rbacConfigs: []*v3rbacpb.RBAC{ 1005 { 1006 Policies: map[string]*v3rbacpb.Policy{ 1007 "certain-destination-ip": { 1008 Permissions: []*v3rbacpb.Permission{ 1009 {Rule: &v3rbacpb.Permission_DestinationIp{DestinationIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}}, 1010 }, 1011 Principals: []*v3rbacpb.Principal{ 1012 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 1013 }, 1014 }, 1015 }, 1016 }, 1017 }, 1018 rbacQueries: []rbacQuery{ 1019 // This incoming RPC Call shouldn't match with the 1020 // certain-destination-ip policy, as the test listens on local 1021 // host. 1022 { 1023 rpcData: &rpcData{ 1024 peerInfo: &peer.Peer{ 1025 Addr: &addr{ipAddress: "10.0.0.0"}, 1026 }, 1027 }, 1028 wantStatusCode: codes.PermissionDenied, 1029 }, 1030 }, 1031 }, 1032 // AllowAndDenyPolicy tests a policy with an allow (on path) and 1033 // deny (on port) policy chained together. This represents how a user 1034 // configured interceptor would use this, and also is a potential 1035 // configuration for a dynamic xds interceptor. 1036 { 1037 name: "AllowAndDenyPolicy", 1038 rbacConfigs: []*v3rbacpb.RBAC{ 1039 { 1040 Policies: map[string]*v3rbacpb.Policy{ 1041 "certain-source-ip": { 1042 Permissions: []*v3rbacpb.Permission{ 1043 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 1044 }, 1045 Principals: []*v3rbacpb.Principal{ 1046 {Identifier: &v3rbacpb.Principal_DirectRemoteIp{DirectRemoteIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}}, 1047 }, 1048 }, 1049 }, 1050 Action: v3rbacpb.RBAC_ALLOW, 1051 }, 1052 { 1053 Policies: map[string]*v3rbacpb.Policy{ 1054 "localhost-fan": { 1055 Permissions: []*v3rbacpb.Permission{ 1056 {Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "localhost-fan-page"}}}}}}, 1057 }, 1058 Principals: []*v3rbacpb.Principal{ 1059 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 1060 }, 1061 }, 1062 }, 1063 Action: v3rbacpb.RBAC_DENY, 1064 }, 1065 }, 1066 rbacQueries: []rbacQuery{ 1067 // This RPC should match with the allow policy, and shouldn't 1068 // match with the deny and thus should be allowed to proceed. 1069 { 1070 rpcData: &rpcData{ 1071 peerInfo: &peer.Peer{ 1072 Addr: &addr{ipAddress: "0.0.0.0"}, 1073 }, 1074 }, 1075 wantStatusCode: codes.OK, 1076 }, 1077 // This RPC should match with both the allow policy and deny policy 1078 // and thus shouldn't be allowed to proceed as matched with deny. 1079 { 1080 rpcData: &rpcData{ 1081 fullMethod: "localhost-fan-page", 1082 peerInfo: &peer.Peer{ 1083 Addr: &addr{ipAddress: "0.0.0.0"}, 1084 }, 1085 }, 1086 wantStatusCode: codes.PermissionDenied, 1087 }, 1088 // This RPC shouldn't match with either policy, and thus 1089 // shouldn't be allowed to proceed as didn't match with allow. 1090 { 1091 rpcData: &rpcData{ 1092 peerInfo: &peer.Peer{ 1093 Addr: &addr{ipAddress: "10.0.0.0"}, 1094 }, 1095 }, 1096 wantStatusCode: codes.PermissionDenied, 1097 }, 1098 // This RPC shouldn't match with allow, match with deny, and 1099 // thus shouldn't be allowed to proceed. 1100 { 1101 rpcData: &rpcData{ 1102 fullMethod: "localhost-fan-page", 1103 peerInfo: &peer.Peer{ 1104 Addr: &addr{ipAddress: "10.0.0.0"}, 1105 }, 1106 }, 1107 wantStatusCode: codes.PermissionDenied, 1108 }, 1109 }, 1110 }, 1111 // This test tests that when there are no SANs or Subject's 1112 // distinguished name in incoming RPC's, that authenticated matchers 1113 // match against the empty string. 1114 { 1115 name: "default-matching-no-credentials", 1116 rbacConfigs: []*v3rbacpb.RBAC{ 1117 { 1118 Policies: map[string]*v3rbacpb.Policy{ 1119 "service-admin": { 1120 Permissions: []*v3rbacpb.Permission{ 1121 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 1122 }, 1123 Principals: []*v3rbacpb.Principal{ 1124 {Identifier: &v3rbacpb.Principal_Authenticated_{Authenticated: &v3rbacpb.Principal_Authenticated{PrincipalName: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: ""}}}}}, 1125 }, 1126 }, 1127 }, 1128 }, 1129 }, 1130 rbacQueries: []rbacQuery{ 1131 // This incoming RPC Call should match with the service admin 1132 // policy. No authentication info is provided, so the 1133 // authenticated matcher should match to the string matcher on 1134 // the empty string, matching to the service-admin policy. 1135 { 1136 rpcData: &rpcData{ 1137 fullMethod: "some method", 1138 peerInfo: &peer.Peer{ 1139 Addr: &addr{ipAddress: "0.0.0.0"}, 1140 AuthInfo: credentials.TLSInfo{ 1141 State: tls.ConnectionState{ 1142 PeerCertificates: []*x509.Certificate{ 1143 { 1144 URIs: []*url.URL{ 1145 { 1146 Host: "cluster.local", 1147 Path: "/ns/default/sa/admin", 1148 }, 1149 }, 1150 }, 1151 }, 1152 }, 1153 }, 1154 }, 1155 }, 1156 wantStatusCode: codes.OK, 1157 }, 1158 }, 1159 }, 1160 // This test tests that an RBAC policy configured with a metadata 1161 // matcher as a permission doesn't match with any incoming RPC. 1162 { 1163 name: "metadata-never-matches", 1164 rbacConfigs: []*v3rbacpb.RBAC{ 1165 { 1166 Policies: map[string]*v3rbacpb.Policy{ 1167 "metadata-never-matches": { 1168 Permissions: []*v3rbacpb.Permission{ 1169 {Rule: &v3rbacpb.Permission_Metadata{ 1170 Metadata: &v3matcherpb.MetadataMatcher{}, 1171 }}, 1172 }, 1173 Principals: []*v3rbacpb.Principal{ 1174 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 1175 }, 1176 }, 1177 }, 1178 }, 1179 }, 1180 rbacQueries: []rbacQuery{ 1181 { 1182 rpcData: &rpcData{ 1183 fullMethod: "some method", 1184 peerInfo: &peer.Peer{ 1185 Addr: &addr{ipAddress: "0.0.0.0"}, 1186 }, 1187 }, 1188 wantStatusCode: codes.PermissionDenied, 1189 }, 1190 }, 1191 }, 1192 // This test tests that an RBAC policy configured with a metadata 1193 // matcher with invert set to true as a permission always matches with 1194 // any incoming RPC. 1195 { 1196 name: "metadata-invert-always-matches", 1197 rbacConfigs: []*v3rbacpb.RBAC{ 1198 { 1199 Policies: map[string]*v3rbacpb.Policy{ 1200 "metadata-invert-always-matches": { 1201 Permissions: []*v3rbacpb.Permission{ 1202 {Rule: &v3rbacpb.Permission_Metadata{ 1203 Metadata: &v3matcherpb.MetadataMatcher{Invert: true}, 1204 }}, 1205 }, 1206 Principals: []*v3rbacpb.Principal{ 1207 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 1208 }, 1209 }, 1210 }, 1211 }, 1212 }, 1213 rbacQueries: []rbacQuery{ 1214 { 1215 rpcData: &rpcData{ 1216 fullMethod: "some method", 1217 peerInfo: &peer.Peer{ 1218 Addr: &addr{ipAddress: "0.0.0.0"}, 1219 }, 1220 }, 1221 wantStatusCode: codes.OK, 1222 }, 1223 }, 1224 }, 1225 // AllowAndDenyPolicy tests a policy with an allow (on path) and 1226 // deny (on port) policy chained together. This represents how a user 1227 // configured interceptor would use this, and also is a potential 1228 // configuration for a dynamic xds interceptor. Further, it tests that 1229 // the audit logger works properly in each scenario. 1230 { 1231 name: "AuditLoggingAllowAndDenyPolicy_ON_ALLOW", 1232 policyName: "test_policy", 1233 rbacConfigs: []*v3rbacpb.RBAC{ 1234 { 1235 Policies: map[string]*v3rbacpb.Policy{ 1236 "localhost-fan": { 1237 Permissions: []*v3rbacpb.Permission{ 1238 {Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "localhost-fan-page"}}}}}}, 1239 }, 1240 Principals: []*v3rbacpb.Principal{ 1241 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 1242 }, 1243 }, 1244 }, 1245 Action: v3rbacpb.RBAC_DENY, 1246 AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{ 1247 AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_NONE, 1248 LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{ 1249 {AuditLogger: &v3corepb.TypedExtensionConfig{ 1250 Name: "TestAuditLoggerBuffer", 1251 TypedConfig: createUDPATypedStruct(t, map[string]any{}, "AuditLoggingAllowAndDenyPolicy_ON_ALLOW_TestAuditLoggerBuffer")}, 1252 IsOptional: false, 1253 }, 1254 }, 1255 }, 1256 }, 1257 { 1258 Policies: map[string]*v3rbacpb.Policy{ 1259 "certain-source-ip": { 1260 Permissions: []*v3rbacpb.Permission{ 1261 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 1262 }, 1263 Principals: []*v3rbacpb.Principal{ 1264 {Identifier: &v3rbacpb.Principal_DirectRemoteIp{DirectRemoteIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}}, 1265 }, 1266 }, 1267 }, 1268 Action: v3rbacpb.RBAC_ALLOW, 1269 AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{ 1270 AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_ON_ALLOW, 1271 LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{ 1272 {AuditLogger: &v3corepb.TypedExtensionConfig{ 1273 Name: "TestAuditLoggerBuffer", 1274 TypedConfig: createUDPATypedStruct(t, map[string]any{}, "AuditLoggingAllowAndDenyPolicy_ON_ALLOW_TestAuditLoggerBuffer")}, 1275 IsOptional: false, 1276 }, 1277 }, 1278 }, 1279 }, 1280 }, 1281 rbacQueries: []rbacQuery{ 1282 // This RPC should match with the allow policy, and shouldn't 1283 // match with the deny and thus should be allowed to proceed. 1284 { 1285 rpcData: &rpcData{ 1286 fullMethod: "", 1287 peerInfo: &peer.Peer{ 1288 Addr: &addr{ipAddress: "0.0.0.0"}, 1289 AuthInfo: credentials.TLSInfo{ 1290 State: tls.ConnectionState{ 1291 PeerCertificates: []*x509.Certificate{ 1292 { 1293 URIs: []*url.URL{ 1294 { 1295 Scheme: "spiffe", 1296 Host: "cluster.local", 1297 Path: "/ns/default/sa/admin", 1298 }, 1299 }, 1300 }, 1301 }, 1302 }, 1303 SPIFFEID: &url.URL{ 1304 Scheme: "spiffe", 1305 Host: "cluster.local", 1306 Path: "/ns/default/sa/admin", 1307 }, 1308 }, 1309 }, 1310 }, 1311 wantStatusCode: codes.OK, 1312 wantAuditEvents: []*audit.Event{ 1313 { 1314 FullMethodName: "", 1315 Principal: "spiffe://cluster.local/ns/default/sa/admin", 1316 PolicyName: "test_policy", 1317 MatchedRule: "certain-source-ip", 1318 Authorized: true, 1319 }, 1320 }, 1321 }, 1322 // This RPC should match with both the allow policy and deny policy 1323 // and thus shouldn't be allowed to proceed as matched with deny. 1324 { 1325 rpcData: &rpcData{ 1326 fullMethod: "localhost-fan-page", 1327 peerInfo: &peer.Peer{ 1328 Addr: &addr{ipAddress: "0.0.0.0"}, 1329 }, 1330 }, 1331 wantStatusCode: codes.PermissionDenied, 1332 }, 1333 // This RPC shouldn't match with either policy, and thus 1334 // shouldn't be allowed to proceed as didn't match with allow. 1335 { 1336 rpcData: &rpcData{ 1337 peerInfo: &peer.Peer{ 1338 Addr: &addr{ipAddress: "10.0.0.0"}, 1339 }, 1340 }, 1341 wantStatusCode: codes.PermissionDenied, 1342 }, 1343 // This RPC shouldn't match with allow, match with deny, and 1344 // thus shouldn't be allowed to proceed. 1345 { 1346 rpcData: &rpcData{ 1347 fullMethod: "localhost-fan-page", 1348 peerInfo: &peer.Peer{ 1349 Addr: &addr{ipAddress: "10.0.0.0"}, 1350 }, 1351 }, 1352 wantStatusCode: codes.PermissionDenied, 1353 }, 1354 }, 1355 }, 1356 { 1357 name: "AuditLoggingAllowAndDenyPolicy_ON_DENY", 1358 policyName: "test_policy", 1359 rbacConfigs: []*v3rbacpb.RBAC{ 1360 { 1361 Policies: map[string]*v3rbacpb.Policy{ 1362 "localhost-fan": { 1363 Permissions: []*v3rbacpb.Permission{ 1364 {Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "localhost-fan-page"}}}}}}, 1365 }, 1366 Principals: []*v3rbacpb.Principal{ 1367 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 1368 }, 1369 }, 1370 }, 1371 Action: v3rbacpb.RBAC_DENY, 1372 AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{ 1373 AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_ON_DENY, 1374 LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{ 1375 {AuditLogger: &v3corepb.TypedExtensionConfig{ 1376 Name: "TestAuditLoggerBuffer", 1377 TypedConfig: createUDPATypedStruct(t, map[string]any{}, "AuditLoggingAllowAndDenyPolicy_ON_DENY_TestAuditLoggerBuffer")}, 1378 IsOptional: false, 1379 }, 1380 }, 1381 }, 1382 }, 1383 { 1384 Policies: map[string]*v3rbacpb.Policy{ 1385 "certain-source-ip": { 1386 Permissions: []*v3rbacpb.Permission{ 1387 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 1388 }, 1389 Principals: []*v3rbacpb.Principal{ 1390 {Identifier: &v3rbacpb.Principal_DirectRemoteIp{DirectRemoteIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}}, 1391 }, 1392 }, 1393 }, 1394 Action: v3rbacpb.RBAC_ALLOW, 1395 AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{ 1396 AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_ON_DENY, 1397 LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{ 1398 {AuditLogger: &v3corepb.TypedExtensionConfig{ 1399 Name: "TestAuditLoggerBuffer", 1400 TypedConfig: createUDPATypedStruct(t, map[string]any{}, "AuditLoggingAllowAndDenyPolicy_ON_DENY_TestAuditLoggerBuffer")}, 1401 IsOptional: false, 1402 }, 1403 }, 1404 }, 1405 }, 1406 }, 1407 rbacQueries: []rbacQuery{ 1408 // This RPC should match with the allow policy, and shouldn't 1409 // match with the deny and thus should be allowed to proceed. 1410 // Audit logging matches with nothing. 1411 { 1412 rpcData: &rpcData{ 1413 fullMethod: "", 1414 peerInfo: &peer.Peer{ 1415 Addr: &addr{ipAddress: "0.0.0.0"}, 1416 }, 1417 }, 1418 wantStatusCode: codes.OK, 1419 }, 1420 // This RPC should match with both the allow policy and deny policy 1421 // and thus shouldn't be allowed to proceed as matched with deny. 1422 // Audit logging matches with deny and short circuits. 1423 { 1424 rpcData: &rpcData{ 1425 fullMethod: "localhost-fan-page", 1426 peerInfo: &peer.Peer{ 1427 Addr: &addr{ipAddress: "0.0.0.0"}, 1428 AuthInfo: credentials.TLSInfo{ 1429 State: tls.ConnectionState{ 1430 PeerCertificates: []*x509.Certificate{ 1431 { 1432 URIs: []*url.URL{ 1433 { 1434 Host: "cluster.local", 1435 Path: "/ns/default/sa/admin", 1436 }, 1437 }, 1438 }, 1439 }, 1440 }, 1441 }, 1442 }, 1443 }, 1444 wantStatusCode: codes.PermissionDenied, 1445 wantAuditEvents: []*audit.Event{ 1446 { 1447 FullMethodName: "localhost-fan-page", 1448 PolicyName: "test_policy", 1449 MatchedRule: "localhost-fan", 1450 Authorized: false, 1451 }, 1452 }, 1453 }, 1454 // This RPC shouldn't match with either policy, and thus 1455 // shouldn't be allowed to proceed as didn't match with allow. 1456 // Audit logging matches with the allow policy. 1457 { 1458 rpcData: &rpcData{ 1459 peerInfo: &peer.Peer{ 1460 Addr: &addr{ipAddress: "10.0.0.0"}, 1461 }, 1462 }, 1463 wantStatusCode: codes.PermissionDenied, 1464 wantAuditEvents: []*audit.Event{ 1465 { 1466 FullMethodName: "", 1467 PolicyName: "test_policy", 1468 MatchedRule: "", 1469 Authorized: false, 1470 }, 1471 }, 1472 }, 1473 // This RPC shouldn't match with allow, match with deny, and 1474 // thus shouldn't be allowed to proceed. 1475 // Audit logging will have the deny logged. 1476 { 1477 rpcData: &rpcData{ 1478 fullMethod: "localhost-fan-page", 1479 peerInfo: &peer.Peer{ 1480 Addr: &addr{ipAddress: "10.0.0.0"}, 1481 }, 1482 }, 1483 wantStatusCode: codes.PermissionDenied, 1484 wantAuditEvents: []*audit.Event{ 1485 { 1486 FullMethodName: "localhost-fan-page", 1487 PolicyName: "test_policy", 1488 MatchedRule: "localhost-fan", 1489 Authorized: false, 1490 }, 1491 }, 1492 }, 1493 }, 1494 }, 1495 { 1496 name: "AuditLoggingAllowAndDenyPolicy_NONE", 1497 policyName: "test_policy", 1498 rbacConfigs: []*v3rbacpb.RBAC{ 1499 { 1500 Policies: map[string]*v3rbacpb.Policy{ 1501 "localhost-fan": { 1502 Permissions: []*v3rbacpb.Permission{ 1503 {Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "localhost-fan-page"}}}}}}, 1504 }, 1505 Principals: []*v3rbacpb.Principal{ 1506 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 1507 }, 1508 }, 1509 }, 1510 Action: v3rbacpb.RBAC_DENY, 1511 AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{ 1512 AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_NONE, 1513 LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{ 1514 {AuditLogger: &v3corepb.TypedExtensionConfig{ 1515 Name: "TestAuditLoggerBuffer", 1516 TypedConfig: createUDPATypedStruct(t, map[string]any{}, "AuditLoggingAllowAndDenyPolicy_NONE_TestAuditLoggerBuffer")}, 1517 IsOptional: false, 1518 }, 1519 }, 1520 }, 1521 }, 1522 { 1523 Policies: map[string]*v3rbacpb.Policy{ 1524 "certain-source-ip": { 1525 Permissions: []*v3rbacpb.Permission{ 1526 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 1527 }, 1528 Principals: []*v3rbacpb.Principal{ 1529 {Identifier: &v3rbacpb.Principal_DirectRemoteIp{DirectRemoteIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}}, 1530 }, 1531 }, 1532 }, 1533 Action: v3rbacpb.RBAC_ALLOW, 1534 AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{ 1535 AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_NONE, 1536 LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{ 1537 {AuditLogger: &v3corepb.TypedExtensionConfig{ 1538 Name: "TestAuditLoggerBuffer", 1539 TypedConfig: createUDPATypedStruct(t, map[string]any{}, "AuditLoggingAllowAndDenyPolicy_NONE_TestAuditLoggerBuffer")}, 1540 IsOptional: false, 1541 }, 1542 }, 1543 }, 1544 }, 1545 }, 1546 rbacQueries: []rbacQuery{ 1547 // This RPC should match with the allow policy, and shouldn't 1548 // match with the deny and thus should be allowed to proceed. 1549 // Audit logging is NONE. 1550 { 1551 rpcData: &rpcData{ 1552 fullMethod: "", 1553 peerInfo: &peer.Peer{ 1554 Addr: &addr{ipAddress: "0.0.0.0"}, 1555 }, 1556 }, 1557 wantStatusCode: codes.OK, 1558 }, 1559 // This RPC should match with both the allow policy and deny policy 1560 // and thus shouldn't be allowed to proceed as matched with deny. 1561 // Audit logging is NONE. 1562 { 1563 rpcData: &rpcData{ 1564 fullMethod: "localhost-fan-page", 1565 peerInfo: &peer.Peer{ 1566 Addr: &addr{ipAddress: "0.0.0.0"}, 1567 }, 1568 }, 1569 wantStatusCode: codes.PermissionDenied, 1570 }, 1571 // This RPC shouldn't match with either policy, and thus 1572 // shouldn't be allowed to proceed as didn't match with allow. 1573 // Audit logging is NONE. 1574 { 1575 rpcData: &rpcData{ 1576 peerInfo: &peer.Peer{ 1577 Addr: &addr{ipAddress: "10.0.0.0"}, 1578 }, 1579 }, 1580 wantStatusCode: codes.PermissionDenied, 1581 }, 1582 // This RPC shouldn't match with allow, match with deny, and 1583 // thus shouldn't be allowed to proceed. 1584 // Audit logging is NONE. 1585 { 1586 rpcData: &rpcData{ 1587 fullMethod: "localhost-fan-page", 1588 peerInfo: &peer.Peer{ 1589 Addr: &addr{ipAddress: "10.0.0.0"}, 1590 }, 1591 }, 1592 wantStatusCode: codes.PermissionDenied, 1593 }, 1594 }, 1595 }, 1596 { 1597 name: "AuditLoggingAllowAndDenyPolicy_ON_DENY_AND_ALLOW", 1598 policyName: "test_policy", 1599 rbacConfigs: []*v3rbacpb.RBAC{ 1600 { 1601 Policies: map[string]*v3rbacpb.Policy{ 1602 "localhost-fan": { 1603 Permissions: []*v3rbacpb.Permission{ 1604 {Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "localhost-fan-page"}}}}}}, 1605 }, 1606 Principals: []*v3rbacpb.Principal{ 1607 {Identifier: &v3rbacpb.Principal_Any{Any: true}}, 1608 }, 1609 }, 1610 }, 1611 Action: v3rbacpb.RBAC_DENY, 1612 AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{ 1613 AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_ON_DENY, 1614 LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{ 1615 {AuditLogger: &v3corepb.TypedExtensionConfig{ 1616 Name: "TestAuditLoggerBuffer", 1617 TypedConfig: createUDPATypedStruct(t, map[string]any{}, "AuditLoggingAllowAndDenyPolicy_ON_DENY_AND_ALLOW_TestAuditLoggerBuffer")}, 1618 IsOptional: false, 1619 }, 1620 }, 1621 }, 1622 }, 1623 { 1624 Policies: map[string]*v3rbacpb.Policy{ 1625 "certain-source-ip": { 1626 Permissions: []*v3rbacpb.Permission{ 1627 {Rule: &v3rbacpb.Permission_Any{Any: true}}, 1628 }, 1629 Principals: []*v3rbacpb.Principal{ 1630 {Identifier: &v3rbacpb.Principal_DirectRemoteIp{DirectRemoteIp: &v3corepb.CidrRange{AddressPrefix: "0.0.0.0", PrefixLen: &wrapperspb.UInt32Value{Value: uint32(10)}}}}, 1631 }, 1632 }, 1633 }, 1634 Action: v3rbacpb.RBAC_ALLOW, 1635 AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{ 1636 AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_ON_DENY_AND_ALLOW, 1637 LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{ 1638 {AuditLogger: &v3corepb.TypedExtensionConfig{ 1639 Name: "TestAuditLoggerBuffer", 1640 TypedConfig: createUDPATypedStruct(t, map[string]any{}, "AuditLoggingAllowAndDenyPolicy_ON_DENY_AND_ALLOW_TestAuditLoggerBuffer")}, 1641 IsOptional: false, 1642 }, 1643 }, 1644 }, 1645 }, 1646 }, 1647 rbacQueries: []rbacQuery{ 1648 // This RPC should match with the allow policy, and shouldn't 1649 // match with the deny and thus should be allowed to proceed. 1650 // Audit logging matches with nothing. 1651 { 1652 rpcData: &rpcData{ 1653 fullMethod: "", 1654 peerInfo: &peer.Peer{ 1655 Addr: &addr{ipAddress: "0.0.0.0"}, 1656 }, 1657 }, 1658 wantStatusCode: codes.OK, 1659 wantAuditEvents: []*audit.Event{ 1660 { 1661 FullMethodName: "", 1662 PolicyName: "test_policy", 1663 MatchedRule: "certain-source-ip", 1664 Authorized: true, 1665 }, 1666 }, 1667 }, 1668 // This RPC should match with both the allow policy and deny policy 1669 // and thus shouldn't be allowed to proceed as matched with deny. 1670 // Audit logging matches with deny and short circuits. 1671 { 1672 rpcData: &rpcData{ 1673 fullMethod: "localhost-fan-page", 1674 peerInfo: &peer.Peer{ 1675 Addr: &addr{ipAddress: "0.0.0.0"}, 1676 }, 1677 }, 1678 wantStatusCode: codes.PermissionDenied, 1679 wantAuditEvents: []*audit.Event{ 1680 { 1681 FullMethodName: "localhost-fan-page", 1682 PolicyName: "test_policy", 1683 MatchedRule: "localhost-fan", 1684 Authorized: false, 1685 }, 1686 }, 1687 }, 1688 // This RPC shouldn't match with either policy, and thus 1689 // shouldn't be allowed to proceed as didn't match with allow. 1690 // Audit logging matches with the allow policy. 1691 { 1692 rpcData: &rpcData{ 1693 peerInfo: &peer.Peer{ 1694 Addr: &addr{ipAddress: "10.0.0.0"}, 1695 }, 1696 }, 1697 wantStatusCode: codes.PermissionDenied, 1698 wantAuditEvents: []*audit.Event{ 1699 { 1700 FullMethodName: "", 1701 PolicyName: "test_policy", 1702 MatchedRule: "", 1703 Authorized: false, 1704 }, 1705 }, 1706 }, 1707 // This RPC shouldn't match with allow, match with deny, and 1708 // thus shouldn't be allowed to proceed. 1709 // Audit logging will have the deny logged. 1710 { 1711 rpcData: &rpcData{ 1712 fullMethod: "localhost-fan-page", 1713 peerInfo: &peer.Peer{ 1714 Addr: &addr{ipAddress: "10.0.0.0"}, 1715 }, 1716 }, 1717 wantStatusCode: codes.PermissionDenied, 1718 wantAuditEvents: []*audit.Event{ 1719 { 1720 FullMethodName: "localhost-fan-page", 1721 PolicyName: "test_policy", 1722 MatchedRule: "localhost-fan", 1723 Authorized: false, 1724 }, 1725 }, 1726 }, 1727 }, 1728 }, 1729 } 1730 for _, test := range tests { 1731 t.Run(test.name, func(t *testing.T) { 1732 b := TestAuditLoggerBufferBuilder{testName: test.name} 1733 audit.RegisterLoggerBuilder(&b) 1734 b2 := TestAuditLoggerCustomConfigBuilder{testName: test.name} 1735 audit.RegisterLoggerBuilder(&b2) 1736 1737 // Instantiate the chainedRBACEngine with different configurations that are 1738 // interesting to test and to query. 1739 cre, err := NewChainEngine(test.rbacConfigs, test.policyName) 1740 if err != nil { 1741 t.Fatalf("Error constructing RBAC Engine: %v", err) 1742 } 1743 // Query the created chain of RBAC Engines with different args to see 1744 // if the chain of RBAC Engines configured as such works as intended. 1745 for _, data := range test.rbacQueries { 1746 func() { 1747 // Construct the context with three data points that have enough 1748 // information to represent incoming RPC's. This will be how a 1749 // user uses this API. A user will have to put MD, PeerInfo, and 1750 // the connection the RPC is sent on in the context. 1751 ctx := metadata.NewIncomingContext(context.Background(), data.rpcData.md) 1752 1753 // Make a TCP connection with a certain destination port. The 1754 // address/port of this connection will be used to populate the 1755 // destination ip/port in RPCData struct. This represents what 1756 // the user of ChainEngine will have to place into context, 1757 // as this is only way to get destination ip and port. 1758 lis, err := net.Listen("tcp", "localhost:0") 1759 if err != nil { 1760 t.Fatalf("Error listening: %v", err) 1761 } 1762 defer lis.Close() 1763 connCh := make(chan net.Conn, 1) 1764 go func() { 1765 conn, err := lis.Accept() 1766 if err != nil { 1767 t.Errorf("Error accepting connection: %v", err) 1768 return 1769 } 1770 connCh <- conn 1771 }() 1772 _, err = net.Dial("tcp", lis.Addr().String()) 1773 if err != nil { 1774 t.Fatalf("Error dialing: %v", err) 1775 } 1776 conn := <-connCh 1777 defer conn.Close() 1778 getConnection = func(context.Context) net.Conn { 1779 return conn 1780 } 1781 ctx = peer.NewContext(ctx, data.rpcData.peerInfo) 1782 stream := &ServerTransportStreamWithMethod{ 1783 method: data.rpcData.fullMethod, 1784 } 1785 1786 ctx = grpc.NewContextWithServerTransportStream(ctx, stream) 1787 err = cre.IsAuthorized(ctx) 1788 if gotCode := status.Code(err); gotCode != data.wantStatusCode { 1789 t.Fatalf("IsAuthorized(%+v, %+v) returned (%+v), want(%+v)", ctx, data.rpcData.fullMethod, gotCode, data.wantStatusCode) 1790 } 1791 if !reflect.DeepEqual(b.auditEvents, data.wantAuditEvents) { 1792 t.Fatalf("Unexpected audit event for query:%v", data) 1793 } 1794 1795 // This builder's auditEvents can be shared for several queries, make sure it's empty. 1796 b.auditEvents = nil 1797 }() 1798 } 1799 }) 1800 } 1801 } 1802 1803 type ServerTransportStreamWithMethod struct { 1804 method string 1805 } 1806 1807 func (sts *ServerTransportStreamWithMethod) Method() string { 1808 return sts.method 1809 } 1810 1811 func (sts *ServerTransportStreamWithMethod) SetHeader(md metadata.MD) error { 1812 return nil 1813 } 1814 1815 func (sts *ServerTransportStreamWithMethod) SendHeader(md metadata.MD) error { 1816 return nil 1817 } 1818 1819 func (sts *ServerTransportStreamWithMethod) SetTrailer(md metadata.MD) error { 1820 return nil 1821 } 1822 1823 // An audit logger that will log to the auditEvents slice. 1824 type TestAuditLoggerBuffer struct { 1825 auditEvents *[]*audit.Event 1826 } 1827 1828 func (logger *TestAuditLoggerBuffer) Log(e *audit.Event) { 1829 *(logger.auditEvents) = append(*(logger.auditEvents), e) 1830 } 1831 1832 // Builds TestAuditLoggerBuffer. 1833 type TestAuditLoggerBufferBuilder struct { 1834 auditEvents []*audit.Event 1835 testName string 1836 } 1837 1838 // The required config for TestAuditLoggerBuffer. 1839 type TestAuditLoggerBufferConfig struct { 1840 audit.LoggerConfig 1841 } 1842 1843 func (b *TestAuditLoggerBufferBuilder) ParseLoggerConfig(configJSON json.RawMessage) (config audit.LoggerConfig, err error) { 1844 return TestAuditLoggerBufferConfig{}, nil 1845 } 1846 1847 func (b *TestAuditLoggerBufferBuilder) Build(config audit.LoggerConfig) audit.Logger { 1848 return &TestAuditLoggerBuffer{auditEvents: &b.auditEvents} 1849 } 1850 1851 func (b *TestAuditLoggerBufferBuilder) Name() string { 1852 return b.testName + "_TestAuditLoggerBuffer" 1853 } 1854 1855 // An audit logger to test using a custom config. 1856 type TestAuditLoggerCustomConfig struct{} 1857 1858 func (logger *TestAuditLoggerCustomConfig) Log(*audit.Event) {} 1859 1860 // Build TestAuditLoggerCustomConfig. This builds a TestAuditLoggerCustomConfig 1861 // logger that uses a custom config. 1862 type TestAuditLoggerCustomConfigBuilder struct { 1863 testName string 1864 } 1865 1866 // The custom config for the TestAuditLoggerCustomConfig logger. 1867 type TestAuditLoggerCustomConfigConfig struct { 1868 audit.LoggerConfig 1869 Abc int 1870 Xyz string 1871 } 1872 1873 // Parses TestAuditLoggerCustomConfigConfig. Hard-coded to match with it's test 1874 // case above. 1875 func (b TestAuditLoggerCustomConfigBuilder) ParseLoggerConfig(configJSON json.RawMessage) (audit.LoggerConfig, error) { 1876 c := TestAuditLoggerCustomConfigConfig{} 1877 err := json.Unmarshal(configJSON, &c) 1878 if err != nil { 1879 return nil, fmt.Errorf("could not parse custom config: %v", err) 1880 } 1881 return c, nil 1882 } 1883 1884 func (b *TestAuditLoggerCustomConfigBuilder) Build(config audit.LoggerConfig) audit.Logger { 1885 return &TestAuditLoggerCustomConfig{} 1886 } 1887 1888 func (b *TestAuditLoggerCustomConfigBuilder) Name() string { 1889 return b.testName + "_TestAuditLoggerCustomConfig" 1890 } 1891 1892 // Builds custom configs for audit logger RBAC protos. 1893 func createUDPATypedStruct(t *testing.T, in map[string]any, name string) *anypb.Any { 1894 t.Helper() 1895 pb, err := structpb.NewStruct(in) 1896 if err != nil { 1897 t.Fatalf("createUDPATypedStructFailed during structpb.NewStruct: %v", err) 1898 } 1899 typedURL := "" 1900 if name != "" { 1901 typedURL = typeURLPrefix + name 1902 } 1903 typedStruct := &v1xdsudpatypepb.TypedStruct{ 1904 TypeUrl: typedURL, 1905 Value: pb, 1906 } 1907 customConfig, err := anypb.New(typedStruct) 1908 if err != nil { 1909 t.Fatalf("createUDPATypedStructFailed during anypb.New: %v", err) 1910 } 1911 return customConfig 1912 } 1913 1914 // Builds custom configs for audit logger RBAC protos. 1915 func createXDSTypedStruct(t *testing.T, in map[string]any, name string) *anypb.Any { 1916 t.Helper() 1917 pb, err := structpb.NewStruct(in) 1918 if err != nil { 1919 t.Fatalf("createXDSTypedStructFailed during structpb.NewStruct: %v", err) 1920 } 1921 typedStruct := &v3xdsxdstypepb.TypedStruct{ 1922 TypeUrl: typeURLPrefix + name, 1923 Value: pb, 1924 } 1925 customConfig, err := anypb.New(typedStruct) 1926 if err != nil { 1927 t.Fatalf("createXDSTypedStructFailed during anypb.New: %v", err) 1928 } 1929 return customConfig 1930 }