istioを使い認証プロセスをアプリケーションから分離する

https://istio.io/latest/docs/concepts/security/
istioのsecurityコンセプトについてまとめられたドキュメントを見ていたら、ゼロトラストや多層防御などと同様にアプリケーションコードからセキュリティを分離するのもスコープとなっていました。認証プロセスをアプリケーションコードから分離するのに良さそうです。
istioを使ってアプリケーションコードを変更せずにアクセスコントロールができるかを見ていきます

実行環境はwsl2(ubuntu20.04)で構築したkubernetesクラスタで行います
主要ソフトウェアとしては以下のバージョンを利用しています

```bash
minikube version: v1.15.1
kubernetes v1.20.0
istio v1.8.1
```
## oauth2-proxyを利用したOpenID Providerとの連携

一例として、workloadへのアクセスについてOIDC認証することを強制してみます。
ありがたいことにJetstackからoauth2-proxyの[設定例](https://www.blog.jetstack.io/blog/istio-oidc/#oauth2-proxy
)が公開されているので参考にします。

ここでやることは以下です
・RequestAuthenticationによってワークロードのリクエスト認証方法をGoogle OAuth applicationと定義
・AuthorizationPolicyによってワークロードへのアクセスにAuthorizationヘッダを含むことを強制
・oauth2-proxyがreverse proxyとして振る舞うことでGoogle OAuth applicationに認証を移譲

### 証明書を用意
cert-managerでletsencryptから証明書を取得してくれるはずなんですが、私の環境では以下のエラーによりアクセス出来ませんでした。とりあえずローカルで起動するように"*.127.0.0.1.nip.io"のドメインに対する証明書を作ってお茶を濁します。

```
curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to :443
```
SSL通信のためワイルドカード証明書を作成しistio-systemにデプロイします

```bash:deploy_wildcard_certification
#!/usr/bin/env bash

mkcert "*.127.0.0.1.nip.io"
mv _wildcard.127.0.0.1.nip.io-key.pem server.key
mv _wildcard.127.0.0.1.nip.io.pem server.crt
kubectl create -n istio-system secret generic wildcard-clusterlocal-credential --from-file=key=server.key --from-file=cert=server.crt
```

### Google OAuth applicationの作成
Google OAuth applicationはkubeflowのドキュメントを参考にしながら作成します
https://www.kubeflow.org/docs/gke/deploy/oauth-setup/

### 各種設定
ブログではコマンドラインで実行できるようになっているのでターゲットのドメインとして
$NGINX_DOMAIN=istio-oicd-auth.127.0.0.1.nip.ioと設定します

oauth2-proxyのconfigurationについては以下で定義されています
https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview/
trusted-ipなどの設定も可能なのでinternalな利用も出来そうですね

RequestAuthentication,AuthorizationPolicyが実際どう使われているかですが、以下のように指定プロバイダからの署名であることの検証とリクエストプリンシパルの検証として定義されています

```yaml

apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
  name: istio-ingressgateway
  namespace: istio-system
spec:
  selector:
    matchLabels:
      app: istio-ingressgateway
  jwtRules:
  - issuer: $OIDC_ISSUER_URL
    jwksUri: $OIDC_JWKS_URI
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: istio-ingressgateway
  namespace: istio-system
spec:
  selector:
    matchLabels:
      app: istio-ingressgateway
  rules:
  - from:
    - source:
        requestPrincipals: ["*"]
```

### デプロイ

ブラウザから[https://istio-oicd-auth.127.0.0.1.nip.io](https://istio-oicd-auth.127.0.0.1.nip.io)にアクセスするとプロバイダの認証画面に遷移するようになります。ブログでも言及されているようにEnvoyFilterはingressgatewayではなくsidecarを選択することも可能で認証を必要としないアプリケーションとの共存ももちろん可能です。

## opa-envoy-pluginを利用したサイドカーパターン

次の例として、Envoy＆OPAポリシーでexternal-authorizationを実装するパターンを検証してみます。
ネットワークホップなしにpolicyを評価できるのが魅力的です。
[リポジトリ内](https://github.com/open-policy-agent/opa-envoy-plugin/tree/master/examples/istio)にistioでのquick startが用意されているのでこれをベースにして進めます

進めていくとadmissionwebhookが用意されinjectionを指定したnamespace内のpodに対してsidecarがデプロイされます。https://github.com/open-policy-agent/opa-envoy-plugin/blob/master/examples/istio/quick_start.yaml#L313
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10683/e4ae8e33-01fa-bf30-59b2-f8d705b34cad.png)

configmapに定義したpolicyに基づいて認可を行います
https://github.com/open-policy-agent/opa-envoy-plugin/blob/master/examples/istio/quick_start.yaml#L393
aliceはguestとして定義されているので/api/v1/productsにaliceとしてリクエストをすると403を返却します


istioが採用しているEnvoyのExternalAuthorizationを使いsidecarとしてデプロイされたOPAがアクセス制御することでアプリケーションから認可を分離できました


```bash
❯ curl --user alice:password -i http://127.0.0.1/api/produtspage
HTTP/1.1 200 OK
content-type: text/html; charset=utf-8
content-length: 3769
server: istio-envoy
date: Sun, 20 Dec 2020 05:18:54 GMT
x-envoy-upstream-service-time: 35

❯ curl --user alice:password -i http://127.0.0.1/api/v1/products
HTTP/1.1 403 Forbidden
date: Sun, 20 Dec 2020 05:26:12 GMT
server: istio-envoy
content-length: 0
x-envoy-upstream-service-time: 5
```

ここまででquick startは終わりですが、例えばOPAで用意されている[JWTの検証](https://www.openpolicyagent.org/docs/v0.16.2/policy-reference/#token-verification)を使いリクエストpathの許可制御を行うことが出来ます。

```rego:opa-policy.yaml

    valid_id_token = true {
      [id_valid, id_header, id_payload] := io.jwt.decode_verify(input.user, {"secret": "secret"})
      id_valid == true
    } 

    default allow = {
      "allowed": false, 
      "reason": "deny"
    }

    allow = true {
      input.attributes.request.http.path = "/unprotected"
    }
    else = true {
      input.attributes.request.http.path = "/protected"
      valid_id_token
    }
```

## 最後に

istioを軸にして認証プロセスをアプリケーションコードから分離することが出来ました。
個人的にこれまでトラフィック制御としてistioを利用することが多かったですがこういったアクセス制御の面も充実していて拡張性が高いソフトウェアだと再認識できました。また1.5のリリースで発表されたようにistioの拡張性はenvoyに統合されwasmサポートとともにさらなる進化を迎えそうな予感がしますね。これからが非常に楽しみです。