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:

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.