Skip to content

Make matchers part of the Policy interface #2514

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

aergus-tng
Copy link

@aergus-tng aergus-tng commented Apr 1, 2025

This PR makes the Matches that are computed from FilterRules part of the Policy interface, so that they don't have to be recomputed at each call to Node.CanAccess. There have already been some discussion on this approach in #2416.

  • 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

@@ -529,7 +529,8 @@ func appendPeerChanges(
// If there are filter rules present, see if there are any nodes that cannot
// access each-other at all and remove them from the peers.
if len(filter) > 0 {
changed = policy.FilterNodesByACL(node, changed, filter)
matchers := polMan.Matchers()
Copy link
Author

Choose a reason for hiding this comment

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

It occurred to me after the implementation that this could be subject to race conditions -- the ACLs could be updated between the call polMan.Filter() and the call to polMan.Matchers().

Would it maybe make more sense if Policy.Filter() returned a tuple ([]tailcfg.FilterRule, []matcher.Match) so that the returned filter rules and matches are always in sync?

Copy link
Collaborator

Choose a reason for hiding this comment

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

That is a good point, I think that is fair, particularly since it will just be returned and not computed. can probably just underscore the matchers when we dont need them.

Copy link
Author

Choose a reason for hiding this comment

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

Done (and also added matchers to tests where the Policy function is tested).

@aergus-tng
Copy link
Author

Given that there is already a changelog section on the policy rework, do you think that a separate entry is necessary for this change?

@@ -69,6 +71,9 @@ func (pm *PolicyManager) updateLocked() (bool, error) {
filterChanged := filterHash == pm.filterHash
pm.filter = filter
pm.filterHash = filterHash
if filterChanged {
Copy link
Collaborator

Choose a reason for hiding this comment

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

There is a couple of failing tests, not sure why, I wonder if this somehow could be the cause if it prevents the matchers from being generated in some conditions, like empty policies? I really do not think it should be related, but I cant see anything else special about v2.

Copy link
Author

@aergus-tng aergus-tng Apr 7, 2025

Choose a reason for hiding this comment

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

Isn't the filterChanged actually computed incorrectly a few lines above? Wouldn't filterChanged := filterHash != pm.filterHash make more sense? Should I change that?

Copy link
Collaborator

Choose a reason for hiding this comment

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

oh dear, you are right... yes please.

Copy link
Author

Choose a reason for hiding this comment

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

Done :)

Copy link

review-ai-agent bot commented Apr 7, 2025

Pull Request Revisions

RevisionDescription
r2
Test case matchers become empty sliceModified policy test case to initialize wantMatchers as an empty slice instead of nil
r1
Policy filter matcher precomputation addedModified policy handling to precompute filter matchers, improving efficiency by avoiding repeated matcher generation during access checks
✅ AI review completed for r2

@@ -66,9 +68,12 @@ func (pm *PolicyManager) updateLocked() (bool, error) {
}

filterHash := deephash.Hash(&filter)
filterChanged := filterHash == pm.filterHash
filterChanged := filterHash != pm.filterHash
Copy link
Collaborator

Choose a reason for hiding this comment

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

I see that only SSH tests in v2 fail now, I suspect that it is because there was a bug here that never returned "early" on line 102, causing 110 (clear ssh) to always run. Now that it does return early, I suspect that my logic for when the ssh needs to be clear is wrong, and it wasnt discovered because of this.

Copy link
Author

Choose a reason for hiding this comment

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

I don't know if this occurs in the integration tests, but here is an example where I think updateLocked behaves incorrectly with the early return: If only the SSHs field of the policy changes, the call to updateLocked in SetPolicy would AFAICT return false, nil, but the SSH policy needs to be recompiled nevertheless in this case.

Would it maybe make sense to just always clear the SSH policy map or add an additional guard checking whether the SSHs field of the policy has changed?

@kradalby kradalby added this to the v0.26.0 milestone Apr 14, 2025
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.

3 participants