Page
Use the Swagger Try It Out feature for Connectivity Link with EnvoyFilter
A multi-cloud GitOps hub-only lab validates these patterns and deploys the AI Computer Vision pattern repository. By completing the Unlock self-service API credentials on Connectivity Link learning path first, that allows you to configure the Kuadrant AuthPolicy, HTTPRoute ResponseHeaderModifier Cross-Origin Resource Sharing (CORS) headers, and Developer Hub Swagger integration. This article shows how a simple EnvoyFilter layer completes the browser experience on Connectivity Link after those controls are already in place.
Machine-to-machine clients, such as curl, and backend services, send authorization headers directly. Browser-based Swagger Try it out in Developer Hub is different; when a non-simple header such as Authorization: Bearer or Authorization: APIKEY is present, the browser sends a CORS preflight (OPTIONS) before the real request.
Red Hat Connectivity Link already does the heavy lifting by the Kuadrant AuthPolicy authenticating requests, and HTTPRoute ResponseHeaderModifier supplying Access-Control-Allow-* headers. For browser Swagger clients, Connectivity Link also skips JSON Web Token (JWT) or API key checks on OPTIONS through an AuthPolicy spec.when definition.
Some gateway API HTTPRoute filter sets do not include a direct respond-with-204 action. When the backend API or the gateway listener still returns a non-2xx status for OPTIONS, browsers block Try it out even though Connectivity Link policies are correct. A small EnvoyFilter custom resource on the Connectivity Link Gateway workload closes that gap. EnvoyFilter comes from OpenShift Service Mesh (Istio): it is a Kubernetes resource that patches the Envoy proxy in the gateway pods, not a command-line tool. Lua returns HTTP 204 for OPTIONS before traffic reaches backends, and an optional access-log patch surfaces AuthPolicy identity (auth_client_id) for operators.
Prerequisites:
- Completion of these lessons:
- Have the
AuthPolicyOPTIONSexemption, andHTTPRouteCORS headers in place. - Verify that the Developer Hub, workshop APIs, and Neuroface Gateways are accessible.
In this lesson, you will:
- Enable Swagger UI and Developer Hub browser clients to call Connectivity Link Gateways without "Failed to fetch" errors or blocked CORS preflight requests.
- Learn and follow the Connectivity Link-first order:
HTTPRouteCORS headers, thenAuthPolicyOPTIONSexemption, then a smallEnvoyFilterLua 204 only if browsers still need a preflight short-circuit. - See who called the API (
auth_client_id) in gateway access logs afterAuthPolicysucceeds. - Keep reusable
EnvoyFilterpatterns separate from workshop-specific or sandbox-only exceptions.
Stay on Connectivity Link first
Prefer Connectivity Link and Gateway API controls first. Add an EnvoyFilter only if the browser still blocks Swagger Try it out after those controls. A preflight short-circuit means Envoy answers OPTIONS with HTTP 204 immediately, so the request never reaches the back end. It is not a quiz. Work through these checks in order:
- Can
HTTPRouteResponseHeaderModifierset CORS headers? If yes, start there. - Can
AuthPolicyexempt OPTIONS with aspec.whendefinition? If yes, add a JWT or API key for authentication. - Does preflight still return a non-2xx OPTIONS? If yes, add
EnvoyFilterLua 204. - Does the backend API break the HTTP contract? For example, does it redirect
POST, or does it not implementOPTIONS? If yes, fix that backend API or use a request rewrite. - Is the Envoy or WebAssembly (WASM) version incompatible? If yes, use the sandbox exception only. See the warning regarding the pattern for removing WASM and MaaS headers in the Developer Sandbox environment.
Figure 1 illustrates the decision flow. The workflow starts with HTTPRoute CORS headers and AuthPolicy OPTIONS exemption before adding EnvoyFilter Lua patches or backend-specific rewrites.

A reusable pattern for CORS preflight short-circuit
Swagger UI sends a non-simple authorization header. Browsers issue a CORS preflight (OPTIONS) before the real request. If the backend API or gateway returns anything other than 2xx for OPTIONS, the browser blocks the call, even when CORS response headers are present on other status codes.
As a solution, you can insert a Lua HTTP filter on the gateway workload that responds to OPTIONS with HTTP 204 before traffic reaches backends. The following example is from the charts/all/neuroface-gateway/templates/envoyfilter-cors-preflight.yaml file:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: neuroface-gateway-cors-preflight
namespace: neuroface-gateway-system
spec:
workloadSelector:
labels:
gateway.networking.k8s.io/gateway-name: neuroface-gateway
configPatches:
- applyTo: HTTP_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
subFilter:
name: envoy.filters.http.router
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.lua.cors_preflight
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inlineCode: |
function envoy_on_request(request_handle)
local headers = request_handle:headers()
if headers:get(":method") == "OPTIONS" then
request_handle:respond({[":status"] = "204"}, "")
end
endImportant
The Lua filter sets status only. Access-Control-Allow-* headers still come from each HTTPRoute's ResponseHeaderModifier. Duplicating CORS headers in Lua causes browser errors.
Where this pattern is applied
The YAML above is the neuroface-gateway copy of that Lua 204 pattern. Apply the same EnvoyFilter once per Connectivity Link Gateway, because the workloadSelector matches a single gateway name. This table lists each gateway in the lab, its namespace, and the EnvoyFilter name:
Gateway | Namespace | EnvoyFilter name |
|---|---|---|
|
|
|
|
|
|
|
| a |
It is important to note that you should copy one EnvoyFilter per gateway because the workloadSelector matches a single gateway name. For example, the neuroface-gateway uses the charts/all/neuroface-gateway/templates/envoyfilter-cors-preflight.yaml file.
The workshop chart ships workshop-apis-gateway-cors-preflight and ai-gateway-cors-preflight as separate EnvoyFilter documents in one multi-document Helm template, charts/all/workshop-kuadrant-apis/templates/envoyfilter-cors-preflight.yaml. This same file also includes the workshop-specific filters for the cities rewrite as described below.
The ai-gateway is an optional third gateway in the workshop chart for AI catalog demos. This is outside the Unlock self-service API credentials on Connectivity Link learning path, which only covers the workshop-apis-gateway and neuroface-gateway.
Figure 2 shows how the three CORS layers work together. The ResponseHeaderModifier supplies the Access-Control-* headers; AuthPolicy skips JWT validation on OPTIONS; EnvoyFilter Lua returns HTTP 204 so browsers accept preflight.

Verify preflight with curl
curl -sk -X OPTIONS \
-H "Origin: https://developer-hub.<apps-domain>" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: authorization,content-type" \
-w "\nHTTP %{http_code}\n" \
"https://neuroface-cv.<apps-domain>/health"The expected output is an HTTP 204 with Access-Control-Allow-Origin from the HTTPRoute filter. Then confirm Swagger Try it out succeeds on an authenticated endpoint in Developer Hub.
Important
This pattern exception is only for the workshop countries’ API. It is not a reusable Connectivity Link CORS pattern.
Problem
The external countriesnow.space API returns HTTP 301 on POST /countries/cities, redirecting to a GET URL API call. Envoy forwards a relative location header. The browser follows it to a path that does not match any HTTPRoute, returning 404 without CORS headers. Swagger shows "Failed to fetch". Rewriting the response location header caused redirect loops.
Solution
Rewrite the request before it leaves the gateway by intercepting POST /countries/cities, read the JSON body, and convert to GET /countries/cities/q?country=<value>, the country name from the JSON country field, for example, Argentina. The Multicloud GitOps charts ship this as a third EnvoyFilter resource, workshop-apis-gateway-restcountries-cities-rewrite, in the same multi-document Helm template as the CORS preflight filters, see the charts/all/workshop-kuadrant-apis/templates/envoyfilter-cors-preflight.yaml file for details. The Lua filter is inserted before the router so it sees the external path /countries/cities, then the existing HTTPRoute URLRewrite adds the backend path /api/v0.1 prefix.
Do not treat this rewrite as a Connectivity Link pattern. Prefer fixing the backend API so it accepts POST, or adjusting the OpenAPI specification to use GET directly.
Important
A shortcoming with Lua to consider, is after calling request_handle:body(), this re-fetches the headers with request_handle:headers(). Reusing a handle captured before body(), triggers Envoy Lua scope errors.
A pattern for WASM removal and MaaS header in the Developer Sandbox environment
Important
This exercise is for the Red Hat Developer Sandbox only, and not for production environments.
Problem
On Red Hat OpenShift AI disposable sandboxes, gateways using data-science-gateway-class might run an Envoy version incompatible with the Kuadrant WebAssembly (WASM) module shipped in Red Hat Connectivity Link 1.4.1. That module runs inside Envoy and enforces AuthPolicy and rate limits. Gateway pods crash-loop or return HTTP 500.
Solution
The following sandbox-only workaround removes Kuadrant WASM filters from gateways in openshift-ingress. It disables AuthPolicy and rate limit enforcement on affected listeners, so it is not a Connectivity Link production configuration. Use only on disposable demo clusters, for example, Red Hat Product Demo System sandboxes. The Helm chart charts/all/models-as-a-service/templates/envoyfilter-wasm-fix.yaml applies a REMOVE patch such as:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: maas-gateway-wasm-removal
namespace: openshift-ingress
spec:
priority: 10
configPatches:
- applyTo: HTTP_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
subFilter:
name: envoy.filters.http.wasm
patch:
operation: REMOVEA companion Lua filter injects X-MaaS-Username and X-MaaS-Group headers required by the Models as a Service (MaaS) API backend when Kuadrant authentication is bypassed.
Disable the sandbox workaround with the following Helm values:
gateway:
wasmFix:
enabled: false
injectMaasHeaders: falseA pattern for identifying clients making API calls in the access log
Problem
The Kuadrant WASM module does not always expose ext_authz metadata in Envoy access logs. Operators cannot see which OpenID Connect (OIDC) client made an API call from the default istio-proxy logs.
Solution
When AuthPolicy injects response headers such as x-auth-client-id, an EnvoyFilter can merge a JSON access log format that reads those headers. The following example is from the Helm chart, charts/all/neuroface-gateway/templates/envoyfilter-access-log.yaml:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: neuroface-gateway-custom-access-log
namespace: neuroface-gateway-system
spec:
workloadSelector:
labels:
gateway.networking.k8s.io/gateway-name: neuroface-gateway
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
patch:
operation: MERGE
value:
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
access_log:
- name: envoy.access_loggers.file
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
path: /dev/stdout
log_format:
json_format:
timestamp: "%START_TIME%"
method: "%REQ(:METHOD)%"
path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
response_code: "%RESPONSE_CODE%"
auth_client_id: "%REQ(x-auth-client-id)%"
auth_metadata: "%REQ(x-auth-metadata)%"This technique is portable to any gateway where AuthPolicy sets identifiable response headers on success. After applying Pattern C, authenticated requests show auth_client_id in the gateway logs; OPTIONS preflight, and unauthenticated API calls show null.
Observe EnvoyFilters in OpenShift Service Mesh
In the OpenShift web console, click Workloads > Pods > neuroface-gateway-istio-* > Service Mesh > Overview.
Figure 3 shows EnvoyFilters on the Connectivity Link Gateway.

Kiali exposes the same gateway workload. Under Logs, you should see the container istio-proxy. The custom access log format shows JWT client identity after applying the pattern on step 4. Figure 4 shows an authenticated request.

Operational checklist for each gateway
Use this checklist once per Connectivity Link Gateway when you enable browser Swagger, and again if Try it out starts failing. These are not ongoing health probes. Every item must be true on that gateway, or the browser will still block preflight.
- The HTTPRoute ResponseHeaderModifier sets Access-Control-Allow-Origin to the Developer Hub URL.
- AuthPolicy includes when: request.method != "OPTIONS".
- EnvoyFilter Lua returns 204 for OPTIONS on this Gateway workload.
- The OPTIONS test returns 204 and CORS headers.
- Swagger Try it out succeeds on an authenticated endpoint.
- Document any backend-specific rewrite separately from reusable patterns.
Learning path summary
You assembled this solution with the multi-cloud GitOps validated pattern on a single hub cluster, using one Pattern CR and the values-hub-only.yaml overlay against the AI Computer Vision repository. That deployment gave you Red Hat Connectivity Link Gateways, Red Hat Developer Hub, Red Hat build of Keycloak, and the NeuroFace demo workloads from GitOps.
You then configured two self-service authentication flows:
- API keys for an external REST API on workshop-apis, with AuthPolicy, PlanPolicy tiers, and Developer Hub key provisioning.
- OIDC client credentials for the Computer Vision inference API on neuroface-cv, with AuthPolicy JWT validation and plan-based rate limits.
You then configured the Red Hat Connectivity Link Gateway API, and Kuadrant AuthPolicy to configure rate limits for authentication and covers most CORS needs. You used the reusable EnvoyFilter Lua 204 pattern for each gateway only to finish browser preflight when listeners still need an explicit 204. Then you strengthen the Connectivity Link operations by creating a pattern to surface auth_client_id in gateway access logs. This keeps backend API rewrites and WASM removal as sandbox or workshop exceptions, not the default path.
Developers now request credentials from Developer Hub, and Kuadrant enforces authentication at the Gateway edge.
Next
If Swagger Try it out still fails after AuthPolicy and HTTPRoute CORS headers, continue with these resources: