Mastering CORS Filters in Istio: Configure Envoy for Secure Cross‑Origin Requests
This guide explains what an Envoy CORS filter is, details its configuration options, and provides step‑by‑step VirtualService and EnvoyFilter examples for simple, credentialed, prefix, regex, exposed‑header, non‑simple requests and shadow logging in Istio.
What is a CORS filter
CORS (Cross‑origin resource sharing) enables resources to be shared across different domains. In Envoy, the CORS filter is an HTTP filter named envoy.filters.http.cors with type URL envoy.extensions.filters.http.cors.v3.Cors . Implementation details are omitted.
Configuration Options
<code>allow_origin_string_match: // allowed client origins
allow_methods: "GET,OPTIONS" // allowed HTTP methods
allow_headers: "content-type" // allowed request headers
allow_credentials: true // whether cookies are allowed
exposeHeaders: // response headers exposed to the caller
- test
- test2
max_age: "60" // cache duration for preflight responses
filter_enabled: // activation flag
default_value:
numerator: 0
denominator: HUNDRED
shadow_enabled: // logging flag
default_value:
numerator: 100
denominator: HUNDRED</code>Practical Examples
3.1 Simple Request
VirtualService implementation
<code>apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: bookinfo
spec:
exportTo:
- '*'
gateways:
- bookinfo-gateway
hosts:
- '*'
http:
- match:
- uri:
exact: /productpage
- uri:
prefix: /static
- uri:
exact: /login
- uri:
exact: /logout
- uri:
prefix: /api/v1/products
corsPolicy:
allowOrigins:
- exact: "http://192.168.229.134:80"
route:
- destination:
host: productpage
port:
number: 9080</code>EnvoyFilter implementation
<code>cat << EOF > ef-cors-allow_origin_string_match.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: cors
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
listener:
name: 0.0.0.0_8080
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: MERGE
value:
name: envoy.filters.network.http_connection_manager
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
codec_type: AUTO
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_route
domains:
- "*"
cors:
allow_origin_string_match:
- exact: "http://192.168.229.134"
filter_enabled:
default_value:
numerator: 100
denominator: HUNDRED
routes:
- match:
path: "/productpage"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
prefix: "/static"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
path: "/login"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
path: "/logout"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
prefix: "/api/v1/products"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
EOF
kubectl apply -f ef-cors-allow_origin_string_match.yaml -n istio-system --context context-cluster1</code>3.2 Simple Request with allowCredentials
VirtualService implementation
<code>apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: bookinfo
spec:
exportTo:
- '*'
gateways:
- bookinfo-gateway
hosts:
- '*'
http:
- match:
- uri:
exact: /productpage
- uri:
prefix: /static
- uri:
exact: /login
- uri:
exact: /logout
- uri:
prefix: /api/v1/products
corsPolicy:
allowCredentials: true
allowOrigins:
- exact: "http://192.168.229.134"
route:
- destination:
host: productpage
port:
number: 9080</code>EnvoyFilter implementation
<code>cat << EOF > ef-cors-allowCredentials.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: cors
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
listener:
name: 0.0.0.0_8080
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: MERGE
value:
name: envoy.filters.network.http_connection_manager
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
codec_type: AUTO
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_route
domains:
- "*"
cors:
allow_origin_string_match:
- exact: "http://192.168.229.134"
allow_credentials: true
filter_enabled:
default_value:
numerator: 100
denominator: HUNDRED
routes:
- match:
path: "/productpage"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
prefix: "/static"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
path: "/login"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
path: "/logout"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
prefix: "/api/v1/products"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
EOF
kubectl apply -f ef-cors-allowCredentials.yaml -n istio-system --context context-cluster1</code>3.3 Simple Request with allowOrigins prefix
VirtualService implementation
<code>apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: bookinfo
spec:
exportTo:
- '*'
gateways:
- bookinfo-gateway
hosts:
- '*'
http:
- match:
- uri:
exact: /productpage
- uri:
prefix: /static
- uri:
exact: /login
- uri:
exact: /logout
- uri:
prefix: /api/v1/products
corsPolicy:
allowOrigins:
- prefix: "http://192"
route:
- destination:
host: productpage
port:
number: 9080</code>EnvoyFilter implementation
<code>cat << EOF > ef-cors-allow_origin_string_match-prefix.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: cors
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
listener:
name: 0.0.0.0_8080
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: MERGE
value:
name: envoy.filters.network.http_connection_manager
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
codec_type: AUTO
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_route
domains:
- "*"
cors:
allow_origin_string_match:
- prefix: "http://192"
allow_credentials: true
filter_enabled:
default_value:
numerator: 100
denominator: HUNDRED
routes:
- match:
path: "/productpage"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
prefix: "/static"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
path: "/login"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
path: "/logout"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
prefix: "/api/v1/products"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
EOF
kubectl apply -f ef-cors-allow_origin_string_match-prefix.yaml -n istio-system --context context-cluster1</code>3.4 Simple Request with allowOrigins regex
VirtualService implementation
<code>apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: bookinfo
spec:
exportTo:
- '*'
gateways:
- bookinfo-gateway
hosts:
- '*'
http:
- match:
- uri:
exact: /productpage
- uri:
prefix: /static
- uri:
exact: /login
- uri:
exact: /logout
- uri:
prefix: /api/v1/products
corsPolicy:
allowOrigins:
- regex: ".*"
route:
- destination:
host: productpage
port:
number: 9080</code>EnvoyFilter implementation
<code>cat << EOF > ef-cors-allow_origin_string_match-regex.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: cors
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
listener:
name: 0.0.0.0_8080
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: MERGE
value:
name: envoy.filters.network.http_connection_manager
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
codec_type: AUTO
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_route
domains:
- "*"
cors:
allow_origin_string_match:
- safeRegex:
googleRe2: {}
regex: .*\n allow_credentials: true
filter_enabled:
default_value:
numerator: 100
denominator: HUNDRED
routes:
- match:
path: "/productpage"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
prefix: "/static"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
path: "/login"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
path: "/logout"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
prefix: "/api/v1/products"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
EOF
kubectl apply -f ef-cors-allow_origin_string_match-regex.yaml -n istio-system --context context-cluster1</code>3.5 Simple Request with exposeHeaders
VirtualService implementation
<code>apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: bookinfo
spec:
exportTo:
- '*'
gateways:
- bookinfo-gateway
hosts:
- '*'
http:
- match:
- uri:
exact: /productpage
- uri:
prefix: /static
- uri:
exact: /login
- uri:
exact: /logout
- uri:
prefix: /api/v1/products
corsPolicy:
allowOrigins:
- exact: "http://192.168.229.134"
exposeHeaders:
- test
- test2
route:
- destination:
host: productpage
port:
number: 9080</code>EnvoyFilter implementation
<code>cat << EOF > ef-cors-exposeHeaders.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: cors
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
listener:
name: 0.0.0.0_8080
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: MERGE
value:
name: envoy.filters.network.http_connection_manager
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
codec_type: AUTO
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_route
domains:
- "*"
cors:
allow_origin_string_match:
- safeRegex:
googleRe2: {}
regex: .*
exposeHeaders: test,test2
filter_enabled:
default_value:
numerator: 100
denominator: HUNDRED
routes:
- match:
path: "/productpage"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
prefix: "/static"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
path: "/login"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
path: "/logout"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
- match:
prefix: "/api/v1/products"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
EOF
kubectl apply -f ef-cors-exposeHeaders.yaml -n istio-system --context context-cluster1</code>3.6 Non‑Simple Request
VirtualService implementation
<code>apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: bookreviews
spec:
exportTo:
- '*'
gateways:
- bookinfo-gateway
hosts:
- '*'
http:
- match:
- uri:
prefix: /reviews
corsPolicy:
allowOrigins:
- exact: "http://192.168.229.134"
allowMethods:
- GET
- OPTIONS
maxAge: "1m"
allowHeaders:
- content-type
route:
- destination:
host: reviews
port:
number: 9080</code>EnvoyFilter implementation
<code>cat << EOF > ef-cors-not-simple.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: cors
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
listener:
name: 0.0.0.0_8080
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: MERGE
value:
name: envoy.filters.network.http_connection_manager
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
codec_type: AUTO
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_route
domains:
- "*"
cors:
allow_origin_string_match:
- exact: "http://192.168.229.134"
allow_methods: "GET,OPTIONS"
allow_headers: "content-type"
max_age: "60"
filter_enabled:
default_value:
numerator: 100
denominator: HUNDRED
routes:
- match:
prefix: "/reviews"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
EOF
kubectl apply -f ef-cors-not-simple.yaml -n istio-system --context context-cluster1</code>3.7 shadow_enabled
VirtualService cannot implement shadow_enabled; use EnvoyFilter
<code>cat << EOF > ef-cors-shadow.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: cors
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: NETWORK_FILTER
match:
listener:
name: 0.0.0.0_8080
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: MERGE
value:
name: envoy.filters.network.http_connection_manager
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
codec_type: AUTO
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_route
domains:
- "*"
cors:
allow_origin_string_match:
- exact: "http://192.168.229.134"
allow_methods: "GET,OPTIONS"
allow_headers: "content-type"
max_age: "60"
filter_enabled:
default_value:
numerator: 0
denominator: HUNDRED
shadow_enabled:
default_value:
numerator: 100
denominator: HUNDRED
routes:
- match:
prefix: "/reviews"
route:
cluster: outbound|9080||productpage.istio.svc.cluster.local
EOF
kubectl apply -f ef-cors-shadow.yaml -n istio-system --context context-cluster1</code>Ops Development Stories
Maintained by a like‑minded team, covering both operations and development. Topics span Linux ops, DevOps toolchain, Kubernetes containerization, monitoring, log collection, network security, and Python or Go development. Team members: Qiao Ke, wanger, Dong Ge, Su Xin, Hua Zai, Zheng Ge, Teacher Xia.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.