LoginSignup
12
11

More than 5 years have passed since last update.

[mruby] RedmineのユーザでBasic認証

Last updated at Posted at 2015-12-03

ngx_mrubyを使ってRedmineのユーザでBasic認証してみるというお話。
ngx_mrubyのビルド方法や前提となるパッケージのインストールは割愛しています。

mruby

必要なmrbgem

ngx_mrubyのbuild_config.rbに以下を追加してビルドします。
mysql-develやらmariadb-develやらが必要です。

build_config.rb
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コネクションをプーリングしておきます。

init_worker_hander.rb
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コネクションを破棄します。

exit_worker_hander.rb
# UserDataに保持していたDB接続を閉じる
Userdata.new("dbconn_#{Process.pid}").connection.close

本題のmruby_access_handlerハンドラ

Redmineのユーザテーブルを参照してパスワード認証を行います。

こちらの記事を大いに参考にさせていただきました。
ngx_mruby で Basic 認証を実装する

basic_auth_by_redmine.rb
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側の設定

/etc/nginx/nginx.conf
# ... 略 ...
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系

/usr/lib/systemd/system/nginx.service
# 以下を[Service]セクションに追加
Environment=REDMINE_HOME=/var/lib/redmine
Environment=RAILS_ENV=production

もしくは

/etc/default/nginx
REDMINE_HOME=/var/lib/redmine
RAILS_ENV=production

何が嬉しい?

Basic認証に対応した製品(Jenkinsとか)にRedmineのユーザでSSOできます。
ついでにRedmineにredmine_http_authプラグインを導入しておけばログイン画面をスキップできます。
他にも静的ファイルに認証かけたり、SSOの用途でいろいろあるかも。思いつかないけど。
Basic認証なんでセキュリティとか気をつけて下さい。

こちらからは以上です。

[補足] PostgreSQLの場合 (差分のみ)

sudo yum install postgresql-devel
build_config.rb
# conf.gem :github => 'mattn/mruby-mysql'
conf.gem :github => 'authorNari/mruby-pg'
init_worker_hander.rb
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"])
basic_auth_by_redmine.rb
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を見て分岐してもいいかもしれないけど、過度な抽象化な気がするので割愛。

12
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
11