ngx_mrubyを使ってRedmineのユーザでBasic認証してみるというお話。
ngx_mrubyのビルド方法や前提となるパッケージのインストールは割愛しています。
mruby
必要なmrbgem
ngx_mrubyのbuild_config.rbに以下を追加してビルドします。
mysql-develやらmariadb-develやらが必要です。
conf.gem :github => 'mattn/mruby-mysql' # コメントアウトを外す
conf.gem :github => 'mattn/mruby-base64' # WWW-Authenticateヘッダのデコード用
conf.gem :github => 'pbosetti/mruby-merb' # database.ymlのテンプレート解釈用
conf.gem :github => 'AndrewBelt/mruby-yaml' # database.ymlのパース用
mruby_init_workerハンドラ
Worker起動時にDBコネクションをプーリングしておきます。
REDMINE_HOME = ENV['REDMINE_HOME'] || '/var/lib/redmine'
RAILS_ENV = ENV['RAILS_ENV'] || 'production'
database_yml = File.read("#{REDMINE_HOME}/config/database.yml")
# database.ymlの内容をparse
merb = Merb.new
database_settings = YAML.load(merb.convert(database_yml))
config = database_settings[RAILS_ENV]
# DB接続をUserDataに格納 (同一Woker内で使い回す)
u = Userdata.new("dbconn_#{Process.pid}")
u.connection = MySQL::Database.new(
:host => config["host"],
:port => config["port"],
:database => config["database"],
:username => config["username"],
:password => config["password"],
:encoding => config["encoding"]||'utf8')
mruby_exit_workerハンドラ
DBコネクションを破棄します。
# UserDataに保持していたDB接続を閉じる
Userdata.new("dbconn_#{Process.pid}").connection.close
本題のmruby_access_handlerハンドラ
Redmineのユーザテーブルを参照してパスワード認証を行います。
こちらの記事を大いに参考にさせていただきました。
ngx_mruby で Basic 認証を実装する
QUERY = "select salt,hashed_password from users where type='User' and status = '1' and login=$1"
# Basic authorizetion
def basic_auth
r = Nginx::Request.new
realm = r.var.realm
if r.headers_in["Authorization"].nil?
r.headers_out["WWW-Authenticate"] = %Q(Basic realm="#{realm}")
return Nginx::HTTP_UNAUTHORIZED
end
auth = r.headers_in["Authorization"]
params = Base64::decode(auth.split(" ")[1]).split(":")
id = params[0]
passwd = params[1]
# Proc call
unless yield(id, passwd)
r.headers_out["WWW-Authenticate"] = %Q(Basic realm="#{realm}")
return Nginx::HTTP_UNAUTHORIZED
end
# OK
return Nginx::DECLINED
end
# SHA1 digest
def sha1(clear_text)
Digest::SHA1.hexdigest(clear_text||"")
end
def authenticate(id, passwd)
# UserDataからコネクションを取得
mysql = Userdata.new("dbconn_#{Process.pid}").connection
row = mysql.execute(QUERY, id)
if row.eof?
row.close
return false
end
cols = row.next
salt = cols[0]
hash = cols[1]
row.close
return sha1("#{salt}#{sha1(passwd)}") == hash
end
Nginx.return basic_auth {|id, password|
authenticate id, password
}
Nginx側の設定
# ... 略 ...
http {
mruby_init_worker /path/to/init_worker_hander.rb cache;
mruby_exit_worker /path/to/exit_worker_hander.rb cache;
# ... 略 ...
server {
location / {
set $realm 'Secure Zone';
mruby_access_handler /path/to/basic_auth_by_redmine.rb cache;
}
# ... 以下略 ...
環境変数を設定してみる
Redhat系
# 以下を[Service]セクションに追加
Environment=REDMINE_HOME=/var/lib/redmine
Environment=RAILS_ENV=production
もしくは
REDMINE_HOME=/var/lib/redmine
RAILS_ENV=production
何が嬉しい?
Basic認証に対応した製品(Jenkinsとか)にRedmineのユーザでSSOできます。
ついでにRedmineにredmine_http_authプラグインを導入しておけばログイン画面をスキップできます。
他にも静的ファイルに認証かけたり、SSOの用途でいろいろあるかも。思いつかないけど。
Basic認証なんでセキュリティとか気をつけて下さい。
こちらからは以上です。
[補足] PostgreSQLの場合 (差分のみ)
sudo yum install postgresql-devel
# conf.gem :github => 'mattn/mruby-mysql'
conf.gem :github => 'authorNari/mruby-pg'
u = Userdata.new("dbconn_#{Process.pid}")
u.connection = PG::Connection.new(
:host => config["host"],
:port => config["port"],
:dbname => config["database"],
:user => config["username"],
:password => config["password"])
def authenticate(id, passwd)
pg = Userdata.new("dbconn_#{Process.pid}").connection
res = pg.exec(QUERY, [id])
return false if res.nil? or res.size <= 0
salt = res[0]["salt"]
hash = res[0]["hashed_password"]
return sha1("#{salt}#{sha1(passwd)}") == hash
end
init_workerでdatabase.ymlのadapterを見て分岐してもいいかもしれないけど、過度な抽象化な気がするので割愛。