Validating JWT with Istio Envoy

🌐 This document is available in both English and Ukrainian. Use the language toggle in the top right corner to switch between versions.
JWT-token (eng. JSON Web Token) — a secure data exchange format that is most commonly used to transfer sensitive information or authorize user HTTP requests. The JWT token can be signed with a secret (using the HMAC algorithm) or a public/private key pair using the RSA or ECDSA algorithms. Standardized in RFC 7519.

1. Introduction

JWT token is usually sent as a Bearer token in the header of a custom HTTP request. Before the request reaches the microservice, Istio Envoy can:

  1. Check the JWT token inside the HTTP header of the request for correctness and compliance with the established rules

  2. Pass traffic with the correct JWT token to the microservice

  3. Do not allow traffic with an incorrect JWT token.

auth

2. Configuring rules for token validation

In general, the configuration of Envoy proxy rules consists of creating the following API objects in the OpenShift cluster for each service that performs user authorization:

apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
  name: request-auth-digital-signature-ops
spec:
  jwtRules:
    - forwardOriginalToken: true
      fromHeaders:
        - name: X-Access-Token
      issuer: >-
        https://platform-keycloak.apps.cicd2.mdtu-ddm.projects.epam.com/auth/realms/mdtu-ddm-edp-cicd-platform-sit-officer-portal
      jwksUri: >-
        https://platform-keycloak.apps.cicd2.mdtu-ddm.projects.epam.com/auth/realms/mdtu-ddm-edp-cicd-platform-sit-officer-portal/protocol/openid-connect/certs
  selector:
    matchLabels:
      app: digital-signature-ops

The configuration consists of several fields:

  • forwardOriginalToken — the token from the initial request will be forwarded;

  • fromHeaders — header name with token;

  • `issuer' — the provider that generated the token;

  • jwksUri — URL of the public key of the provider set to verify the signature of the JWT token;

  • selector — the selector determines to which microservice the configuration should be applied.

To reject requests without valid JWT tokens, you need to add an authorization policy with a rule specifying the DENY' action for requests without a `RequestPrincipal', shown as `notRequestPrincipals: ["*"] in the following example.

apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: digital-signature-ops
spec:
  selector:
    matchLabels:
      app: digital-signature-ops
  action: DENY
  rules:
  - from:
    - source:
        notRequestPrincipals: ["*"]

Thus, the AuthorizationPolicy rule rejects requests without valid JWT tokens.

Next, the Istio Envoy proxy receives the configuration from istiod in the following order:

  1. When starting a new pod, using the ``MutationWebhooks'' mechanism, an additional Envoy proxy container is added to it, which is responsible for intercepting all traffic in front of the main microservice container.

  2. During initialization, the Envoy-proxy receives the necessary configuration from istiod, which contains the following information, which was specified in the previous step when creating the RequestAuthentication object:

...
{
"name": "envoy.filters.http.jwt_authn",
"typed_config": {
  "@type": "type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication",
  "providers": {
    "origins-0": {
      "issuer": "https://platform-keycloak.apps.cicd2.mdtu-ddm.projects.epam.com/auth/realms/mdtu-ddm-edp-cicd-sk-test-qa-admin",
      "local_jwks": {
          "inline_string": "<JWKS which will be received from the issuer>"
        },
      "forward": true,
      "from_headers": [{
         "name": "X-Access-Token"
       }],
      "payload_in_metadata": "https://platform-keycloak.apps.cicd2.mdtu-ddm.projects.epam.com/auth/realms/mdtu-ddm-edp-cicd-sk-test-qa-admin"
}
...
  1. In the next step, the Envoy proxy, using the URL from the issuer field, receives JWKS with a public key from the JWT token generation microservice (Keycloak) and writes it in the local_jwks field. By default, the duration after which the cached public key will expire is for 2 minutes.

  2. Next, another additional configuration is performed and soon the Envoy proxy is ready to process requests.

3. Token validation on the Envoy proxy side

Every request that comes to the microservice is intercepted by the Envoy proxy and checked for compliance specified in RequestAuthentication, namely:

  1. Checking if the JWT token is present at all

  2. Getting the JWT token from the header

  3. Validating the JWT token using the public key obtained earlier from the URL.

The following is an example of Envoy logs:

2021-12-24T12:48:45.867291Z	debug	envoy http	[C8][S790218861205563098] request end stream
2021-12-24T12:48:45.867334Z	debug	envoy jwt	Called Filter : setDecoderFilterCallbacks
2021-12-24T12:48:45.867376Z	debug	envoy jwt	Called Filter : decodeHeaders
2021-12-24T12:48:45.867393Z	debug	envoy jwt	Prefix requirement '/' matched.
2021-12-24T12:48:45.867400Z	debug	envoy jwt	extract x-access-token
2021-12-24T12:48:45.867447Z	debug	envoy jwt	Jwt authentication completed with: OK
2021-12-24T12:48:45.867497Z	debug	envoy filter	AuthenticationFilter::decodeHeaders with config
policy {
  peers {
    mtls {
      mode: PERMISSIVE
    }
  }
  origins {
    jwt {
      issuer: "https://platform-keycloak.apps.cicd2.mdtu-ddm.projects.epam.com/auth/realms/mdtu-ddm-edp-cicd-sk-test-qa-admin"
    }
  }
  origins {
    jwt {
      issuer: "https://platform-keycloak.apps.cicd2.mdtu-ddm.projects.epam.com/auth/realms/mdtu-ddm-edp-cicd-sk-test-qa-citizen-portal"
    }
  }
  origins {
    jwt {
      issuer: "https://platform-keycloak.apps.cicd2.mdtu-ddm.projects.epam.com/auth/realms/mdtu-ddm-edp-cicd-sk-test-qa-external-system"
    }
  }
  origins {
    jwt {
      issuer: "https://platform-keycloak.apps.cicd2.mdtu-ddm.projects.epam.com/auth/realms/mdtu-ddm-edp-cicd-sk-test-qa-officer-portal"
    }
  }
  origin_is_optional: true
  principal_binding: USE_ORIGIN
}
skip_validate_trust_domain: true

2021-12-24T12:48:45.867507Z	debug	envoy filter	[C8] validateX509 mode PERMISSIVE: ssl=false, has_user=false

2021-12-24T12:48:45.867616Z	debug	envoy rbac	checking request: requestedServerName: , sourceIP: 10.128.32.10:55660, directRemoteIP: 10.128.32.10:55660, remoteIP: 10.128.32.10:55660,localAddress: 10.130.18.67:8080, ssl: none, headers: ':authority', '10.130.18.67:8080'

2021-12-24T12:48:45.867628Z	debug	envoy rbac	enforced allowed, matched policy none