ククログ

株式会社クリアコード > ククログ > REMOTE_USERを使ってRedmineで自動ログイン

REMOTE_USERを使ってRedmineで自動ログイン

数カ月ぶりにRedmineのプラグインを開発した阿部です。

Redmine Plugin Auth Remote User」を開発しました。 これはREMOTE_USERX-Forwarded-User)をRedmineのログインIDと見なして自動ログインするプラグインです。 いわゆるシングルサインオン(SSO: Single Sign On)ができるようになるプラグインです。

本記事ではそのプラグインの紹介と実装のポイントを説明します。

本題の前に注意

「Redmine Plugin Auth Remote User」はリバースプロキシ + Redmineの構成での利用を想定しています。 リバースプロキシで設定されたREMOTE_USER環境変数が X-Forwarded-UserヘッダとしてRedmineに渡されるような構成です。

このプラグインではRedmineで受け取ったX-Forwarded-Userヘッダの値を、RedmineのログインIDと見なして自動ログインをします。

Redmineに不正なX-Forwarded-Userヘッダがリクエストされる環境で利用すると、セキュリティリスクがあることをご承知おきください。

Redmine Plugin Auth Remote User の紹介

機能説明

「本題の前に注意」でほぼ説明していまいましたが、X-Forwarded-Userヘッダの値をRedmineのログインIDと見なして自動ログインをします。 プラグインをインストールするとその機能が有効になり、X-Forwarded-Userヘッダの値をRedmineのログインIDが一致していれば自動でログインするようになります。

インストールコマンド:

cd redmine
git clone https://gitlab.com/redmine-plugin-auth-remote-user/redmine-plugin-auth-remote-user.git plugins/auth_remote_user

設定できること

X-Forwarded-Userヘッダの値をRedmineのログインIDと見なして自動ログインをしますが、RedmineのログインIDと一致しないこともあります。 例えばActive DirectoryのID(domain\username)で考えると、RedmineのログインIDとして採用されるのは username の部分のみのことが多いはずです。 usernameのみがログインIDの場合は、domain\usernameでRedmineにログインを試みても当然失敗します。

上述のケースにも対応できるようにX-Forwarded-Userヘッダの一部を変更できる機能も搭載しています。 具体的には次の2つの機能があります。

  1. Active DirectoryのIDであるdomain\usernameからdomain\の部分のみ削除
  2. 正規表現を使って置換

「本題の前に注意」と同様の注意ですが、X-Forwarded-Userヘッダの値を変更することで、予期せぬログインが発生するリスクがあることを理解してご利用ください。

設定画面のキャプチャ:

スクリーンショット:設定画面

また、プラグインの設定画面でプラグインの有効・無効を設定することもできます。

1. domain\usernameからdomain\の部分のみ削除

機能名の通りdomain\usernameからdomain\の部分のみ削除をします。 設定画面でチェックを入れると有効になります。

Active DirectoryのIDの場合はこの機能を利用するのがオススメです。

2. 正規表現を使って置換

RubyのString#gsubで置換します。 設定画面で「REMOTE_USERの一部を置換する」を有効にしてから設定します。

上述のdomain\usernameの場合は以下の設定で同様の置換ができます。

  • 置き換える文字列の正規表現: \Adomain\\
  • 置き換え後文字列: (空欄)

この設定で置換が行われるとdomain\usernamedomain\の部分が削除(空文字に置換)され、username のみがログインIDとして使用されます。

設定を誤ると予期せぬログインが発生するリスクがありますので、機能を理解した上、入念な検証をした後にご利用ください。

補足: プラグインの有効・無効

シンプルにこのプラグイン(= 自動ログイン)の有効・無効が設定画面で設定できます。

このプラグイン(= 自動ログイン)が有効だとX-Forwarded-Userの情報で自動ログインをするため、例えば「検証用の別アカウントを使った検証」などが実施しにくくなります。そういった場合に一時的に無効にして検証する、という使い方もできます。

実装のポイント

実装方針

RedmineのApplicationControllerを確認するとtry_to_autologinというメソッドがあります。 メソッド名から想像するに、今回やりたい自動ログインをしてそうです。

コードを見るとCookieの値で自動ログインを試みるメソッドでした。

app/controllers/application_controller.rb:

  def try_to_autologin
    if cookies[autologin_cookie_name] && Setting.autologin?
      # auto-login feature starts a new session
      user = User.try_to_autologin(cookies[autologin_cookie_name])
      if user
        reset_session
        start_user_session(user)
      end
      user
    end
  end

Cookieの値を使うか、X-Forwarded-Userの値を使うか、の違いだけで同様の処理なので try_to_autologin を拡張する方針で修正することにしました。

実装の説明

肝となるauto_loginable.rbのコードを掲示して説明します。 コード中に解説コメントを入れる形で説明します。(このコメントはコミットしてあるコードには入っていません、あくまでククログでの説明用です。)

lib/auth_remote_user/auto_loginable.rb:

module AuthRemoteUser
  module AutoLoginable
    def try_to_autologin
      # 次の2行で「Cookieの値で自動ログイン」を処理しています。
      user = super
      return user if user

      # `X-Forwarded-User`の値をチェック
      remote_user = request.env["HTTP_X_FORWARDED_USER"]
      return nil unless remote_user

      # 本記事での説明は割愛しますが、`AuthRemoteUser::Normalizer` が設定を元に
      # `X-Forwarded-User`の値を置換します。
      normalizer = Normalizer.new(Setting.plugin_auth_remote_user)

      # 設定の通りに置換された`X-Forwarded-User`の値で有効なユーザがいるか探します。
      user = User.active.find_by_login(
        normalizer.normalize(remote_user))
      return nil unless user

      # 有効なユーザが見つかればログイン処理
      reset_session
      start_user_session(user)
      user.update_last_login_on!
      user
    end
  end
end

# `ApplicationController`に`prepend`して`try_to_autologin`を拡張
ApplicationController.prepend(AuthRemoteUser::AutoLoginable)

まとめ

REMOTE_USERX-Forwarded-User)をRedmineのログインIDと見なして自動ログインするプラグインの紹介と実装ポイントの説明でした。

リバースプロキシ + Redmineの構成を想定していると書きましたが、IISでリバースプロキシをする場合はひと手間が必要です。 IIS(リバースプロキシ) + Redmineの構成で利用する方法は別記事で紹介します! (追記: 紹介しました。)

なお、この機能はクリアコードが提供しているRedmineサポート契約の中で開発しました。 クリアコードが提供するRedmineサポートではお客さんが必要としている機能を理解した上で 汎用的なプラグインとして対応することが多いです。 その際、プラグインは自由なソフトウェアとして開発し広く公開します。 汎用的な機能のため、お客さんの機密情報などが含まれることがなく、 お客さんにとってデメリットはありません。 また他のお客さんでも使うことになったら、メンテナンス費用を 按分できるコストメリットもあります。

プラグインではなくRedmine本体を改良した方がよさそうな場合もあります。 その時はお客さんが使っているRedmineにパッチを当ててもらうのではなく、 Redmine本体に機能を提案してRedmine本体に取り込んでもらえるようにします。 自分たちでパッチを当てるよりもRedmine本体に入っていた方がアップデートが楽になり、 メンテナンスコストが下がるからです。 ただ、Redmine本体の開発をコントロールすることはできないので、 一時的にプラグインで対処したりパッチを当ててもらうということはあります。

そのようなRedmineサポートがよい方はお問い合わせフォームよりご連絡ください。