Saturday, May 24, 2014

あなたのapacheのBasic認証をgoogle 2段階認証に対応させるには

Googleの2要素認証ですが、数年前のリリース以来、少しずつ普及しているようです。

ただ、googleのサービス=例えばgmailとか=でしか使えないし、あるいは自分のwebサービスに導入するにはgoogle様に何らかのお布施をしなければならないんでしょ...と思っているあなた! それは単なる思い込みや誤解です。

目標

  1. 自分が管理するapache上のコンテンツの一部に認証をかけたいとして、
  2. ユーザー名がfooでパスワードがbarだとする。
  3. 導入が比較的お手軽なBasic認証ですませたい。
  4. ただしgoogle2要素認証も使う。スマホのアプリでワンタイムパスワード(以下OTP)を受け取るやつ。
  5. ブラウザ上に出現するbasic認証のダイアログには、アカウントとしてfoo、パスワードとしてbar+OTPを入力する。例えばOTPが123456であればパスワードとして"bar123456"で認証できるようにする

用意するもの

  • linuxマシン。以下の例ではOracleVirtualBox+Vagrantで構築したCentOS6.5を使用(もっと古いディストリでも大丈夫)
  • スマートフォン。android,iphoneどっちでも。

手順

まず、スマホにGoogle認証アプリをインストールしておく。androidならplayストア、iphoneならitunesで「Google認証」で検索すればすぐ見つかります。 既にインストール済みの人はそれをそのまま使えます。

次に、サーバにhttpdやgoogle-authenticatorなどをインストールします。いずれもyumで可能です。ただしgoogle-authenticatorのパッケージはepelリポジトリにしか無いので注意。あとhttpd-develやgccなんかも必要です。

[vagrant@localhost ~]$ cat /etc/redhat-release 
CentOS release 6.5 (Final)
[vagrant@localhost ~]$ sudo yum install http://ftp.riken.jp/Linux/fedora/epel/6/i386/epel-release-6-8.noarch.rpm
[vagrant@localhost ~]$ sudo yum install httpd httpd-devel subversion google-authenticator
[vagrant@localhost ~]$ sudo /etc/init.d/httpd start

この時点で普通にapacheが起動できるはずです。ブラウザでアクセスして確認しておきましょう。iptablesとかは適宜設定しておくか、切っておきましょう。

なお、サーバの時計をできるだけ正確に合わせておいてください。google認証はシークレットキーと時刻情報からワンタイムパスワードを割り出すので、 スマホ側とサーバ側とで時計が狂いすぎているとうまく認証できません。

さてここでgoogle-authenticator-apache-moduleの登場です。 コンパイル済みのバイナリもダウンロードできますが、 それは古くてOTPだけの認証しかできない(2要素認証になってない)バージョンなので、 最新ソースをcheckoutして自分でビルドします。 同梱の設定ファイルのコピーも忘れずに。

$ svn checkout http://google-authenticator-apache-module.googlecode.com/svn/trunk/ google-authenticator-apache-module-read-only
$ cd google-authenticator-apache-module-read-only
$ make
$ sudo make install
$ sudo cp googleauth.conf /etc/httpd/conf.d/

$ sudo vi /etc/httpd/conf.d/googleauth.conf で下記のように編集。(Alias /tmp /tmp を追加しただけ)

Loadmodule authn_google_module modules/mod_authn_google.so
Alias /tmp /tmp
<Directory /tmp>
    Options FollowSymLinks Indexes ExecCGI
    AllowOverride All
    Order deny,allow
    Allow from all
    AuthType Basic
    AuthName "My Test"
    AuthBasicProvider "google_authenticator"
    Require valid-user
    GoogleAuthUserPath ga_auth
    GoogleAuthCookieLife 3600
    GoogleAuthEntryWindow 2
</Directory>

次に、ユーザー"foo"用の認証設定ファイルの準備。 最初のほうでインストールしたgoogle-authenticatorコマンドを使います。

[vagrant@localhost ~]$ google-authenticator
https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/vagrant@localhost.localdomain%3Fsecret%3DOVLUR4T2XXXXXXXX
Your new secret key is: OVLUR4T2XXXXXXXX
Your verification code is 995999
Your emergency scratch codes are:
  39831342
  73015310
  75610143
  61758264
  10475486

Do you want me to update your "~/.google_authenticator" file (y/n) y

Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n) y

By default, tokens are good for 30 seconds and in order to compensate for
possible time-skew between the client and the server, we allow an extra
token before and after the current time. If you experience problems with poor
time synchronization, you can increase the window from its default
size of 1:30min to about 4min. Do you want to do so (y/n) y

If the computer that you are logging into isn't hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting (y/n) y
[vagrant@localhost ~]$ 

何やら楽しそうなURLが表示されました。urlをブラウザにコピペして開いてみましょう。 QRコードが表示されるはずです。 スマホのGoogle認証アプリを起動し、右上のメニューから「アカウントを設定」→「バーコードをスキャン」でブラウザに映っているQRコードを読み取ります。 出力されているsecret keyとverification codeを手作業でスマホに入力する方法でももちろんOKですが、面倒です。

スマホのアプリ画面は次のようになるはずです。赤枠の部分が今回の作業で追加した認証トークンになります。

これでスマホのGoogle認証アプリの準備はできました。サーバ側の設定作業に戻ります。

さきほどのgoogle-authenticatorコマンドの出力結果が ~/.google_authenticator に保存されています。 それを一部コピペ&編集してapacheのユーザー認証設定として使います。

[vagrant@localhost ~]$ cat ~/.google_authenticator 
OVLUR4XXXXXXXXXX
" RATE_LIMIT 3 30
" WINDOW_SIZE 17
" DISALLOW_REUSE
" TOTP_AUTH
39831342
73015310
75610143
61758264
10475486

この内容を少し改変して、/etc/httpd/ga_auth/[ユーザー名] というファイルに格納します。ここではfooというユーザーにしますので、下のような感じ。

[vagrant@localhost ~]$ sudo mkdir /etc/httpd/ga_auth
[vagrant@localhost ~]$ sudo vi /etc/httpd/ga_auth/foo

最終的にこんな結果になるように編集しておき、apacheを再起動したら作業完了です!

[vagrant@localhost ~]$ cat /etc/httpd/ga_auth/foo
OVLUR4XXXXXXXXXX
" RATE_LIMIT 3 30
" WINDOW_SIZE 17
" TOTP_AUTH
" PASSWORD=bar

[vagrant@localhost ~]$ sudo /etc/init.d/httpd restart

PASSWORD=bar というところがポイントです。好きなようにパスワードを決めて書いておいてください。これが2要素認証のうちの第1要素にあたります。

いよいよブラウザでapacheにアクセスしてみましょう。/tmpというURIをファイルシステムの/tmpにAliasし、そこだけ2要素認証がかかるようにしたので、 http://サーバのip/tmp というurlにアクセスします。

いつものBasic認証ダイアログが出現します。ここでユーザー名はfoo、そしてパスワードの欄は bar + ワンタイムパスワードです。 上の画像で言えば、379076を示しているので、ダイアログのパスワードには bar379076 を入力します。/tmp の配下のファイル一覧が見えれば成功です。

補足

本家のwikiに書いてある通り、できればSSLも使うべきです。 そうすればBasic認証のリクエストヘッダも暗号化通信の対象となるため安心です。あと、Basic認証ではなくDigest認証もできるようですね。ためしてないけど。

それから、お気づきの通り、google-authenticatorコマンドはシークレットキーとともにいくつかの緊急用コードも出力してくれます。これはスマホを盗難、紛失、破損などしてOTPを得られなくなってしまった場合に備えての緊急用です。が、google-authenticator-apache-moduleはこれには対応してないようです(詳しく調べてません)。ソースほじろうかと思いましたが疲れたんでこのへんで。

Webの安全のために手をつくしてくれているGoogle先生とプログラマー諸氏に感謝!

Tuesday, May 6, 2014

spring-bootと組み込みTomcatが便利すぎて鼻血でそう

Tomcat embeded - 組み込み型Tomcat - が正式にサポートされたのは2011年5月のTomcat7.0.14の前後あたりのようです。もう3年も前です。 一方で2014年4月、Spring frameworkはSpring Boot 1.0.0を正式にリリースしました。

この二つのニュースを別個に見ると「ふーん」としか思えません。時間軸もズレすぎですし。 しかし、最近、某所でSpring-Bootとtomcat-embededを組み合わせて使ってみたら、便利すぎて鼻血出そうになりました。

便利さがうまく伝わるかどうかわかりませんが、せめてメンテついでにMixer2のサンプルアプリで使ってみるか、と思いまして、 mixer2-fruitshop-springbootを作りました。 動かし方はこちらのページとほぼ同じ。

SpringFrameworkと組み込み型Servletコンテナをうまいこと組み合わせてくれる、という機能はspring-bootの数多くの機能のうちのひとつにすぎませんが、 とにかくここでは組み込みTomcatとの連携機能の話だけにします。

とりあえずmixer2-fruitshop-springbootをざっくりご覧いただきつつ ポイントを挙げます。

  • src/main/webapp/WEB-INF/web.xml のようなフォルダやxml設定ファイルはもはや存在しません。こうした旧来型のWebアプリケーションとしての構造は分業開発の妨げになりやすいのですが、これは普通の単純なjavaライブラリとまったく同じ構造です。もちろんこれは、Servlet3.0準拠であることや、あるいは、ビューエンジンとしてjspではなくMixer2を使っているおかげでビューのテンプレートをwebappとかWEB-INFとかそういうところに置く必要が無い、ということによる効果でもあります。
  • pom.xmlのbuildタグ周辺を見てください。実質的にはmaven-jar-pluginとmaven-dependency-pluginが使われているだけです。特別なビルド方式は必要ありません。spring-bootにはmaven用のプラグインもあれこれ用意されているのですが、ビルド方式までもが特定のフレームワーク提供のプラグインに縛られるのは個人的には好みではないのでこの方法を採用しました。これにあとmaven-assembly-pluginを追加して、アプリ本体のjarとlibフォルダ配下に集められるjarをまとめてzip化するようなセッティングをすれば、リリース成果物の生成も簡単です。
  • pom.xmlのpackaging指定はwarではなくjarです。このwebアプリケーションはmvn packageするとjarファイルになります。いわゆるfat-jarではなく純粋なjarです。tomcat-embeded-x.y.z.jar等の組み込みtomcatライブラリの助けを得ながらwebアプリケーションとして起動します。コマンドラインで言えば java -jar mixer2-fruitshop-springboot-1.0-SNAPSHOT.jar -classpath "lib/*" で起動できます。
  • サンプルアプリではServerクラスにmain()メソッドを置いてあります。eclipse等での開発作業中はこのserverクラスを通常のjavaアプリとして起動し、ブラウザで8080ポートを見て動作確認をします。繰り返しますがwebアプリではあるものの構造上は単なるjavaアプリなので、eclipseでWTPのようなプラグインは必要ありません。tomcatも別途用意する必要がありません。だって組み込みtomcatだから。ただのjavaアプリだから。
  • tomcatのcontext.xmlやwebアプリとしてのWEB-INF/web.xmlにあたるものは全てjavaコードとして記述します。サンプルアプリではMvcConfigurationSessionTrackingConfigListenerにあたります。もちろんここのコード上で設定されている値はspringの種々の機能を使えば、他の設定ファイルやコマンドライン引数等で外側から上書きすることが可能となります。Springのprofile機能も使いこなせるでしょう。

こんな話だけでは便利さが伝わらないかもしれません。とにかくこれはちょっとしたパラダイムシフトですね。 これからは、「tomcatを用意してそこにSpringMVCによるwebアプリケーション(*.warファイル)をdeployする」のではなくて、 「webアプリケーションとtomcatをSpringFramework上で起動する」という感覚になります。もう、webアプリとtomcatを別々に用意する必要はないのです。これらは、ひとつです。