認証とSSL化はApacheをフロントエンドにするのが良いよね、と考える人は大勢いるようで Wikiにドキュメントの「Apache Reverse Proxy for same origin and authentication」で解説されています。
この手順の問題点は null_authentication_handler を使うように指示しているところで、 Security Features Overviewに記述されているSecurity Modelを無視する形になるところです。
そこで認証自体はApacheで行なって、ヘッダについている"Authorization"行を解析してユーザ名を取り出して、/_users/org.couchdb.user:$usernameを参照してRolesを設定するようにCouchDBを改造してみました。
- 注意事項
- 使い方
- local.iniファイルの編集
- Apache側の設定
- /etc/ssl/certs/ssl-cert-couch.pem, /etc/ssl/private/ssl-key-couch.pem
- /etc/apache2/htdigest.db
- /etc/apache2/htpassword.db
- 変更を加えたcouch_httpd_auth.erl用のパッチ全体
注意事項
今回作成したhandlerは渡された情報を信用し、自分でパスワードの妥当性を検証しません。
ApacheではBasic認証とDigest認証が選択できますが、Basic認証に対応する"Authorization Basic ..."ヘッダは"default_authentication_handler"につかまってしまうため、"default_authentication_handler"を有効にすることができません。
"default_authentication_handler"は不要じゃないか、と思うかもしれませんが、127.0.0.1:5984から接続する際に問題が発生します。
"Authorization Basic ..."ヘッダを捏造してアクセスすると、"webproxy_authentication_handler"はパスワードの妥当性を検証しないため、任意のユーザになりすます事ができてしまいます。
今回のようにApacheでDigest認証を有効にして、"require_valid_user = true"にした設定で、"default_authentication_handler"を併用するのがお勧めです。
想定される解決策
adminsや/_users以下の文書にパスワードが記載されているユーザについては、Basic認証の時にパスワードが一致するか検証する事ができます。
この変更は少し時間をみてやってみようと思います。
Proxy認証のようにsaltを元にした情報をくっつける方法は、今回のようにlocalhostにログインできるユーザへの対応としては適当ではないと考えています。
使い方
couch_httpd_auth.erlに対するpatchは後半に載せています。 適当なファイル("apache-couchdb-1.0.1.webproxy.diff")にして、patchコマンドで適用していきます。
$ ls -ld apache-couchdb-1.0.1 <samp>drwxr-sr-x 11 user1 user1 4096 2010-08-12 03:19 apache-couchdb-1.0.1</samp> $ patch -p0 < apache-couchdb-1.0.1.webproxy.diff patching file apache-couchdb-1.0.1/src/couchdb/couch_httpd_auth.erl
あとはsrc/couchdbディレクトリに移動してmakeするなり、erlc couch_httpd_auth.erlでbeamファイルに変換してから、既存のファイルと置き換えて全体をリスタートします。
$ cd apache-couchdb-1.0.1/src/couchdb/ $ make $ sudo cp couch_httpd_auth.beam /usr/local/lib/couchdb/erlang/lib/couch-1.0.1/ebin/ $ sudo /etc/init.d/couchdb restart
/etc/init.d/couchdbファイルは/usr/local/etc/init.d/couchdbファイルをコピーしたものです。
local.iniファイルの編集
関連する設定項目は次のとおりですが、default.iniとの差分を全部載せるのは大変です。 CouchDBの設定については/_configを経由して、今回の場合は/_config/httpdと/_config/couch_httpd_authの出力が役に立つはずです。
[httpd]
WWW-Authenticate = Basic realm="administrator"
authentication_handlers = {couch_httpd_auth, default_authentication_handler}, {couch_httpd_auth, webproxy_authentication_handler}
[couch_httpd_auth]
require_valid_user = true
require_authentication_db_entry = true
local.iniファイルに記述したadminsのID(admin), Password(password)を使って設定内容を出力してみます。
$ curl -u admin:password http://127.0.0.1:5984/_config/httpd
{ "max_connections":"2048", "bind_address":"127.0.0.1", "vhost_global_handlers":"_utils, _uuids, _session, _oauth, _users", "port":"5984", "default_handler":"{couch_httpd_db, handle_request}", "secure_rewrites":"true", "allow_jsonp":"false", "authentication_handlers":"{couch_httpd_auth, default_authentication_handler}, {couch_httpd_auth, webproxy_authentication_handler}", "WWW-Authenticate":"Basic realm=\"administrator\"" }
$ curl -u admin:password http://127.0.0.1:5984/_config/couch_httpd_auth
{ "auth_cache_size":"50", "timeout":"600", "secret":"329435e5e66be8a9a652af105f42401e", "require_valid_user":"true", "require_authentication_db_entry":"true", "authentication_db":"_users", "authentication_redirect":"/_utils/session.html" }
Apache側の設定
Ubuntu Server 10.04 LTS付属のApacheを使っています。 SSLを有効にするような細かい設定は省いて、ポイントになるところだけ抜粋しておきます。
<VirtualHost couch.example.org:80>
...
<IfModule mod_alias.c>
Redirect permanent / https://couch.yasundial.org/
</IfModule>
</VirtualHost>
<IfModule mod_ssl.c>
<VirtualHost couch.example.org:443>
...
SSLCertificateFile /etc/ssl/certs/ssl-cert-couch.pem
SSLCertificateKeyFile /etc/ssl/private/ssl-key-couch.pem
...
<Location />
## Digest Auth
AuthType Digest
AuthName "CouchDB"
AuthDigestDomain /
AuthDigestProvider file
AuthUserFile /etc/apache2/htdigest.db
## end of Digest Auth
## Basic Auth
# AuthType Basic
# AuthName "CouchDB"
# AuthUserFile /etc/apache2/htpassword.db
## end of Basic Auth
Require valid-user
</Location>
<IfModule mod_proxy.c>
ProxyPass / http://127.0.0.1:5984/
ProxyPassReverse / http://127.0.0.1:5984/
</IfModule>
</VirtualHost>
</IfModule>
設定ファイルにあって、新しく配置したファイルと作成方法の概略は次の通りです。
/etc/ssl/certs/ssl-cert-couch.pem, /etc/ssl/private/ssl-key-couch.pem
$ /usr/lib/ssl/misc/CA.pl -newreq $ /usr/lib/ssl/misc/CA.pl -sign $ rm newreq.pem $ cp newcert.pem ssl-cert-couch.pem $ cp newkey.pem ssl-key-couch.pem
/etc/apache2/htdigest.db
$ sudo htdigest -c htdigest.db CouchDB admin
/etc/apache2/htpassword.db
$ sudo htpasswd -c htpassword.db admin
変更を加えたcouch_httpd_auth.erl用のパッチ全体
--- apache-couchdb-1.0.1/src/couchdb/couch_httpd_auth.erl 2010-06-23 22:21:30.000000000 -0700
+++ apache-couchdb-1.0.1.webproxy_auth/src/couchdb/couch_httpd_auth.erl 2010-11-25 19:09:49.000000000 -0800
@@ -17,6 +17,7 @@
-export([cookie_authentication_handler/1]).
-export([null_authentication_handler/1]).
-export([proxy_authentification_handler/1]).
+-export([webproxy_authentication_handler/1]).
-export([cookie_auth_header/2]).
-export([handle_session_req/1]).
@@ -347,3 +348,102 @@
make_cookie_time() ->
{NowMS, NowS, _} = erlang:now(),
NowMS * 1000000 + NowS.
+
+%%
+%% webproxy auth handler %%
+%%
+%% This handler allows a user authentication by an external system.
+%% It expects the external system passes 'Authorization Basic' or 'Authorization Digest' header.
+%% The authenticated username and corresponding user roles will be set into the userCtx object.
+%% Corresponding user roles are referred from the /$authentication_db/org.couchdb.user:$username document.
+%%
+%% The following article suggested to use the null_authentication_handler, but it doesn't maintain userCtx object.
+%% -> http://wiki.apache.org/couchdb/Apache_As_a_Reverse_Proxy
+%%
+%% This handler implicitly uses new config entry, require_authentication_db_entry, the possible value is true or false.
+%% If it's true, then the $username document is required to set username and roles to the userCtx object.
+%% Otherwise, the authentication will be failed.
+%% It's the default behavior.
+%%
+%% If it's false and there is no corresponding $username document at $authentication_db,
+%% then the only $username will be set into the userCxt object with empty roles.
+%%
+%% Note: The password has never been used, but only username is referred.
+%% Note: The digest authentication is recommended because default_authentication_handler will trap the basic authentication header.
+%%
+webproxy_authentication_handler(Req) ->
+ AuthorizationHeader = header_value(Req, "Authorization"),
+ case AuthorizationHeader of
+ "Basic " ++ _ ->
+ webproxy_basic_auth(Req);
+ "Digest " ++ DigestValue ->
+ webproxy_digest_auth(Req, DigestValue);
+ _ -> webproxy_default_terminate_action(Req)
+ end.
+
+webproxy_digest_find_user([H|T]) ->
+ case H of
+ ["username",U] ->
+ %% RFC2069 says U must be a quoted-string, so remove double quote charaters.
+ User = string:sub_string(U,2,string:len(U)-1),
+ ["username",User];
+ _ -> webproxy_digest_find_user(T)
+ end.
+
+webproxy_default_terminate_action(Req) ->
+ %% reference: http://wiki.apache.org/couchdb/Security_Features_Overview
+ case couch_server:has_admins() of
+ true ->
+ Req;
+ false ->
+ case couch_config:get("couch_httpd_auth", "require_valid_user", "false") of
+ "true" -> Req;
+ %% If no admins, and no user required, then everyone is admin!
+ %% Yay, admin party!
+ _ -> Req#httpd{user_ctx=#user_ctx{roles=[<<"_admin">>]}}
+ end
+ end.
+
+webproxy_basic_auth(Req) ->
+ case basic_name_pw(Req) of
+ {User, _} ->
+ case couch_auth_cache:get_user_creds(User) of
+ nil ->
+ case couch_config:get("couch_httpd_auth", "require_authentication_db_entry", "true") of
+ "true" ->
+ throw({unauthorized, <<"Name couldn't be found on authentication_db.">>});
+ _ ->
+ Req#httpd{user_ctx=#user_ctx{name=?l2b(User), roles=[]}}
+ end;
+ UserProps ->
+ Req#httpd{user_ctx=#user_ctx{
+ name=?l2b(User),
+ roles=couch_util:get_value(<<"roles">>, UserProps, [])
+ }}
+ end;
+ _ -> webproxy_default_terminate_action(Req)
+ end.
+
+webproxy_digest_auth(Req, DigestValue) ->
+ %% DigestValue might be "username=\"yasu\", realm=\"CouchDB\", ..."
+ DigestKVSplitFun = fun(X) -> string:tokens(string:strip(X), "=") end,
+ DigestItemList = [DigestKVSplitFun(X) || X <- string:tokens(DigestValue,",")],
+ %% DigestItemList might be [[key0,value0], ["username","yasu"], [key1,value1], ...]
+ case webproxy_digest_find_user(DigestItemList) of
+ ["username", User] ->
+ case couch_auth_cache:get_user_creds(User) of
+ nil ->
+ case couch_config:get("couch_httpd_auth", "require_authentication_db_entry", "true") of
+ "true" ->
+ throw({unauthorized, <<"Name couldn't be found on authentication_db.">>});
+ _ ->
+ Req#httpd{user_ctx=#user_ctx{name=?l2b(User), roles=[]}}
+ end;
+ UserProps ->
+ Req#httpd{user_ctx=#user_ctx{
+ name=?l2b(User),
+ roles=couch_util:get_value(<<"roles">>, UserProps, [])
+ }}
+ end;
+ _ -> webproxy_default_terminate_action(Req)
+ end.
0 件のコメント:
コメントを投稿