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

Tailscale WireGuard Mesh VPN Multi-tenant ACL Tag Group Subnet Router Exit Node MagicDNS Headscale Zero Trust Production
| Isolation Method | Complexity | Isolation Level | Best For |
|---|---|---|---|
| Tag-based ACL | ต่ำ | ปานกลาง (Shared Tailnet) | ทีมภายใน 2-10 กลุ่ม |
| Group-based ACL | ปานกลาง | ปานกลาง-สูง | องค์กร หลายแผนก |
| Separate Tailnet | สูง | สูงสุด | ลูกค้าภายนอก Compliance |
| Headscale (Self-hosted) | สูง | สูงสุด + Data Control | Enterprise 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

# === 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