Skip to content

[Feature] Support group-gated admin capability for Tailscale clients #3367

Description

@nos1609

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

  • I can write the design doc for this feature
  • I can contribute this feature

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions