oauth2_proxyでRundeckにGitHub認証でログインする

# はじめに
Rundeck便利なんだけど、ユーザ管理が貧弱すぎて、デフォルトだとWeb画面からユーザの追加すらできない。
じゃあどうやってユーザを追加するかというと設定ファイルにユーザ名とパスワードのハッシュを書くらしい。えっ、まじで？

最近新しく作ってるサーバは基本Docker化しているので、ユーザ追加するたびに設定ファイルとかいじりたくないんだけど。。。
ほらイメージ更新したら、Dockerコンテナ再起動しないといけないし、それだけのためにジョブ止めたくないし。

なんかよい方法はないかと思って公式ドキュメント読んだらLDAP認証とかActiveDirectory認証とかのは出てくるんだけど、そーゆーのはあんまり好きじゃないんだ。Google認証とかGitHub認証とかOAuth2使えないの？って思って、Issue/PullRequestを眺めてたらこんなのを見つけた。

[rundeck/rundeck #1883: Preauthentication filter](https://github.com/rundeck/rundeck/pull/1883)

認証処理をいじって、[bitly/oauth2_proxy](https://github.com/bitly/oauth2_proxy) と組み合わせてOAuth2でGitHub認証でログインできるようにしてみたよと。
あなたが神か。そう欲しかったのはこれだよ感。

で、このPullRequestからリンクされてるGistに簡単なサンプルコードがあったりするんだけど、
https://gist.github.com/tlots/730a123054b9a737b78cf59661bd3274
https://gist.github.com/donaldguy/eaa99fb0d1f17c0576255c3cf3ffc7ea

Rundeckやoauth2_proxyの設定方法が微妙に情報が古かったり、

公式ドキュメントにもちょっとだけ記載があるんだけど、
http://rundeck.org/docs/administration/authenticating-users.html#preauthenticated-mode

apache+mod_proxy_ajpの構成で、nginxだとどうすればよいのか書いてなかったりして、なんかいろいろ悩んだ。

あれこれ試行錯誤した結果、oauth2_proxyと組み合わせてGitHub認証することに成功したので、やりかたを共有しようかと思う。
現時点では、英語でもまとまった情報がないんだけど、日本語の情報は皆無だったので、誰かの役に立つかなと。

# できること
この記事のゴールは以下のとおり。

- GitHubの特定のorgに所属している人だけRundeckにログインできる
- GitHubの特定のorgの特定のチームに所属している人だけRundeckにログインできる

ちなみに最初Google認証でやろうとしてたんだけど、Google認証だと、

- 特定のドメインxxx@example.comに所属している人だけRundeckにログインできる

はやってみたらわりと簡単だったのだけど、

- 特定のドメインxxx@example.comの特定のGoogleグループに所属している人だけRundeckにログインできる

を実現するのに、oauth2_proxyにそれっぽいオプションがあり技術的にはできそうではあったものの、
G SuiteのAdminSDKからGoogleグループのメンバーを取得する必要があって、
そのためには、GCPでプロジェクト作ってサービスアカウントを発行して、それにG Suiteの管理権限で必要なポリシーを付与する
というちょっとめんどくさいことをしないといけなそうで、この方式は試していないです。

# 構成
稼働確認したバージョンは以下の通り。
- Rundeck: 2.7.1-1-GA
- oauth2_proxy: 2.1
- nginx: 1.11

補足として、前段にSSLを復号化するためにnginxがいる。

ブラウザ => nginx(443/tcp) => oauth2_proxy(4180/tcp) => rundeck(4440/tcp)

実際の構成としては全部Dockerで作ってるんだけど、設定どうすればよいかというところをメインで説明するので、そこはあんまり本題とは関係ないです。OSとしてはDebianなのでRedhat系だともしかするとファイルのパスが若干違うかもしれない。

# Rundeckのhttps化
OAuth2認証入れる前からhttps化はしてたので、SSLがない状態では試してないけど、OAuth2認証するならSSLは必要なんじゃないかと思う。というわけで、一応Rundeckのhttps化についても簡単に別記事でまとめておいたので、まだhttpsになってなかったら以下を参考にhttps化を先にやることをおすすめする。

[Rundeckの前段にnginxを配置してhttps化する](http://qiita.com/minamijoyo/items/caffc902c4c7f003d01d)

# Preauthenticated Modeの有効化
OAuth2認証する場合、RundeckでPreauthenticated Modeというのを有効化する必要がある。
これは `/etc/rundeck/rundeck-config.properties` に以下のような設定を追加する。

```/etc/rundeck/rundeck-config.properties
...

# Pre Auth mode settings
rundeck.security.authorization.preauthenticated.enabled=true
rundeck.security.authorization.preauthenticated.attributeName=REMOTE_USER_GROUPS
rundeck.security.authorization.preauthenticated.delimiter=,
# Header from which to obtain user name
rundeck.security.authorization.preauthenticated.userNameHeader=X-Forwarded-User
# Header from which to obtain list of roles
rundeck.security.authorization.preauthenticated.userRolesHeader=X-Forwarded-Roles
# Redirect to upstream logout url
rundeck.security.authorization.preauthenticated.redirectLogout=true
rundeck.security.authorization.preauthenticated.redirectUrl=/oauth2/sign_in
```

若干補足しておくと、 `userNameHeader` が認証したユーザ名をHTTPヘッダ `X-Forwarded-User` から取得し、
`userRolesHeader` が認証したユーザを HTTPヘッダ `X-Forwarded-Roles` で指定したロールにマップする。
ここからわかることは、現状この認証方式ではRundeck側でユーザとロールのマップを永続化できないので、全部HTTPヘッダで指定する必要があり、どのロールに所属するかも決め打ちしないといけない。 `X-Forwarded-User` の情報は oauth2_proxy がセットしてくれてるようなので特に気にする必要はないが、 `X-Forwarded-Roles` はセットしてくれないので、前段のnginxで明示的にこのヘッダをセットしてやる必要がある。このロールは実運用を考えると現状 `admin` 決め打ちにするしかないように思われる。

次に、 `/var/lib/rundeck/exp/webapp/WEB-INF/web.xml` から `<auth-constraint> 〜 </auth-constraint>` の設定を削除する。sedコマンドで書くとこんなかんじ。

```bash
$ sed -i -e "/<auth-constraint>/,/<\/auth-constraint>/d" /var/lib/rundeck/exp/webapp/WEB-INF/web.xml
```

これの意味するところは、Preauthenticated ModeとはRundeckの認証を外部に移譲し、Rundeck自体は認証処理をしないという意味である。つまり、この状態で、Rundeckのポートに直接通信できないようにファイアウォールなどの設定をしておかないと、認証無しでアクセスできてしまうので注意。あと、この仕組み上、OAuth2認証とパスワード認証が共存できないことも意味している。

あと、今のところRundeckの操作はWebのUIだけで、APIは使ってないのだけど、もしoauth2_proxyを挟んだ構成で、API呼び出しも使いたい場合は、もうひと工夫必要かもしれない。


(2017/04/05 追記): oauth2_proxyがセットしてくれるユーザ名はGitHubのログイン名ではなくGitHubに登録してるプライマリメールアドレスの＠の前になってしまうっぽくて、これは人によってはわかりにくい名前になってしまう可能性がある。最初Google認証用に実装されたときはメアドの＠の前をユーザ名として使うのは妥当な設計だったのだろうけど、OAuth2用に汎用化したときにもそのままになってて、GitHubとかだとメアドをユーザ名として扱うのは微妙である。[Issueも立ってるみたいなんだけど](https://github.com/bitly/oauth2_proxy/issues/229) 現状設定では回避できなさそう。

# nginxの設定
nginxの設定はあんまり難しいところはないんだけど、ポイントはさっき言ってた `X-Forwarded-Roles` ヘッダに `admin` をセットするのと、nginxからは直接Rundeckに接続せずに、間にoauth2_proxyをかませるところである。

```/etc/nginx/nginx.conf
...

http {
    ....

	server {
		listen 443;
		set_real_ip_from   '10.0.0.0/8';
		real_ip_header     X-Forwarded-For;
		proxy_set_header   Host $host;
		proxy_set_header   X-Forwarded-Proto https;

		ssl on;
		ssl_certificate /etc/nginx/keys/server.crt;
		ssl_certificate_key /etc/nginx/keys/server.key;

		server_name rundeck.example.com;
        ....

		# Set rundeck role
		proxy_set_header   X-Forwarded-Roles admin;

		location / {
			proxy_pass http://oauth2_proxy:4180;
		}
	}
}
```

# OAuth Applicationの登録
GitHubでOAuth2認証するにはOAuth Applicationの登録が必要である。
これはGitHubの設定画面からできる。
https://github.com/settings/applications/new

コールバックのURLには `https://rundeck.example.com/oauth2/callback` のように、RundeckのWeb画面のURLに `/oauth2/callback` を末尾につけたURLとしておく。

![image](https://qiita-image-store.s3.amazonaws.com/0/61152/2c27cecc-70a7-6ec8-372a-6eb0d52dbf58.png)

OAuth Applicationを登録すると、`Client ID` と `Client Secret` が発行される。これをあとで使う。

# oauth2_proxyの設定
oauth2_proxyはGolangで書かれたシングルバイナリのコマンドなので、インストールは以下のReleaseページからプラットフォームに合わせた最新のバイナリをダウンロードしてきて、適当なパスに配置すればよい。

https://github.com/bitly/oauth2_proxy/releases

ちなみにDockerで動かすには、公式のDockerイメージがなかったので、以下のような簡単なDockerfileを作ってビルドして使っている。

```Dockerfile
FROM alpine:3.5

RUN apk --no-cache add curl

RUN curl -sL -o oauth2_proxy.tar.gz \
    "https://github.com/bitly/oauth2_proxy/releases/download/v2.1/oauth2_proxy-2.1.linux-amd64.go1.6.tar.gz" \
  && tar xzvf oauth2_proxy.tar.gz \
  && mv oauth2_proxy-2.1.linux-amd64.go1.6/oauth2_proxy /bin/ \
  && chmod +x /bin/oauth2_proxy \
  && rm -r oauth2_proxy*
```

起動する前に、 さっき発行した `Client ID` と `Client Secret` を以下のように環境変数にexportする。

```bash
$ export OAUTH2_PROXY_CLIENT_ID=XXXXX
$ export OAUTH2_PROXY_CLIENT_SECRET=XXXX
```

また、CookieのSecretも設定しておいたほうがよいと思うけど、これは適当に乱数で文字列を生成して以下のように環境変数にセットしておけばよい。

```bash
$ export OAUTH2_PROXY_COOKIE_SECRET=XXXXX
```

oauth2_proxyの起動方法は以下のようなかんじ。設定値は設定ファイルにもかけるんだけど、全部引数に渡して起動することもできる。
設定値の意味は、読んでそのままだけど、細かいことはREADMEにちゃんと書いてあるので読んでね。

```bash
$ /bin/oauth2_proxy
    -provider="github"
    -github-org="hoge"
    -github-team="rundeck"
    -http-address="0.0.0.0:4180"
    -redirect-url="https://rundeck.example.com/oauth2/callback"
    -upstream="http://rundeck:4440/"
    -email-domain="*"
    -cookie-domain="rundeck.example.com"
    -cookie-refresh="1h"
```

上記の例は `hoge` というGitHubのorgの `rundeck` というチームに所属している人だけログインできるように指定している。ちなみにGitHub上のチーム名が大文字の `Rundeck` の場合でも なぜだか小文字の `rundeck` じゃないと認証できないという罠があったので注意。
あとorgは指定するが、チームの指定をしない、または空文字列にすると、そのorgに所属するメンバーであれば誰でもログインできるようになるのでチーム制限とかいらんよーという場合は、未指定でよい。

# まとめ
oauth2_proxyでRundeckにGitHub認証でログインできるようになった。oauth2_proxy使うと便利とセキュアが両立できてよさげ。

Rundeckのユーザ管理がイマイチだなぁと思ってる人は試してみてはどうでしょう？