Валідація JWT-токенів за допомогою Istio Envoy

JWT-токен (англ. JSON Web Token) — формат безпечного обміну даними, який використовується найчастіше для передачі чутливої інформації або авторизації HTTP-запитів користувачів. JWT-токен може бути підписаний секретом (за допомогою HMAC алгоритму) або пар відкритий/приватний ключ використовуючи алгоритми RSA або ECDSA. Стандартизований у RFC 7519.

Вступ

JWT-токен зазвичай відправляється як Bearer токен у заголовку користувацького HTTP запиту. Перед тим як запит досягне мікросервісу, Istio Envoy може:

  1. Перевірити JWT-токен всередині заголовку HTTP запиту на коректність та відповідність встановленим правилам

  2. Пропускати трафік з коректним JWT-токеном у мікросервіс

  3. Не пропускати трафік з не коректним JWT-токеном.

auth

1. Конфігурація правил для валідації токенів

Загалом, конфігурація Envoy проксі правил складається зі створення наступних API обʼєктів у OpenShift кластері для кожного сервісу, який виконує авторизацію користувачів:

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

Конфігурація складається з декількох полів:

  • forwardOriginalToken — токен з початкового запиту буде переданий далі;

  • fromHeaders — імʼя заголовока з токеном;

  • issuer — постачальник який сгенерував токен;

  • jwksUri — URL-адреса відкритого ключа постачальника, встановленого для перевірки підпису JWT-токена;

  • selector — селектор визначає до якого мікросервісу треба застосувати конфігурацію.

Щоб відхилити запити без коректних JWT-токенів, треба додати політику авторизації з правилом, що вказує дію DENY для запитів без RequestPrincipal, що відображаються як notRequestPrincipals: ["*"] у наступному прикладі.

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: ["*"]

Таким чином, правило AuthorizationPolicy відхиляє запити без коректних JWT-токенів.

Далі Istio Envoy проксі отримує конфігурацію з istiod у наступному порядку:

  1. При старті нової поди, за допомогою механізму MutationWebhooks, у неї додається додатковий контейнер Envoy проксі, який відповідає за перехоплення усього трафіку перед основним контейнером мікросервісу.

  2. При ініціалізації Envoy-проксі отримує необхідну конфігурацію від istiod, яка містить у собі наступну інформацію, яку було задано на минулому кроці при створенні RequestAuthentication обʼєкту:

...
{
"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 який буде отримано від 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. На наступному кроці Envoy проксі використовуючи URL з поля issuer отримує JWKS з відкритим ключем від мікросервісу генерації JWT-токенів (Keycloak) та записує його у local_jwks поле. За замовчуванням, тривалість, після якої термін дії кешованого відкритого ключа закінчиться дорівнює 2 хвилинам.

  2. Далі виконується інша додаткова конфігурація та невдовзі Envoy проксі готовий обробляти запити.

2. Валідація токенів на стороні Envoy проксі

Кожний запит який надходить на мікросервіс перехоплюється Envoy проксі та перевіряється на відповідність вказану у RequestAuthentication, а саме:

  1. Перевірка чи присутній JWT-токен взагалі

  2. Отримання JWT-токена з заголовку

  3. Перевірка JWT-токена за допомогою відкритого ключа отриманого раніше з URL.

Далі наведений приклад Envoy логів:

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