はじめに
自宅NAS上でn8nを動かしていると、外部からWebhookを受け取りたくなるケースがあります。
今回はDiscordから自宅NAS上のn8nへWebhookを通したくなりました。
ただ、
- 直接ポート開放はしたくない
- IP制限だけでは不安
- Discordから外部へWebhookする際はDiscord署名の検証が必要
- 自宅サーバをなるべくゼロトラスト寄りにしたい
ということで、以下の構成でWebhookを安全に通すようにしました。
全体構成
Discord
↓(Discord署名検証)
Cloudflare Worker
↓(Cloudflare Access(Service Auth)検証)
Cloudflare Tunnel
↓ (リバーストンネル)
自宅NAS上のn8n
ポイントは以下の3層構造です。
- Discord署名検証(Workerで実施)
- Cloudflare Access(Service Auth)によるTunnelの保護
- Cloudflare Tunnel経由でWebhookのエンドポイントを公開(ポート開放なし)
① Discord → Workerで署名検証
DiscordのInteractionには X-Signature-Ed25519 と X-Signature-Timestamp が付与されます。
Worker側で以下を実施します。
- Raw Bodyを取得
-
timestamp + bodyを結合 - DiscordのPublic KeyでEd25519検証
- 失敗したら
401を返す
これにより、
- ✅ 自分のDiscordボット以外からの偽POSTを遮断
- ✅ Workerをエッジ検証ポイントとして利用
- ✅ n8n側ではDiscord署名検証ロジックを持たなくて済む
② Worker → Tunnel間はCloudflare Access(Service Auth)で保護
n8nはCloudflare Tunnelで公開するため、しっかりとアクセス制限をかけたい部分です。
今回はCloudflare Access(Service Auth)で保護しています。
Accessポリシーは1つだけ作成してアプリケーションに付与します。構成は以下のとおりです。
- アクション → Service Auth(※必ずService Authを選択。他の選択肢はブラウザユーザー向け)
- セッション時間 → 有効期限なし(即時期限切れ)
Workerはn8nへPOSTする際に、Accessで作成しておいたサービス資格情報を基に以下のヘッダを付与します。
CF-Access-Client-Id: <client_id>
CF-Access-Client-Secret: <client_secret>
これにより、
- ✅ Service Authが通らなければAccessが遮断
- ✅ Tunnelは未認証の通信を受信しない
インターネットからアクセスできるけど「Accessで完全に閉じている」状態です。
番外編:追加のセキュリティ対策
- ✅ Cloudflareでレート制限を設定する
- ✅ n8nのWebhookはURLパスとして
/webhookと/webhook-testのみを使用するため、それ以外をTraefikで制限する
今後の改善点
現状、n8nのWebhookエンドポイント側では特別な認証を入れていません。
Worker + Accessで十分に保護されている状態ですが、Workerでn8n用の内部トークンを付与するのも良さそうです。
また、Cloudflare Workerのプランによっては利用回数やリソースに制限があります。
制限を超えると動かなくなるようなので、用途によってはプランを変える必要があります。
※Cloudflare Workers の無料枠や実行制限については公式ドキュメントを参照してください。
- 料金ページ:https://www.cloudflare.com/plans/developer-platform/
- 実行制限一覧:https://developers.cloudflare.com/workers/platform/limits/
まとめ
- Discord署名検証はWorkerで行う
- Worker→n8nはAccess(Service Auth)で保護
- Tunnelで自宅サーバを安全公開(ポート開放なし)
- 将来的にn8n側でもトークン検証を追加するとより堅牢
自宅サーバ + Cloudflare + Discordの組み合わせはかなり便利です。
ゼロトラスト寄りにWebhookを受けたい人の参考になれば嬉しいです。