概要
例えばうちはdev.tkgstrator.workにアクセスするとCloudflaredを経由して誰でもStable Diffusionが利用できるようになっている。
flowchart TB A("User") <--> |HTTPS| B("dev.tkgstrator.work") subgraph Cloudflare B <-.-> |Zero Trust| D("Cloudflare Tunnel") end subgraph Server D <-.-> |Zero Trust| E("Cloudflare Tunnel") E --> F("Stable Diffusion") endこのときの流れは上のようになっている。Serverにアクセスするまでの過程はCloudflareのZero TrustとHTTPSで安全であることは保障されるが、アクセスしてきて人間が悪意のある人間かどうかはチェックされていない。要するに、誰でもサービスを利用できてしまうのである。
ところが、一応Stable Diffusionは計算リソースを使う上に使い続ければ電気代もかかるし、一般的に不適切とされる絵を生成されても困るので「アクセスできるのは一部の人にする」という仕組みが欲しい。
Cloudflare Access
完全に理解しているわけではないので推測になってしまうのだが、Cloudflare Accessはセッションが有効なユーザーのみのアクセスを許可する仕組みを提供する。つまり、最初に一回認証をすればセッションが切れるまでは再認証せずにアクセスができるようになる。
ではどのようにユーザーを認証するのかということになるのだが、それを提供するのがZero TrustのAccess Groupという仕組みだ。
このAccess Groupは豊富な認証方式を提供しており、一例を挙げると、
- Emails
- Emails ending in
- External Evaluation
- Authentication Method
- Access groups
- IP ranges
- Country
- Everyone
- Common name
- Valid Certificate
- Service Token
- Any Access Service token
- Login Methods
- OIDC Claims
があり、例えばAuthentication MethodであればSMS認証やパスワード認証などいろいろ選ぶことができる。この機能がなんと無料で使えてしまう、本当に大丈夫なのか。
で、例えばメールアドレス認証をするなら「特定のメールアドレスであれば認証する」というルールをAccess Groupとして設定し、設定したAccess GroupだけがCloudflare Tunnelが展開しているURLと連携しているApplicationにアクセスする権限を持つというように設定すれば良い。
flowchart TB A("User") <--> |HTTPS| B("dev.tkgstrator.work") subgraph Cloudflare B <-.-> |Zero Trust| C("Cloudflare Access") C --> |Access Group| D subgraph Application D("Cloudflare Tunnel") end end subgraph Server D <-.-> |Zero Trust| E("Cloudflare Tunnel") E --> F("Stable Diffusion") endCloudflare Accessは指定したドメインに対する認証システムを提供する。今回の場合で言えばdev.tkgstrator.workに対する認証をつけるとしよう。
手順
Access GroupsとApplicationsの二つのパートに分けて解説する。
Access Groups
Cloudflareにアクセスして、Access -> Applicationsから設定を確認する。
今回はStable Diffusionに対するアクセスを制限したいのでGroup nameはStable Diffusionとした。
認証方法はメールアドレスとし、登録されているメールアドレスであればログインができるようにする。
あとは設定を保存して終了である。
Applications
Cloudflareにアクセスして、Networks -> Tunnelsから設定を確認する。
Routes: dev.tkgstrator.workに対してTunnelが貼られており、ステータスがHEALTHYになっていればサービス自体はちゃんと動いている。
次に、Access -> Applicationsからサブドメインdev.tkgstrator.workを認証するためのアプリケーションを作成する。このサブドメインで動いているのはStable DiffusionなのでStable Diffusionという名前でアプリケーションを作成した。
Tunnelでサービスを動かしている場合はType: SELF-HOSTEDを指定する。
セッションの有効期限であるSession Durationは好きな値を入れれば良いが、長すぎるとセッションハイジャックをされたときにめんどくさいので個人的にはNo duration, expires immediatelyを設定している。
とはいえ、これをやるとアクセスするたびに認証が要求されるのだが…
Application domainにはCloudflare Tunnelで動作中のサービスのドメインを指定する。今回の場合はdev.tkgstrator.workである。
次にこのApplicationにアクセスする権限を持つPolicyを設定する。いろいろ言葉が出てきて難しいが、Policyとは要するにAccess Groupsを拡張したものである。
Access GroupsとPolicyが何故別れているかというと、例えばAとBの二つのAccess Groupがあったとして一方には管理者権限をつけたいが、もう一方はただ閲覧できるだけというような設定をしたい場合があるからだ。
この場合、AはAllow, BはBlockと設定したPolicyをApplicationと紐つければ良い。つくづく便利な仕組みである。
SAML認証
とはいえ、ここまで読んで「あれ、これめんどくさくない?」と思った方も多いと思う。
何故なら、例えばアクセスできるユーザーを一人増やそうと思ったら、
- メールアドレスを訊く
- 返事が返ってくる
- Access Groupにその人のメールアドレスを登録する
という三つのステップが要求されるためだ。人が増えてくれば管理するのもめんどくさくなってくる。
そこで利用したいのがSSO(シングルサインオン)である。
必要なもの
- Cloudflareのアカウント
- Discordのアカウント
Cloudflareのアカウントがあるのは当たり前なのだが、ここではDiscordのアカウントも用意する。
Cloudflare
Cloudflareにアクセスして、Settings -> Custom PagesからTeam domainの値を確認する。
今回の場合はtkgstrator.cloudflareaccess.comだったのでこれをメモしておく。この値をTEAM_DOMAINとすることにしよう。
KV
Worker & Pagesから適当なKVを作成する。
今回はDiscordの認証なのでDiscordという名前にしたが、名前自体は何でも良い。
作成したらNamespace IDをコピーする。この値をNAMESPACE_IDとすることにしよう。
Discord Developer Portal
Discord Developer Portalから新規アプリケーションを作成する。
OAuth2
作成したらOAuth2を開いてCLIENT IDとCLIENT SECRETの値をコピーする。
更にRedirectsのところにhttps://${TEAM_DOMAIN}/cdn-cgi/access/callbackと入力する。TEAM_DOMAINのところにはさっきメモした値を入力する。
自分の場合は
https://tkgstrator.cloudflareaccess.com/cdn-cgi/access/callbackとなった
これができたらURL GeneratorからSCOPESのbotにだけチェックを入れて作成されたGENERATED URLの値をコピーする。この値をGENERATED_URLとしよう。
このURLを開けば自分が管理するサーバーに対して作成したアプリケーションを招待することができる。
Bot
次にBot -> TOKENを発行してメモする。この値をDISCORD_TOKENとすることにしよう。
自分用の認証なのでAuthorization Flow -> PUBLIC BOTは外しておくと良い。
ここまでできればDiscord Developer Portalでの作業は完了である。
discord-oidc-worker
さて、ここまでいくつかの値をコピーしてきたが、洩れがないか確認しよう。
| キー | サービス | 利用場所 |
|---|---|---|
| TEAM_DOMAIN | Cloudflare(Dashboard) | Discord Developer PortalのRedirects |
| NAMESPACE_ID | Cloudflare(Worker & Pages) | discord-oidc-worker |
| CLIENT_ID | Discord Developer Portal(OAuth2) | discord-oidc-worker |
| CLIENT_SECRET | Discord Developer Portal(OAuth2) | discord-oidc-worker |
| GENERATED_URL | Discord Developer Portal(OAuth2) | discord-oidc-worker |
| DISCORD_TOKEN | Discord Developer Portal(Bot) | discord-oidc-worker |
取得したほとんどすべての値はこれからの作業に必要になる。入力ミスをすると当然動かないので注意しよう。
git clone https://github.com/tkgstrator/discord-oidc-workercd discord-oidc-workeryarn installwrangler.toml
最初にwrangler.tomlを編集する。
name = "discord-oidc"main = "worker.js"compatibility_date = "2022-12-24"
kv_namespaces = [ { binding = "KV", id = "${NAMESPACE_ID}", preview_id = "b43c22430b5240dea89cd6cc350d3946" }]IDのところにNAMESPACE_IDを入力する。
元々のドキュメントのところにpreview_idに関する記述はなかったので多分こっちは何でも良い。触らずにいておくとよいだろう。
存在しないidを指定するとこの後で
deployしたときにNo KV namespace found with ID 0b9abda82acf461c8d4ecda656217e20 [code: 10041]というエラーが発生する
config.json
config.jsonは機密情報を含むのでconfig.sample.jsonからコピーする。
cp config.sample.json config.jsonBOTを呼んだサーバーのアイコンの上で右クリックでサーバーID(SERVER_ID)をコピーする。こうすることでユーザーが認証を行ったときにそのユーザーがそのサーバーで持つ役職IDを取得することができる。
{ "clientId": "${CLIENT_ID}", "clientSecret": "${CLIENT_SECRET}", "redirectURL": "https://${TEAM_DOMAIN}.cloudflareaccess.com/cdn-cgi/access/callback", "serversToCheckRolesFor": [ "${SERVER_ID}" ]}wrangler
ログイン
yarn wrangler loginでブラウザ経由でCloudflareにログインできる。
まだログインしていない場合はログインしておこう。
デプロイ
yarn wrangler secret put DISCORD_TOKENを実行するとシークレットとしてDISCORD_TOKENというキー名を持つ値をCloudflareに保存することができる。
このコマンドはそのままこれを入力すること、
DISCORD_TOKENはなにかの文字に置き換えるわけではない
yarn wrangler secret put DISCORD_TOKEN-------------------------------------------------------? Enter a secret value: ›シークレット値を入力しろと出るので、ここで先程コピーしておいたDISCORD_TOKENを入力してエンターキーを押す。
特に何も起こらないが、これで設定が保存される。
yarn deploy最後にyarn deployをする。
すると以下のような出力が得られる。
Your worker has access to the following bindings:- KV Namespaces: - KV: 1ec96d0feddd41d79444c95438595960Total Upload: 72.40 KiB / gzip: 17.05 KiBUploaded discord-oidc (1.49 sec)Published discord-oidc (0.35 sec) https://discord-oidc.lemonandchan.workers.devCurrent Deployment ID: d5b5d389-3601-44fd-a36e-fb9a8aa5bee8✨ Done in 3.58s.ここのhttps://discord-oidc.lemonandchan.workers.devのように表示されている値をWORKER_URLとしてコピーしておこう。
Cloudflare
ここまでできたら再びCloudflareのサイトに戻る。
CloudflareからSettings -> Authentication -> Login methods -> Add new -> OpenID Connectを選択する。
| キー | 値 |
|---|---|
| Name | 任意 |
| App ID | CLIENT_ID |
| Client secret | CLIENT_SECRET |
| Auth URL | ${WORKER_URL}/authorize/guilds |
| Token URL | ${WORKER_URL}/token |
| Certificate URL | ${WORKER_URL}/jwks.json |
| Proof Key for Code Exchange | 有効 |
このままの設定でもいいのだが、こうするとDiscordで認証できる(アカウントを持っている全員)がサービスを使えてしまうので、何らかのチェックを行えるようにする。
Auth URLは末尾は
guildsに変えてguildsの方が多分使い勝手が良い
なのでその下のOIDC Claimsから別のフィールドを返すように設定する。
| キー名 | 意味 |
|---|---|
| id | ユーザーID |
| username | ユーザー名 |
| guilds | 所属しているサーバーID |
| discriminator | ディスコードのタグ |
| roles:${SERVER_ID} | そのサーバーで割り当てられている役職ID |
その他にもpreferred_usernameやnameやglobal_nameなどが設定できるようだがnameに関してはユニークな値でないため認証として利用するのは避けたほうが良いとのこと。
使えるキー名についてはユーザー構造体を読むと良い。
ここまで設定ができれば保存してテストを実行してみよう。ミスがなければDiscordの認証画面が開いて、認証後にユーザーの情報が取得できるはずだ。
{ "email": "lemonandchan@gmail.com", "oidc_fields": { "id": "430364540899819520", "username": "devonly", "discriminator": "0", "roles:1109467734779117741": [ "1110574058732523520" ] }}Access Groups
次にDiscord認証に対応したAccess Groupを作成する。
設定するのはDefine group criteriaでSelectorにはLogin Methodsを指定してOpenID Connect・Discordを指定する。
これでここまで苦労して作ってきたシングルサインオンとAccess Groupを紐つける。
Applications
はい、これでようやく最後です。
作成したApplicationsにDiscordのPolicyでAssign a groupでDiscordを設定します。
Create additional rulesでOIDC Claimsのフィールドの値をつければ良いです。
例えば、あるサーバーである役職を持っている人だけを許可するのであれば、
| selector | Claim name | Claim value |
|---|---|---|
| OIDC Claims | roles:1109467734779117741 | 1110574058732523520 |
と設定すれば良いです。
記事は以上。