docker-composeでletsencrypt＋SAML認証のリバースプロキシサーバーを構築する

## はじめに
前回redhat6のapache2.2にmod_auth_mellonをインストールして大変苦労しましたが、今回はdockerで簡単に構築出来たので手順を共有します。

## 環境
|項目|内容|
|---|---|
|SP|Apache mod_auth_mellon|
|IdP|Microsoft Azure Active Directory|
* シングルサインオンさせたいサーバーはリバースプロキシのバックエンドにいてサブドメインでアクセスするものとします。

## dockerの設定
* hogeディレクトリ以下に構築するものとします。

```shell:ディレクトリ構成
hoge
├── certs
├── dhparam
├── docker-compose.yml
├── html
├── proxy
│   └── log
├── saml
│   ├── proxy.conf.template
│   ├── saml_idp.xml
│   ├── saml_sp.cert
│   ├── saml_sp.key
│   └── saml_sp.xml
└── vhost
```

```yaml:docker-compose.yml
version: '3'
services:
    saml-proxy:
        image: "mpar/saml-proxy"
        environment:
            - BACKEND="http://www.yahoo.co.jp" #テスト用の定義
            - VIRTUAL_HOST=hoge.example.com
            - VIRTUAL_PORT=8080
            - LETSENCRYPT_HOST=hoge.example.com
            - LETSENCRYPT_EMAIL=hoge@example.com
        ports:
            - "8080:80"
        volumes:
            - ./saml/saml_idp.xml:/etc/httpd/conf.d/saml_idp.xml
            - ./saml/saml_sp.key:/etc/httpd/conf.d/saml_sp.key
            - ./saml/saml_sp.cert:/etc/httpd/conf.d/saml_sp.cert
            - ./saml/saml_sp.xml:/etc/httpd/conf.d/saml_sp.xml
            - ./saml/proxy.conf.template:/etc/httpd/conf.d/proxy.conf.template
    nginx-proxy:
        image: jwilder/nginx-proxy
        container_name: nginx-proxy
        ports:
            - "80:80"
            - "443:443"
        restart: unless-stopped
        volumes:
            - ./proxy/log:/var/log/nginx
            - ./html:/usr/share/nginx/html
            - ./dhparam:/etc/nginx/dhparam
            - ./vhost:/etc/nginx/vhost.d
            - ./certs:/etc/nginx/certs:ro
            - /var/run/docker.sock:/tmp/docker.sock:ro
        environment:
            TZ: Asia/Tokyo
        labels:
            - "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"

    letsencrypt:
        image: jrcs/letsencrypt-nginx-proxy-companion
        container_name: nginx-proxy-lets-encrypt
        depends_on:
            - "nginx-proxy"
        volumes:
            - ./certs:/etc/nginx/certs:rw
            - ./vhost:/etc/nginx/vhost.d
            - ./html:/usr/share/nginx/html
            - /var/run/docker.sock:/var/run/docker.sock:ro
```
(2022/01/29 追記)
リバースプロキシ先のコンテナにVIRTUAL_PORTの設定をすると、jwilder/nginx-proxyから接続出来ないケースがありました。
docker exec -it (コンテナ名) bash
でコンテナ内に入り、プロキシ先のコンテナからcurl等でコンテンツが取得出来るようなら、VIRTUAL_PORTの設定をコメントアウトしてみて下さい。

## mod_auth_mellonの設定
### mellon_create_metadata.shの取得
* hoge/saml ディレクトリを作成します。
* https://gist.github.com/michaelrice/3c052dce7b1e2e1bf46e224c4e9bb692 
から mellon_create_metadata.sh を取得して hoge/saml に保存します。

### mellon_create_metadata.shの実行
hoge/saml ディレクトリで以下を実行します。

```bash
$ chmod 755 mellon_create_metadata.sh
$ sudo ./mellon_create_metadata.sh \
  -k saml_sp.key \
  -c saml_sp.cert \
  -m saml_sp.xml \
  -e https://hoge.example.com/mellon/metadata \
  -b https://hoge.example.com/mellon
```
このコマンド実行後に出来た、
<pre>saml_sp.xml</pre>
をAzure ADの「メタデータファイルをアップロードする」でアップロードします。

アップロードして保存したら[フェデレーション メタデータ XML]をダウンロードして hoge/saml ディレクトリに
<pre>saml_idp.xml</pre>
というファイル名で保存します。

### proxy.conf.templateの編集
* hoge/saml ディレクトリに proxy.conf.template を追加し自分の環境に合わせて編集します。
* このサンプルは https://hoge.example.com/fuga へのアクセスをローカルのバックエンドサーバー（192.168.1.11）に送る例です。
* サブドメインごとバックエンドに送る場合はこのファイルは不要ですので docker-compose.yml で proxy.conf.template のボリュームをマウントしている行を削除して下さい。

```config:proxy.conf.template
ServerName $SCHEMA://$HOST

<Location />
    # Add mod_auth_mellon info to all contexts
    MellonEnable "info"

    # Auth redirects will be located under /$MELLON_PATH
    MellonEndpointPath /$MELLON_PATH

    # service provider metadata, cert, and key
    MellonSPPrivateKeyFile /etc/httpd/conf.d/saml_sp.key
    MellonSPCertFile       /etc/httpd/conf.d/saml_sp.cert
    MellonSPMetadataFile   /etc/httpd/conf.d/saml_sp.xml

    # idp metadata
    MellonIdPMetadataFile /etc/httpd/conf.d/saml_idp.xml
</Location>

<LocationMatch "/fuga/.*">
    # Protect with auth
    MellonEnable "auth"

    # Proxy to backend once authenticated
    #ProxyPass $BACKEND
    ProxyPass        http://192.168.1.11
    ProxyPassReverse http://192.168.1.11

    <If "-z env('REMOTE_USER_SAML_ATTRIBUTE')">
       # Set the Remote-User header to the value of the authenticated username
       RequestHeader set Remote-User %{MELLON_NAME_ID}e env=MELLON_NAME_ID
    </If>
    <Else>
       # Use a custom attribute as the remote username by setting $REMOTE_USER_SAML_ATTRIBUTE to a custom attribute name
       # Require a value in the attribute we're going to use:
       MellonCond ${REMOTE_USER_SAML_ATTRIBUTE} .+ [REG]
       # Map the long attribute name to a nice short one
       MellonSetEnv user ${REMOTE_USER_SAML_ATTRIBUTE}
       # Set the Remote-User header to the value of the mapped envvar:
       RequestHeader set Remote-User %{MELLON_user}e env=MELLON_user
    </Else>

    <If "-n env('REMOTE_USER_NAME_SAML_ATTRIBUTE')">
       MellonSetEnv name ${REMOTE_USER_NAME_SAML_ATTRIBUTE}
       RequestHeader set Remote-User-Name %{MELLON_name}e env=MELLON_name
    </If>

    <If "-n env('REMOTE_USER_EMAIL_SAML_ATTRIBUTE')">
       MellonSetEnv email ${REMOTE_USER_EMAIL_SAML_ATTRIBUTE}
       RequestHeader set Remote-User-Email %{MELLON_email}e env=MELLON_email
    </If>

    <If "-n env('REMOTE_USER_PREFERRED_USERNAME_SAML_ATTRIBUTE')">
       MellonSetEnv preferred_username ${REMOTE_USER_PREFERRED_USERNAME_SAML_ATTRIBUTE}
       RequestHeader set Remote-User-Preferred-Username %{MELLON_preferred_username}e env=MELLON_preferred_username
    </If>

    ${REQUEST_HEADERS}
</LocationMatch>
```

### テスト
Azure ADのユーザーをエンタープライズ アプリケーションに追加して
https://hoge.example.com/fuga
にアクセスし、Azure ADのログイン画面を経てバックエンドサーバーのfugaディレクトリの中身が見られれば完了です。

## まとめ
前回の大ハマりと違って今回は特に問題なくサーバー構築が出来ました。
なお、ディレクトリ毎にリバースプロキシ先を変えたりアクセス制限をかけたい場合などは、１コンテナに詰め込むのではなく単機能のコンテナを多段に組み合わせて構築した方が良さそうです。