SiamCafe · Blog
Tailscale Mesh Multi-tenant Design — ออกแบบ VPN
บทความ

Tailscale Mesh Multi-tenant Design — ออกแบบ VPN

เผยแพร่ 28 พฤษภาคม 2569

Tailscale Multi-tenant Mesh

Tailscale Mesh Multi-tenant Design — ออกแบบ VPN

Tailscale WireGuard Mesh VPN Multi-tenant ACL Tag Group Subnet Router Exit Node MagicDNS Headscale Zero Trust Production

Isolation MethodComplexityIsolation LevelBest For
Tag-based ACLต่ำปานกลาง (Shared Tailnet)ทีมภายใน 2-10 กลุ่ม
Group-based ACLปานกลางปานกลาง-สูงองค์กร หลายแผนก
Separate Tailnetสูงสูงสุดลูกค้าภายนอก Compliance
Headscale (Self-hosted)สูงสูงสุด + Data ControlEnterprise Self-hosted

ACL Configuration

# === Tailscale ACL Policy ===

# tailscale ACL (HuJSON format)
# {
#   "groups": {
#     "group:tenant-a-admins": ["alice@example.com"],
#     "group:tenant-a-users": ["bob@example.com", "carol@example.com"],
#     "group:tenant-b-admins": ["dave@example.com"],
#     "group:tenant-b-users": ["eve@example.com"],
#     "group:platform-admins": ["admin@example.com"]
#   },
#   "tagOwners": {
#     "tag:tenant-a": ["group:tenant-a-admins"],
#     "tag:tenant-b": ["group:tenant-b-admins"],
#     "tag:shared": ["group:platform-admins"],
#     "tag:monitoring": ["group:platform-admins"]
#   },
#   "acls": [
#     // Tenant A can access only their devices
#     {"action": "accept", "src": ["group:tenant-a-admins", "group:tenant-a-users"],
#      "dst": ["tag:tenant-a:*"]},
#     // Tenant B can access only their devices
#     {"action": "accept", "src": ["group:tenant-b-admins", "group:tenant-b-users"],
#      "dst": ["tag:tenant-b:*"]},
#     // Everyone can access shared services on port 443
#     {"action": "accept", "src": ["*"], "dst": ["tag:shared:443"]},
#     // Platform admins can access monitoring
#     {"action": "accept", "src": ["group:platform-admins"], "dst": ["tag:monitoring:*"]},
#     // Platform admins can access everything
#     {"action": "accept", "src": ["group:platform-admins"], "dst": ["*:*"]}
#   ],
#   "tests": [
#     {"src": "alice@example.com", "accept": ["tag:tenant-a:22"]},
#     {"src": "alice@example.com", "deny": ["tag:tenant-b:22"]},
#     {"src": "dave@example.com", "accept": ["tag:tenant-b:80"]},
#     {"src": "dave@example.com", "deny": ["tag:tenant-a:80"]}
#   ]
# }

from dataclasses import dataclass

@dataclass
class ACLRule:
    tenant: str
    src: str
    dst: str
    ports: str
    purpose: str

rules = [
    ACLRule("Tenant A",
        "group:tenant-a-admins + users",
        "tag:tenant-a:*",
        "All Ports",
        "Tenant A เข้าถึงเฉพาะ Device ของตัวเอง"),
    ACLRule("Tenant B",
        "group:tenant-b-admins + users",
        "tag:tenant-b:*",
        "All Ports",
        "Tenant B เข้าถึงเฉพาะ Device ของตัวเอง"),
    ACLRule("Shared Services",
        "* (Everyone)",
        "tag:shared:443",
        "443 (HTTPS)",
        "ทุก Tenant เข้าถึง Shared API Gateway"),
    ACLRule("Monitoring",
        "group:platform-admins",
        "tag:monitoring:*",
        "All Ports",
        "Platform Admin ดู Monitoring ทุก Tenant"),
    ACLRule("Platform Admin",
        "group:platform-admins",
        "*:*",
        "All Ports",
        "Super Admin เข้าถึงทุก Device (Emergency)"),
]

print("=== ACL Rules ===")
for r in rules:
    print(f"  [{r.tenant}] {r.src} → {r.dst}:{r.ports}")
    print(f"    Purpose: {r.purpose}")

Network Architecture

Tailscale Mesh Multi-tenant Design — ออกแบบ VPN
# === Multi-tenant Network Design ===

@dataclass
class NetworkComponent:
    component: str
    config: str
    per_tenant: bool
    shared: bool
    note: str

components = [
    NetworkComponent("Subnet Router",
        "tailscale up --advertise-routes=10.1.0.0/24",
        True,
        False,
        "แต่ละ Tenant มี Subnet Router แยก เชื่อม On-prem"),
    NetworkComponent("Exit Node",
        "tailscale up --advertise-exit-node",
        True,
        False,
        "แยก Exit Node ต่อ Tenant สำหรับ IP Isolation"),
    NetworkComponent("MagicDNS",
        "device.tailnet-name.ts.net",
        False,
        True,
        "ทุก Device มี DNS Name อัตโนมัติ"),
    NetworkComponent("Split DNS",
        "Resolve *.tenant-a.internal → Subnet Router A",
        True,
        False,
        "แต่ละ Tenant มี Internal DNS แยก"),
    NetworkComponent("Auth Key",
        "tailscale up --auth-key=tskey-xxx",
        True,
        False,
        "สร้าง Auth Key ต่อ Tenant สำหรับ Auto-join"),
    NetworkComponent("Funnel",
        "tailscale funnel 443",
        False,
        True,
        "เปิด Shared Services สู่ Internet (ถ้าต้องการ)"),
]

print("=== Network Components ===")
for c in components:
    tenant = "Per-tenant" if c.per_tenant else "Shared"
    print(f"  [{c.component}] {tenant}")
    print(f"    Config: {c.config}")
    print(f"    Note: {c.note}")

Automation & Monitoring

# === Tailscale API Automation ===

# Tailscale API
# curl -s https://api.tailscale.com/api/v2/tailnet/example.com/devices \
#   -H "Authorization: Bearer tskey-api-xxx" | jq '.devices[] | {name, online}'

# Terraform Provider
# terraform {
#   required_providers {
#     tailscale = { source = "tailscale/tailscale" }
#   }
# }
# resource "tailscale_acl" "main" {
#   acl = file("acl.json")
# }
# resource "tailscale_tailnet_key" "tenant_a" {
#   reusable      = true
#   ephemeral     = false
#   preauthorized = true
#   tags          = ["tag:tenant-a"]
# }

@dataclass
class AutomationTask:
    task: str
    tool: str
    trigger: str
    action: str

tasks = [
    AutomationTask("Device Provisioning",
        "Terraform + Tailscale Provider",
        "New Tenant Onboarding",
        "สร้าง Auth Key + ACL Rule + Subnet Router"),
    AutomationTask("ACL Update",
        "Terraform + Git PR Review",
        "Tenant Change Request",
        "Update ACL JSON → PR → Review → Apply"),
    AutomationTask("Device Monitoring",
        "Tailscale API + Prometheus",
        "Continuous (every 60s)",
        "ตรวจ Device Online/Offline Last Seen"),
    AutomationTask("Key Rotation",
        "Tailscale API + Cron Job",
        "Every 90 days",
        "Rotate Auth Keys สร้าง Key ใหม่ ลบ Key เก่า"),
    AutomationTask("Audit Log Review",
        "Tailscale Admin Console",
        "Weekly",
        "ตรวจ Login Attempts ACL Changes Device Joins"),
]

print("=== Automation Tasks ===")
for t in tasks:
    print(f"  [{t.task}] Tool: {t.tool}")
    print(f"    Trigger: {t.trigger}")
    print(f"    Action: {t.action}")

เคล็ดลับ

  • ACL Tests: เขียน Tests ใน ACL ตรวจ Rule ก่อน Deploy
  • Tag: ใช้ Tag แบ่ง Tenant ง่ายสุด ไม่ต้องแยก Tailnet
  • Subnet Router: ตั้ง Subnet Router แยกต่อ Tenant
  • Key Expiry: เปิด Key Expiry 90 วัน บังคับ Re-auth
  • Terraform: ใช้ Terraform จัดการ ACL เป็น IaC

Tailscale คืออะไร

Zero-config VPN WireGuard Mesh Peer-to-peer NAT Traversal MagicDNS ACL Subnet Router Exit Node Taildrop SSH Funnel Free 3 Users