Antrea Cluster Network Policy で高度なネットワークポリシーを使う

# Antrea とは

Antrea とは、オープンソースの Kubernetes CNI で、L3/4 のネットワーキングの機能とセキュリティを Kubernetes クラスターに提供するものです。オープンな仮想スイッチとして長い実績を誇る Open vSwitch をデータプレーンに利用しています。

https://github.com/antrea-io/antrea

Antrea では標準的な Kubernetes クラスターネットワーキングを拡張する様々な機能の実装が試みられています。NetwotkPolicy を拡張する機能として Antrea Cluster Network Policy (ACNP) があります。Antrea は標準的な NetworkPolicy もサポートしていますが、ACNP ではクラスター管理者の要求によりよく答えれるように以下のような機能を提供しています。

- ポリシーの階層化(Tier)と優先度設定(Priority)、Tier レベルの RBAC
- Namespace に限定されない、クラスターレベルのセキュリティ設定
- アクションルールのサポート: Allow, Drop, Reject のアクションを指定可能
- FQDN フィルタのサポート
- L7 プロトコルレベルのフィルタのサポート

本記事では、Antrea Cluster Network Policy に注目して機能の確認をしていきたいと思います。

# Antrea クラスターの準備

Kubernetes クラスタへの Antrea のインストールおよび `antctl` コマンドのインストールについては、以下の記事に例を記載しておりますのでご参照下さい。
https://qiita.com/ynakaoku/items/449b15d06a661fb83e91

本記事は Antrea 1.12.1 の利用を前提に記載しています。

### 設定の確認

Antrea を展開した段階で、Antrea の Configmap が作成されています。現在の設定状態を `antrea-config` で確認してみます。 

```zsh
kubectl get configmap antrea-config -n kube-system -o yaml
```

ここでは `AntreaPolicy` の設定を確認してみます。`antrea-agent.conf` および `antrea-controller.conf` 設定の中に `AntreaPolicy: True` で設定されていますが、コメントアウトされています。Antrea 1.0 からデフォルトで Antrea Network Policy が enable になっているので、この状態ですでに有効になっています。  

```yaml
apiVersion: v1
data:
  antrea-agent.conf: |
    # FeatureGates is a map of feature names to bools that enable or disable experimental features.
    featureGates:
    # AllAlpha is a global toggle for alpha features. Per-feature key values override the default set by AllAlpha.
    #  AllAlpha: false

    # AllBeta is a global toggle for beta features. Per-feature key values override the default set by AllBeta.
    #  AllBeta: false

    # Enable AntreaProxy which provides ServiceLB for in-cluster Services in antrea-agent.
    # It should be enabled on Windows, otherwise NetworkPolicy will not take effect on
    # Service traffic.
    #  AntreaProxy: true

    # Enable EndpointSlice support in AntreaProxy. Don't enable this feature unless that EndpointSlice
    # API version v1 is supported and set as enabled in Kubernetes. If AntreaProxy is not enabled,
    # this flag will not take effect.
    #  EndpointSlice: true

    # Enable TopologyAwareHints in AntreaProxy. This requires AntreaProxy and EndpointSlice to be
    # enabled, otherwise this flag will not take effect.
    #  TopologyAwareHints: true

    # Enable traceflow which provides packet tracing feature to diagnose network issue.
    #  Traceflow: true

    # Enable NodePortLocal feature to make the Pods reachable externally through NodePort
    #  NodePortLocal: true

    # Enable Antrea ClusterNetworkPolicy feature to complement K8s NetworkPolicy for cluster admins
    # to define security policies which apply to the entire cluster, and Antrea NetworkPolicy
    # feature that supports priorities, rule actions and externalEntities in the future.
    #  AntreaPolicy: true

(snip...)
  antrea-controller.conf: |
    # FeatureGates is a map of feature names to bools that enable or disable experimental features.
    featureGates:
    # AllAlpha is a global toggle for alpha features. Per-feature key values override the default set by AllAlpha.
    #  AllAlpha: false

    # AllBeta is a global toggle for beta features. Per-feature key values override the default set by AllBeta.
    #  AllBeta: false

    # Enable traceflow which provides packet tracing feature to diagnose network issue.
    #  Traceflow: true

    # Enable Antrea ClusterNetworkPolicy feature to complement K8s NetworkPolicy for cluster admins
    # to define security policies which apply to the entire cluster, and Antrea NetworkPolicy
    # feature that supports priorities, rule actions and externalEntities in the future.
    #  AntreaPolicy: true

(snip...)
```

# Antrea Cluster Network Policy を試す

では実際に Antrea Cluster Network Policy を使ってみます。K8s 標準の NetworkPolicy との動作の違いも見てみたいと思います。

## サンプルアプリのデプロイ

まず、動作確認のために使用するアプリケーションをデプロイします。ここではサンプルアプリとして kubernetes.io のチュートリアルに含まれている Guestbook アプリを利用します。
https://kubernetes.io/docs/tutorials/stateless-application/guestbook/

`guestbook` という Namespace を作成し、以下のコマンドで Guestbook アプリを展開します。

```zsh
kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/application/guestbook/redis-leader-deployment.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/application/guestbook/redis-leader-service.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/application/guestbook/redis-follower-deployment.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/application/guestbook/redis-follower-service.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/application/guestbook/frontend-deployment.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/application/guestbook/frontend-service.yaml
```

`frontend`, `redis-follower`, `redis-leader` の三種類の Service と Pod が作成されます。

```shell-session
$ kubectl get all -n guestbook
NAME                                  READY   STATUS      RESTARTS   AGE
pod/frontend-767747dfdd-7sl6f         1/1     Running     0          42m
pod/frontend-767747dfdd-pqvnn         1/1     Running     0          42m
pod/redis-follower-66497dbb55-4gmqc   1/1     Running     0          42m
pod/redis-follower-66497dbb55-ksjpl   1/1     Running     0          42m
pod/redis-leader-6bfcff974b-btvr5     1/1     Running     0          42m

NAME                     TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)        AGE
service/frontend         LoadBalancer   10.103.238.3     172.19.0.212   80:32456/TCP   42m
service/redis-follower   ClusterIP      10.97.241.156    <none>         6379/TCP       42m
service/redis-leader     ClusterIP      10.111.203.236   <none>         6379/TCP       42m

NAME                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/frontend         2/2     2            2           42m
deployment.apps/redis-follower   2/2     2            2           42m
deployment.apps/redis-leader     1/1     1            1           42m

NAME                                        DESIRED   CURRENT   READY   AGE
replicaset.apps/frontend-767747dfdd         2         2         2       42m
replicaset.apps/redis-follower-66497dbb55   2         2         2       42m
replicaset.apps/redis-leader-6bfcff974b     1         1         1       42m
```

`frontend` Service はチュートリアルでは `type: ClusterIP` ですが、ここでは `type: LoadBalancer` タイプに修正し、外部ロードバランサを介して外部から接続できるようにしています。使用環境に合わせて設定して下さい。
設定後、構成は以下のようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/d5150f45-8e09-54ab-3c37-c24a7f3af642.png)

以下の URL にアクセスすると Guestbook の UI が表示されます。

```http
http://172.19.0.212/
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/dcb9ff67-7e37-0fdb-358b-825ff8c72ba4.png)

ここで、`Messages` 欄にメッセージを一つ書き込んでみます。`first message` と記入し Submit すると、Redis に格納されたメッセージが以下のように表示されるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/ca4413ab-26f8-7b93-7692-b3494d23cf88.png)


ここで各 Pod のラベル設定を確認しておきます。フロントエンドの Pod である `frontend` には`app: guestbook`の、バックエンドの`redis-leader`と`redis-follower`には`app: redis`のラベルが設定されています。ラベルはこのあと NetworkPolicy や Antrea Network Policy の `PodSelector`設定で使用します。

```shell-session
$ kubectl get pod -L=app
NAME                              READY   STATUS      RESTARTS   AGE   APP
frontend-767747dfdd-7sl6f         1/1     Running     0          43m   guestbook
frontend-767747dfdd-pqvnn         1/1     Running     0          43m   guestbook
redis-follower-66497dbb55-4gmqc   1/1     Running     0          43m   redis
redis-follower-66497dbb55-ksjpl   1/1     Running     0          43m   redis
redis-leader-6bfcff974b-btvr5     1/1     Running     0          43m   redis
```

## NetworkPolicy 

Antrea Cluster Network Policy を試す前に、標準の NetworkPolicy の動作を確認してみます。

### Namespace 内のコンテナ間制御

以下のポリシーを適用します。このポリシーは `app: redis` の Pod に対して同じラベルを持つ Pod との通信を許可するものですが、それ以外の通信は不許可となります。つまり Namespace 内のフロントエンド`frontend`とバックエンド`redis`の通信は阻害されることになります。  

```yaml:np1.yaml
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: drop-access-to-redis
spec:
  policyTypes:
  - Ingress
  podSelector:
    matchLabels:
      app: redis
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: redis
```
```shell-session
$ kubectl apply -f np1.yaml
networkpolicy.networking.k8s.io/drop-access-to-redis created
```

再度 Guestbook の URL にアクセスすると以下のようにメッセージが表示されなくなっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/f20a14d7-c39c-2511-5ec2-f96148e8572a.png)

ちょっと分かりにくいですが、下の図のように Redis バックエンドへの接続ができないために記録したメッセージを読み出せなくなっているのです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/1ac42c44-d505-e059-63bb-9d6ec922a018.png)

この NetworkPolicy を削除すると、再び Redis からメッセージを取得できるようになります。

```shell-session
$ kubectl delete -f np1.yaml
networkpolicy.networking.k8s.io "drop-access-to-redis" deleted
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/865661d0-fba1-23ae-f308-bb5d38f9a96d.png)


### Namespace 外からのアクセスの制御

今度はフロントエンド `guestbook` へのアクセスを禁じるポリシーを作成し適用します。

```yaml:np2.yaml
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: drop-access-to-guestbook
spec:
  policyTypes:
  - Ingress
  podSelector:
    matchLabels:
      app: guestbook
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: other
```
```shell-session
$ kubectl apply -f np2.yaml
networkpolicy.networking.k8s.io/drop-access-to-guestbook created
```

すると、以下のように`guestbook`にアクセスできなくなりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/78601cb8-0639-44c5-5b4f-88fb6e0499b4.png)

実際には以下のように外部から `frontend` への接続が阻害されたため、外部 LB ダウンしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/b707b6cf-f6dc-6a5d-1e49-ac55aff715c7.png)

再びポリシーを削除するとフロントエンドからもアクセスできるようになります。

```shell-session
$ kubectl delete -f np2.yaml
networkpolicy.networking.k8s.io "drop-access-to-guestbook" deleted
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/8acf68af-7f2b-e037-da2d-c4054bed1470.png)

### Namespace 間の制御

今度は他の Namespace からのトラフィックの制御を確認してみます。このテストを行うために Namespace `test` を作成し、`project:test` のラベルを付けます。

```shell-session
$ kubectl create namespace test
namespace/test created
$ kubectl label namespace test project=test
namespace/test labeled
```

次にテスト用の `frontend` コンテナとサービスを `test` Namespace 内に作成します。

```shell-session
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/application/guestbook/frontend-deployment.yaml -n test
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/application/guestbook/frontend-service.yaml -n test
$ kubectl get all -n test -L=app
NAME                            READY   STATUS      RESTARTS   AGE     APP
pod/frontend-7cc596bb48-dsgds   1/1     Running     0          9m24s   guestbook
pod/frontend-7cc596bb48-l6wsg   1/1     Running     0          9m19s   guestbook

NAME               TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE     APP
service/frontend   LoadBalancer   10.105.1.244   172.19.0.217   80:30448/TCP   9m10s   guestbook

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE     APP
deployment.apps/frontend   2/2     2            2           9m24s  

NAME                                  DESIRED   CURRENT   READY   AGE     APP
replicaset.apps/frontend-7cc596bb48   2         2         2       9m24s   guestbook
```
`frontend` Service は同じく `type: LoadBalancer` に修正しています。
また `frontend` Deployment の `env` を変更して、`guestbook` Namespace 内に作成したバックエンド Redis にアクセスするように変更してリスタートしました。以下のように編集しています。

```yaml
(snip...)
      - env:
        - name: GET_HOSTS_FROM
          value: env
        - name: REDIS_FOLLOWER_SERVICE_HOST
          value: redis-follower.guestbook.svc.cluster.local
        - name: REDIS_LEADER_SERVICE_HOST
          value: redis-leader.guestbook.svc.cluster.local
(snip...)
```

次に、以下のポリシーを作成して `test` Namespace からのアクセスを許可します。

```yaml:np3.yaml
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: allow-access-from-other-ns
spec:
  policyTypes:
  - Ingress
  podSelector:
    matchLabels:
      app: redis
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          project: test
```
```shell-session
$ kubectl apply -f np3.yaml -n guestbook
networkpolicy.networking.k8s.io/allow-access-from-other-ns created
```
このポリシーでは `test` Namespace から `guestbook` Namespace の Redis へのアクセスを許可しています。構成は以下のようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/08abed6f-6366-d95f-7e36-aa92912dc8c2.png)

`test` Namespace 側の `frontend` Service にアクセスすると、先ほど書き込んだメッセージとともに Guestbook アプリが表示されます。

```http
http://172.19.0.217/
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/a995f619-98b5-746d-0525-85df484ba584.png)

では、今度は以下のような NetwotkPolicy を作成して、`test` ネームスペース内の Pod から `guestbook` Namespace 内の Redis へのアクセスを制限しようとしてみます。

```yaml:np4.yaml
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: drop-access-from-other-ns-pod
spec:
  policyTypes:
  - Ingress
  podSelector:
    matchLabels:
      app: redis
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          project: test
    - podSelector:
        matchLabels:
          app: other
```
```shell-session
$ kubectl delete -f np3.yaml
networkpolicy.networking.k8s.io "allow-access-from-other-ns" deleted
$ kubectl apply -f np4.yaml
networkpolicy.networking.k8s.io/allow-access-from-other-ns created
```
Namespace と Pod の組み合わせで制限したつもりでしたが、実際には以下のように Redis にアクセスできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/775701e9-ec86-9b69-e4f1-f87c2dc57935.png)


このように、NetworkPolicy ではクラスターに閉じた形で Namespace 毎のアクセス制御が可能ですが、Namespace やクラスターを超えて同じルールを適用することはできません。また、ポリシーは allow ルールのみですが同時に暗黙の deny ルールが記述されます。そのため細やかな deny ルールを書くようなセキュリティの掛け方には向かないようです。
Antrea Cluster Network Policy はこれらの課題に対応します。

## Antrea Cluster Network Policy (ACNP)

ではいよいよ Antrea Cluster Network Policy を設定していきます。
最近の Antrea では最初から Antrea Cluster Network Policy が enable になっていますので、すぐに設定を開始できますが、まず Antrea Cluster Network Policy 独自の Tier について確認しておきます。

### Tier 

Tier はポリシーを階層化して管理するための概念で、ポリシーに対して優先度をつけたい場合に有用です。初期設定では Tier は以下のように設定されています。

```shell-session
$ kubectl get tier --sort-by=.spec.priority
NAME          PRIORITY   AGE
emergency     50         37m
securityops   100        37m
networkops    150        37m
platform      200        37m
application   250        37m
baseline      253        37m
```

各 Tier の `PRIORITY` が、実際に処理される優先度を表します。小さい数値ほど優先度が高くなります。たとえば最優先で処理させたいポリシーがある場合は `emergency` に設定すれば最初に処理されるようになります。標準の NetworkPolicy で設定されたポリシーは、`application` Tier の後で処理されます。

Tier は自分で作成することも可能です。ここでは新しい Tier を以下のマニフェスト`mytier.yaml`で作成してみます。

```yaml:mytier.yaml
apiVersion: crd.antrea.io/v1alpha1
kind: Tier
metadata:
  name: mytier
spec:
  priority: 10
  description: "my custom tier"
```

```shell-session
$ kubectl apply -f mytier.yaml
tier.crd.antrea.io/mytier created
$
$ kubectl get tiers --sort-by=.spec.priority
mytier                        10         36s
emergency                     50         124d
securityops                   100        124d
networkops                    150        124d
platform                      200        124d
application                   250        124d
baseline                      253        124d
```

### Antrea Cluster Network Policy の適用

では Antrea Cluster Network Policy を実際に作成していきます。まず以下のマニフェストを作成します。

```yaml:acnp1.yaml
apiVersion: crd.antrea.io/v1alpha1
kind: ClusterNetworkPolicy
metadata:
  name: acnp-drop-access-to-redis
spec:
  priority: 5
  tier: securityops
  appliedTo:
    - podSelector:
        matchLabels:
          app: redis
  ingress:
    - action: Allow
      from:
        - podSelector:
            matchLabels:
              app: other
      name: AllowFromOther
      enableLogging: true
```

これは NetworkPolicy のところで最初に作成した`np1.yaml`によく似ていますが、実際の動作は異なります。以下のコマンドでこのマニフェストを適用します。

```shell-session
$ kubectl apply -f acnp1.yaml
clusternetworkpolicy.crd.antrea.io/acnp-drop-access-to-redis created
$ 
$ kubectl get acnp
NAME                        TIER          PRIORITY   DESIRED NODES   CURRENT NODES   AGE
acnp-drop-access-to-redis   securityops   5          1               1               24s
```

この状態で Guestbook アプリにアクセスしても、バックエンドへのアクセスは阻害されないため、正常に表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/162958c3-8597-3d7f-3b24-60d27222714a.png)

NetworkPolicy ではポリシーが反映されると同時に暗黙の Deny が有効になり、対象の Pod に対して明示的に許可されないトラフィックはドロップされるようになりますが、ACNP では明示的に Drop ルールが書かれない限りトラフィックは許可されます。  
なお、ACNP の kind は`ClusterNetworkPolicy`ですが、上の`kubectl get acnp`コマンドのように、`acnp`という省略形で操作を行うことも可能です。  

今度は、`acnp1.yaml`を修正して以下の`acp11.yaml`を作成し適用してみます。明示的にソースとなる Pod を`podSelector` で指定し、　`action`を `Drop` に変更します。

```yaml:acnp11.yaml
apiVersion: crd.antrea.io/v1alpha1
kind: ClusterNetworkPolicy
metadata:
  name: acnp-drop-access-to-redis
spec:
  priority: 5
  tier: securityops
  appliedTo:
    - podSelector:
        matchLabels:
          app: redis
  ingress:
    - action: Drop
      from:
        - podSelector:
            matchLabels:
              app: guestbook
      name: DropFromGuestbook
      enableLogging: true
```

これを同じく適用します。

```shell-session
$ kubectl apply -f acnp11.yaml
clusternetworkpolicy.crd.antrea.io/acnp-drop-access-to-redis configured
```

すると、今度は Redis へのアクセスが阻害されるため、以下のようにメッセージが表示されなくなりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/61a77c00-025a-963d-4f21-59ca95ca916a.png)

上のマニフェストでは `enableLogging: true` に設定しているので、条件にマッチしたトラフィックが流れると各ノードの `/var/log/antrea/networkpolicy/np.log` にロギングされるようになります。ノードにログインしてこのファイルを確認すると、最後に以下のようなログが出力されていることが確認できました。

```:/var/log/antrea/networkpolicy/np.log
... 
2023/11/01 08:02:06.424992 AntreaPolicyIngressRule AntreaClusterNetworkPolicy:acnp-drop-access-to-redis DropFromGuestbook Drop 44900 192.168.1.97 41662 192.168.1.30 6379 TCP 60 <nil>
2023/11/01 08:02:07.438157 AntreaPolicyIngressRule AntreaClusterNetworkPolicy:acnp-drop-access-to-redis DropFromGuestbook Drop 44900 192.168.1.97 41662 192.168.1.30 6379 TCP 60 <nil>
2023/11/01 08:02:09.453315 AntreaPolicyIngressRule AntreaClusterNetworkPolicy:acnp-drop-access-to-redis DropFromGuestbook Drop 44900 192.168.1.97 41662 192.168.1.30 6379 TCP 60 <nil>
```
`DropFromGuestbook` の Drop ルールに基づいて、送信元 Pod `192.168.1.97` から宛先 Pod `192.168.1.30`、宛先ポート番号 `6379` への通信がドロップされたことが分かります。

また、この ACNP では、`tier` を `securityops`、 `priority` を `5` に指定しています。`priority` は `tier` の内部での処理の優先度を表します。
上述のように `securityops` Tier に指定したポリシーは標準の NetworkPolicy よりも優先的に処理されます。例えば、`np1.yaml`を最初に適用してバックエンドへのアクセスが阻害されている状態を作り、その上で以下の`acnp12.yaml`を適用すると、ACNP の処理が優先されてバックエンドへのアクセスが許可されるようになります。

```yaml:acnp12.yaml
apiVersion: crd.antrea.io/v1alpha1
kind: ClusterNetworkPolicy
metadata:
  name: acnp-allow-access-to-redis
spec:
  priority: 5
  tier: securityops
  appliedTo:
    - podSelector:
        matchLabels:
          app: redis
  ingress:
    - action: Allow
      from:
        - podSelector:
            matchLabels:
              app: guestbook
      name: AllowFromGuestbook
      enableLogging: true
```

```shell-session
$ kubectl apply -f np1.yaml
networkpolicy.networking.k8s.io/drop-access-to-redis created
$ kubectl apply -f acnp12.yaml
clusternetworkpolicy.crd.antrea.io/acnp-allow-access-to-redis created
$ kubectl get netpol
NAME                   POD-SELECTOR   AGE
drop-access-to-redis   app=redis      33s
$ kubectl get acnp
NAME                         TIER          PRIORITY   DESIRED NODES   CURRENT NODES   AGE
acnp-allow-access-to-redis   securityops   5          1               1               18s
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/e3a6b9fc-4e07-3509-b094-f55e7ac25dd1.png)

この例を図示すると以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/6bbbbe55-5cac-42a5-1eee-6d3f56440c5f.png)

このように、Antrea Cluster Network Policy を使えば、標準の NetworkPolicy よりもより明確に送信元と宛先を指定したセキュリティポリシーの定義ができます。  


### ACNP による Namespace 間の制御

ここからは ACNP による Namespace をまたいだ制御を見ていきたいと思います。その前に、もう一度 K8s NetworkPolicy ではどのような挙動になるかを確認しておきます。上で利用した `np1.yaml` コピーして、 Guestbook アプリ間のトラフィックを許可するように変更した `np11.yaml` を作成し適用します。

```yaml:np11.yaml
kkind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: allow-access-from-guestbook-to-redis
spec:
  policyTypes:
  - Ingress
  podSelector:
    matchLabels:
      app: redis
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: guestbook
```
```shell-session
$ kubectl apply -f np11.yaml
networkpolicy.networking.k8s.io/allow-access-from-guestbook-to-redis created
```

現在、`guestbook`と`test` の Namespace にデプロイ済みの Pod の状態は以下のようになっています。

```shell-session
$ kubectl get pod -L=app -n guestbook
NAME                              READY   STATUS    RESTARTS   AGE     APP
frontend-767747dfdd-hx7xc         1/1     Running   0          148m    guestbook
frontend-767747dfdd-mngdm         1/1     Running   0          148m    guestbook
redis-follower-66497dbb55-ksjpl   1/1     Running   0          4h13m   redis
redis-follower-66497dbb55-vm75v   1/1     Running   0          3h16m   redis
redis-leader-85f4588648-2wffl     1/1     Running   0          3h55m   redis
$ kubectl get pod -L=app -n test
NAME                        READY   STATUS    RESTARTS   AGE   APP
frontend-7cc596bb48-dsgds   1/1     Running   0          94m   guestbook
frontend-7cc596bb48-l6wsg   1/1     Running   0          94m   guestbook
```

この状態では、`test` Namespace 側の Guestbook にアクセスしても、Redis へのアクセスは許可されないので、以下のようにメッセージを取得できません。K8s NetworkPolicy では Namespace をまたいで `podSelector` を利用できないからです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/95b4c1ea-6144-f995-804f-085d443a0ba6.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/5c6dd40d-cfa6-6b9e-14aa-2a1dfe23f2fb.png)

では、ACNP で同じようなポリシーを作成してトライしてみましょう。

```yaml:acnp2.yaml
apiVersion: crd.antrea.io/v1alpha1
kind: ClusterNetworkPolicy
metadata:
  name: acnp-allow-access-from-guestbook-to-redis
spec:
  priority: 5
  tier: securityops
  appliedTo:
    - podSelector:
        matchLabels:
          app: redis
  ingress:
    - action: Allow
      from:
        - podSelector:
            matchLabels:
              app: guestbook
      name: AllowFromGuestbook
      enableLogging: true
```
```shell-session
$ kubectl apply -f acnp2.yaml
clusternetworkpolicy.crd.antrea.io/acnp-allow-access-from-guestbook-to-redis created
```
`np11.yaml` と同様に `appliedTo` と `from` の両方の `podSelector` でラベル `app: guestbook` を指定していますが、Namespace は特に指定していません。また、前に作成した K8s NetworkPolicy もそのままにしてあります。

```
$ kubectl get netpol
NAME                                   POD-SELECTOR   AGE
allow-access-from-guestbook-to-redis   app=redis      11m
$ 
$ kubectl get acnp
NAME                                        TIER          PRIORITY   DESIRED NODES   CURRENT NODES   AGE
acnp-allow-access-from-guestbook-to-redis   securityops   5          1               1               103s
```

この状態で再び `test` 側の Guestbook にアクセスすると、以下のように許可されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/7d7699bb-3bff-b18a-b8e5-d92dd32f0b10.png)

ACNP の Allow ルールにマッチして処理されたので、以下のようなログも出力されます。
```:/var/log/antrea/networkpolicy/np.log
... 
2023/11/01 08:15:34.431560 AntreaPolicyIngressRule AntreaClusterNetworkPolicy:acnp-allow-access-from-guestbook-to-redis AllowFromGuestbook Allow 44900 192.168.1.97 59608 192.168.1.41 6379 TCP 60 <nil>
```
図示すると以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/97b09bf0-f4ef-19c5-c983-7d308922b067.png)

ACNP ではこのように Namespace をまたがって `PodSelector` を評価することができます。
また、ACNP では `podSelector` と `namespaceSelector` を併用して、namespace 毎に適用対象や送信元となる pod を明示的に指定することができます。`acnp2.yaml` を編集して `namespaceSelector` 条件を追加した `acnp21.yaml` を作成します。

```yaml:acnp21.yaml
apiVersion: crd.antrea.io/v1alpha1
kind: ClusterNetworkPolicy
metadata:
  name: acnp-allow-access-from-guestbook-to-redis
spec:
  priority: 5
  tier: securityops
  appliedTo:
    - podSelector:
        matchLabels:
          app: redis
      namespaceSelector:
        matchLabels:
          project: prod
  ingress:
    - action: Allow
      from:
        - podSelector:
            matchLabels:
              app: guestbook
          namespaceSelector:
            matchLabels:
              project: test
      name: AllowFromGuestbook
      enableLogging: true
```
```shell-session
$ kubectl apply -f acnp21.yaml
clusternetworkpolicy.crd.antrea.io/acnp-allow-access-from-to-guestbook configured
```

この修正により、受信側 Namespace のラベル条件に `project: prod`、送信側 namespace のラベル条件に `project: test` が追加されました。受信側の Guestbook Pods は `guestbook` Namespace 内にありますが、ラベルが付いていないので、この状態では `test` 側からはアクセスできません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/9f69e70b-a6d6-01a2-8206-66d237a77f21.png)

そこで `guestbook` Namespace にラベルを追加します。

```shell-session
$ kubectl label ns guestbook project=prod
namespace/guestbook labeled
$kubectl get ns -L=project
NAME                   STATUS   AGE     PROJECT
default                Active   125d    
guestbook              Active   6d23h   prod
kube-node-lease        Active   125d    
kube-public            Active   125d    
kube-system            Active   125d    
test                   Active   23h     test
```

すると `test` Namespace 内の Guestbook から `guestbook` Namespace 内の Redis にアクセスできるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/ad3f5bd4-35b3-b25a-8b5e-0b381b5124d1.png)
```:/var/log/antrea/networkpolicy/np.log
... 
2023/11/01 08:28:16.723581 AntreaPolicyIngressRule AntreaClusterNetworkPolicy:acnp-allow-access-from-guestbook-to-redis AllowFromGuestbook Allow 44900 192.168.1.71 49126 192.168.1.30 6379 TCP 60 <nil>
```

このように、Antrea Cluster Network Policy では、Namespace に限定されないクラスターレベルのネットワークポリシーを設定することが可能です。クラスター管理者は Namespace に依存しないクラスターレベルのセキュリティポリシーを設定することができるようになります。

## ClusterGroup

ClusterGroup は ACNP の特徴的な機能の一つで、エンドポイントをグルーピングするルールを NetworkPolicy の外で作成し再利用することができます。これまでに紹介した `podSelector` や `namespaceSelector`、おなじみの `ipBlock` 以外に、K8s Service を利用する `serviceReference` やグルーピングのネスト構造を定義できる `childGroups` といった定義を使うことが可能です。

ClusterGroup を使ったルールを作成していく前に、これまでに作成してきたすべてのポリシーをいったん削除します。

```shell-session
$ kubectl get networkpolicy --no-headers | awk '{print $1}'| xargs kubectl delete networkpolicy
networkpolicy.networking.k8s.io "allow-access-from-guestbook-to-redis" deleted
$ 
$ kubectl get acnp --no-headers | awk '{print $1}'| xargs kubectl delete acnp
clusternetworkpolicy.crd.antrea.io "acnp-allow-access-from-guestbook-to-redis" deleted
```

そして `guestbook` Namespace 内の Pod へのすべてのトラフィックを暗黙的に不許可にするために以下の `acnp-dropany.yaml` を作成し適用します。適用先の Tier として `baseline` を指定しています。この場合、このルールは K8s NetworkPolicy を含むすべてのポリシールールが評価された後に適用されるようになります。

```yaml:acnp-dropany.yaml
apiVersion: crd.antrea.io/v1alpha1
kind: ClusterNetworkPolicy
metadata:
  name: acnp-drop-any-in-production
spec:
  priority: 100
  tier: baseline
  appliedTo:
    - podSelector: {}
      namespaceSelector:
        matchLabels:
          project: prod
  ingress:
    - action: Drop
      from:
        - podSelector: {}
      name: DropFromAny
      enableLogging: true 
```
```shell-session
$ kubectl apply -f acnp-dropany.yaml
clusternetworkpolicy.crd.antrea.io/acnp-drop-any-in-production created
```

すべての Pod 間の通信が不許可になるため、Guestbook アプリのフロントエンドと Redis バックエンド間の通信もできなくなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/74b1edc2-af77-55ee-d12b-255254f9486c.png)
```:/var/log/antrea/networkpolicy/np.log
... 
2023/11/01 08:31:25.542766 IngressDefaultRule AntreaClusterNetworkPolicy:acnp-drop-any-in-production DropFromAny Drop 16 192.168.1.92 44850 192.168.1.30 6379 TCP 60 <nil>
2023/11/01 08:31:26.573559 IngressDefaultRule AntreaClusterNetworkPolicy:acnp-drop-any-in-production DropFromAny Drop 16 192.168.1.92 44850 192.168.1.30 6379 TCP 60 <nil>
2023/11/01 08:31:28.589537 IngressDefaultRule AntreaClusterNetworkPolicy:acnp-drop-any-in-production DropFromAny Drop 16 192.168.1.92 44850 192.168.1.30 6379 TCP 60 <nil>
```

そこで、 フロントエンドとバックエンド間の通信を許可するポリシーを記載していきますが、そこで ClusterGroup を使用してみます。ここでは、Redis バックエンドサービスのグルーピングに `serviceReference` と `childGroups` を使ってみます。

```yaml:cg1.yaml
apiVersion: crd.antrea.io/v1alpha3
kind: ClusterGroup
metadata:
  name: cg-guestbook
spec:
  podSelector:
    matchLabels:
      app: guestbook
---
apiVersion: crd.antrea.io/v1alpha3
kind: ClusterGroup
metadata:
  name: cg-redis-leader
spec:
  serviceReference:
    name: redis-leader
    namespace: guestbook
---
apiVersion: crd.antrea.io/v1alpha3
kind: ClusterGroup
metadata:
  name: cg-redis-follower
spec:
  serviceReference:
    name: redis-follower
    namespace: guestbook
---
apiVersion: crd.antrea.io/v1alpha3
kind: ClusterGroup
metadata:
  name: cg-redis-nested
spec:
  childGroups: [cg-redis-leader, cg-redis-follower]
```
```shell-session
$ kubectl apply -f cg1.yaml
clustergroup.crd.antrea.io/cg-guestbook created
clustergroup.crd.antrea.io/cg-redis-leader created
clustergroup.crd.antrea.io/cg-redis-follower created
clustergroup.crd.antrea.io/cg-redis-nested created
```

次にこれらの ClusterGroup を利用した ACNP を `acnp3.yaml` として作成します。

```yaml:acnp3.yaml
apiVersion: crd.antrea.io/v1alpha1
kind: ClusterNetworkPolicy
metadata:
  name: acnp-allow-access-to-guestbook
spec:
  priority: 5
  tier: securityops
  appliedTo:
    - group: "cg-guestbook"
  ingress:
    - action: Allow
      from:
        - group: "cg-guestbook"
      name: AllowToGuestbook
      enableLogging: true
---
apiVersion: crd.antrea.io/v1alpha1
kind: ClusterNetworkPolicy
metadata:
  name: acnp-allow-access-to-redis
spec:
  priority: 5
  tier: securityops
  appliedTo:
    - group: "cg-redis-nested"
  ingress:
    - action: Allow
      from:
        - group: "cg-guestbook"
      name: AllowFromGuestbookToRedis
      enableLogging: true
---
apiVersion: crd.antrea.io/v1alpha1
kind: ClusterNetworkPolicy
metadata:
  name: acnp-allow-access-to-redis-follower
spec:
  priority: 5
  tier: securityops
  appliedTo:
    - group: "cg-redis-follower"
  ingress:
    - action: Allow
      from:
        - group: "cg-redis-leader"
      name: AllowToRedisFollower
      enableLogging: true 
```
```shell-session
$ kubectl apply -f acnp3.yaml
clusternetworkpolicy.crd.antrea.io/acnp-allow-access-to-guestbook created
clusternetworkpolicy.crd.antrea.io/acnp-allow-access-to-redis created
clusternetworkpolicy.crd.antrea.io/acnp-allow-access-to-redis-follower created
```

この状態でブラウザからアクセスすると、Redis バックエンドへのアクセスも許可されて正常に応答が返ってくるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/18cd803c-92f0-67bf-4aac-5f7917a6fe6c.png)
```:/var/log/antrea/networkpolicy/np.log
... 
2023/11/01 08:33:32.281153 AntreaPolicyIngressRule AntreaClusterNetworkPolicy:acnp-allow-access-to-redis AllowFromGuestbookToRedis Allow 44900 192.168.1.92 35194 192.168.1.30 6379 TCP 60 <nil>
```

いかがでしたでしょうか。Antrea Cluster Network Policy では、クラスターレベルで柔軟なエンドポイントのグルーピングが可能になっていることを理解いただけたかと思います。

## FQDN フィルタリング

FQDN フィルタリングは、Pod からの外向け通信において、通信先の完全修飾ドメイン名 (FQDN) を指定して特定のドメインとの通信を制御する仕組みです。ACNP の `egress` ルールで使用できます。FQDN とのマッチングは完全一致とワイルドカードを用いた部分一致を使うことができます。FQDN と実際の通信で使用される IP アドレスの関連付けは、Pod からの DNS クエリーをインスペクションすることで行います。
では早速 FQDN フィルタリングを試してみます。前のステップで作成した NetworkPolicy と ACNP をすべて削除します。次に `guestbook` Namespace からの出力トラフィックをすべて拒否する以下のマニフェストを作成し、適用します。

```yaml:acnp-reject-egress.yaml
apiVersion: crd.antrea.io/v1alpha1
kind: ClusterNetworkPolicy
metadata:
  name: acnp-reject-egress-in-production
spec:
  priority: 100
  tier: baseline
  appliedTo:
    - podSelector: {}
      namespaceSelector:
        matchLabels:
          project: prod
  egress:
    - action: Reject
      name: RejectToAny
      enableLogging: true
```
```shell-session
$ kubectl apply -f acnp-reject-egress.yaml
clusternetworkpolicy.crd.antrea.io/acnp-reject-egress-in-production created
```
この状態で `frontend` Pod から www.google.com にアクセスしようとすると、以下のように拒否されます。
```shell-session
$ kubectl exec frontend-767747dfdd-5896s -it -- curl -o tempfile www.google.com
curl: (7) Failed to connect to www.google.com port 80: Connection refused
command terminated with exit code 7
```
ここで適用したマニフェストではアクションを `Reject` に設定したので、TCP 接続が Antrea により拒否され接続はすぐに失敗します。Reject したことがログに出力されます。
```:/var/log/antrea/networkpolicy/np.log 
...
2023/11/01 10:41:36.949294 EgressDefaultRule AntreaClusterNetworkPolicy:acnp-reject-egress-in-production RejectToAny Reject 16 192.168.1.97 45020 172.217.26.228 80 TCP 60 <nil>
```
次に、google.com ドメインへのアクセスを許可する以下のようなマニフェストを作成して適用します。許可ルールにて `fqdn: "*google.com"` と指定することで、google.com のすべてのホスト、サブドメインへのアクセスが許可されます。
```yaml: acnp-allow-google.yaml 
apiVersion: crd.antrea.io/v1alpha1
kind: ClusterNetworkPolicy
metadata:
  name: acnp-fqdn-allow-google
spec:
  priority: 5
  appliedTo:
    - podSelector: {}
      namespaceSelector:
        matchLabels:
          project: prod
  egress:
  - action: Allow
    to:
      - fqdn: "*google.com"
    name: AllowToGoogle
    enableLogging: true
```
```shell-session
$ kubectl apply -f acnp-allow-google.yaml
clusternetworkpolicy.crd.antrea.io/acnp-fqdn-allow-google created
```
もう一度 `frontend` Pod から www.google.com にアクセスすると、以下のように許可されます。
```shell-session
$ kubectl exec frontend-767747dfdd-5896s -it -- curl -o tempfile www.google.com
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 19602    0 19602    0     0  98010      0 --:--:-- --:--:-- --:--:-- 98010
```
```:/var/log/antrea/networkpolicy/np.log 
...
2023/11/01 10:56:56.359560 AntreaPolicyEgressRule AntreaClusterNetworkPolicy:acnp-fqdn-allow-google AllowToGoogle Allow 14500 192.168.1.97 45802 172.217.26.228 80 TCP 60 <nil>
```
なお、この時点で ACNP は以下のような状態になっています。
```shell-session
$ kubectl get acnp
NAME                               TIER          PRIORITY   DESIRED NODES   CURRENT NODES   AGE
acnp-fqdn-allow-google             application   5          1               1               6m5s
acnp-reject-egress-in-production   baseline      100        1               1               26m
```
Google ドメインへの接続を許可するポリシーは Reject ポリシーよりも優先度の高い `application` Tier に設定されているため、先に評価されることが分かります。 

<!--

## L7 protocol フィルタリング

ACNP は通常 Layer3/4 のトラフィックフィルタリングを行いますが、L7 protocol フィルタリングを使用するとアプリケーション層 (Layer7) を認識して高度なフィルタリングを行うことができます。例えば HTTP トラフィックを
https://github.com/antrea-io/antrea/blob/main/docs/antrea-l7-network-policy.md
では早速 FQDN フィルタリングを試してみます。前のステップで作成した NetworkPolicy と ACNP をすべて削除します。次に `guestbook` Namespace からの出力トラフィックをすべて拒否する以下のマニフェストを作成し、適用します。
-->

---  
次回以降、Antrea Cluster Network Policy についてより詳細な操作方法などをご紹介していければと思います。