From 07adceb5c181bb62ee1863e7b4a2ee01aa253c78 Mon Sep 17 00:00:00 2001 From: Norwin Schnyder Date: Mon, 28 Apr 2025 11:18:52 +0000 Subject: [PATCH 1/3] CEL for HTTP header values Signed-off-by: Norwin Schnyder --- apis/v1/httproute_types.go | 10 + .../gateway.networking.k8s.io_grpcroutes.yaml | 96 ++++++-- .../gateway.networking.k8s.io_httproutes.yaml | 214 +++++++++++++++--- pkg/generated/openapi/zz_generated.openapi.go | 4 +- pkg/test/cel/httproute_experimental_test.go | 59 +++++ 5 files changed, 329 insertions(+), 54 deletions(-) diff --git a/apis/v1/httproute_types.go b/apis/v1/httproute_types.go index 6131d70b7b..5847025df6 100644 --- a/apis/v1/httproute_types.go +++ b/apis/v1/httproute_types.go @@ -612,9 +612,14 @@ type HTTPHeaderMatch struct { Name HTTPHeaderName `json:"name"` // Value is the value of HTTP Header to be matched. + // + // Must consist of printable US-ASCII characters, optionally separated + // by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 + // // // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=4096 + // Value string `json:"value"` } @@ -981,9 +986,14 @@ type HTTPHeader struct { Name HTTPHeaderName `json:"name"` // Value is the value of HTTP Header to be matched. + // + // Must consist of printable US-ASCII characters, optionally separated + // by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 + // // // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=4096 + // Value string `json:"value"` } diff --git a/config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml b/config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml index 8efb5867b7..c64bac1c29 100644 --- a/config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml +++ b/config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml @@ -544,11 +544,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP - Header to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable + ASCII characters, optionally separated + by single tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -619,11 +627,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP - Header to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable + ASCII characters, optionally separated + by single tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -827,11 +843,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP - Header to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable + ASCII characters, optionally separated + by single tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -902,11 +926,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP - Header to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable + ASCII characters, optionally separated + by single tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -1194,11 +1226,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP Header - to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable ASCII + characters, optionally separated by single + tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -1268,11 +1308,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP Header - to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable ASCII + characters, optionally separated by single + tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -1475,11 +1523,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP Header - to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable ASCII + characters, optionally separated by single + tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -1549,11 +1605,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP Header - to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable ASCII + characters, optionally separated by single + tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value diff --git a/config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml b/config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml index 050ba3277d..5c5c42096e 100644 --- a/config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml +++ b/config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml @@ -819,11 +819,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP - Header to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable + ASCII characters, optionally separated + by single tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -894,11 +902,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP - Header to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable + ASCII characters, optionally separated + by single tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -1255,11 +1271,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP - Header to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable + ASCII characters, optionally separated + by single tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -1330,11 +1354,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP - Header to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable + ASCII characters, optionally separated + by single tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -2045,11 +2077,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP Header - to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable ASCII + characters, optionally separated by single + tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -2119,11 +2159,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP Header - to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable ASCII + characters, optionally separated by single + tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -2479,11 +2527,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP Header - to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable ASCII + characters, optionally separated by single + tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -2553,11 +2609,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP Header - to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable ASCII + characters, optionally separated by single + tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -2882,11 +2946,18 @@ spec: - RegularExpression type: string value: - description: Value is the value of HTTP Header to - be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable ASCII characters, + optionally separated by single tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -4445,11 +4516,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP - Header to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable + ASCII characters, optionally separated + by single tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -4520,11 +4599,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP - Header to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable + ASCII characters, optionally separated + by single tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -4881,11 +4968,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP - Header to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable + ASCII characters, optionally separated + by single tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -4956,11 +5051,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP - Header to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable + ASCII characters, optionally separated + by single tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -5671,11 +5774,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP Header - to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable ASCII + characters, optionally separated by single + tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -5745,11 +5856,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP Header - to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable ASCII + characters, optionally separated by single + tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -6105,11 +6224,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP Header - to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable ASCII + characters, optionally separated by single + tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -6179,11 +6306,19 @@ spec: pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string value: - description: Value is the value of HTTP Header - to be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable ASCII + characters, optionally separated by single + tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -6508,11 +6643,18 @@ spec: - RegularExpression type: string value: - description: Value is the value of HTTP Header to - be matched. + description: |- + Value is the value of HTTP Header to be matched. + + Must consist of printable US-ASCII characters, optionally separated + by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 type: string + x-kubernetes-validations: + - message: must only contain printable ASCII characters, + optionally separated by single tabs or spaces + rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index 9279b1767a..5550643127 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -4386,7 +4386,7 @@ func schema_sigsk8sio_gateway_api_apis_v1_HTTPHeader(ref common.ReferenceCallbac }, "value": { SchemaProps: spec.SchemaProps{ - Description: "Value is the value of HTTP Header to be matched.", + Description: "Value is the value of HTTP Header to be matched. Must consist of printable US-ASCII characters, optionally separated by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 \n\n", Default: "", Type: []string{"string"}, Format: "", @@ -4502,7 +4502,7 @@ func schema_sigsk8sio_gateway_api_apis_v1_HTTPHeaderMatch(ref common.ReferenceCa }, "value": { SchemaProps: spec.SchemaProps{ - Description: "Value is the value of HTTP Header to be matched.", + Description: "Value is the value of HTTP Header to be matched. Must consist of printable US-ASCII characters, optionally separated by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 \n\n", Default: "", Type: []string{"string"}, Format: "", diff --git a/pkg/test/cel/httproute_experimental_test.go b/pkg/test/cel/httproute_experimental_test.go index f6ff0d455b..0dea8da8c9 100644 --- a/pkg/test/cel/httproute_experimental_test.go +++ b/pkg/test/cel/httproute_experimental_test.go @@ -568,3 +568,62 @@ func TestHTTPRequestMirrorFilterExperimental(t *testing.T) { }) } } + +func TestHTTPRouteHeaderValueExperimental(t *testing.T) { + tests := []struct { + name string + wantErrors []string + headerValue gatewayv1.HTTPHeaderValue + }{ + { + name: "valid with multiple values", + wantErrors: []string{}, + headerValue: "text/plain; charset=UTF-8, application/xml", + }, + { + name: "valid with special characters", + wantErrors: []string{}, + headerValue: "attachment; filename=\"example file.txt\"", + }, + { + name: "invalid because of non-print characters (newline)", + wantErrors: []string{"must only contain printable ASCII characters, optionally separated by single tabs or spaces"}, + headerValue: "this\r\nis\ninvalid\r", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + route := &gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("foo-%v", time.Now().UnixNano()), + Namespace: metav1.NamespaceDefault, + }, + Spec: gatewayv1.HTTPRouteSpec{ + CommonRouteSpec: gatewayv1.CommonRouteSpec{ + ParentRefs: []gatewayv1.ParentReference{ + { + Name: "example", + }, + }, + }, + Rules: []gatewayv1.HTTPRouteRule{ + { + Matches: []gatewayv1.HTTPRouteMatch{ + { + Headers: []gatewayv1.HTTPHeaderMatch{ + { + Name: "X-Test", + Value: tc.headerValue, + }, + }, + }, + }, + }, + }, + }, + } + validateHTTPRoute(t, route, tc.wantErrors) + }) + } +} From 0a5580a09504cf1c180fad0b76ae690f646587a4 Mon Sep 17 00:00:00 2001 From: Norwin Schnyder Date: Mon, 28 Apr 2025 11:45:39 +0000 Subject: [PATCH 2/3] Use a pattern instead of a CEL to validate HTTP header values Signed-off-by: Norwin Schnyder --- apis/v1/httproute_types.go | 4 +- .../gateway.networking.k8s.io_grpcroutes.yaml | 48 ++------ .../gateway.networking.k8s.io_httproutes.yaml | 106 +++--------------- pkg/generated/openapi/zz_generated.openapi.go | 4 +- pkg/generator/main.go | 12 ++ pkg/test/cel/httproute_experimental_test.go | 59 ---------- 6 files changed, 42 insertions(+), 191 deletions(-) diff --git a/apis/v1/httproute_types.go b/apis/v1/httproute_types.go index 5847025df6..f5defcc306 100644 --- a/apis/v1/httproute_types.go +++ b/apis/v1/httproute_types.go @@ -619,7 +619,7 @@ type HTTPHeaderMatch struct { // // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=4096 - // + // Value string `json:"value"` } @@ -993,7 +993,7 @@ type HTTPHeader struct { // // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=4096 - // + // Value string `json:"value"` } diff --git a/config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml b/config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml index c64bac1c29..3560ceecda 100644 --- a/config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml +++ b/config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml @@ -551,12 +551,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable - ASCII characters, optionally separated - by single tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -634,12 +630,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable - ASCII characters, optionally separated - by single tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -850,12 +842,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable - ASCII characters, optionally separated - by single tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -933,12 +921,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable - ASCII characters, optionally separated - by single tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -1233,12 +1217,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable ASCII - characters, optionally separated by single - tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -1315,12 +1295,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable ASCII - characters, optionally separated by single - tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -1530,12 +1506,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable ASCII - characters, optionally separated by single - tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -1612,12 +1584,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable ASCII - characters, optionally separated by single - tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value diff --git a/config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml b/config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml index 5c5c42096e..4a13c86176 100644 --- a/config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml +++ b/config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml @@ -826,12 +826,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable - ASCII characters, optionally separated - by single tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -909,12 +905,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable - ASCII characters, optionally separated - by single tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -1278,12 +1270,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable - ASCII characters, optionally separated - by single tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -1361,12 +1349,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable - ASCII characters, optionally separated - by single tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -2084,12 +2068,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable ASCII - characters, optionally separated by single - tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -2166,12 +2146,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable ASCII - characters, optionally separated by single - tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -2534,12 +2510,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable ASCII - characters, optionally separated by single - tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -2616,12 +2588,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable ASCII - characters, optionally separated by single - tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -2953,11 +2921,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable ASCII characters, - optionally separated by single tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -4523,12 +4488,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable - ASCII characters, optionally separated - by single tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -4606,12 +4567,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable - ASCII characters, optionally separated - by single tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -4975,12 +4932,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable - ASCII characters, optionally separated - by single tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -5058,12 +5011,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable - ASCII characters, optionally separated - by single tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -5781,12 +5730,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable ASCII - characters, optionally separated by single - tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -5863,12 +5808,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable ASCII - characters, optionally separated by single - tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -6231,12 +6172,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable ASCII - characters, optionally separated by single - tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -6313,12 +6250,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable ASCII - characters, optionally separated by single - tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value @@ -6650,11 +6583,8 @@ spec: by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 maxLength: 4096 minLength: 1 + pattern: ^[!-~]+([\t ]?[!-~]+)*$ type: string - x-kubernetes-validations: - - message: must only contain printable ASCII characters, - optionally separated by single tabs or spaces - rule: self.matches('^[!-~]+([\\t ]?[!-~]+)*$') required: - name - value diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index 5550643127..f69e286430 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -4386,7 +4386,7 @@ func schema_sigsk8sio_gateway_api_apis_v1_HTTPHeader(ref common.ReferenceCallbac }, "value": { SchemaProps: spec.SchemaProps{ - Description: "Value is the value of HTTP Header to be matched. Must consist of printable US-ASCII characters, optionally separated by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 \n\n", + Description: "Value is the value of HTTP Header to be matched. Must consist of printable US-ASCII characters, optionally separated by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 \n\n", Default: "", Type: []string{"string"}, Format: "", @@ -4502,7 +4502,7 @@ func schema_sigsk8sio_gateway_api_apis_v1_HTTPHeaderMatch(ref common.ReferenceCa }, "value": { SchemaProps: spec.SchemaProps{ - Description: "Value is the value of HTTP Header to be matched. Must consist of printable US-ASCII characters, optionally separated by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 \n\n", + Description: "Value is the value of HTTP Header to be matched. Must consist of printable US-ASCII characters, optionally separated by single tabs or spaces. See: https://tools.ietf.org/html/rfc7230#section-3.2 \n\n", Default: "", Type: []string{"string"}, Format: "", diff --git a/pkg/generator/main.go b/pkg/generator/main.go index dc170b8823..4531c6af38 100644 --- a/pkg/generator/main.go +++ b/pkg/generator/main.go @@ -217,6 +217,18 @@ func gatewayTweaks(channel string, name string, jsonProps apiext.JSONSchemaProps Rule: celMatch[2], }) } + + patternRe := regexp.MustCompile(validationPrefix + "Pattern=`([^`]*)`") + patternMatches := patternRe.FindAllStringSubmatch(jsonProps.Description, 64) + if len(patternMatches) == 1 && jsonProps.Pattern == "" { + patternMatch := patternMatches[0] + if len(patternMatch) != 2 { + log.Fatalf("Invalid %s Pattern tag for %s", validationPrefix, name) + } + + numValid++ + jsonProps.Pattern = patternMatch[1] + } } if numValid < numExpressions { diff --git a/pkg/test/cel/httproute_experimental_test.go b/pkg/test/cel/httproute_experimental_test.go index 0dea8da8c9..f6ff0d455b 100644 --- a/pkg/test/cel/httproute_experimental_test.go +++ b/pkg/test/cel/httproute_experimental_test.go @@ -568,62 +568,3 @@ func TestHTTPRequestMirrorFilterExperimental(t *testing.T) { }) } } - -func TestHTTPRouteHeaderValueExperimental(t *testing.T) { - tests := []struct { - name string - wantErrors []string - headerValue gatewayv1.HTTPHeaderValue - }{ - { - name: "valid with multiple values", - wantErrors: []string{}, - headerValue: "text/plain; charset=UTF-8, application/xml", - }, - { - name: "valid with special characters", - wantErrors: []string{}, - headerValue: "attachment; filename=\"example file.txt\"", - }, - { - name: "invalid because of non-print characters (newline)", - wantErrors: []string{"must only contain printable ASCII characters, optionally separated by single tabs or spaces"}, - headerValue: "this\r\nis\ninvalid\r", - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - route := &gatewayv1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("foo-%v", time.Now().UnixNano()), - Namespace: metav1.NamespaceDefault, - }, - Spec: gatewayv1.HTTPRouteSpec{ - CommonRouteSpec: gatewayv1.CommonRouteSpec{ - ParentRefs: []gatewayv1.ParentReference{ - { - Name: "example", - }, - }, - }, - Rules: []gatewayv1.HTTPRouteRule{ - { - Matches: []gatewayv1.HTTPRouteMatch{ - { - Headers: []gatewayv1.HTTPHeaderMatch{ - { - Name: "X-Test", - Value: tc.headerValue, - }, - }, - }, - }, - }, - }, - }, - } - validateHTTPRoute(t, route, tc.wantErrors) - }) - } -} From b7ab3abed70012ed2ad2e5e2634ef6e00b44ed23 Mon Sep 17 00:00:00 2001 From: Norwin Schnyder Date: Thu, 15 May 2025 09:20:30 +0000 Subject: [PATCH 3/3] Add examples with valid and invalid header values Signed-off-by: Norwin Schnyder --- examples/experimental/http-response-header.yaml | 2 ++ .../experimental/httproute/invalid-header-value.yaml | 12 ++++++++++++ pkg/generator/main.go | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 hack/invalid-examples/experimental/httproute/invalid-header-value.yaml diff --git a/examples/experimental/http-response-header.yaml b/examples/experimental/http-response-header.yaml index cbcd28eeb7..25ef470c41 100644 --- a/examples/experimental/http-response-header.yaml +++ b/examples/experimental/http-response-header.yaml @@ -20,6 +20,8 @@ spec: value: header-add-2 - name: X-Header-Add-3 value: header-add-3 + - name: Content-Disposition + value: "attachment; filename=\"example_file.txt\"" backendRefs: - name: echo port: 8080 diff --git a/hack/invalid-examples/experimental/httproute/invalid-header-value.yaml b/hack/invalid-examples/experimental/httproute/invalid-header-value.yaml new file mode 100644 index 0000000000..d2e00a7115 --- /dev/null +++ b/hack/invalid-examples/experimental/httproute/invalid-header-value.yaml @@ -0,0 +1,12 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: invalid-header-value +spec: + rules: + - filters: + - type: ResponseHeaderModifier + responseHeaderModifier: + add: + - name: X-Test-Header + value: "this\nis\rinvalid\r\nvalue" diff --git a/pkg/generator/main.go b/pkg/generator/main.go index 4531c6af38..17abed2484 100644 --- a/pkg/generator/main.go +++ b/pkg/generator/main.go @@ -225,7 +225,7 @@ func gatewayTweaks(channel string, name string, jsonProps apiext.JSONSchemaProps if len(patternMatch) != 2 { log.Fatalf("Invalid %s Pattern tag for %s", validationPrefix, name) } - + numValid++ jsonProps.Pattern = patternMatch[1] }