zero trust architecture

Deploying Tailscale for Zero Trust VPN

Deploy and configure Tailscale as a WireGuard-based zero trust mesh VPN with identity-aware access controls, ACLs, and exit nodes for secure peer-to-peer connectivity.

aclheadscaleidentity-awaremesh-vpnpeer-to-peertailscalewireguardzero-trust
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Tailscale is a zero trust mesh VPN built on WireGuard that creates encrypted peer-to-peer connections between devices without requiring traditional VPN servers or complex network configuration. Every connection in a Tailscale network (tailnet) is end-to-end encrypted using WireGuard's Noise protocol framework with Curve25519 key exchange. Tailscale implements zero trust networking by authenticating every connection request through identity providers, enforcing granular Access Control Lists (ACLs), and supporting features like exit nodes, subnet routers, MagicDNS, and Tailscale SSH. For organizations preferring self-hosted infrastructure, Headscale provides an open-source implementation of the Tailscale control server.

When to Use

  • When deploying or configuring deploying tailscale for zero trust vpn capabilities in your environment
  • When establishing security controls aligned to compliance requirements
  • When building or improving security architecture for this domain
  • When conducting security assessments that require this implementation

Prerequisites

  • Identity provider (Okta, Azure AD, Google Workspace, GitHub, or OIDC-compatible)
  • Devices running supported OS (Linux, Windows, macOS, iOS, Android, FreeBSD)
  • Administrative access to configure DNS and firewall rules
  • Understanding of WireGuard protocol fundamentals
  • Network planning documentation for subnet routing requirements

Architecture

                    Tailscale Coordination Server
                    (or self-hosted Headscale)
                           |
                    Key Distribution
                    & NAT Traversal
                           |
         +-----------------+-----------------+
         |                 |                 |
    +----+----+      +----+----+      +----+----+
    | Node A  |<---->| Node B  |<---->| Node C  |
    | (Linux) |      | (macOS) |      |(Windows)|
    +---------+      +---------+      +---------+
    WireGuard         WireGuard        WireGuard
    Encrypted         Encrypted        Encrypted
    P2P Tunnel        P2P Tunnel       P2P Tunnel
 
    Each node connects directly to every other node.
    DERP relay servers used only when direct P2P fails.

Installation and Setup

Linux Installation

# Add Tailscale repository and install
curl -fsSL https://tailscale.com/install.sh | sh
 
# Start Tailscale and authenticate
sudo tailscale up
 
# Check connection status
tailscale status
 
# View assigned IP address
tailscale ip -4
tailscale ip -6

Windows / macOS Installation

# Windows: Download from https://tailscale.com/download/windows
# macOS: Install via Homebrew
brew install --cask tailscale
 
# Or download from https://tailscale.com/download/mac

Docker Deployment

# docker-compose.yml for Tailscale sidecar
version: '3.8'
services:
  tailscale:
    image: tailscale/tailscale:latest
    container_name: tailscale
    hostname: my-service
    environment:
      - TS_AUTHKEY=tskey-auth-xxxxx  # Pre-auth key
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_EXTRA_ARGS=--advertise-tags=tag:container
    volumes:
      - tailscale-state:/var/lib/tailscale
      - /dev/net/tun:/dev/net/tun
    cap_add:
      - net_admin
      - sys_module
    restart: unless-stopped
 
volumes:
  tailscale-state:

Kubernetes Deployment

# Tailscale operator for Kubernetes
apiVersion: v1
kind: Secret
metadata:
  name: tailscale-auth
  namespace: tailscale
type: Opaque
stringData:
  TS_AUTHKEY: "tskey-auth-xxxxx"
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: tailscale
  namespace: tailscale
spec:
  selector:
    matchLabels:
      app: tailscale
  template:
    metadata:
      labels:
        app: tailscale
    spec:
      containers:
      - name: tailscale
        image: tailscale/tailscale:latest
        env:
        - name: TS_AUTHKEY
          valueFrom:
            secretKeyRef:
              name: tailscale-auth
              key: TS_AUTHKEY
        - name: TS_KUBE_SECRET
          value: tailscale-state
        - name: TS_USERSPACE
          value: "true"
        securityContext:
          capabilities:
            add: ["NET_ADMIN"]

Access Control Lists (ACLs)

Tailscale ACLs define who can access what within your tailnet using a declarative JSON format. The default policy is deny-all, making it zero trust by design.

{
  "acls": [
    // Engineering team can access development servers
    {
      "action": "accept",
      "src": ["group:engineering"],
      "dst": ["tag:dev-server:*"]
    },
    // SRE team can access production infrastructure
    {
      "action": "accept",
      "src": ["group:sre"],
      "dst": ["tag:production:22,443,8080"]
    },
    // Database access restricted to backend services
    {
      "action": "accept",
      "src": ["tag:backend"],
      "dst": ["tag:database:5432,3306,27017"]
    },
    // All employees can access internal tools
    {
      "action": "accept",
      "src": ["group:employees"],
      "dst": ["tag:internal-tools:443"]
    }
  ],
 
  "groups": {
    "group:engineering": ["user@company.com", "dev@company.com"],
    "group:sre": ["sre@company.com", "oncall@company.com"],
    "group:employees": ["autogroup:members"]
  },
 
  "tagOwners": {
    "tag:dev-server": ["group:engineering"],
    "tag:production": ["group:sre"],
    "tag:backend": ["group:sre"],
    "tag:database": ["group:sre"],
    "tag:internal-tools": ["group:sre"],
    "tag:container": ["group:sre"]
  },
 
  "ssh": [
    {
      "action": "check",
      "src": ["group:sre"],
      "dst": ["tag:production"],
      "users": ["root", "admin"]
    },
    {
      "action": "accept",
      "src": ["group:engineering"],
      "dst": ["tag:dev-server"],
      "users": ["autogroup:nonroot"]
    }
  ],
 
  "nodeAttrs": [
    {
      "target": ["autogroup:members"],
      "attr": ["funnel:deny"]
    }
  ]
}

Exit Nodes and Subnet Routing

Configure Exit Node

# On the exit node machine
sudo tailscale up --advertise-exit-node
 
# On the client machine, use the exit node
sudo tailscale up --exit-node=<exit-node-ip>
 
# Verify exit node routing
curl ifconfig.me  # Should show exit node's public IP

Subnet Router Configuration

# Advertise local subnets through Tailscale
sudo tailscale up --advertise-routes=10.0.0.0/24,192.168.1.0/24
 
# Enable IP forwarding on Linux
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
 
# Accept routes on client
sudo tailscale up --accept-routes

Tailscale SSH (Zero Trust SSH)

Tailscale SSH replaces traditional SSH key management with identity-based access.

# Enable Tailscale SSH on a server
sudo tailscale up --ssh
 
# Connect using Tailscale SSH (no SSH keys needed)
ssh user@hostname  # Authenticates via Tailscale identity
 
# Session recording (audit logging)
# Configure in ACL policy:
# "ssh": [{"action": "check", "src": [...], "dst": [...], "users": [...]}]
# "check" action requires re-authentication and records sessions

MagicDNS Configuration

# MagicDNS is enabled by default in new tailnets
# Access devices by hostname instead of IP
ping my-server  # Resolves via MagicDNS
 
# Custom DNS configuration via admin console
# Split DNS: route specific domains to internal DNS servers
# Global nameservers: override default DNS resolution

Self-Hosted with Headscale

# Install Headscale (open-source Tailscale control server)
wget https://github.com/juanfont/headscale/releases/latest/download/headscale_linux_amd64
chmod +x headscale_linux_amd64
sudo mv headscale_linux_amd64 /usr/local/bin/headscale
 
# Create configuration
sudo mkdir -p /etc/headscale
sudo headscale generate config > /etc/headscale/config.yaml
 
# Edit config for your environment
# Key settings:
#   server_url: https://headscale.example.com
#   listen_addr: 0.0.0.0:8080
#   private_key_path: /etc/headscale/private.key
#   db_type: sqlite3
#   db_path: /var/lib/headscale/db.sqlite
 
# Start Headscale
sudo headscale serve
 
# Create user and pre-auth key
headscale users create myorg
headscale preauthkeys create --user myorg --reusable --expiration 24h
 
# Connect Tailscale client to Headscale
tailscale up --login-server https://headscale.example.com

Security Hardening

Key Expiry and Rotation

# Set key expiry in admin console (default: 180 days)
# Force re-authentication periodically
 
# Disable key expiry for servers (use auth keys instead)
sudo tailscale up --authkey=tskey-auth-xxxxx
 
# Pre-auth keys for automated deployment
# Create ephemeral, single-use keys for CI/CD

Device Authorization

{
  "nodeAttrs": [
    {
      "target": ["autogroup:members"],
      "attr": [
        "mullvad:deny",
        "funnel:deny"
      ]
    }
  ],
  "autoApprovers": {
    "routes": {
      "10.0.0.0/24": ["group:sre"],
      "192.168.0.0/16": ["group:sre"]
    },
    "exitNode": ["group:sre"]
  }
}

Network Lock (Tailnet Lock)

# Initialize network lock with signing keys
tailscale lock init
 
# Add trusted signing keys
tailscale lock add nodekey:xxxxx
 
# All new nodes require signing before joining
# Prevents unauthorized nodes from joining the tailnet

Monitoring and Observability

# View network status
tailscale status --json | jq '.Peer | to_entries[] | {name: .value.HostName, online: .value.Online, os: .value.OS}'
 
# Check connection quality
tailscale ping <peer-ip>
 
# View network map
tailscale netcheck
 
# Audit logs available in Tailscale admin console
# Integration with SIEM via webhook or API

Integration Patterns

Service Mesh Integration

# Tailscale as sidecar for service-to-service communication
# Each service gets a Tailscale identity
# ACLs enforce service-to-service access policies
 
# Example: API service can only reach database service
# ACL: tag:api -> tag:database:5432

CI/CD Pipeline Integration

# Use ephemeral auth keys in CI/CD
export TS_AUTHKEY=tskey-auth-xxxxx-ephemeral
tailscale up --authkey=$TS_AUTHKEY --hostname=ci-runner-$CI_JOB_ID
 
# Access internal resources during build/deploy
# Node automatically removed when container stops

References

Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md1.8 KB

Tailscale Zero Trust VPN — API Reference

Libraries

Library Install Purpose
requests pip install requests Tailscale API v2 client

Tailscale API v2 Endpoints

Method Endpoint Description
GET /api/v2/tailnet/{tailnet}/devices List all devices in tailnet
GET /api/v2/tailnet/{tailnet}/acl Get ACL policy
PUT /api/v2/tailnet/{tailnet}/acl Update ACL policy
GET /api/v2/tailnet/{tailnet}/dns/nameservers Get DNS nameservers
GET /api/v2/tailnet/{tailnet}/keys List auth keys
GET /api/v2/device/{deviceid} Get device details
DELETE /api/v2/device/{deviceid} Remove device from tailnet
GET /api/v2/tailnet/{tailnet}/webhooks List webhooks

Base URL & Authentication

Base: https://api.tailscale.com
Header: Authorization: Bearer <api-key>

ACL Policy Structure

Field Description
acls Access control rules (src, dst, action)
groups Named groups of users
tagOwners Tag-based device ownership
ssh Tailscale SSH access rules
autoApprovers Auto-approve routes and exit nodes
tests ACL policy unit tests

Device Fields

Field Description
hostname Device hostname
os Operating system
clientVersion Tailscale client version
keyExpiryDisabled Whether key expiry is disabled
online Current online status
lastSeen Last seen timestamp
addresses Tailscale IP addresses

External References

standards.md1.9 KB

Standards Reference: Tailscale Zero Trust VPN

Protocol Standards

WireGuard Protocol

  • Encryption: ChaCha20 for symmetric encryption
  • Key Exchange: Curve25519 for Diffie-Hellman
  • MAC: Poly1305 for message authentication
  • Hashing: BLAKE2s for hashing
  • Framework: Noise Protocol Framework for key negotiation

NIST SP 800-207: Zero Trust Architecture

  • Tailscale implements identity-aware proxying (Section 3.2.2)
  • End-to-end encryption satisfies data-in-transit requirements
  • ACL-based access control implements least privilege access
  • Device identity via WireGuard keys maps to device trust

NIST SP 800-77: Guide to IPsec VPNs

  • WireGuard provides alternative to IPsec with reduced complexity
  • Tailscale automates key distribution and NAT traversal
  • Mesh topology eliminates single point of failure

Tailscale Security Model

Identity Layer

  • Authentication via OIDC-compatible identity providers
  • SSO integration with Okta, Azure AD, Google Workspace, GitHub
  • MFA enforcement through identity provider policies
  • Key expiry forces periodic re-authentication

Network Layer

  • Default deny ACL policy (zero trust)
  • Per-connection authorization based on identity and tags
  • No implicit trust based on network location
  • All traffic encrypted with WireGuard (256-bit keys)

Device Layer

  • Unique WireGuard key pair per device
  • Device authorization required before network access
  • Network Lock prevents unauthorized node addition
  • Ephemeral nodes for temporary workloads

Compliance Considerations

SOC 2

  • End-to-end encryption for data in transit
  • ACL-based access control for authorization
  • Audit logging for all connection events
  • Key management through coordination server

GDPR

  • Data minimization: Tailscale only routes traffic, does not inspect
  • Encryption: All traffic encrypted end-to-end
  • Self-hosted option (Headscale) for data sovereignty
  • Log retention configurable per organization policy
workflows.md3.3 KB

Workflows: Deploying Tailscale for Zero Trust VPN

Workflow 1: Initial Tailnet Deployment

Step 1: Plan Network Architecture
  - Identify all devices and services requiring connectivity
  - Map existing network topology and access requirements
  - Define user groups and access policies
  - Plan subnet routing for legacy network integration
  - Determine exit node placement for internet routing
 
Step 2: Configure Identity Provider
  - Enable SSO with organizational identity provider
  - Configure MFA enforcement policies
  - Map identity provider groups to Tailscale groups
  - Set key expiry policy (recommended: 90 days)
 
Step 3: Deploy Tailscale Nodes
  - Install on critical infrastructure first (servers, databases)
  - Deploy to user endpoints (laptops, mobile devices)
  - Configure subnet routers for non-Tailscale networks
  - Set up exit nodes for secure internet access
  - Enable MagicDNS for hostname resolution
 
Step 4: Configure ACLs
  - Start with deny-all baseline
  - Define groups matching organizational structure
  - Create tag-based policies for infrastructure
  - Test ACLs in audit mode before enforcement
  - Document all ACL rules and their business justification
 
Step 5: Validate and Monitor
  - Test connectivity between all required paths
  - Verify ACL enforcement blocks unauthorized access
  - Enable audit logging
  - Configure alerts for connection anomalies

Workflow 2: ACL Policy Development

Step 1: Inventory Access Requirements
  - List all user roles and their resource needs
  - Map application dependencies (service-to-service)
  - Identify privileged access paths
  - Document temporary/exception access needs
 
Step 2: Design Policy Structure
  - Define groups (users, teams, roles)
  - Define tags (environments, service types, sensitivity)
  - Map access rules: group/tag -> destination:ports
  - Plan SSH access policies with session recording
 
Step 3: Implement and Test
  - Write ACL JSON configuration
  - Deploy in test/staging tailnet first
  - Validate each rule with test connections
  - Verify deny rules block unauthorized access
  - Review with security team before production deployment
 
Step 4: Maintain and Audit
  - Review ACLs quarterly for stale rules
  - Audit access logs for policy violations
  - Update groups when team membership changes
  - Remove deprecated rules and tags

Workflow 3: Headscale Self-Hosted Deployment

Step 1: Prepare Infrastructure
  - Provision server with public IP and domain
  - Configure TLS certificate (Let's Encrypt)
  - Set up PostgreSQL or SQLite database
  - Configure firewall rules (port 443, DERP relay ports)
 
Step 2: Install and Configure Headscale
  - Download latest Headscale binary
  - Generate configuration file
  - Configure OIDC provider integration
  - Set up DNS records for coordination server
  - Configure DERP relay servers
 
Step 3: Onboard Users and Devices
  - Create users/namespaces in Headscale
  - Generate pre-auth keys for automated deployment
  - Connect client devices to Headscale server
  - Configure ACLs via Headscale policy file
 
Step 4: Operational Maintenance
  - Monitor Headscale server health
  - Rotate pre-auth keys regularly
  - Backup database and configuration
  - Update Headscale and client versions
  - Review and rotate DERP relay configuration

Scripts 2

agent.py5.0 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Tailscale zero trust VPN deployment audit agent."""

import json
import sys
import argparse
from datetime import datetime

try:
    import requests
except ImportError:
    print("Install: pip install requests")
    sys.exit(1)


class TailscaleClient:
    """Client for Tailscale API v2."""

    def __init__(self, api_key, tailnet="-"):
        self.base_url = "https://api.tailscale.com/api/v2"
        self.tailnet = tailnet
        self.headers = {"Authorization": f"Bearer {api_key}"}

    def _get(self, path):
        resp = requests.get(f"{self.base_url}{path}",
                            headers=self.headers, timeout=15)
        resp.raise_for_status()
        return resp.json()

    def list_devices(self):
        return self._get(f"/tailnet/{self.tailnet}/devices").get("devices", [])

    def get_acl(self):
        return self._get(f"/tailnet/{self.tailnet}/acl")

    def list_dns(self):
        return self._get(f"/tailnet/{self.tailnet}/dns/nameservers")

    def get_key_expiry_disabled(self):
        return self._get(f"/tailnet/{self.tailnet}/keys")

    def list_webhooks(self):
        return self._get(f"/tailnet/{self.tailnet}/webhooks").get("webhooks", [])


def audit_devices(devices):
    """Audit device compliance: OS versions, key expiry, last seen."""
    findings = []
    now = datetime.utcnow()
    for dev in devices:
        hostname = dev.get("hostname", "unknown")
        if dev.get("keyExpiryDisabled", False):
            findings.append({
                "device": hostname,
                "issue": "Key expiry disabled — device never requires re-authentication",
                "severity": "HIGH",
            })
        if not dev.get("updateAvailable", False) is False and dev.get("updateAvailable"):
            findings.append({
                "device": hostname,
                "issue": "Tailscale update available but not installed",
                "severity": "MEDIUM",
            })
        os_name = dev.get("os", "")
        if dev.get("blocksIncomingConnections", False):
            findings.append({
                "device": hostname,
                "issue": "Device blocks incoming connections (shields up mode)",
                "severity": "INFO",
            })
    return findings


def audit_acl(acl_data):
    """Check ACL policy for overly permissive rules."""
    findings = []
    acls = acl_data.get("acls", []) if isinstance(acl_data, dict) else []
    for i, rule in enumerate(acls):
        src = rule.get("src", [])
        dst = rule.get("dst", [])
        if "*" in src and any("*:*" in d for d in dst):
            findings.append({
                "rule_index": i,
                "issue": "Allow-all rule: src=* dst=*:* — no zero trust segmentation",
                "severity": "CRITICAL",
            })
    ssh_rules = acl_data.get("ssh", []) if isinstance(acl_data, dict) else []
    for rule in ssh_rules:
        if rule.get("action") == "accept" and "*" in rule.get("src", []):
            findings.append({
                "rule": "SSH",
                "issue": "SSH access allowed from all users",
                "severity": "HIGH",
            })
    return findings


def run_audit(api_key, tailnet):
    """Execute Tailscale zero trust audit."""
    print(f"\n{'='*60}")
    print(f"  TAILSCALE ZERO TRUST VPN AUDIT")
    print(f"  Generated: {datetime.utcnow().isoformat()} UTC")
    print(f"{'='*60}\n")

    client = TailscaleClient(api_key, tailnet)
    report = {}

    devices = client.list_devices()
    report["total_devices"] = len(devices)
    print(f"--- DEVICES ({len(devices)}) ---")
    for d in devices[:15]:
        print(f"  {d.get('hostname','')}: {d.get('os','')}/{d.get('clientVersion','')} "
              f"({'online' if d.get('online') else 'offline'})")

    dev_findings = audit_devices(devices)
    report["device_findings"] = dev_findings
    print(f"\n--- DEVICE FINDINGS ({len(dev_findings)}) ---")
    for f in dev_findings[:10]:
        print(f"  [{f['severity']}] {f['device']}: {f['issue']}")

    acl_data = client.get_acl()
    acl_findings = audit_acl(acl_data)
    report["acl_findings"] = acl_findings
    print(f"\n--- ACL POLICY FINDINGS ({len(acl_findings)}) ---")
    for f in acl_findings[:10]:
        print(f"  [{f['severity']}] {f['issue']}")

    dns = client.list_dns()
    report["dns_config"] = dns
    print(f"\n--- DNS CONFIG ---")
    for ns in dns.get("dns", []):
        print(f"  Nameserver: {ns}")

    return report


def main():
    parser = argparse.ArgumentParser(description="Tailscale Zero Trust Audit")
    parser.add_argument("--api-key", required=True, help="Tailscale API key")
    parser.add_argument("--tailnet", default="-", help="Tailnet name (default: current)")
    parser.add_argument("--output", help="Save report to JSON file")
    args = parser.parse_args()

    report = run_audit(args.api_key, args.tailnet)
    if args.output:
        with open(args.output, "w") as f:
            json.dump(report, f, indent=2, default=str)
        print(f"\n[+] Report saved to {args.output}")


if __name__ == "__main__":
    main()
process.py11.6 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
Tailscale Zero Trust VPN Management and Monitoring.

Manages Tailscale deployment, ACL generation, network health monitoring,
and compliance reporting for zero trust mesh VPN infrastructure.
"""

import json
import subprocess
import datetime
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional


@dataclass
class TailscaleNode:
    hostname: str
    ip4: str
    ip6: str = ""
    os: str = ""
    online: bool = False
    tags: list = field(default_factory=list)
    key_expiry: str = ""
    last_seen: str = ""
    exit_node: bool = False
    subnet_routes: list = field(default_factory=list)


@dataclass
class ACLRule:
    action: str  # "accept" or "deny"
    src: list
    dst: list
    description: str = ""


@dataclass
class ACLGroup:
    name: str
    members: list


class TailscaleACLGenerator:
    """Generate and validate Tailscale ACL configurations."""

    def __init__(self):
        self.groups: dict[str, list] = {}
        self.tag_owners: dict[str, list] = {}
        self.acls: list[dict] = []
        self.ssh_rules: list[dict] = []
        self.auto_approvers: dict = {"routes": {}, "exitNode": []}
        self.node_attrs: list[dict] = []

    def add_group(self, name: str, members: list):
        if not name.startswith("group:"):
            name = f"group:{name}"
        self.groups[name] = members

    def add_tag(self, tag: str, owners: list):
        if not tag.startswith("tag:"):
            tag = f"tag:{tag}"
        self.tag_owners[tag] = owners

    def add_acl_rule(self, src: list, dst: list, action: str = "accept"):
        self.acls.append({"action": action, "src": src, "dst": dst})

    def add_ssh_rule(self, src: list, dst: list, users: list, action: str = "check"):
        self.ssh_rules.append({
            "action": action,
            "src": src,
            "dst": dst,
            "users": users,
        })

    def add_auto_approver_route(self, cidr: str, approvers: list):
        self.auto_approvers["routes"][cidr] = approvers

    def add_auto_approver_exit_node(self, approvers: list):
        self.auto_approvers["exitNode"] = approvers

    def generate_policy(self) -> dict:
        policy = {}
        if self.groups:
            policy["groups"] = self.groups
        if self.tag_owners:
            policy["tagOwners"] = self.tag_owners
        if self.acls:
            policy["acls"] = self.acls
        if self.ssh_rules:
            policy["ssh"] = self.ssh_rules
        if self.auto_approvers["routes"] or self.auto_approvers["exitNode"]:
            policy["autoApprovers"] = self.auto_approvers
        if self.node_attrs:
            policy["nodeAttrs"] = self.node_attrs
        return policy

    def export_policy(self, output_path: str):
        policy = self.generate_policy()
        path = Path(output_path)
        path.parent.mkdir(parents=True, exist_ok=True)
        with open(path, "w") as f:
            json.dump(policy, f, indent=2)
        return policy

    def validate_policy(self) -> list:
        """Validate ACL policy for common issues."""
        issues = []
        policy = self.generate_policy()

        # Check for empty ACLs
        if not policy.get("acls"):
            issues.append("WARNING: No ACL rules defined - all traffic will be denied")

        # Check for overly permissive rules
        for i, rule in enumerate(self.acls):
            if "*" in rule.get("src", []) and "*" in rule.get("dst", []):
                issues.append(f"CRITICAL: Rule {i} allows all-to-all access")
            for dst in rule.get("dst", []):
                if dst.endswith(":*"):
                    issues.append(f"WARNING: Rule {i} allows all ports to {dst}")

        # Check groups reference valid members
        for group_name, members in self.groups.items():
            if not members:
                issues.append(f"WARNING: Group {group_name} has no members")

        # Check tag owners exist
        for tag, owners in self.tag_owners.items():
            for owner in owners:
                if owner.startswith("group:") and owner not in self.groups:
                    issues.append(f"ERROR: Tag {tag} references undefined group {owner}")

        # Check SSH rules
        for i, rule in enumerate(self.ssh_rules):
            if "root" in rule.get("users", []) and rule.get("action") != "check":
                issues.append(f"WARNING: SSH rule {i} allows root access without re-auth check")

        return issues


class TailscaleMonitor:
    """Monitor Tailscale network health and compliance."""

    def __init__(self):
        self.nodes: list[TailscaleNode] = []

    def get_status(self) -> dict:
        """Get current Tailscale status via CLI."""
        try:
            result = subprocess.run(
                ["tailscale", "status", "--json"],
                capture_output=True, text=True, timeout=10
            )
            if result.returncode == 0:
                return json.loads(result.stdout)
        except (subprocess.TimeoutExpired, FileNotFoundError, json.JSONDecodeError):
            pass
        return {}

    def parse_nodes(self, status: dict) -> list[TailscaleNode]:
        """Parse Tailscale status into node objects."""
        self.nodes = []
        peers = status.get("Peer", {})
        for peer_id, peer_data in peers.items():
            node = TailscaleNode(
                hostname=peer_data.get("HostName", "unknown"),
                ip4=peer_data.get("TailscaleIPs", [""])[0] if peer_data.get("TailscaleIPs") else "",
                ip6=peer_data.get("TailscaleIPs", ["", ""])[1] if len(peer_data.get("TailscaleIPs", [])) > 1 else "",
                os=peer_data.get("OS", ""),
                online=peer_data.get("Online", False),
                tags=peer_data.get("Tags", []),
                key_expiry=peer_data.get("KeyExpiry", ""),
                last_seen=peer_data.get("LastSeen", ""),
                exit_node=peer_data.get("ExitNode", False),
            )
            self.nodes.append(node)
        return self.nodes

    def check_health(self) -> dict:
        """Run health checks on the tailnet."""
        report = {
            "timestamp": datetime.datetime.now().isoformat(),
            "total_nodes": len(self.nodes),
            "online_nodes": sum(1 for n in self.nodes if n.online),
            "offline_nodes": sum(1 for n in self.nodes if not n.online),
            "expiring_keys": [],
            "untagged_nodes": [],
            "exit_nodes": [],
            "issues": [],
        }

        for node in self.nodes:
            # Check for expiring keys
            if node.key_expiry:
                try:
                    expiry = datetime.datetime.fromisoformat(node.key_expiry.replace("Z", "+00:00"))
                    days_until = (expiry - datetime.datetime.now(datetime.timezone.utc)).days
                    if days_until < 30:
                        report["expiring_keys"].append({
                            "hostname": node.hostname,
                            "expires_in_days": days_until,
                        })
                except (ValueError, TypeError):
                    pass

            # Check for untagged nodes
            if not node.tags:
                report["untagged_nodes"].append(node.hostname)

            # Track exit nodes
            if node.exit_node:
                report["exit_nodes"].append(node.hostname)

        # Generate issues
        if report["offline_nodes"] > 0:
            report["issues"].append(
                f"{report['offline_nodes']} nodes offline"
            )
        if report["expiring_keys"]:
            report["issues"].append(
                f"{len(report['expiring_keys'])} nodes with keys expiring within 30 days"
            )
        if report["untagged_nodes"]:
            report["issues"].append(
                f"{len(report['untagged_nodes'])} nodes without tags (ungoverned by ACLs)"
            )

        return report

    def generate_compliance_report(self) -> dict:
        """Generate zero trust compliance report for the tailnet."""
        report = {
            "report_date": datetime.datetime.now().isoformat(),
            "zero_trust_checks": {
                "encryption": {
                    "status": "PASS",
                    "detail": "All connections use WireGuard end-to-end encryption",
                },
                "identity_based_access": {
                    "status": "PASS" if all(n.tags for n in self.nodes) else "FAIL",
                    "detail": "All nodes should have identity tags for ACL enforcement",
                },
                "least_privilege": {
                    "status": "REVIEW",
                    "detail": "ACL policy review required - validate minimum necessary access",
                },
                "continuous_verification": {
                    "status": "PASS" if not any(
                        n.key_expiry == "" for n in self.nodes
                    ) else "WARNING",
                    "detail": "Key expiry should be enabled for all non-server nodes",
                },
                "device_trust": {
                    "status": "REVIEW",
                    "detail": "Verify device authorization and Network Lock status",
                },
            },
        }
        return report


def generate_example_policy():
    """Generate an example zero trust ACL policy for Tailscale."""
    gen = TailscaleACLGenerator()

    # Define groups
    gen.add_group("engineering", ["eng1@company.com", "eng2@company.com"])
    gen.add_group("sre", ["sre1@company.com", "sre2@company.com"])
    gen.add_group("security", ["sec1@company.com"])
    gen.add_group("management", ["mgr1@company.com"])

    # Define tags
    gen.add_tag("production", ["group:sre"])
    gen.add_tag("staging", ["group:engineering", "group:sre"])
    gen.add_tag("development", ["group:engineering"])
    gen.add_tag("database", ["group:sre"])
    gen.add_tag("monitoring", ["group:sre", "group:security"])
    gen.add_tag("ci-runner", ["group:sre"])

    # ACL rules - zero trust, least privilege
    gen.add_acl_rule(
        src=["group:engineering"],
        dst=["tag:development:*", "tag:staging:443,8080"]
    )
    gen.add_acl_rule(
        src=["group:sre"],
        dst=["tag:production:22,443,8080", "tag:staging:*", "tag:database:5432,3306"]
    )
    gen.add_acl_rule(
        src=["group:security"],
        dst=["tag:monitoring:443,9090", "tag:production:443"]
    )
    gen.add_acl_rule(
        src=["tag:ci-runner"],
        dst=["tag:staging:443,8080", "tag:production:443"]
    )

    # SSH rules with re-authentication
    gen.add_ssh_rule(
        src=["group:sre"],
        dst=["tag:production"],
        users=["admin"],
        action="check"  # Requires re-auth, records session
    )
    gen.add_ssh_rule(
        src=["group:engineering"],
        dst=["tag:development"],
        users=["autogroup:nonroot"],
        action="accept"
    )

    # Auto-approvers for subnet routes
    gen.add_auto_approver_route("10.0.0.0/16", ["group:sre"])
    gen.add_auto_approver_exit_node(["group:sre"])

    # Validate
    issues = gen.validate_policy()
    if issues:
        print("Policy Validation Issues:")
        for issue in issues:
            print(f"  - {issue}")
    else:
        print("Policy validation: PASSED")

    # Export
    policy = gen.export_policy("tailscale_acl_policy.json")
    print(f"\nGenerated ACL policy with:")
    print(f"  Groups: {len(gen.groups)}")
    print(f"  Tags: {len(gen.tag_owners)}")
    print(f"  ACL Rules: {len(gen.acls)}")
    print(f"  SSH Rules: {len(gen.ssh_rules)}")
    print(f"\nPolicy saved to: tailscale_acl_policy.json")
    print(f"\nPolicy preview:")
    print(json.dumps(policy, indent=2))


if __name__ == "__main__":
    generate_example_policy()

Assets 1

template.mdtext/markdown · 2.3 KB
Keep exploring