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検証をしてくれない。検証に失敗してエラーになるのであれば調べようもあるのだが全く無視してくるとは想定外の挙動だった…。
色々と調べたところ、以下の公式ドキュメントを見つけた。
ようはどの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の説明に以下のような記載がある。
ポイントは多分、「the authenticated JWT token.」って記載。
これはようはJWT署名検証がなされた場合にのみrequest.auth.claimsに設定されるということなのかな、と(思う)。
なのでRequestAuthentication ⇨ AuthorizationPolicyの順に評価されているのだろうなぁと推測した。
おしまい。
この記事が気に入ったらサポートをしてみませんか?