[Feature] "grants" feature support
Hello. Does Headscale support the new "grants" field in the ACL file?
https://tailscale.com/blog/acl-grants https://tailscale.com/kb/1324/grants
AFAIK no, but that could be a great feature to add. In my use case, I would like to let groups of users use only a subset of all the exit nodes
We do not, I imagine we will eventually work on it, but fixing the current acls (and other things) will be a priority so I would not expect this for a while.
Thank you for your reply, but I’d like to know whether all the efforts to fix ACLs will also be applicable to the future implementation of grants. If I understand correctly, Tailscale treats grants as a replacement for the existing (legacy) ACLs. In that case, would it be reasonable to skip implementing some of the legacy logic?
That’s what I was coming to ask if grants replace legacy acl and grants are required to support all the new features shouldn’t that be pivoted to? I don’t know the internal ramifications tho
Hello, I'm currently looking into “grant” implementation, primarily aiming to support the new Peer-relay feature, which requires apps field under grant (#2841 ).
I've discovered that "ip" section is merely the same with ACL and can use the same logic. The FilterRules in tailcfg also include a CapGrant field, which appears to be directly usable.
I'm currently working on this section: the fork branch is located at https://github.com/Victrid/headscale/tree/grant.
My goal is to initially implement the capability for the app and ip fields. I'm not yet clear on how the Tailscale program handles via and postures fields, so I'm not planning to implement related operations at this stage. The current implementation can already accomplish Peer-Relay (https://github.com/juanfont/headscale/issues/2841, https://tailscale.com/kb/1591/peer-relays) (Unlike the official server, it requires manually adding the relay-target cap on the reverse side).
Details
Set the relay port on lighthouse servers
tailscale set --relay-server-port=40000
acl.json
{
...
"tagOwners": {
"tag:lighthouse": ["gix@"],
},
"grants": [ // My default grants
{
"src": ["*"],
"dst": ["*"],
"ip": ["*"]
},
{ // The "Peer-Relay" option
"src": ["*"],
"dst": ["tag:lighthouse"],
"app": {
"tailscale.com/cap/relay": []
}
},
{
"src": ["tag:lighthouse"],
"dst": ["*"],
"app": {
"tailscale.com/cap/relay-target": [] // Require this to be set manually
}
},
],
...
}
Using commit:
https://github.com/Victrid/headscale/commit/eab0396a158a8696c38c8eafa58cd6dfda871896
I have a built docker image from Dockerfile.integration at victrid/headscale:git-eab0396a
Results
At first glance, this part doesn't introduce any new external dependencies, should be incremental to current ACL, and doesn't appear to involve significant logical inconsistencies (at least according to the grant syntax migration guide).
If the maintainers are interested in this potential PR, I should be able to provide bugfix-level maintenance for this feature at least by the end of 2026.
Wow nicely done, looking over the code i'm a little shocked it was that easy to turn up lol
It seems that "via" (#2409 ) and "postures" (#2458 ) are likely should be implemented within the control node, which performs as a filter to current ACL rules.
It seems that the implementations of these two is similar: The posture implementation is quite straightforward (if don't need to support the 3rd party postures) when distributing NetMap, simply apply the posture rules to the source during filterForNode - ReduceFilterRules. Implementing via requires this along with corresponding filtering of routes broadcast by Peers when distributing Peer's info.
Implementing this with the current filtering method might introduce extra computing overhead. My plan is to use aliases (along with corresponding postures/vias) as indices to configure a 2-way reverse lookup set for each node and route, and rules. By this way, each group/tag only needs to be enumerated once.
Regarding the via rules, as described in #2865, my understanding is that we first need a rule allowing A to see B. Then, using autogroup:internet, A should then be able to obtain B's routes. This differs from the current implementation where, once A can connect to B, A immediately sees all of B's routes (#2852 , #2788?). In other words:
grant: {
src = A,
dst = B, C,
ip = *
}
With this, A can see B as a peer, but B does not expose its routes:
A:
Peers: [{
{ B:
“AllowedIPs”: [
“100.64.0.2/32”
],}
{ C:
“AllowedIPs”: [
“100.64.0.3/32”
],}]
Then configure autogroup:internet to distribute exit node routes to A:
grant: [{
src = A,
dst = B, C,
ip = *
},
{
src = A,
dst = “autogroup:internet”,
ip = *
}]
A:
Peers: [
{ B:
“AllowedIPs”: [
“0.0.0.0/0”,
“100.64.0.2/32”
],}
{ C:
“AllowedIPs”: [
“0.0.0.0/0”,
“100.64.0.3/32”
],}
]
Configuring via is to restrict the scope of routes distributed by exit nodes:
grant: [{
src = A,
dst = B, C,
ip = *
},
{
src = A,
dst = “autogroup:internet”,
via = B
ip = *
}]
This configuration results in:
A:
Peers: [
{ B:
“AllowedIPs”: [
“0.0.0.0/0”,
“100.64.0.2/32”
],}
{ C:
“AllowedIPs”: [ // No exit nodes!
“100.64.0.3/32”
],}
]
If this filtering part is fixed, implementing via will also be very straightforward.
If this filtering part is fixed, implementing
viawill also be very straightforward.
Just a note, via should also be available to sub routes.
// Example copied from official website
// The following example demonstrates a scenario in which the engineering team group can access a 192.0.2.0/24 using any available router if they comply with the latestMac posture (which ensures they are running the latest stable version of the Tailscale client for macOS). Anyone else (autogroup:member) can access 192.0.2.0/24 using the designated office router (tag:office-router).
"postures": {
"posture:latestMac": [
"node:os == 'macos'",
"node:osVersion == '13.4.0'",
"node:tsReleaseTrack == 'stable'",
]
},
"grants": [
{
"src": ["group:eng"],
"srcPosture": ["posture:latestMac"],
"dst": ["192.0.2.0/24"],
"ip": ["*"],
},
{
"src": ["autogroup:member"],
"dst": ["192.0.2.0/24"],
"via": ["tag:office-router"],
"ip": ["*"],
},
]
Combined with high availability features, some complex routing health checks and switching logic may be required.
I just want to chime in with a few thoughts, some not researched yet, but mostly to manage expectations.
I think it is important to separate what grant support means, I would say it is two things:
- A new, more modern syntax
- Unblock the implementation of new features:
appor capabilitiesviahttps://tailscale.com/kb/1378/viasrcPosture(but this could already have been done inacls- Maybe even
ipsetshttps://tailscale.com/kb/1387/ipsets - IP Pool (https://github.com/juanfont/headscale/issues/2912)
of the top of my head.
As it seems to have been discovered here, implementing 1. should be fairly straight forward. It is more or less a way different way to represent the current acls with a slightly different syntax.
However, I suspect that each feature under 2. vary quite a lot in the complexity and while they are all doable over time, I do not think that "grants support" should include all these features.
I think we need to ask ourselves, "what is the minimum value we can get from grants over the acls" and I think the answer to that is app capabilities. So I would argue that we should focus on implementing the new grants syntax and app capabilities and then work from there.
As for a little implementation detail. We have a lot (but not enough) policy tests that are currently running against the ACLs. I think that the most valuable part to implementing grants would be to implement a converter for our current acls to grants so we can have the tests run fully as both ACLs and as Grants. That should allow us to keep acls around, and have great test coverage.
I imagine something like:
- Implement grant types (and reuse what can be reused) in policy v2.
- Implement a converter of ACL rules to Grants
- Remove all specific ACL types, fully relying on Grants
- Add app capability support
I was discussing with @nblock some weeks ago that we probably would want to rework the roadmap a bit, and likely swap the features in 0.29.0 and 0.31.0, moving CLI/API changes back two releases and making grants a priority for 0.29.0.