はじめに
今回は、 Swagger などの開発運営側にしか見られたくないけど、 Basic 認証とかちょっと運用が面倒*1になってきたので
GitHub Organization に所属している人ならアクセスができるようにしてあげようって思ってやってみたので備忘録です。
本記事では個人的に設定しておいているものだけなので、ユーザ指定でやるやつを設定しています。
調べていたら、 oauth2-proxy
という便利そうなものが存在していたのでこちらを使用していこうと思います。
前提
- macOS 10.15.7
- Docker engine v20.10.2
- docker-compose 1.27.4
ざっとこんな感じ。
最終的にはこんな感じの docker-compose が立ち上がります。
導入
これを実装してみようと思ったのは、前述の通り、(副業で)今までは Swagger の URI などに対して Basic 認証をかけていたのですが、アプリも同じサーバを向いているので Authorization ヘッダーが Basic 認証と取り合うから別の X-App-Authorization
のようなカスタムヘッダーを使っていたり、
人が増えてきてアクセスキーを共有するのが面倒になってきたり、そもそも自分も Basic 認証のパスワードとか覚えてないから毎回パスワードをコピーしてくるのに面倒になっていた。
とかもろもろありました。
あと、私は Web フロントエンドの開発に一切興味がない(やりたくない)ので Web フロントエンドエンジニアに対してこれはここで Basic 認証かかってるからちゃんとやってねっとかの共有ができてない(コミュニケーションがなかった)事案が発生してたりして、
Staging のアプリで通信ができていない?よくわからない?どういうことだ?(パケット見たら Basic 認証通っていない)とかあったし、もろもろ面倒になったのでだったらということで今回のやつを導入することにした。
普段は Django REST Framework で開発を行っていますが、今回はわざわざ Django を使うほどではないので FastAPI を使ってデフォルト機能の Swagger UI の提供をデモに使っています。
バックエンドの提供
前章で書いたように今回は FastAPI を使用しています。
基本の開発環境は Docker という前提で書くので今回はバージョン情報はソースコードの ./backend/Pipfile.lock
を参照ください。
使ったソースコードは FastAPI のドキュメントで紹介されてあるコードをほぼそのままお借りしました。
from typing import Any, Dict, Optional import uvicorn from fastapi import FastAPI app = FastAPI(redoc_url=None) @app.get("/") async def read_root() -> Dict[str, str]: return {"Hello": "World"} @app.get("/items/{item_id}") async def read_item(item_id: int, q: Optional[str] = None) -> Dict[str, Any]: return {"item_id": item_id, "q": q} if __name__ == "__main__": uvicorn.run("app:app", port=8000, reload=True)
ちなみにローカルで実行したい場合は
$ uvicorn app:app
もしくは
$ python app.py
で起動することはできます。
実際に叩いてみるとちゃんと返ってきますね。
$ curl -s http://127.0.0.1:8000 | jq { "Hello": "World" }
ちなみに http://127.0.0.1:8000/docs
にアクセスすると Swagger UI が表示されました。
FastAPI はこの辺にしておきましょう。
oauth2-proxy
について
もともとは bitly/oauth2_proxy を Fork したものとのこと。
自分もはじめに出会ったのはやはり先祖のリポジトリでした。
引用: https://github.com/oauth2-proxy/oauth2-proxy
どういうことかはよくわからんけど、認証基盤をいい感じにやってくれるらしい。
公式で提供してくださっている Docker イメージを使用しています。
今回行った GitHub 以外の他に Google や Facebook などたくさん対応している*2らしい。
例えば Google Workspace を使っているのであれば会社のドメインを使用しているメールアドレスのみアクセスを許可するなど簡単にできるとのこと。
今回は、コマンドラインと一緒に configuration file を書いてやっているのでご紹介
http_address = "0.0.0.0:4180" upstreams = [ "http://127.0.0.1:8000" ] email_domains = [ "*" ] provider = "github" github_users = [ "nnsnodnb" ] cookie_expire = "24h"
GitHub での認可なので email_domains
はワイルドカードを指定しています。
GitHub Organization に所属している人のみアクセスを許可したい場合は、 github_org = "organization_name"
を指定すると動きます。
github_org
を指定していて github_users
も指定している場合は、指定した Organization にユーザが所属していなくても指定されているユーザであればアクセスが通るとのことです。
GitHub OAuth Apps を設定
GitHub にログインをしている状態で個人であれば https://github.com/settings/applications/new を開き、
Organization であれば https://github.com/organizations/{{ organization_name }}/settings/applications/new を開きましょう。
開くとこんな感じの画面を表示されます。
Application Name と Homepage URL はわかるように適当に設定しましょう。
Authorization callback URL は、実際に SSL でアクセスができるサイトを用意する必要があります。 HTTP なローカルホストにコールバックさせたら oauth2-proxy がエラーを吐いたのですが、 HTTPS なローカルホストでの確認はしていません。
今回もいつもお世話になっている ngrok を使用しました。
$ ngrok http 80
このコマンドで起動したら、いつものエンドポイントが表示されると思うので HTTPS の方のドメインをコピーして Authorization callback URL に https://hoge.jp.ngrok.io/oauth2/callback
のような感じになるように /oauth2/callback
を最後につけて設定を完了しましょう。
OAuth App が作成できたら自動で遷移するので Generate a new client secret
ボタンを押して Client secret
を生成しましょう。このコードは1度しか表示されないので気をつけましょう。
Nginx の設定
ポート通信を使うのがちょっとアレなので(別にそっち側の宗教の人じゃないけど)、今回は FastAPI をソケットを通して Nginx にプロキシしてもらうようにします。
gunicorn
を使っているのでとても簡単です。
ソースコードの ./backend/gunicorn_conf.py
を見てください。
bind = "unix://tmp/gunicorn.sock"
こんなコードがあります。現在のディレクトリの以下に /tmp
があるのでその下に gunicorn.sock
を作るようにしています。
gunicorn
の起動コマンドで -c gunicorn_conf.py
を指定しているので結構いい感じに動いてくれます。(?????) 詳しくは Dockefile
Nginx のコンテナの /etc/nginx/conf.d/default.conf
を上書きするように docker-compose.yml で定義しています。
upstream gunicorn_server { server unix:/app/tmp/gunicorn.sock; } upstream github_oauth_server { server github_oauth:4180; } server { listen 80; server_name default_server; location / { try_files $uri @proxy_to_app; } location /oauth2/ { proxy_pass http://github_oauth_server; 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 = /oauth2/auth { proxy_pass http://github_oauth_server; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Scheme $scheme; proxy_set_header Content-Length ""; proxy_pass_request_body off; } location /docs { auth_request /oauth2/auth; error_page 401 = /oauth2/sign_in; auth_request_set $user $upstream_http_x_auth_request_user; auth_request_set $email $upstream_http_x_auth_request_email; proxy_set_header X-User $user; proxy_set_header X-Email $email; auth_request_set $token $upstream_http_x_auth_request_access_token; proxy_set_header X-Access-Token $token; auth_request_set $auth_cookie $upstream_http_set_cookie; add_header Set-Cookie $auth_cookie; auth_request_set $auth_cookie_name_upstream_1 $upstream_cookie_auth_cookie_name_1; if ($auth_cookie ~* "(; .*)") { set $auth_cookie_name_0 $auth_cookie; set $auth_cookie_name_1 "auth_cookie_name_1=$auth_cookie_name_upstream_1$1"; } if ($auth_cookie_name_upstream_1) { add_header Set-Cookie $auth_cookie_name_0; add_header Set-Cookie $auth_cookie_name_1; } try_files $uri @proxy_to_app; } location @proxy_to_app { proxy_redirect off; proxy_http_version 1.1; proxy_redirect off; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; proxy_pass http://gunicorn_server; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }
gunicorn_server
と github_oauth_server
の upstream を定義しています。
上記の Nginx の設定を参考に設定しました。 Nginx 詳しくないので間違っているところあればコメントとかお願いします...
参考の設定ファイルだと、ルートディレクトリにアクセスしたら OAuth に入るというような感じなのでそれだとちょっとつらい...ということなので location /docs
の前方一致で反応するように設定しました。
認可が通った場合、 try_files $uri @proxy_to_app;
としているところがちょっとよくわからんけどこうやったら通ったのでとりあえず設定してまする。
docker-compose.yml
サービスとして
- backend
- proxy
- github_oauth
を定義しました。
起動前に .env.sample
をコピーして .env
を作成しパラメータを設定してください。
参考までにそのまま貼りますが
version: "3.8" services: backend: build: ./backend image: oauth2-proxy-backend container_name: oauth2_proxy_backend restart: always volumes: - type: volume source: socket target: /app/tmp tty: true proxy: image: nginx:1.18.0 container_name: oauth2_proxy_nginx restart: always volumes: - type: bind source: ./nginx/default.conf target: /etc/nginx/conf.d/default.conf - type: volume source: socket target: /app/tmp ports: - "80:80" depends_on: - backend - github_oauth github_oauth: image: quay.io/oauth2-proxy/oauth2-proxy:v6.1.1 container_name: oauth2_proxy_github_oauth restart: always volumes: - type: bind source: ./oauth2/oauth2-proxy.cfg target: /etc/oauth2-proxy.cfg expose: - "4180" command: --config=/etc/oauth2-proxy.cfg --client-id="${GITHUB_CLIENT_ID}" --client-secret="${GITHUB_CLIENT_SECRET}" --cookie-secret="${COOKIE_SECRET}" volumes: socket:
$ docker-compose up --build -d
で起動します。
では試してみます。
最後に
今回は docker-compose
を使って oauth2-proxy
を使って Swagger UI に対して OAuth2 認可を設定してみました。
実際に組み込むときにはわりと時間を要しました(既存の Nginx の設定と oauth2-proxy
の設定がちょっとわからんかった)がなんとか数時間程度で実装できました。実際に自動化で実装するところまでは持って行けていないのですがそれはまた追々...
全く関係ないけど、早く旅行に行けるようにならないかな
参考
*1: Authorization ヘッダーが重複して取り合ったり、人が増えてきてアクセスキーを共有するのが面倒
*2:https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider