Repository: envoyproxy/gateway
Pull request #4987 (2024-12-31 09:30:04)

fix: validate SecurityPolicy on controller and `egctl translate`
**What type of PR is this?**
<!--
Your PR title should be descriptive, and generally start with type that contains a subsystem name with `()` if necessary
and summary followed by a colon. format `chore/docs/api/feat/fix/refactor/style/test: summary`.
Examples:
* "docs: fix grammar error"
* "feat(translator): add new feature"
* "fix: fix xx bug"
* "chore: change ci & build tools etc"
* "api: add xxx fields in ClientTrafficPolicy"
-->

<!--
NOTE: If your PR contains any API changes (changes under `/api`), we recommend you to separate these API changes into
a new PR, and we will review the API part first. It will save you lots of implementation time if the API get accepted.
-->

**What this PR does / why we need it**:

- `ValidateSecurityPolicy` exists, but isn't called like other validation functions (ValidateEnvoyProxy etc).
-  Fix the validation; Currently, all `SecurityPolicy`s without CORS or JWT are considered invalid and failed at the validation. 

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #

<!--
For any non-trivial changes, you need to provide a brief description of the changes in the release notes.
Please add the description to the release-notes/current.yaml file and include this file in the PR.
-->
Release Notes: No


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/4987?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) Report
Attention: Patch coverage is `35.29412%` with `11 lines` in your changes missing coverage. Please review.
> Project coverage is 66.70%. Comparing base [(`24a50b4`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/24a50b49fae835d440b1c013140f07dc4180be5f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) to head [(`282ee6c`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/282ee6ce5a1c1e22381f8d2fc1dcae4fb4e6283a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).
> Report is 4 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/envoyproxy/gateway/pull/4987?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) | Patch % | Lines |
|---|---|---|
| [api/v1alpha1/validation/securitypolicy\_validate.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/4987?src=pr&el=tree&filepath=api%2Fv1alpha1%2Fvalidation%2Fsecuritypolicy_validate.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-YXBpL3YxYWxwaGExL3ZhbGlkYXRpb24vc2VjdXJpdHlwb2xpY3lfdmFsaWRhdGUuZ28=) | 0.00% | [8 Missing :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/4987?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |
| [internal/provider/kubernetes/controller.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/4987?src=pr&el=tree&filepath=internal%2Fprovider%2Fkubernetes%2Fcontroller.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwvcHJvdmlkZXIva3ViZXJuZXRlcy9jb250cm9sbGVyLmdv) | 0.00% | [3 Missing :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/4987?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #4987      +/-   ##
==========================================
- Coverage   66.71%   66.70%   -0.02%     
==========================================
  Files         209      209              
  Lines       32052    32069      +17     
==========================================
+ Hits        21384    21391       +7     
- Misses       9388     9398      +10     
  Partials     1280     1280              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/envoyproxy/gateway/pull/4987?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).

sanposhiho: Another question around the validation is that, shouldn't we do it with a validation webhook instead of at the reconciliation? 
I mean, I know it's a tradeoff between "GW users can immediately/easily find the mistakes at applying" vs "admin has to maintain the webhook properly (e.g., autoscaling etc)" though.
sanposhiho: @arkodg @zhaohuabing Sorry I had missed the test was failing.
I just fixed them, please retake a look.

Patch:
diff --git a/api/v1alpha1/validation/securitypolicy_validate.go b/api/v1alpha1/validation/securitypolicy_validate.go
index 64425afdd..30a8ce797 100644
--- a/api/v1alpha1/validation/securitypolicy_validate.go
+++ b/api/v1alpha1/validation/securitypolicy_validate.go
@@ -25,7 +25,7 @@ func ValidateSecurityPolicy(policy *egv1a1.SecurityPolicy) error {
 		return errors.New("policy is nil")
 	}
 	if err := validateSecurityPolicySpec(&policy.Spec); err != nil {
-		errs = append(errs, errors.New("policy is nil"))
+		errs = append(errs, err)
 	}
 
 	return utilerrors.NewAggregate(errs)
@@ -43,6 +43,17 @@ func validateSecurityPolicySpec(spec *egv1a1.SecurityPolicySpec) error {
 		sum++
 	case spec.JWT != nil:
 		sum++
+		if err := ValidateJWTProvider(spec.JWT.Providers); err != nil {
+			errs = append(errs, err)
+		}
+	case spec.Authorization != nil:
+		sum++
+	case spec.BasicAuth != nil:
+		sum++
+	case spec.ExtAuth != nil:
+		sum++
+	case spec.OIDC != nil:
+		sum++
 	}
 	if sum == 0 {
 		errs = append(errs, errors.New("no security policy is specified"))
@@ -53,10 +64,6 @@ func validateSecurityPolicySpec(spec *egv1a1.SecurityPolicySpec) error {
 		return utilerrors.NewAggregate(errs)
 	}
 
-	if err := ValidateJWTProvider(spec.JWT.Providers); err != nil {
-		errs = append(errs, err)
-	}
-
 	return utilerrors.NewAggregate(errs)
 }
 
diff --git a/internal/cmd/egctl/translate.go b/internal/cmd/egctl/translate.go
index 7be07c4cf..08a592ed0 100644
--- a/internal/cmd/egctl/translate.go
+++ b/internal/cmd/egctl/translate.go
@@ -322,6 +322,7 @@ func translateGatewayAPIToGatewayAPI(resources *resource.Resources) (resource.Re
 		}
 		gRes.EnvoyProxyForGatewayClass = resources.EnvoyProxyForGatewayClass
 	}
+
 	if !epInvalid {
 		status.SetGatewayClassAccepted(resources.GatewayClass, true, string(gwapiv1.GatewayClassReasonAccepted), status.MsgValidGatewayClass)
 	}
diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go
index ad505562d..416ce9808 100644
--- a/internal/gatewayapi/helpers.go
+++ b/internal/gatewayapi/helpers.go
@@ -249,25 +249,6 @@ func OwnerLabels(gateway *gwapiv1.Gateway, mergeGateways bool) map[string]string
 	return GatewayOwnerLabels(gateway.Namespace, gateway.Name)
 }
 
-// servicePortToContainerPort translates a service port into an ephemeral
-// container port.
-func servicePortToContainerPort(servicePort int32, envoyProxy *egv1a1.EnvoyProxy) int32 {
-	if envoyProxy != nil {
-		if !envoyProxy.NeedToSwitchPorts() {
-			return servicePort
-		}
-	}
-
-	// If the service port is a privileged port (1-1023)
-	// add a constant to the value converting it into an ephemeral port.
-	// This allows the container to bind to the port without needing a
-	// CAP_NET_BIND_SERVICE capability.
-	if servicePort < minEphemeralPort {
-		return servicePort + wellKnownPortShift
-	}
-	return servicePort
-}
-
 // computeHosts returns a list of intersecting listener hostnames and route hostnames
 // that don't intersect with other listener hostnames.
 func computeHosts(routeHostnames []string, listenerContext *ListenerContext) []string {
diff --git a/internal/gatewayapi/listener.go b/internal/gatewayapi/listener.go
index 18fbebb0a..950290c28 100644
--- a/internal/gatewayapi/listener.go
+++ b/internal/gatewayapi/listener.go
@@ -109,7 +109,7 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR resource
 
 			// Add the listener to the Xds IR
 			servicePort := &protocolPort{protocol: listener.Protocol, port: int32(listener.Port)}
-			containerPort := servicePortToContainerPort(int32(listener.Port), gateway.envoyProxy)
+			containerPort := t.servicePortToContainerPort(int32(listener.Port), gateway.envoyProxy)
 			switch listener.Protocol {
 			case gwapiv1.HTTPProtocolType, gwapiv1.HTTPSProtocolType:
 				irListener := &ir.HTTPListener{
@@ -552,3 +552,27 @@ func validCELExpression(expr string) bool {
 	_, issue := celEnv.Parse(expr)
 	return issue.Err() == nil
 }
+
+// servicePortToContainerPort translates a service port into an ephemeral
+// container port.
+func (t *Translator) servicePortToContainerPort(servicePort int32, envoyProxy *egv1a1.EnvoyProxy) int32 {
+	if t.ListenerPortShiftDisabled {
+		return servicePort
+	}
+
+	if envoyProxy != nil {
+		if !envoyProxy.NeedToSwitchPorts() {
+			return servicePort
+		}
+	}
+
+	// If the service port is a privileged port (1-1023)
+	// add a constant to the value converting it into an ephemeral port.
+	// This allows the container to bind to the port without needing a
+	// CAP_NET_BIND_SERVICE capability.
+	if servicePort < minEphemeralPort {
+		return servicePort + wellKnownPortShift
+	}
+
+	return servicePort
+}
diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go
index 629758929..9c2bf9c5d 100644
--- a/internal/gatewayapi/runner/runner.go
+++ b/internal/gatewayapi/runner/runner.go
@@ -144,14 +144,15 @@ func (r *Runner) subscribeAndTranslate(ctx context.Context) {
 			for _, resources := range *val {
 				// Translate and publish IRs.
 				t := &gatewayapi.Translator{
-					GatewayControllerName:   r.Server.EnvoyGateway.Gateway.ControllerName,
-					GatewayClassName:        gwapiv1.ObjectName(resources.GatewayClass.Name),
-					GlobalRateLimitEnabled:  r.EnvoyGateway.RateLimit != nil,
-					EnvoyPatchPolicyEnabled: r.EnvoyGateway.ExtensionAPIs != nil && r.EnvoyGateway.ExtensionAPIs.EnableEnvoyPatchPolicy,
-					BackendEnabled:          r.EnvoyGateway.ExtensionAPIs != nil && r.EnvoyGateway.ExtensionAPIs.EnableBackend,
-					Namespace:               r.Namespace,
-					MergeGateways:           gatewayapi.IsMergeGatewaysEnabled(resources),
-					WasmCache:               r.wasmCache,
+					GatewayControllerName:     r.Server.EnvoyGateway.Gateway.ControllerName,
+					GatewayClassName:          gwapiv1.ObjectName(resources.GatewayClass.Name),
+					GlobalRateLimitEnabled:    r.EnvoyGateway.RateLimit != nil,
+					EnvoyPatchPolicyEnabled:   r.EnvoyGateway.ExtensionAPIs != nil && r.EnvoyGateway.ExtensionAPIs.EnableEnvoyPatchPolicy,
+					BackendEnabled:            r.EnvoyGateway.ExtensionAPIs != nil && r.EnvoyGateway.ExtensionAPIs.EnableBackend,
+					Namespace:                 r.Namespace,
+					MergeGateways:             gatewayapi.IsMergeGatewaysEnabled(resources),
+					WasmCache:                 r.wasmCache,
+					ListenerPortShiftDisabled: r.EnvoyGateway.Provider != nil && r.EnvoyGateway.Provider.IsRunningOnHost(),
 				}
 
 				// If an extension is loaded, pass its supported groups/kinds to the translator
diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go
index 071c7dc9c..603977ee1 100644
--- a/internal/gatewayapi/securitypolicy.go
+++ b/internal/gatewayapi/securitypolicy.go
@@ -30,6 +30,7 @@ import (
 	gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 
 	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+	"github.com/envoyproxy/gateway/api/v1alpha1/validation"
 	"github.com/envoyproxy/gateway/internal/gatewayapi/resource"
 	"github.com/envoyproxy/gateway/internal/gatewayapi/status"
 	"github.com/envoyproxy/gateway/internal/ir"
@@ -150,6 +151,17 @@ func (t *Translator) ProcessSecurityPolicies(securityPolicies []*egv1a1.Security
 					continue
 				}
 
+				if err := validation.ValidateSecurityPolicy(policy); err != nil {
+					status.SetTranslationErrorForPolicyAncestors(&policy.Status,
+						parentGateways,
+						t.GatewayControllerName,
+						policy.Generation,
+						status.Error2ConditionMsg(fmt.Errorf("invalid SecurityPolicy: %w", err)),
+					)
+
+					continue
+				}
+
 				if err := t.translateSecurityPolicyForRoute(policy, targetedRoute, resources, xdsIR); err != nil {
 					status.SetTranslationErrorForPolicyAncestors(&policy.Status,
 						parentGateways,
diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go
index 23e651b6c..27963570d 100644
--- a/internal/gatewayapi/translator.go
+++ b/internal/gatewayapi/translator.go
@@ -91,6 +91,11 @@ type Translator struct {
 
 	// WasmCache is the cache for Wasm modules.
 	WasmCache wasm.Cache
+
+	// ListenerPortShiftDisabled disables translating the
+	// gateway listener port into a non privileged port
+	// and reuses the specified value.
+	ListenerPortShiftDisabled bool
 }
 
 type TranslateResult struct {


Test patch:
diff --git a/internal/cmd/egctl/testdata/translate/in/invalid-securitypolicy.yaml b/internal/cmd/egctl/testdata/translate/in/invalid-securitypolicy.yaml
new file mode 100644
index 000000000..bf754393b
--- /dev/null
+++ b/internal/cmd/egctl/testdata/translate/in/invalid-securitypolicy.yaml
@@ -0,0 +1,103 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: GatewayClass
+metadata:
+  name: eg
+spec:
+  controllerName: gateway.envoyproxy.io/gatewayclass-controller
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: eg
+spec:
+  gatewayClassName: eg
+  listeners:
+    - name: http
+      protocol: HTTP
+      port: 80
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: backend
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: backend
+  labels:
+    app: backend
+    service: backend
+spec:
+  clusterIP: 7.7.7.7
+  ports:
+    - name: http
+      port: 3000
+      targetPort: 3000
+  selector:
+    app: backend
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: backend
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: backend
+      version: v1
+  template:
+    metadata:
+      labels:
+        app: backend
+        version: v1
+    spec:
+      serviceAccountName: backend
+      containers:
+        - image: gcr.io/k8s-staging-gateway-api/echo-basic:v20231214-v1.0.0-140-gf544a46e
+          imagePullPolicy: IfNotPresent
+          name: backend
+          ports:
+            - containerPort: 3000
+          env:
+            - name: POD_NAME
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.name
+            - name: NAMESPACE
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.namespace
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: SecurityPolicy
+metadata:
+  name: jwt-example
+spec:
+  targetRef:
+    group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: backend
+# No policy inside, which is invalid
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: backend
+spec:
+  parentRefs:
+    - name: eg
+  hostnames:
+    - "www.example.com"
+  rules:
+    - backendRefs:
+        - group: ""
+          kind: Service
+          name: backend
+          port: 3000
+          weight: 1
+      matches:
+        - path:
+            type: PathPrefix
+            value: /foo
diff --git a/internal/cmd/egctl/testdata/translate/out/invalid-securitypolicy.all.yaml b/internal/cmd/egctl/testdata/translate/out/invalid-securitypolicy.all.yaml
new file mode 100644
index 000000000..9dc0fdfd2
--- /dev/null
+++ b/internal/cmd/egctl/testdata/translate/out/invalid-securitypolicy.all.yaml
@@ -0,0 +1,115 @@
+gatewayClass:
+  kind: GatewayClass
+  metadata:
+    creationTimestamp: null
+    name: eg
+    namespace: envoy-gateway-system
+  spec:
+    controllerName: gateway.envoyproxy.io/gatewayclass-controller
+  status:
+    conditions:
+    - lastTransitionTime: null
+      message: Valid GatewayClass
+      reason: Accepted
+      status: "True"
+      type: Accepted
+gateways:
+- kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: eg
+    namespace: envoy-gateway-system
+  spec:
+    gatewayClassName: eg
+    listeners:
+    - 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
+httpRoutes:
+- kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: backend
+    namespace: envoy-gateway-system
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - name: eg
+    rules:
+    - backendRefs:
+      - group: ""
+        kind: Service
+        name: backend
+        port: 3000
+        weight: 1
+      matches:
+      - path:
+          type: PathPrefix
+          value: /foo
+  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: eg
+securityPolicies:
+- kind: SecurityPolicy
+  metadata:
+    creationTimestamp: null
+    name: jwt-example
+    namespace: envoy-gateway-system
+  spec:
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: backend
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: eg
+        namespace: envoy-gateway-system
+      conditions:
+      - lastTransitionTime: null
+        message: 'Invalid SecurityPolicy: no security policy is specified.'
+        reason: Invalid
+        status: "False"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
diff --git a/internal/cmd/egctl/translate_test.go b/internal/cmd/egctl/translate_test.go
index e87167ce3..0dafcc49d 100644
--- a/internal/cmd/egctl/translate_test.go
+++ b/internal/cmd/egctl/translate_test.go
@@ -269,6 +269,13 @@ func TestTranslate(t *testing.T) {
 			output: yamlOutput,
 			expect: true,
 		},
+		{
+			name:   "invalid-securitypolicy",
+			from:   "gateway-api",
+			to:     "gateway-api",
+			output: yamlOutput,
+			expect: true,
+		},
 		{
 			name:   "no-gateway-class-resources",
 			from:   "gateway-api",
diff --git a/internal/gatewayapi/testdata/securitypolicy-override-replace.in.yaml b/internal/gatewayapi/testdata/securitypolicy-override-replace.in.yaml
index bf3bfcedc..f34bff003 100644
--- a/internal/gatewayapi/testdata/securitypolicy-override-replace.in.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-override-replace.in.yaml
@@ -142,3 +142,7 @@ securityPolicies:
       group: gateway.networking.k8s.io
       kind: HTTPRoute
       name: httproute-3
+    cors:
+      allowOrigins:
+      - "http://*.example.com"
+      maxAge: 1000s
diff --git a/internal/gatewayapi/testdata/securitypolicy-override-replace.out.yaml b/internal/gatewayapi/testdata/securitypolicy-override-replace.out.yaml
index c6f720655..bc300a92e 100644
--- a/internal/gatewayapi/testdata/securitypolicy-override-replace.out.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-override-replace.out.yaml
@@ -218,6 +218,10 @@ securityPolicies:
     name: policy-for-route-3
     namespace: default
   spec:
+    cors:
+      allowOrigins:
+      - http://*.example.com
+      maxAge: 16m40s
     targetRef:
       group: gateway.networking.k8s.io
       kind: HTTPRoute
@@ -427,4 +431,10 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /baz
-        security: {}
+        security:
+          cors:
+            allowOrigins:
+            - distinct: false
+              name: ""
+              safeRegex: http://.*\.example\.com
+            maxAge: 16m40s
diff --git a/internal/gatewayapi/testdata/securitypolicy-status-conditions.in.yaml b/internal/gatewayapi/testdata/securitypolicy-status-conditions.in.yaml
index 910a5ce62..3eb4a17ac 100644
--- a/internal/gatewayapi/testdata/securitypolicy-status-conditions.in.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-status-conditions.in.yaml
@@ -29,6 +29,10 @@ securityPolicies:
       group: gateway.networking.k8s.io
       kind: HTTPRoute
       name: httproute-1
+    cors:
+      allowOrigins:
+      - "http://*.example.com"
+      maxAge: 1000s
 - apiVersion: gateway.envoyproxy.io/v1alpha1
   kind: SecurityPolicy
   metadata:
@@ -45,6 +49,10 @@ securityPolicies:
     namespace: envoy-gateway
     name: target-grpcroute-in-gateway-2
   spec:
+    cors:
+      allowOrigins:
+      - "http://*.example.com"
+      maxAge: 1000s
     targetRef:
       group: gateway.networking.k8s.io
       kind: GRPCRoute
diff --git a/internal/gatewayapi/testdata/securitypolicy-status-conditions.out.yaml b/internal/gatewayapi/testdata/securitypolicy-status-conditions.out.yaml
index 3dc5888bb..35eb026bb 100644
--- a/internal/gatewayapi/testdata/securitypolicy-status-conditions.out.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-status-conditions.out.yaml
@@ -248,6 +248,10 @@ securityPolicies:
     name: target-httproute-in-gateway-1
     namespace: envoy-gateway
   spec:
+    cors:
+      allowOrigins:
+      - http://*.example.com
+      maxAge: 16m40s
     targetRef:
       group: gateway.networking.k8s.io
       kind: HTTPRoute
@@ -299,6 +303,10 @@ securityPolicies:
     name: target-grpcroute-in-gateway-2
     namespace: envoy-gateway
   spec:
+    cors:
+      allowOrigins:
+      - http://*.example.com
+      maxAge: 16m40s
     targetRef:
       group: gateway.networking.k8s.io
       kind: GRPCRoute
@@ -434,7 +442,13 @@ xdsIR:
           distinct: false
           name: ""
           prefix: /
-        security: {}
+        security:
+          cors:
+            allowOrigins:
+            - distinct: false
+              name: ""
+              safeRegex: http://.*\.example\.com
+            maxAge: 16m40s
   envoy-gateway/gateway-2:
     accessLog:
       text:
@@ -468,7 +482,13 @@ xdsIR:
           name: grpcroute-1
           namespace: envoy-gateway
         name: grpcroute/envoy-gateway/grpcroute-1/rule/0/match/0/*
-        security: {}
+        security:
+          cors:
+            allowOrigins:
+            - distinct: false
+              name: ""
+              safeRegex: http://.*\.example\.com
+            maxAge: 16m40s
     tcp:
     - address: 0.0.0.0
       name: envoy-gateway/gateway-2/tcp
diff --git a/internal/gatewayapi/translator_test.go b/internal/gatewayapi/translator_test.go
index 96a88bfde..6ac3765f0 100644
--- a/internal/gatewayapi/translator_test.go
+++ b/internal/gatewayapi/translator_test.go
@@ -763,9 +763,10 @@ func TestIsValidCrossNamespaceRef(t *testing.T) {
 
 func TestServicePortToContainerPort(t *testing.T) {
 	testCases := []struct {
-		servicePort   int32
-		containerPort int32
-		envoyProxy    *egv1a1.EnvoyProxy
+		servicePort               int32
+		containerPort             int32
+		envoyProxy                *egv1a1.EnvoyProxy
+		listenerPortShiftDisabled bool
 	}{
 		{
 			servicePort:   99,
@@ -826,10 +827,15 @@ func TestServicePortToContainerPort(t *testing.T) {
 				},
 			},
 		},
+		{
+			servicePort:               99,
+			containerPort:             99,
+			listenerPortShiftDisabled: true,
+		},
 	}
-
 	for _, tc := range testCases {
-		got := servicePortToContainerPort(tc.servicePort, tc.envoyProxy)
+		translator := &Translator{ListenerPortShiftDisabled: tc.listenerPortShiftDisabled}
+		got := translator.servicePortToContainerPort(tc.servicePort, tc.envoyProxy)
 		assert.Equal(t, tc.containerPort, got)
 	}
 }