Skip to content

Commit

Permalink
Backport of Case sensitive route match into release/1.17.x (#20305)
Browse files Browse the repository at this point in the history
backport of commit 1b458c4

Co-authored-by: Lord-Y <[email protected]>
  • Loading branch information
hc-github-team-consul-core and Lord-Y authored Jan 22, 2024
1 parent 82e8aa2 commit 1c7e944
Show file tree
Hide file tree
Showing 10 changed files with 495 additions and 6 deletions.
3 changes: 3 additions & 0 deletions .changelog/19647.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
connect: Add `CaseInsensitive` flag to service-routers that allows paths and path prefixes to ignore URL upper and lower casing.
```
8 changes: 5 additions & 3 deletions agent/structs/config_entry_discoverychain.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,9 +372,10 @@ func (m *ServiceRouteMatch) IsEmpty() bool {

// ServiceRouteHTTPMatch is a set of http-specific match criteria.
type ServiceRouteHTTPMatch struct {
PathExact string `json:",omitempty" alias:"path_exact"`
PathPrefix string `json:",omitempty" alias:"path_prefix"`
PathRegex string `json:",omitempty" alias:"path_regex"`
PathExact string `json:",omitempty" alias:"path_exact"`
PathPrefix string `json:",omitempty" alias:"path_prefix"`
PathRegex string `json:",omitempty" alias:"path_regex"`
CaseInsensitive bool `json:",omitempty" alias:"case_insensitive"`

Header []ServiceRouteHTTPMatchHeader `json:",omitempty"`
QueryParam []ServiceRouteHTTPMatchQueryParam `json:",omitempty" alias:"query_param"`
Expand All @@ -385,6 +386,7 @@ func (m *ServiceRouteHTTPMatch) IsEmpty() bool {
return m.PathExact == "" &&
m.PathPrefix == "" &&
m.PathRegex == "" &&
!m.CaseInsensitive &&
len(m.Header) == 0 &&
len(m.QueryParam) == 0 &&
len(m.Methods) == 0
Expand Down
14 changes: 14 additions & 0 deletions agent/structs/config_entry_discoverychain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2742,6 +2742,20 @@ func TestServiceRouterConfigEntry(t *testing.T) {
}),
validateErr: "contains an invalid retry condition: \"invalid-retry-condition\"",
},
////////////////
{
name: "default route with case insensitive match",
entry: makerouter(routeMatch(httpMatch(&ServiceRouteHTTPMatch{
CaseInsensitive: true,
}))),
},
{
name: "route with path prefix and case insensitive match /apI",
entry: makerouter(routeMatch(httpMatch(&ServiceRouteHTTPMatch{
PathPrefix: "/apI",
CaseInsensitive: true,
}))),
},
}

for _, tc := range cases {
Expand Down
304 changes: 304 additions & 0 deletions agent/structs/config_entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,310 @@ func TestDecodeConfigEntry(t *testing.T) {
},
},
},
{
name: "service-router: kitchen sink case insensitive",
snake: `
kind = "service-router"
name = "main"
meta {
"foo" = "bar"
"gir" = "zim"
}
routes = [
{
match {
http {
path_exact = "/foo"
case_insensitive = true
header = [
{
name = "debug1"
present = true
},
{
name = "debug2"
present = false
invert = true
},
{
name = "debug3"
exact = "1"
},
{
name = "debug4"
prefix = "aaa"
},
{
name = "debug5"
suffix = "bbb"
},
{
name = "debug6"
regex = "a.*z"
},
]
}
}
destination {
service = "carrot"
service_subset = "kale"
namespace = "leek"
prefix_rewrite = "/alternate"
request_timeout = "99s"
idle_timeout = "99s"
num_retries = 12345
retry_on_connect_failure = true
retry_on_status_codes = [401, 209]
request_headers {
add {
x-foo = "bar"
}
set {
bar = "baz"
}
remove = ["qux"]
}
response_headers {
add {
x-foo = "bar"
}
set {
bar = "baz"
}
remove = ["qux"]
}
}
},
{
match {
http {
path_prefix = "/foo"
methods = [ "GET", "DELETE" ]
query_param = [
{
name = "hack1"
present = true
},
{
name = "hack2"
exact = "1"
},
{
name = "hack3"
regex = "a.*z"
},
]
}
}
},
{
match {
http {
path_regex = "/foo"
}
}
},
]
`,
camel: `
Kind = "service-router"
Name = "main"
Meta {
"foo" = "bar"
"gir" = "zim"
}
Routes = [
{
Match {
HTTP {
PathExact = "/foo"
CaseInsensitive = true
Header = [
{
Name = "debug1"
Present = true
},
{
Name = "debug2"
Present = false
Invert = true
},
{
Name = "debug3"
Exact = "1"
},
{
Name = "debug4"
Prefix = "aaa"
},
{
Name = "debug5"
Suffix = "bbb"
},
{
Name = "debug6"
Regex = "a.*z"
},
]
}
}
Destination {
Service = "carrot"
ServiceSubset = "kale"
Namespace = "leek"
PrefixRewrite = "/alternate"
RequestTimeout = "99s"
IdleTimeout = "99s"
NumRetries = 12345
RetryOnConnectFailure = true
RetryOnStatusCodes = [401, 209]
RequestHeaders {
Add {
x-foo = "bar"
}
Set {
bar = "baz"
}
Remove = ["qux"]
}
ResponseHeaders {
Add {
x-foo = "bar"
}
Set {
bar = "baz"
}
Remove = ["qux"]
}
}
},
{
Match {
HTTP {
PathPrefix = "/foo"
Methods = [ "GET", "DELETE" ]
QueryParam = [
{
Name = "hack1"
Present = true
},
{
Name = "hack2"
Exact = "1"
},
{
Name = "hack3"
Regex = "a.*z"
},
]
}
}
},
{
Match {
HTTP {
PathRegex = "/foo"
}
}
},
]
`,
expect: &ServiceRouterConfigEntry{
Kind: "service-router",
Name: "main",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
Routes: []ServiceRoute{
{
Match: &ServiceRouteMatch{
HTTP: &ServiceRouteHTTPMatch{
PathExact: "/foo",
CaseInsensitive: true,
Header: []ServiceRouteHTTPMatchHeader{
{
Name: "debug1",
Present: true,
},
{
Name: "debug2",
Present: false,
Invert: true,
},
{
Name: "debug3",
Exact: "1",
},
{
Name: "debug4",
Prefix: "aaa",
},
{
Name: "debug5",
Suffix: "bbb",
},
{
Name: "debug6",
Regex: "a.*z",
},
},
},
},
Destination: &ServiceRouteDestination{
Service: "carrot",
ServiceSubset: "kale",
Namespace: "leek",
PrefixRewrite: "/alternate",
RequestTimeout: 99 * time.Second,
IdleTimeout: 99 * time.Second,
NumRetries: 12345,
RetryOnConnectFailure: true,
RetryOnStatusCodes: []uint32{401, 209},
RequestHeaders: &HTTPHeaderModifiers{
Add: map[string]string{"x-foo": "bar"},
Set: map[string]string{"bar": "baz"},
Remove: []string{"qux"},
},
ResponseHeaders: &HTTPHeaderModifiers{
Add: map[string]string{"x-foo": "bar"},
Set: map[string]string{"bar": "baz"},
Remove: []string{"qux"},
},
},
},
{
Match: &ServiceRouteMatch{
HTTP: &ServiceRouteHTTPMatch{
PathPrefix: "/foo",
Methods: []string{"GET", "DELETE"},
QueryParam: []ServiceRouteHTTPMatchQueryParam{
{
Name: "hack1",
Present: true,
},
{
Name: "hack2",
Exact: "1",
},
{
Name: "hack3",
Regex: "a.*z",
},
},
},
},
},
{
Match: &ServiceRouteMatch{
HTTP: &ServiceRouteHTTPMatch{
PathRegex: "/foo",
},
},
},
},
},
},
{
name: "service-splitter: kitchen sink",
snake: `
Expand Down
5 changes: 5 additions & 0 deletions agent/xds/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/hashicorp/consul/agent/xds/response"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/wrapperspb"
)

// routesFromSnapshot returns the xDS API representation of the "routes" in the
Expand Down Expand Up @@ -834,6 +835,10 @@ func makeRouteMatchForDiscoveryRoute(discoveryRoute *structs.DiscoveryRoute) *en
}
}

if match.HTTP.CaseInsensitive {
em.CaseSensitive = wrapperspb.Bool(false)
}

if len(match.HTTP.Header) > 0 {
em.Headers = make([]*envoy_route_v3.HeaderMatcher, 0, len(match.HTTP.Header))
for _, hdr := range match.HTTP.Header {
Expand Down
7 changes: 4 additions & 3 deletions api/config_entry_discoverychain.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ type ServiceRouteMatch struct {
}

type ServiceRouteHTTPMatch struct {
PathExact string `json:",omitempty" alias:"path_exact"`
PathPrefix string `json:",omitempty" alias:"path_prefix"`
PathRegex string `json:",omitempty" alias:"path_regex"`
PathExact string `json:",omitempty" alias:"path_exact"`
PathPrefix string `json:",omitempty" alias:"path_prefix"`
PathRegex string `json:",omitempty" alias:"path_regex"`
CaseInsensitive bool `json:",omitempty" alias:"case_insensitive"`

Header []ServiceRouteHTTPMatchHeader `json:",omitempty"`
QueryParam []ServiceRouteHTTPMatchQueryParam `json:",omitempty" alias:"query_param"`
Expand Down
Loading

0 comments on commit 1c7e944

Please sign in to comment.