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

Istio深化 — なぜそう流れるのか

2 つのポリシーを RBAC フィルターに移して評価順序を確かめる

TT Labで続きを見る

目標

DENY・ALLOWの2つのAuthorizationPolicyを書き、評価順序で結果を先に予測した後、2つのポリシーをEnvoyの2つのRBACフィルターに変換して起動し、予測と突き合わせます。順序の入れ替え、ALLOWの除去、dry-runで、ルールを1つずつ確認します。

なぜ重要なのか

認可の事故の大半は、評価順序の読み間違いから来ます。ALLOWを1つ足したらすべて止まったり、パスだけを書いたDENYがTCPサービスを切ったりします。ポリシーがフィルターの連鎖にどう変換されるかがわかれば、403を1つ見て、どのポリシーのどの行が止めたのかを突き止められます。

ステップ

  1. /root/ist2-authz/deny.yamlにAuthorizationPolicydeny-admin(ネームスペースdefault、selector.matchLabels.app: payments、action: DENY、ルール1つ。to.operation.paths: ["/admin*"])を、/root/ist2-authz/allow.yamlにallow-get(同じネームスペース・selector、action: ALLOW、to.operation.methods: ["GET"])を書いてください。/root/ist2-authzでistioctl validate -f deny.yaml -f allow.yamlの出力と終了コードを、/root/ist2-authz/01-validate.txtに入れてください(最後の行はrc=)。
  2. 2つのポリシーがpaymentsに一緒にかかっているとき、4つのリクエスト(GET /、GET /admin/x、POST /、POST /admin)の判定を、評価順序だけで予測し、/root/ist2-authz/02-predict.txtに、<메서드> <경로>=allow|denyの形(プレースホルダーはメソッドとパスです)で4行書いてください(例: HEAD /=deny)。まだ何も起動しません。
  3. /root/ist2-authz/authz.yamlにEnvoyの設定を書いてください。管理ポートは9987、リスナーvirtualInboundは127.0.0.1:10087、HCMのstat_prefixはinbound_0.0.0.0_8110で、すべてのパスをクラスターinbound|8110||(127.0.0.1:8110)へ送ります。http_filtersは、ちょうど3つを、この順序で置きます。(1) istio_authz_deny: RBAC、rules.action: DENY、ポリシー名ns[default]-policy[deny-admin]-rule[0]、権限はurl_pathのプレフィックス/admin。(2) envoy.filters.http.rbac: RBAC、rules.action: ALLOW、ポリシー名ns[default]-policy[allow-get]-rule[0]、権限はヘッダー:methodが完全にGET。2つのポリシーともprincipals: [{any: true}]。(3)ルーター。その後、yqでhttp_filtersを読み取り、/root/ist2-authz/03-chain.txtに、フィルターごとに1行ずつ、<이름> <rules.action>(ルーターは-)を書いてください(プレースホルダーは名前とrules.actionです)。
  4. python3 /opt/lab/envoy/upstream.py 8110 okでアップストリームを起動し、/root/ist2-authz/authz.yamlでEnvoyを起動してください。ステップ2の4つのリクエストをlocalhost:10087へ送り、/root/ist2-authz/04-result.txtに、<메서드> <경로>=<HTTP 코드>の形で4行を書き(プレースホルダーはメソッド、パス、HTTPコードです)、5行目のbody_403=には、GET /admin/xが受け取った本文の最初の行を書いてください。
  5. /root/ist2-authz/authz.yamlを/root/ist2-authz/authz-swapped.yamlにコピーしてから、2つのRBACフィルターの順序だけを入れ替えてください(ALLOWフィルターが前、DENYフィルターが後、ルーターは一番最後で、残りはそのままです)。このファイルでEnvoyを起動し直し、同じ4つのリクエストを送って、/root/ist2-authz/05-swapped.txtに、<메서드> <경로>=<코드>の形の4行(プレースホルダーはメソッド、パス、コードです)と、same_as_04=(4つのコードがステップ4とすべて同じならyes、そうでなければno)を書いてください。
  6. /root/ist2-authz/authz.yamlからALLOWフィルター(envoy.filters.http.rbac)だけを除いた/root/ist2-authz/authz-denyonly.yamlを作り、Envoyを起動し直してください。POST /とGET /admin/xを送り、/root/ist2-authz/06-denyonly.txtに、<메서드> <경로>=<코드>の形で2行を書き(プレースホルダーはメソッド、パス、コードです)、3行目のevaluation_rule=には、POST /を通したのが、評価順序の5行のうち何行目なのかを、数字で書いてください。
  7. /root/ist2-authz/authz.yamlをもとに/root/ist2-authz/authz-dryrun.yamlを作ってください。DENYフィルター(istio_authz_deny)のポリシーをrulesからshadow_rulesに移し(rulesは置きません)、同じフィルターにshadow_rules_stat_prefix: istio_dry_run_deny_を加えます。ALLOWフィルターとルーターはそのままです。このファイルでEnvoyを起動し直し、GET /admin/xとPOST /を1回ずつ送って、/root/ist2-authz/07-dryrun.txtに4行を書いてください。2つのリクエストの<메서드> <경로>=<코드>(プレースホルダーはメソッド、パス、コードです)、stat=(管理ポートの/statsで、shadow_deniedで終わる統計のうち、プレフィックスが付いたものの完全な名前)、shadow_denied=(その値)です。
  8. /root/ist2-authz/08-report.mdに5行を書いてください。chain=(ステップ3のhttp_filtersの名前を、順番にカンマ区切りで)、deny_body=(RBACが拒否するときの本文)、swapped_same=(ステップ5のsame_as_04の値)、dry_run_field=(dry-runポリシーが入るRBACフィールドの名前)、deny_tcp_fix=(ステップ1の警告が勧める、DENYルールのoperationに加えるフィールドの名前)です。その下に、- で始まる説明を4行以上書いてください。

参考

DENYとALLOWの2つのポリシーを書き、istioctlで検査する

/root/ist2-authz/deny.yamlにAuthorizationPolicydeny-admin(ネームスペースdefault、selector.matchLabels.app: payments、action: DENY、ルール1つ。to.operation.paths: ["/admin*"])を、/root/ist2-authz/allow.yamlにallow-get(同じネームスペース・selector、action: ALLOW、to.operation.methods: ["GET"])を書いてください。/root/ist2-authzでistioctl validate -f deny.yaml -f allow.yamlの出力と終了コードを、/root/ist2-authz/01-validate.txtに入れてください(最後の行はrc=)。

istioctlは、クラスターがなくてもポリシーを検査します。2つのファイルのうち1つには警告が付きます。終了コードは0ですが、そのまま見過ごしてはいけない内容です。警告の文を最後まで読んでおいてください。DENYは「リクエストにその属性がないとき」の扱いがALLOWと逆なので出る警告で、ステップ8でもう一度使います。出力は> 파일 2>&1(プレースホルダーはファイル名です)で、2つのストリームをまとめて入れてください。

起動する前に、評価順序で結果を予測する

2つのポリシーがpaymentsに一緒にかかっているとき、4つのリクエスト(GET /、GET /admin/x、POST /、POST /admin)の判定を、評価順序だけで予測し、/root/ist2-authz/02-predict.txtに、<메서드> <경로>=allow|denyの形(プレースホルダーはメソッドとパスです)で4行書いてください(例: HEAD /=deny)。まだ何も起動しません。

Istioの評価順序は5行です。CUSTOMが拒否すれば拒否 → DENYにマッチすれば拒否 → そのワークロードにALLOWポリシーが1つもなければ許可 → ALLOWにマッチすれば許可 → 残りは拒否です。リクエストごとに、上から順にたどり、最初に止まる行を探してください。/admin*はプレフィックスマッチです。ALLOWポリシーのあるワークロードで、「どのALLOWにも当たらないリクエスト」がどうなるかが核心です。

2つのポリシーを2つのRBACフィルターに変換する

/root/ist2-authz/authz.yamlにEnvoyの設定を書いてください。管理ポートは9987、リスナーvirtualInboundは127.0.0.1:10087、HCMのstat_prefixはinbound_0.0.0.0_8110で、すべてのパスをクラスターinbound|8110||(127.0.0.1:8110)へ送ります。http_filtersは、ちょうど3つを、この順序で置きます。(1) istio_authz_deny: RBAC、rules.action: DENY、ポリシー名ns[default]-policy[deny-admin]-rule[0]、権限はurl_pathのプレフィックス/admin。(2) envoy.filters.http.rbac: RBAC、rules.action: ALLOW、ポリシー名ns[default]-policy[allow-get]-rule[0]、権限はヘッダー:methodが完全にGET。2つのポリシーともprincipals: [{any: true}]。(3)ルーター。その後、yqでhttp_filtersを読み取り、/root/ist2-authz/03-chain.txtに、フィルターごとに1行ずつ、<이름> <rules.action>(ルーターは-)を書いてください(プレースホルダーは名前とrules.actionです)。

istiodは、1つのワークロードにかかったDENYポリシーをまとめてフィルター1つに、ALLOWポリシーをまとめてもう1つのフィルターにし、DENY側を前に置きます。RBACフィルター1つは、actionを1つしか持てないからです。ポリシー名ns[<네임스페이스>]-policy[<이름>]-rule[<번호>](プレースホルダーはネームスペース、名前、番号です)は、Istioが実際に付ける形で、本番でログやconfig_dumpを見て、元のリソースを探す手がかりになります。Istioの/admin*は、Envoyではurl_path.path.prefixになり、methodsは:methodヘッダーのマッチになります。"@type"はtype.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACです。yqで値がないときの代替値は、(.a // "-")と書きます。

起動して予測と突き合わせる

python3 /opt/lab/envoy/upstream.py 8110 okでアップストリームを起動し、/root/ist2-authz/authz.yamlでEnvoyを起動してください。ステップ2の4つのリクエストをlocalhost:10087へ送り、/root/ist2-authz/04-result.txtに、<메서드> <경로>=<HTTP 코드>の形で4行を書き(プレースホルダーはメソッド、パス、HTTPコードです)、5行目のbody_403=には、GET /admin/xが受け取った本文の最初の行を書いてください。

メソッドはcurl -X POSTで変えます。コードは-o 파일 -w '%{http_code}'(プレースホルダーはファイル名です)で受け取り、本文はそのファイルに残ります。RBACフィルターが拒否すると、リクエストはアップストリームに届かず、Envoyが直接403と短い本文を返します。ステップ2でallowとしたものは200、denyとしたものは403が出れば、予測が合っています。外れていたら、どの行で止まったのかを、もう一度たどってみてください。

フィルターの順序を入れ替えても判定は同じになる

/root/ist2-authz/authz.yamlを/root/ist2-authz/authz-swapped.yamlにコピーしてから、2つのRBACフィルターの順序だけを入れ替えてください(ALLOWフィルターが前、DENYフィルターが後、ルーターは一番最後で、残りはそのままです)。このファイルでEnvoyを起動し直し、同じ4つのリクエストを送って、/root/ist2-authz/05-swapped.txtに、<메서드> <경로>=<코드>の形の4行(プレースホルダーはメソッド、パス、コードです)と、same_as_04=(4つのコードがステップ4とすべて同じならyes、そうでなければno)を書いてください。

HTTPフィルターの連鎖で、各RBACフィルターにできることは2つだけです。次のフィルターへ渡すか、その場で拒否するかです。「許可」という判定で連鎖を飛ばすフィルターはありません。それなら、2つのフィルターがリクエストを通す条件は、どう結ばれるでしょうか。yqでリストの2つの項目を入れ替えても、ファイルを手で直しても構いません。結果を書く前に、envoy --mode validateで先に検査してください。

ALLOWポリシーが1つもなければ、DENYに当たらないものはすべて通る

/root/ist2-authz/authz.yamlからALLOWフィルター(envoy.filters.http.rbac)だけを除いた/root/ist2-authz/authz-denyonly.yamlを作り、Envoyを起動し直してください。POST /とGET /admin/xを送り、/root/ist2-authz/06-denyonly.txtに、<메서드> <경로>=<코드>の形で2行を書き(プレースホルダーはメソッド、パス、コードです)、3行目のevaluation_rule=には、POST /を通したのが、評価順序の5行のうち何行目なのかを、数字で書いてください。

istiodは、ワークロードにALLOWポリシーがなければ、ALLOWフィルターをそもそも作りません。空のALLOWフィルターを置くと、すべてのリクエストが拒否されるからです(RBACのrulesにポリシーが1つもなければ、すべて拒否します)。このラボのアップストリームはGETしか知らないので、ほかのメソッドには501を返します。403とRBAC: access deniedでなければ、プロキシは通したのであり、コードはアプリが決めたものです。受け取ったコードをそのまま書いてください。

dry-runは同じポリシーをshadow_rulesに入れる

/root/ist2-authz/authz.yamlをもとに/root/ist2-authz/authz-dryrun.yamlを作ってください。DENYフィルター(istio_authz_deny)のポリシーをrulesからshadow_rulesに移し(rulesは置きません)、同じフィルターにshadow_rules_stat_prefix: istio_dry_run_deny_を加えます。ALLOWフィルターとルーターはそのままです。このファイルでEnvoyを起動し直し、GET /admin/xとPOST /を1回ずつ送って、/root/ist2-authz/07-dryrun.txtに4行を書いてください。2つのリクエストの<메서드> <경로>=<코드>(プレースホルダーはメソッド、パス、コードです)、stat=(管理ポートの/statsで、shadow_deniedで終わる統計のうち、プレフィックスが付いたものの完全な名前)、shadow_denied=(その値)です。

Istioでは、ポリシーにistio.io/dry-run: "true"アノテーションを付けると、istiodは、そのポリシーをrulesの代わりにshadow_rulesに入れます。RBACフィルターは、shadow側を評価するだけで、判定には使いません。結果は、統計と動的メタデータとしてだけ残ります。rulesがまったくないRBACフィルターは、何も拒否しません。統計名のどこに、どんな区切り文字でプレフィックスが付くかは、自分で見て書き写してください。curl -s localhost:<관리포트>/stats | grep shadow(プレースホルダーは管理ポートです)で見られます。

AuthorizationPolicyがEnvoyでどうなるかをまとめる

/root/ist2-authz/08-report.mdに5行を書いてください。chain=(ステップ3のhttp_filtersの名前を、順番にカンマ区切りで)、deny_body=(RBACが拒否するときの本文)、swapped_same=(ステップ5のsame_as_04の値)、dry_run_field=(dry-runポリシーが入るRBACフィールドの名前)、deny_tcp_fix=(ステップ1の警告が勧める、DENYルールのoperationに加えるフィールドの名前)です。その下に、- で始まる説明を4行以上書いてください。

値は、前のステップのファイルから移してください。ステップ1の警告は、DENYが「リクエストにない属性」をマッチと見なすことから出ます。HTTPパスだけを書いたDENYは、パスという属性のないTCP接続をすべてマッチさせます。警告の文が絞るよう勧めている対象を、operationのフィールド名(複数形)で書いてください。