Cloud Native 16 min read

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.

Ops Development Stories
Ops Development Stories
Ops Development Stories
Mastering CORS Filters in Istio: Configure Envoy for Secure Cross‑Origin Requests

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>
cloud-nativeconfigurationistioCORSEnvoyEnvoyFilterVirtualService
Ops Development Stories
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.