はじめに
Ubuntu 20.04上でMediawikiにOpenIDConnect extensionをインストールして使おうとした際に遭遇したもろもろのメモです。
【2020/03/04追記】
https://github.com/dexidp/dex/pull/1473 のコミットによってDexにuserinfo_endpointが実装されました。
【2020/12/01追記】
2020/03/04に追記したようにuserinfo_endpointがサポートされました。変更からかなりの時間が経過しましたので、古い内容は削除し、新しくリリースされたLTSの1.35.0に合わせて内容を修正しています。
この記事では、DexをOpenID ConnectのProviderとして利用し、MediawikiのOpenIDConnect拡張を利用して動いています。
利用したソフトウェアのバージョン等
Webサーバーはapache2を利用しています。
- OS: Ubuntu 20.04 on VMWare Workstation
- Packages: 下記の"導入しなければいけないモジュール"を参照
- Mediawiki: 1.35.0 (LTSの最新版)
- Mediawiki Extensions: 1.35に対応している最新のモジュール)
- PluggableAuth-REL1_35-2a465ae.tar.gz
- OpenIDConnect-REL1_35-05d76c0.tar.gz
1.35.0からは、PHP 7.3.19以降を要求します。このためUbuntuのバージョンは20.04 LTSにアップグレードしています。
参考文献
- https://www.mediawiki.org/wiki/Extension:OpenID_Connect
- https://www.mediawiki.org/wiki/Extension:PluggableAuth
- https://developers.google.com/identity/protocols/OpenIDConnect
- https://connect2id.com/products/server/docs/api/token
基本的な手順
- パッケージの導入
- mediawikiのtar.gzアーカイブを/var/www/html等に展開
- ($wgResourceBasePathに対応する)展開したディレクトリ直下のextensionsディレクトリに必要なExtensionのアーカイブを展開
- LocalSettings.phpの編集
- maintenanceディレクトリに移動し、
php update.php
コマンドを実行 - (maintenanceから../に移動した)mediawikiを展開したディレクトリ直下で、composer.local.jsonファイルを作成
- (5)のディレクトリで
composer update
を実行
だいたいこのような流れでしょうか。
コマンドライン
$ cd /var/www/html
$ sudo tar xvzf ~/mediawiki-1.35.0.tar.gz
$ cd mediawiki-1.35.0
$ cd extensions/
$ sudo tar xvzf ~/OpenIDConnect-REL1_35-05d76c0.tar.gz
$ sudo tar xvzf ~/PluggableAuth-REL1_35-2a465ae.tar.gz
$ cd ..
$ sudo chown -R www-data:www-data .
$
導入しなければいけないモジュール
Ansibleを利用してパッケージを導入しましたが、次のようなリストをwith_items:に指定しています。
with_items:
- apache2
- mysql-server
- mysql-client
- libapache2-mod-php
- php-mysql
- php-mbstring
- php-xml
- php-curl
- python3-pymysql
- composer
- unzip
意外なところは、unzipがなくて、composerがダウンロードしたパッケージをunpackできないところでした。
composerの依存関係に入っていないんですね…。
ansibleによる設定の内容
mysql-serverのパッケージを導入し、mysqldが稼動した状態になったら、作業用にあらかじめALTER USERでroot@localhostに適当なパスワードを設定します。
$ ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'xxxxxxxx';
検証用の環境設定なので作業はansibleを利用しています。
"xxxxxxxx"と"yyyyyyyy"の部分は適当なパスワードで置き換えてください。
- name: setup mysql database
become: True
mysql_db:
name: "mediadb"
login_user: "root"
login_password: "xxxxxxxx"
state: present
- name: setup mysql user and password
become: True
mysql_user:
login_user: "root"
login_password: "xxxxxxxx"
name: mwiki
password: yyyyyyyy
priv: '*.*:ALL,GRANT'
state: present
- name: extract mediawiki-1.35.0 archive to /var/www/html
become: True
unarchive:
src: "https://releases.wikimedia.org/mediawiki/1.35/mediawiki-1.35.0.tar.gz"
dest: "/var/www/html/"
remote_src: yes
- name: extract openid connect module to /var/www/html/mediawiki-1.31.3/extensions
become: True
unarchive:
src: "{{ item }}"
dest: "/var/www/html/mediawiki-1.35.0/extensions/"
remote_src: yes
with_items:
- "https://extdist.wmflabs.org/dist/extensions/OpenIDConnect-REL1_35-05d76c0.tar.gz"
- "https://extdist.wmflabs.org/dist/extensions/PluggableAuth-REL1_35-2a465ae.tar.gz"
- name: change owner ship
become: True
file:
path: "/var/www/html/mediawiki-1.35.0"
recurse: yes
owner: www-data
group: www-data
mode: "a+rwX,go-w"
これで/var/www/html/以下にファイルが展開され、MySQLサーバー上に"media_db”と接続用のID, mwikiが設定されました。
ここまで設定すると、http://127.0.0.1/mediawiki-1.35.0/ でアクセスができ、初期設定が可能になります。
自己署名TSL接続用のファイル配置
設定自体は正規のTSL鍵でも同様ですが、外部からアクセスできない環境なのでLet's Encryptは利用できません。
ここでは自分でeasy-rsa3によって立ち上げたCA局を利用して、サーバー用PEMファイルと、KEYファイルを配置します。
Ubuntu系列の場合は、PEMファイルは /usr/share/ca-certificates/以下に適当なディレクトリを作成して配置し、/etc/ca-certificates.conf に作成したディレクトリからのエントリを追記します。
$ $ ls /usr/share/ca-certificates/local/
oidcmediawiki.example.net.crt
$ tail -2 /etc/ca-certificates.conf
mozilla/UCA_Global_G2_Root.crt
local/oidcmediawiki.example.net.crt
/etc/ca-certificates.confを更新してから、update-ca-certificatesコマンドを実行すると、/etc/ssl/certs/が更新されます。
$ sudo update-ca-certificates
Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.
$ ls -l /etc/ssl/certs/ | grep example.net
lrwxrwxrwx 1 root root 34 Dec 1 13:40 85f6afaf.0 -> oidcmediawiki.example.net.pem
lrwxrwxrwx 1 root root 67 Dec 1 13:40 oidcmediawiki.example.net.pem -> /usr/share/ca-certificates/local/oidcmediawiki.example.net.crt
mediawikiの初期設定
以前のこのドキュメントはmediawikiが動き出している前提でしたが、今回は mediawiki 1.35.0を新しいVMに構築したので初期化処理も行なっていきます。
oidcmediawiki.example.netのIPアドレスを 192.168.1.100 として、Webブラウザからアクセスします。
今回はTLSの設定は後にしています。
今回のDB名はansibleで設定したように、mediadb、DBにアクセスするID:PWは、mwiki:yyyyyyyy になっています。
設定が終わると、LocalSettings.php がダウンロードできるので、oidcmediawiki.example.netの/var/www/html/mediawiki-1.35.0/ 直下に配置します。
Apache2のTLS設定
PHPはパッケージ導入と一緒にモジュールが有効になっているはずですが、いくつかTLSに必要なモジュールが mod-enabled にないと思いますので、設定しておきます。
$ sudo a2enmod ssl
$ sudo a2enmod rewrite
設定ファイルは、/etc/apache2/sites-available/default-ssl.conf が配置されていますが、反映されていないので、今回はこのファイルを直接編集します。
もうちょっとちゃんとしたテスト、本番環境を設定する場合には、別途ansibleなどで配布することをお勧めします。
--- default-ssl.conf.orig 2020-12-01 13:55:35.711255633 +0900
+++ default-ssl.conf 2020-12-01 13:55:45.147523460 +0900
@@ -29,8 +29,8 @@
# /usr/share/doc/apache2/README.Debian.gz for more info.
# If both key and certificate are stored in the same file, only the
# SSLCertificateFile directive is needed.
- SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
- SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
+ SSLCertificateFile /etc/ssl/certs/oidcmediawiki.example.net.pem
+ SSLCertificateKeyFile /etc/ssl/private/oidcmediawiki.example.net.nopass.key
# Server Certificate Chain:
# Point SSLCertificateChainFile at a file containing the
--- 000-default.conf.orig 2020-12-01 13:58:46.908394031 +0900
+++ 000-default.conf 2020-12-01 13:58:49.188455670 +0900
@@ -1,4 +1,7 @@
<VirtualHost *:80>
+RewriteEngine on
+RewriteCond %{HTTPS} off
+RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# The ServerName directive sets the request scheme, hostname and port that
# the server uses to identify itself. This is used when creating
# redirection URLs. In the context of virtual hosts, the ServerName
設定を/etc/apache2/sites-enabled/ に反映させます。
$ sudo a2ensite default-ssl
Enabling site default-ssl.
...
$ sudo systemctl restart apache2
初期設定時にダウンロードした LocalSettings.php を、配置したことを確認してからアクセスすると、https://にリダイレクトされます。
LocalSettings.phpの追記内容
$wgGroupPermissions['*']['edit'] = false;
$wgGroupPermissions['*']['read'] = false;
$wgGroupPermissions['*']['autocreateaccount'] = true;
wfLoadExtension( 'PluggableAuth' );
$wgPluggableAuth_EnableAutoLogin = true;
$wgPluggableAuth_EnableLocalLogin = false;
wfLoadExtension( 'OpenIDConnect' );
$wgOpenIDConnect_UseEmailNameAsUserName = true;
$wgOpenIDConnect_Config['https://dex.example.com/dex'] = [
'clientID' => 'yourclientid',
'clientsecret' => '.........your secret..........',
'scope' => [ 'openid','profile','email' ]
];
composer.local.jsonの内容
雛形はcomposer.local.json-sampleの名前でmediawikiのtar.gzを展開した直下にあるはずなので、これをコピーして中央の1行を編集しています。
$ sudo cp -ip composer.local.json-sample composer.local.json
コピーいたら composer.local.json ファイルを編集します。
{
"extra": {
"merge-plugin": {
"include": [
"extensions/OpenIDConnect/composer.json"
]
}
}
}
update.phpの実行
LocalSettings.phpの内容を更新したタイミングで、update.php等を実行します。
$ cd /var/www/html/mediawiki-1.35.0
$ sudo composer update
$ sudo php maintenance/update.php
update.phpを実行しないと、動作は正常に見えますが、dexからredirectした時点でエラーになります。
Dex側でのclientidの登録など
Dex側はkubernetesで動かしているので、ConfigMapでconfig-ldap.yamlを管理しています。
ポイントはredirectURIsの登録で、index.php/Special:PluggableAuthLogin
を指定して、次のようになっています。
httpsを利用するか、httpを利用するかは、LocalSettings.phpの wgServer の設定を確認してください。
redirectURIs:
- 'http://mediawiki.example.com/mediawiki-1.35.0/index.php/特別:PluggableAuthLogin'
- 'http://mediawiki.example.com/mediawiki-1.35.0/index.php/Special:PluggableAuthLogin'
修正前のDexではここで日本語や記号などの%エンコードの対象となる文字列を含んでいると正常に動作しません。
この問題は後述します。
デバッグ用の追加設定
いろいろ問題が発生するので、LocalSettings.phpの先頭に次のようなデバッグ設定を追加しています。
<?php
error_reporting( -1 );
ini_set( 'display_startup_errors', 1 );
ini_set( 'display_errors', 1 );
$wgDebugToolbar = true;
$wgShowExceptionDetails = true;
# This file was automatically generated by the MediaWiki 1.31.3
# installer. If you make manual changes, please keep track in case you
# need to recreate them later.
ページの最下部にDebug LogやConsoleなどのタブが表示されるので、そこをクリックすることでメッセージを確認することができます。
この他にも$wgDebugLogFile
を試してみましたが、あまり有用ではなかったので、最終的に削除しています。
composer updateに失敗した場合のエラー
しばらくunzipがない、php-curlがない、などの理由でcomposerの実行がエラーになっている事に気がつかず、依存関係が解決できなかったので、次のようなエラーが表示されていました。
[7f109e2d098b727e2ab5738a] /mediawiki-1.31.3/index.php/Special:PluggableAuthLogin Error from line 131 of /var/www/html/mediawiki-1.31.3/extensions/OpenIDConnect/src/OpenIDConnect.php: Class 'Jumbojett\OpenIDConnectClient' not found
Backtrace:
#0 /var/www/html/mediawiki-1.31.3/extensions/PluggableAuth/includes/PluggableAuthLogin.php(31): OpenIDConnect->authenticate(NULL, NULL, NULL, NULL, NULL)
#1 /var/www/html/mediawiki-1.31.3/includes/specialpage/SpecialPage.php(565): PluggableAuthLogin->execute(NULL)
#2 /var/www/html/mediawiki-1.31.3/includes/specialpage/SpecialPageFactory.php(568): SpecialPage->run(NULL)
#3 /var/www/html/mediawiki-1.31.3/includes/MediaWiki.php(288): SpecialPageFactory::executePath(Title, RequestContext)
#4 /var/www/html/mediawiki-1.31.3/includes/MediaWiki.php(861): MediaWiki->performRequest()
#5 /var/www/html/mediawiki-1.31.3/includes/MediaWiki.php(524): MediaWiki->main()
#6 /var/www/html/mediawiki-1.31.3/index.php(42): MediaWiki->run()
#7 {main}
dexからredirectされた後に、ログイン状態にならない
この現象は最新のDexではuserinfo_endpointに対応しているため問題にはなりません。過去にあった事象の記録として掲載していますが、基本的にはこのセクションは無視してください。
DexからSpecial:PluggableAuthLoginにRedirectされた後、ログイン状態とならずに画面に何も表示されない状態になっています。
[PluggableAuth] ERROR: Jumbojett\OpenIDConnectClientException: The provider userinfo_endpoint could not be fetched. Make sure your provider has a well known configuration available. in /var/www/html/mediawiki-1.31.3/vendor/jumbojett/openid-connect-php/src/OpenIDConnectClient.php:474
Stack trace:
#0 /var/www/html/mediawiki-1.31.3/vendor/jumbojett/openid-connect-php/src/OpenIDConnectClient.php(439): Jumbojett\OpenIDConnectClient->getWellKnownConfigValue('userinfo_endpoi...', NULL)
#1 /var/www/html/mediawiki-1.31.3/vendor/jumbojett/openid-connect-php/src/OpenIDConnectClient.php(917): Jumbojett\OpenIDConnectClient->getProviderConfigValue('userinfo_endpoi...')
#2 /var/www/html/mediawiki-1.31.3/extensions/OpenIDConnect/src/OpenIDConnect.php(163): Jumbojett\OpenIDConnectClient->requestUserInfo('name')
#3 /var/www/html/mediawiki-1.31.3/extensions/PluggableAuth/includes/PluggableAuthLogin.php(31): OpenIDConnect->authenticate(NULL, NULL, NULL, NULL, NULL)
#4 /var/www/html/mediawiki-1.31.3/includes/specialpage/SpecialPage.php(565): PluggableAuthLogin->execute(NULL)
#5 /var/www/html/mediawiki-1.31.3/includes/specialpage/SpecialPageFactory.php(568): SpecialPage->run(NULL)
#6 /var/www/html/mediawiki-1.31.3/includes/MediaWiki.php(288): SpecialPageFactory::executePath(Object(Title), Object(RequestContext))
#7 /var/www/html/mediawiki-1.31.3/includes/MediaWiki.php(861): MediaWiki->performRequest()
#8 /var/www/html/mediawiki-1.31.3/includes/MediaWiki.php(524): MediaWiki->main()
#9 /var/www/html/mediawiki-1.31.3/index.php(42): MediaWiki->run()
#10 {main}
"userinfo_endpoint"と"dex"をキーワードに検索すると、issue#376の中で、MUSTなんだけれどdexではuserinfo_endpointに対応していない様子が分かります。
自分の環境で、https://dex.example.com/dex/.well-known/openid-configuration にアクセスすると、dexが公開している情報は次のようになっています。
{
"issuer": "https://dex.example.com/dex",
"authorization_endpoint": "https://dex.example.com/dex/auth",
"token_endpoint": "https://dex.example.com/dex/token",
"jwks_uri": "https://dex.example.com/dex/keys",
"response_types_supported": [
"code"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"scopes_supported": [
"openid",
"email",
"groups",
"profile",
"offline_access"
],
"token_endpoint_auth_methods_supported": [
"client_secret_basic"
],
"claims_supported": [
"aud",
"email",
"email_verified",
"exp",
"iat",
"iss",
"locale",
"name",
"sub"
]
}
この一方で、https://accounts.google.com/.well-known/openid-configuration では、次のような情報が得られます。
{
"issuer": "https://accounts.google.com",
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"token_endpoint": "https://oauth2.googleapis.com/token",
"userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
"revocation_endpoint": "https://oauth2.googleapis.com/revoke",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
"response_types_supported": [
"code",
"token",
"id_token",
"code token",
"code id_token",
"token id_token",
"code token id_token",
"none"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"scopes_supported": [
"openid",
"email",
"profile"
],
"token_endpoint_auth_methods_supported": [
"client_secret_post",
"client_secret_basic"
],
"claims_supported": [
"aud",
"email",
"email_verified",
"exp",
"family_name",
"given_name",
"iat",
"iss",
"locale",
"name",
"picture",
"sub"
],
"code_challenge_methods_supported": [
"plain",
"S256"
]
}
気を取り直して、OpenIDConnectClient.phpのエラー元の474行目付近を確認すると、ここでは関数の呼び出し時に指定されたendpointの情報があるか or ないかでエラーを出しているだけなので、917行目を確認すると、userinfo_endpointを明示的に指定しています。
どんな情報を利用しているのか、呼び出し元のOpenIDConnect.phpの163行目付近を確認してみると、要求しているのは name, email, sub のみで、これなら認証した時の$claimsに格納されているよね、と思いました。
そこで、少しコードを変更して、比較のため元のコードは一応残していますが、実際には認証時のclaimsを参照するようにしました。
--- OpenIDConnect.php.orig 2019-06-18 04:14:42.000000000 +0900
+++ OpenIDConnect.php 2019-08-07 15:52:53.728532724 +0900
@@ -160,9 +160,9 @@
wfDebugLog( 'OpenID Connect', 'Redirect URL: ' . $redirectURL );
if ( $oidc->authenticate() ) {
- $realname = $oidc->requestUserInfo( 'name' );
- $email = $oidc->requestUserInfo( 'email' );
- $this->subject = $oidc->requestUserInfo( 'sub' );
+ $realname = $oidc->getVerifiedClaims( 'name') or $oidc->requestUserInfo( 'name' );
+ $email = $oidc->getVerifiedClaims( 'email' ) or $oidc->requestUserInfo( 'email' );
+ $this->subject = $oidc->getVerifiedClaims( 'sub' ) or $oidc->requestUserInfo( 'sub' );
$this->issuer = $oidc->getProviderURL();
wfDebugLog( 'OpenID Connect', 'Real name: ' . $realname .
', Email: ' . $email . ', Subject: ' . $this->subject .
@@ -318,7 +318,7 @@
$preferred_username =
$oidc->requestUserInfo( $config['preferred_username'] );
} else {
- $preferred_username = $oidc->requestUserInfo( 'preferred_username' );
+ $preferred_username = $oidc->getVerifiedClaims( 'preferred_username' ) or $oidc->requestUserInfo( 'preferred_username' );
}
if ( strlen( $preferred_username ) > 0 ) {
$preferred_username = $preferred_username;
一応これでログインできるようになりました。