TT Lab
はじめる
学ぶ 学習パス コース

ICA — Istio認定アソシエイト

ポリシーを追加したら決済APIが開いた

TT Labで続きを見る

目標

実際のIstioのサイドカーで、ワークロードのアイデンティティとJWTの主体の組み合わせを検証し、許可の範囲が広がってしまった事故を直します。

なぜ重要なのか

ファイル名やkubectl applyの成功だけでは、セキュリティを確認できません。送信元・トークン・メソッド・パス・ポートを変えた実際のリクエストで、正常な許可と迂回の拒否を一緒に確認します。4つの比較用サーバーは最後まで残るので、全体の採点のときにも再び検査します。

環境と安全の範囲

このVMの中のica-policyネームスペースだけを変更してください。本番のクラスターや外部のIdPではありません。k3s v1.35.8+k3s1とIstio 1.31.0が用意されていて、orders・strangerと4つのサーバーの実際のコンテナが実行されます。公開鍵は用意された素材を使い、実際のユーザートークンは入れません。unionは、事故を再現するためにわざと広く許可する隔離の対象です。セッションを終了すると、VMと作業ファイルが回収されます。

すべてのポリシーはsecurity.istio.io/v1を使い、リクエストのヘルパーはpython3 /opt/fixtures/ica-policy/runtime.py observe です。ヘルパーはポリシーを変更せず、実際のHTTPコードと本文を出力します。ポリシーを適用した直後は、伝播を待ってから再観測してください。採点は、2回連続で同じ結果が出て初めて通過します。

ステップ

  1. ica-policyネームスペースのPodの一覧を取得し、apiVersion・kindと、各itemsのmetadata.name・metadata.uidだけを抜き出して、/root/ica-policy/inventory.jsonに保存してください。サイドカーの詳細を含む全体の出力は、入力の制限128KiBを超えることがあります。baseline・union・intersection・scoped・orders・strangerの6つが実際にReadyで、サイドカーがある必要があります。Podを作り直した場合は、UIDも記録し直してください。
  2. /root/ica-policy/baseline.yamlに、ica-policyのallow-baselineというAuthorizationPolicyを作成して適用してください。app=baseline、action=ALLOW、単一のルールのsource.principalsはcluster.local/ns/ica-policy/sa/orders 1つ、operationはPOSTと/v1/chargesが1つずつです。JWTの条件は入れないでください。baselineの観測で、ordersの正常・トークンなしは200、strangerの正常なトークンは403であることを確認してください。
  3. 隔離されたunionの対象でだけ、意図的に誤った構成を再現します。/root/ica-policy/union.yamlに、ica-policyのjwt-unionというAuthorizationPolicyを作成・適用してください。app=union、action=ALLOW、rules 1つのfrom 1つに、requestPrincipals=[https://issuer.example.invalid/*]だけを置き、toは入れないでください。既存のallow-unionは保持してください。unionの観測で、トークンのないorders、正常なトークンのstranger、正常なordersのGET /admin/testまで200であることを確認してください。本番に適用しないでください。
  4. /root/ica-policy/intersection.yamlに、ica-policyのallow-intersectionのポリシーを作成・適用してください。app=intersection、action=ALLOW、単一のルールの同じsourceに、principals=[cluster.local/ns/ica-policy/sa/orders]と、requestPrincipals=[https://issuer.example.invalid/*]を一緒に置きます。同じルールのoperationは、POSTと/v1/chargesだけを許可します。既存のallow-intersectionをこの内容に置き換えますが、ほかのALLOWは追加しないでください。正常なordersだけが200で、トークンなし・stranger・別のパスは403である必要があります。
  5. /root/ica-policy/scoped.yamlに、ica-policyのjwt-scopedのポリシーを作成・適用してください。app=scoped、action=DENY、単一のルールに、from.source.notRequestPrincipals=[*]とto.operation.ports=["8080"]を一緒に置きます。既存のallow-scopedはそのまま保持し、追加の条件・ルール・dry-runは入れないでください。正常なordersの決済の呼び出しだけが200で、トークンなし・stranger・別のパスは403である必要があります。
  6. intersectionの初期のJWT設定は、payments-apiとother-apiのaudienceをどちらも許可します。audienceの観測でこれを確認したあと、/root/ica-policy/authentication.yamlに、ica-policyのjwt-intersectionというRequestAuthenticationを作成・適用してください。app=intersection、jwtRules 1つのissuer=https://issuer.example.invalid, audiences=[payments-api], jwksは、/opt/fixtures/ica-policy/public-jwks.jsonの実際の公開鍵のJSON文字列です。jwksUriや追加の発行者は入れないでください。正常なトークンは200、別のaudienceは403、別の発行者・期限切れ・形式エラーは401であることを、本文と一緒に確認してください。
  7. portsの観測コマンドのJSON出力を、/root/ica-policy/ports.jsonに保存してください。scopedにトークンなしでPOST /v1/chargesを呼び出すordersの、8080の応答は403/RBAC拒否、8081の応答は200/synthetic-orderである必要があります。観測結果を、手で成功の値に書き換えないでください。採点は、現在のリクエストをもう一度送って突き合わせます。
  8. /root/ica-policy/incident.jsonに、allow_composition(ポリシー間のORまたはAND)、source_fields(同じsourceのフィールド間のORまたはAND)、jwt_missing_fix(scopedで使ったaction)、deny_ports(保護した文字列のポートの配列)、audience_error(JWTのaudience拒否の層: jwt_authnまたはrbac)、principal_error(ワークロードのアイデンティティの認可の拒否の層: jwt_authnまたはrbac)を書いてください。正常なintersectionとscopedのポートの境界を保持してください。

参考

現在のメッシュのPodのアイデンティティを記録する

ica-policyネームスペースのPodの一覧を取得し、apiVersion・kindと、各itemsのmetadata.name・metadata.uidだけを抜き出して、/root/ica-policy/inventory.jsonに保存してください。サイドカーの詳細を含む全体の出力は、入力の制限128KiBを超えることがあります。baseline・union・intersection・scoped・orders・strangerの6つが実際にReadyで、サイドカーがある必要があります。Podを作り直した場合は、UIDも記録し直してください。

名前が同じだけで、同じPodだとは限りません。UIDとReady、実際のistio-proxyコンテナを区別して見てください。

トークンのない正常なワークロードが通過する基準線

/root/ica-policy/baseline.yamlに、ica-policyのallow-baselineというAuthorizationPolicyを作成して適用してください。app=baseline、action=ALLOW、単一のルールのsource.principalsはcluster.local/ns/ica-policy/sa/orders 1つ、operationはPOSTと/v1/chargesが1つずつです。JWTの条件は入れないでください。baselineの観測で、ordersの正常・トークンなしは200、strangerの正常なトークンは403であることを確認してください。

正常なJWTかどうかと、送信元のワークロードは、別の軸です。baselineは、送信元・メソッド・パスだけを制限します。

JWTの許可を別に付けて、事故を再現する

隔離されたunionの対象でだけ、意図的に誤った構成を再現します。/root/ica-policy/union.yamlに、ica-policyのjwt-unionというAuthorizationPolicyを作成・適用してください。app=union、action=ALLOW、rules 1つのfrom 1つに、requestPrincipals=[https://issuer.example.invalid/*]だけを置き、toは入れないでください。既存のallow-unionは保持してください。unionの観測で、トークンのないorders、正常なトークンのstranger、正常なordersのGET /admin/testまで200であることを確認してください。本番に適用しないでください。

新しい許可は、既存の許可をより厳しくするフィルターではありません。どの条件が各リクエストを通過させるのかを、別々に説明してみてください。

同じsourceで2つのアイデンティティを結合する

/root/ica-policy/intersection.yamlに、ica-policyのallow-intersectionのポリシーを作成・適用してください。app=intersection、action=ALLOW、単一のルールの同じsourceに、principals=[cluster.local/ns/ica-policy/sa/orders]と、requestPrincipals=[https://issuer.example.invalid/*]を一緒に置きます。同じルールのoperationは、POSTと/v1/chargesだけを許可します。既存のallow-intersectionをこの内容に置き換えますが、ほかのALLOWは追加しないでください。正常なordersだけが200で、トークンなし・stranger・別のパスは403である必要があります。

2つのアイデンティティを、fromの別々の項目に分けて入れることと、同じsourceに入れることは、違います。

JWTなしのDENYをHTTPポートに限定する

/root/ica-policy/scoped.yamlに、ica-policyのjwt-scopedのポリシーを作成・適用してください。app=scoped、action=DENY、単一のルールに、from.source.notRequestPrincipals=[*]とto.operation.ports=["8080"]を一緒に置きます。既存のallow-scopedはそのまま保持し、追加の条件・ルール・dry-runは入れないでください。正常なordersの決済の呼び出しだけが200で、トークンなし・stranger・別のパスは403である必要があります。

DENYだけを残すと、拒否の条件に当てはまらないリクエストの最小権限が、なくなることがあります。既存のALLOWとブロックの範囲を、一緒に見てください。

別のAPI用のトークンの再利用を防ぐ

intersectionの初期のJWT設定は、payments-apiとother-apiのaudienceをどちらも許可します。audienceの観測でこれを確認したあと、/root/ica-policy/authentication.yamlに、ica-policyのjwt-intersectionというRequestAuthenticationを作成・適用してください。app=intersection、jwtRules 1つのissuer=https://issuer.example.invalid, audiences=[payments-api], jwksは、/opt/fixtures/ica-policy/public-jwks.jsonの実際の公開鍵のJSON文字列です。jwksUriや追加の発行者は入れないでください。正常なトークンは200、別のaudienceは403、別の発行者・期限切れ・形式エラーは401であることを、本文と一緒に確認してください。

鍵は新しく作りません。用意された公開鍵を文字列として挿入し、audienceの許可リストだけを、求められる範囲に絞ってください。

保護したポートと残したポートを区別する

portsの観測コマンドのJSON出力を、/root/ica-policy/ports.jsonに保存してください。scopedにトークンなしでPOST /v1/chargesを呼び出すordersの、8080の応答は403/RBAC拒否、8081の応答は200/synthetic-orderである必要があります。観測結果を、手で成功の値に書き換えないでください。採点は、現在のリクエストをもう一度送って突き合わせます。

portとtargetPortを混同せず、適用されたポリシーの文字列のportsの一覧を確認してください。このステップは、8081まで保護する課題ではありません。

ポリシーの組み合わせによる事故の原因を報告する

/root/ica-policy/incident.jsonに、allow_composition(ポリシー間のORまたはAND)、source_fields(同じsourceのフィールド間のORまたはAND)、jwt_missing_fix(scopedで使ったaction)、deny_ports(保護した文字列のポートの配列)、audience_error(JWTのaudience拒否の層: jwt_authnまたはrbac)、principal_error(ワークロードのアイデンティティの認可の拒否の層: jwt_authnまたはrbac)を書いてください。正常なintersectionとscopedのポートの境界を保持してください。

403をすべて同じエラーとして扱わないでください。応答の本文と、どのポリシーがそのリクエストを拒否したかを、結び付けて報告してください。