Use case
Headscale currently does not model Tailscale-style user roles such as admin or owner. As a result, tailcfg.CapabilityAdmin (https://tailscale.com/cap/is-admin) is emitted as an always-on baseline instead of being restricted to users that should actually be considered administrators.
This makes self-hosted admin workflows harder to expose cleanly. Tailscale clients derive the admin URL from the configured control server URL (ControlURL + "/admin"). A Headscale deployment can redirect /admin to a local admin surface, but it cannot make that client-side admin entry point visible only to actual administrators.
A practical deployment may want:
- users in
group:admin to receive the admin capability;
- regular client groups to use the tailnet normally without receiving admin-oriented UI/actions;
- tagged service or exit nodes to inherit admin capability only when their tag owner maps to an admin-capable identity.
Description
Please add a way for Headscale policy to gate tailcfg.CapabilityAdmin by user, group, tag ownership, or a first-class Headscale role mapping.
The exact syntax is not important, but something policy-driven would be useful. For example:
{
"groups": {
"group:admin": ["admin@example.com"],
"group:client": ["user@example.com"]
},
"nodeAttrs": [
{
"target": ["group:admin"],
"attr": ["https://tailscale.com/cap/is-admin"]
}
]
}
or a first-class role mapping:
{
"roles": {
"admin": ["group:admin"],
"owner": ["admin@example.com"]
}
}
Expected behavior:
- admin users/groups receive
tailcfg.CapabilityAdmin;
- non-admin users/groups do not receive
tailcfg.CapabilityAdmin;
- tagged nodes receive/inherit admin capability only when the relevant tag ownership maps to an admin-capable identity;
- existing deployments have a backwards-compatible default or migration flag.
Network ACLs alone do not solve this. They can restrict traffic, but they do not control the Tailscale client's admin capability or whether the client receives an admin-oriented control-plane action/link.
Contribution
How can it be implemented?
One possible implementation path:
- introduce a minimal Headscale role model, initially only
admin and optionally owner;
- allow role membership to be sourced from policy groups and explicit users;
- use that role state when building node maps, instead of emitting
tailcfg.CapabilityAdmin unconditionally;
- make tagged-node behavior follow tag ownership where possible;
- keep the current always-on behavior behind a compatibility default or migration option.
There is related upstream context in tailnet_state_caps.go: CapabilityAdmin is documented as user-role gated in Tailscale's hosted control plane, while Headscale currently has no user-role model and emits it as part of the always-on baseline. The comment also points at autogroup:admin support as the long-term fix.
Use case
Headscale currently does not model Tailscale-style user roles such as admin or owner. As a result,
tailcfg.CapabilityAdmin(https://tailscale.com/cap/is-admin) is emitted as an always-on baseline instead of being restricted to users that should actually be considered administrators.This makes self-hosted admin workflows harder to expose cleanly. Tailscale clients derive the admin URL from the configured control server URL (
ControlURL + "/admin"). A Headscale deployment can redirect/adminto a local admin surface, but it cannot make that client-side admin entry point visible only to actual administrators.A practical deployment may want:
group:adminto receive the admin capability;Description
Please add a way for Headscale policy to gate
tailcfg.CapabilityAdminby user, group, tag ownership, or a first-class Headscale role mapping.The exact syntax is not important, but something policy-driven would be useful. For example:
or a first-class role mapping:
Expected behavior:
tailcfg.CapabilityAdmin;tailcfg.CapabilityAdmin;Network ACLs alone do not solve this. They can restrict traffic, but they do not control the Tailscale client's admin capability or whether the client receives an admin-oriented control-plane action/link.
Contribution
How can it be implemented?
One possible implementation path:
adminand optionallyowner;tailcfg.CapabilityAdminunconditionally;There is related upstream context in
tailnet_state_caps.go:CapabilityAdminis documented as user-role gated in Tailscale's hosted control plane, while Headscale currently has no user-role model and emits it as part of the always-on baseline. The comment also points atautogroup:adminsupport as the long-term fix.