Apacheへのアクセスに二要素認証を適用する

これまで接続元を社内のIPに制限していたRedmineのサーバを
諸事情により社外のどこからでもアクセスできるようにしたいという要望があったので、
Apacheとmod_authn_otpを使って二要素認証を実現してみました。

手順作成には以下のサイトを参考にしました。

- https://github.com/archiecobbs/mod-authn-otp
- https://nazx.jp/x/apache_tips_000
- http://freedom-man.com/blog/openam-hotp-googleauthenticator/

今回の手順は、AWS上のAmazon Linuxで動作確認しました。
Apacheは既に導入･設定が済んでいる前提です。

### mod_authn_otpの導入

まずはmod_authn_otpとその前提条件を導入します。
mod_authn_otpはGitHub上で公開されています。

```
sudo yum install httpd24-devel openssl-devel automake gcc
git clone https://github.com/archiecobbs/mod-authn-otp
cd mod-authn-otp
./autogen.sh -c
make
sudo make install
```

導入できたらApache側でモジュールの読み込みを有効化します。

```
echo "LoadModule authn_otp_module modules/mod_authn_otp.so" | sudo tee /etc/httpd/conf.d/authn_otp.conf > /dev/null
service httpd configtest
sudo service httpd reload
```

### Apache設定

今回はもともと書いてあったVirtualHostの設定に追記しています。
適宜環境に合わせて記述内容は調整してください。

```
<VirtualHost *:443>
  ServerName <your_server_name>
  DocumentRoot <your_document_root>
  SSLEngine on

  <Location />
    Satisfy any
    Order allow,deny
    Allow from 127.0.0.1
    # 特定のIP帯からはMFA無しで利用できるようにしたい場合
    Allow from xxx.xxx.xxx.xxx/30

    AuthType Basic
    AuthName "OTP Authentication (Enter OTP as password)"
    AuthBasicProvider OTP
    Require valid-user
    OTPAuthUsersFile /var/www/otp/users
    OTPAuthMaxLinger 3600
    OTPAuthLogoutOnIPChange On
  </Location>
</VirtualHost>
```

### OTP用のUserファイル作成

Usersファイルをどのような形式で書けばよいかは、
同梱されているusers.sampleファイル内に書かれています。

https://github.com/archiecobbs/mod-authn-otp/blob/master/users.sample

色々項目がありますが、ユーザ作成の段階で必要なのは
Token Type, Username, PIN, Token Keyの4項目です。
後の項目は認証が行われるたびに都度更新されます。

今回はこれらの4項目を以下のように設定しました。

- Token Type: HOTP/T30(Google Authenticatorの時間ベースで扱える形式)
- Username: 適当に
- PIN: 無し
- Token Key: ランダムに生成したsecret_keyをBase16エンコードした値を記入

Token KeyにはBase16エンコードした値を入れておくのがポイントです。
サーバ側が持つusersファイルにはsecret_keyをBase16エンコードした値を格納しておき、
ユーザが利用するGoogle Authenticator等のツールにはsecret_keyをBase32エンコードした値を設定する必要があります。

手で記入するのは面倒なので以下のようなスクリプトでユーザを作成しました。
issuerの部分は適宜変更してください。

```
#!/bin/bash -e
user=${1:?Usage: $0 username}
issuer=${2:-your_company_name}
secret=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 15 | head -n 1)
secret_base16=$(python -c "import base64; print base64.b16encode('${secret}')")
secret_base32=$(python -c "import base64; print base64.b32encode('${secret}')")
otpauth_uri="otpauth://totp/${issuer}:${user}?secret=${secret_base32}&issuer=${issuer}"
otpauth_uri=$(python -c "import urllib; print urllib.quote('${otpauth_uri}')")
qrcode_url="https://chart.googleapis.com/chart?chs=300x300&cht=qr&chl=${otpauth_uri}"

file="/var/www/otp/users"
if [ ! -f "${file}" ]; then
  [ -d $(dirname "$file") ] || mkdir -p $(dirname "$file")
  touch ${file}
  chown -R apache:apache $(dirname "$file")
fi
[ -w "${file}" ] || (echo "${file}: Permission denied" && exit 1)

count=$(awk "\$2 ~ /^$user}\$/" ${file} | wc -l)
if [ $count -le 0 ]; then
  echo "HOTP/T30 $(printf '%-12s' $user) - ${secret_base16}" >> ${file}
  echo "$qrcode_url"
else
  echo "User '$user' already exists"
fi
```

引数にUsernameを指定して実行すると、必要な項目を埋めた行が /var/www/otp/users に追記され、
標準出力にGoogle Authenticator登録用のQRコードが表示されるURLが出力されます。
設定したUsernameとQRコードのURLをユーザに伝えれば利用準備は完了です。

### Google Authenticatorに登録する

Android版のGoogle Authenticator(Google認証システム)の場合は以下のような手順で登録できます。

- アプリを起動
- メニューからアカウントを設定を選択
- バーコードをスキャン
- QRコードをスキャンして完了

後は実際にアクセスする際に、指定されたUsernameと表示されたOTPを
それぞれBasic認証のIDとPasswordの欄に入力すれば接続できるようになります。
今回の例ではPINを設定しませんでしたが、PINを設定した場合はPasswordの欄に入力するのはPINとOTPを結合したものになります。