見出し画像

OpenShift Service MeshでのJWT検証

OpenShift Service Meshを使ってIDP側で発行したJWTを検証する設定をしようとしたら色々想定と違っていてハマったのでその時のメモ。

Service MeshにおけるJWT検証設定

Service MeshでJWT検証するには以下二つのリソース設定をする必要がある。

  • RequestAuthentication

  • AuthorizationPolicy

RequestAuthentication設定

まずはRequestAuthenticationの設定。
このリソースはHTTPヘッダーからJWTを取り出してその署名検証を行うリソースとなっている。以下のように設定。

apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
 name: sample-disable-mtls-with-jwt
spec:
 jwtRules:
 - forwardOriginalToken: true
   fromHeaders:
   - name: Authorization
     prefix: 'Bearer '
   issuer: https://xxxxx.com/auth/realms/saml-auth-office
   jwksUri: http://xxxxx.com:8080/auth/realms/saml-auth-office/protocol/openid-connect/certs
 selector:
   matchLabels:
     app: sample

AuthorizationPolicyの設定

次にAuthorizationPolicyの設定。
このリソースはHTTPリクエストの内容に応じて様々な認可処理を行うリソースだが、JWT検証を行う場合はJWTのPayload部が期待する内容になっているかをチェックする設定を行うのが一般的か。以下のように設定した。

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: sample-disable-mtls-with-jwt
spec:
  action: ALLOW
  rules:
  - when:
    - key: request.auth.claims[iss]
      values:
      - https://xxxxx.com/auth/realms/saml-auth-office
    selector:
    matchLabels:
      app: sample

ちなみに上記で指定しているkeyであるrequest.auth.claims[iss]の部分はRequestAuthenticationでJWT署名検証が行われた結果がここに格納されることになるもの。つまり、RequestAuthenticationでJWT検証を行なっていない(RequestAuthenticationリソースの設定をしていない)場合、このkeyにJWTの値が入ってくることはないためPayload部の検証ができず、必ず403エラーとなる。
よって、JWT検証するときはRequestAuthentication、AuthorizationPolicyをセットで設定しないと有効に機能しない、ということができる。

ハマったところと解決方法

RequestAuthentication、AuthorizationPolicyの両方の設定を行い、JWTをHTTPヘッダーにつけてリクエストしたところ、不正なJWTであろうがなかろうがHTTP 200 OKが返ってきてしまい、全くJWT検証をしてくれない。検証に失敗してエラーになるのであれば調べようもあるのだが全く無視してくるとは想定外の挙動だった…。

色々と調べたところ、以下の公式ドキュメントを見つけた。

Istio supports proxying any TCP traffic. This includes HTTP, HTTPS, gRPC, as well as raw TCP protocols. In order to provide additional capabilities, such as routing and rich metrics, the protocol must be determined. This can be done automatically or explicitly specified.

[Istio公式ドキュメント]Protocol Selection

ようはどのProtocolでProxyするかちゃんと教えてね。ということを言っている模様。読み進めるとServiceリソースに設定が足りていないのだなとわかる。今回はHTTPヘッダーに設定されているJWTを検証したいわけなのでTCPではなくHTTPのProtocolでProxyしてね、って教えてあげないとダメそう。なのでServiceリソースに以下のように設定。

apiVersion: v1
kind: Service
metadata:
  labels:
    app: sample
  name: sample
spec:
  ports:
  - appProtocol: http2
    name: tcp-8080
    port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    deployment: sample
  sessionAffinity: None
  type: ClusterIP

ポイントはspec.ports.appProtocolの部分。マニュアルに従い、この部分にhttpだよってことを教えてあげる設定をする。なお、Kubernetes 1.18 < の場合はappProtocolではなくname側にname: <protocol>[-<suffix>]というフォーマットで指定してあげる必要がある。個人的にnameに設定の意味を持たせる仕様が気に入らないのでこれは使いたくなかったのでappProtocolが使えるバージョンで本当によかった。

Service側に上記のProtocolの指定をしたらようやく想定通りに動いてくれた。Service Mesh、というかIstioのそもそもの設定をちゃんと理解していなかったのが敗因か…。

まとめ

公式ドキュメントの一部を読んだり、サンプル(productpageという公式サンプル)を試して設定ができた気になっていたが、自分のちゃんとしたアプリで設定して動かなかったので理解が甘かったと痛感。まとめると以下の理解が足りていなかった。

  • Service Meshの設定はProtocolをきちんと意識してそれを教えてあげる必要がある。

  • RequestAuthentication、AuthorizationPolicyはそれぞれ別な設定を司るリソースなので基本的な依存関係はない。しかしながらAuthorizationPolicyのruleで指定する一部のkeyはRequestAuthenticationでJWT検証した場合にしか使用できないものがある。

2点目は設定でハマった、というよりもきちんと理解できていなかったのでおまけ。ここは公式ドキュメントに記載があるが理解していないとこの仕様は読み解けないのでは、と思ったり…。

このドキュメントのrequest.auth.claimsの説明に以下のような記載がある。

Raw claims of the authenticated JWT token. The claim name is surrounded by [] without any quotes, nested claim can also be used, requires request authentication policy applied. Note only support claim of type string or list of string

[Istio公式ドキュメント]Authorization Policy Conditions

ポイントは多分、「the authenticated JWT token.」って記載。
これはようはJWT署名検証がなされた場合にのみrequest.auth.claimsに設定されるということなのかな、と(思う)。

なのでRequestAuthentication ⇨ AuthorizationPolicyの順に評価されているのだろうなぁと推測した。

おしまい。

この記事が気に入ったらサポートをしてみませんか?