Istio での Authorization Policy を利用したワークロードのアクセス制御

## はじめに

この記事では Istio からワークロードのアクセス制御を行うための機能として提供されている Authorization Policy の仕組みや使い方を紹介していきます。

ここでいうアクセス制御というのはリクエスト元のワークロードのアクセス権限を確認した上でリクエストを 許可/拒否 する認可処理のことを指しています。

## Istio における認可モデルの変遷

Istio 1.4 からアクセス制御を行うための認可モデルが Alpha 版として提供されていた [RBAC Policy](https://archive.istio.io/v1.4/docs/reference/config/security/istio.rbac.v1alpha1/) から、Beta 版としてシンプルさと柔軟性に重点を置いた [Authorization Policy](https://istio.io/latest/docs/reference/config/security/authorization-policy/) に変更されました。[^1]

改善点としては RBAC Policy では `ClusterRbacConfig`, `ServiceRole`, `ServiceRoleBinding` というカスタムリソースを定義する必要があったのが、Authorization Policy では `AuthorizationPolicy` というカスタムリソースを定義するだけで済むようになり管理方法がシンプルになったことや、Ingress/Egress Gateway に対してポリシーが適用できるようになったことで Service Mesh 内だけではなく Service Mesh に出入りするトラフィックに対してもアクセス制御を行えるようになったことなどが挙げられています。[^2]

なお、最新バージョン Istio 1.6 では [istio/istio#22060](https://github.com/istio/istio/issues/22060) で RBAC Policy を廃止する作業が進められているため、RBAC Policy を利用していたユーザーが 1.6 にバージョンアップする際には Istio Blog で公開されている [Introducing the Istio v1beta1 Authorization Policy](https://istio.io/latest/blog/2019/v1beta1-authorization-policy/) を参考にして Authorization Policy への移行を行う必要があります。

## Authorization Policy の仕組み

先述の通り Authorization Policy では `AuthorizationPolicy` リソースでポリシーを定義することでワークロードのアクセス制御を行うことができます。また、アクセス制御のスコープを Service Mesh 全体、特定 Namespace、特定 Workload など設定によって柔軟に定義することが可能です。

アーキテクチャとしては Control Plane である istiod が `AuthorizationPolicy` リソースで定義されたポリシーを Envoy の [RBAC Filter](https://www.envoyproxy.io/docs/envoy/v1.15.0/intro/arch_overview/security/rbac_filter.html) の設定に変換[^3] して LDS 経由で Data Plane である istio-proxy（Envoy）に反映する仕組みとなっており、ポリシーのチェックは istio-proxy 内部で実行されるためパフォーマンスへの影響もありません。

<img width="956" alt="スクリーンショット 2020-07-15 16.22.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49924/a19fb25d-d11c-c01f-9c69-cb32ee76611a.png">

引用: https://istio.io/latest/docs/concepts/security/#authorization-architecture

## AuthorizationPolicy リソースについて

以下は `AuthorizationPolicy` リソースの Spec についての説明を記載した図となります。
<img width="1250" alt="スクリーンショット 2020-07-16 17.19.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49924/64844d4b-225e-bc93-4cc2-525fba9234cb.png">
上記のポリシーは `ec-system` ネームスペース内で `app: backend` というラベルを持つワークロード（Pod）に対して、`cluster.local/ns/ec-system/sa/web` という Workload Identity[^4] を持つワークロードから、`8080` ポートの `/data` エンドポイントを `GET` するリクエストを `ALLOW(許可)` するものとなります。

基本的にポリシーは同じネームスペース内のワークロードに対して適用されますが、以下のように Istio の `Root Namespace` にポリシーを作成することで全てのネームスペース内のワークロードに対してポリシーを適用することができます。`Root Namespace` は [MeshConfig](https://istio.io/latest/docs/reference/config/istio.mesh.v1alpha1/#MeshConfig) で設定可能となっていて、デフォルトでは `istio-system` が使用されます。

```yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: all-ns-policy
  namespace: istio-system
spec:
  ...
```

### action について

Authorization Policy ではリクエストを許可する（`ALLOW`）ポリシーとリクエストを拒否する（`DENY`）ポリシーがサポートされており、どちらのポリシーにするかを [action](https://istio.io/latest/docs/reference/config/security/authorization-policy/#AuthorizationPolicy-Action) で指定する仕様になっています。

```yaml
# リクエストを許可するポリシー
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: allow-policy
  namespace: foo
spec:
  action: ALLOW
  selector:
  ...
  rules:
  ...

# リクエストを拒否するポリシー
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: deny-policy
  namespace: foo
spec:
  action: DENY
  selector:
  ...
  rules:
  ...
```

ワークロードに両方のポリシーにマッチした場合には `DENY` ポリシーが先に評価される仕様なので注意してください。また、action のデフォルト値は `ALLOW` のため未定義の場合には `ALLOW` ポリシーとなりますが、わかりやすさの観点で明示的に定義することが推奨されています。

### selector について

selector はポリシーを適用するワークロード（Pod）をラベルベースで指定するもので [Gateway](https://istio.io/latest/docs/reference/config/networking/gateway/), [Sidecar](https://istio.io/latest/docs/reference/config/networking/sidecar/), [EnvoyFilter](https://istio.io/latest/docs/reference/config/networking/envoy-filter/) の selector と同様の仕組みが利用されています。以下は `foo` ネームスペース内の `app: bar` というラベルを持つワークロードに適用されるポリシーの例となります。

```yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: bar-policy
  namespace: foo
spec:
  action: ALLOW
  selector:
    matchLabels:
      app: bar
  rules:
  ...
```

selector を定義することでワークロードを指定できますが、以下のように selector が未定義の場合には同じネームスペース内の全てのワークロードにポリシーが適用される仕様です。

```yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: foo-ns-policy
  namespace: foo
spec:
  action: ALLOW
  rules:
  ...
```

### rules について

[rules](https://istio.io/latest/docs/reference/config/security/authorization-policy/#Rule) では `from` でリクエスト元を指定、`to` でリクエスト内容を指定、`when` でその他の条件を指定してポリシーのルールを定義することで、ルールにマッチしたリクエストに対して action で指定した判定を適用することができます。更に `from`, `to`, `when` の文字列型のフィールドではワイルドカード(*) を利用したマッチもサポートされています。[^5]

以下は `cluster.local/ns/foo/sa/hoge` という Workload Identity[^4] を持つワークロードから `8080` ポートを `GET` するリクエストに対してのルールを定義したポリシーとなります。

```yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: bar-policy
  namespace: foo
spec:
  action: ALLOW
  selector:
    matchLabels:
      app: bar
  rules:
  - from:
    - source:
      principals: ["cluster.local/ns/foo/sa/hoge"]
    to:
    - operation:
      methods: ["GET"]
    when:
    - key: destination.port
      values: ["8080"]
```

#### from について

[from](https://istio.io/latest/docs/reference/config/security/authorization-policy/#Rule-From) では以下のような値を元にリクエスト元を指定することができます。[^6]

- Principal（X.509 証明書に含まれる SPIFFE ID）
- JWT の iss と sub
- Namespace
- IP や CIDR

#### to について

[to](https://istio.io/latest/docs/reference/config/security/authorization-policy/#Rule-To) では以下のような値を元にリクエスト内容を指定することができます。

- ホスト名
- ポート
- HTTP メソッド
- URL パス

#### when について

[when](https://istio.io/latest/docs/reference/config/security/conditions/) で以下のような値をその他の条件として指定することができます。[^6]

- HTTP リクエストヘッダー
- Principal（X.509 証明書に含まれる SPIFFE ID）
- JWT クレーム
- Namespace
- IP や CIDR

`from` や `to` で指定可能な値と重複しているフィールドが多く存在しているので、ルールをわかりやすくするためにも `when` でしか指定できない条件のみを定義するのが良いかもしれません。

## チュートリアル

Istio 1.6.5 をインストールした Kuberntes クラスタを作成します。

```bash
kind create cluster --name istio-dev --image kindest/node:v1.18.0
istioctl manifest apply --set profile=demo
```

セットアップが完了したかどうかを確認します。

```bash
$ istioctl version
client version: 1.6.5
control plane version: 1.6.5
data plane version: 1.6.5 (3 proxies)
```

サンプルアプリとして Bookinfo をデプロイします。

```bash
kubectl create ns bookinfo
kubectl label namespace bookinfo istio-injection=enabled
kubectl apply -n bookinfo -f https://raw.githubusercontent.com/istio/istio/1.6.5/samples/bookinfo/platform/kube/bookinfo.yaml
```

あとは [Authorization for HTTP traffic](https://istio.io/latest/docs/tasks/security/authorization/authz-http/) を実施するだけです。チュートリアルの中で `AuthorizationPolicy` リソースの Spec を変更してみたり、ポリシーの適用後に Envoy に設定された RBAC Filter を Admin Interface から参照してみたりすると理解が深まるかもしれません。

## さいごに

今回は Istio からワークロードのアクセス制御を行うための機能として提供されている Authorization Policy の仕組みや使い方を紹介しました。認可モデルの変遷でも言及したとおり、以前の RBAC Policy と比べて Authorization Policy はシンプルで使いやすいモデルになっています。

TCP トラフィックに対するアクセス制御、HTTP トラフィックに対するアクセス制御、Ingress Gateway 上でのアクセス制御など、各ユースケースに応じた Authorization Policy の利用方法が [公式ドキュメント](https://istio.io/latest/docs/tasks/security/authorization/) で提供されているので、導入を検討している方はそちらを参考に作業を進めていくのが良いかと思います。

Authorization Policy がこのまま GA になるのか、[servicemeshinterface/smi-adapter-istio](https://github.com/servicemeshinterface/smi-adapter-istio) を利用して SMI ベースでアクセス制御を行うのが推奨されていくのか、など気になることもあるので、引き続き Istio の Workload Identity とアクセス制御周りの動向に注目していきたいと思います。

## 参考資料

- https://istiobyexample-ja.github.io/istiobyexample/authorization/
- https://istio.io/latest/docs/concepts/security/#authorization-architecture

[^1]: [Authorization policy model in beta - Announcing Istio 1.4](https://istio.io/latest/news/releases/1.4.x/announcing-1.4/#authorization-policy-model-in-beta)
[^2]: Authorization Policy での改善点とデザインゴールで Istio Configuration Model について言及されていたが肝心の [ドキュメント](https://goo.gl/x3STjD) は非公開となっているのでこちらでは割愛。
[^3]: Istio 1.6 で RBAC Filter を生成しているソースコードは [こちら](https://github.com/istio/istio/blob/1.6.0/pilot/pkg/security/authz/builder/builder.go)
[^4]: Istio では [SPIFFE](https://spiffe.io/) がサポートされており mTLS が有効になっていると全てのワークロードに Workload Identity となる SPIFFE ID（`spiffe://<Trust Domain>/ns/<Namespace>/sa/<Service Account>`）が SAN URI にセットされた X.509 証明書が配布される。Istio では SPIFFE ID をワークロードの Namespace と Service Account から構成するため、`foo` という Namespace に `bar` という Service Account を使用してデプロイされた Pod には `spiffe://cluster.local/ns/foo/sa/bar` という SPIFFE ID を含む X.509 証明書が配布される仕様となっている。
[^5]: マッチ方法として Exact, Prefix, Suffix, Presence がサポートされている。詳細は [こちら](https://istio.io/latest/docs/reference/config/security/authorization-policy/#Rule) を参照
[^6]: 指定可能なフィールドの中には Principal や Namespace など mTLS 有効化が必須なものがあります。Security の観点から mTLS による相互認証とワークロード間の通信の暗号化はとても重要なので、それらのフィールドを使わない要件だったとしても基本的には mTLS を有効化した上でアクセス制御を行うのが良いかと思います。