🔎

dexidp/dex と AuthProxy Connector を試す

2022/04/27に公開

TL;DR

  • Dex の AuthProxy connector を使うと、basic 認証を用いて ID token を発行することができる
  • AuthProxy を使うには nginx や apache などのリバースプロキシが必要
    • /dex/callback/:id に来たリクエストについて検証して、正しいなら X-Remote-Userヘッダを追加する

はじめに

dexidp/dex の connector を見ていたら、AuthProxy を使うことで Basic認証に通ったユーザに ID token を発行することができそうだと思いました。
ドキュメントには具体的なフローが書いておらず、手元で試すまで時間がかかったので再現するための詳しい手順を書いておきます。

前半は Getting Started の説明になっているので、AuthProxy の話が見たい人は後半から読んでください。

dex の使い方

dexidp.io の Getting Started をもとに説明します。

準備

$ git clone https://github.com/dexidp/dex.git
$ cd dex/
$ make build # dex 本体がビルドされ ./bin/dex に配置されます。

また、検証用に用意されているアプリケーションもビルドしておきます。

$ make examples # example-app がビルドされ ./bin/example-app に配置されます

検証用の example-app を試すためには、用意されている config ファイルを dex に与えて起動する必要があります。

$ ./bin/dex serve examples/config-dev.yaml

また、別のコンソールで example-app を起動しましょう。

$ ./bin/example-app

トークン取得

次の手順で dex を用いて ID トークンを取得する流れを体験することができます。

  1. http://localhost:5555 で待ち受けている example-app にアクセスする
  2. login を押すと、 dex にリダイレクトされる
  3. dex の画面で認証方法が選べる
    • Log in with Email
      • admin@example.com, password をそれぞれ入力する
        • examples/config-dev.yaml に認証情報が書かれている
    • Log in With Example
      • 内部でモックされたユーザーデータを利用する
  4. Grant Access をクリックして、アクセスの承認を行う
  5. example-app に取得した ID トークンが表示される

次に実際のアクセスをベースにしたフローを示します。

AuthProxy の設定の仕方

今回は、認証方法に AuthProxy を用いて Basic 認証を追加してみます。
AuthProxy のドキュメント
先にフローを示すと次のようになります。

ここで、AuthProxy を使うためには X-Remote-User ヘッダの指定が必要であることがわかります。
そのため、/dex/callback/basicauth にきたリクエストに対して basic認証の検証を行い、その結果によって X-Remote-User ヘッダをセットするプロキシが必要になります。

プロキシの設定

ドキュメントには Apache2 の例が示されているのですが、今回は Nginx で構成してみます。

nginx をリバースプロキシとして利用するために必要な設定を以下に示します。

$ htpasswd -c -b htpasswd test password
Adding password for user test

$ ls
nginx.conf htpasswd

$ cat nginx.conf
user nginx;
    worker_processes  3;
    error_log  /var/log/nginx/error.log;
    events {
      worker_connections  10240;
    }

    http {
      access_log    /dev/stdout;
      error_log     /dev/stderr  warn;
      
      server {
        listen 80;
        location /dex/callback/myBasicAuth {
          auth_basic "basic auth";
          auth_basic_user_file /etc/nginx/htpasswd;
          set $user "foo@example.com";

          proxy_set_header X-Forwarded-Host $host:$server_port;
          proxy_set_header X-Forwarded-Server $host;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Remote-User $user;
          proxy_pass "http://$HOST_IP:5556/dex/callback/myBasicAuth";
        }

        location /dex/ {
          proxy_set_header X-Forwarded-Host $host:$server_port;
          proxy_set_header X-Forwarded-Server $host;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_pass "http://$HOST_IP:5556/dex/";
        }
      }
    }

docker を使って起動します。

$ docker run -it --rm --name nginx -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf -v $(pwd)/htpasswd:/etc/nginx/htpasswd -p 8080:80 nginx

dex の config も変更する必要があります。
変更点は二箇所です。

  • issuer を localhost:8080/dex に変更
  • connectors に AuthProxy を追加
    • id は何でも良いのですが、nginx で指定した /dex/callback/:id と合わせる必要があります。
$ git diff examples/config-dev.yaml
diff --git a/examples/config-dev.yaml b/examples/config-dev.yaml
index 6cae823c..5441c587 100644
--- a/examples/config-dev.yaml
+++ b/examples/config-dev.yaml
@@ -4,8 +4,9 @@
 # The base path of dex and the external name of the OpenID Connect service.
 # This is the canonical URL that all clients MUST use to refer to dex. If a
 # path is provided, dex's HTTP service will listen at a non-root URL.
-issuer: http://127.0.0.1:5556/dex
+#issuer: http://127.0.0.1:5556/dex
+issuer: http://localhost:8080/dex
 # The storage configuration determines where dex stores its state. Supported
 # options include SQL flavors and Kubernetes third party resources.
 #
@@ -120,6 +121,9 @@ connectors:
 - type: mockCallback
   id: mock
   name: Example
+- type: authproxy
+  id: myBasicAuth
+  name: myBasicAuth
 # - type: google
 #   id: google

Basic認証を通じてトークンを取得してみる

設定が終わったら、 dex と nginx をそれぞれ別のコンソールで起動します。

$ docker run -it --rm --name nginx -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf -v $(pwd)/htpasswd:/etc/nginx/htpasswd -p 8080:80 nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
$ ./bin/dex serve examples/config-dev.yaml
time="2022-04-27T03:12:07Z" level=info msg="Dex Version: fd15dd2248900a16849af9513d9a70c0a0e5103f-dirty, Go Version: go1.17.7, Go OS/ARCH: darwin arm64"
time="2022-04-27T03:12:07Z" level=info msg="config issuer: http://localhost:8080/dex"
time="2022-04-27T03:12:07Z" level=info msg="config storage: sqlite3"
time="2022-04-27T03:12:07Z" level=info msg="config static client: Example App"
time="2022-04-27T03:12:07Z" level=info msg="config connector: mock"
time="2022-04-27T03:12:07Z" level=info msg="config connector: myBasicAuth"
time="2022-04-27T03:12:07Z" level=info msg="config connector: local passwords enabled"
time="2022-04-27T03:12:07Z" level=info msg="config refresh tokens rotation enabled: true"
time="2022-04-27T03:12:07Z" level=info msg="listening (telemetry) on 0.0.0.0:5558"
time="2022-04-27T03:12:07Z" level=info msg="listening (http) on 0.0.0.0:5556"

また、example-app も issuer をオプションで指定して起動します。

$ ./bin/example-app --issuer http://localhost:8080/dex
2022/04/27 12:26:00 listening on http://127.0.0.1:5555

この状態で localhost:5555 にアクセスし、Login -> Log in with myBasicAuth をクリックすると basic 認証が要求されます。


test:password を入力して、認証画面を進み、ID token が表示されたら成功です 🎉

備考

dex の設定で skipApprovalScreen というものがあります。
skipApprovalScreen: true にすることで、先のフローにあった承認画面をスキップでき、認証コードをそのまま取得できます。

この方法を使うと、curl などのCLIアクセスでも 認証コードを取得することができるようになります。
connector のおかげで、ブラウザからはSSO、CLIアクセスは basic auth を使ってそれぞれ id token を取得する、といった構成も考えられるかと思います。

Discussion