L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Déploiement et opérations · SKILL.md

tailnet-policy

tailnet-policy

Author, test, and deploy Tailscale-compatible huJSON policy files for Headscale tailnets — ACLs, Grants, Tags, Auto Approvers, Tailscale SSH rules. Use when configuring access control, writing policy files, or troubleshooting connectivity issues caused by ACLs.

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentation de la Skill

GitHub

name: tailnet-policy description: Author, test, and deploy Tailscale-compatible huJSON policy files for Headscale tailnets — ACLs, Grants, Tags, Auto Approvers, Tailscale SSH rules. Use when configuring access control, writing policy files, or troubleshooting connectivity issues caused by ACLs. metadata: category: devops

tailnet-policy

Overview

Headscale uses Tailscale-compatible policy files written in huJSON (Human JSON — standard JSON with trailing commas and // comments). Policy files control:

  • ACLs (deprecated legacy syntax) — {action, users, ports} rules
  • Grants (modern syntax) — {src, dst, ip, proto, via} rules
  • Tags — Node identity tags (tag:dev, tag:prod)
  • TagOwners — Which users/groups can apply which tags
  • AutoApprovers — Auto-approval for subnet routers and exit nodes
  • Tailscale SSH — SSH access rules via ssh.users and ssh.action

ACLs vs Grants

FeatureACLs (legacy)Grants (modern)
Format{action: "accept", users: [...], ports: [...]}{src: [...], dst: [...], ip: [...], proto: "tcp"}
Port filteringEmbedded in ports: ["*:*"]Separate ip field for ports
Protocol filteringNot supportedproto field (tcp, udp, icmp)
Destination routingNot supportedvia field for relay/exit nodes
StatusDeprecated by TailscaleCurrent recommended syntax

Use Grants wherever possible. The migrate-acls-to-grants.py script can convert legacy ACL files automatically.

Policy Location

The policy file path is configured in Headscale's config.yaml:

policy:
  path: /etc/headscale/policy.hujson

After modifying the policy file, reload it on the Headscale server:

# Reload via SIGHUP
kill -HUP $(pgrep headscale)

# Or use the convenience script
./skills/tailnet-policy/reload-headscale-policy.sh

Writing Policy

Allow-All (default-open)

{
  // Grants that allow all traffic
  "grants": [
    {
      "src": ["autogroup:member"],
      "dst": ["autogroup:member"],
      "ip": ["*:*"]
    }
  ],
  // Tag ownership
  "tagOwners": {
    "tag:dev": ["autogroup:admin"],
    "tag:prod": ["autogroup:admin"]
  }
}

Deny-All (default-closed)

{
  "grants": [
    // Only allow ICMP (ping) between all members
    {
      "src": ["autogroup:member"],
      "dst": ["autogroup:member"],
      "ip": ["*"],
      "proto": "icmp"
    }
  ],
  // Specific grants added per-service
  "tagOwners": {
    "tag:monitor": ["autogroup:admin"]
  }
}

Segmented (environments)

{
  "grants": [
    // Dev can reach dev
    {
      "src": ["tag:dev"],
      "dst": ["tag:dev"],
      "ip": ["*:*"]
    },
    // Prod can reach prod
    {
      "src": ["tag:prod"],
      "dst": ["tag:prod"],
      "ip": ["*:*"]
    },
    // Admin access to all
    {
      "src": ["autogroup:admin"],
      "dst": ["tag:dev", "tag:prod"],
      "ip": ["*:*"]
    }
  ],
  "tagOwners": {
    "tag:dev": ["autogroup:admin"],
    "tag:prod": ["autogroup:admin"]
  }
}

Tag-Based Patterns

Tags are node-level identifiers set via tailscale up --advertise-tags=tag:dev. They decouple policy from user identity.

{
  "tagOwners": {
    "tag:ci-runner":  ["autogroup:admin"],
    "tag:database":   ["autogroup:admin"],
    "tag:webserver":  ["autogroup:admin"],
    "tag:monitoring": ["autogroup:admin"]
  },
  "grants": [
    {
      "src": ["tag:monitoring"],
      "dst": ["tag:webserver", "tag:database"],
      "ip": ["*:*"]
    },
    {
      "src": ["tag:webserver"],
      "dst": ["tag:database"],
      "ip": ["tcp:5432"]
    }
  ]
}

Grants Syntax

Grants are the modern policy primitive:

{
  "grants": [
    {
      "src": ["tag:source", "user@example.com"],
      "dst": ["tag:destination", "100.64.0.1"],
      "ip": ["*:*"],                    // proto:port — "*:*" means all
      "proto": "tcp",                   // optional protocol filter
      "via": ["tag:exit-node"]          // optional via/routing
    }
  ]
}

Fields:

  • src — Source entities (tags, users, autogroups, IPs)
  • dst — Destination entities
  • ip — Protocol and port filter (e.g. tcp:80, udp:53, *:*, *)
  • proto — Protocol constraint (tcp, udp, icmp)
  • via — Route through a specific exit node or relay

Auto Approvers

Auto-approvers let specific users approve subnet routes and exit nodes without manual intervention:

{
  "autoApprovers": {
    "routes": {
      "10.0.0.0/8": ["autogroup:admin"],
      "172.16.0.0/12": ["alice@example.com"]
    },
    "exitNode": ["autogroup:admin"]
  }
}
  • routes: Maps CIDR ranges to lists of users who can auto-approve those routes
  • exitNode: Lists users who can advertise exit nodes

Autogroups

Autogroups are dynamic groups resolved by Headscale/Tailscale at runtime:

AutogroupDescription
autogroup:memberAll tailnet members
autogroup:adminTailnet admins
autogroup:taggedAll tagged nodes (any node with at least one tag)
autogroup:internetThe public internet (used for exit node routing)

Tailscale SSH Configuration

Tailscale SSH rules are configured via the ssh section:

{
  "ssh": [
    {
      "action": "accept",         // "accept" or "check"
      "src": ["autogroup:admin"],
      "dst": ["tag:webserver"],
      "users": ["root", "ubuntu"]
    },
    {
      "action": "check",          // "check" requires node-level SSH authorization
      "src": ["autogroup:member"],
      "dst": ["tag:dev"],
      "users": ["*"]
    }
  ]
}
  • action: "accept" (allow directly) or "check" (require node-level auth)
  • src: Source users/groups
  • dst: Destination tags/users
  • users: Which OS users can be SSH'd into

Testing Policies

Policy files include test definitions that are validated when loaded:

{
  "grants": [...],
  "tests": [
    {
      "src": "alice@example.com",
      "dst": "tag:webserver",
      "ip": ["tcp:443"],
      "action": "accept"  // expected result
    },
    {
      "src": "bob@example.com",
      "dst": "tag:database",
      "ip": ["tcp:22"],
      "action": "drop"    // expected result
    }
  ]
}

Validate tests with:

./skills/tailnet-policy/validate-policy.py --policy policy.hujson

Gotchas

  • Headscale does NOT support device posture — rules like devicePosture or device:managed are Tailscale-only
  • Headscale does NOT support IP sets — ipSets and ipprotocol are not supported
  • Headscale does NOT support OIDC groups in ACLs — groups from OIDC claims cannot be used in policy; use autogroup:admin and autogroup:member instead
  • Tag names must start with tag: and contain only lowercase letters, numbers, and hyphens
  • Policy reload via SIGHUP does not return errors on failure — always validate before reloading
  • ACL users field and Grant src field are NOT interchangeable — grants use src/dst, legacy ACLs use users/ports
  • Tests are not enforced at runtime — they only validate during parsing; a passing test does not guarantee runtime behavior

Trigger Conditions

This skill is automatically loaded when the user's message contains any of these keywords:

  • tailnet policy
  • headscale acl
  • hujson policy
  • tailscale grant
  • tagowners
  • autoapprovers
  • tailscale ssh policy
  • policy validation
  • migrate acls
  • /etc/headscale/policy
  • policy.hujson

When not to use

Do not use this skill for deploying the Headscale server (load headscale-deploy instead) or for client connectivity issues unrelated to access control (load tailscale-client). It covers huJSON policy authoring and testing only.

Skills enregistrées