Files
Anthropic-Cybersecurity-Skills/skills/deploying-tailscale-for-zero-trust-vpn/scripts/agent.py
T
mukul975 c21af3347e Complete folder anatomy for all 649 cybersecurity skills + update LICENSE to Mahipal
- Add scripts/agent.py and references/api-reference.md to all remaining skills
- Update all 648 LICENSE files: copyright now reads 'Mahipal'
- Add implementing-security-monitoring-with-datadog (new skill with full anatomy)
- All 649 skills now have: SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md
2026-03-11 00:22:12 +01:00

153 lines
5.0 KiB
Python

#!/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()