minikubeでoauth2-proxyとdexを動かして認証マイクロサービスを体験する

# はじめに

認証マイクロサービスというのを実際に動かしてみて、その雰囲気を試してみました。

以前にもbuzzfeed/ssoをつかった内容の記事を書きましたが、今回は別のツールを使ってやってみます。

https://qiita.com/inajob/items/d26d242d741fafceac16

※この記事はZ Labの業務の一環として作成しました

# oauth2-proxyとは

https://github.com/oauth2-proxy/oauth2-proxy

https://oauth2-proxy.github.io/oauth2-proxy/

oauth2-proxyは汎用的な認証プロキシです。OAuth2に準拠した外部の認可システムを利用して認証を行い、認証が成功した場合にのみバックエンドに通信を許すという動作をします。

以前はbitlyが開発していたのですが、メンテされなくなり、その後紆余曲折あってoauth2-proxyという独立したorgのもとで現在も開発が進められているようです。

# dexとは

https://github.com/dexidp/dex

https://dexidp.io/

さまざまな認証の仕組みとやりとりをして、OpenID Connectとして扱うことができる認証サービスです。
こちらも元々CoreOSが開発していましたが、今はdexidpという独立したorgで開発が行われています。

# minikubeでクラスタの準備を行う

この実験はMac OS上で実施しているのですが、minikubeでクラスタを作る際には、もろもろもの事情からhyperkitを使用するモードを利用します。（`minikube tunnel`を使った際にClusterIPがLBのIPと同じものになる動作が今回は必要なのです）

```
$ minikube start --driver=hyperkit
```

ingress addonを利用します

```
$ minikube addons enable ingress
```

ターミナルをもう一つ開いてminikube tunnelを実行しておきます

```
$ minikube tunnel
```

# oauth2-proxyのインストール

インストールはhelmを使って実施します。

```
$ helm repo add oauth2-proxy https://oauth2-proxy.github.io/manifests
$ helm install oauth2-proxy oauth2-proxy/oauth2-proxy
```

この後、設定値を修正していきます。
そのために、現在の設定値を一度yamlに書き出します。

```
$ helm show values  oauth2-proxy/oauth2-proxy > values.yaml
```

今後修正を反映するためにはvalues.yamlを編集したのちに以下を実行します。

```
$ helm upgrade oauth2-proxy oauth2-proxy/oauth2-proxy -f values.yaml
```

# http-dumpのインストール

バックエンドとしてはなんでもよかったのですが、リクエストの中身がみられるhttp-dumpを利用します。　

https://github.com/daime/http-dump

```
$ kubectl create deployment http-dump --image daime/http-dump
$ kubectl expose deploy http-dump --port 8080 --type=LoadBalancer
```

# oauth2-proxy を使ってhttp-dumpに認証をかける

認証プロバイダとしてはGitHubを使います。
GitHubのWebUIでApplicationを作成し、ClientIDやClientSecretを取得しておきます。
(この記事などを参考にしてみると良いです https://yurakawa.hatenablog.jp/entry/2018/06/04/002033 )

Authorization Callback URLとしては`https://[minikubeのIP]/oauth2/callback`を指定します。

これはIngress経由でoauth2-proxyにアクセスするためのURLです。

oauth2_proxyの設定としては

- clientID, clientSecret
- upstreamを `http://http-dump:8080`
  - これはKubernetesのクラスタ内DNSを利用したURLです
- redirect-urlをGitHubに登録したものと同じ`https://[minikubeのIP]/oauth2/callback`
- ingressを有効にしhostを`""`に設定（Helm Chartの都合上この設定をしないとおかしなIngressリソースが作成されました）

<details><summary>diff（環境固有の値があるので参考程度のものです）</summary><div>

```
11c11
<   clientID: "XXXXXXX"
---
>   clientID: "ないしょ"
13c13
<   clientSecret: "XXXXXXXX"
---
>   clientSecret: "ないしょ"
32c32
<     upstreams = [ "file:///dev/null" ]
---
>     upstreams = [ "http://http-dump:8080" ]
65c65,67
< extraArgs: {}
---
> extraArgs:
>   provider: github
>   redirect-url: https://192.168.64.9/oauth2/callback
117c119
<   enabled: false
---
>   enabled: true
123c125,126
<   # hosts:
---
>   hosts:
>      - ""
```
</div></details>

ここまで設定すると、ブラウザから`https://[minikubeのip]/`にアクセスすることで、GitHubの認証を許可した場合のみhttp-dumpにアクセスできるような構成が出来上がりました。
（今回はhttp-dumpにtype=LoadBalancerも登録しているので、そちらからアクセスすれば認証を迂回して直接アクセスすることもできます）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100375/4d28a218-a63c-afd5-01a7-febd4a823247.png)

http-dumpではリクエストヘッダなども表示されます。X-Forwared-EmailなどをみるとGitHubの認証で用いられたメールアドレスなども知ることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100375/3ac63c9d-0e8b-c56d-0ca9-ff5587853d7f.png)

現段階での動作を図にするとこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100375/85f06b62-7708-6eb6-af25-d9a1dba6c74c.png)


# oauth2-proxyをプロキシとして使うのをやめて Nginxのauth_requestを使う

Nginxには認証されていない場合は認証サービスにアクセスするというauth_requestの機能が備わっており、 Nginx Ingress Controllerでは、Ingressリソースの Annotationに少し設定を書くことで、この機能を利用することができます。

```
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: http-dump
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/auth-url: "https://$host/oauth2/auth"
    nginx.ingress.kubernetes.io/auth-signin: "https://$host/oauth2/start?rd=$escaped_request_uri"
spec:
  rules:
  - http:
      paths:
      - path: /test
        pathType: Prefix
        backend:
          service:
            name: http-dump
            port:
              number: 8080

```

このようにIngressリソースに少し設定を加えることで、認証していないとアクセスができない設定ができます。
この設定を反映した上で、ブラウザから`https://[minikubeのip]/test`にアクセスすると、認証を経由した上でhttp-dumpにアクセスできました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100375/df001e6e-c43a-61cf-1697-eef54928bdb4.png)

ここまでの動作を図で書くとこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100375/8d486766-44cb-f97e-ce20-e68d3e390b0d.png)


# dexのインストール

ここまでで、oauth2_proxyを使った認証プロキシの動作を確認できました。

次はさらに認証にdexを使うようにしてみましょう。

dexのリポジトリのexampleにあるマニフェストを少し編集してminikubeで作ったKubernetesクラスタにデプロイします。

変更点は以下です

- replicaを3から1に
- issuerを`https://[dexのExternal IP]:5556`
  - （ここで指定するIPがクラスタ内でもラップトップからも同じであることが求められます、minikubeをhyperkitで動かしているのはこのためです）
- redirectURIを`https://[dexのExternal IP]:5556/callback`にする
- staticClientsに以下を追加

```
+    - id: oauth2-proxy
+      redirectURIs:
+      - 'https://[minikubeのIP == Ingress Controller == oauth2_proxy]/oauth2/callback'
+      name: 'oauth2-proxy'
+      secret: proxy
```

- Service typeをLoadBalancerに変更

<details><summary>diff（環境固有の値があるので参考程度のものです）</summary><div>

```
@@ -12,7 +12,7 @@ metadata:
   name: dex
   namespace: dex
 spec:
-  replicas: 3
+  replicas: 1
   selector:
     matchLabels:
       app: dex
@@ -72,7 +72,7 @@ metadata:
   namespace: dex
 data:
   config.yaml: |
-    issuer: https://dex.example.com:32000
+    issuer: https://10.97.15.76:5556
     storage:
       type: kubernetes
       config:
@@ -88,12 +88,18 @@ data:
       config:
         clientID: $GITHUB_CLIENT_ID
         clientSecret: $GITHUB_CLIENT_SECRET
-        redirectURI: https://dex.example.com:32000/callback
+        redirectURI: https://10.97.15.76:5556/callback
         org: kubernetes
     oauth2:
       skipApprovalScreen: true

     staticClients:
+    - id: oauth2-proxy
+      redirectURIs:
+      - 'https://192.168.64.9/oauth2/callback'
+      name: 'oauth2-proxy'
+      secret: proxy
+
     - id: example-app
       redirectURIs:
       - 'http://127.0.0.1:5555/callback'
@@ -114,7 +120,7 @@ metadata:
   name: dex
   namespace: dex
 spec:
-  type: NodePort
+  type: LoadBalancer
   ports:
   - name: dex
     port: 5556
```
</div></details>


dexの起動に必要なsecretなどを用意します。

```
$ cd dex/examples/k8s
$ ./gencert.sh
$ kubectl -n dex create secret tls dex.example.com.tls --cert=ssl/cert.pem --key=ssl/key.pem
$ kubectl -n dex create secret \
    generic github-client \
    --from-literal=client-id=$GITHUB_CLIENT_ID \
    --from-literal=client-secret=$GITHUB_CLIENT_SECRET
```

# dexとoauth2-proxyの連携

oauth2-proxyの設定で認証プロバイダとしてdexを利用するようにします。

- provider: oidc
- client-id: oauth2-proxy # staticClientsに設定したもの
- client-secret: proxy
- redirect-url: https://[minikubeのIP]/oauth2/callback # dexを使わない時と同じ
- set-authorization-header: true # せっかくOIDCなので設定する
- oidc-issuer-url: https://[dexのExternal IP]:5556
- ssl-insecure-skip-verify: true # dexのTLS証明書が検証できないのでskip

<details><summary>diff（環境固有の値があるので参考程度のものです）</summary><div>

```
11c11
<   clientID: "XXXXXXX"
---
>   clientID: "oauth2-proxy"
13c13
<   clientSecret: "XXXXXXXX"
---
>   clientSecret: "proxy"
32c32
<     upstreams = [ "file:///dev/null" ]
---
>     upstreams = [ "http://http-dump:8080" ]
65c65,70
< extraArgs: {}
---
> extraArgs:
>   provider: oidc
>   redirect-url: https://192.168.64.9/oauth2/callback
>   oidc-issuer-url: https://10.97.15.76:5556
>   set-authorization-header: true
>   ssl-insecure-skip-verify: true
117c122
<   enabled: false
---
>   enabled: true
123c128,129
<   # hosts:
---
>   hosts:
>      - ""
```
</div></details>


ここまで設定すると、認証のためにdexにアクセスし、さらにそこからGitHubにアクセスし、認証が完了したらdex、そしてoauth2_proxyまで戻ってきて、最終的にhttp-dumpにアクセスできるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100375/185240f3-f2fc-d3e8-f609-21b6dac4dbb9.png)

ingressに追加で`nginx.ingress.kubernetes.io/auth-response-headers: Authorization`というアノテーションを追加することでバックエンドにAuthorizationヘッダーを送ることも可能です。

この状態を図で表すとこのようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100375/0bc805ed-69d8-3daf-4787-a5616a8bb520.png)


# まとめ

 oauth2-proxyとdexを使って、Kubernetes上で簡単に認証つきのWebサービスを作成する方法を検証してみました。

# 類似ソフトウェア

社内でこの内容を紹介した際に、いくつか類似のソフトウェアを教わったのでここに書いておきます。

https://github.com/typester/gate

https://www.pomerium.com/

https://github.com/ory/oathkeeper

https://github.com/f110/heimdallr
  - [リモートワークでも便利な認証付きプロキシを作っている](https://zenn.dev/f110/articles/f250328d18e6c4)