LoginSignup
16
8

More than 5 years have passed since last update.

有象無象の社内ツールの認証と権限管理をいい感じにする

Last updated at Posted at 2017-12-10

mixiグループ Advent Calendar 2017の10日目の記事です。

XFLAGスタジオでデータ分析基盤の運用を担当している ojima です。

私たちのチームでは分析基盤の一部として、Zeppelinre:dash、内製のダッシュボードなど、データ可視化ツール・分析ツールをいくつか社内向けに提供しています。こういったツールには必ず認証と権限管理の問題がついて回ってきます。

認証や権限管理などを集中管理する場合は LDAP などを使うのが一般的かと思いますが、 LDAP の運用はなかなか辛いものがあります。そこで、私たちのチームでは Google OAuth 2.0 と Google Group を用いて認証・権限管理を行うことにしました。

この記事では、 Zeppelin を例にして認証の流れや設定例を紹介したいと思います。

(Zeppelinは、ブラウザ上でデータの可視化や分析ができるツールです。Jupyter Notebook によく似ていますが、マルチユーザー向けの機能やプラグインの管理画面などが充実しています。)

認証の流れ

nginx と oauth2_proxy を組み合わせて認証部分を組み立てます。

diagram.png

  1. ユーザーが nginx にアクセス
  2. nginx は oauth2_proxy に認証状態を問い合わせる。

    1. 認証済みであれば oauth2_proxy は 202 を返す。
    2. 認証済みでなければ Google OAuth の画面にリダイレクト
    3. 認証に成功すれば 202、そうでばなければ 401 を返す。ここで特定のグループに所属するユーザーだけを許可することも可能。
    4. 認証に成功した場合、ユーザー名をHTTPヘッダにセットして nginx に返す。

    参考: http://lamanotrama.hateblo.jp/entry/2016/01/18/142116

  3. 認証が成功していれば nginx は zeppelin にアクセスする。その際に X-Remote-User ヘッダにユーザー情報を付加する。

  4. Zeppelin は X-Remote-User ヘッダにセットされているユーザーとしてログインした状態でリクエストを処理する。

注意

  • 上の構成では、Zeppelin は X-Remote-User がセットされていれば、無条件にログインしているものとして扱います。nginx 以外からはアクセスできないようにしておかないといけません。
  • nginx 無しで oauth2_proxy 単体でもプロキシとして使うことができますが、oauth2_proxy は WebSocket に対応していません。Zeppelin では WebSocket が必要なため上のような構成にしています。

oauth2_proxy をちょっと拡張

基本的には以上の流れで認証を行うのですが、本家の bitly/oauth2_proxy では少し機能が足りなかったので、 ojima-h/oauth2_proxy にフォークしてちょっとだけ機能を追加しました。

変更したのは以下の点です:

  • 認証に成功した際に、グループ情報もHTTPヘッダに含めるようにしました。これにより、Zeppelin にグループ情報を渡すことができ、Zepplin 側でグループに応じてより細かな権限の制御ができるようになります。
  • bitly/oauth2_proxy では、特定のグループにアクセスを制限するために、 Google Directory API を使っており、ドメインの管理者権限が必要でした。そこで、Google Apps Script を用いてグループ情報を取得できるようにし、管理者権限がなくても使えるようにしました。

グループ情報がHTTPヘッダを通して Zeppelin に渡ることで、Google Group ごとに Zeppelin の権限を割り振ることができるわけです。

このあたりの機能が必要であれば ojima-h/oauth2_proxy を試してもらえると良いかもしれません。

設定例

フォーク版の ojima-h/oauth2_proxy を使う場合の設定例を以下で紹介します。

Google Apps Script 作成

function listMyGroups() {
  var groups = GroupsApp.getGroups();

  return groups.map(function (group) {
    var group_name = group.getEmail();
    Logger.log(group_name);
    return group_name;
  });
}

このようなスクリプトを作成し、Execution API で外部から呼び出せるようにします。
Execution API の設定は以下を参考にしてください。

oauth2_proxy の設定

./oauth2_proxy \
   --email-domain="example.com"  \
   --upstream=http://127.0.0.1:8080/ \
   --cookie-secret="secret"\
   --client-id="CLIENT_ID"\
   --client-secret="CLIENT_SECRET"\
   --redirect-url=http://your-host/oauth2/callback \
   --scope="profile email https://www.googleapis.com/auth/groups" \
   --google-group="your-group@example.com,admin-group@example.com" \
   --google-script-id="SCRIPT_ID" \
   --google-script-function-name="FUNCTION_NAME" \
   --set-xauthrequest

CLIENT_ID や SCRIPT_ID は、 Execution API の設定時に取得したものを使います。

--google-group に2つのグループを指定しています。ユーザーがこれら2つのグループのいずれかに所属していれば、認証は成功し、これら2つのグループのうち実際にユーザーが所属しているグループの名前がHTTPヘッダにセットされます。

--set-xauthrequest を指定することで、nginx とユーザー情報などをやりとりできるようになります。

nginx の設定

server {
    listen       80;
    server_name  localhost;

    location /oauth2/ {
        proxy_pass       OAUTH2_PROXY_URL;
        proxy_set_header Host                    $host;
        proxy_set_header X-Real-IP               $remote_addr;
        proxy_set_header X-Scheme                $scheme;
        proxy_set_header X-Auth-Request-Redirect $request_uri;
    }

    location / {
        auth_request /oauth2/auth;
        error_page 401 = /oauth2/sign_in;

        # pass information via X-Remote-User and X-Remote-Email headers to backend,
        # requires running with --set-xauthrequest flag
        auth_request_set $user           $upstream_http_x_auth_request_user;
        auth_request_set $groups         $upstream_http_x_auth_request_groups; # フォーク版のみ
        proxy_set_header X-Remote-User   $user;
        proxy_set_header X-Remote-Groups $groups; # フォーク版のみ

        # if you enabled --cookie-refresh, this is needed for it to work with auth_request
        auth_request_set $auth_cookie $upstream_http_set_cookie;
        add_header Set-Cookie $auth_cookie;

        proxy_pass ZEPPELIN_URL;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;
        proxy_redirect off;
    }

    location /ws {  # For websocket support
        proxy_pass ZEPPELIN_URL;
        proxy_http_version 1.1;
        proxy_set_header Upgrade websocket;
        proxy_set_header Connection upgrade;
        proxy_read_timeout 86400;
    }

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
}

参考:

Zeppelin の設定 ~ HTTP ヘッダを用いたログインをサポートする

Zeppelin はデフォルトでは X-Remote-User などの HTTP ヘッダを用いたログインをサポートしていません。
しかし、Zeppelin の認証部分には Apache Shiro という認証フレームワークが用いられており、いくつかプラグインが提供されています。

今回は shiro-remote-user というプラグインを使います。

  1. shiro-remote-user を clone
  2. ビルド: mvn package
  3. target/shiro-remote-user-0.0.1-SNAPSHOT.jar を、Zeppelin の lib/ 以下にコピー

Zeppelin の設定 ~ 認証方法とグループごとの権限を設定

conf/shiro.conf

[main]
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager

securityManager.sessionManager = $sessionManager
# 86,400,000 milliseconds = 24 hour
securityManager.sessionManager.globalSessionTimeout = 86400000
shiro.loginUrl = /api/login

remoteUserFilter = com.yahoo.shiro.remoteuser.filter.RemoteUserAuthenticationFilter
remoteUserFilter.remoteUserHeaderName = X-Remote-User
remoteUserFilter.remoteRolesHeaderName = X-Remote-Groups
remoteUserFilter.remoteRolesSeparator = ","
remoteUserRealm = com.yahoo.shiro.remoteuser.realm.RemoteUserRealm
securityManager.realms = $remoteUserRealm


[urls]
# This section is used for url-based security.
# You can secure interpreter, configuration and credential information by urls. Comment or uncomment the below urls that you want to hide.
# anon means the access is anonymous.
# authc means Form based Auth Security
# To enfore security, comment the line below and uncomment the next one
/api/version = anon
/api/interpreter/** = authc, roles[admin-group@example.com]
/api/configurations/** = authc, roles[admin-group@example.com]
/api/credential/** = authc, roles[admin-group@example.com]
/** = remoteUserFilter

ここでは

  • ユーザー情報を X-Remote-User から取得
  • グループ情報を X-Remote-Groups から取得
  • interpreter や credential などの API には admin-group のみアクセスを許可

などを設定しています。

まとめ

以上 outh2_proxy を用いて認証と権限管理を行う方法を紹介してみました。
かなり Zeppelin 寄りの説明になりましたが、もちろん幅広いWebアプリケーションに適用できます。
また、 oauth2_proxy は Google 以外の認証もサポートしており、Github などを利用することも可能です。

以上の方法では、アプリケーション側で認証機能を持つ必要がなく、ユーザー情報やグループ情報はHTTPヘッダから取得すれば良いだけなので、認証が必要なツールを自前で作る際もだいぶ手間が省けるのではないでしょうか。
nginx や oauth2_proxy は docker 上で実行してしまうのもおすすめです。

それでは良いお年を:christmas_tree:

16
8
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
16
8