全面拒否から最小権限へ絞っていく
目標
既定の拒否から出発して、呼び出しグラフのとおりにだけ権限を開く最小権限の認可を作り、各呼び出しの結果をマトリクスで説明できるようになります。
なぜ重要なのか
認可設計で最も多い間違いは、必要なものを1つずつ塞いでいく方向で考えることです。そうすると、塞ぎ忘れた経路が永遠に開いたままになり、そのことに誰も気づきません。逆に、全面拒否から出発すれば、開け忘れた経路はすぐに403として表に出るので、設計が完結します。Istioは、この逆転をspec: {}の1行で表現します。ALLOWポリシーは存在するのに、一致するルールがないので、誰も通過できません。
評価の順序も、必ず身につけておく必要があります。CUSTOM → DENY → ALLOWの順で、ALLOWは「ポリシーが1つでもあれば、一致したものだけが通過する」という仕組みです。つまり、ALLOWポリシーを初めて追加した瞬間に、そのワークロードが許可リストモードに変わります。この事実を知らずにポリシーを1つ追加して、既存のトラフィックがすべて止まる事故がよくあります。DENYがALLOWより先に評価されるという性質は、逆にセーフティネットになります。管理用の経路のように「何があっても塞がなければならないもの」は、DENYでもう1枚敷いておきます。
最後に、認可ポリシーのprincipalsは、mTLS証明書の身元から得られます。平文で入ってきたリクエストはprincipalが空で、決して一致しないので、STRICTへの切り替えが認可の前提条件です。
ステップ
開始前の準備: ラボのPodはラボごとに新しく起動するため、前のラボのメッシュ設定は残っていません。kubectl get crd authorizationpolicies.security.istio.ioの結果が空なら、istioctl manifest generate --set profile=minimal > /root/istio/manifest.yamlを実行したあと、kubectl apply -f /root/istio/manifest.yamlを2回実行し、kubectl create ns mesh-lab && kubectl label ns mesh-lab istio-injection=enabledでネームスペースを準備してください。ステップ2とステップ4でselectorが指すpaymentsとratingsのワークロードも、このラボの中で新しく作成する必要があります。
mesh-labにAuthorizationPolicydeny-allを作成してください。spec: {}だけを置き、selectorもrulesもなしで(actionは省略して既定値のALLOWにします)適用します。そして、/root/istio/authz/out/denyall-note.txtに、ルールがないと誰も通過できない理由を書いてください。mesh-labにServiceAccountfrontendを作成し(前のラボのpaymentsワークロードがなければ、一緒に作成してください)、AuthorizationPolicyallow-frontendを作成してください。selector.matchLabels.appはpayments、actionはALLOWで、rules[0].from[0].source.principalsにはcluster.local/ns/mesh-lab/sa/frontendを書いてください。spiffe://プレフィックスを付けてはいけません。- 同じ
allow-frontendポリシーに、rules[0].to[0].operationを追加してください。methodsは["GET"]だけ、pathsには/api/*を含めてください。そして、/root/istio/authz/out/path-note.txtに、パスのマッチングは正規表現ではなく、プレフィックス/サフィックスのワイルドカードだけをサポートするという点を書いてください。 mesh-labにratingsワークロード(Serviceratings、Deploymentratings-v1、Podラベルapp: ratings)を作成し、AuthorizationPolicyallow-mesh-labを作成してください。selector.matchLabels.appはratings、rules[0].from[0].source.namespacesは["mesh-lab"]です。そして、/root/istio/authz/out/ns-note.txtに、ネームスペース単位はサービスアカウント(principal)単位より粗い制御であるという点を書いてください。mesh-labにAuthorizationPolicydeny-adminを作成してください。actionはDENYで、rules[0].to[0].operation.pathsに/admin/*を含めます。そして、/root/istio/authz/order.txtに、評価の順序を1行に1つずつ、3行で書いてください。1行目にCUSTOM、2行目にDENY、3行目にALLOWが入る必要があります(1行にまとめて書いてはいけません)。mesh-labにAuthorizationPolicyallow-v2-clientsを作成してください。rules[0].whenに条件を2つ置き、1つはkey: request.headers[x-api-version]にvalues: ["v2"]で、もう1つはkey: source.namespaceにnotValues: ["legacy"]で書いてください。notValuesを使う条件のkeyには、request.headersが入ってはいけません。mesh-labにAuthorizationPolicyaudit-sensitiveを作成してください。actionはAUDITで、rules[0].to[0].operation.pathsに監査するパス(例:/internal/*)を指定します。そして、/root/istio/authz/out/audit-note.txtに、AUDITはトラフィックを止めずに記録だけをするという点と、ポリシーを実際に有効にする前に、影響を事前に見るためのものだという点を、一緒に書いてください。/root/istio/authz/out/authz-matrix.jsonを作成してください。cases配列に6件以上を入れ、各項目にはexpected("allow"か"deny"のどちらか1つ)とpolicy(その結果を生んだポリシーの名前。mesh-labに実際に存在していて、空白のない1語である必要があります)が必要です。許可の想定が2件以上、拒否の想定が2件以上必要です。mesh-labのAuthorizationPolicyは5つ以上である必要があり、istioctl analyze -n mesh-labの結果を/root/istio/authz/out/analyze.txtに保存してください。ただし、Error [が含まれていてはいけません。
参考
- ラボのPodはラボごとに新しく起動するため、前のラボのクラスター状態は残っていません。そのため、メッシュ設定をマニフェストとして残しておくことが、そのまま再現可能性になります。認可ポリシーをGitで管理すると、変更履歴と承認の手続きがそのまま監査証跡になるのも、同じ性質によるものです。
- マトリクスの例を1行:
{"from": "frontend", "to": "payments", "method": "GET", "path": "/api/v1/charges", "expected": "allow", "policy": "allow-frontend"}。拒否の例としては、/admin/*へのリクエスト(deny-admin)と、ポリシーにない呼び出し元(deny-all)を入れるとよいでしょう。 - ステップ6の
when条件のキーには、request.headers[...]、source.namespace、source.principal、request.auth.claims[...]などを使えます。 - ポリシー名がクラスターにないと、ステップ8が失敗します。
kubectl get authorizationpolicy -n mesh-labで名前を確認してから、マトリクスを書いてください。 - よくある間違い1:
principalsにspiffe://を付けること。概念の説明では付きますが、このフィールドではプレフィックスを外します。 - よくある間違い2: 評価の順序を、1行に矢印でつなげて書くこと。行番号で順序を判定するので、3行に分ける必要があります。
空のルールで全面拒否を敷く
mesh-labにAuthorizationPolicy deny-allを作成してください。spec: {}だけを置き、selectorもrulesもなしで(actionは省略して既定値のALLOWにします)適用します。そして、/root/istio/authz/out/denyall-note.txtに、ルールがないと誰も通過できない理由を書いてください。
ルールが1つもない許可ポリシーが、そのまま全面拒否です。なぜそうなるのかは、評価順序の最後の行に答えがあります。ネームスペース全体にかける必要があるので、selectorは置かないでください。
呼び出し元の身元で1つの経路だけを開ける
mesh-labにServiceAccount frontendを作成し(前のラボのpaymentsワークロードがなければ、一緒に作成してください)、AuthorizationPolicy allow-frontendを作成してください。selector.matchLabels.appはpayments、actionはALLOWで、rules[0].from[0].source.principalsにはcluster.local/ns/mesh-lab/sa/frontendを書いてください。spiffe://プレフィックスを付けてはいけません。
ポリシーは、受け取る側のワークロードに付きます。身元はIPではなくサービスアカウントで、このフィールドにはプレフィックスを付けないという点に注意してください。
メソッドとパスまで絞る
同じallow-frontendポリシーに、rules[0].to[0].operationを追加してください。methodsは["GET"]だけ、pathsには/api/*を含めてください。そして、/root/istio/authz/out/path-note.txtに、パスのマッチングは正規表現ではなく、プレフィックス/サフィックスのワイルドカードだけをサポートするという点を書いてください。
読み取りだけを許可するステップです。パスのマッチングが正規表現ではないことを確認して、メモに書いてください。
ネームスペース単位で許可する
mesh-labにratingsワークロード(Service ratings、Deployment ratings-v1、Podラベルapp: ratings)を作成し、AuthorizationPolicy allow-mesh-labを作成してください。selector.matchLabels.appはratings、rules[0].from[0].source.namespacesは["mesh-lab"]です。そして、/root/istio/authz/out/ns-note.txtに、ネームスペース単位はサービスアカウント(principal)単位より粗い制御であるという点を書いてください。
身元単位よりも粗い制御です。どんなときはこの程度で十分で、どんなときは足りないのかを、メモに残してください。
拒否のセーフティネットを敷いて評価順序を書く
mesh-labにAuthorizationPolicy deny-adminを作成してください。actionはDENYで、rules[0].to[0].operation.pathsに/admin/*を含めます。そして、/root/istio/authz/order.txtに、評価の順序を1行に1つずつ、3行で書いてください。1行目にCUSTOM、2行目にDENY、3行目にALLOWが入る必要があります(1行にまとめて書いてはいけません)。
拒否は許可より先に評価されるので、許可ポリシーのミスを防ぎます。順序の整理は、1行に1つずつ書く必要があります。
条件でさらにきめ細かく絞る
mesh-labにAuthorizationPolicy allow-v2-clientsを作成してください。rules[0].whenに条件を2つ置き、1つはkey: request.headers[x-api-version]にvalues: ["v2"]で、もう1つはkey: source.namespaceにnotValues: ["legacy"]で書いてください。notValuesを使う条件のkeyには、request.headersが入ってはいけません。
条件は複数かけられ、除外条件も表現できます。ヘッダー条件と除外条件は、別々の項目に分けてください。
止めずに記録だけをするポリシーを作成する
mesh-labにAuthorizationPolicy audit-sensitiveを作成してください。actionはAUDITで、rules[0].to[0].operation.pathsに監査するパス(例: /internal/*)を指定します。そして、/root/istio/authz/out/audit-note.txtに、AUDITはトラフィックを止めずに記録だけをするという点と、ポリシーを実際に有効にする前に、影響を事前に見るためのものだという点を、一緒に書いてください。
ポリシーを本当に有効にする前に、影響を先に見るための仕組みです。トラフィックにどんな影響を与えるのかを、正確に書いてください。
呼び出しごとの許可・拒否マトリクスを作成する
/root/istio/authz/out/authz-matrix.jsonを作成してください。cases配列に6件以上を入れ、各項目にはexpected("allow"か"deny"のどちらか1つ)とpolicy(その結果を生んだポリシーの名前。mesh-labに実際に存在していて、空白のない1語である必要があります)が必要です。許可の想定が2件以上、拒否の想定が2件以上必要です。mesh-labのAuthorizationPolicyは5つ以上である必要があり、istioctl analyze -n mesh-labの結果を/root/istio/authz/out/analyze.txtに保存してください。ただし、Error [が含まれていてはいけません。
各呼び出しが、どのポリシーのためにそう決まるのかまで、書く必要があります。ポリシー名は、クラスターに実際にあるものでなければなりません。