Cloudflare Zero Trust による社内ツール(管理画面)のセキュアかつ低コストな公開
|
Cloudflare Zero Trust Security Access Control 社内ツール Tunnel
Cloudflare Zero Trustを使い、VPNなしで社内管理画面を安全に公開する方法。Cloudflare TunnelとAccessポリシーを組み合わせた低コストなセキュアアクセス設計。
はじめに:「社内ツールをどう公開するか」の選択肢とその問題
管理画面や内部APIを特定メンバーだけがアクセスできるよう公開する方法は複数ある。しかしどれも一長一短がある。
主な選択肢とその問題:
1. IPホワイトリスト(セキュリティグループ)
設定: 固定IPのみ許可
問題: リモートワーク・出張・モバイル環境では固定IPが使えない
メンバーのIP変更のたびに設定更新が必要
自宅のIPを晒すことになる
2. VPN(WireGuard / OpenVPN)
設定: VPNサーバーを立て、全員がクライアントをインストール
問題: VPNサーバーの運用コスト(EC2: $20〜$50/月)
証明書・鍵の管理と配布
クライアントソフトのインストールが必要
接続が切れると全アクセス不能
3. Basic認証
設定: nginx / Apache で .htpasswd を設定
問題: パスワードが平文に近い形で送信される(HTTPS前提でも弱い)
ブルートフォース攻撃に対して脆弱
全員同じパスワードになりがち
4. Cloudflare Zero Trust
設定: cloudflared でトンネルを作成、Accessポリシーで認証
問題: Cloudflare の設定学習コスト(初回のみ)
メリット: サーバーにインバウンドポートを一切開けない
Google / GitHub OAuth でメール単位の認証
Free Tier で50ユーザーまで無料
Cloudflare Zero Trust の核心は「サーバー側にファイアウォールの穴を開けない」点だ。 外部からのアクセスは全てCloudflareを経由し、認証済みのトラフィックだけが内側のサーバーに届く。
Part 1:Cloudflare Zero Trust の構成原理
従来のアクセス方式:
[ユーザー] ──HTTPS──→ [サーバー:443 open] → [管理画面]
↑
ポートを開けないといけない
IPホワイトリストで絞るが限界がある
Cloudflare Zero Trust の方式:
[ユーザー] ──HTTPS──→ [Cloudflare Edge]
↓ 認証チェック(Access Policy)
↓ 認証OK のみ通す
[Cloudflare Tunnel]
↓ アウトバウンド接続のみ
[cloudflared デーモン]
↓
[管理画面 :8080](インバウンドポート不要)
ポイント:
- サーバーからCloudflareへのアウトバウンド接続のみ
- サーバー側のインバウンドポート(443, 80)は完全にクローズ可能
- Cloudflareが認証レイヤーを担当する
Part 2:Cloudflare Tunnel のセットアップ
cloudflared のインストールと Tunnel 作成
# サーバー(EC2 / 任意のLinux)での作業
# 1. cloudflared をインストール(Amazon Linux 2)
curl -L --output cloudflared.rpm \
https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-x86_64.rpm
sudo rpm -ivh cloudflared.rpm
# Ubuntu / Debian の場合
curl -L --output cloudflared.deb \
https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb
# 2. Cloudflareにログイン(ブラウザが開く / URLをコピーして認証)
cloudflared tunnel login
# 3. トンネルを作成(名前は任意)
cloudflared tunnel create my-admin-tunnel
# 出力例:
# Created tunnel my-admin-tunnel with id 550e8400-e29b-41d4-a716-446655440000
# 認証情報ファイル: ~/.cloudflared/550e8400-...json
設定ファイルの作成
# ~/.cloudflared/config.yml
tunnel: 550e8400-e29b-41d4-a716-446655440000
credentials-file: /home/ec2-user/.cloudflared/550e8400-e29b-41d4-a716-446655440000.json
ingress:
# 管理画面(ローカルの 8080 番ポートに転送)
- hostname: admin.example.com
service: http://localhost:8080
# 別の内部ツール(Grafana など)も追加可能
- hostname: metrics.example.com
service: http://localhost:3000
# マッチしないリクエストは 404
- service: http_status:404
DNS レコードの設定と Tunnel 起動
# 4. DNS CNAME レコードを自動作成
cloudflared tunnel route dns my-admin-tunnel admin.example.com
# → admin.example.com の CNAME が <tunnel-id>.cfargotunnel.com に設定される
# 5. systemd サービスとして登録(自動起動)
sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflared
# 6. 動作確認
sudo systemctl status cloudflared
# Active: active (running)
Part 3:Cloudflare Access でアクセス制御を設定する
Cloudflare ダッシュボード(Zero Trust → Access → Applications)からGUIで設定する。
Access Application の設定
Cloudflare Zero Trust ダッシュボードでの設定手順:
1. Zero Trust → Access → Applications → Add an application
2. Self-hosted を選択
3. Application Configuration:
- Application name: Admin Dashboard
- Session Duration: 24h(長すぎず短すぎず)
- Application domain: admin.example.com
4. Access Policy を追加:
Policy name: Allow team members
Action: Allow
Rules:
Include:
- Emails: [
"tono@example.com",
"teammate@example.com"
]
または
- Email domain: example.com ← ドメイン全体を許可
5. Additional settings:
- Enable App Launcher visibility: ON(SSO ポータルに表示)
- CORS settings: 管理画面がAPIを呼ぶ場合に設定
One-time PIN 認証(無料で使える最もシンプルな方法)
One-time PIN の動作フロー:
ユーザーが admin.example.com にアクセス
↓
Cloudflare Access の認証画面にリダイレクト
↓
メールアドレスを入力
↓
Cloudflare がそのアドレスに 6桁のPINを送信
↓
PINを入力 → 認証成功
↓
管理画面にアクセス(JWTセッションが発行される)
コスト: 無料(メール送信は Cloudflare が処理)
前提: メールアドレスが Access Policy に含まれていること
Google OAuth との統合(チーム運用向け)
Cloudflare Zero Trust ダッシュボード:
Settings → Authentication → Login methods → Add new
→ Google を選択
Google Cloud Console での設定:
1. OAuth 2.0 クライアントIDを作成
2. 承認済みリダイレクトURI に追加:
https://<team-name>.cloudflareaccess.com/cdn-cgi/access/callback
設定後の動作:
- ユーザーが admin.example.com にアクセス
- 「Google でログイン」ボタンが表示
- Google アカウントで認証 → Cloudflareがメールアドレスを確認
- Access Policy に含まれるアドレスのみ通過
Part 4:Terraform で Tunnel と DNS を管理する
手動設定は初回の確認用として、本番運用では Terraform で管理する。
terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
}
}
variable "cloudflare_account_id" { type = string }
variable "cloudflare_zone_id" { type = string }
variable "tunnel_secret" {
type = string
sensitive = true
description = "32バイト以上のランダム文字列(base64エンコード)"
}
# Tunnel の作成
resource "cloudflare_tunnel" "admin" {
account_id = var.cloudflare_account_id
name = "my-admin-tunnel"
secret = base64encode(var.tunnel_secret)
}
# Tunnel の設定(ingress ルール)
resource "cloudflare_tunnel_config" "admin" {
account_id = var.cloudflare_account_id
tunnel_id = cloudflare_tunnel.admin.id
config {
ingress_rule {
hostname = "admin.example.com"
service = "http://localhost:8080"
}
ingress_rule {
hostname = "metrics.example.com"
service = "http://localhost:3000"
}
ingress_rule {
service = "http_status:404"
}
}
}
# DNS CNAME レコード
resource "cloudflare_record" "admin" {
zone_id = var.cloudflare_zone_id
name = "admin"
value = "${cloudflare_tunnel.admin.id}.cfargotunnel.com"
type = "CNAME"
proxied = true # Cloudflare プロキシ経由(オレンジ雲)必須
}
# Access Application
resource "cloudflare_access_application" "admin" {
account_id = var.cloudflare_account_id
name = "Admin Dashboard"
domain = "admin.example.com"
session_duration = "24h"
type = "self_hosted"
}
# Access Policy(許可するメールアドレス)
resource "cloudflare_access_policy" "allow_team" {
application_id = cloudflare_access_application.admin.id
account_id = var.cloudflare_account_id
name = "Allow team members"
precedence = 1
decision = "allow"
include {
email = [
"tono@example.com",
"teammate@example.com",
]
}
}
# Tunnel シークレットを SSM に保存(cloudflared デーモンが参照)
resource "aws_ssm_parameter" "tunnel_secret" {
name = "/my-app/cloudflare-tunnel-secret"
type = "SecureString"
value = var.tunnel_secret
}
output "tunnel_token" {
value = cloudflare_tunnel.admin.tunnel_token
sensitive = true
description = "cloudflared サービスの起動に使用するトークン"
}
EC2 ユーザーデータでの自動セットアップ
#!/bin/bash
# EC2 起動時に cloudflared を自動インストール・設定する
# cloudflared インストール
curl -L -o /tmp/cloudflared.rpm \
https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-x86_64.rpm
rpm -ivh /tmp/cloudflared.rpm
# SSM からトンネルトークンを取得
TUNNEL_TOKEN=$(aws ssm get-parameter \
--name "/my-app/cloudflare-tunnel-token" \
--with-decryption \
--query "Parameter.Value" \
--output text)
# systemd サービスとして起動
cloudflared service install "$TUNNEL_TOKEN"
systemctl enable cloudflared
systemctl start cloudflared
Part 5:セキュリティの確認とアクセスログ
cloudflared が正常に動作しているか確認
# サービス状態
sudo systemctl status cloudflared
# トンネルの接続状態確認
cloudflared tunnel info my-admin-tunnel
# Status: healthy
# Connections: 4 (to multiple Cloudflare PoPs)
# Cloudflare ダッシュボードでも確認
# Zero Trust → Networks → Tunnels → my-admin-tunnel → Healthy
アクセスログの確認
Cloudflare Zero Trust ダッシュボード:
Logs → Access
→ 誰が・いつ・どこから・どのアプリにアクセスしたかが記録される
表示例:
┌────────────────┬──────────────────┬──────────────────┬──────────┐
│ User │ Application │ Timestamp │ Decision │
├────────────────┼──────────────────┼──────────────────┼──────────┤
│ tono@ex.com │ Admin Dashboard │ 2025-06-15 10:30 │ Allow │
│ unknown@evil │ Admin Dashboard │ 2025-06-15 10:31 │ Block │
└────────────────┴──────────────────┴──────────────────┴──────────┘
Conclusion:コスト比較と適用判断
| 比較軸 | VPN(WireGuard on EC2) | Cloudflare Zero Trust |
|---|---|---|
| 月額コスト | $20〜$50(EC2 t3.micro〜small) | $0(50ユーザーまで無料) |
| インバウンドポート | 1194/UDP を開ける必要がある | 不要(アウトバウンドのみ) |
| クライアント設定 | 全員がWireGuardをインストール | ブラウザのみで接続可能 |
| 認証方式 | 証明書 / PSK | Google / GitHub / OTP |
| 証明書管理 | 自前でローテーション | Cloudflare が管理 |
| ユーザー追加 | 鍵の配布が必要 | メールアドレスをポリシーに追加するだけ |
| 障害時の影響 | VPNサーバーが落ちると全員アクセス不能 | Cloudflareの複数PoP経由で冗長化済み |
「社内ツールを安全に公開する」という要件に対して、VPNは機能の過剰提供とコストの過剰負担が発生しやすい。Cloudflare Zero Trust は「認証済みのトラフィックだけをサーバーに届ける」というシンプルな責務を最小コストで実現する。
サーバーのセキュリティグループ設定で すべてのインバウンドルールを削除できる——これが、この設計の最もシンプルな価値の表現だ。
この記事をシェア