VPSのDocker上のアプリにアクセス制限をかける

# はじめに
- 本記事は、[Qiitaエンジニアフェスタ2021](https://qiita.com/official-campaigns/engineer-festa/2021)の[「Auth0を使ってID管理にまつわる記事を投稿しよう！」](https://qiita.com/official-events/5fcd3867b233a9228fd0)への投稿記事です。
- 自分の調査や開発に利用しているサーバ（[さくらのVPS](https://vps.sakura.ad.jp/)）上で、Dockerコンテナとして動作しているWebアプリや開発ツールをインターネットを通して利用するにあたり、アクセス制限をしています。
- VPSの[パケットフィルタ機能](https://vps.sakura.ad.jp/feature/packetfilter.html)で、ポートや接続元IPを絞っていますが、さらに強化するために認証・認可のためのプラットフォームである[Auth0](https://auth0.com/jp)を使いました。

![login0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/517f3c92-964a-c937-1e38-ae662c0ab745.png)


# 構成
![architecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/725c4944-0b15-3bc4-2251-bf77ca024971.png)

- CentOS 7マシンにDocker環境を構築しています。Docker上のアプリにアクセスするために、Dockerホスト上にインストールしたApacheをリバースプロキシとして使っています。
- リバースプロキシとなるApacheには、外部からは、`8443`ポートのHTTPS通信で接続します。VPSの[パケットフィルタ機能](https://vps.sakura.ad.jp/feature/packetfilter.html)で、`8443`ポートを許可しています。
- Apacheに対して、Auth0の設定を施し、アクセス制限をかけます。
- Docker上の各アプリには、Apacheの名前ベースのバーチャルホスト機能を利用し、各アプリに割り当てられたドメインのURLでアクセスします。
TLS証明書は、以下の記事を参考にさせていただき、Let's Encryptでワイルドカード証明書を取得しています。

https://qiita.com/F_clef/items/136d81223c030904523c

### 環境
- さくらのVPS
- CentOS 7.9
- Apache 2.4

# Auth0準備
まずは、Auth0に登録し、利用のための設定をします。
無料で登録できるとのことなので、下記から登録していきます。
私の場合は、Googleアカウントで登録していきました。

https://auth0.com/signup

## アカウント・テナント作成
上のURLにアクセスしていくと`Account Type`を聞かれたので、「Personal」を選択しました。
また、リージョンが選択できるようなので、「I need advanced settings」にチェックを入れて進みました。
![account0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/5db44c83-fe84-3e2d-674a-84498d41bb6c.png)

次の画面の`Tenant Domain`は、デフォルトで設定されるもののままとし、`Regin`は、「Japan」を選択しました。
![account1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/c296c04c-50be-1fbe-8a1d-6824f72899ad.png)



## アプリケーション作成・設定
アカウント作成後に表示されるダッシュボード画面で、「Create Application」を選択します。
![new-app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/482c2d66-c49a-807c-554d-ef57e2622ab3.png)

表示されるダイアログで、適当なアプリケーションの名前を入力し、`application type`は、「Regular Web Application」を選択します。
![new-app1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/90f05c65-303a-a1a1-6de8-fcd8712db88e.png)

これで、アプリケーションが作成されました。
![top.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/67b74107-67bc-f232-0145-de343fea2d6f.png)

続いて、設定をしていきます。
「Settings」タブ ＞「Application URIs」 ＞ 「Allowed Callback URLs」にApacheのバーチャルホストに設定する2つのドメイン分のURLをカンマ区切り入力し、下の方の「Save Changes」を押します。
（この値は、後のApacheの設定ファイルに記述する`OIDCRedirectURI`の値とあわせます）

- `https://www-a.example.com:8443/redirect_uri/`
- `https://www-b.example.com:8443/redirect_uri/`

![settings1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/5fb69eba-b959-3d8b-37e7-9db8e49710be.png)

今回は、自分で利用するだけでGoogleアカウントと連携しての認証は行わないので、ユーザ名/パスワード認証のみにします。
「Connections」タブで、「google-oauth2」を無効にしておきます。
![settings2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/2695cdda-3d79-f36f-f81e-5ee8c8b777bc.png)

さらにクライアント側からのユーザの新規登録ができないように、サイドメニューの「Authentication」＞「Database」>「Disable Sign Ups」をオンにします。
![settings3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/0cb85b54-fd67-045f-b711-49486d2a5194.png)


## ユーザ作成
ログイン画面で入力するユーザIDとパスワードを作成します。
サイドメニューの「User Management」＞「Users」で「Create User」をクリックします。
![user1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/6668a25d-4f59-b080-6727-2aa550981128.png)

表示されるダイアログの項目を埋めます。
![use2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/61fb2f65-7d53-609a-3bba-ca4bd71b2628.png)

「Create」すると、ユーザの一覧に作成したユーザが表示されました。
![user3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/93d6ff69-cb31-cf57-ad9a-c7903b282919.png)



# Apacheモジュール導入
次に必要なApacheのモジュールを導入します。
CentOS 7では、yumで`mod_auth_openidc`をインストールするだけでした。
Apacheの再起動は、次のバーチャルホストの設定等の反映とあわせて行います。

```console
# yum install -y mod_auth_openidc
```

# Apacheの設定
Apacheの設定ファイルを編集していきます。
auth0のための設定は、ダッシュボードの「Quick Start」タブで「Apache」を選択し表示される設定例をベースにします。
`Client Secret`は、「Settings」タブの項目にある値を使います。

![apache1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/b0f4878e-8278-c39c-2070-19de7590ff70.png)

バーチャルホストの設定は、以下の例のように設定します。

- アプリA
  - `www-a.example.com`のFQDNの`8443ポート`で受け付けたリクエストをローカルホストの`8500`ポートにプロキシ
- アプリB
  - `www-b.example.com`のFQDNの`8443ポート`で受け付けたリクエストをローカルホストの`8600`ポートにプロキシ

```apache:/etc/httpd/conf.d/vhosts.conf
Listen 8443 https
ProxyRequests Off

# アプリA
<VirtualHost *:8443>
    ServerName www-a.example.com

    # HTTPSのための設定
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
    SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem

    OIDCProviderMetadataURL https://dev-xxxxxxxx.jp.auth0.com/.well-known/openid-configuration
    OIDCClientID 9FXXXXXXXXXXXXXXXXXHK
    OIDCClientSecret ylxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxym

    OIDCScope "openid name email"
    OIDCRedirectURI https://www-a.example.com:8443/redirect_uri/
    OIDCCryptoPassphrase <passwordToEncryptTheSessionInformationOnTheCookie>

    <Location />
        AuthType openid-connect
        Require valid-user
        LogLevel debug
    </Location>

    # Dockerアプリへのプロキシ設定
    ProxyPass / http://localhost:8500/
    ProxyPassReverse / http://localhost:8500/
</VirtualHost>

# アプリB
<VirtualHost *:8443>
    ServerName www-b.example.com

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
    SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem

    OIDCProviderMetadataURL https://dev-xxxxxxxx.jp.auth0.com/.well-known/openid-configuration
    OIDCClientID 9FXXXXXXXXXXXXXXXXXHK
    OIDCClientSecret ylxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxym

    OIDCScope "openid name email"
    OIDCRedirectURI https://www-b.example.com:8443/redirect_uri/
    OIDCCryptoPassphrase <passwordToEncryptTheSessionInformationOnTheCookie>

    <Location />
        AuthType openid-connect
        Require valid-user
        LogLevel debug
    </Location>

    ProxyPass / http://localhost:8600/
    ProxyPassReverse / http://localhost:8600/
</VirtualHost>
```

ここまでの設定をApacheに反映するために、Apacheの再起動を行います。

```console
# httpd -t
  → 「Syntax OK」を確認
# systemctl restart httpd
```

# 実際にアクセス
最後に実際にアプリにアクセスしてみます。
アプリのURLにアクセスすると、以下のようなログイン画面が表示されました。
先ほど作成したユーザID/パスワードを入力すると、Docker上のアプリの画面が表示されます。

![app1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/872d5751-0c73-71ef-86aa-7c6253552f84.png)


無事にログインできました！
ここでは、開発用ツールとして、VSCodeをWebブラウザ上で操作できる[code-server](https://github.com/cdr/code-server)を動作させています。
![vscode.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/879a7ce7-a5dc-5f88-327a-c6f50773f1dd.png)


Docker上で動作させているアプリについては、別記事にて紹介しています。

https://qiita.com/mochi_yu2/items/860ea0abbc9101a90afa

# 多要素認証の導入
最後にパスワード+SMSを使った2要素認証を導入してみます。
以下のドキュメントにしたがって、2要素認証の設定を行いました。

https://auth0.com/docs/mfa

設定後、アプリにIDとパスワードを入力すると、以下のようなSMSを送信する電話番号の入力画面が出てきました。
![mfa0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/1f294fca-42b8-ba87-3416-8d31c65d5622.png)


送信先の番号を入力して、「続ける」をクリックすると、SMSが届きました。

![mfa1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/02181966-e861-6d9e-8153-fb838f490aa1.png)

届いた6桁のコードを入力することで、アプリにログインすることができました。

![mfa2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/7b761d1c-3107-fd87-cfad-abd9f3c11cdb.png)



# おわりに
本記事では、auth0をリバースプロキシのApacheに設定し、プロキシ先アプリに対するアクセス制限に利用しました。
比較的簡単な手順で、リッチなログイン画面を導入することができました。
自分ひとりだけで利用するには、Basic認証で十分かもしれませんが、複数人で利用するようなシチュエーションでは便利かなと思いました。
また、多要素認証も実現できるので、実際のサービス開発・運用にも有力なソリューションだなと思いました。

# 参考
本記事の作成にあたり、以下の記事を参考にさせていただきました。

https://auth0.com/docs/quickstart/webapp/apache

https://qiita.com/KWS_0901/items/e86563abfa40390b4df5

https://qiita.com/hisashiyamaguchi/items/2d0bcae052077f6ae4a4

https://dev.classmethod.jp/articles/auth0-apache-mod_auth_openidc/