references/acl-examples.md
# Tailscale ACL and Grants Examples
Comprehensive examples for configuring access control in Tailscale tailnets.
## ACL vs Grants
**ACLs** (legacy, still supported):
- Network-layer only
- Specify src, dst, ports
- No new features being added
**Grants** (modern, recommended):
- Network AND application-layer capabilities
- Cleaner syntax
- Future-proof
**Both work indefinitely**, but use Grants for new configurations.
## Basic Patterns
### Default Allow All
```json
{
"acls": [
{
"action": "accept",
"src": ["*"],
"dst": ["*:*"]
}
]
}
```
Or with Grants:
```json
{
"grants": [
{
"src": ["*"],
"dst": ["*"],
"ip": ["*"]
}
]
}
```
### Deny All (Emergency Lockdown)
```json
{
"acls": []
}
```
Or:
```json
{
"grants": []
}
```
## Groups and Tags
### Define Groups and Tags
```json
{
"groups": {
"group:engineering": [
"alice@example.com",
"bob@example.com"
],
"group:ops": [
"ops@example.com"
],
"group:finance": [
"finance@example.com"
]
},
"tagOwners": {
"tag:dev": ["group:engineering"],
"tag:staging": ["group:engineering", "group:ops"],
"tag:prod": ["group:ops"],
"tag:database": ["group:ops"],
"tag:monitoring": ["autogroup:admin"]
}
}
```
### Group Best Practices
✅ **Use groups for people**: `group:engineering`, `group:ops`
✅ **Use tags for machines**: `tag:server`, `tag:database`
✅ **Sync from IdP**: Use identity provider groups when possible
```json
{
"groups": {
"group:engineering@example.com": [], // Synced from Google Workspace
"group:ops@example.com": [] // Synced from Okta
}
}
```
## Common Scenarios
### Scenario 1: Personal + Shared Devices
Everyone accesses their own devices + shared servers.
```json
{
"tagOwners": {
"tag:shared": ["autogroup:admin"]
},
"grants": [
{
"src": ["autogroup:member"],
"dst": ["autogroup:self"],
"ip": ["*"]
},
{
"src": ["autogroup:member"],
"dst": ["tag:shared"],
"ip": ["22", "80", "443"]
}
]
}
```
### Scenario 2: Team-Based Access
Different teams access different resources.
```json
{
"groups": {
"group:engineering": ["alice@example.com", "bob@example.com"],
"group:design": ["designer@example.com"],
"group:finance": ["cfo@example.com"]
},
"tagOwners": {
"tag:dev-servers": ["group:engineering"],
"tag:design-tools": ["group:design"],
"tag:financial-systems": ["group:finance"]
},
"grants": [
{
"src": ["autogroup:member"],
"dst": ["autogroup:self"],
"ip": ["*"]
},
{
"src": ["group:engineering"],
"dst": ["tag:dev-servers"],
"ip": ["*"]
},
{
"src": ["group:design"],
"dst": ["tag:design-tools"],
"ip": ["*"]
},
{
"src": ["group:finance"],
"dst": ["tag:financial-systems"],
"ip": ["22", "443"]
}
]
}
```
### Scenario 3: Dev/Staging/Prod Isolation
```json
{
"groups": {
"group:developers": ["dev@example.com"],
"group:qa": ["qa@example.com"],
"group:ops": ["ops@example.com"]
},
"tagOwners": {
"tag:dev": ["group:developers"],
"tag:staging": ["group:developers", "group:qa"],
"tag:prod": ["group:ops"]
},
"grants": [
{
"src": ["group:developers"],
"dst": ["tag:dev"],
"ip": ["*"]
},
{
"src": ["group:developers", "group:qa"],
"dst": ["tag:staging"],
"ip": ["22", "80", "443"]
},
{
"src": ["group:ops"],
"dst": ["tag:prod"],
"ip": ["22"]
},
{
"src": ["autogroup:member"],
"dst": ["tag:prod"],
"ip": ["443"]
}
],
"ssh": [
{
"action": "check",
"src": ["group:ops"],
"dst": ["tag:prod"],
"users": ["root", "ubuntu"]
}
]
}
```
### Scenario 4: Customer Access (Shared Users)
Share specific machines with external users.
```json
{
"groups": {
"group:customers": ["customer@external.com"]
},
"tagOwners": {
"tag:shared-demo": ["autogroup:admin"]
},
"grants": [
{
"src": ["group:customers"],
"dst": ["tag:shared-demo"],
"ip": ["80", "443"]
}
]
}
```
### Scenario 5: Site-to-Site VPN
Connect office networks via subnet routers.
```json
{
"tagOwners": {
"tag:subnet-router-sf": ["autogroup:admin"],
"tag:subnet-router-nyc": ["autogroup:admin"]
},
"grants": [
{
"src": ["*"],
"dst": ["tag:subnet-router-sf"],
"ip": ["*"]
},
{
"src": ["*"],
"dst": ["tag:subnet-router-nyc"],
"ip": ["*"]
}
],
"autoApprovers": {
"routes": {
"10.1.0.0/16": ["tag:subnet-router-sf"],
"10.2.0.0/16": ["tag:subnet-router-nyc"]
},
"exitNode": ["tag:subnet-router-sf"]
}
}
```
## SSH Access Control
### Basic SSH Setup
```json
{
"grants": [
{
"src": ["alice@example.com"],
"dst": ["tag:servers"],
"ip": ["22"]
}
],
"ssh": [
{
"action": "accept",
"src": ["alice@example.com"],
"dst": ["tag:servers"],
"users": ["ubuntu", "alice"]
}
]
}
```
### SSH with Check Mode (MFA)
Requires recent re-authentication for sensitive access.
```json
{
"ssh": [
{
"action": "check",
"src": ["group:ops"],
"dst": ["tag:prod"],
"users": ["root"]
},
{
"action": "accept",
"src": ["group:ops"],
"dst": ["tag:prod"],
"users": ["autogroup:nonroot"]
}
]
}
```
### SSH User Mapping
```json
{
"ssh": [
{
"action": "accept",
"src": ["alice@example.com"],
"dst": ["tag:servers"],
"users": ["alice", "ubuntu"]
},
{
"action": "accept",
"src": ["bob@example.com"],
"dst": ["tag:servers"],
"users": ["bob"]
},
{
"action": "accept",
"src": ["group:ops"],
"dst": ["tag:servers"],
"users": ["root", "autogroup:nonroot"]
}
]
}
```
## Advanced Patterns
### Protocol-Specific Rules
```json
{
"acls": [
{
"action": "accept",
"src": ["group:developers"],
"dst": ["tag:database:5432"],
"proto": "tcp"
},
{
"action": "accept",
"src": ["tag:monitoring"],
"dst": ["*:*"],
"proto": "icmp"
}
]
}
```
### IP Sets for Complex Networks
```json
{
"ipSets": {
"office-networks": [
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16"
],
"cloud-cidrs": [
"10.100.0.0/16",
"10.200.0.0/16"
]
},
"grants": [
{
"src": ["group:employees"],
"dst": ["ipSet:office-networks"],
"ip": ["*"]
}
]
}
```
### Auto-Approvers
Automatically approve certain operations without admin intervention.
```json
{
"autoApprovers": {
"routes": {
"192.168.0.0/16": ["tag:subnet-router"],
"10.0.0.0/8": ["tag:cloud-gateway"]
},
"exitNode": ["tag:exit-nodes"]
}
}
```
### Posture-Based Access
Require device compliance before granting access.
```json
{
"postures": {
"posture:secure": [
"node:os == 'linux' || node:os == 'darwin'",
"node:tsVersion >= '1.50.0'"
]
},
"grants": [
{
"src": ["group:engineering"],
"dst": ["tag:prod"],
"ip": ["22"],
"capabilities": {
"posture": ["posture:secure"]
}
}
]
}
```
## Testing ACLs
### ACL Tests
Add tests to validate your policy:
```json
{
"tests": [
{
"src": "alice@example.com",
"accept": ["tag:dev:22", "tag:dev:443"],
"deny": ["tag:prod:*"]
},
{
"src": "bob@example.com",
"accept": ["tag:prod:443"],
"deny": ["tag:prod:22"]
}
]
}
```
### Preview Mode
In admin console:
1. Go to Access Controls
2. Click "Preview rules" tab
3. Select user
4. View what they can access
### Testing Commands
```bash
# Test network connectivity (ignores ACLs)
tailscale ping --tsmp target
# Test with ACLs applied
tailscale ping --icmp target
# If TSMP works but ICMP fails → ACL is blocking
```
## Common Mistakes
### ❌ Don't: Overly Permissive Personal Devices
```json
{
"grants": [
{
"src": ["user@example.com"], // Personal account
"dst": ["tag:prod"], // Production servers
"ip": ["*"] // All ports
}
]
}
```
✅ **Do: Use groups and limit ports**
```json
{
"groups": {
"group:ops": ["user@example.com"]
},
"grants": [
{
"src": ["group:ops"],
"dst": ["tag:prod"],
"ip": ["22", "443"]
}
]
}
```
### ❌ Don't: Forget Network-Level Access
```json
{
"ssh": [
{
"action": "accept",
"src": ["user@example.com"],
"dst": ["tag:servers"],
"users": ["ubuntu"]
}
]
// Missing network access grant!
}
```
✅ **Do: Include both network and SSH rules**
```json
{
"grants": [
{
"src": ["user@example.com"],
"dst": ["tag:servers"],
"ip": ["22"]
}
],
"ssh": [
{
"action": "accept",
"src": ["user@example.com"],
"dst": ["tag:servers"],
"users": ["ubuntu"]
}
]
}
```
### ❌ Don't: Hardcode Email Addresses
```json
{
"grants": [
{
"src": ["alice@example.com", "bob@example.com", "charlie@example.com"],
"dst": ["tag:servers"],
"ip": ["*"]
}
]
}
```
✅ **Do: Use groups**
```json
{
"groups": {
"group:engineering": ["alice@example.com", "bob@example.com", "charlie@example.com"]
},
"grants": [
{
"src": ["group:engineering"],
"dst": ["tag:servers"],
"ip": ["*"]
}
]
}
```
## Auto-Groups Reference
Built-in groups you can use:
| Auto-Group | Description |
|------------|-------------|
| `autogroup:admin` | Tailnet admins and owners |
| `autogroup:member` | All users in the tailnet |
| `autogroup:self` | Each user's own devices |
| `autogroup:nonroot` | All non-root users on a system |
| `autogroup:internet` | The public internet (for exit nodes) |
| `autogroup:shared` | Devices shared with other users |
## GitOps Integration
### GitHub Actions Example
```yaml
name: Update Tailscale ACLs
on:
push:
paths:
- 'tailscale-acl.json'
branches:
- main
jobs:
update-acls:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Update Tailscale ACLs
env:
TAILSCALE_API_KEY: ${{ secrets.TAILSCALE_API_KEY }}
TAILSCALE_TAILNET: ${{ secrets.TAILSCALE_TAILNET }}
run: |
curl -X POST \
"https://api.tailscale.com/api/v2/tailnet/${TAILSCALE_TAILNET}/acl" \
-u "${TAILSCALE_API_KEY}:" \
-H "Content-Type: application/json" \
--data @tailscale-acl.json
```
### Pre-commit Testing
```bash
#!/bin/bash
# .git/hooks/pre-commit
# Test ACL syntax
if ! tailscale debug policy lint acl.json; then
echo "ACL lint failed"
exit 1
fi
echo "ACL validation passed"
```
## Migration from ACLs to Grants
### Before (ACLs):
```json
{
"acls": [
{
"action": "accept",
"src": ["group:engineering"],
"dst": ["tag:dev:22", "tag:dev:443"]
}
]
}
```
### After (Grants):
```json
{
"grants": [
{
"src": ["group:engineering"],
"dst": ["tag:dev"],
"ip": ["22", "443"]
}
]
}
```
## Full Example: Complete Policy
```json
{
"groups": {
"group:engineering": ["alice@example.com", "bob@example.com"],
"group:ops": ["ops@example.com"],
"group:contractors": ["contractor@external.com"]
},
"tagOwners": {
"tag:dev": ["group:engineering"],
"tag:staging": ["group:engineering"],
"tag:prod": ["group:ops"],
"tag:subnet-router": ["autogroup:admin"],
"tag:exit-node": ["autogroup:admin"]
},
"grants": [
{
"src": ["autogroup:member"],
"dst": ["autogroup:self"],
"ip": ["*"]
},
{
"src": ["group:engineering"],
"dst": ["tag:dev"],
"ip": ["*"]
},
{
"src": ["group:engineering"],
"dst": ["tag:staging"],
"ip": ["22", "80", "443"]
},
{
"src": ["group:ops"],
"dst": ["tag:prod"],
"ip": ["22", "443"]
},
{
"src": ["group:contractors"],
"dst": ["tag:dev"],
"ip": ["80", "443"]
}
],
"ssh": [
{
"action": "accept",
"src": ["group:engineering"],
"dst": ["tag:dev"],
"users": ["ubuntu", "autogroup:nonroot"]
},
{
"action": "check",
"src": ["group:ops"],
"dst": ["tag:prod"],
"users": ["root"]
},
{
"action": "accept",
"src": ["group:ops"],
"dst": ["tag:prod"],
"users": ["autogroup:nonroot"]
}
],
"autoApprovers": {
"routes": {
"192.168.0.0/16": ["tag:subnet-router"],
"10.0.0.0/8": ["tag:subnet-router"]
},
"exitNode": ["tag:exit-node"]
},
"tests": [
{
"src": "alice@example.com",
"accept": ["tag:dev:22", "tag:staging:443"],
"deny": ["tag:prod:22"]
},
{
"src": "ops@example.com",
"accept": ["tag:prod:22", "tag:prod:443"],
"deny": []
}
]
}
```
references/api-usage.md
# Tailscale API Usage Guide
Complete guide for automating Tailscale management via the REST API.
## Authentication
### OAuth Clients (Recommended)
**Create OAuth client** in admin console → Settings → OAuth clients
```bash
# Store credentials
export TS_CLIENT_ID="your-client-id"
export TS_CLIENT_SECRET="your-secret"
export TS_TAILNET="example.com"
# Make API calls
curl -u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" \
"https://api.tailscale.com/api/v2/tailnet/$TS_TAILNET/devices"
```
### API Keys (Legacy)
Generate in admin console → Settings → Keys → Generate API key
```bash
export TS_API_KEY="tskey-api-..."
export TS_TAILNET="example.com"
curl -H "Authorization: Bearer $TS_API_KEY" \
"https://api.tailscale.com/api/v2/tailnet/$TS_TAILNET/devices"
```
## Common Operations
### List Devices
```bash
curl -u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" \
"https://api.tailscale.com/api/v2/tailnet/$TS_TAILNET/devices" \
| jq '.devices[] | {name, addresses, os}'
```
### Get Device Details
```bash
DEVICE_ID="12345"
curl -u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" \
"https://api.tailscale.com/api/v2/device/$DEVICE_ID"
```
### Delete Device
```bash
curl -X DELETE \
-u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" \
"https://api.tailscale.com/api/v2/device/$DEVICE_ID"
```
### Set Device Tags
```bash
curl -X POST \
-u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" \
-H "Content-Type: application/json" \
"https://api.tailscale.com/api/v2/device/$DEVICE_ID/tags" \
-d '{"tags": ["tag:server", "tag:production"]}'
```
### Update Device Settings
```bash
curl -X POST \
-u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" \
-H "Content-Type: application/json" \
"https://api.tailscale.com/api/v2/device/$DEVICE_ID" \
-d '{
"keyExpiryDisabled": true,
"updateAvailable": false
}'
```
### Generate Auth Key
```bash
curl -X POST \
-u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" \
-H "Content-Type: application/json" \
"https://api.tailscale.com/api/v2/tailnet/$TS_TAILNET/keys" \
-d '{
"capabilities": {
"devices": {
"create": {
"reusable": true,
"ephemeral": false,
"preauthorized": true,
"tags": ["tag:server"]
}
}
},
"expirySeconds": 86400
}'
```
## ACL Management
### Get ACL Policy
```bash
curl -u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" \
"https://api.tailscale.com/api/v2/tailnet/$TS_TAILNET/acl"
```
### Update ACL Policy
```bash
curl -X POST \
-u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" \
-H "Content-Type: application/json" \
"https://api.tailscale.com/api/v2/tailnet/$TS_TAILNET/acl" \
--data @acl-policy.json
```
### Validate ACL Without Applying
```bash
curl -X POST \
-u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" \
-H "Content-Type: application/json" \
"https://api.tailscale.com/api/v2/tailnet/$TS_TAILNET/acl/validate" \
--data @acl-policy.json
```
## DNS Management
### Get DNS Configuration
```bash
curl -u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" \
"https://api.tailscale.com/api/v2/tailnet/$TS_TAILNET/dns/preferences"
```
### Update DNS Settings
```bash
curl -X POST \
-u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" \
-H "Content-Type: application/json" \
"https://api.tailscale.com/api/v2/tailnet/$TS_TAILNET/dns/preferences" \
-d '{
"magicDNS": true
}'
```
### Manage DNS Nameservers
```bash
curl -X POST \
-u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" \
-H "Content-Type: application/json" \
"https://api.tailscale.com/api/v2/tailnet/$TS_TAILNET/dns/nameservers" \
-d '{
"dns": ["1.1.1.1", "8.8.8.8"]
}'
```
## Terraform Provider
### Provider Setup
```hcl
terraform {
required_providers {
tailscale = {
source = "tailscale/tailscale"
version = "~> 0.13"
}
}
}
provider "tailscale" {
api_key = var.tailscale_api_key
tailnet = var.tailnet
}
```
### Create Auth Key
```hcl
resource "tailscale_tailnet_key" "server_key" {
reusable = true
ephemeral = false
preauthorized = true
tags = ["tag:server"]
expiry = 3600
}
output "auth_key" {
value = tailscale_tailnet_key.server_key.key
sensitive = true
}
```
### Manage ACL
```hcl
resource "tailscale_acl" "main" {
acl = jsonencode({
groups = {
"group:engineering" = ["alice@example.com"]
}
tagOwners = {
"tag:server" = ["group:engineering"]
}
grants = [
{
src = ["group:engineering"]
dst = ["tag:server"]
ip = ["*"]
}
]
})
}
```
### Define DNS Records
```hcl
resource "tailscale_dns_nameservers" "main" {
nameservers = ["1.1.1.1", "8.8.8.8"]
}
resource "tailscale_dns_search_paths" "main" {
search_paths = ["example.com"]
}
```
## Python SDK
### Installation
```bash
pip install tailscale
```
### Basic Usage
```python
from tailscale import Tailscale
# Initialize client
ts = Tailscale(
api_key="tskey-api-...",
tailnet="example.com"
)
# List devices
devices = ts.devices()
for device in devices:
print(f"{device['hostname']}: {device['addresses']}")
# Get device by name
device = ts.device_by_name("myserver")
# Delete device
ts.delete_device(device['id'])
# Create auth key
key = ts.create_auth_key(
reusable=True,
ephemeral=False,
preauthorized=True,
tags=["tag:server"]
)
print(f"Auth key: {key['key']}")
```
## Automation Examples
### Automated Device Provisioning
```bash
#!/bin/bash
# provision-server.sh
set -e
# Generate ephemeral auth key
AUTH_KEY=$(curl -s -X POST \
-u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" \
-H "Content-Type: application/json" \
"https://api.tailscale.com/api/v2/tailnet/$TS_TAILNET/keys" \
-d '{
"capabilities": {
"devices": {
"create": {
"ephemeral": true,
"preauthorized": true,
"tags": ["tag:ci"]
}
}
},
"expirySeconds": 3600
}' | jq -r '.key')
# Install and connect
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --auth-key="$AUTH_KEY" --advertise-tags=tag:ci
echo "Device connected to tailnet"
```
### Inventory Management
```python
#!/usr/bin/env python3
import json
from datetime import datetime, timedelta
from tailscale import Tailscale
ts = Tailscale(api_key="tskey-api-...", tailnet="example.com")
# Find stale devices (offline >30 days)
threshold = datetime.now() - timedelta(days=30)
stale_devices = []
for device in ts.devices():
last_seen = datetime.fromisoformat(device['lastSeen'].replace('Z', '+00:00'))
if last_seen < threshold:
stale_devices.append({
'name': device['hostname'],
'last_seen': last_seen.isoformat(),
'id': device['id']
})
print(f"Found {len(stale_devices)} stale devices")
print(json.dumps(stale_devices, indent=2))
# Optional: Delete them
# for device in stale_devices:
# ts.delete_device(device['id'])
```
### GitOps ACL Workflow
```yaml
# .github/workflows/tailscale-acl.yml
name: Update Tailscale ACL
on:
push:
paths:
- 'acl.json'
branches:
- main
jobs:
update-acl:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Validate ACL
env:
TS_CLIENT_ID: ${{ secrets.TS_CLIENT_ID }}
TS_CLIENT_SECRET: ${{ secrets.TS_CLIENT_SECRET }}
TS_TAILNET: ${{ secrets.TS_TAILNET }}
run: |
curl -X POST \
-u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" \
-H "Content-Type: application/json" \
"https://api.tailscale.com/api/v2/tailnet/$TS_TAILNET/acl/validate" \
--data @acl.json || exit 1
- name: Apply ACL
env:
TS_CLIENT_ID: ${{ secrets.TS_CLIENT_ID }}
TS_CLIENT_SECRET: ${{ secrets.TS_CLIENT_SECRET }}
TS_TAILNET: ${{ secrets.TS_TAILNET }}
run: |
curl -X POST \
-u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" \
-H "Content-Type: application/json" \
"https://api.tailscale.com/api/v2/tailnet/$TS_TAILNET/acl" \
--data @acl.json
- name: Notify
run: echo "ACL updated successfully"
```
## Webhooks
### Configure Webhook
```bash
curl -X POST \
-u "$TS_CLIENT_ID:$TS_CLIENT_SECRET" \
-H "Content-Type: application/json" \
"https://api.tailscale.com/api/v2/webhooks" \
-d '{
"endpointUrl": "https://your-server.com/webhook",
"providerType": "slack",
"subscriptions": ["nodeCreated", "nodeDeleted"]
}'
```
### Webhook Events
Available event types:
- `nodeCreated` - Device added to tailnet
- `nodeDeleted` - Device removed
- `nodeApprovalChanged` - Device approval status changed
- `nodeKeyExpiringInOneDay` - Key expiring soon
- `nodeKeyExpired` - Key has expired
- `userCreated` - New user added
- `userDeleted` - User removed
- `userSuspended` - User suspended
- `userRestored` - User un-suspended
- `policyUpdate` - ACL policy changed
- `exitNodeSuggestionChanged` - Recommended exit node changed
### Process Webhook
```python
from flask import Flask, request
import hmac
import hashlib
app = Flask(__name__)
WEBHOOK_SECRET = "your-webhook-secret"
@app.route('/webhook', methods=['POST'])
def handle_webhook():
# Verify signature
signature = request.headers.get('Tailscale-Webhook-Signature')
body = request.get_data()
expected = hmac.new(
WEBHOOK_SECRET.encode(),
body,
hashlib.sha256
).hexdigest()
if signature != expected:
return 'Invalid signature', 401
# Process event
event = request.json
print(f"Event: {event['type']}")
print(f"Data: {event['data']}")
return 'OK', 200
```
## Rate Limits
- 120 requests per minute per API key
- Exponential backoff recommended for failures
## Best Practices
✅ **Use OAuth clients** instead of API keys for better security
✅ **Scope OAuth clients** to minimum required permissions
✅ **Rotate credentials** regularly
✅ **Validate ACLs** before applying
✅ **Use webhooks** for event-driven automation
✅ **Implement retry logic** with exponential backoff
✅ **Log API calls** for audit trail
✅ **Version control** ACL policies
✅ **Test in dev** before applying to production
## Error Handling
```python
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
def get_tailscale_session():
session = requests.Session()
# Retry with exponential backoff
retry = Retry(
total=5,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504]
)
adapter = HTTPAdapter(max_retries=retry)
session.mount('https://', adapter)
session.mount('http://', adapter)
return session
# Use it
session = get_tailscale_session()
response = session.get(
f"https://api.tailscale.com/api/v2/tailnet/{tailnet}/devices",
auth=(client_id, client_secret)
)
```
SKILL.md
---
name: tailscale
description: Comprehensive Tailscale VPN setup, configuration, and management for mesh networking, secure access, and zero-trust infrastructure. Covers installation, CLI commands, subnet routers, exit nodes, Tailscale SSH, ACL/grants configuration, MagicDNS, Tailscale Serve/Funnel, API automation, and production deployment best practices. Use when setting up Tailscale, configuring tailnet access controls, deploying subnet routers or exit nodes, enabling Tailscale SSH, exposing services with Serve/Funnel, automating via the Tailscale API, troubleshooting connectivity, or planning production Tailscale deployments.
---
# Tailscale Network Management
## Quick Start
```bash
# Install (Linux)
curl -fsSL https://tailscale.com/install.sh | sh
# Install (macOS)
brew install tailscale
# Connect and authenticate
sudo tailscale up
# Check status
tailscale status
# Get your Tailscale IP
tailscale ip -4
```
## Common Operations
### Connection Management
```bash
tailscale up # Connect
tailscale down # Disconnect (daemon stays running)
tailscale status # View peers
tailscale status --json | jq # Detailed network map
tailscale ping machine-name # Test connectivity (ignores ACLs)
tailscale ping --icmp machine-name # Test with ACLs
tailscale set --exit-node=name # Use exit node
tailscale set --exit-node= # Stop using exit node
```
Use `tailscale set` to change settings without reconnecting. Use `tailscale up` for initial setup.
### Subnet Router Setup
Run `scripts/setup_subnet_router.sh <subnet_cidr> [auth_key]` for automated setup.
**Manual steps:**
1. Enable IP forwarding on the router device
2. `sudo tailscale up --advertise-routes=192.168.1.0/24`
3. Approve routes in admin console (Machines > device > Edit route settings)
4. Linux clients: `sudo tailscale up --accept-routes`
### Exit Node Setup
Run `scripts/setup_exit_node.sh [auth_key]` for automated setup.
**Manual steps:**
1. Enable IP forwarding on the exit node
2. `sudo tailscale up --advertise-exit-node`
3. Approve in admin console (Machines > device > Edit route settings > Use as exit node)
4. Clients: `tailscale set --exit-node=node-name --exit-node-allow-lan-access`
### Tailscale SSH
```bash
# Enable on server
sudo tailscale set --ssh
# Connect from client (no special setup needed)
ssh machine-name
```
Requires both network access grant and SSH ACL rule. See [acl-examples.md](references/acl-examples.md) for SSH ACL patterns.
### Serve and Funnel
```bash
# Serve locally to tailnet
tailscale serve 3000
# Expose to public internet (ports 443, 8443, or 10000 only)
tailscale funnel 3000
# TCP forwarding with TLS termination
tailscale serve --tls-terminated-tcp=5432 localhost:5432
# Check status / turn off
tailscale serve status
tailscale serve off
```
## Access Control
Use **Grants** (modern, recommended) over ACLs (legacy). Both work, but Grants support application-layer capabilities.
```json
{
"groups": {
"group:engineering": ["alice@example.com"]
},
"tagOwners": {
"tag:server": ["group:engineering"]
},
"grants": [
{
"src": ["group:engineering"],
"dst": ["tag:server"],
"ip": ["22", "443"]
}
]
}
```
**Key patterns:** Use groups for people, tags for machines. Always include both network grants and SSH rules for SSH access.
For detailed ACL scenarios, SSH access patterns, posture checks, auto-approvers, GitOps integration, and common mistakes, see [acl-examples.md](references/acl-examples.md).
## Reference Files
- **[cli-reference.md](references/cli-reference.md)** - Complete CLI command reference with all flags, target formats, and platform-specific notes
- **[acl-examples.md](references/acl-examples.md)** - Detailed ACL/grants configuration: team-based access, dev/staging/prod isolation, SSH patterns, posture checks, auto-approvers, GitOps, migration from ACLs to Grants
- **[api-usage.md](references/api-usage.md)** - REST API, Terraform provider, Python SDK, webhooks, automation examples
- **[troubleshooting.md](references/troubleshooting.md)** - Connectivity diagnostics, subnet router issues, exit node issues, SSH problems, MagicDNS, performance tuning, common error messages
- **[production-setup.md](references/production-setup.md)** - Architecture patterns, HA setup, security hardening, IaC (Terraform/Ansible/K8s), monitoring, DR, operational procedures
## Scripts
- **`scripts/setup_subnet_router.sh <subnet_cidr> [auth_key]`** - Automated subnet router setup (installs Tailscale, enables IP forwarding, configures routes)
- **`scripts/setup_exit_node.sh [auth_key]`** - Automated exit node setup (installs Tailscale, enables IP forwarding, advertises as exit node)