[Bug] Headscale exit node won´t let me access the internet when limited to specific user #967

Closed
opened 2025-12-29 02:26:49 +01:00 by adam · 5 comments
Owner

Originally created by @Lodeiro0001 on GitHub (Mar 10, 2025).

Is this a support request?

  • This is not a support request

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

Hi, I´ve configured my nodes to be only accessible by their respective users (I don´t want every user to be able to see other users´nodes).
But after doing so, I'm still able to see my exit node, but I can´t access the internet through them

Expected Behavior

Each user should only be able to see their nodes, and they should be able to access the internet when they select their respective exit node

Steps To Reproduce

Limit each user to only see their nodes and not every users´. My current ACL config bellow.
After doing this, users can only see their respective nodes, but cannot connect to the internet (exit nodes are visible and I can select them as exit nodes, but I´m not able to access the internet)

Environment

- OS:Debian 12
- Headscale version: 0.25.1
- Tailscale version: latest

Runtime environment

  • Headscale is behind a (reverse) proxy
  • Headscale runs in a container

Anything else?

My current ACL config is as follows:

  "groups": {},
  "tagOwners": {},
  "hosts": {},
  "acls": [
    {
      "action": "accept",
      "src": [
        "user1"
      ],
      "dst": [
       "user1:*"
      ]
    },
    {
      "action": "accept",
      "src": [
        "user2"
      ],
      "dst": [
       "user2:*"
      ]
    }
  ],
  "ssh": []
}
Originally created by @Lodeiro0001 on GitHub (Mar 10, 2025). ### Is this a support request? - [x] This is not a support request ### Is there an existing issue for this? - [x] I have searched the existing issues ### Current Behavior Hi, I´ve configured my nodes to be only accessible by their respective users (I don´t want every user to be able to see other users´nodes). But after doing so, I'm still able to see my exit node, but I can´t access the internet through them ### Expected Behavior Each user should only be able to see their nodes, and they should be able to access the internet when they select their respective exit node ### Steps To Reproduce Limit each user to only see their nodes and not every users´. My current ACL config bellow. After doing this, users can only see their respective nodes, but cannot connect to the internet (exit nodes are visible and I can select them as exit nodes, but I´m not able to access the internet) ### Environment ```markdown - OS:Debian 12 - Headscale version: 0.25.1 - Tailscale version: latest ``` ### Runtime environment - [x] Headscale is behind a (reverse) proxy - [x] Headscale runs in a container ### Anything else? My current ACL config is as follows: ```{ "groups": {}, "tagOwners": {}, "hosts": {}, "acls": [ { "action": "accept", "src": [ "user1" ], "dst": [ "user1:*" ] }, { "action": "accept", "src": [ "user2" ], "dst": [ "user2:*" ] } ], "ssh": [] }
adam added the bug label 2025-12-29 02:26:49 +01:00
adam closed this issue 2025-12-29 02:26:49 +01:00
Author
Owner

@nblock commented on GitHub (Mar 15, 2025):

Your issue has a few interestings topics, let's discuss it one after the other.

I'm still able to see my exit node, but I can´t access the internet through them

Yes, your ACL does not have the required autogroup:internet rule for exit nodes. Your nodes will be able to use an exit node as soon as you add this block to your ACL configuration:

{
  "action": "accept",
  "src": [
    "user1",
    "user2"
  ],
  "dst": [
    "autogroup:internet:*"
  ]
}

Each user should only be able to see their nodes, and they should be able to access the internet when they select their respective exit node

To me, this indicates a setup where each user has their individual nodes (laptops, phones, …) and a dedicated exit node that should only be used by the respective user. This design is problematic for two reasons:

  • Headscale as of 0.25 does not provide ACL Grants and Via which is required to force a specific exit node for a user. You'd need an ACL rule similar to Route traffic through exit nodes based on location.
  • A user can access all services of their respective exit node. With your provided ACL example, a phone from user1 is able to for example login to the exit node via SSH. This is probably not what you want.

Here is an alternative approach (tested with Debian 12, Tailscale 1.80.3 and Headscale 0.25.1):

  • Each user has their individual nodes assigned to them (laptop, phone, …): n1, n2
  • A dedicated user, for example infra has exit nodes assigned to them: n3
$ headscale user list
ID | Name | Username | Email | Created            
1  |      | user1    |       | 2025-03-15 08:49:49
2  |      | infra    |       | 2025-03-15 08:49:52
$ headscale nodes list
ID | Hostname | Name | MachineKey | NodeKey | User  | IP addresses                  | Ephemeral | Last seen           | Expiration | Connected | Expired
2  | n2       | n2   | [SEIRq]    | [xPyKh] | user1 | 100.64.0.2, fd7a:115c:a1e0::2 | false     | 2025-03-15 10:24:48 | N/A        | online    | no     
3  | n3       | n3   | [iwCtq]    | [KF+nA] | infra | 100.64.0.3, fd7a:115c:a1e0::3 | false     | 2025-03-15 10:38:03 | N/A        | online    | no     
4  | n1       | n1   | [8Z5Fk]    | [lNUZv] | user1 | 100.64.0.4, fd7a:115c:a1e0::4 | false     | 2025-03-15 10:37:53 | N/A        | online    | no
$ headscale routes list
ID | Node | Prefix    | Advertised | Enabled | Primary
1  | n3   | 0.0.0.0/0 | true       | true    | -      
2  | n3   | ::/0      | true       | true    | -  

With this minimal ACL:

{
  "acls": [
    {
      "action": "accept",
      "src": [
        "user1"
      ],
      "dst": [
        "user1:*"
      ]
    },
    {
      "action": "accept",
      "src": [
        "user1"
      ],
      "dst": [
        "autogroup:internet:*"
      ]
    }
  ]
}

you get:

  • The nodes of user1 can communicate freely with each other
  • The nodes n1 and n2 can use n3 as exit node
  • The nodes n1 and n2 can't use any services offered by n3. They can ping n3 and access the Internet through n3, but nothing more.

Does this provide a suitable solution for your use case?

@nblock commented on GitHub (Mar 15, 2025): Your issue has a few interestings topics, let's discuss it one after the other. > I'm still able to see my exit node, but I can´t access the internet through them Yes, your ACL does *not* have the required [`autogroup:internet` rule for exit nodes](https://tailscale.com/kb/1103/exit-nodes#prerequisites). Your nodes will be able to use an exit node as soon as you add this block to your ACL configuration: ```json { "action": "accept", "src": [ "user1", "user2" ], "dst": [ "autogroup:internet:*" ] } ``` --- > Each user should only be able to see their nodes, and they should be able to access the internet when they select their respective exit node To me, this indicates a setup where each user has their individual nodes (laptops, phones, …) and a dedicated exit node that should only be used by the respective user. This design is problematic for two reasons: * Headscale as of 0.25 does not provide ACL [Grants and Via](https://tailscale.com/kb/1324/grants) which is required to force a *specific* exit node for a user. You'd need an ACL rule similar to [Route traffic through exit nodes based on location](https://tailscale.com/kb/1458/grant-samples#route-traffic-through-exit-nodes-based-on-location). * A user can access all services of their respective exit node. With your provided ACL example, a phone from `user1` is able to for example login to the exit node via SSH. This is probably not what you want. Here is an alternative approach (tested with Debian 12, Tailscale 1.80.3 and Headscale 0.25.1): * Each user has their individual nodes assigned to them (laptop, phone, …): `n1`, `n2` * A dedicated user, for example `infra` has exit nodes assigned to them: `n3` ```bash $ headscale user list ID | Name | Username | Email | Created 1 | | user1 | | 2025-03-15 08:49:49 2 | | infra | | 2025-03-15 08:49:52 ``` ```bash $ headscale nodes list ID | Hostname | Name | MachineKey | NodeKey | User | IP addresses | Ephemeral | Last seen | Expiration | Connected | Expired 2 | n2 | n2 | [SEIRq] | [xPyKh] | user1 | 100.64.0.2, fd7a:115c:a1e0::2 | false | 2025-03-15 10:24:48 | N/A | online | no 3 | n3 | n3 | [iwCtq] | [KF+nA] | infra | 100.64.0.3, fd7a:115c:a1e0::3 | false | 2025-03-15 10:38:03 | N/A | online | no 4 | n1 | n1 | [8Z5Fk] | [lNUZv] | user1 | 100.64.0.4, fd7a:115c:a1e0::4 | false | 2025-03-15 10:37:53 | N/A | online | no ``` ```bash $ headscale routes list ID | Node | Prefix | Advertised | Enabled | Primary 1 | n3 | 0.0.0.0/0 | true | true | - 2 | n3 | ::/0 | true | true | - ``` With this minimal ACL: ```json { "acls": [ { "action": "accept", "src": [ "user1" ], "dst": [ "user1:*" ] }, { "action": "accept", "src": [ "user1" ], "dst": [ "autogroup:internet:*" ] } ] } ``` you get: * The nodes of `user1` can communicate freely with each other * The nodes `n1` and `n2` can use `n3` as exit node * The nodes `n1` and `n2` can't use any services offered by `n3`. They can ping `n3` and access the Internet through `n3`, but nothing more. Does this provide a suitable solution for your use case?
Author
Owner

@Lodeiro0001 commented on GitHub (Mar 15, 2025):

Thank you for your help. Unfortunately, what you mentioned does not solve my use case. Let me detail my scenario and current configuration:

$ headscale user list
ID | Name | Username | Email | Created            
1  |      | user1    |       | XXX
2  |      | user2    |       | XXX
$ headscale nodes list
ID | Hostname | Name | MachineKey | NodeKey | User  | IP addresses                  | Ephemeral | Last seen           | Expiration | Connected | Expired
2  | n1       | n1   | [XXX]    | [XXX] | user1 | XXX | false     | XXX | N/A        | online    | no     
3  | n2       | n2   | [XXX]    | [XXX] | user1 | XXX | false     | XXX | N/A        | online    | no     
4  | n3       | n3   | [XXX]    | [XXX] | user2 | XXX | false     | XXX | N/A        | online    | no
4  | n4       | n4   | [XXX]    | [XXX] | user2 | XXX | false     | XXX | N/A        | online    | no
$ headscale routes list
ID | Node | Prefix    | Advertised | Enabled | Primary
1  | n2   | 0.0.0.0/0 | true       | true    | -      
2  | n2   | ::/0      | true       | true    | -  
3  | n4   | 0.0.0.0/0 | true       | true    | -      
4  | n4   | ::/0      | true       | true    | - 

As it is reflected, I have two users, each with two nodes. Let's suppose that n1 is a smartphone and n2 is server 1, all belonging to user1. We also consider that n3 is another smartphone and n4 is server 2, These last two belong to user2.

With my initial ACL, user1 could only see their own server 1 and smartphone. User2 could also only see their own smartphone and server 2. That's what I want. The problem is that there was no internet access when selecting the exit node.

Now I’ve tried modifying the ACLs with what you mentioned, and it looks like this:

{
  "groups": {},
  "tagOwners": {},
  "hosts": {},
  "acls": [
    {
      "action": "accept",
      "src": [
        "user1"
      ],
      "dst": [
       "user1:*"
      ]
    },
    {
      "action": "accept",
      "src": [
        "user2"
      ],
      "dst": [
       "user2:*"
      ]
    },
    {
      "action": "accept",
      "src": [
        "user1", "user2"
      ],
      "dst": [
        "autogroup:internet:*"
      ]
    }
  ],
  "ssh": []
}

What happens now:

User1 can see n2, which is server 1, and use it as an exit node, with the internet connection working. But they can also see n4, which belongs to user2, and they can use it as an exit node (server 2). Similarly, user2 can use n4 as an exit node, but they can also use n2, which belongs to the other user.

What I need:

User1 should only be able to see and use n2 as the exit node, while user2 should only be able to see and use n4 as the exit node.

@Lodeiro0001 commented on GitHub (Mar 15, 2025): Thank you for your help. Unfortunately, what you mentioned does not solve my use case. Let me detail my scenario and current configuration: ``` bash $ headscale user list ID | Name | Username | Email | Created 1 | | user1 | | XXX 2 | | user2 | | XXX ``` ``` bash $ headscale nodes list ID | Hostname | Name | MachineKey | NodeKey | User | IP addresses | Ephemeral | Last seen | Expiration | Connected | Expired 2 | n1 | n1 | [XXX] | [XXX] | user1 | XXX | false | XXX | N/A | online | no 3 | n2 | n2 | [XXX] | [XXX] | user1 | XXX | false | XXX | N/A | online | no 4 | n3 | n3 | [XXX] | [XXX] | user2 | XXX | false | XXX | N/A | online | no 4 | n4 | n4 | [XXX] | [XXX] | user2 | XXX | false | XXX | N/A | online | no ``` ``` bash $ headscale routes list ID | Node | Prefix | Advertised | Enabled | Primary 1 | n2 | 0.0.0.0/0 | true | true | - 2 | n2 | ::/0 | true | true | - 3 | n4 | 0.0.0.0/0 | true | true | - 4 | n4 | ::/0 | true | true | - ``` As it is reflected, I have two users, each with two nodes. Let's suppose that n1 is a smartphone and n2 is server 1, all belonging to user1. We also consider that n3 is another smartphone and n4 is server 2, These last two belong to user2. With my initial ACL, user1 could only see their own server 1 and smartphone. User2 could also only see their own smartphone and server 2. That's what I want. The problem is that there was no internet access when selecting the exit node. Now I’ve tried modifying the ACLs with what you mentioned, and it looks like this: ``` json { "groups": {}, "tagOwners": {}, "hosts": {}, "acls": [ { "action": "accept", "src": [ "user1" ], "dst": [ "user1:*" ] }, { "action": "accept", "src": [ "user2" ], "dst": [ "user2:*" ] }, { "action": "accept", "src": [ "user1", "user2" ], "dst": [ "autogroup:internet:*" ] } ], "ssh": [] } ``` What happens now: User1 can see n2, which is server 1, and use it as an exit node, with the internet connection working. But they can also see n4, which belongs to user2, and they can use it as an exit node (server 2). Similarly, user2 can use n4 as an exit node, but they can also use n2, which belongs to the other user. What I need: User1 should only be able to see and use n2 as the exit node, while user2 should only be able to see and use n4 as the exit node.
Author
Owner

@nblock commented on GitHub (Mar 15, 2025):

What happens now:

User1 can see n2, which is server 1, and use it as an exit node, with the internet connection working. But they can also see n4, which belongs to user2, and they can use it as an exit node (server 2). Similarly, user2 can use n4 as an exit node, but they can also use n2, which belongs to the other user.

Yes, this is expected. A exit node is visible to all users of the network.

What I need:

User1 should only be able to see and use n2 as the exit node, while user2 should only be able to see and use n4 as the exit node.

AFAIK, this use case is currently not supported with Headscale, as it requires support for Grant and via. See also:

@nblock commented on GitHub (Mar 15, 2025): > What happens now: > > User1 can see n2, which is server 1, and use it as an exit node, with the internet connection working. But they can also see n4, which belongs to user2, and they can use it as an exit node (server 2). Similarly, user2 can use n4 as an exit node, but they can also use n2, which belongs to the other user. Yes, this is expected. A exit node is visible to all users of the network. > What I need: > > User1 should only be able to see and use n2 as the exit node, while user2 should only be able to see and use n4 as the exit node. AFAIK, this use case is currently not supported with Headscale, as it requires support for [Grant and via](https://tailscale.com/kb/1324/grants#via). See also: * https://github.com/tailscale/tailscale/issues/1567 * https://tailscale.com/kb/1458/grant-samples#route-traffic-through-exit-nodes-based-on-location * https://github.com/juanfont/headscale/issues/2409 * https://github.com/juanfont/headscale/issues/657
Author
Owner

@Lodeiro0001 commented on GitHub (Mar 15, 2025):

Ok, thank you very much for your help.

@Lodeiro0001 commented on GitHub (Mar 15, 2025): Ok, thank you very much for your help.
Author
Owner

@kradalby commented on GitHub (May 21, 2025):

Since this ended up being another feature request for via support, I will close this a s a duplicate of https://github.com/juanfont/headscale/issues/2409

@kradalby commented on GitHub (May 21, 2025): Since this ended up being another feature request for `via` support, I will close this a s a duplicate of https://github.com/juanfont/headscale/issues/2409
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/headscale#967