Skip to content

Commit 12a377b

Browse files
authored
xds: nack route configuration with regexes that don't compile (#4388)
1 parent c15291b commit 12a377b

File tree

4 files changed

+128
-40
lines changed

4 files changed

+128
-40
lines changed

xds/internal/client/client.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"context"
2525
"errors"
2626
"fmt"
27+
"regexp"
2728
"sync"
2829
"time"
2930

@@ -271,7 +272,9 @@ type VirtualHost struct {
271272
// Route is both a specification of how to match a request as well as an
272273
// indication of the action to take upon match.
273274
type Route struct {
274-
Path, Prefix, Regex *string
275+
Path *string
276+
Prefix *string
277+
Regex *regexp.Regexp
275278
// Indicates if prefix/path matching should be case insensitive. The default
276279
// is false (case sensitive).
277280
CaseInsensitive bool
@@ -304,20 +307,20 @@ type WeightedCluster struct {
304307

305308
// HeaderMatcher represents header matchers.
306309
type HeaderMatcher struct {
307-
Name string `json:"name"`
308-
InvertMatch *bool `json:"invertMatch,omitempty"`
309-
ExactMatch *string `json:"exactMatch,omitempty"`
310-
RegexMatch *string `json:"regexMatch,omitempty"`
311-
PrefixMatch *string `json:"prefixMatch,omitempty"`
312-
SuffixMatch *string `json:"suffixMatch,omitempty"`
313-
RangeMatch *Int64Range `json:"rangeMatch,omitempty"`
314-
PresentMatch *bool `json:"presentMatch,omitempty"`
310+
Name string
311+
InvertMatch *bool
312+
ExactMatch *string
313+
RegexMatch *regexp.Regexp
314+
PrefixMatch *string
315+
SuffixMatch *string
316+
RangeMatch *Int64Range
317+
PresentMatch *bool
315318
}
316319

317320
// Int64Range is a range for header range match.
318321
type Int64Range struct {
319-
Start int64 `json:"start"`
320-
End int64 `json:"end"`
322+
Start int64
323+
End int64
321324
}
322325

323326
// SecurityConfig contains the security configuration received as part of the

xds/internal/client/rds_test.go

Lines changed: 98 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,26 @@ package client
2222

2323
import (
2424
"fmt"
25+
"regexp"
2526
"testing"
2627
"time"
2728

28-
v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
29-
v2routepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/route"
30-
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
31-
v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
32-
v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3"
3329
"github.com/golang/protobuf/proto"
34-
anypb "github.com/golang/protobuf/ptypes/any"
35-
wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
3630
"github.com/google/go-cmp/cmp"
3731
"github.com/google/go-cmp/cmp/cmpopts"
38-
3932
"google.golang.org/grpc/internal/xds/env"
4033
"google.golang.org/grpc/xds/internal/httpfilter"
4134
"google.golang.org/grpc/xds/internal/version"
4235
"google.golang.org/protobuf/types/known/durationpb"
36+
37+
v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
38+
v2routepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/route"
39+
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
40+
v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
41+
v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
42+
v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3"
43+
anypb "github.com/golang/protobuf/ptypes/any"
44+
wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
4345
)
4446

4547
func (s) TestRDSGenerateRDSUpdateFromRouteConfiguration(t *testing.T) {
@@ -915,6 +917,51 @@ func (s) TestRoutesProtoToSlice(t *testing.T) {
915917
}},
916918
wantErr: false,
917919
},
920+
{
921+
name: "good with regex matchers",
922+
routes: []*v3routepb.Route{
923+
{
924+
Match: &v3routepb.RouteMatch{
925+
PathSpecifier: &v3routepb.RouteMatch_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: "/a/"}},
926+
Headers: []*v3routepb.HeaderMatcher{
927+
{
928+
Name: "th",
929+
HeaderMatchSpecifier: &v3routepb.HeaderMatcher_SafeRegexMatch{SafeRegexMatch: &v3matcherpb.RegexMatcher{Regex: "tv"}},
930+
},
931+
},
932+
RuntimeFraction: &v3corepb.RuntimeFractionalPercent{
933+
DefaultValue: &v3typepb.FractionalPercent{
934+
Numerator: 1,
935+
Denominator: v3typepb.FractionalPercent_HUNDRED,
936+
},
937+
},
938+
},
939+
Action: &v3routepb.Route_Route{
940+
Route: &v3routepb.RouteAction{
941+
ClusterSpecifier: &v3routepb.RouteAction_WeightedClusters{
942+
WeightedClusters: &v3routepb.WeightedCluster{
943+
Clusters: []*v3routepb.WeightedCluster_ClusterWeight{
944+
{Name: "B", Weight: &wrapperspb.UInt32Value{Value: 60}},
945+
{Name: "A", Weight: &wrapperspb.UInt32Value{Value: 40}},
946+
},
947+
TotalWeight: &wrapperspb.UInt32Value{Value: 100},
948+
}}}},
949+
},
950+
},
951+
wantRoutes: []*Route{{
952+
Regex: func() *regexp.Regexp { return regexp.MustCompile("/a/") }(),
953+
Headers: []*HeaderMatcher{
954+
{
955+
Name: "th",
956+
InvertMatch: newBoolP(false),
957+
RegexMatch: func() *regexp.Regexp { return regexp.MustCompile("tv") }(),
958+
},
959+
},
960+
Fraction: newUInt32P(10000),
961+
WeightedClusters: map[string]WeightedCluster{"A": {Weight: 40}, "B": {Weight: 60}},
962+
}},
963+
wantErr: false,
964+
},
918965
{
919966
name: "query is ignored",
920967
routes: []*v3routepb.Route{
@@ -960,6 +1007,44 @@ func (s) TestRoutesProtoToSlice(t *testing.T) {
9601007
},
9611008
wantErr: true,
9621009
},
1010+
{
1011+
name: "bad regex in path specifier",
1012+
routes: []*v3routepb.Route{
1013+
{
1014+
Match: &v3routepb.RouteMatch{
1015+
PathSpecifier: &v3routepb.RouteMatch_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: "??"}},
1016+
Headers: []*v3routepb.HeaderMatcher{
1017+
{
1018+
HeaderMatchSpecifier: &v3routepb.HeaderMatcher_PrefixMatch{PrefixMatch: "tv"},
1019+
},
1020+
},
1021+
},
1022+
Action: &v3routepb.Route_Route{
1023+
Route: &v3routepb.RouteAction{ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName}},
1024+
},
1025+
},
1026+
},
1027+
wantErr: true,
1028+
},
1029+
{
1030+
name: "bad regex in header specifier",
1031+
routes: []*v3routepb.Route{
1032+
{
1033+
Match: &v3routepb.RouteMatch{
1034+
PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/a/"},
1035+
Headers: []*v3routepb.HeaderMatcher{
1036+
{
1037+
HeaderMatchSpecifier: &v3routepb.HeaderMatcher_SafeRegexMatch{SafeRegexMatch: &v3matcherpb.RegexMatcher{Regex: "??"}},
1038+
},
1039+
},
1040+
},
1041+
Action: &v3routepb.Route_Route{
1042+
Route: &v3routepb.RouteAction{ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName}},
1043+
},
1044+
},
1045+
},
1046+
wantErr: true,
1047+
},
9631048
{
9641049
name: "unrecognized header match specifier",
9651050
routes: []*v3routepb.Route{
@@ -1063,7 +1148,7 @@ func (s) TestRoutesProtoToSlice(t *testing.T) {
10631148
}
10641149

10651150
cmpOpts := []cmp.Option{
1066-
cmp.AllowUnexported(Route{}, HeaderMatcher{}, Int64Range{}),
1151+
cmp.AllowUnexported(Route{}, HeaderMatcher{}, Int64Range{}, regexp.Regexp{}),
10671152
cmpopts.EquateEmpty(),
10681153
cmp.Transformer("FilterConfig", func(fc httpfilter.FilterConfig) string {
10691154
return fmt.Sprint(fc)
@@ -1074,17 +1159,15 @@ func (s) TestRoutesProtoToSlice(t *testing.T) {
10741159
t.Run(tt.name, func(t *testing.T) {
10751160
oldFI := env.FaultInjectionSupport
10761161
env.FaultInjectionSupport = !tt.disableFI
1162+
defer func() { env.FaultInjectionSupport = oldFI }()
10771163

10781164
got, err := routesProtoToSlice(tt.routes, nil, false)
10791165
if (err != nil) != tt.wantErr {
1080-
t.Errorf("routesProtoToSlice() error = %v, wantErr %v", err, tt.wantErr)
1081-
return
1166+
t.Fatalf("routesProtoToSlice() error = %v, wantErr %v", err, tt.wantErr)
10821167
}
1083-
if !cmp.Equal(got, tt.wantRoutes, cmpOpts...) {
1084-
t.Errorf("routesProtoToSlice() got = %v, want %v, diff: %v", got, tt.wantRoutes, cmp.Diff(got, tt.wantRoutes, cmpOpts...))
1168+
if diff := cmp.Diff(got, tt.wantRoutes, cmpOpts...); diff != "" {
1169+
t.Fatalf("routesProtoToSlice() returned unexpected diff (-got +want):\n%s", diff)
10851170
}
1086-
1087-
env.FaultInjectionSupport = oldFI
10881171
})
10891172
}
10901173
}

xds/internal/client/xds.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"errors"
2323
"fmt"
2424
"net"
25+
"regexp"
2526
"strconv"
2627
"strings"
2728
"time"
@@ -437,7 +438,12 @@ func routesProtoToSlice(routes []*v3routepb.Route, logger *grpclog.PrefixLogger,
437438
case *v3routepb.RouteMatch_Path:
438439
route.Path = &pt.Path
439440
case *v3routepb.RouteMatch_SafeRegex:
440-
route.Regex = &pt.SafeRegex.Regex
441+
regex := pt.SafeRegex.GetRegex()
442+
re, err := regexp.Compile(regex)
443+
if err != nil {
444+
return nil, fmt.Errorf("route %+v contains an invalid regex %q", r, regex)
445+
}
446+
route.Regex = re
441447
default:
442448
return nil, fmt.Errorf("route %+v has an unrecognized path specifier: %+v", r, pt)
443449
}
@@ -452,7 +458,12 @@ func routesProtoToSlice(routes []*v3routepb.Route, logger *grpclog.PrefixLogger,
452458
case *v3routepb.HeaderMatcher_ExactMatch:
453459
header.ExactMatch = &ht.ExactMatch
454460
case *v3routepb.HeaderMatcher_SafeRegexMatch:
455-
header.RegexMatch = &ht.SafeRegexMatch.Regex
461+
regex := ht.SafeRegexMatch.GetRegex()
462+
re, err := regexp.Compile(regex)
463+
if err != nil {
464+
return nil, fmt.Errorf("route %+v contains an invalid regex %q", r, regex)
465+
}
466+
header.RegexMatch = re
456467
case *v3routepb.HeaderMatcher_RangeMatch:
457468
header.RangeMatch = &Int64Range{
458469
Start: ht.RangeMatch.Start,

xds/internal/resolver/matcher.go

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ package resolver
2020

2121
import (
2222
"fmt"
23-
"regexp"
2423
"strings"
2524

2625
"google.golang.org/grpc/internal/grpcrand"
@@ -34,11 +33,7 @@ func routeToMatcher(r *xdsclient.Route) (*compositeMatcher, error) {
3433
var pathMatcher pathMatcherInterface
3534
switch {
3635
case r.Regex != nil:
37-
re, err := regexp.Compile(*r.Regex)
38-
if err != nil {
39-
return nil, fmt.Errorf("failed to compile regex %q", *r.Regex)
40-
}
41-
pathMatcher = newPathRegexMatcher(re)
36+
pathMatcher = newPathRegexMatcher(r.Regex)
4237
case r.Path != nil:
4338
pathMatcher = newPathExactMatcher(*r.Path, r.CaseInsensitive)
4439
case r.Prefix != nil:
@@ -53,12 +48,8 @@ func routeToMatcher(r *xdsclient.Route) (*compositeMatcher, error) {
5348
switch {
5449
case h.ExactMatch != nil && *h.ExactMatch != "":
5550
matcherT = newHeaderExactMatcher(h.Name, *h.ExactMatch)
56-
case h.RegexMatch != nil && *h.RegexMatch != "":
57-
re, err := regexp.Compile(*h.RegexMatch)
58-
if err != nil {
59-
return nil, fmt.Errorf("failed to compile regex %q, skipping this matcher", *h.RegexMatch)
60-
}
61-
matcherT = newHeaderRegexMatcher(h.Name, re)
51+
case h.RegexMatch != nil:
52+
matcherT = newHeaderRegexMatcher(h.Name, h.RegexMatch)
6253
case h.PrefixMatch != nil && *h.PrefixMatch != "":
6354
matcherT = newHeaderPrefixMatcher(h.Name, *h.PrefixMatch)
6455
case h.SuffixMatch != nil && *h.SuffixMatch != "":

0 commit comments

Comments
 (0)