許可の経路と拒否の条件を一緒に読む
一言でいうと
認証情報を得ることと、その情報を満たしたときだけ許可することは、別の作業です。このラボは、実際のIstioのサイドカーがある4つの決済サービスを比較しながら、ポリシーを1つ追加したのに、アクセスできるリクエストがむしろ増えてしまう事故を調査します。
なぜ必要なのか
ordersサービスだけが決済APIを呼び出せるようにしたチームがあるとします。セキュリティ点検で、ユーザーのJWTも必要だという要求が出ました。チームは、既存のワークロード許可のポリシーを残したまま、JWTの主体があるリクエストを許可するポリシーを追加しました。ファイル名もrequire-jwtにしました。レビューでは、ポリシー2つをどちらも読んだので、条件も2つになったと思いました。
ところが、トークンのないordersのリクエストが通り続けました。さらに驚くべきことは、正常なトークンを持つstrangerも通ったという点です。ファイル名は実行上の意味ではありません。サーバーが評価するのは、ポリシーの組み合わせです。既存の許可と新しい許可のどちらかを満たせば開く状況では、ポリシーを追加することが、扉をもう1つ作ることになる場合があります。
これは、YAMLの単語を1つ変えるだけの問題ではありません。リクエストの送信元、ユーザートークン、メソッド、パス、ポートが、それぞれどの条件に結び付いているかを確認する必要があります。成功するリクエストを1つ送っただけでは、この事故は見えません。正常なユーザーが成功する条件と、見知らぬユーザーが失敗する条件を、同じ表に置く必要があります。
どう動くのか
ラボのVMのica-policyネームスペースには、ordersとstrangerという2つのクライアントがあります。それぞれ別のServiceAccountを使います。baseline、union、intersection、scopedは、同じ応答を返す4つのサーバーですが、ポリシーを互いに独立して付けられます。ラボは、外部のIdPに接続せず、VMで生成したRSA鍵と合成のJWTを使います。実際のユーザートークンや本番の鍵を持ち込まないでください。
| 比較の対象 | 調べる問い |
|---|---|
| baseline | ワークロードの許可だけがあるとき、トークンのないordersはどうなるか |
| union | JWTの許可を別に追加すると、strangerや別のパスも開くか |
| intersection | 同じsourceに2種類のアイデンティティを入れると、何が変わるか |
| scoped | JWTなしのDENYを適用しながら、ほかのポートをどう保全するか |
principalsはmTLSで確認したワークロードのアイデンティティで、requestPrincipalsは検証されたJWTのリクエストの主体です。2つの値は、同じ人の別の名前ではありません。サーバー間の接続の送信元と、その接続を通じて伝えられたユーザーの主張を、それぞれ扱います。同じsourceのフィールドを一緒に要求することはできますが、from項目を2つに分けると、意味が変わります。1つの項目の中での結合と、リストでの選択を、図で考えてみてください。
ALLOWポリシー同士では、どれか1つが一致する道が残るかを見ます。DENYを使う場合は、まず拒否の条件を検査し、拒否されなかったリクエストに対して、既存の最小権限の許可が動作するように設計します。このラボのscopedは、トークンのない8080のリクエストだけを拒否します。8081まで保護すると説明せず、実際にordersのトークンのない8081のリクエストを許可する、比較の事例として残します。
ポートの範囲は、付随的な飾りではありません。HTTP専用の属性を使ったDENYを設計するときは、ほかのプロトコルやポートに及ぼす影響まで考える必要があります。ここでは、2つのHTTPポートだけを実測します。ラボの結果から、TCP全体やアンビエントメッシュまで検証したと、広げて言うことはしません。
現場での姿
ポリシーの変更依頼書をレビューするときは、要求事項を短い文でまず書いてみてください。たとえば「ordersから、検証された決済用JWTで、POST /v1/chargesに到達するリクエストだけを許可する」です。次に、カンマで分けた条件が、1つのルールに結び付いているか、ほかの許可ポリシーがその条件を迂回していないかを確認します。最後に、この文の単語を1つずつ変えたリクエストを送ります。送信元をstrangerに、トークンをなしに、パスを/admin/testに変えても結果が同じなら、要求事項を十分に実装できていない可能性があります。
403だけを収集しても、原因は確定しません。今回の固定バージョンでの実測では、別のaudienceのトークンも403を返しましたが、本文はJWTのaudience拒否で、ワークロードのアイデンティティやパスの認可の拒否は、RBAC拒否でした。逆に、期限切れ・別の発行者・誤った形式は401でした。応答コード、本文、適用されたポリシーを一緒に記録して初めて、次の人がJWT検証と認可を混同せずに済みます。
ポリシーの保存の成功は、データプレーンへの反映の完了ではありません。このラボの採点は、同じリクエストの集合が2回連続で一致するかも確認します。applyした直後に失敗したなら、まず観測コマンドで現在の応答を見て、伝播が収束してから、もう一度採点してください。失敗をなくそうとして条件をさらに広げることは、復旧ではありません。
次のラボですること
まず、6つのPodの現在のUIDを記録します。続いて、baselineと、意図的に誤ったunionを比較し、intersectionとscopedを正しく構成します。intersectionの初期のaudienceの一覧は、決済用と別のAPI用の両方を許可するように広く置かれているので、決済用1つに絞ります。最後に、ポートの境界の実測と、事故のレポートを残します。
4つの比較の対象は、最後まで一緒に残ります。したがって、全体の採点は、以前のステップの履歴上の成功の印を信じず、現在の状態をもう一度検査します。ただし、unionは、事故を再現するためにわざと広く開けてある隔離の対象です。この構成を本番環境に持ち込まないでください。VMを終了すると、合成の鍵と作業ファイルも一緒に回収されます。
公式ドキュメントでさらに確認する
- AuthorizationPolicy: ポリシーの組み合わせ、Sourceフィールドの結合、DENYのポートの範囲。
- RequestAuthentication: issuer、audiences、公開鍵の素材の意味。
- JWTの認可のラボ: 認証と認可を分けて検証する、公式の例。