CouchDBはデフォルトで 127.0.0.1:5984にバインドするようになってはいますが、ローカルユーザに対するセキュリティはデフォルトでは何もありません。 loopback IPアドレスへのバインドがセキュリティだなんていわないですよね…。
デスクトップ用のDBを探しているとはいえ、Linuxは元々マルチユーザOSですし、それでなくてもいろいろ心配です。
認証(Authentication)はCouchDB全体で一つで良いですが、できれば認証はLDAPと連携したいし、承認(Authorization)はDB毎に行ないたいところです。FAQをみるとDocument毎の承認は無理みたいですね…。
たまたま CouchDBのCookie認証についての記事をみつけたので参考にしつつ、どういうシステムなのか実際に設定を変更して確認してみました。
- 準備作業
- Basic認証 - 普通のWebサーバレベル
- 認証方式の設定について
- proxy_authentification_handler を使った感想
- ファイルレベルのパーミッションについて
- まとめ
準備作業
source codeを確認する必要がありそうなので、apt-get source couchdbと最新の apache-couchdb-1.0.1.tar.gzを展開しました。
またUbuntu上の0.10.0でも確認するようにしていますが、基本的には最新の1.0.1をmake installして確認作業をしています。
Basic認証 - 普通のWebサーバレベル
参考にさせて頂いたyssk22さんの記事だと「local.iniにユーザ名とパスワードの組を書いておく」べし、となっています。
どういう事なのかなと思ってlocal.iniファイルをみると、次のような変更が必要でした。
...
;WWW-Authenticate = Basic realm="administrator"
...
; require_valid_user = false
...
[admins]
;admin = mysecretpassword
...
WWW-Authenticate = Basic realm="administrator"
...
require_valid_user = true
...
[admins]
;admin = mysecretpassword
user1 = ab5e29d1
この設定でBasic認証が要求されるようになりましたが、少し問題もありました。
最初の問題は、local.ini に書いた生のパスワードは次のようにハッシュ化した状態で書き換えられるため、local.ini ファイルに書き込み権限がないと起動に失敗します。
user1 = -hashed-2f8dfa38b8543afaf3675d62f46c575bc96e7972,2ae6fb964a7f37818a1db077bbfc1def
これはパスワードをじかに書いた後の一度だけなので、起動してしまえば書き込み権限は不要です。
それも避けたいという場合には、直接この行を生成することもできます。
カンマで区切られた後半の文字列はSaltですから、次のようにすれば手動で生成できます。 パスワードやSaltを適宜変更することを忘れないでください。
$ echo -n "ab5e29d1""2ae6fb964a7f37818a1db077bbfc1def" | sha1sum 2f8dfa38b8543afaf3675d62f46c575bc96e7972 -
次の問題はUbuntuのパッケージから導入したり、make installすると、local.iniのファイルパーミッションが644になるところです。 CouchDB WikiのInstalling on Ubuntuでは、次のようなパーミッションを設定しています。
chown -R root:couchdb /etc/couchdb chmod 664 /etc/couchdb/*.ini chmod 775 /etc/couchdb/*.d
これが悪いとはいわないけれど、ガイドの記述に追加して、Otherに対するアクセス権を落しています。
$ sudo chmod 2750 /etc/couchdb $ sudo chmod o-rwx /etc/couchdb/local.ini
Makefile.amにchmod o-rwxを入れる事もできるけれど、どういうパーミッションにするのかはサイトのポリシーの問題でもあります。 少なくともやたらと書き込み権限を落すと起動しなくなる場合があることには注意が必要でしょう。
残りはRuby用のCouchモジュールで、Basic認証の機能がないので元々準備されていた@optionsインスタンス変数を利用して、ID/Passwordを与えるように修正しました。
--- couchdb.rb.orig 2010-11-09 19:27:38.000000000 +0900
+++ couchdb.rb 2010-11-09 19:27:31.000000000 +0900
@@ -32,6 +32,7 @@
end
def request(req)
+ req.basic_auth @options['user'], @options['password'] if @options.kind_of?(Hash) and @options.has_key?('user') and @options.has_key?('password')
res = Net::HTTP.start(@host, @port) { |http|http.request(req) }
unless res.kind_of?(Net::HTTPSuccess)
handle_error(req, res)
--- main.rb.orig 2010-11-09 19:17:53.000000000 +0900
+++ main.rb 2010-11-09 19:18:27.000000000 +0900
@@ -9,7 +9,7 @@
csv = GenCSVText.new
-couch = Couch::Server.new("172.16.73.197","5984")
+couch = Couch::Server.new("172.16.73.197","5984",{'user'=>'user1', 'password'=>'ab5e29d1'})
1000.times do |doc_id|
doc_url = "/csv/qa." + doc_id.to_s
res = nil
Basic認証については、こんなところです。
認証方式の設定について
couchdbでの認証方法の種類を設定する方法や、その仕組みについてまとめておきます。
まずは default.ini ファイルに書かれている authentication_handlers について。
authentication_handlers = {couch_httpd_oauth, oauth_authentication_handler}, {couch_httpd_auth, cookie_authentication_handler}, {couch_httpd_auth, default_authentication_handler}
右辺にあるタプルの前半はモジュール名で、src/couchdb/couch_httpd_auth.erl などにある関数を、2番目の *_handler で指定しています。
default_authentication_handler が実際に使われる主な処理をしているところだと思います。
使える候補としては、couch_httpd_auth.erlで-export()で宣言されている関数は次の通りです。
-export([default_authentication_handler/1,special_test_authentication_handler/1]). -export([cookie_authentication_handler/1]). -export([null_authentication_handler/1]). -export([proxy_authentification_handler/1]). -export([cookie_auth_header/2]).
yssk22さんの説明は助かっていますが、残念ながらCookie認証についてはほとんど理解できていません。 Cookie認証は少し放っておいて、コメントのある proxy_authentification_handler を試すことにしました。
proxy_authentification_handler について
Ubuntu 10.04 LTSのパッケージで導入している0.10.0では、 proxy_authentification_handlerはありません。 CHANGESファイルによれば、0.11.0から導入されたようです。 ここから先は手動でVMWare上のUbuntu 10.04に導入した、1.0.1で試しています。
この proxy_authentification_handler はコメントを読むと、認証を他のシステムで行なった結果を送ってもらい、couchdbとその認証システムで共有しているtokenをキーに生成した情報を照合するという仕組みのようです。
Cookieのような時間制限をどうやって行なっているんだろうと思いつつ、試してみることにします。
proxy_authentification_handler の設定
クライアントはどうにかして、HTTPヘッダにいくつかのパラメータを設定します。
- X-Auth-CouchDB-UserName - 'admin'や'user1'といったBasic認証と同じようなID名
- X-Auth-CouchDB-Roles - Basic認証でいうところの'admin'です
- X-Auth-CouchDB-Token - secret と X-Auth-CouchDB-UserNameを連結した文字列のSHA1 Message Digest
対応するlocal.iniの[couch_httpd_auth]に設定するパラメータは次のとおりです。
- (必須)proxy_use_secret - "true"の時にX-Auth-CouchDB-Tokenの照合を行ない、それ以外の値であれば無条件にX-Auth-CouchDB-UserNameのUser名とX-Auth-CouchDB-Rolesのロールを設定
- (必須)secret - X-Auth-CouchDB-Tokenを生成に使う文字列
- x_auth_username - X-Auth-CouchDB-UserNameの別名を定義
- x_auth_roles - X-Auth-CouchDB-Rolesの別名を定義
- x_auth_token - X-Auth-CouchDB-Tokenの別名を定義
サーバ側の設定作業
まずは default.ini に、proxy_authentification_handler を設定します。
authentication_handlers = {couch_httpd_auth, proxy_authentification_handler}
次に local.ini に、必須な2つのフィールドを追加します。
[couch_httpd_auth]
secret = 329435e5e66be809a656af105f42401e
proxy_use_secret = true
これで一応はサーバ側の準備はできましたが、問題はクライアントの準備です。
クライアント側の設定作業
さきほどのcouchdb.rbにさらに変更を加えることにしました。
--- couchdb.rb.orig 2010-11-10 20:44:14.000000000 +0900
+++ couchdb.rb 2010-11-10 20:45:04.000000000 +0900
@@ -33,6 +33,9 @@
def request(req)
req.basic_auth @options['user'], @options['password'] if @options.kind_of?(Hash) and @options.has_key?('user') and @options.has_key?('password')
+ req["X-Auth-CouchDB-UserName"] = @options['proxy_auth_user'] if @options.kind_of?(Hash) and @options.has_key?('proxy_auth_user')
+ req["X-Auth-CouchDB-Roles"] = @options['proxy_auth_roles'] if @options.kind_of?(Hash) and @options.has_key?('proxy_auth_roles')
+ req["X-Auth-CouchDB-Token"] = @options['proxy_auth_token'] if @options.kind_of?(Hash) and @options.has_key?('proxy_auth_token')
res = Net::HTTP.start(@host, @port) { |http|http.request(req) }
unless res.kind_of?(Net::HTTPSuccess)
handle_error(req, res)
--- main.rb.orig 2010-11-10 20:43:54.000000000 +0900
+++ main.rb 2010-11-10 20:27:55.000000000 +0900
@@ -8,7 +8,9 @@
require 'json'
csv = GenCSVText.new
-couch = Couch::Server.new("127.0.0.1","5984",{'user'=>'user1', 'password'=>'ab5e29d1'})
+couch = Couch::Server.new("127.0.0.1","5984",{'user'=>'user1', 'password'=>'ab5e29d1',
+ :proxy_auth_user => "user1", :proxy_auth_roles => "_admin,_users",
+ :proxy_auth_token => "d4c3b0fd10bed9642fb5bbfcc0203ca27c707300" })
10.times do |doc_id|
doc_url = "/csv/qa." + doc_id.to_s
res = nil
実際に使う段階になると、 proxy_auth_token の生成方法をどうしようかなと思いました。
erlを使って文字列を生成する方法は次のようになります。
couch_util.erl か couch_util.beam のあるディレクトリ(今回は ~/apache-couchdb-1.0.1/src/couchdb/)をpathに追加するため、 -paオプションに渡しています。 ファイルが存在すれば、/usr/lib/couchdb/erlang/lib/couch-0.10.0/ebin などでも構いません。
$ erl -pa ~/apache-couchdb-1.0.1/src/couchdb/ 1> nl(couch_util). 2> nl(crypto). 3> crypto:start(). 4> D = <<"user1">>. 5> KK = <<"329435e5e66be809a656af105f42401e">>. 6> couch_util:to_hex(crypto:sha_mac(KK,D)).
最後のコマンドを入力すると、この場合は "d4c3b0fd10bed9642fb5bbfcc0203ca27c707300" になります。
改造したcouchdb.rbを利用するmain.rbの全体は、次のようになっています。
#!/usr/local/bin/ruby1.9 -I.
# -*- encoding: UTF-8 -*-
$:.unshift File.dirname($0)
require 'couchdb'
require 'json'
couch = Couch::Server.new("127.0.0.1","5984",{:proxy_auth_user => "user1",
:proxy_auth_roles => "_admin",
:proxy_auth_token => "d4c3b0fd10bed9642fb5bbfcc0203ca27c707300" })
res = couch.get("/_all_dbs")
p JSON.parse(res.body)
2010/11/17追記:
このスクリプトとcouchdb.rbは同じディレクトリにあると仮定しています。
couchdb.rbがカレントディレクトリにない場所からパスを指定してスクリプトを起動した場合に、動かないため$:.unshift "."
だった記述を修正しました。
パスワードに相当する情報はtokenに何も反映されませんから、MACの強度にだけ依存しています。 せめてMACに与えるSecretにtoken以外のユーザ毎に変化する何かも加えられれば良かったんですけどね。
全体で一つのtokenに依存していますから、頻繁にtokenを入れ替えない限りはちょっと使いたくない感じのものだという事はわかりました。
ちなみに、WebブラウザのProxyに指定してX-CouchDB-Auth-*ヘッダを追加するProxyサーバのコードも載せておきます。
#!/usr/bin/ruby
require "socket"
require "uri"
gs = TCPServer.open("localhost", 8880)
while TRUE
Thread.start(gs.accept) do |s|
## prepare and modify request header part
remote = nil
begin
h = Array.new
while (l = s.gets)
break if l =~ /^\r\n|^\n/
if l =~ /^Keep-Alive:/
## ignore the keep-alive header
elsif l =~ /Proxy-Connection:/
## remove the proxy-connection header because of no keep-alive support.
h << "Connection: close\r\n"
elsif l =~ /Auth/
p l
elsif l =~ /^Cookie/
p l
else
h << l
end
end
h << "X-Auth-CouchDB-UserName: user1\r\n"
h << "X-Auth-CouchDB-Roles: _admin\r\n"
h << "X-Auth-CouchDB-Token: d4c3b0fd10bed9642fb5bbfcc0203ca27c707300\r\n"
h << "\r\n"
## override first line of header
hp, huri, hv = h[0].split(/\s+/)
raise "wrong request header" if hp.nil? or hv.nil?
uri = URI.parse(huri)
p huri
h[0] = "#{hp} #{uri.path} #{hv}\r\n"
## open endpoint
remote = TCPSocket.open(uri.host, uri.port)
## send header info
h.each {|i|
remote.puts i
}
## get response
while(l = remote.gets)
s.puts l
break if l =~ /^\r\n|^\n/
end
## get body
while (l = remote.gets)
s.puts l
end
ensure
remote.close
s.close
end
end
end
このスクリプトを走らせた後に、WebブラウザのProxy設定で ホスト名"127.0.0.1", ポート番号"8880"を指定してアクセスすればCouchDBのWebフロントエンド Futon にアクセスできます。
このProxyを経由して他のサーバにアクセスに行くと、余計な認証情報をばらまく事になるので、URIの解析を始めに行なってh << "X-Auth-CouchDB-UserName: user1\r\n" if uri.port == "5984"
ぐらいしておくと安心かもしれません。
proxy_authentification_handler を使った感想
X-Auth-CouchDB-*のヘッダを付与しないリクエストを送信する一般ユーザでもDBの参照は可能なままです。
これは handler のコードが次のようになっていて、実際の処理を行なうproxy_auth_user関数がnilを返した時には拒否をせずに現状のReqコンテキストを返すことに起因しているようです。
proxy_authentification_handler(Req) ->
case proxy_auth_user(Req) of
nil -> Req;
Req2 -> Req2
end.
Proxyを経由しないアクセスを拒否するのであれば、Reqを返さずにnil -> throw({unauthorized, <<"token is incorrect.">>});;
ぐらいにすると、{"error":"unauthorized","reason":"token is incorrect."} という返答が返るようになります。
cookie_authentication_handlerは良さそうだけれど、とりあえずは default_authentication_handler を設定して、proxy_authentification_handler が失敗した場合でもBasic認証がかかるようにしておきました。
次は cookie_authentication_handler について、 Secure Cookie Authentication for CouchDBをまずは読んでみようとしています。
ファイルレベルのパーミッションについて
ファイルのパーミッションは、個々の作業の中でもいろいろ出てきたので、まとめておきたいと思います。
とりあえずUbuntuで標準的に導入されるパッケージの設定では/var/lib/couchdbのディレクトリレベルでパーミッションを0750になっています。
認証を行なう場合は default.ini や local.ini あるいは、DB自体に何らかの情報を含む可能性があるので、それらのディレクトリの所有者/グループを root:couchdb に変更し、サーバが書き込む必要のないところは、g-wX,o-rwX、必要なところは g+wX,o-rwX ぐらいをchmodで設定するのがベストです。
ただ、それでも新規に作成されるDBなどのファイルは644で、バックアップファイルの扱いなど懸念があります。 起動スクリプトに'.'で読み込まれる /etc/default/couchdb にumask 027の一行を加えておきました。
Ubuntu 10.04 LTSで使っているCouchDB用には、次のような設定をしています。
$ sudo chmod -R o-rwX /var/lib/couchdb $ sudo chmod g+s /var/lib/couchdb /var/lib/couchdb/0.10.0 $ sudo chown -R root:couchdb /etc/couchdb $ sudo chmod -R g-w,g+rX,o-rwX /etc/couchdb $ sudo chmod g+s /etc/couchdb $ echo umask 027 | sudo tee -a /etc/default/couchdb
default.ini, local.ini をcouchdbプロセスが書き込めるようにするかは、必要性とポリシー次第かなと思います。
Linuxなら /etc/login.defs 辺りで、UMASK 027 を設定するべきかなとも思います。
まとめ
微妙に綴りの違う、*_authentication_handler と proxy_authenti fication_handler がまぎらわしい。
CouchDBは良さそうだけど、エラーメッセージ系はもうちょっと改善してほしいところ。
冗談ではないけれど、もう少しまじめに書くと、cookie認証にあるようなtimestampの概念はProxy認証では導入されていませんでした。 これは必要じゃないかなと感じてcookie認証についてのドキュメントとコードを読み始めています。
Proxy認証は自分で認証モジュールを作りたい場合の雛型コードとしてはシンプルで良いけれど、実用的なものではなさそうだというのが今のところの印象です。
経緯を調べてみたんですが、みつけられなかったんですよね。
0 件のコメント:
コメントを投稿