From 3510edad6954b18a14c90a79d9084ff3339fffd6 Mon Sep 17 00:00:00 2001 From: jaynis Date: Sat, 20 Jan 2024 09:16:22 +0100 Subject: [PATCH] feat(cors): Allowed more wildcard options (#2453) * allowed more CORS wildcard options Signed-off-by: jaynis * fixed quotes with wrong character encoding Signed-off-by: jaynis * generated manifests Signed-off-by: jaynis * gofmt Signed-off-by: jaynis * fixed regex escape characters in cel validation Signed-off-by: jaynis * fixed cel validation test Signed-off-by: jaynis * removed wildcard port matching Signed-off-by: jaynis * removed wildcard port test Signed-off-by: jaynis --------- Signed-off-by: jaynis Co-authored-by: Huabing Zhao --- api/v1alpha1/cors_types.go | 11 +- ...ateway.envoyproxy.io_securitypolicies.yaml | 12 +- internal/gatewayapi/securitypolicy_test.go | 18 ++ .../testdata/securitypolicy-with-cors.in.yaml | 62 +++++- .../securitypolicy-with-cors.out.yaml | 181 +++++++++++++++++- site/content/en/latest/user/cors.md | 8 +- test/cel-validation/securitypolicy_test.go | 68 ++++++- 7 files changed, 341 insertions(+), 19 deletions(-) diff --git a/api/v1alpha1/cors_types.go b/api/v1alpha1/cors_types.go index 6188978b206..2c2fb50f681 100644 --- a/api/v1alpha1/cors_types.go +++ b/api/v1alpha1/cors_types.go @@ -8,19 +8,22 @@ package v1alpha1 import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" // Origin is defined by the scheme (protocol), hostname (domain), and port of -// the URL used to access it. The hostname can be “precise” which is just the -// domain name or “wildcard” which is a domain name prefixed with a single -// wildcard label such as “*.example.com”. +// the URL used to access it. The hostname can be "precise" which is just the +// domain name or "wildcard" which is a domain name prefixed with a single +// wildcard label such as "*.example.com". +// In addition to that a single wildcard (with or without scheme) can be +// configured to match any origin. // // For example, the following are valid origins: // - https://foo.example.com // - https://*.example.com // - http://foo.example.com:8080 // - http://*.example.com:8080 +// - https://* // // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 -// +kubebuilder:validation:Pattern=`^https?:\/\/(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*(:[0-9]+)?$` +// +kubebuilder:validation:Pattern=`^(\*|https?:\/\/(\*|(\*\.)?(([\w-]+\.?)+)?[\w-]+)(:\d{1,5})?)$` type Origin string // CORS defines the configuration for Cross-Origin Resource Sharing (CORS). diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml index 32a52cdb3a3..1e34cf93bfb 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml @@ -125,14 +125,16 @@ spec: items: description: "Origin is defined by the scheme (protocol), hostname (domain), and port of the URL used to access it. The hostname - can be “precise” which is just the domain name or “wildcard” + can be \"precise\" which is just the domain name or \"wildcard\" which is a domain name prefixed with a single wildcard label - such as “*.example.com”. \n For example, the following are - valid origins: - https://foo.example.com - https://*.example.com - - http://foo.example.com:8080 - http://*.example.com:8080" + such as \"*.example.com\". In addition to that a single wildcard + (with or without scheme) can be configured to match any origin. + \n For example, the following are valid origins: - https://foo.example.com + - https://*.example.com - http://foo.example.com:8080 - http://*.example.com:8080 + - https://*" maxLength: 253 minLength: 1 - pattern: ^https?:\/\/(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*(:[0-9]+)?$ + pattern: ^(\*|https?:\/\/(\*|(\*\.)?(([\w-]+\.?)+)?[\w-]+)(:\d{1,5})?)$ type: string minItems: 1 type: array diff --git a/internal/gatewayapi/securitypolicy_test.go b/internal/gatewayapi/securitypolicy_test.go index 0b84f478f82..829144b81b0 100644 --- a/internal/gatewayapi/securitypolicy_test.go +++ b/internal/gatewayapi/securitypolicy_test.go @@ -62,6 +62,24 @@ func Test_wildcard2regex(t *testing.T) { origin: "http://foo.example.com", want: 0, }, + { + name: "test8", + wildcard: "http://*", + origin: "http://foo.example.com", + want: 1, + }, + { + name: "test9", + wildcard: "http://*", + origin: "https://foo.example.com", + want: 0, + }, + { + name: "test10", + wildcard: "*", + origin: "http://foo.example.com", + want: 1, + }, } for _, tt := range tests { diff --git a/internal/gatewayapi/testdata/securitypolicy-with-cors.in.yaml b/internal/gatewayapi/testdata/securitypolicy-with-cors.in.yaml index 82ed4671d6c..3b773726448 100644 --- a/internal/gatewayapi/testdata/securitypolicy-with-cors.in.yaml +++ b/internal/gatewayapi/testdata/securitypolicy-with-cors.in.yaml @@ -27,6 +27,20 @@ gateways: allowedRoutes: namespaces: from: All +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-3 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All grpcRoutes: - apiVersion: gateway.networking.k8s.io/v1alpha2 kind: GRPCRoute @@ -62,12 +76,31 @@ httpRoutes: backendRefs: - name: service-1 port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-3 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-2 + port: 8080 securityPolicies: - apiVersion: gateway.envoyproxy.io/v1alpha1 kind: SecurityPolicy metadata: namespace: envoy-gateway - name: policy-for-gateway + name: policy-for-gateway-1 spec: targetRef: group: gateway.networking.k8s.io @@ -78,6 +111,7 @@ securityPolicies: allowOrigins: - "http://*.example.com" - "http://foo.bar.com" + - "https://*" allowMethods: - GET - POST @@ -92,7 +126,7 @@ securityPolicies: kind: SecurityPolicy metadata: namespace: default - name: policy-for-route + name: policy-for-route-1 spec: targetRef: group: gateway.networking.k8s.io @@ -113,3 +147,27 @@ securityPolicies: - "x-header-7" - "x-header-8" maxAge: 2000s +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: default + name: policy-for-route-2 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-2 + namespace: default + cors: + allowOrigins: + - "*" + allowMethods: + - GET + - POST + allowHeaders: + - "x-header-5" + - "x-header-6" + exposeHeaders: + - "x-header-7" + - "x-header-8" + maxAge: 2000s diff --git a/internal/gatewayapi/testdata/securitypolicy-with-cors.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-cors.out.yaml index 0de22c6369b..c50b5e03ad6 100644 --- a/internal/gatewayapi/testdata/securitypolicy-with-cors.out.yaml +++ b/internal/gatewayapi/testdata/securitypolicy-with-cors.out.yaml @@ -79,6 +79,46 @@ gateways: kind: HTTPRoute - group: gateway.networking.k8s.io kind: GRPCRoute +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-3 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute grpcRoutes: - apiVersion: gateway.networking.k8s.io/v1alpha2 kind: GRPCRoute @@ -152,6 +192,44 @@ httpRoutes: name: gateway-2 namespace: envoy-gateway sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-3 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-2 + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-3 + namespace: envoy-gateway + sectionName: http infraIR: envoy-gateway/gateway-1: proxy: @@ -183,12 +261,27 @@ infraIR: gateway.envoyproxy.io/owning-gateway-name: gateway-2 gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway name: envoy-gateway/gateway-2 + envoy-gateway/gateway-3: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-3/http + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-3 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-3 securityPolicies: - apiVersion: gateway.envoyproxy.io/v1alpha1 kind: SecurityPolicy metadata: creationTimestamp: null - name: policy-for-route + name: policy-for-route-1 namespace: default spec: cors: @@ -221,7 +314,39 @@ securityPolicies: kind: SecurityPolicy metadata: creationTimestamp: null - name: policy-for-gateway + name: policy-for-route-2 + namespace: default + spec: + cors: + allowHeaders: + - x-header-5 + - x-header-6 + allowMethods: + - GET + - POST + allowOrigins: + - '*' + exposeHeaders: + - x-header-7 + - x-header-8 + maxAge: 33m20s + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-2 + namespace: default + status: + conditions: + - lastTransitionTime: null + message: SecurityPolicy has been accepted. + reason: Accepted + status: "True" + type: Accepted +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + creationTimestamp: null + name: policy-for-gateway-1 namespace: envoy-gateway spec: cors: @@ -234,6 +359,7 @@ securityPolicies: allowOrigins: - http://*.example.com - http://foo.bar.com + - https://* exposeHeaders: - x-header-3 - x-header-4 @@ -283,6 +409,9 @@ xdsIR: - distinct: false exact: http://foo.bar.com name: "" + - distinct: false + name: "" + safeRegex: https://.* exposeHeaders: - x-header-3 - x-header-4 @@ -349,3 +478,51 @@ xdsIR: distinct: false name: "" prefix: / + envoy-gateway/gateway-3: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-3/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - backendWeights: + invalid: 0 + valid: 0 + cors: + allowHeaders: + - x-header-5 + - x-header-6 + allowMethods: + - GET + - POST + allowOrigins: + - distinct: false + name: "" + safeRegex: .* + exposeHeaders: + - x-header-7 + - x-header-8 + maxAge: 33m20s + destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / diff --git a/site/content/en/latest/user/cors.md b/site/content/en/latest/user/cors.md index 5f24c626d9b..3be30a9b5fb 100644 --- a/site/content/en/latest/user/cors.md +++ b/site/content/en/latest/user/cors.md @@ -16,7 +16,11 @@ Before proceeding, you should be able to query the example backend using HTTP. ## Configuration -The below example defines a SecurityPolicy that allows CORS requests from `www.foo.com`. +When configuring CORS either an origin with a precise hostname can be configured or an hostname containing a wildcard prefix, +allowing all subdomains of the specified hostname. +In addition to that the entire origin (with or without specifying a scheme) can be a wildcard to allow all origins. + +The below example defines a SecurityPolicy that allows CORS for all HTTP requests originating from `www.foo.com`. ```shell cat <