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 は「認証済みのトラフィックだけをサーバーに届ける」というシンプルな責務を最小コストで実現する。

サーバーのセキュリティグループ設定で すべてのインバウンドルールを削除できる——これが、この設計の最もシンプルな価値の表現だ。

この記事をシェア

Twitter / X LinkedIn
記事一覧に戻る
🤖
Cloud Assistant
IBM Watson powered

こんにちは!クラウドエンジニアのポートフォリオサイトへようこそ。AWS構成・副業サービス・お仕事のご相談など、何でも聞いてください 👋