mod_authnz_fcgiでRedmineのアカウントを使ってBasic認証を行う
2016/12/27
Gitoliteを建てたり、Redmineを建てたり、Redmineと連携させたり、SmartHTTPに対応したり、暗号化してみたり、僕自身も一体何を目指しているのかよく分からなくなってきたGitサーバ構築
お次はSmartHTTPのBasic認証をRedmineのアカウントで出来るように連携してみようと思います。
スポンサーリンク
RedmineでBasic認証
今の流行りはシングルサインオン、LDAPやらOAuthやらOpenIDやらって奴ですね。
Webサービスでもよく見かける「Twitterでログイン」とか「GitHubでログイン」とかもそれなのかな。
とにかく「1つのアカウントでログイン出来る様にしよう」みたいな奴ですね(逆に言えば、パスワードが漏れると全部にログインされちゃう訳でもありますが……)
と言うより、散ったパスワードを管理するのが面倒なのでRedmineのアカウントでSmartHTTPが利用できれば良いよね、と言う魂胆です。(パスワードのリセットや変更も簡単ですしね。)
連携させておけば、彼女(そんなものは居ない)が開発に参加してくれる事になった時でもきっと喜ばれる事間違いなしでしょう。
「私チルノ!!パスワードが分からなくなっちゃった!!」なんて事もなくなり、優雅にアカウントを管理出来ます。
前提条件
とりあえず、前提条件は以下の通りです。
- Apache 2.4.10以降(Ubuntu 16.04やRaspbianはOK、RHEL系はパッケージが風化してるので怪しいかも知れない)
- mod_authnz_fcgiが利用可能(普通にDSOが使えるなら使える)
- Redmineインストール済み(データベースはMySQLで確認、ポスグレでも行けるとは思いますが未確認)
やり方
細かい解説は後にして、先にやり方を紹介してしまいましょう。
認証用のプログラムを入手
後で説明しますが、mod_authnz_fcgiは認証を他のプログラムに任せる形になるので、認証を行うプログラムが必要です。
と、言う訳でRedmineのアカウントでの認証を行うプログラムを組んでみました(ソースはGitHubにあります、興味があればどうぞ)
適当な場所に配置しておきましょう(今回は例として/etc/apache2/に配置します。)
cd /etc/apache2/
sudo curl -O https://raw.githubusercontent.com/HimaJyun/mod_authnz_fcgi-for-redmine/master/fcgiauthredmine.pl
sudo chmod +x fcgiauthredmine.pl
initスクリプトを配置
認証用のプログラム<->Apache間の通信はFastCGIで行われます。
FastCGIとして起動するためにspawn-fcgiが必要なのでインストールしておいてください。(Apacheのfastcgistarterでは機能不足)
sudo apt-get install spawn-fcgi
デーモンとして常駐させる事になる訳ですが、そのためにはinitスクリプトを利用します。
initスクリプトも用意しましたのでぜひご利用下さい。
cd /etc/init.d
sudo curl -O https://raw.githubusercontent.com/HimaJyun/mod_authnz_fcgi-for-redmine/master/fcgiauthredmine
sudo chmod +x fcgiauthredmine
initスクリプトの設定
initスクリプト内に設定項目があるので、これを好みに合わせて調整してください。
sudo editor /etc/init.d/fcgiauthredmine
設定項目は以下の通りです。
# === config ===
# 先ほどDLした認証用のプログラムのパス
path="/etc/apache2/fcgiauthredmine.pl"
# pidファイルを配置するパス
pid="/run/fcgiauthredmine.pid"
# 子プロセス数
proc=3
# 使用するポート番号
port=8989
# バインドするアドレス、127.0.0.1なら外部から接続不可になって安全
bind="127.0.0.1"
# 起動時に指定する引数
args=(--dsn='DBI:mysql:database=redmine;host=localhost;mysql_socket=/run/mysqld/mysqld.sock' \
--user='root' \
--pass='123' \
--mysql_auto_reconnect)
# ==============
起動時に使用する引数は以下の物が指定可能です。
# データベースに接続するための文字列
--dsn="string"
# データベースのユーザ名(SQLiteでは不要)
--user="string"
# データベースのパスワード(SQLiteでは不要)
--pass="string"
# MySQLのタイムアウト対策、MySQLの場合は設定を推奨
--mysql_auto_reconnect
dsnは主に以下のような形になります。
# MySQL(ソケット通信:オススメ)
DBI:mysql:database=${データベース名};host=localhost;mysql_socket=${ソケットのパス}
# MySQL(TCP通信)
DBI:mysql:database=${データベース名};host=localhost;port=3306
# PostgreSQL
DBI:Pg:database=${データベース名};host=localhost;port=5432
# SQLite(ロックの関係から非推奨)
DBI:SQLite:database=${データベースファイルまでのパス}
起動時に実行するように設定
CentOSもUbuntuもsystemctlで出来る様になって説明の手間が省けますわ。
sudo systemctl enable fcgiauthredmine
Apacheの設定を更新
後はApacheに用意した認証用プログラムと連携させる様に設定を仕込むだけです。
かなり省いていますが以下のようになります。
AuthnzFcgiDefineProvider authn AuthRedmine fcgi://localhost:8989/
<VirtualHost *:80>
ServerName vegito.jyn.jp
DocumentRoot /var/www/git
<Location />
AuthType Basic
AuthName "Git SmartHTTP"
AuthBasicProvider AuthRedmine
Require valid-user
</Location>
</VirtualHost>
ポイントがいくつかありますよ。
「AuthnzFcgiDefineProvider」はサーバ全体の設定に対してしか利用できません、<VirtualHost>の中には書けないので外に出しましょう。
「AuthnzFcgiDefineProvider authn」の部分、「authn」です、「authz」でも「authnz」でもなく、「authn」です、間違えないようにしましょう。(nが認証(パスワードが正しいか)、zが承認(ユーザの要求する操作が許可されているか)らしいです。)
「AuthRedmine」は何でも構いません、好きな名前を設定しましょう、ここで設定した値を「AuthBasicProvider」に指定します。
「fcgi://localhost:8989/」、ポートを変更している場合は合わせて調整して下さい。
mod_authnz_fcgiをロードするのを忘れずに!!
sudo a2enmod authnz_fcgi
後はサーバを再起動すれば行けるはず。
sudo systemctl restart fcgiauthredmine
sudo systemctl restart apache2
解説
どうしてこんな面倒な方法でやらなきゃならないのかを説明してみましょう。
Apacheと連携させるのは中々難しい
Apache 2.2まではmod_auth_mysqlと言う物があり、パッチあてこそ必要なものの、これを使う事で認証が可能だったみたいです。
が、2.4からmod_auth_mysqlは使えなくなり、代わりにmod_auth_dbdが導入されました。
ただ、これで行けるほど世の中は甘くありません。
mod_auth_dbdではApacheが認識できる形のパスワードである必要があります。(BCryptとかSHAとか)
Redmineのパスワードのハッシュ自体はSHA1みたいですが、塩(ソルト)が掛かっています。(幸い(?)ストレッチングはないみたいですが)
どっち道、自分でパッチあてしてコンパイルしたモジュールなんか使いたくありません(せっかくのパッケージ管理システムが無駄になるので)
Redmine.pm
RedmineにはRedmine.pmとか言うPerlモジュールが付属しています。
なんでも、GitやSVNのレポジトリを使えるようにしたり、認証、その他諸々などを提供出来るApache向けの物みたいです。
が、なんだか使い方がややこしく、認証のためだけのに使うには仰々しいのでちょっとアレです。(僕のやり方も充分仰々しいですけど、まだ認証Onlyなだけマシ、Redmine.pmはまず第一に認証のためだけに使えるのかどうかすら不明)
mod_authnz_fcgi
じゃあ不可能なのか?世の中に出来ない事など(少ししか)ありません。
Apache 2.4.10からmod_authnz_fcgiと言う物が導入され、利用出来るようになりました。
これは、FastCGIに対応した認証バックエンドに対してアカウントの認証を委譲出来る機能です。
端的に言えば、認証の部分だけを他のプログラムに任せられるような物、的な認識で構わないでしょう。(実際そうですし)
mod_authnz_fcgiは文字通りfcgiで連携するのでFastCGIに対応するようにプログラムを書く必要がありますが、認証結果を軽く出力してやれば良いだけなので割と簡単に出来ます。(少なくとも僕の基準では)
つまりこれはどう言う事かというと、極端な話、プログラムさえ用意すれば相手が何だろうと連携出来る訳です。RedmineとApache、くらいなら下手にLDAPなんか使うより楽です。
mod_authのnとかzとかって奴
mod_authn_fileとか、mod_authz_coreとか、今回に至ってはmod_authnz_fcgi、このnとかzってなんだ?!
nはAuthentication(オーセンティケーション)のn、zはAuthorization(オーソリゼーション)のz……だと思う(間違ってたらごめんなさい)
よく似ているが微妙に意味が違う、Authenticationは認証で、Authorizationは認可。
Authentication(認証)は文字通り、証を認める、つまり、ユーザの持つ証(パスワード)が正しいかを確かめる
Authorization(認可)は認めるに可(可能かどうか)、で、その行動(例えば「このページが見たい」)などが許されているかどうか
みたいな感じだと思う(保証はしない)、以下と未満、的な奴に似ている。
なので、mod_authnは認証を行うためのモジュール、例えば.htpasswdなどで使うmod_authn_fileはこっち。
mod_authzは認可を行うためのモジュール、Require valid-userなどのRequireが含まれているmod_authz_coreはこっち。
そして、mod_authnzはそのどちらも可能である事を示す。
なので、今回僕の作ったプログラムはnになる訳。
Redmineのパスワードハッシュアルゴリズム
Redmineのパスワード(データベースに保存されているもの)は、SHA1で計算されたハッシュに塩が振られ更にSHA1されてあります。
仮にパスワードを123、ソルトを456としましょう。(実際には、ソルトはランダムな値、パスワードは最低8桁なのでここまで弱くはありませんが)
123のSHA1ハッシュは「40bd001563085fc35165329ea1ff5c5ecbdbbeef」です。
これにソルトを結合した「45640bd001563085fc35165329ea1ff5c5ecbdbbeef」を更にSHA1でハッシュします。
ハッシュ結果は「19f5eaafa9908d21c49fd3e0301d1118be835152」となり、これがデータベースに保存されています。
認証の際はこれと同じ事をユーザが入力したパスワードに対して行い、一致するか否かを調べれば良いのです。(普通のパスワード検証ですね。)
ちなみに、今回僕が作った認証プログラムでは、タイミング攻撃とブルートフォース対策(?)として、ユーザが見つからなかった、パスワードが一致しなかった場合に1秒のsleepを入れています。
GitoliteのSmartHTTPで利用する際の注意
GitoliteはSmartHTTP経由でアクセスしてきたユーザを識別するために、Basic認証を通過したユーザ名を利用します。
Redmine(と言うよりデータベース依存?)はユーザ名の大文字小文字を区別しません、HimaJyunでも、himajyunでもログイン出来ます。
今回僕が作ったプログラムはSELECTを投げるだけのシンプル設計なので、区別の有無はデータベースや設定に左右されます。(僕の手元のMySQLでは区別しませんでした)
そして、Gitoliteのユーザ名は大文字小文字を区別します(と言うより、*nixがファイル名の大文字小文字を区別するのに依存しているのかも?)
なので、ユーザ名の大文字小文字には注意しましょう(最も、小文字で統一する様にしたりしてRedmineとGitoliteのユーザ名の大文字小文字を揃えていないのが悪いのですが……)