feature image

2016年12月25日 | ブログ記事

署名付きクッキー / Omniauth Strategyを書く / GitLabで独自認証

メリークリスマス!
traPの鯖管kazです。

この記事はアドベントカレンダー2016 25日目の記事……なんですが、
「曲がりなりにも東工大の技術系サークルであるtraPのアドベントカレンダー企画にしては技術系の記事少なくない!?」
みたいな声が聞こえたり聞こえなかったりしたので、技術ネタです。

シングル・サイン・オンにあこがれて

ちょっとだけ、前座にお付き合いください。

GitLab

うちのサークルでは、複数人開発プロジェクトの全てでgitを使っているんですが、
そのリモートリポジトリをホスティングするためにGitLabのCommunityEditionを使っています。

ほかにも

これはちょっと(未来の新入部員に向けた)宣伝というか、どうでもいいんですけど、
他にも部内SNSだとか、ファイル共有用にownCloudだとか、ドキュメント共有用のcrowiだとか、
このブログもそうですし、こんなかんなカンジでいろいろなアプリを運用しています。

あとDockerを使ってミニVPSみたいなことも……とか。

crowiはあんまり有名じゃないOSSですけど、Markdownでwikiを管理できるカンジのアレで、
非常に使い勝手が良いです。オススメ。

アカウント管理

で、こうやってアプリをたくさん運用してるんですが、アプリごとにアカウントを用意してると非常にメンドくないですか?
アプリごとにログインしなくちゃならないし、メールアドレスを変えたら全部変えなくちゃならないし、パスワードはどうしよう?とか。

SSO

そこでシングル・サイン・オンです!
1つのアカウントですべてのサービスにログインできたら嬉しいじゃないですか。
シングル・サイン・オンというのは、1つのアカウントで複数のサービスにログインできる仕組みのことです。

SAML

SAMLというのがありまして、コレは認証情報をXMLでやり取りするための仕様で、
シングル・サイン・オンの実装として用いられます。

SAMLは認証を担当するIdPと、認証情報を利用するSPに分かれています。
SPというのが各アプリで、この人達がIdPに認証を委任するカンジですね。

SAMLのツライところ

SAMLってけっこうニッチな技術っていうか、流行ってないというか。
企業とかそういうトコじゃないと、シングル・サイン・オン自体の需要があんまりないんですかね。

とにかく情報が少ないので色々苦労します。
アプリごとに実装されてる言語が違うわけで、その言語ごとにSAMLの実装を探さないといけないし。
SAMLライブラリがあっても保守されてなくて結局自分でなんとかしなくちゃならなかったり。。。

はじめはOpenAMを使おうかなと思ったんですけどよく分からないのでやめました。
うちのサークルで使ってるSNSは内製なんですが、コレにSAML IdPを自分で実装しました。

とにかくつらい

とにかくつらいのでもうSAML使いたくないです。

SAMLはけっこう複雑なんですけど、なんで複雑なのかって言うと、
異なるドメイン間での認証ができたりいろいろ細かい機能があるからなんですかね。

署名付きクッキー☆

そこでボクがSSOの実装として推したいのが署名付きクッキーです。
クッキーでSSOを実装している例をあまり見ないのですけど、やっぱりコレ何か問題があるのだろうか。
小一時間考えたけどそんなに問題なさそうだった。

どうやって認証するかというと、クッキーにユーザ識別子(要はIDです)をのっけとくだけ!
……これだけだと余裕でCookie書き換えられるし、クソ簡単なCTFかな???ってなってしまうので、
HMACとか非対称暗号で署名を付けます。

認証情報を利用する側で署名検証をして一致してなかったら弾くようにすれば、
鍵を持ってる認証サーバだけが認証情報を発行できるのでなりすましができない!わけです。

うれしさ

SAMLだと、

  1. SPにアクセス → IdPにリダイレクト
  2. IdPにアクセス → SPにリダイレクト
  3. SPにアクセス → ログイン完了

ってカンジで3往復目でやっとログインが完了するんですけど、
クッキーは毎回送ってるので、

  1. SPにアクセス → ログイン完了

というカンジに一発OKです。速い!!!
この要求されてないけど大事な情報を毎回送ってるってのがどうなの?ってカンジはありますが。。。。

つらさ

Cookieの仕様てきに、クロスオリジンでの認証がツライです。

それ意味ないじゃん!って思うかもしれませんが、
上位のドメインにならクッキーを書き込めるので、同じSuffixをもつドメインで運用しているなら何も問題がありません。

(いまこれを書いててdomain=*.comみたいなクッキーを発行したらどうなるんだろうとちょっと気になった。)

セキュリティ面で思いつくヤバそうなポイントとしては、

どうせそんなに超重要なデータなんて預かってない(と思う)のでなんとかなるよ!

前置きが長い

そういうことで、GitLabに署名付きクッキーによる独自認証機能をつける運びとなりました。

OmniAuth

GitLabはOmniauthを介して、いろんなアカウントでログインできるようになってるので、
比較的かんたんにSSOを実現できます。

Strategy

Omniauthでは、あるサービスのアカウントを使ってのログイン動作をStrategyと呼ばれるクラスに記述します。
このStrategyを自分で実装すれば、独自認証ができるわけです。

Strategyの書き方

Strategy Contribution Guideを読めば大体わかります。
Developer Strategyを改造しながら作るといいよ!って書いてあるので従いましょう。


module OmniAuth
module Strategies
class Mylogin
include OmniAuth::Strategy

option :fields, [:name, :email]
option :uid_field, :email

def request_phase
form = OmniAuth::Form.new(:title => 'User Info', :url => callback_path)
options.fields.each do |field|
form.text_field field.to_s.capitalize.tr('_', ' '), field.to_s
end
form.button 'Sign In'
form.to_response
end

uid do
request.params[options.uid_field.to_s]
end

info do
options.fields.inject({}) do |hash, field|
hash[field] = request.params[field.to_s]
hash
end
end
end
end
end

Declarative Configuration


option :fields, [:name, :email]
option :uid_field, :email

はじめにこんな感じで、認証に使うデータを定義しておきましょうみたいなお話らしいです。
ガチなやつを作るなら、外部サービスのAPIキーとかそういうやつでしょうか。

Defining the Request Phase


def request_phase
form = OmniAuth::Form.new(:title => 'User Info', :url => callback_path)
options.fields.each do |field|
form.text_field field.to_s.capitalize.tr('_', ' '), field.to_s
end
form.button 'Sign In'
form.to_response
end

リクエストフェーズでは、実際に外部サービスに認証を委任したりします。
このDeveloperStrategyでは、画面にフォームを入力してユーザー名をメールアドレスを入力してもらう形になってます。
本来なら、ここで外部サービスにリダイレクトを飛ばしたりします。

Defining the Callback Phase


uid do
request.params[options.uid_field.to_s]
end

info do
options.fields.inject({}) do |hash, field|
hash[field] = request.params[field.to_s]
hash
end
end

uidでIDを返して、infoでメールとか名前とかのハッシュを返せばOKです。

RequestPhaseで得られるcallback_pathにリダイレクトを返してもらうようにすれば、
外部サービスから認証情報が飛んできて、そいつをココでパースして認証完了、という流れです。

本当はココはcallback_phaseというメソッドを定義して、そこでomniauth.authにAuthHashを組み立てるんですけど、
uidとinfoをセットすればsuperクラスでうまいこと処理してくれます。
なので、callback_phaseをオーバーライドするならば自分でAuthHashを作るか、最後にsuperのcallback_phaseを呼ばないと死にます。

おわり

簡単ですね(白目)

すいません、ぶっちゃけ既存のStrategyを読んてパクった方が手っ取り早いです。
List of Strategiesにたくさんのってるので、適当にコードを追ってみれば処理の流れがわかるかと思います。

日本語でStrategyの書き方が紹介されてる記事

GitLabに組み込もう

そしたらGitLabに組み込んでみたくなりますよね。

Using Custom Omniauth Providersっていう説明があるんですけど、

Note: The following information only applies for installations from source.

は?って言うカンジですね。
ちなみに、GitLabをソースからインストールするのはマジで闇なのでオススメしません。

普通はOmnibusパッケージっていうのでインストールするはずなんですけど、ココにOmniauthストラテジを追加するにはどうするかというお話ですが、
今回はDockerイメージを使ってインストールしたGitLabで説明します。

設置

GitLabのソースがあるディレクトリを探して、(dockerでインストールしたなら、コンテナの中の/opt/gitlab/embedded/service/gitlab-railsです)
ここから相対パスでlib/omniauth/strategies/に自分で作ったStrategyをいれます。仮にmylogin.rbとしましょう。

そしたらconfig/initializers/omniauth.rbに


@@ -29,6 +29,7 @@ end

module OmniAuth
module Strategies
+    autoload :Mylogin, Rails.root.join('lib', 'omniauth', 'strategies', 'mylogin')
autoload :Bitbucket, Rails.root.join('lib', 'omniauth', 'strategies', 'bitbucket')
end
end

こういうカンジの行を追加します。

設定の変更

/etc/gitlab.rbを設定します


gitlab_rails['omniauth_enabled'] = true
gitlab_rails['omniauth_allow_single_sign_on'] = ['mylogin'] # SSOを許可する
gitlab_rails['omniauth_auto_sign_in_with_provider'] = 'mylogin' # GitLab標準のログイン画面をスキップしてこのStrategyでログインする
gitlab_rails['omniauth_block_auto_created_users'] = false # Omniauthで新規ユーザができたときにBlockしない
gitlab_rails['omniauth_providers'] = [
{
'name' => 'mylogin',
'args' => {
# ここに書いた変数は、Strategyからoptions.hogeみたいなかんじでアクセスできます
}
}
]

コレで、gitlab-ctl reconfigureすればおしまいです。

うまくいっていればこんな感じにボタンがでてきます。

参考

うちのサークルで使ってるGitlabがあててるパッチです。
もしかしたら参考になるかもしれない。
https://github.com/kaz/docker-gitlab/blob/master/patch.diff

おしまい

だいぶ雑ですけど、おわりです。

ちなみに、アイキャッチはGitLabのコントリビューターが1000人になった記念でもらったカードです。

おまけ

こんなツイートを見た

「サーバ管理者が暇そうにしているのは、彼がちゃんと仕事をしている証拠だ」という考え方がある。彼はちゃんと仕事をしているからサーバは安定しトラブルもなく動くので、彼は暇になっているのだ。

— 吉良理人@ねもい (@big_bros) 2016年12月20日

traPの鯖管は滅茶苦茶にいそがしいです。

kaz icon
この記事を書いた人
kaz

シスアド班の人です。サーバー/部内システム/インフラを管理しています。 好きな言語はPerl/JavaScript/Go、エディタはSublimeText3です。

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

2016年12月7日
Clojure, Elixir でプロセス間通信 〜TCP通信でBF & UDS通信でなんでも掲示板〜
Double_oxygeN icon Double_oxygeN
2016年12月25日
After Effectsで昼夜グラデーション画像を作ろう
Souring icon Souring
2016年12月23日
ファミコンに自作ゲームを移植しよう。その1
gotoh icon gotoh
2016年12月23日
UnityでtiteQuest(のようなもの)を作ってみる
satoriku icon satoriku
Prologの動作原理と自然言語処理 feature image
2016年12月23日
Prologの動作原理と自然言語処理
David icon David
2016年12月19日
MSGSで無課金DTMする人
uynet icon uynet
記事一覧 タグ一覧 Google アナリティクスについて 特定商取引法に基づく表記