Understanding Headscale ACLs and Users

I spent quite a bit of time while I was learning Headscale being confused about users. Here are some key points that helped me understand them.

Here are some things to keep in mind about Headscale users and ACLs that might make your Headscale configuration less confusing:

  • If you are using OIDC, users nodes will be be created in a “user” according to their login name.
  • “Users” can be used as the src or dst in an ACL.
  • “Users” can be grouped, and users can be in more than one group.
  • The “user” also appears in the MagicDNS: nodename.user.base_domain.
  • Nodes that have tags will not get logged out or be subject to the “oidc expiry” time.
  • “autogroup:self” does not exist in headscale at this moment. None of the “autogroups” do.

Here is how I’m using OIDC, ACLs and users in my organization:

  • I have OIDC set up with our Google “Workspace” Business account.
  • Our people can then install tailscale and login with Google. They are then put into a group based in the username they logged in with (left of the @ in their email address).
  • People have to re-login on their devices every 15 days.
  • Our servers are all in an “ovl” “user”. This is for “overlay network”.
  • Our servers all connected via “tailscale up –accept-routes=false”, because our firewalls advertise routes to the LANs, and tailscale will overwrite the routing so that LAN traffic is forced to go over the tailnet.
  • Our servers all have tags based on their roles and locations, so they won’t be logged out.
  • In our ACLs, I have two groups defined: “ops” and “devs”. All the “ops” are in the “devs” group as well.
  • “group:devs” have extensive access to development machines and limited access to staging and production tagged resources (things like viewing logs and graphs).
  • “group:ops” have extensive access to staging and production and other protected resources.
  • People are able to reach their own and other peoples devices by DNS using: nodename.user and servers via hostname.env.ovl, because the servers nodenames are short_hostname.envname.

Additional, non-standard things:

  • I have a small script that uses the group membership and headscale user-to-IP mappings to generate ipset lists for the different groups. So our legacy firewalling can use those IP sets to tell what group a user is in and apply the correct set of firewall rules to them.
  • The longer term plan is to have people no longer accessing machines via their LAN addresses through the firewall and it’s iptables, but to directly access machines on the tailnet and use ACLs for access.