Skip to content

feat: add autogroup:member, autogroup:tagged #2572

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 17, 2025

Conversation

vdovhanych
Copy link
Contributor

@vdovhanych vdovhanych commented May 7, 2025

This implements two autogroups autogroup:member and autogroup:tagged. I talked with @kradalby about leaving out autogroup:self for now, as that is a bit trickier in the end.

The logic of the autogroups and test is taken from the draft pr #2230, but it has been implemented for the new policy structure.

  • have read the CONTRIBUTING.md file
  • raised a GitHub issue or discussed it on the projects chat beforehand
  • added unit tests
  • added integration tests
  • updated documentation if needed
  • updated CHANGELOG.md

Copy link

review-ai-agent bot commented May 7, 2025

Pull Request Revisions

RevisionDescription
r3
Added support for autogroup member, taggedImplemented autogroup:member and autogroup:tagged in policy system, updating ACL resolving logic and test coverage
r2
Enhance autogroup member and tagged logicImproved autogroup:member and autogroup:tagged resolution with more nuanced tag handling and ownership checks
r1
New autogroups support added nowImplemented autogroup:member and autogroup:tagged in policy resolution, updating ACL handling and test coverage

✅ AI review completed for r3
Help React with emojis to give feedback on AI-generated reviews:
  • 👍 means the feedback was helpful and actionable
  • 👎 means the feedback was incorrect or unhelpful
💬 Replying to feedback with a comment helps us improve the system. Your input also contributes to shaping future interactions with the AI reviewer.

We'd love to hear from you—reach out anytime at [email protected].

Comment on lines 1286 to 1298
func allIPs() *netipx.IPSet {
if allIPSet != nil {
return allIPSet
}

var build netipx.IPSetBuilder
build.AddPrefix(netip.MustParsePrefix("::/0"))
build.AddPrefix(netip.MustParsePrefix("0.0.0.0/0"))

allTheIps, _ := build.IPSet()

return allTheIps
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In allIPs() function, the IPSet is being built but not stored in the global allIPSet variable. This means the caching mechanism isn't working - the function always builds a new set even if it's already been created.

Comment on lines +1143 to +1253
},
},
},
2,
)
defer scenario.ShutdownAssertNoPanics(t)

allClients, err := scenario.ListTailscaleClients()
require.NoError(t, err)

err = scenario.WaitForTailscaleSync()
require.NoError(t, err)

// Test that untagged nodes can access each other
for _, client := range allClients {
status, err := client.Status()
require.NoError(t, err)
if status.Self.Tags != nil && status.Self.Tags.Len() > 0 {
continue
}

for _, peer := range allClients {
if client.Hostname() == peer.Hostname() {
continue
}

status, err := peer.Status()
require.NoError(t, err)
if status.Self.Tags != nil && status.Self.Tags.Len() > 0 {
continue
}

fqdn, err := peer.FQDN()
require.NoError(t, err)

url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url)

result, err := client.Curl(url)
assert.Len(t, result, 13)
require.NoError(t, err)
}
}
}

func TestACLAutogroupTagged(t *testing.T) {
IntegrationSkip(t)
t.Parallel()

scenario := aclScenario(t,
&policyv1.ACLPolicy{
ACLs: []policyv1.ACL{
{
Action: "accept",
Sources: []string{"autogroup:tagged"},
Destinations: []string{"autogroup:tagged:*"},
},
},
},
2,
)
defer scenario.ShutdownAssertNoPanics(t)

allClients, err := scenario.ListTailscaleClients()
require.NoError(t, err)

err = scenario.WaitForTailscaleSync()
require.NoError(t, err)

// Test that tagged nodes can access each other
for _, client := range allClients {
status, err := client.Status()
require.NoError(t, err)
if status.Self.Tags == nil || status.Self.Tags.Len() == 0 {
continue
}

for _, peer := range allClients {
if client.Hostname() == peer.Hostname() {
continue
}

status, err := peer.Status()
require.NoError(t, err)
if status.Self.Tags == nil || status.Self.Tags.Len() == 0 {
continue
}

fqdn, err := peer.FQDN()
require.NoError(t, err)

url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url)

result, err := client.Curl(url)
assert.Len(t, result, 13)
require.NoError(t, err)
}
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The integration tests for autogroup:member and autogroup:tagged have duplicated code structures. Consider extracting a common test helper function that takes the autogroup type and condition check as parameters to reduce duplication and increase maintainability.

@@ -122,6 +122,7 @@ working in v1 and not tested might be broken in v2 (and vice versa).
[#2438](https://github.com/juanfont/headscale/pull/2438)
- Add documentation for routes
[#2496](https://github.com/juanfont/headscale/pull/2496)
- Add support for `autogroup:member`, `autogroup:tagged`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets add the PR to this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 2b3ee90

@kradalby
Copy link
Collaborator

kradalby commented May 9, 2025

Great start!

Comment on lines +1162 to +1194
require.NoError(t, err)

err = scenario.WaitForTailscaleSync()
require.NoError(t, err)

// Test that untagged nodes can access each other
for _, client := range allClients {
status, err := client.Status()
require.NoError(t, err)
if status.Self.Tags != nil && status.Self.Tags.Len() > 0 {
continue
}

for _, peer := range allClients {
if client.Hostname() == peer.Hostname() {
continue
}

status, err := peer.Status()
require.NoError(t, err)
if status.Self.Tags != nil && status.Self.Tags.Len() > 0 {
continue
}

fqdn, err := peer.FQDN()
require.NoError(t, err)

url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url)

result, err := client.Curl(url)
assert.Len(t, result, 13)
require.NoError(t, err)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both TestACLAutogroupMember and TestACLAutogroupTagged contain similar error handling and test setup logic. Consider extracting a helper function to reduce duplication and improve maintainability.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message in validateAutogroupForSSHDst incorrectly states 'for SSH sources' but should say 'for SSH destinations' to match the function's purpose.

Copy link
Contributor Author

@vdovhanych vdovhanych left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the comments are for me to better understand the logic, i was getting lost in it a bit.

@@ -122,6 +122,7 @@ working in v1 and not tested might be broken in v2 (and vice versa).
[#2438](https://github.com/juanfont/headscale/pull/2438)
- Add documentation for routes
[#2496](https://github.com/juanfont/headscale/pull/2496)
- Add support for `autogroup:member`, `autogroup:tagged`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 2b3ee90

@vdovhanych vdovhanych requested a review from kradalby May 13, 2025 12:09
Copy link
Collaborator

@kradalby kradalby left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks great, I am happy to merge this and add more tests as we continue testing.

@vdovhanych
Copy link
Contributor Author

I think this looks great, I am happy to merge this and add more tests as we continue testing.

Okay, cool. I will squash the fixup commit.
Also, @kradalby do you think I should remove some comments from the code? They were more for me than something essential to have there.

@kradalby
Copy link
Collaborator

None of them seem to out of place, let's leave them and then we can update them as needed

@kradalby
Copy link
Collaborator

I'll run the tests when you have squashed and if all passes I'll merge it

@vdovhanych vdovhanych force-pushed the add-autogroup-member-and-tagged branch from 2b3ee90 to 54d7e61 Compare May 16, 2025 10:34
@vdovhanych
Copy link
Contributor Author

Squashed

Comment on lines +1143 to +1253
},
},
},
2,
)
defer scenario.ShutdownAssertNoPanics(t)

allClients, err := scenario.ListTailscaleClients()
require.NoError(t, err)

err = scenario.WaitForTailscaleSync()
require.NoError(t, err)

// Test that untagged nodes can access each other
for _, client := range allClients {
status, err := client.Status()
require.NoError(t, err)
if status.Self.Tags != nil && status.Self.Tags.Len() > 0 {
continue
}

for _, peer := range allClients {
if client.Hostname() == peer.Hostname() {
continue
}

status, err := peer.Status()
require.NoError(t, err)
if status.Self.Tags != nil && status.Self.Tags.Len() > 0 {
continue
}

fqdn, err := peer.FQDN()
require.NoError(t, err)

url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url)

result, err := client.Curl(url)
assert.Len(t, result, 13)
require.NoError(t, err)
}
}
}

func TestACLAutogroupTagged(t *testing.T) {
IntegrationSkip(t)
t.Parallel()

scenario := aclScenario(t,
&policyv1.ACLPolicy{
ACLs: []policyv1.ACL{
{
Action: "accept",
Sources: []string{"autogroup:tagged"},
Destinations: []string{"autogroup:tagged:*"},
},
},
},
2,
)
defer scenario.ShutdownAssertNoPanics(t)

allClients, err := scenario.ListTailscaleClients()
require.NoError(t, err)

err = scenario.WaitForTailscaleSync()
require.NoError(t, err)

// Test that tagged nodes can access each other
for _, client := range allClients {
status, err := client.Status()
require.NoError(t, err)
if status.Self.Tags == nil || status.Self.Tags.Len() == 0 {
continue
}

for _, peer := range allClients {
if client.Hostname() == peer.Hostname() {
continue
}

status, err := peer.Status()
require.NoError(t, err)
if status.Self.Tags == nil || status.Self.Tags.Len() == 0 {
continue
}

fqdn, err := peer.FQDN()
require.NoError(t, err)

url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url)

result, err := client.Curl(url)
assert.Len(t, result, 13)
require.NoError(t, err)
}
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The integration tests for autogroup:member and autogroup:tagged have nearly identical structure and logic. Consider extracting a common test helper function that takes the autogroup type and filter conditions as parameters to reduce duplication.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the validateAutogroupForSSHDst function, the error message incorrectly refers to 'SSH sources' instead of 'SSH destinations', which could be confusing during troubleshooting.

@kradalby kradalby merged commit 6750414 into juanfont:main May 17, 2025
277 of 284 checks passed
@kradalby kradalby mentioned this pull request May 21, 2025
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants