Using Network Policy with Istio

## やりたいこと

* Istio上のワークロードたちのNamespace横断の通信をNetwork Policyで遮断したい

## [Using Network Policy with Istio](https://istio.io/latest/blog/2017/0.1-using-network-policy/) ※DeepL

> Kubernetes上で動作するアプリケーションを保護するためにNetwork Policyを使用することは、現在では業界のベストプラクティスとして広く受け入れられています。Istioもポリシーをサポートしていることから、Istio PolicyとKubernetes Network Policyがどのように相互作用し、アプリケーションを安全に提供するために互いにサポートし合っているかを説明することに時間を費やしたいと思います。

> 基本的なことから始めましょう：
なぜIstioとKubernetes Network Policyの両方を使いたいと思うのでしょうか？簡単な答えは、両者が異なることに長けているからです。IstioとNetwork Policyの主な違いを考えてみましょう（ここではCalicoなどの「典型的な」実装を説明しますが、実装の詳細はネットワークプロバイダーによって異なる可能性があります）。

||Istio Policy |	Network Policy|
|:--|:--|:--|
|Layer|	“Service” — L7|	“Network” — L3-4|
|Implementation|	User space	|Kernel|
|Enforcement Point|	Pod	|Node|


### Layer

> Istio Policyは、ネットワーク・アプリケーションの「サービス」層で動作します。これは OSI モデルから見るとレイヤー 7（アプリケーション）ですが、クラウドネイティブアプリケーションのデファクトモデルでは、レイヤー 7 は実際には少なくともサービス層とコンテンツ層の 2 つの層で構成されているとされています。サービス層は通常HTTPであり、実際のアプリケーションデータ（コンテンツ層）をカプセル化する。IstioのEnvoyプロキシが動作するのは、このHTTPのサービス層である。これに対して、Network Policyは、OSIモデルの第3層（Network）と第4層（Transport）で動作する。

> サービス層で動作することで、Envoy プロキシは、現在 HTTP/1.1 と HTTP/2 (gRPC は HTTP/2 で動作) を含む理解できるプロトコルに対して、ポリシー決定の根拠となる豊富な属性セットを得ることができます。そのため、バーチャルホストやURL、その他のHTTPヘッダに基づいてポリシーを適用することができます。将来的には、Istioはレイヤー7のプロトコルを幅広くサポートし、汎用的なTCPやUDPのトランスポートにも対応する予定です。

> 一方、ネットワーク層での運用は、すべてのネットワークアプリケーションがIPを使用するため、汎用性が高いという利点があります。ネットワーク層では、レイヤー7のプロトコルに関係なく、ポリシーを適用することができます。DNS、SQLデータベース、リアルタイムストリーミング、HTTPを使用しないその他多くのサービスを保護することができます。ネットワーク・ポリシーは、古典的なファイアウォールのIPアドレス、プロト、ポートのタプルに限定されるものではありません。IstioとNetwork Policyの両方は、ポッドエンドポイントを記述するためのリッチなKubernetesラベルを意識しています。

### Implementation

> IstioのプロキシはEnvoyをベースにしており、データプレーンにユーザースペースデーモンとして実装され、標準ソケットを使ってネットワーク層とやり取りをします。このため、処理に大きな柔軟性があり、コンテナで配布（とアップグレード！）することができます。

> ネットワークポリシーデータプレーンは通常、カーネル空間に実装されます（例えば、iptables、eBPF フィルタ、あるいはカスタムカーネルモジュールを使用します）。カーネル空間にあることで、非常に高速になりますが、Envoy プロキシのような柔軟性はありません。

### Enforcement point

> Envoyプロキシを使ったポリシー実行は、ポッド内部で、同じネットワークのネームスペースにあるサイドカーコンテナとして実装されています。これにより、シンプルなデプロイメントモデルが実現する。一部のコンテナには、そのポッド内のネットワークを再構成する権限（CAP_NET_ADMIN）が与えられている。このようなサービスインスタンスが侵害されたり、（悪意のあるテナントのように）誤動作したりすると、プロキシがバイパスされる可能性がある。

> 攻撃者が他のIstio対応ポッドにアクセスすることはできませんが、それらが正しく構成されている限り、いくつかの攻撃ベクトルを開くことになります。

> * 保護されていないポッドへの攻撃
> * 大量のトラフィックを送信し、保護されたポッドへのサービスを拒否しようとする。
> * ポッドで収集したデータの流出
> * クラスタインフラ（サーバやKubernetesサービス）への攻撃
> * データベース、ストレージアレイ、レガシーシステムなど、メッシュの外側のサービスを攻撃する。

> ネットワーク・ポリシーは、通常、ゲスト・ポッドのネットワーク・ネームスペースの外側にあるホスト・ノードで実施されます。これは、侵害されたポッドや不正な動作をするポッドが、強制を回避するためにルート名前空間に侵入しなければならないことを意味します。Kubernetes 1.8でegressポリシーが追加される予定ですが、この違いにより、ネットワークポリシーは、侵害されたワークロードからインフラを保護するための重要な部分となります。

### Examples

> Istio対応アプリケーションのKubernetes Network Policyで何をしたいのか、いくつかの例を挙げて説明します。Bookinfoのサンプルアプリケーションを考えてみましょう。ここでは、Network Policyの次のような使用例を取り上げます。

> * アプリケーションのイングレスの攻撃面を減らす
> * アプリケーション内のきめ細かな分離を強制する

#### Reduce attack surface of the application ingress

> アプリケーションのイングレスコントローラーは、外界から我々のアプリケーションへの主なエントリポイントです。`istio.yaml`をちょっと覗いてみると、Istioのingressはこのように定義されています。

```yaml
apiVersion: v1
kind: Service
metadata:
  name: istio-ingress
  labels:
    istio: ingress
spec:
  type: LoadBalancer
  ports:
  - port: 80
    name: http
  - port: 443
    name: https
  selector:
    istio: ingress
```

> istio-ingressは80番と443番ポートを公開しています。受信トラフィックをこの2つのポートだけに制限してみましょう。Envoyには管理用インターフェイスが組み込まれており、誤った設定のistio-ingressイメージで誤って管理用インターフェイスを外部に公開してしまわないようにしたい。これは深層防護の例です。適切に設定されたイメージはインタフェースを公開しないはずで、適切に設定されたネットワークポリシーは、誰かがそれに接続するのを防ぎます。どちらかが失敗したり、間違って設定されたりしても、私たちはまだ保護されています。

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: istio-ingress-lockdown
  namespace: default
spec:
  podSelector:
    matchLabels:
      istio: ingress
  ingress:
  - ports:
    - protocol: TCP
      port: 80
    - protocol: TCP
      port: 443
```

#### Enforce fine-grained isolation within the application

> 以下は、Bookinfoアプリケーションのサービスグラフです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/270d0f04-19fa-5dd6-eaf3-baee62a42471.png)

> このグラフは、正しく機能するアプリケーションが行うことを許可されるべきすべての接続を示しています。それ以外の接続、例えばIstio Ingressから直接Ratingサービスに接続するような接続は、アプリケーションの一部ではありません。これらの余計な接続をロックアウトして、攻撃者に利用されないようにしましょう。例えば、Ingressポッドが攻撃者に任意のコードを実行させるエクスプロイトによって侵害されたと想像してください。ネットワーク・ポリシーを使用してProduct Pageポッドへの接続のみを許可すると、攻撃者はサービス・メッシュのメンバーを侵害しても、私のアプリケーション・バックエンドへのアクセス権を得ることはできなくなります。

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: product-page-ingress
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: productpage
  ingress:
  - ports:
    - protocol: TCP
      port: 9080
    from:
    - podSelector:
        matchLabels:
          istio: ingress
```

> 各サービスに対して同様のポリシーを記述し、他のポッドがそれぞれへのアクセスを許可されるように強制することができますし、そうする必要があります。

### Summary

> 私たちは、IstioとNetwork Policyでは、ポリシーを適用する上での強みが異なると考えています。Istioはアプリケーションプロトコルを認識し、非常に柔軟であるため、サービスルーティング、リトライ、サーキットブレーキングなどの運用目標や、トークン検証などのアプリケーション層で動作するセキュリティに対応したポリシー適用に最適である。ネットワークポリシーは、普遍的で非常に効率的であり、ポッドから分離されているため、ネットワークセキュリティの目標をサポートするポリシーの適用に理想的です。さらに、ネットワークスタックの異なるレイヤーで動作するポリシーを持つことは、状態を混同することなく各レイヤーに特定のコンテキストを与え、責任の分離を可能にするため、本当に良いことです。

## つまるとこどういうことか

* 多層防御の観点からも、Authorization Policyだけじゃなくて、Network Policyはやっておいた方がいい。
    * うんうん、
* きめ細かくはできないけど、Network Policyのほうが高速でもある。
    * うんうん、

## Service Mesh上のNamespaceがdefault1とdefault2の間の通信を確認する

#### 仕込み

```bash
$ default1_src=`kubectl -n default1 get pod | grep ratings | awk '{print $1}'`
$ default2_src=`kubectl -n default2 get pod | grep ratings | awk '{print $1}'`
$ default1_dst=`kubectl -n default1 get svc | grep prod | awk '{print $3":"$5}' | sed 's/\/.*//g'`
$ default2_dst=`kubectl -n default2 get svc | grep prod | awk '{print $3":"$5}' | sed 's/\/.*//g'`
$ echo $default1_src
ratings-v1-b6994bb9-s5s4l
$ echo $default2_src
ratings-v1-b6994bb9-j5hlv
$ echo $default1_dst
10.107.136.55:9080
$ echo $default2_dst
10.110.26.100:9080
```

#### テスト

```bash
$ kubectl -n default1 exec $default1_src -- curl -s -o /dev/null -w %{http_code} http://$default1_dst
200
$ kubectl -n default1 exec $default1_src -- curl -s -o /dev/null -w %{http_code} http://$default2_dst
200
$ kubectl -n default2 exec $default2_src -- curl -s -o /dev/null -w %{http_code} http://$default1_dst
200
$ kubectl -n default2 exec $default2_src -- curl -s -o /dev/null -w %{http_code} http://$default2_dst
200
```

#### 結果

| | 宛先がdefault1のproductpage | 宛先がdefault2のproductpage
|:--|:---:|:---:|
|送信元がdefault1のratings|○|○|
|送信元がdefault2のratings|○|○|

## Network Policyを効かせて、Service Mesh上のNamespaceがdefault1とdefault2の間の通信を遮断する

#### 仕込み１

まず、全遮断。

```bash
$ cat default-deny.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
$ kubectl -n default2 apply -f default-deny.yaml
```

#### テスト１

```bash
$ kubectl -n default1 exec $default1_src -- curl -s -o /dev/null -w %{http_code} http://$default1_dst
200
$ kubectl -n default1 exec $default1_src -- curl -s -o /dev/null -w %{http_code} http://$default2_dst
200
$ kubectl -n default2 exec $default2_src -- curl -s -o /dev/null -w %{http_code} http://$default1_dst
200
$ kubectl -n default2 exec $default2_src -- curl -s -o /dev/null -w %{http_code} http://$default2_dst
200
```

#### 結果１

んー、なるほど？SVC目掛けてだとそもそも効かない？

| | 宛先がdefault1のproductpage | 宛先がdefault2のproductpage
|:--|:---:|:---:|
|送信元がdefault1のratings|○|○|
|送信元がdefault2のratings|○|○|

#### 仕込み２

NamespaceSelectorだとどうなるか？

```bash
$ cat namespaceSelector.yaml 
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: nsselector
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          project: hogehoge
    ports:
    - protocol: TCP
      port: 7777
$ kubectl -n default2 apply -f namespaceSelector.yaml 
networkpolicy.networking.k8s.io/nsselector created
```

#### テスト２

```
$ kubectl -n default1 exec $default1_src -- curl -s -o /dev/null -w %{http_code} http://$default1_dst
200
$ kubectl -n default1 exec $default1_src -- curl -s -o /dev/null -w %{http_code} http://$default2_dst
200
$ kubectl -n default2 exec $default2_src -- curl -s -o /dev/null -w %{http_code} http://$default1_dst
200
$ kubectl -n default2 exec $default2_src -- curl -s -o /dev/null -w %{http_code} http://$default2_dst
200
```

#### 結果２

んー、なるほど？やっぱSVC目掛けてだと効かないか。Authorization Policyなら効くだろうけど。
上のドキュメントはIngress、Egressに言及しているから、ns横断の通信とはスコープが違うのかも。もうちょっと勉強しよ。

| | 宛先がdefault1のproductpage | 宛先がdefault2のproductpage
|:--|:---:|:---:|
|送信元がdefault1のratings|○|○|
|送信元がdefault2のratings|○|○|

## ToDo

* Authorization Policyでやる（多分できる）
* Authorization Policyでやったとき、EnvoyがInjectionされていないワークロードからの通信はブロックできるの？（ここがわからん）