KeycloakとActive Directoryを連携して、ドメイン参加マシンからApache WebサーバーにSSO

タイトルを短くするのが難しい。

WindowsのActive Directoryで便利な事の一つに、ドメイン参加しているWindows PCからIISのWebサーバーにアクセスするときは、PCにサインインした時のドメインアカウントでSSO出来るというのがある。Webサイトへのアクセス時にユーザー名とパスワードを入力しなくてもユーザー認証と認可制御が効いて、ユーザビリティという点では大変便利。ADドメインが使える企業内のイントラネットで使われる。

同じようにLinux上のWebサーバー（今回はApache）でも、ADドメイン参加マシンからSSOで、パスワード入力無しのユーザー認証（いわゆる統合Windows認証）をやってみたい。
以下の様なアクセス構成になる。

「Windows(ADメンバ)のWebブラウザ(Chrome)」
　→「Linux(RHEL)のWebサーバー(apache)」
　　→「Linux(RHEL)上のKeycloak」
　　　→「WindowsのADドメコン」

Keycloakが胆で、これがWebサーバーに対するOIDCプロバイダーになっており、かつADドメコンからLDAPでユーザー情報を引っ張って来つつ、KerberosでSPNEGOのSSOを捌くサーバーとなる。

# 検証環境
以下４台の仮想マシンをVMware上に作成。
・ADドメコン
　Windows Server 2019、ADドメコン、DNSサーバー。
・Keycloakサーバー
　RHEL 8.3、Keycloakサーバー。
・Webサーバー
　RHEL 8.3、httpd(Apache)。
・Webクライアント
　Windows Server 2019、Chrome。

WebクライアントマシンはWindows 10の方が良いのだろうけども、OSイメージを何種類もダウンロードするのが難儀だったため、WS2019を使用。
各VMのスペックは適当に、CPU 2個、メモリ 2GB、HDD 20GBとか。
Windows Server 2019はStandard EditionのGUI付きで、RHELはMinimal Install。

# ADドメインの準備
１．ADドメコンサーバーを、ADドメコンとして構築する。手順は細かくは省略。Active Directory Domain Serviceの役割を有効にして、「example.com」ドメインのドメコン、かつそのDNSサーバーとして設定する。

２．DNSにKeycloakサーバーのA、PTRレコードを追加する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/759852d3-ab70-d11f-792c-3f144b733ad9.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/6c95c62d-3266-6633-c2a2-51e95ff4d943.png)

３．同じく、DNSにWebサーバーのA、PTRレコードを追加する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/94e82bcb-2e0b-f26c-d064-c767e3c589a7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/eca47e5d-8d5e-74db-f3f5-34a81d730bac.png)

３．テスト用のユーザーとしてUsersに「aduser01」という名前の一般ユーザーを作成する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/a403d7b6-7849-fda8-73c3-c3ac3b493086.png)

４．同じく、「keycloak」ユーザーを一般ユーザーとして作成する。keycloakからのAD連携用に使うもので、後のkeycloakの設定と合わせるなら、パスワードは「Passw0rd!」で。また、パスワードは無期限で。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/2f2aecce-0fdf-fc96-1a78-bcb6a7c3798d.png)

５．Kerberos連携用のkeytabファイルを作成する。cmdのコマンドプロンプト（powershell不可）を開き、以下を実行。

```
> ktpass ^
-out keycloak.HTTP.keytab ^
-princ HTTP/keycloak.example.com@EXAMPLE.COM ^
-ptype KRB5_NT_PRINCIPAL ^
-mapuser keycloak ^
-pass Passw0rd!
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/a244005e-d40d-ac97-ff6e-72f5daef075d.png)


６．出来たkeytabファイル「kc-kerberos.HTTP.keytab」をKeycloakサーバーの「/root」ディレクトリに転送する。


# Keycloakのインストール
参照したインストールガイドは以下。検証時点のKeycloakのバージョンは12.0.4。
https://www.keycloak.org/docs/latest/server_installation/index.html
ダウンロード先はこちら。
https://www.keycloak.org/downloads

１．Keycloakサーバーにて、nameserverがADドメコンを向いていて、searchドメインが「example.com」なのを確認する。

```
[root@keycloak log]# cat /etc/resolv.conf
# Generated by NetworkManager
search example.com
nameserver 192.168.0.221
```

２．RHEL 8.3のインストールCDをVMに接続した状態で、Keycloakサーバーで以下を実行する。

```
# mount /dev/cdrom /media
# cat > /etc/yum.repos.d/media.repo << 'EOF'
[media-baseos]
name=RHEL - Media - BaseOS
baseurl=file:///media/BaseOS
gpgcheck=0
enabled=1

[media-appstream]
name=RHEL - Media - AppStream
baseurl=file:///media/AppStream
gpgcheck=0
enabled=1
EOF
# yum install -y java-1.8.0-openjdk

# curl -L -o keycloak.tar.gz https://github.com/keycloak/keycloak/releases/download/12.0.4/keycloak-12.0.4.tar.gz
# tar xf keycloak.tar.gz

# sed -i keycloak-12.0.4/standalone/configuration/standalone.xml -e 's/jboss.bind.address:127.0.0.1/jboss.bind.address:0.0.0.0/'
# keycloak-12.0.4/bin/add-user-keycloak.sh -r master -u admin -p password
# firewall-cmd --add-port=8080/tcp --permanent
# firewall-cmd --reload
# setenforce permissive

# keycloak-12.0.4/bin/standalone.sh
```

３．実行後、Webブラウザで「http://＜KeycloakサーバーのIP＞:8080/auth」を開くと以下の画面が表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/f9cf686c-d59b-9ca1-c2e9-225ca3ad86e5.png)
「Administration Console」から管理画面にログイン。管理者のユーザー名、パスワードは、先のadd-user-keycloak.shでそれぞれ「admin」、「password」を設定している。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/39841921-f9df-af39-fbb8-fe7b736daa48.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/58d4d15d-f42f-ed15-f301-7580e3ad10db.png)


# Keycloakの設定
KeycloakのAdmin Consoleで以下を実施。

１．左メニューから「web」レルムを作成。Webレルムを開く。

２．左メニューの「User Federation」を開く。「Add provider...」から「ldap」を選択。

３．以下を入力して「Save」。
[Required Settings]
　Vendor:    Active Directory
　Connection URL:    ldap://＜ADドメコンのIPアドレス＞
　Users DN:    CN=Users,DC=example,DC=com
　Bind DN:    keycloak
　Bind Credential:    Passw0rd!
[Kerberos Integration]
　Allow Kerberos authentication:    ON
　Kerberos Realm:    EXAMPLE.COM
　Server Principal:    `HTTP/keycloak.example.com@EXAMPLE.COM`
　KeyTab:    /root/kc-kerberos.HTTP.keytab

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/4722973d-1efd-5f09-bcc3-3c8f672d61e1.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/b451fc5e-a8f5-e0cf-06bc-1158383c1fc7.png)

４．左メニューの「Clients」を開く。「Create」をクリックし、以下を入力して「Save」。
なお、Root URLの設定等、ところどころ「web.example.com」とFQDNではなく、「web」とホスト名を指定しているが、これがSSO上すこぶる重要である。
　Client ID:    web
　Client Protocol:    openid-connect
　Root URL:    `http://web/`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/b8f55d1f-dbd1-a4e4-bfb9-7a2da942e3f2.png)

５．「Clients > web」の設定画面で以下の値を修正し、「Save」。
　Access Type:    confidential
　Valid Redirect URIs:    http://web/redirect_uri
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/25a7c294-952a-9b31-497b-5a69d81b5499.png)

６．「Clients > web」の「Credential」のタブで「Secret」の値をコピーして手元にとっておく。後で使う。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/352b4ed2-000e-acf4-a2f8-b19c9901ebbf.png)


# Webサーバーの設定
１．Webサーバーにて、nameserverがADドメコンを向いていて、searchドメインが「example.com」なのを確認する。

```
[root@web ~]# cat /etc/resolv.conf
# Generated by NetworkManager
search example.com
nameserver 192.168.0.221
```

２．Webサーバーにhttpd(Apache HTTPD Server)とmod_auth_openidcを導入する。
RHEL 8.3のインストールCDをVMに接続した状態で、Webサーバーで以下を実施。

```
# mount /dev/cdrom /media
# cat > /etc/yum.repos.d/media.repo << 'EOF'
[media-baseos]
name=RHEL - Media - BaseOS
baseurl=file:///media/BaseOS
gpgcheck=0
enabled=1

[media-appstream]
name=RHEL - Media - AppStream
baseurl=file:///media/AppStream
gpgcheck=0
enabled=1
EOF
# yum install -y httpd
# yum install -y /media/AppStream/Packages/mod_auth_openidc-2.3.7-4.module+el8.2.0+6919+ac02cfd2.3.x86_64.rpm /media/AppStream/Packages/cjose-0.6.1-2.module+el8+2454+f890a43a.x86_64.rpm

# firewall-cmd --add-service=http --permanent
# firewall-cmd --reload
```

３．Apacheで、Webサイト全体でOIDCのアクセス制御を有効にする。以下のファイル「/etc/httpd/conf.d/oidc.conf」を作成する。
OIDCClientSecretの値「69ecc621-1bef-4835-ad86-d5c3e84594eb」は、先に入手したSecret。

```:/etc/httpd/conf.d/oidc.conf
OIDCResponseType code
OIDCCryptoPassphrase xxxxxxxx

OIDCProviderMetadataURL http://keycloak:8080/auth/realms/web/.well-known/openid-configuration
OIDCSSLValidateServer Off

OIDCClientID web
OIDCClientSecret 196ea352-4a85-4fa5-8a23-fac4ad60248a
OIDCRedirectURI http://web/redirect_uri

<Location />
   AuthType openid-connect
   Require valid-user
</Location>
```

４．httpd(Apache)を起動する。

```
# systemctl start httpd
```

# WebブラウザでWebサーバーにアクセスする

１．Webクライアント（Windows Serverマシン）をexample.comドメインのメンバーとして追加する。

２．WebクライアントにChromeをインストールする。
https://www.google.com/chrome/?standalone=1&platform=win64

３．Administratorでログイン中の場合は一旦ログオフし、Webクライアントにドメインユーザー「aduser01」でサインインする。

４．Chromeを起動し、「`http://web/`」にアクセスする。

さて、以下の様なApacheの画面が表示されただろうか。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/d0babb07-40b7-0fad-27a9-56de50554c02.png)

そりゃ表示されんだろ、と思うかもしれないが、このページはApacheの設定によりOIDCのアクセス制御がされていて、画面は「aduser01」ユーザーとしてWebサーバーに認証された結果として表示されている。この時、Webブラウザでページを開く際にユーザー名とパスワード入力が必要なかったというのが重要である。

試しにPHPでWebサーバーの環境変数を表示するページを用意してみよう。

```
（Webサーバーで実行）
# yum install -y php
# cat > /var/www/cgi-bin/phpinfo.php << EOF
<?php
phpinfo();
?>
EOF
# systemctl restart httpd
```

Webブラウザで「`http://web/cgi-bin/phpinfo.php`」にアクセスすると以下の様な画面が表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/37a80301-5a0a-8ae3-17ff-59af81df2f9d.png)


その下の方、PHP Variablesの表で「OIDC_access_token」、「OIDC_CLAIM_name」といった環境変数が有効なのを見てとれる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/8b3ded2b-a5db-da58-4e07-52c46e3b34f5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/88fe8f68-7919-09e2-4218-4fe0828c6914.png)

「OIDC_CLAIM_name」からは認証済みのADドメイン上のユーザー名（aduser01）が取得でき、「OIDC_access_token」は以下の様なサイトでデコード可能なJWTである。
https://jwt.io/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/e5f60763-8e91-cdf8-06be-84303e414fb2.png)

# 補足
ドメイン参加PCからのアクセスであればユーザー名とパスワード入力無しに認証付のWebサイトにアクセスできるようになるのは、使い勝手としては素晴らしいのだけれども、プライバシーの観点でパブリックWebサイトで同様の手法は使えない。不穏なWebサイトにアクセスしたらADドメインなりユーザーレジストリの自分のIDが勝手に抜かれてましたというのは大変不味い。

であるのでこの統合Windows認証というのは、基本的には会社のイントラネットでの用途に限定したほうが良いのだが、まあ、DNS次第なのでWebサーバーの実体がパブリックインターネット上にあっても使えることは使えるのか？そういう意味だとサーバーがドメイン参加しないといけないIISよりも優れているかも。HTTPSにするとFQDNじゃなきゃ的な面倒毎が増えそうだが。

ちなみにこの構成をとるとKeycloakサーバー用にWindows Server CALが１つ追加で必要になると思うが、まあその請求責任はライセンスリセラーに有るのでそこが文句言わなければ知らんぷりをしても良いだろう。

Apacheだけでなく、OIDCに対応したAPIゲートウェイ的なリバースプロキシで統合Windows認証もやってみたい。次の目標はそれか。