GKE+ASM(Istio)でAuthorization Policyを使って送信元IPアドレスを制限してみる

#はじめに
IstioのAuthorization Policyを使って送信元IPアドレス制限を試してみたいと思います。

##Authorization Policyとは
Authorization Policyは、IstioのCRDでService mesh内のワークロードのアクセス制御を可能にします。

>承認ポリシーは、許可ポリシーと拒否ポリシーの両方をサポートします。許可ポリシーと拒否ポリシーがワークロードに同時に使用される場合、拒否ポリシーが最初に評価されます。評価は、次のルールによって決定されます。

>リクエストに一致するDENYポリシーがある場合は、リクエストを拒否します。
ワークロードにALLOWポリシーがない場合は、要求を許可します。
ALLOWポリシーのいずれかが要求に一致する場合は、要求を許可します。
リクエストを拒否します。

Istio 1.7
https://istio.io/latest/docs/reference/config/security/authorization-policy/

Istio 1.6
https://istio.io/v1.6/docs/tasks/security/authorization/authz-ingress/

##確認した環境
GKEバージョン：1.17.12-gke.1504
Anthos Service Mesh(ASM, Istio)：1.6.11
Anthos Service Meshは、Googleのフルマネージドサービスメッシュです。Istio 互換の API, コントロールプレーンを Google が管理します。
https://cloud.google.com/anthos/service-mesh

#試した構成
Authorization Policyを、istio-system namespaceで有効にして、Istio Ingress Gatewayに対する通信において、送信元IPアドレス制限を実現します。
namespace単位でenforce可能です。また、細かく制御したい場合は、Selectorでも指定可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78184/acc83af3-ddd6-85ad-84d5-e701cc72c4a7.png)

#設定内容
以下のPolicyでは、istio-systemに存在するワークロードは、指定したIPアドレス（X.X.X.X）からの通信のみ許可されます。つまり、X.X.X.X以外のIPアドレスからの通信は拒否されます

```
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: ingress-policy
  namespace: istio-system
spec:
  action: ALLOW
  rules:
  - from:
    - source:
       ipBlocks: ["X.X.X.X"]
```

#試してみます
以下を参考にASMをインストールします。
https://cloud.google.com/service-mesh/docs/scripted-install/gke-asm-onboard-1-7

以下を参考にサンプルアプリケーションをインストールします。
https://cloud.google.com/service-mesh/docs/onlineboutique-install-kpt

AuthorizationPolicyを作成します。

```
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: ingress-policy
  namespace: istio-system
spec:
  action: ALLOW
  rules:
  - from:
    - source:
       ipBlocks: ["X.X.X.X"]
```

istio ingress gatewayに対して接続してみる。

```
$ k get svc istio-ingressgateway -n istio-system
NAME                   TYPE           CLUSTER-IP   EXTERNAL-IP     PORT(S)                                                      AGE
istio-ingressgateway   LoadBalancer   10.228.5.0   34.X.X.X   15021:32664/TCP,80:31208/TCP,443:31006/TCP,15443:30608/TCP   10d
```

接続試してみます。access deniedになります。

```
$ curl 34.X.X.X
RBAC: access denied
```

これは、Istio Ingress Gatewayへの通信がNATされていて、送信元IPアドレスが設定したしたものと異なるために発生します。docker.io/kennethreitz/httpbinをデプロイしてみて、送信元IPアドレスを確認してみます。
https://istio.io/v1.6/docs/tasks/security/authorization/authz-ingress/

```
apiVersion: v1
kind: Service
metadata:
  name: http-bin
spec:
  selector:
    app: httpbin
    version: v1
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 80
```

```
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpbin
      version: v1
  template:
    metadata:
      labels:
        app: httpbin
        version: v1
    spec:
      containers:
      - image: docker.io/kennethreitz/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        ports:
        - containerPort: 80
```

Localから接続確認した場合、IPアドレスはVPCのアドレスにNATされます。

```
$ curl 34.Y.Y.Y/ip
{
  "origin": "10.224.3.1"
}
``` 

こちらを防ぐには、serviceの設定に externalTrafficPolicy: Localを記載します。externalTrafficPolicyを設定して、再度確認してみます。

```
apiVersion: v1
kind: Service
metadata:
  name: http-bin
spec:
  selector:
    app: httpbin
    version: v1
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 80
  externalTrafficPolicy: Local
```

Localの送信IPアドレスがそのまま使えるようになりました。

```
$ curl 34.Y.Y.Y/ip
{
  "origin": "164.70.X.X"
}
```

Istio Ingress GatewayのサービスにもexternalTrafficPolicy: Localを設定します。

```
k edit svc istio-ingressgateway -n istio-system
```

```
$ curl 34.X.X.X -o /dev/null -w '%{http_code}\n' -s
200
```

接続できました。

Authorization Policyで指定したIPアドレスと異なる送信元からだとエラーになります。


```
$  curl 34.X.X.X -o /dev/null -w '%{http_code}\n' -s
403
$  curl 34.X.X.X 
RBAC: access denied
```

#注意点
externalTrafficPolicy: Localにした場合、リクエストを受け取ったNodeに該当のPodがいない場合、リクエストが破棄されます。

ASMバージョンアップ時に、Istio Ingress Gatewayの設定が上書きされる可能性があります。