Quantumleap
2828 words
14 minutes
Cloudflare Access+Discordでアクセス制限をしよう

概要#

例えばうちは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")
    end

Cloudflare 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認証#

とはいえ、ここまで読んで「あれ、これめんどくさくない?」と思った方も多いと思う。

何故なら、例えばアクセスできるユーザーを一人増やそうと思ったら、

  1. メールアドレスを訊く
  2. 返事が返ってくる
  3. 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 IDCLIENT 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_DOMAINCloudflare(Dashboard)Discord Developer PortalのRedirects
NAMESPACE_IDCloudflare(Worker & Pages)discord-oidc-worker
CLIENT_IDDiscord Developer Portal(OAuth2)discord-oidc-worker
CLIENT_SECRETDiscord Developer Portal(OAuth2)discord-oidc-worker
GENERATED_URLDiscord Developer Portal(OAuth2)discord-oidc-worker
DISCORD_TOKENDiscord Developer Portal(Bot)discord-oidc-worker

取得したほとんどすべての値はこれからの作業に必要になる。入力ミスをすると当然動かないので注意しよう。

git clone https://github.com/tkgstrator/discord-oidc-worker
cd discord-oidc-worker
yarn install

wrangler.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.json

BOTを呼んだサーバーのアイコンの上で右クリックでサーバー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: 1ec96d0feddd41d79444c95438595960
Total Upload: 72.40 KiB / gzip: 17.05 KiB
Uploaded discord-oidc (1.49 sec)
Published discord-oidc (0.35 sec)
  https://discord-oidc.lemonandchan.workers.dev
Current 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 IDCLIENT_ID
Client secretCLIENT_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に変えてemailもできるがguildsの方が多分使い勝手が良い

なのでその下のOIDC Claimsから別のフィールドを返すように設定する。

キー名意味
idユーザーID
usernameユーザー名
guilds所属しているサーバーID
discriminatorディスコードのタグ
roles:${SERVER_ID}そのサーバーで割り当てられている役職ID

その他にもpreferred_usernamenameglobal_nameなどが設定できるようだがnameに関してはユニークな値でないため認証として利用するのは避けたほうが良いとのこと。

使えるキー名についてはユーザー構造体を読むと良い。

ここまで設定ができれば保存してテストを実行してみよう。ミスがなければDiscordの認証画面が開いて、認証後にユーザーの情報が取得できるはずだ。

{
  "email": "[email protected]",
  "oidc_fields": {
    "id": "430364540899819520",
    "username": "devonly",
    "discriminator": "0",
    "roles:1109467734779117741": [
      "1110574058732523520"
    ]
  }
}

Access Groups#

次にDiscord認証に対応したAccess Groupを作成する。

設定するのはDefine group criteriaSelectorにはLogin Methodsを指定してOpenID Connect・Discordを指定する。

これでここまで苦労して作ってきたシングルサインオンとAccess Groupを紐つける。

Applications#

はい、これでようやく最後です。

作成したApplicationsにDiscordのPolicyでAssign a groupでDiscordを設定します。

Create additional rulesでOIDC Claimsのフィールドの値をつければ良いです。

例えば、あるサーバーである役職を持っている人だけを許可するのであれば、

selectorClaim nameClaim value
OIDC Claimsroles:11094677347791177411110574058732523520

と設定すれば良いです。

記事は以上。

Cloudflare Access+Discordでアクセス制限をしよう
https://fuwari.vercel.app/posts/2024/02/cloudflare_access/
Author
tkgling
Published at
2024-02-10