1
18
19 package xds_test
20
21 import (
22 "context"
23 "encoding/json"
24 "fmt"
25 "net"
26 "strconv"
27 "strings"
28 "testing"
29
30 v3xdsxdstypepb "github.com/cncf/xds/go/xds/type/v3"
31 v3routerpb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3"
32 "github.com/google/go-cmp/cmp"
33 "google.golang.org/grpc"
34 "google.golang.org/grpc/authz/audit"
35 "google.golang.org/grpc/codes"
36 "google.golang.org/grpc/credentials/insecure"
37 "google.golang.org/grpc/internal"
38 "google.golang.org/grpc/internal/testutils"
39 "google.golang.org/grpc/internal/testutils/xds/e2e"
40 "google.golang.org/grpc/status"
41 "google.golang.org/protobuf/types/known/anypb"
42 "google.golang.org/protobuf/types/known/structpb"
43 "google.golang.org/protobuf/types/known/wrapperspb"
44
45 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
46 v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
47 v3rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3"
48 v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
49 rpb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/rbac/v3"
50 v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
51 v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
52 testgrpc "google.golang.org/grpc/interop/grpc_testing"
53 testpb "google.golang.org/grpc/interop/grpc_testing"
54 )
55
56
57
58
59
60
61 func (s) TestServerSideXDS_RouteConfiguration(t *testing.T) {
62 managementServer, nodeID, bootstrapContents, resolver, cleanup1 := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{})
63 defer cleanup1()
64
65 lis, cleanup2 := setupGRPCServer(t, bootstrapContents)
66 defer cleanup2()
67
68 host, port, err := hostPortFromListener(lis)
69 if err != nil {
70 t.Fatalf("failed to retrieve host and port of server: %v", err)
71 }
72 const serviceName = "my-service-fallback"
73 resources := e2e.DefaultClientResources(e2e.ResourceParams{
74 DialTarget: serviceName,
75 NodeID: nodeID,
76 Host: host,
77 Port: port,
78 SecLevel: e2e.SecurityLevelNone,
79 })
80
81
82
83
84 vhs := []*v3routepb.VirtualHost{
85
86 {
87 Domains: []string{"this will not match*"},
88 Routes: []*v3routepb.Route{
89 {
90 Match: &v3routepb.RouteMatch{
91 PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
92 },
93 Action: &v3routepb.Route_NonForwardingAction{},
94 },
95 },
96 },
97
98 {
99 Domains: []string{"*"},
100 Routes: []*v3routepb.Route{
101
102 {
103 Match: &v3routepb.RouteMatch{
104 PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/grpc.testing.TestService/EmptyCall"},
105
106 },
107
108 Action: &v3routepb.Route_NonForwardingAction{},
109 },
110
111
112
113
114
115 {
116 Match: &v3routepb.RouteMatch{
117 PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/grpc.testing.TestService/EmptyCall"},
118
119 },
120
121 Action: &v3routepb.Route_Route{
122 Route: &v3routepb.RouteAction{ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: ""}},
123 },
124 },
125
126 {
127 Match: &v3routepb.RouteMatch{
128 PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/grpc.testing.TestService/UnaryCall"},
129 },
130
131 Action: &v3routepb.Route_Route{
132 Route: &v3routepb.RouteAction{ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: ""}},
133 },
134 },
135
136 {
137 Match: &v3routepb.RouteMatch{
138 PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/grpc.testing.TestService/StreamingInputCall"},
139 },
140
141 Action: &v3routepb.Route_Route{
142 Route: &v3routepb.RouteAction{ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: ""}},
143 },
144 },
145
146 }},
147 }
148 inboundLis := &v3listenerpb.Listener{
149 Name: fmt.Sprintf(e2e.ServerListenerResourceNameTemplate, net.JoinHostPort(host, strconv.Itoa(int(port)))),
150 Address: &v3corepb.Address{
151 Address: &v3corepb.Address_SocketAddress{
152 SocketAddress: &v3corepb.SocketAddress{
153 Address: host,
154 PortSpecifier: &v3corepb.SocketAddress_PortValue{
155 PortValue: port,
156 },
157 },
158 },
159 },
160 FilterChains: []*v3listenerpb.FilterChain{
161 {
162 Name: "v4-wildcard",
163 FilterChainMatch: &v3listenerpb.FilterChainMatch{
164 PrefixRanges: []*v3corepb.CidrRange{
165 {
166 AddressPrefix: "0.0.0.0",
167 PrefixLen: &wrapperspb.UInt32Value{
168 Value: uint32(0),
169 },
170 },
171 },
172 SourceType: v3listenerpb.FilterChainMatch_SAME_IP_OR_LOOPBACK,
173 SourcePrefixRanges: []*v3corepb.CidrRange{
174 {
175 AddressPrefix: "0.0.0.0",
176 PrefixLen: &wrapperspb.UInt32Value{
177 Value: uint32(0),
178 },
179 },
180 },
181 },
182 Filters: []*v3listenerpb.Filter{
183 {
184 Name: "filter-1",
185 ConfigType: &v3listenerpb.Filter_TypedConfig{
186 TypedConfig: testutils.MarshalAny(t, &v3httppb.HttpConnectionManager{
187 HttpFilters: []*v3httppb.HttpFilter{e2e.HTTPFilter("router", &v3routerpb.Router{})},
188 RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{
189 RouteConfig: &v3routepb.RouteConfiguration{
190 Name: "routeName",
191 VirtualHosts: vhs,
192 },
193 },
194 }),
195 },
196 },
197 },
198 },
199 {
200 Name: "v6-wildcard",
201 FilterChainMatch: &v3listenerpb.FilterChainMatch{
202 PrefixRanges: []*v3corepb.CidrRange{
203 {
204 AddressPrefix: "::",
205 PrefixLen: &wrapperspb.UInt32Value{
206 Value: uint32(0),
207 },
208 },
209 },
210 SourceType: v3listenerpb.FilterChainMatch_SAME_IP_OR_LOOPBACK,
211 SourcePrefixRanges: []*v3corepb.CidrRange{
212 {
213 AddressPrefix: "::",
214 PrefixLen: &wrapperspb.UInt32Value{
215 Value: uint32(0),
216 },
217 },
218 },
219 },
220 Filters: []*v3listenerpb.Filter{
221 {
222 Name: "filter-1",
223 ConfigType: &v3listenerpb.Filter_TypedConfig{
224 TypedConfig: testutils.MarshalAny(t, &v3httppb.HttpConnectionManager{
225 HttpFilters: []*v3httppb.HttpFilter{e2e.HTTPFilter("router", &v3routerpb.Router{})},
226 RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{
227 RouteConfig: &v3routepb.RouteConfiguration{
228 Name: "routeName",
229 VirtualHosts: vhs,
230 },
231 },
232 }),
233 },
234 },
235 },
236 },
237 },
238 }
239 resources.Listeners = append(resources.Listeners, inboundLis)
240
241 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
242 defer cancel()
243
244 if err := managementServer.Update(ctx, resources); err != nil {
245 t.Fatal(err)
246 }
247
248 cc, err := grpc.DialContext(ctx, fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(resolver))
249 if err != nil {
250 t.Fatalf("failed to dial local test server: %v", err)
251 }
252 defer cc.Close()
253
254 client := testgrpc.NewTestServiceClient(cc)
255
256
257
258
259
260
261 if _, err = client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil {
262 t.Fatalf("rpc EmptyCall() failed: %v", err)
263 }
264
265
266
267
268 if _, err = client.UnaryCall(ctx, &testpb.SimpleRequest{}); status.Code(err) != codes.Unavailable {
269 t.Fatalf("client.UnaryCall() = _, %v, want _, error code %s", err, codes.Unavailable)
270 }
271
272
273
274
275 stream, err := client.StreamingInputCall(ctx)
276 if err != nil {
277 t.Fatalf("StreamingInputCall(_) = _, %v, want <nil>", err)
278 }
279 if _, err = stream.CloseAndRecv(); status.Code(err) != codes.Unavailable || !strings.Contains(err.Error(), "the incoming RPC matched to a route that was not of action type non forwarding") {
280 t.Fatalf("streaming RPC should have been denied")
281 }
282
283
284
285 dStream, err := client.FullDuplexCall(ctx)
286 if err != nil {
287 t.Fatalf("FullDuplexCall(_) = _, %v, want <nil>", err)
288 }
289 if _, err = dStream.Recv(); status.Code(err) != codes.Unavailable || !strings.Contains(err.Error(), "the incoming RPC did not match a configured Route") {
290 t.Fatalf("streaming RPC should have been denied")
291 }
292 }
293
294
295
296 func serverListenerWithRBACHTTPFilters(t *testing.T, host string, port uint32, rbacCfg *rpb.RBAC) *v3listenerpb.Listener {
297
298
299 hcm := &v3httppb.HttpConnectionManager{
300 RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{
301 RouteConfig: &v3routepb.RouteConfiguration{
302 Name: "routeName",
303 VirtualHosts: []*v3routepb.VirtualHost{{
304 Domains: []string{"*"},
305 Routes: []*v3routepb.Route{{
306 Match: &v3routepb.RouteMatch{
307 PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
308 },
309 Action: &v3routepb.Route_NonForwardingAction{},
310 }},
311
312
313 TypedPerFilterConfig: map[string]*anypb.Any{
314 "rbac": testutils.MarshalAny(t, &rpb.RBACPerRoute{Rbac: rbacCfg}),
315 },
316 }}},
317 },
318 }
319 hcm.HttpFilters = nil
320 hcm.HttpFilters = append(hcm.HttpFilters, e2e.HTTPFilter("rbac", rbacCfg))
321 hcm.HttpFilters = append(hcm.HttpFilters, e2e.RouterHTTPFilter)
322
323 return &v3listenerpb.Listener{
324 Name: fmt.Sprintf(e2e.ServerListenerResourceNameTemplate, net.JoinHostPort(host, strconv.Itoa(int(port)))),
325 Address: &v3corepb.Address{
326 Address: &v3corepb.Address_SocketAddress{
327 SocketAddress: &v3corepb.SocketAddress{
328 Address: host,
329 PortSpecifier: &v3corepb.SocketAddress_PortValue{
330 PortValue: port,
331 },
332 },
333 },
334 },
335 FilterChains: []*v3listenerpb.FilterChain{
336 {
337 Name: "v4-wildcard",
338 FilterChainMatch: &v3listenerpb.FilterChainMatch{
339 PrefixRanges: []*v3corepb.CidrRange{
340 {
341 AddressPrefix: "0.0.0.0",
342 PrefixLen: &wrapperspb.UInt32Value{
343 Value: uint32(0),
344 },
345 },
346 },
347 SourceType: v3listenerpb.FilterChainMatch_SAME_IP_OR_LOOPBACK,
348 SourcePrefixRanges: []*v3corepb.CidrRange{
349 {
350 AddressPrefix: "0.0.0.0",
351 PrefixLen: &wrapperspb.UInt32Value{
352 Value: uint32(0),
353 },
354 },
355 },
356 },
357 Filters: []*v3listenerpb.Filter{
358 {
359 Name: "filter-1",
360 ConfigType: &v3listenerpb.Filter_TypedConfig{
361 TypedConfig: testutils.MarshalAny(t, hcm),
362 },
363 },
364 },
365 },
366 {
367 Name: "v6-wildcard",
368 FilterChainMatch: &v3listenerpb.FilterChainMatch{
369 PrefixRanges: []*v3corepb.CidrRange{
370 {
371 AddressPrefix: "::",
372 PrefixLen: &wrapperspb.UInt32Value{
373 Value: uint32(0),
374 },
375 },
376 },
377 SourceType: v3listenerpb.FilterChainMatch_SAME_IP_OR_LOOPBACK,
378 SourcePrefixRanges: []*v3corepb.CidrRange{
379 {
380 AddressPrefix: "::",
381 PrefixLen: &wrapperspb.UInt32Value{
382 Value: uint32(0),
383 },
384 },
385 },
386 },
387 Filters: []*v3listenerpb.Filter{
388 {
389 Name: "filter-1",
390 ConfigType: &v3listenerpb.Filter_TypedConfig{
391 TypedConfig: testutils.MarshalAny(t, hcm),
392 },
393 },
394 },
395 },
396 },
397 }
398 }
399
400
401
402
403
404 func (s) TestRBACHTTPFilter(t *testing.T) {
405 internal.RegisterRBACHTTPFilterForTesting()
406 defer internal.UnregisterRBACHTTPFilterForTesting()
407 tests := []struct {
408 name string
409 rbacCfg *rpb.RBAC
410 wantStatusEmptyCall codes.Code
411 wantStatusUnaryCall codes.Code
412 wantAuthzOutcomes map[bool]int
413 eventContent *audit.Event
414 }{
415
416
417 {
418 name: "allow-anything",
419 rbacCfg: &rpb.RBAC{
420 Rules: &v3rbacpb.RBAC{
421 Action: v3rbacpb.RBAC_ALLOW,
422 Policies: map[string]*v3rbacpb.Policy{
423 "anyone": {
424 Permissions: []*v3rbacpb.Permission{
425 {Rule: &v3rbacpb.Permission_Any{Any: true}},
426 },
427 Principals: []*v3rbacpb.Principal{
428 {Identifier: &v3rbacpb.Principal_Any{Any: true}},
429 },
430 },
431 },
432 AuditLoggingOptions: &v3rbacpb.RBAC_AuditLoggingOptions{
433 AuditCondition: v3rbacpb.RBAC_AuditLoggingOptions_ON_ALLOW,
434 LoggerConfigs: []*v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{
435 {
436 AuditLogger: &v3corepb.TypedExtensionConfig{
437 Name: "stat_logger",
438 TypedConfig: createXDSTypedStruct(t, map[string]any{}, "stat_logger"),
439 },
440 IsOptional: false,
441 },
442 },
443 },
444 },
445 },
446 wantStatusEmptyCall: codes.OK,
447 wantStatusUnaryCall: codes.OK,
448 wantAuthzOutcomes: map[bool]int{true: 2, false: 0},
449
450
451 eventContent: &audit.Event{
452 FullMethodName: "/grpc.testing.TestService/UnaryCall",
453 MatchedRule: "anyone",
454 Authorized: true,
455 },
456 },
457
458
459
460
461 {
462 name: "allow-certain-path",
463 rbacCfg: &rpb.RBAC{
464 Rules: &v3rbacpb.RBAC{
465 Action: v3rbacpb.RBAC_ALLOW,
466 Policies: map[string]*v3rbacpb.Policy{
467 "certain-path": {
468 Permissions: []*v3rbacpb.Permission{
469 {Rule: &v3rbacpb.Permission_UrlPath{UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "/grpc.testing.TestService/UnaryCall"}}}}}},
470 },
471 Principals: []*v3rbacpb.Principal{
472 {Identifier: &v3rbacpb.Principal_Any{Any: true}},
473 },
474 },
475 },
476 },
477 },
478 wantStatusEmptyCall: codes.PermissionDenied,
479 wantStatusUnaryCall: codes.OK,
480 },
481
482
483
484
485 {
486 name: "absent-rules",
487 rbacCfg: &rpb.RBAC{
488 Rules: nil,
489 },
490 wantStatusEmptyCall: codes.OK,
491 wantStatusUnaryCall: codes.OK,
492 },
493
494
495
496
497
498
499
500
501
502
503 {
504 name: "match-on-authority",
505 rbacCfg: &rpb.RBAC{
506 Rules: &v3rbacpb.RBAC{
507 Action: v3rbacpb.RBAC_ALLOW,
508 Policies: map[string]*v3rbacpb.Policy{
509 "match-on-authority": {
510 Permissions: []*v3rbacpb.Permission{
511 {Rule: &v3rbacpb.Permission_Header{Header: &v3routepb.HeaderMatcher{Name: ":authority", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_PrefixMatch{PrefixMatch: "my-service-fallback"}}}},
512 },
513 Principals: []*v3rbacpb.Principal{
514 {Identifier: &v3rbacpb.Principal_Any{Any: true}},
515 },
516 },
517 },
518 },
519 },
520 wantStatusEmptyCall: codes.OK,
521 wantStatusUnaryCall: codes.OK,
522 },
523
524
525
526
527
528 {
529 name: "match-on-host",
530 rbacCfg: &rpb.RBAC{
531 Rules: &v3rbacpb.RBAC{
532 Action: v3rbacpb.RBAC_ALLOW,
533 Policies: map[string]*v3rbacpb.Policy{
534 "match-on-authority": {
535 Permissions: []*v3rbacpb.Permission{
536 {Rule: &v3rbacpb.Permission_Header{Header: &v3routepb.HeaderMatcher{Name: "host", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_PrefixMatch{PrefixMatch: "my-service-fallback"}}}},
537 },
538 Principals: []*v3rbacpb.Principal{
539 {Identifier: &v3rbacpb.Principal_Any{Any: true}},
540 },
541 },
542 },
543 },
544 },
545 wantStatusEmptyCall: codes.OK,
546 wantStatusUnaryCall: codes.OK,
547 },
548
549
550
551 {
552 name: "deny-post",
553 rbacCfg: &rpb.RBAC{
554 Rules: &v3rbacpb.RBAC{
555 Action: v3rbacpb.RBAC_DENY,
556 Policies: map[string]*v3rbacpb.Policy{
557 "post-method": {
558 Permissions: []*v3rbacpb.Permission{
559 {Rule: &v3rbacpb.Permission_Header{Header: &v3routepb.HeaderMatcher{Name: ":method", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_ExactMatch{ExactMatch: "POST"}}}},
560 },
561 Principals: []*v3rbacpb.Principal{
562 {Identifier: &v3rbacpb.Principal_Any{Any: true}},
563 },
564 },
565 },
566 },
567 },
568 wantStatusEmptyCall: codes.PermissionDenied,
569 wantStatusUnaryCall: codes.PermissionDenied,
570 },
571
572
573
574
575 {
576 name: "allow-only-te",
577 rbacCfg: &rpb.RBAC{
578 Rules: &v3rbacpb.RBAC{
579 Action: v3rbacpb.RBAC_ALLOW,
580 Policies: map[string]*v3rbacpb.Policy{
581 "post-method": {
582 Permissions: []*v3rbacpb.Permission{
583 {Rule: &v3rbacpb.Permission_Header{Header: &v3routepb.HeaderMatcher{Name: "TE", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_ExactMatch{ExactMatch: "trailers"}}}},
584 },
585 Principals: []*v3rbacpb.Principal{
586 {Identifier: &v3rbacpb.Principal_Any{Any: true}},
587 },
588 },
589 },
590 },
591 },
592 wantStatusEmptyCall: codes.PermissionDenied,
593 wantStatusUnaryCall: codes.PermissionDenied,
594 },
595
596
597
598
599 {
600 name: "action-log",
601 rbacCfg: &rpb.RBAC{
602 Rules: &v3rbacpb.RBAC{
603 Action: v3rbacpb.RBAC_LOG,
604 Policies: map[string]*v3rbacpb.Policy{
605 "anyone": {
606 Permissions: []*v3rbacpb.Permission{
607 {Rule: &v3rbacpb.Permission_Any{Any: true}},
608 },
609 Principals: []*v3rbacpb.Principal{
610 {Identifier: &v3rbacpb.Principal_Any{Any: true}},
611 },
612 },
613 },
614 },
615 },
616 wantStatusEmptyCall: codes.OK,
617 wantStatusUnaryCall: codes.OK,
618 },
619 }
620
621 for _, test := range tests {
622 t.Run(test.name, func(t *testing.T) {
623 func() {
624 lb := &loggerBuilder{
625 authzDecisionStat: map[bool]int{true: 0, false: 0},
626 lastEvent: &audit.Event{},
627 }
628 audit.RegisterLoggerBuilder(lb)
629
630 managementServer, nodeID, bootstrapContents, resolver, cleanup1 := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{})
631 defer cleanup1()
632
633 lis, cleanup2 := setupGRPCServer(t, bootstrapContents)
634 defer cleanup2()
635
636 host, port, err := hostPortFromListener(lis)
637 if err != nil {
638 t.Fatalf("failed to retrieve host and port of server: %v", err)
639 }
640 const serviceName = "my-service-fallback"
641 resources := e2e.DefaultClientResources(e2e.ResourceParams{
642 DialTarget: serviceName,
643 NodeID: nodeID,
644 Host: host,
645 Port: port,
646 SecLevel: e2e.SecurityLevelNone,
647 })
648 inboundLis := serverListenerWithRBACHTTPFilters(t, host, port, test.rbacCfg)
649 resources.Listeners = append(resources.Listeners, inboundLis)
650
651 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
652 defer cancel()
653
654 if err := managementServer.Update(ctx, resources); err != nil {
655 t.Fatal(err)
656 }
657
658 cc, err := grpc.DialContext(ctx, fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(resolver))
659 if err != nil {
660 t.Fatalf("failed to dial local test server: %v", err)
661 }
662 defer cc.Close()
663
664 client := testgrpc.NewTestServiceClient(cc)
665
666 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); status.Code(err) != test.wantStatusEmptyCall {
667 t.Fatalf("EmptyCall() returned err with status: %v, wantStatusEmptyCall: %v", status.Code(err), test.wantStatusEmptyCall)
668 }
669
670 if _, err := client.UnaryCall(ctx, &testpb.SimpleRequest{}); status.Code(err) != test.wantStatusUnaryCall {
671 t.Fatalf("UnaryCall() returned err with status: %v, wantStatusUnaryCall: %v", err, test.wantStatusUnaryCall)
672 }
673
674 if test.wantAuthzOutcomes != nil {
675 if diff := cmp.Diff(lb.authzDecisionStat, test.wantAuthzOutcomes); diff != "" {
676 t.Fatalf("authorization decision do not match\ndiff (-got +want):\n%s", diff)
677 }
678 }
679 if test.eventContent != nil {
680 if diff := cmp.Diff(lb.lastEvent, test.eventContent); diff != "" {
681 t.Fatalf("unexpected event\ndiff (-got +want):\n%s", diff)
682 }
683 }
684 }()
685 })
686 }
687 }
688
689
690
691
692 func serverListenerWithBadRouteConfiguration(t *testing.T, host string, port uint32) *v3listenerpb.Listener {
693 return &v3listenerpb.Listener{
694 Name: fmt.Sprintf(e2e.ServerListenerResourceNameTemplate, net.JoinHostPort(host, strconv.Itoa(int(port)))),
695 Address: &v3corepb.Address{
696 Address: &v3corepb.Address_SocketAddress{
697 SocketAddress: &v3corepb.SocketAddress{
698 Address: host,
699 PortSpecifier: &v3corepb.SocketAddress_PortValue{
700 PortValue: port,
701 },
702 },
703 },
704 },
705 FilterChains: []*v3listenerpb.FilterChain{
706 {
707 Name: "v4-wildcard",
708 FilterChainMatch: &v3listenerpb.FilterChainMatch{
709 PrefixRanges: []*v3corepb.CidrRange{
710 {
711 AddressPrefix: "0.0.0.0",
712 PrefixLen: &wrapperspb.UInt32Value{
713 Value: uint32(0),
714 },
715 },
716 },
717 SourceType: v3listenerpb.FilterChainMatch_SAME_IP_OR_LOOPBACK,
718 SourcePrefixRanges: []*v3corepb.CidrRange{
719 {
720 AddressPrefix: "0.0.0.0",
721 PrefixLen: &wrapperspb.UInt32Value{
722 Value: uint32(0),
723 },
724 },
725 },
726 },
727 Filters: []*v3listenerpb.Filter{
728 {
729 Name: "filter-1",
730 ConfigType: &v3listenerpb.Filter_TypedConfig{
731 TypedConfig: testutils.MarshalAny(t, &v3httppb.HttpConnectionManager{
732 RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{
733 RouteConfig: &v3routepb.RouteConfiguration{
734 Name: "routeName",
735 VirtualHosts: []*v3routepb.VirtualHost{{
736
737
738
739
740 Domains: []string{"will-never-match"},
741 Routes: []*v3routepb.Route{{
742 Match: &v3routepb.RouteMatch{
743 PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
744 },
745 Action: &v3routepb.Route_NonForwardingAction{},
746 }}}}},
747 },
748 HttpFilters: []*v3httppb.HttpFilter{e2e.RouterHTTPFilter},
749 }),
750 },
751 },
752 },
753 },
754 {
755 Name: "v6-wildcard",
756 FilterChainMatch: &v3listenerpb.FilterChainMatch{
757 PrefixRanges: []*v3corepb.CidrRange{
758 {
759 AddressPrefix: "::",
760 PrefixLen: &wrapperspb.UInt32Value{
761 Value: uint32(0),
762 },
763 },
764 },
765 SourceType: v3listenerpb.FilterChainMatch_SAME_IP_OR_LOOPBACK,
766 SourcePrefixRanges: []*v3corepb.CidrRange{
767 {
768 AddressPrefix: "::",
769 PrefixLen: &wrapperspb.UInt32Value{
770 Value: uint32(0),
771 },
772 },
773 },
774 },
775 Filters: []*v3listenerpb.Filter{
776 {
777 Name: "filter-1",
778 ConfigType: &v3listenerpb.Filter_TypedConfig{
779 TypedConfig: testutils.MarshalAny(t, &v3httppb.HttpConnectionManager{
780 RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{
781 RouteConfig: &v3routepb.RouteConfiguration{
782 Name: "routeName",
783 VirtualHosts: []*v3routepb.VirtualHost{{
784
785
786
787
788 Domains: []string{"will-never-match"},
789 Routes: []*v3routepb.Route{{
790 Match: &v3routepb.RouteMatch{
791 PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
792 },
793 Action: &v3routepb.Route_NonForwardingAction{},
794 }}}}},
795 },
796 HttpFilters: []*v3httppb.HttpFilter{e2e.RouterHTTPFilter},
797 }),
798 },
799 },
800 },
801 },
802 },
803 }
804 }
805
806 func (s) TestRBACToggledOn_WithBadRouteConfiguration(t *testing.T) {
807 managementServer, nodeID, bootstrapContents, resolver, cleanup1 := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{})
808 defer cleanup1()
809
810 lis, cleanup2 := setupGRPCServer(t, bootstrapContents)
811 defer cleanup2()
812
813 host, port, err := hostPortFromListener(lis)
814 if err != nil {
815 t.Fatalf("failed to retrieve host and port of server: %v", err)
816 }
817 const serviceName = "my-service-fallback"
818
819
820
821 resources := e2e.DefaultClientResources(e2e.ResourceParams{
822 DialTarget: serviceName,
823 NodeID: nodeID,
824 Host: host,
825 Port: port,
826 SecLevel: e2e.SecurityLevelNone,
827 })
828
829
830
831 inboundLis := serverListenerWithBadRouteConfiguration(t, host, port)
832 resources.Listeners = append(resources.Listeners, inboundLis)
833
834 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
835 defer cancel()
836
837 if err := managementServer.Update(ctx, resources); err != nil {
838 t.Fatal(err)
839 }
840
841 cc, err := grpc.DialContext(ctx, fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(resolver))
842 if err != nil {
843 t.Fatalf("failed to dial local test server: %v", err)
844 }
845 defer cc.Close()
846
847 client := testgrpc.NewTestServiceClient(cc)
848 if _, err := client.EmptyCall(ctx, &testpb.Empty{}); status.Code(err) != codes.Unavailable {
849 t.Fatalf("EmptyCall() returned err with status: %v, if RBAC is disabled all RPC's should proceed as normal", status.Code(err))
850 }
851 if _, err := client.UnaryCall(ctx, &testpb.SimpleRequest{}); status.Code(err) != codes.Unavailable {
852 t.Fatalf("UnaryCall() returned err with status: %v, if RBAC is disabled all RPC's should proceed as normal", status.Code(err))
853 }
854 }
855
856 type statAuditLogger struct {
857 authzDecisionStat map[bool]int
858 lastEvent *audit.Event
859 }
860
861 func (s *statAuditLogger) Log(event *audit.Event) {
862 s.authzDecisionStat[event.Authorized]++
863 *s.lastEvent = *event
864 }
865
866 type loggerBuilder struct {
867 authzDecisionStat map[bool]int
868 lastEvent *audit.Event
869 }
870
871 func (loggerBuilder) Name() string {
872 return "stat_logger"
873 }
874
875 func (lb *loggerBuilder) Build(audit.LoggerConfig) audit.Logger {
876 return &statAuditLogger{
877 authzDecisionStat: lb.authzDecisionStat,
878 lastEvent: lb.lastEvent,
879 }
880 }
881
882 func (*loggerBuilder) ParseLoggerConfig(config json.RawMessage) (audit.LoggerConfig, error) {
883 return nil, nil
884 }
885
886
887
888 const typeURLPrefix = "grpc.authz.audit_logging/"
889
890
891 func createXDSTypedStruct(t *testing.T, in map[string]any, name string) *anypb.Any {
892 t.Helper()
893 pb, err := structpb.NewStruct(in)
894 if err != nil {
895 t.Fatalf("createXDSTypedStruct failed during structpb.NewStruct: %v", err)
896 }
897 typedStruct := &v3xdsxdstypepb.TypedStruct{
898 TypeUrl: typeURLPrefix + name,
899 Value: pb,
900 }
901 customConfig, err := anypb.New(typedStruct)
902 if err != nil {
903 t.Fatalf("createXDSTypedStruct failed during anypb.New: %v", err)
904 }
905 return customConfig
906 }
907
View as plain text