pixiv insideは移転しました! ≫ https://inside.pixiv.blog/

gateを使って手軽に認証を導入する

ISUCON4 で準優勝した @catatsuy です。
賞金の使い道はまだ考えていません。

f:id:catatsuy:20141114192037p:plain

ところでピクシブ株式会社では冬インターンをやります!

エンジニア向け pixiv開発のbugリストからの脱出!エンジニア職インターン - ピクシブ株式会社 採用サイト

ISUCON4 の予選問題を解くだけでインターンに参加できるチャンスなのでぜひ挑戦してみてください!!1

pixiv/intern2014w

この記事は当初はアドベントカレンダーの記事にする予定でしたが,今が旬だと圧力をかけられたので今公開します。

なおこの記事は ピクシブ株式会社 Advent Calendar 2014 - Qiita-17 日目の記事です。

今までの社内ツールの認証は各サーバーに設定されていたために退職者などの対応が非常に大変でした。そこで最近のピクシブ株式会社では typester/gate を導入することで社内ツールに Google Apps 認証を導入しました。

gate については以下の記事が分かりやすいと思います。

Google認証なリバースプロクシ&静的コンテンツ配信サーバー「gate」 - unknownplace.org

gate は簡単に説明すると OAuth 認証を行い,権限がある人に対してだけプロキシサーバーとして機能するプロキシサーバーです。OAuth 認証には Google 認証が使えるので Google Apps を使っていれば特定のドメインの Google アカウントのみに権限を与えることで簡単に社員だけがアクセスできるプロキシサーバーを用意することができます。現在ピクシブ株式会社では元々社内ツールとして使っていたいくつかのツールを gate 経由で使うようにしましたので今回は gate の使い方と実際に gate を導入する際の注意点などを紹介します。

gateの設定

今回は Google アカウントでの設定を紹介します。

Google Developers Console にアクセスして以下の手順で Client ID を生成します。

f:id:catatsuy:20141114191435p:plain

f:id:catatsuy:20141114191448p:plain

f:id:catatsuy:20141114191500p:plain

デフォルトは HTTPS の URL を要求されます。

今回ピクシブ株式会社では gate 導入を機に社内ツールを HTTPS で通信することにしました。

gate はすぐに使えるバイナリが配布されていますが,Golang 製のアプリケーションですので自分でビルドしても大して手間はかかりません。ただ gate をデーモンとして起動する必要があるのでデーモン化する手段を何か用意する必要があります。それには supervisord などを使ってもいいでしょうし,基本的に社内でよく使われているものを使った方が混乱がないと思います。ピクシブ株式会社ではほぼ全てのサーバーで Debian を使用しているので start-stop-daemon を使って自前で init.d スクリプトを用意して monit で監視するようにしました。その際に gate のログを見れるように以下の記事のようなスクリプトを用意しました。

Linux - 標準出力にログを吐くアプリケーションのデーモンを start-stop-daemon で立てる - Qiita

そしてフロントに nginx を立てて gate に proxy_pass するようにします。

今回はフロントでは HTTPS 通信を行うようにしているため少し複雑ですがおおむね以下の様な設定を行います。

server {
    listen 443 ssl;
    server_name gate.example.com;

    # sslの設定
    include include-confs/ssl.conf;

    location / {
        satisfy any;

        # officeのIP
        allow ***.***.***.***;
        deny all;

        auth_basic "Please enter user / password";
        auth_basic_user_file htpasswd;

        proxy_http_version 1.1;
        proxy_pass http://127.0.0.1:9999;
    }

    error_log /var/log/nginx/gate.example.com_error.log error;
    access_log /var/log/nginx/gate.example.com_access.log;
}

satisfy any を使うことで特定の IP アドレスからのアクセスは許可をしてそれ以外は BASIC 認証を要求するという設定ができます。proxy_pass する先は gate を立てたところに向けてください。もちろんフロントサーバーと同じである必要はないですし,ポート番号も config.yml から設定可能です。

既存の社内ツールをgate経由で通信するように

ピクシブ株式会社では一つのサーバー上に複数のアプリケーションが起動していることは非常によくあるためにヘッダーにホスト名を付けた上でプロキシしなければなりません。gate は v0.4 以降から vhost に対応したためにプロキシ先の host 名を指定することができます。

また公式の README には strip_path を yes に設定したものしか書かれていませんがピクシブ株式会社では今のところすべて strip_path をすべて no にして運用しています。他社さんがどのような設定をしているのかよくわかりませんが,ピクシブ株式会社の設定をいくつかご紹介します。

設定例

具体的な設定例を見たいという声もありそうなのでまず設定例を紹介します。

# address to bind
address: 127.0.0.1:9999

auth:
  session:
    # authentication key for cookie store
    key: **************

  info:
    # oauth2 provider name (`google` or `github`)
    service: google
    # your app keys for the service
    client_id: **********.apps.googleusercontent.com
    client_secret: *********
    # your app redirect_url for the service: if the service is Google, path is always "/oauth2callback"
    redirect_url: https://gate.example.com/oauth2callback

restrictions:
  - pixiv.co.jp # domain of your Google App (Google)

# document root for static files
htdocs: ./public/

# proxy definitions
proxy:
  - path: /admin
    host: admin.example.com
    dest: http://admin.example.com
    strip_path: no
  - path: /deploy
    dest: http://192.168.*.*:9000
    strip_path: no

これだけではありませんが,このような設定で社内ツールをアプリケーション側の変更をせずに簡単に Google Apps 認証に対応させることができました。

以下では具体的にどうしてこのような設定にしたのか説明します。

PHP製社内ツール

社内ツールは PHP 製でアプリケーション内で $_SERVER['SCRIPT_NAME'] が URL のパスに一致することが前提となっているコードがいたるところに存在しました。またサーバー内部で叩くための API も提供しているため,gate を通らないアクセスも考慮に入れる必要がありました。このアプリケーションは DocumentRoot がアプリケーション内の htdocs ディレクトリが指定されており,その中でさらにディレクトリが切られていて(仮に admin とします)その中でアプリケーションや静的ファイルを配信しています。そのため内向き用のドメインを config.yml の host と dest に指定して strip_path を no にするという設定をしました。

これで gate 経由で叩かれた時も内部で叩いた時も URL のパスには変更がなくアプリケーションの変更をせずに移行できました。strip_path を yes にすると gate 側で指定した path をプロキシ先に渡す際に削ぎ落としてしまいます。今回のアプリケーションではそれは都合が悪いので strip_path を no にしています。

デプロイツール pploy

現在社内では pploy というデプロイツールを使用してアプリケーションのデプロイを行っています。

edvakf/pploy

pploy は Scala でよく使われている Play Framework を使用しています。そこで Play Framework の application.context 機能を使って gate に対応しました。起動時のオプションに -Dapplication.context=/deploy/ を指定して,これもまた strip_path を no にすることで自動生成しているリンクや静的ファイルの配信に gate を通しても問題が起こらないようになっています。

自前でビルドする

社内でこういったツールを導入すると微妙な挙動が気になることがあります。オープンソースなので自分で直してしまいたくなります。ということで現在ピクシブ株式会社では私が独自にビルドした gate が使われています。そこで私が独自に行なっている工夫を紹介します。

/が無ければリダイレクトする

gate はアプリケーションごとに path を指定して名前空間を作るイメージですが,path 名の後に / を忘れてアクセスをしてしまうことがよくあります。そんな時に / を付けた URL にリダイレクトしてくれる機能を付けました。いらないという方のために path の設定の最後に / を付けていたら発動しないようになっています。この機能はすでにプルリクエストを行い本家にマージされています。

Pull Request #11 · typester/gate

再認証後のリダイレクト先のGETクエリが無視されてしまう

GET クエリ付きの URL を見ようとして再認証になった場合,再認証後にちゃんと元の URL にリダイレクトされないバグがありました。これは gate ではなく内部で使用している martini-contrib/oauth2 のバグみたいなので自前で修正しています。

修正内容は以下のプルリクエストの通り一行直しただけです。

Pull Request #39 · martini-contrib/oauth2

これで対応することができます。

まとめ

gate の使い方を紹介しました。

手軽に Google Apps 認証で社内ツールにアクセスできるようにして仕事効率を上げていきましょう!!