Edited at

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

More than 1 year has passed since last update.

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

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

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

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

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

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


認証の流れ

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


  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: