Repository: envoyproxy/gateway

Issue:
CTP, onInvalidMessage not implemented
*Description*:
The onInvalidMessage field defined in the ClientTrafficPolicy is not [currently passed](https://github.com/envoyproxy/gateway/blob/d65cff846fe14c750d03c3dae38e5fe060519871/internal/gatewayapi/clienttrafficpolicy.go#L743) to the IR. 

We could use [buildIRHTTP2Settings](https://github.com/envoyproxy/gateway/blob/d65cff846fe14c750d03c3dae38e5fe060519871/internal/gatewayapi/http.go#L25) insted of [translateHTTP2Settings](https://github.com/envoyproxy/gateway/blob/d65cff846fe14c750d03c3dae38e5fe060519871/internal/gatewayapi/clienttrafficpolicy.go#L743) for CTP.

Discussion:
@arkodg: cc @guydc @guydc: I think that it's a case where a shared struct was extended for the BTP case, but not implemented for CTP. Envoy supports this option in HCM, so there's no reason to not implement it... It's possible that the two functions mentioned by the user use different secure defaults, so I'm not sure that they interchangeable. 

Solution patch:
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index f1b71766b..6edca5c9d 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -36,14 +36,14 @@ jobs:
     - uses: ./tools/github-actions/setup-deps
 
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@4e94bd11f71e507f7f87df81788dff88d1dacbfb  # v3.29.5
+      uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee  # v3.29.5
       with:
         languages: ${{ matrix.language }}
 
     - name: Autobuild
-      uses: github/codeql-action/autobuild@4e94bd11f71e507f7f87df81788dff88d1dacbfb  # v3.29.5
+      uses: github/codeql-action/autobuild@0499de31b99561a6d14a36a5f662c2a54f91beee  # v3.29.5
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb  # v3.29.5
+      uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee  # v3.29.5
       with:
         category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/license-scan.yml b/.github/workflows/license-scan.yml
index d0bf94982..b43ac588e 100644
--- a/.github/workflows/license-scan.yml
+++ b/.github/workflows/license-scan.yml
@@ -18,7 +18,7 @@ jobs:
     - name: Checkout code
       uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8  # v5.0.0
     - name: Run scanner
-      uses: google/osv-scanner-action/osv-scanner-action@e92b5d07338d4f0ba0981dffed17c48976ca4730  # v2.2.3
+      uses: google/osv-scanner-action/osv-scanner-action@9bb69575e74019c2ad085a1860787043adf47ccb  # v2.2.4
       with:
         scan-args: |-  # See allowed licenses at https://github.com/cncf/foundation/blob/main/policies-guidance/allowed-third-party-license-policy.md#approved-licenses-for-allowlist
           --licenses=Apache-2.0,0BSD,BSD-2-Clause,BSD-2-Clause-FreeBSD,BSD-3-Clause,MIT,MIT-0,ISC,OpenSSL,OpenSSL-standalone,PSF-2.0,Python-2.0,Python-2.0.1,PostgreSQL,SSLeay-standalone,UPL-1.0,X11,Zlib
diff --git a/.github/workflows/osv-scanner.yml b/.github/workflows/osv-scanner.yml
index 0c28e1509..db05aef01 100644
--- a/.github/workflows/osv-scanner.yml
+++ b/.github/workflows/osv-scanner.yml
@@ -19,7 +19,7 @@ permissions:
 jobs:
   scan-scheduled:
     if: ${{ github.event_name == 'push' || github.event_name == 'schedule' }}
-    uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@e92b5d07338d4f0ba0981dffed17c48976ca4730"  # v2.2.3
+    uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@9bb69575e74019c2ad085a1860787043adf47ccb"  # v2.2.4
     with:
       scan-args: |-
         --recursive
@@ -32,7 +32,7 @@ jobs:
 
   scan-pr:
     if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }}
-    uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@e92b5d07338d4f0ba0981dffed17c48976ca4730"  # v2.2.3
+    uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@9bb69575e74019c2ad085a1860787043adf47ccb"  # v2.2.4
     with:
       scan-args: |-
         --recursive
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index bc87e003b..edf878bcb 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -40,6 +40,6 @@ jobs:
         retention-days: 5
 
     - name: "Upload to code-scanning"
-      uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb  # v3.29.5
+      uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee  # v3.29.5
       with:
         sarif_file: results.sarif
diff --git a/internal/envoygateway/config/loader/configloader.go b/internal/envoygateway/config/loader/configloader.go
index 8bd284262..e32a1a27e 100644
--- a/internal/envoygateway/config/loader/configloader.go
+++ b/internal/envoygateway/config/loader/configloader.go
@@ -10,6 +10,7 @@ import (
 	"io"
 	"time"
 
+	"github.com/envoyproxy/gateway/api/v1alpha1/validation"
 	"github.com/envoyproxy/gateway/internal/envoygateway/config"
 	"github.com/envoyproxy/gateway/internal/filewatcher"
 	"github.com/envoyproxy/gateway/internal/logging"
@@ -67,6 +68,12 @@ func (r *Loader) Start(ctx context.Context, logOut io.Writer) error {
 					// TODO: add a metric for this?
 					continue
 				}
+
+				if err := validation.ValidateEnvoyGateway(eg); err != nil {
+					r.logger.Error(err, "failed to validate EnvoyGateway config")
+					continue
+				}
+
 				// Set defaults for unset fields
 				eg.SetEnvoyGatewayDefaults()
 				r.cfg.EnvoyGateway = eg
diff --git a/internal/gatewayapi/clienttrafficpolicy.go b/internal/gatewayapi/clienttrafficpolicy.go
index 1577c8461..925683b20 100644
--- a/internal/gatewayapi/clienttrafficpolicy.go
+++ b/internal/gatewayapi/clienttrafficpolicy.go
@@ -449,9 +449,11 @@ func (t *Translator) translateClientTrafficPolicyForListener(policy *egv1a1.Clie
 		}
 
 		// Translate HTTP2 Settings
-		if err = translateHTTP2Settings(policy.Spec.HTTP2, httpIR); err != nil {
+		if h2, err := buildIRHTTP2Settings(policy.Spec.HTTP2); err != nil {
 			err = perr.WithMessage(err, "HTTP2")
 			errs = errors.Join(errs, err)
+		} else {
+			httpIR.HTTP2 = h2
 		}
 
 		// enable http3 if set and TLS is enabled
@@ -745,52 +747,6 @@ func translateHTTP1Settings(http1Settings *egv1a1.HTTP1Settings, connection *ir.
 	return nil
 }
 
-func translateHTTP2Settings(http2Settings *egv1a1.HTTP2Settings, httpIR *ir.HTTPListener) error {
-	if http2Settings == nil {
-		return nil
-	}
-
-	var (
-		http2 = &ir.HTTP2Settings{}
-		errs  error
-	)
-
-	if http2Settings.InitialStreamWindowSize != nil {
-		initialStreamWindowSize, ok := http2Settings.InitialStreamWindowSize.AsInt64()
-		switch {
-		case !ok:
-			errs = errors.Join(errs, fmt.Errorf("invalid InitialStreamWindowSize value %s", http2Settings.InitialStreamWindowSize.String()))
-		case initialStreamWindowSize < MinHTTP2InitialStreamWindowSize || initialStreamWindowSize > MaxHTTP2InitialStreamWindowSize:
-			errs = errors.Join(errs, fmt.Errorf("InitialStreamWindowSize value %s is out of range, must be between %d and %d",
-				http2Settings.InitialStreamWindowSize.String(),
-				MinHTTP2InitialStreamWindowSize,
-				MaxHTTP2InitialStreamWindowSize))
-		default:
-			http2.InitialStreamWindowSize = ptr.To(uint32(initialStreamWindowSize))
-		}
-	}
-
-	if http2Settings.InitialConnectionWindowSize != nil {
-		initialConnectionWindowSize, ok := http2Settings.InitialConnectionWindowSize.AsInt64()
-		switch {
-		case !ok:
-			errs = errors.Join(errs, fmt.Errorf("invalid InitialConnectionWindowSize value %s", http2Settings.InitialConnectionWindowSize.String()))
-		case initialConnectionWindowSize < MinHTTP2InitialConnectionWindowSize || initialConnectionWindowSize > MaxHTTP2InitialConnectionWindowSize:
-			errs = errors.Join(errs, fmt.Errorf("InitialConnectionWindowSize value %s is out of range, must be between %d and %d",
-				http2Settings.InitialConnectionWindowSize.String(),
-				MinHTTP2InitialConnectionWindowSize,
-				MaxHTTP2InitialConnectionWindowSize))
-		default:
-			http2.InitialConnectionWindowSize = ptr.To(uint32(initialConnectionWindowSize))
-		}
-	}
-
-	http2.MaxConcurrentStreams = http2Settings.MaxConcurrentStreams
-
-	httpIR.HTTP2 = http2
-	return errs
-}
-
 func translateHealthCheckSettings(healthCheckSettings *egv1a1.HealthCheckSettings, httpIR *ir.HTTPListener) {
 	// Return early if not set
 	if healthCheckSettings == nil {
diff --git a/release-notes/current.yaml b/release-notes/current.yaml
index 19e589cdf..59cf04af4 100644
--- a/release-notes/current.yaml
+++ b/release-notes/current.yaml
@@ -11,6 +11,7 @@ new features: |
 
 bug fixes: |
  - Fixed Listener port limit typo 65353 -> 65535.
+ - Fixed issue where reloading invalid envoy gateway configuration.
 # Enhancements that improve performance.
 performance improvements: |
 



Test patch:
diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-http2.in.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-http2.in.yaml
index fd435bbb5..bbb3eb9e5 100644
--- a/internal/gatewayapi/testdata/clienttrafficpolicy-http2.in.yaml
+++ b/internal/gatewayapi/testdata/clienttrafficpolicy-http2.in.yaml
@@ -9,6 +9,7 @@ clientTrafficPolicies:
       initialStreamWindowSize: 64Ki
       initialConnectionWindowSize: 32Mi
       maxConcurrentStreams: 200
+      onInvalidMessage: TerminateConnection
     targetRef:
       group: gateway.networking.k8s.io
       kind: Gateway
diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-http2.out.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-http2.out.yaml
index 8ee7f5817..c89f9102c 100644
--- a/internal/gatewayapi/testdata/clienttrafficpolicy-http2.out.yaml
+++ b/internal/gatewayapi/testdata/clienttrafficpolicy-http2.out.yaml
@@ -9,6 +9,7 @@ clientTrafficPolicies:
       initialConnectionWindowSize: 32Mi
       initialStreamWindowSize: 64Ki
       maxConcurrentStreams: 200
+      onInvalidMessage: TerminateConnection
     targetRef:
       group: gateway.networking.k8s.io
       kind: Gateway
@@ -191,6 +192,7 @@ xdsIR:
         initialConnectionWindowSize: 65536
         initialStreamWindowSize: 33554432
         maxConcurrentStreams: 200
+        resetStreamOnError: false
       isHTTP2: false
       metadata:
         kind: Gateway
@@ -206,8 +208,6 @@ xdsIR:
       externalPort: 8080
       hostnames:
       - www.example.com
-      http2:
-        maxConcurrentStreams: 200
       isHTTP2: false
       metadata:
         kind: Gateway