keycloak + oauth2-proxy でWebアプリのSSO + ロール認証実装してみた

# この記事は
- keycloakのレルムロールを用いて，oauth2-proxyをリバプロとして認証を行う方法
- keycloakのIdPとしてGoogleのログインを行い，SSOを実現する方法
- これらの仕組みをdocker-composeで実現する方法

についてまとめる．

# あらすじ
[前回の記事](https://qiita.com/syou551/items/e617f068d06f2ffbc4cd)および，自作のWebアプリをdocker-composeで運用する[記事](https://qiita.com/syou551/items/6b7d252cf7cd6ca61ed4)に引き続いて，今回はこれらのアプリと認証基盤を実際に運用するための構築を行なっていく．

今回は，Webアプリにアクセスできるのは，Admin（筆者）が認めた人物のみに絞りたいため，keycloakのレルムロールを用いて，特定のロールが付与されているかをoauth2-proxyで検証し，ロールが付与されていれば，Webアプリにアクセスできるという制御ができるように，いろいろ設定していく．ちなみに，アクセスが認められない人はちゃんと403を受け取れるようにもする．

# docker-composeの構築
### Webアプリ側
まず，Webアプリ側のdocker-compose.yamlの内容を以下に示す．[Webアプリ側の構築の記事](https://qiita.com/syou551/items/6b7d252cf7cd6ca61ed4)で構築した分に加えて，今回は認証を行うためにoauth2-proxyを導入する．

ただし，今回oauth2-proxyは，nginxよりも前に位置するリバプロとして動作させる．そして，特定ロールを持つユーザーのみにアップストリームのWebアプリへのアクセスを許可するように設定する．

```yaml:docker-compose.yaml
services:
  front:
    build:
      context: ./front
      dockerfile: Dockerfile
    working_dir: /node/FileUploader/front/dir-share
    command: npm run start
  back:
    build:
      context: ./back
      dockerfile: Dockerfile
    working_dir: /go/src/FileUploader/back
    volumes:
      - ./.tmp:/tmp/share
    command: bash -c "./main"
  nginx:
    build:
      context: ./nginx
      dockerfile: Dockerfile
    depends_on:
      - front
      - back
  oauth2-proxy:
    image: quay.io/oauth2-proxy/oauth2-proxy:v7.7.1
    command:
      - --provider=keycloak-oidc
      - --oidc-issuer-url=http://auth.syou551.dev/realms/auth
      - --client-id=fileUploader
      - --client-secret=${CLIENT_SECRET}
      - --cookie-secret=${COOKIE_SECRET}
      - --redirect-url=https://fileup.syou551.dev/oauth2/callback
      - --upstream=http://nginx:80
      - --email-domain=*
      - --http-address=0.0.0.0:4180
      - --errors-to-info-log
      - --cookie-secure=false
      - --reverse-proxy=true
      - --allowed-role=${ALLOWED}
      - --whitelist-domain=auth.syou551.dev
    ports:
      - "8080:4180"
    depends_on:
      - nginx
```

oauth2-proxyは，keycloakのOIDCをサポートしているため，commandで`--provider=keycloak-oidc`を指定すれば良い．そのほかは，適宜OIDCの設定項目を指定していく．また，今回はリバプロとして動作させる必要があるので，`--reverse-proxy=true`を指定するのを忘れないようにする．

そして，肝心の特定ロールのみ通過させるには`--allowed-role=<許可するロール>`を指定する．

`--upstream`に関しては，同じdocker-compose内で動作させるため，サービス名で名前解決ができる．一方で，`--oidc-issure-url`や`--redirect-url`に関しては，クライアントが実際にリクエストするために使う値であるため，ドメインなどを指定するようにする．

### keycloak側
これらのファイルに関しては，[前回の記事](https://qiita.com/syou551/items/e617f068d06f2ffbc4cd)で説明しているため，詳細は省略する．起動時に自動でSSLなしで外部からアクセスできるようにするため，元のENTRYPOINTとは別のshファイルを指定している．

```Dockerfile:keycloak/Dockerfile
FROM quay.io/keycloak/keycloak:latest

WORKDIR /opt/keycloak/bin
COPY ./setup.sh .

ENTRYPOINT ["/opt/keycloak/bin/setup.sh"]
```

```yaml:docker-compose-yaml
version: "3"
services:
  keycloak:
    build: 
      context: ./keycloak
      dockerfile: Dockerfile
    tty: true
    stdin_open: true
    environment:
      DB_VENDOR: POSTGRES
      DB_ADDR: postgres
      DB_DATABASE: ${POSTGRES_DB_NAME}
      DB_USER: ${POSTGRES_USER}
      DB_PASSWORD: ${POSTGRES_PASSWORD}
      KEYCLOAK_ADMIN: ${KC_ADMIN_NAME}
      KEYCLOAK_ADMIN_PASSWORD: ${KC_ADMIN_PASSWORD}
      TZ: "Asia/Tokyo"
    ports:
      - "8010:8080"
    depends_on:
      - postgres
    volumes:
      - ./keycloak/.data:/opt/keycloak/data

  postgres:
    image: postgres:latest
    environment:
      POSTGRES_DB: ${POSTGRES_DB_NAME}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    ports:
      - "5432:5432"
    volumes:
      - ./postgres/.data:/var/lib/postgresql/data
```

これら2つのdocker-composeをそれぞれ起動させ，それぞれに対して外部からアクセスできるようにする．今回は，cloudflare tuunelを用いて公開した．（docker-compose.yamlに記されているURLはこれである)

# Keycloakの設定
次に，KeycloakのWebUIを用いて設定を行っていく．

まず，公開したkeycloakサービスのURLにアクセスすると，masterレルムのログイン画面にリダイレクトされるはずなので，そこにyamlで指定したadminのユーザーネームとパスワードを入力し，ログインする．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3892865/985efc57-ca7e-429f-ba8b-0fd6361c24dc.png)

### レルムの追加
次に，認証用に新たなレルムを作成する．左上のコンボボックスをクリックし，出てくるCreate realmを押すと作成できる．（今回は新たなレルムであるauthを作成済み）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3892865/2012accd-d8de-49e5-bb2f-cee9a74cf140.png)

### Clientの作成
そして，新たに作成したレルムのClientsページ（上記の写真のページ）で，Create Clientをクリックし，Webアプリ用のクライアントを作成する．

まず，表示される最初のページでは，今回はOIDCを用いるので，OpenID Connectを選択し，Client IDに任意の値を設定する．このIDが，oauth2-proxyの`--client-id`に設定する値となる．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3892865/9b4e3acd-9293-4ede-9e76-b07715573600.png)

次に，認証の詳細設定を行う．ここでは，初期設定では，Client authenticationがオフになっているので，オンにし，Authorizationもオンにする．そして，チェックを入れる項目は，選択固定されているものと，Standard flowのみで良い．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3892865/ef3474cb-9d28-4c1b-b262-19cd7def6f2b.png)

最後に，認可後にoauth2-proxyの方へリダイレクトするために，Valid redirect URIsを設定する．ここには，クライアント側のドメインまでを明示的に指定すればよく，パスに関してはワイルドカードを使用できる．（筆者の場合は，[https://fileup.syou551.dev/*]を設定している．）

なお，この設定は後でもできるため，URIが確定した時点で登録すれば良い，また，TailscaleなどのIPも入力することが可能だった．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3892865/7a5aa9a3-5f34-433d-8dd0-7b40d0f7727a.png)

#### Audienceの設定
次に，新たに作成されたClientにAudienceを追加する．これは，デフォルトでは設定されていないが，oauth2-proxyで認証を行うには必須の設定である．

参照: 

https://qiita.com/uturned0/items/44e6e7c77ed3347fce69 


Client Scopesの`<clientid>-dedicated`をクリックし，Add Mapperをクリックする．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3892865/0199c1d5-f894-48a0-9807-b3217b6da55b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3892865/8a9daf40-3bd2-4527-8df5-30be306ff0c8.png)

そして，By Configurationを選択し，表示されるリストの中からAudienceを選択する．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3892865/03c444f4-c39d-4ad5-b418-6b36a8c4e21f.png)

Audienceの設定では，Included Custom Audienceを設定する必要があり，この値はClient IDを指定する．Nameは設定必須だが，任意の名前を指定できる．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3892865/88e2f65e-61d2-471a-be92-f48624695e72.png)

これらの設定が完了すれば，画面下部のSaveをクリックする．

### IdPの設定
次に，GoogleをIdPとして使用するための設定を行う．最初に，左のConfigure欄のIdentity　Providerの項目を選択する．初期状態ならば，様々なIdPの選択肢が表示されるが，その中からGoogleを選択する．すると，以下のような画面へ移動する．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3892865/05d33c4f-667d-481a-bf29-720af88cf2fe.png)

Googleのconsoleでの設定は，詳しくは省略するが，事前にOAuthのクライアントIDを取得しておく．（以下のような画面で，クライアントが作成できていればOK）

そこで，クライアントIDとシークレットを控え，それをkeycloakのClient IDとClient Secretに入力する．Aliasがgoogleとしておけば良いだろう．また，Google側には，承認済みのリダイレクト URIとして，keycloakのRedirectURIに表示されているURIを設定しておく．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3892865/9b2f6ca0-e734-402a-a51e-7ea101e691bb.png)

### レルムロールの追加
次に，認証に用いるレルムロールを追加する．これは簡単で，Manage欄のRealm rolesを選択し，そこれCreate roleをクリックすると，以下のような画面へ飛ぶ．あとは，任意のロールの名前を設定すれば良い．ただし，oauth2-proxyのdocker-composeで指定したロールと同じロール名にすることを忘れないように．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3892865/6b188183-00a3-4cb9-a400-6c31ac245797.png)

### oauth2-proxyへの設定
最後に，oauth2-proxyにClientとしてのデータを設定するための処理をする．先ほど作成した新たなClientを選択し，その中のCredentialsを選択する．そして，Client AuthenticatorとしてClient Id and Secretを選択すると，Client Secretが取得できるようになる．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3892865/ad3f900f-505b-4114-b127-cc566e0900e5.png)

ここで控えたSecretをdocker-compose.yamlの環境変数に設定するために，`.env`などを用意して，そこに書き込んでおく．

```sh:.env
CLIENT_SECRET=<先ほど入手したkeycloakのやつ>
```

以上で，認証を行うための最低限の設定が完了した．

# 実際にユーザーにロールを割り当てるまで
これで，あとは特定のユーザーにロールを割り当てれば認証できるようになる．

まずは，実際にGoogleアカウントで認可を行い，keycloakにユーザーを作成する．新たに追加されたユーザーに対して，先ほど作成したレルムロールをAssignする．

:::note warn
ここでロールを追加するのは，自分のGoogleアカウントのメールアドレスを持ったユーザー，もしくは自分が確実にアクセスを許可したいユーザーである場合だけにすること
:::

また，oauth2-proxyでは，EmailがVerifyされてないとInternal Server Errorとなってしまうので，手動でVerifyしておく．（確認メールを送信することもできるが，今回は設定していないので手動でやってしまう）

これで，特定ロールを持たないユーザーには403が返されるようになる．

# まとめ
これで，無事にoauth2-proxyとkeycloakを用いて独自のロールを用いたSSOの仕組みを整えることができました！

Googleだけでなく，Github等のIdPも使用することができるみたい．便利〜

https://qiita.com/suin/items/cb60e903eb1ddb15719b


# 参考
- https://qiita.com/uturned0/items/44e6e7c77ed3347fce69
- https://qiita.com/suin/items/108f0b9ee6350226e011
- https://qiita.com/masuo3/items/e02d58c6cf4897a9dd6e