In Istio 1.6.13 we use JWT authentication via security.istio.io/v1beta1/RequestAuthentication
and security.istio.io/v1beta1/AuthorizationPolicy
attached to an Istio ingress gateway, and the corsPolicy
feature in VirtualService
s attached to that gateway to allow cross origin requests from our JavaScript based web app.
When JWT validation is successful, requests are successfully passed to our backend services, and responses include the proper CORS headers as defined in the VirtualServices. However, when JWT validation fails, responses do NOT include the proper CORS headers, which leads to browsers throwing CORS errors instead of returning the 40x responses.
I would expect JWT validation failures from RequestAuthentication
and authorization failures from AuthorizationPolicy
to still respond with CORS headers as defined in the corsPolicy
of VirtualServices
.
Here is output from curl while making a cross origin request to the Istio gateway WITH proper JWT token, notice that it does return expected CORS response headers:
$ curl -i \
-H 'Origin: https://localhost' \
-H 'Access-Control-Request-Method: POST,PUT' \
-H 'Access-Control-Request-Headers: foo' \
https://a.example.com \
-H 'Authorization: Bearer <REDACTED valid token>'
HTTP/2 200
access-control-allow-origin: https://localhost
access-control-allow-credentials: true
access-control-allow-methods: GET,HEAD,OPTIONS
access-control-allow-headers: foo
access-control-max-age: 1800
date: Mon, 04 Jan 2021 18:55:17 GMT
server: istio-envoy
Compared to cross origin request to the gateway WITHOUT proper JWT token, notice that it DOES NOT return expected CORS response headers:
$ curl -i \
-H 'Origin: https://localhost' \
-H 'Access-Control-Request-Method: POST,PUT' \
-H 'Access-Control-Request-Headers: foo' \
https://a.example.com \
-H 'Authorization: Bearer <REDACTED expired token>'
HTTP/2 401
content-length: 14
content-type: text/plain
date: Mon, 04 Jan 2021 19:00:17 GMT
server: istio-envoy
Jwt is expired
I am using a RequestAuthentication
resource, specifying JWT authentication for the gateway similar to below:
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: foo
namespace: istio-system
spec:
jwtRules:
- forwardOriginalToken: true
fromHeaders:
- name: Authorization
prefix: 'Bearer '
issuer: https://your.jwt.issuer
jwksUri: https://your.jwks.endpoint
selector:
matchLabels:
app: foo
An AuthorizationPolicy
resource, disallowing non-authenticated requests to the gateway similar to below:
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: foo
namespace: istio-system
spec:
action: ALLOW
rules:
- to:
- operation:
paths:
- /*
when:
- key: request.auth.claims[iss]
values:
- https://your.jwt.issuer
selector:
matchLabels:
app: foo
Finally, our VirtualService
resource exposing your service behind the gateway with a corsPolicy
is similar to:
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
labels:
app: bar
name: bar
namespace: default
spec:
gateways:
- istio-system/foo
hosts:
- a.example.com
http:
- corsPolicy:
allowCredentials: true
allowHeaders:
- foo
allowMethods:
- GET
- HEAD
- OPTIONS
allowOrigin:
- https://localhost
allowOrigins:
- exact: https://localhost
maxAge: 30m
route:
- destination:
host: bar.default.svc.cluster.local
Is it expected in Istio 1.6.13 to be missing the CORS headers for all failed JWT validations? We hate to remove JWT validation from the gateway and need to handle it in the upstream app but don’t see a way to do so in Istio.