From 1dabef0732bcd0e83d95d10c7b7a735cb02d4952 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Tue, 5 Nov 2024 14:53:32 -0500 Subject: [PATCH 1/4] Support HTTPQueryParamMatch as CEL routeRuleConditions Signed-off-by: Alex Snaps --- pkg/wasm/utils.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/wasm/utils.go b/pkg/wasm/utils.go index f4919d7d2..f077ab90a 100644 --- a/pkg/wasm/utils.go +++ b/pkg/wasm/utils.go @@ -144,9 +144,14 @@ func PredicatesFromHTTPRouteMatch(match gatewayapiv1.HTTPRouteMatch) []string { predicates = append(predicates, predicateFromHeader(headerMatch)) } - // TODO(eguzki): query params. Investigate integration with wasm regarding Envoy params - // from https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes - // request.query -> string : The query portion of the URL in the format of “name1=value1&name2=value2”. + // query param, only consider the first in case of repetition, as per spec + queryParams := make(map[gatewayapiv1.HTTPHeaderName]bool) + for _, queryParamMatch := range match.QueryParams { + if !queryParams[queryParamMatch.Name] { + queryParams[queryParamMatch.Name] = true + predicates = append(predicates, predicateFromQueryParam(queryParamMatch)) + } + } return predicates } @@ -181,3 +186,7 @@ func predicateFromHeader(headerMatch gatewayapiv1.HTTPHeaderMatch) string { // https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPHeaderMatch return fmt.Sprintf("request.headers['%s'] == '%s'", headerMatch.Name, headerMatch.Value) } + +func predicateFromQueryParam(queryParam gatewayapiv1.HTTPQueryParamMatch) string { + return fmt.Sprintf("decodeQueryString(request.query, false)['%s'] == '%s'", queryParam.Name, queryParam.Value) +} From 7293ac12082271bec5a71e264b98c7426d186db5 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Wed, 6 Nov 2024 08:42:41 -0500 Subject: [PATCH 2/4] By default decodeQueryString will now ignore repeated params Signed-off-by: Alex Snaps --- pkg/wasm/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/wasm/utils.go b/pkg/wasm/utils.go index f077ab90a..f5b9e43f6 100644 --- a/pkg/wasm/utils.go +++ b/pkg/wasm/utils.go @@ -188,5 +188,5 @@ func predicateFromHeader(headerMatch gatewayapiv1.HTTPHeaderMatch) string { } func predicateFromQueryParam(queryParam gatewayapiv1.HTTPQueryParamMatch) string { - return fmt.Sprintf("decodeQueryString(request.query, false)['%s'] == '%s'", queryParam.Name, queryParam.Value) + return fmt.Sprintf("decodeQueryString(request.query)['%s'] == '%s'", queryParam.Name, queryParam.Value) } From 695f15dff805ad17d4aef0d019098fe3a21deef0 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Wed, 6 Nov 2024 08:42:41 -0500 Subject: [PATCH 3/4] By default queryMap will now ignore repeated params Signed-off-by: Alex Snaps --- pkg/wasm/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/wasm/utils.go b/pkg/wasm/utils.go index f5b9e43f6..a189911c7 100644 --- a/pkg/wasm/utils.go +++ b/pkg/wasm/utils.go @@ -188,5 +188,5 @@ func predicateFromHeader(headerMatch gatewayapiv1.HTTPHeaderMatch) string { } func predicateFromQueryParam(queryParam gatewayapiv1.HTTPQueryParamMatch) string { - return fmt.Sprintf("decodeQueryString(request.query)['%s'] == '%s'", queryParam.Name, queryParam.Value) + return fmt.Sprintf("queryMap(request.query)['%s'] == '%s'", queryParam.Name, queryParam.Value) } From 61661d2975771d00c906ab3a46073df045a3d1e5 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Wed, 6 Nov 2024 15:09:53 -0500 Subject: [PATCH 4/4] Test for mapping `HTTPRouteMatch` to CEL Predicates Signed-off-by: Alex Snaps --- pkg/wasm/utils_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/pkg/wasm/utils_test.go b/pkg/wasm/utils_test.go index 155963e8c..5b2cbd5b3 100644 --- a/pkg/wasm/utils_test.go +++ b/pkg/wasm/utils_test.go @@ -6,9 +6,12 @@ import ( "errors" "testing" + "gotest.tools/assert" + "github.com/google/go-cmp/cmp" "google.golang.org/protobuf/types/known/structpb" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" "sigs.k8s.io/yaml" ) @@ -248,3 +251,51 @@ func TestConfigFromStruct(t *testing.T) { }) } } + +func TestPredicatesFromHTTPRouteMatch(t *testing.T) { + queryParams := make([]gatewayapiv1.HTTPQueryParamMatch, 0) + queryParamMatch := gatewayapiv1.QueryParamMatchExact + queryParams = append(queryParams, gatewayapiv1.HTTPQueryParamMatch{ + Type: &queryParamMatch, + Name: "foo", + Value: "bar", + }) + queryParams = append(queryParams, gatewayapiv1.HTTPQueryParamMatch{ + Type: &queryParamMatch, + Name: "foo", + Value: "baz", + }) // this param will be ignored, as `foo` was defined above to match `bar` + queryParams = append(queryParams, gatewayapiv1.HTTPQueryParamMatch{ + Type: &queryParamMatch, + Name: "kua", + Value: "drant", + }) + + headerMatch := gatewayapiv1.HeaderMatchExact + header := gatewayapiv1.HTTPHeaderMatch{ + Type: &headerMatch, + Name: "x-auth", + Value: "kuadrant", + } + + method := gatewayapiv1.HTTPMethodTrace + + pathMatch := gatewayapiv1.PathMatchPathPrefix + path := "/admin" + predicates := PredicatesFromHTTPRouteMatch(gatewayapiv1.HTTPRouteMatch{ + Path: &gatewayapiv1.HTTPPathMatch{ + Type: &pathMatch, + Value: &path, + }, + Headers: []gatewayapiv1.HTTPHeaderMatch{header}, + QueryParams: queryParams, + Method: &method, + }) + + assert.Equal(t, predicates[0], "request.method == 'TRACE'") + assert.Equal(t, predicates[1], "request.url_path.startsWith('/admin')") + assert.Equal(t, predicates[2], "request.headers['x-auth'] == 'kuadrant'") + assert.Equal(t, predicates[3], "queryMap(request.query)['foo'] == 'bar'") + assert.Equal(t, predicates[4], "queryMap(request.query)['kua'] == 'drant'") + assert.Equal(t, len(predicates), 5) +}