-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Update ACL syntax and add support for protocol filtering #618
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
Changes from 9 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
3e35300
Migrate ACLs syntax to new Tailscale format
juanfont ab1aac9
Improve ACLs by adding protocol parsing support
juanfont 8287ba2
Do not lint the protocol magic numbers
juanfont 39f03b8
Added ACL test file
juanfont c47354b
Update internal docs to the new syntax
juanfont 818d26b
Updated changelog
juanfont 5bc1189
Update internal docs with protocol usage
juanfont 19b9688
Added missing file
juanfont 80ad1db
Merge branch 'main' into acl-syntax-fixes
juanfont 735a6aa
Use const for IANA protcol numbers
juanfont cdf41bd
Merge branch 'acl-syntax-fixes' of https://github.com/juanfont/headsc…
juanfont 3d7be5b
Minor rename
juanfont 7cd0f5e
Merge branch 'main' into acl-syntax-fixes
kradalby 569f3ca
Use constants in tests
kradalby File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -23,6 +23,7 @@ const ( | |||||
errInvalidGroup = Error("invalid group") | ||||||
errInvalidTag = Error("invalid tag") | ||||||
errInvalidPortFormat = Error("invalid port format") | ||||||
errWildcardIsNeeded = Error("wildcard as port is required for the protocol") | ||||||
) | ||||||
|
||||||
const ( | ||||||
|
@@ -123,23 +124,31 @@ func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) { | |||||
} | ||||||
|
||||||
srcIPs := []string{} | ||||||
for innerIndex, user := range acl.Users { | ||||||
srcs, err := h.generateACLPolicySrcIP(machines, *h.aclPolicy, user) | ||||||
for innerIndex, src := range acl.Sources { | ||||||
srcs, err := h.generateACLPolicySrcIP(machines, *h.aclPolicy, src) | ||||||
if err != nil { | ||||||
log.Error(). | ||||||
Msgf("Error parsing ACL %d, User %d", index, innerIndex) | ||||||
Msgf("Error parsing ACL %d, Source %d", index, innerIndex) | ||||||
|
||||||
return nil, err | ||||||
} | ||||||
srcIPs = append(srcIPs, srcs...) | ||||||
} | ||||||
|
||||||
protocols, needsWildcard, err := parseProtocol(acl.Protocol) | ||||||
if err != nil { | ||||||
log.Error(). | ||||||
Msgf("Error parsing ACL %d. protocol unknown %s", index, acl.Protocol) | ||||||
|
||||||
return nil, err | ||||||
} | ||||||
|
||||||
destPorts := []tailcfg.NetPortRange{} | ||||||
for innerIndex, ports := range acl.Ports { | ||||||
dests, err := h.generateACLPolicyDestPorts(machines, *h.aclPolicy, ports) | ||||||
for innerIndex, dest := range acl.Destinations { | ||||||
dests, err := h.generateACLPolicyDest(machines, *h.aclPolicy, dest, needsWildcard) | ||||||
if err != nil { | ||||||
log.Error(). | ||||||
Msgf("Error parsing ACL %d, Port %d", index, innerIndex) | ||||||
Msgf("Error parsing ACL %d, Destination %d", index, innerIndex) | ||||||
|
||||||
return nil, err | ||||||
} | ||||||
|
@@ -149,6 +158,7 @@ func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) { | |||||
rules = append(rules, tailcfg.FilterRule{ | ||||||
SrcIPs: srcIPs, | ||||||
DstPorts: destPorts, | ||||||
IPProto: protocols, | ||||||
}) | ||||||
} | ||||||
|
||||||
|
@@ -158,17 +168,18 @@ func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) { | |||||
func (h *Headscale) generateACLPolicySrcIP( | ||||||
machines []Machine, | ||||||
aclPolicy ACLPolicy, | ||||||
u string, | ||||||
src string, | ||||||
) ([]string, error) { | ||||||
return expandAlias(machines, aclPolicy, u, h.cfg.OIDC.StripEmaildomain) | ||||||
return expandAlias(machines, aclPolicy, src, h.cfg.OIDC.StripEmaildomain) | ||||||
} | ||||||
|
||||||
func (h *Headscale) generateACLPolicyDestPorts( | ||||||
func (h *Headscale) generateACLPolicyDest( | ||||||
machines []Machine, | ||||||
aclPolicy ACLPolicy, | ||||||
d string, | ||||||
dest string, | ||||||
needsWildcard bool, | ||||||
) ([]tailcfg.NetPortRange, error) { | ||||||
tokens := strings.Split(d, ":") | ||||||
tokens := strings.Split(dest, ":") | ||||||
if len(tokens) < expectedTokenItems || len(tokens) > 3 { | ||||||
return nil, errInvalidPortFormat | ||||||
} | ||||||
|
@@ -195,7 +206,7 @@ func (h *Headscale) generateACLPolicyDestPorts( | |||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
ports, err := expandPorts(tokens[len(tokens)-1]) | ||||||
ports, err := expandPorts(tokens[len(tokens)-1], needsWildcard) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
|
@@ -214,6 +225,54 @@ func (h *Headscale) generateACLPolicyDestPorts( | |||||
return dests, nil | ||||||
} | ||||||
|
||||||
// parseProtocol reads the proto field of the ACL and generates a list of | ||||||
// protocols that will be allowed, following the IANA IP protocol number | ||||||
// https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml | ||||||
// | ||||||
// If the ACL proto field is empty, it allows ICMPv4, ICMPv6, TCP, and UDP, | ||||||
// as per Tailscale behaviour (see tailcfg.FilterRule). | ||||||
// | ||||||
// Also returns a boolean indicating if the protocol | ||||||
// requires all the destinations to use wildcard as port number (only TCP, | ||||||
// UDP and SCTP support specifying ports). | ||||||
func parseProtocol(protocol string) ([]int, bool, error) { | ||||||
switch protocol { | ||||||
case "": | ||||||
return []int{1, 58, 6, 17}, false, nil | ||||||
case "igmp": | ||||||
return []int{2}, true, nil | ||||||
case "ipv4", "ip-in-ip": | ||||||
return []int{4}, true, nil | ||||||
case "tcp": | ||||||
return []int{6}, false, nil | ||||||
case "egp": | ||||||
return []int{8}, true, nil | ||||||
case "igp": | ||||||
return []int{9}, true, nil | ||||||
case "udp": | ||||||
return []int{17}, false, nil | ||||||
case "gre": | ||||||
return []int{47}, true, nil | ||||||
case "esp": | ||||||
return []int{50}, true, nil | ||||||
case "ah": | ||||||
return []int{51}, true, nil | ||||||
case "sctp": | ||||||
return []int{132}, false, nil | ||||||
case "icmp": | ||||||
return []int{1, 58}, true, nil | ||||||
|
||||||
default: | ||||||
protocolNumber, err := strconv.Atoi(protocol) | ||||||
if err != nil { | ||||||
return nil, false, err | ||||||
} | ||||||
needsWildcard := protocolNumber != 6 && protocolNumber != 17 && protocolNumber != 132 // nolint | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
That wont do ;) |
||||||
|
||||||
return []int{protocolNumber}, needsWildcard, nil | ||||||
} | ||||||
} | ||||||
|
||||||
// expandalias has an input of either | ||||||
// - a namespace | ||||||
// - a group | ||||||
|
@@ -268,6 +327,7 @@ func expandAlias( | |||||
alias, | ||||||
) | ||||||
} | ||||||
|
||||||
return ips, nil | ||||||
} else { | ||||||
return ips, err | ||||||
|
@@ -359,13 +419,17 @@ func excludeCorrectlyTaggedNodes( | |||||
return out | ||||||
} | ||||||
|
||||||
func expandPorts(portsStr string) (*[]tailcfg.PortRange, error) { | ||||||
func expandPorts(portsStr string, needsWildcard bool) (*[]tailcfg.PortRange, error) { | ||||||
if portsStr == "*" { | ||||||
return &[]tailcfg.PortRange{ | ||||||
{First: portRangeBegin, Last: portRangeEnd}, | ||||||
}, nil | ||||||
} | ||||||
|
||||||
if needsWildcard { | ||||||
return nil, errWildcardIsNeeded | ||||||
} | ||||||
|
||||||
ports := []tailcfg.PortRange{} | ||||||
for _, portStr := range strings.Split(portsStr, ",") { | ||||||
rang := strings.Split(portStr, "-") | ||||||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of these numbers will trigger "magicnumber" when the linter is up and running, and in any case, it would be better to probably have constants for these