feat(headscale): add file-based ACL policy

Ship policy.hujson (mounted + installed on first deploy, edits preserved) and
wire policy.mode=file / policy.path in config.yaml. Translate the Tailscale
"grants" default into headscale's legacy "acls" format (self-access, tag:shared,
Tailscale SSH), since headscale 0.28 doesn't support grants. Embed in deploy.sh
and document `headscale policy check`.
This commit is contained in:
2026-06-12 16:04:24 -05:00
parent 25f20037e9
commit 573785f2cc
6 changed files with 167 additions and 64 deletions
+16
View File
@@ -46,3 +46,19 @@ Unattended provisioning: [`cloud-init.yml`](cloud-init.yml).
DNS for `HEADSCALE_DOMAIN` must resolve to the host and 80/443 be reachable
before deploy.
## ACL policy
A starter ACL ships in [`policy.hujson`](policy.hujson) (installed to
`$STACK_DIR/policy.hujson` on first deploy; your edits survive re-deploys).
**headscale uses Tailscale's legacy `acls` format, not the newer `grants`
syntax** from the Tailscale admin console — the bundled policy is that default
translated over (per-user self-access, a `tag:shared` everyone can reach, and
SSH to your own devices). Some pieces (`autogroup:self`, the `ssh` block) are
newer/experimental in headscale; a permissive fallback is included in the file.
```bash
$EDITOR /srv/headscale/policy.hujson
headscale policy check # validate (host CLI wrapper)
cd /srv/headscale && docker compose restart headscale # apply
```
+5 -5
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
#
# build.sh -- (re)embed docker-compose.yml, Caddyfile, config.yaml, and
# .env.example into deploy.sh as a base64-encoded tar.gz payload after the
# __ARCHIVE_BELOW__ marker. Idempotent: strips any existing payload first.
# build.sh -- (re)embed docker-compose.yml, Caddyfile, config.yaml,
# policy.hujson and .env.example into deploy.sh as a base64-encoded tar.gz
# payload after the __ARCHIVE_BELOW__ marker. Idempotent: strips first.
#
# Run after editing any of the loose files. The resulting deploy.sh is
# self-contained and can be scp'd to the target box on its own.
@@ -14,12 +14,12 @@ SCRIPT="$DIR/deploy.sh"
MARKER="__ARCHIVE_BELOW__"
[[ -f "$SCRIPT" ]] || { echo "deploy.sh not found at $SCRIPT" >&2; exit 1; }
for f in docker-compose.yml Caddyfile config.yaml .env.example; do
for f in docker-compose.yml Caddyfile config.yaml policy.hujson .env.example; do
[[ -f "$DIR/$f" ]] || { echo "Missing $DIR/$f" >&2; exit 1; }
done
PAYLOAD=$(tar -czf - -C "$DIR" \
docker-compose.yml Caddyfile config.yaml .env.example | base64)
docker-compose.yml Caddyfile config.yaml policy.hujson .env.example | base64)
TMP=$(mktemp)
trap 'rm -f "$TMP"' EXIT
+6
View File
@@ -44,6 +44,12 @@ log:
level: info
format: text
# ACL policy, loaded from a HuJSON file (see policy.hujson, mounted into the
# container). headscale uses Tailscale's legacy "acls" format, not "grants".
policy:
mode: file
path: /etc/headscale/policy.hujson
# Magic DNS for the tailnet. base_domain becomes the suffix for node
# DNS names (e.g. laptop.__TAILNET_DOMAIN__).
dns:
+90 -59
View File
@@ -22,9 +22,9 @@
# regenerates config.yaml from the current .env so edits to .env propagate
# (but secrets in .env are never touched once seeded).
#
# Self-contained: docker-compose.yml, Caddyfile, config.yaml, .env.example
# are embedded as a base64-encoded tar.gz at the bottom of this file.
# Rebuild with build.sh after editing the loose source files.
# Self-contained: docker-compose.yml, Caddyfile, config.yaml, policy.hujson
# and .env.example are embedded as a base64-encoded tar.gz at the bottom of
# this file. Rebuild with build.sh after editing the loose source files.
#
# Usage:
# bash deploy.sh # interactive prompts
@@ -130,7 +130,7 @@ else
die "No embedded archive found. Run build.sh to embed deployment files."
fi
for f in docker-compose.yml Caddyfile config.yaml .env.example; do
for f in docker-compose.yml Caddyfile config.yaml policy.hujson .env.example; do
[[ -f "$SCRIPT_DIR/$f" ]] || die "Embedded archive missing $f"
done
@@ -213,6 +213,16 @@ set -a; . "$ENV_FILE"; set +a
log "Generating config.yaml from template..."
install -m 0600 "$SCRIPT_DIR/config.yaml" "$STACK_DIR/config.yaml"
# ACL policy: install the default on first run only, so your edits survive
# re-deploys. headscale reads it on start (policy mode = file); after editing,
# validate with `headscale policy check` and `docker compose restart headscale`.
if [[ ! -f "$STACK_DIR/policy.hujson" ]]; then
log "Installing default ACL policy -> $STACK_DIR/policy.hujson"
install -m 0640 "$SCRIPT_DIR/policy.hujson" "$STACK_DIR/policy.hujson"
else
log "ACL policy exists; leaving $STACK_DIR/policy.hujson alone."
fi
# Escape replacement values for sed (handle slashes/&). Headscale URLs and
# OIDC secrets can contain those.
sed_escape() { printf '%s' "$1" | sed -e 's/[\/&]/\\&/g'; }
@@ -301,6 +311,10 @@ Stack dir: ${STACK_DIR}
OIDC redirect/callback URI -- register THIS in your pocket-id client:
https://${HEADSCALE_DOMAIN}/oidc/callback
ACL policy: ${STACK_DIR}/policy.hujson
headscale policy check # validate after editing
cd ${STACK_DIR} && docker compose restart headscale # apply changes
Headscale CLI -- use the installed wrapper from anywhere:
headscale users create alice
headscale users list
@@ -327,58 +341,75 @@ EOF
# __ARCHIVE_BELOW__ marker is the embedded tar.gz payload (base64).
exit 0
__ARCHIVE_BELOW__
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
ea3kewBQAAA=
+1
View File
@@ -55,6 +55,7 @@ services:
- /var/run/headscale
volumes:
- ./config.yaml:/etc/headscale/config.yaml:ro
- ./policy.hujson:/etc/headscale/policy.hujson:ro
- headscale-data:/var/lib/headscale
healthcheck:
test: ["CMD", "headscale", "health"]
+49
View File
@@ -0,0 +1,49 @@
// headscale ACL policy (HuJSON: JSON with // comments and trailing commas).
//
// IMPORTANT: headscale uses Tailscale's *legacy* policy format -- the top-level
// "acls" array -- NOT the newer "grants" syntax from the Tailscale admin
// console. This file is the grants-style default translated into "acls".
//
// Edit, then:
// headscale policy check # validate (uses the wrapper)
// docker compose restart headscale # apply (policy mode = file)
//
// A few features here (autogroup:self, the ssh block) are newer/experimental
// in headscale and may depend on your version. If headscale rejects this
// policy at start, switch to the permissive fallback at the bottom.
{
// Static user groups, e.g. "group:admins": ["alice@example.com"].
"groups": {},
// Who may apply which tags to devices.
"tagOwners": {
// tag:shared -- a device any member is allowed to reach.
"tag:shared": ["autogroup:member"],
},
// Network access rules (first match wins; default-deny otherwise).
"acls": [
// Each user can reach their own devices, on all ports.
{ "action": "accept", "src": ["autogroup:member"], "dst": ["autogroup:self:*"] },
// Anyone can reach devices tagged tag:shared, on all ports.
{ "action": "accept", "src": ["*"], "dst": ["tag:shared:*"] },
],
// Tailscale SSH rules (nodes must run `tailscale up --ssh`).
"ssh": [
// SSH into your own devices. "check" = periodic browser re-auth (the
// Tailscale default). Use "accept" if your headscale lacks check mode.
{
"action": "check",
"src": ["autogroup:member"],
"dst": ["autogroup:self"],
"users": ["autogroup:nonroot", "root"],
},
],
// ---- Permissive fallback ----------------------------------------------
// Replace the "acls" above with this (and drop "ssh") if the rules are
// rejected, to allow everything while you iterate:
// "acls": [ { "action": "accept", "src": ["*"], "dst": ["*:*"] } ],
}