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