mirror of
https://github.com/juanfont/headscale.git
synced 2026-11-04 04:34:07 +09:00
policy/v2: compile a policy without rules as explicit allow-all
As SaaS, sources are the tailnet ranges and approved routes. They were "*", so a host on an exit node's network could reach its clients.
This commit is contained in:
1 parent
5741dba87d
commit
d2a85b1d16
6 files changed
+25
-17
No files matched your search
@@ -6,6 +6,7 @@ import (
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
@@ -132,12 +133,29 @@ func (pol *Policy) compileFilterRules(
|
||||
nodes views.Slice[types.NodeView],
|
||||
) []tailcfg.FilterRule {
|
||||
if pol == nil || (pol.ACLs == nil && pol.Grants == nil) {
|
||||
return tailcfg.FilterAllowAll
|
||||
return noRulesFilter(users, nodes)
|
||||
}
|
||||
|
||||
return globalFilterRules(pol.compileGrants(users, nodes))
|
||||
}
|
||||
|
||||
// allowAllPolicy is the rule a policy without acls or grants stands for.
|
||||
var allowAllPolicy = sync.OnceValue(func() *Policy {
|
||||
pol, err := unmarshalPolicy([]byte(`{"acls":[{"action":"accept","src":["*"],"dst":["*:*"]}]}`))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return pol
|
||||
})
|
||||
|
||||
// noRulesFilter is the filter of a policy without acls or grants. As on SaaS,
|
||||
// its sources are the tailnet's ranges and approved routes, not every address,
|
||||
// or a host on an exit node's network could open connections to its clients.
|
||||
func noRulesFilter(users types.Users, nodes views.Slice[types.NodeView]) []tailcfg.FilterRule {
|
||||
return globalFilterRules(allowAllPolicy().compileGrants(users, nodes))
|
||||
}
|
||||
|
||||
func (pol *Policy) destinationsToNetPortRange(
|
||||
users types.Users,
|
||||
nodes views.Slice[types.NodeView],
|
||||
@@ -201,7 +219,7 @@ func (pol *Policy) compileFilterRulesForNode(
|
||||
nodes views.Slice[types.NodeView],
|
||||
) []tailcfg.FilterRule {
|
||||
if pol == nil {
|
||||
return tailcfg.FilterAllowAll
|
||||
return noRulesFilter(users, nodes)
|
||||
}
|
||||
|
||||
grants := pol.compileGrants(users, nodes)
|
||||
|
||||
@@ -3687,7 +3687,7 @@ func TestFilterAllowAllFix(t *testing.T) {
|
||||
|
||||
rules := tt.pol.compileFilterRules(users, nodes)
|
||||
|
||||
isFilterAllowAll := cmp.Diff(tailcfg.FilterAllowAll, rules) == ""
|
||||
isFilterAllowAll := cmp.Diff(noRulesFilter(users, nodes), rules) == ""
|
||||
assert.Equal(t, tt.wantFilterAllow, isFilterAllowAll,
|
||||
"FilterAllowAll mismatch: got rules=%v", rules)
|
||||
})
|
||||
|
||||
@@ -342,7 +342,7 @@ func (pm *PolicyManager) updateLocked() (bool, error) {
|
||||
|
||||
var filter []tailcfg.FilterRule
|
||||
if pm.pol == nil || (pm.pol.ACLs == nil && pm.pol.Grants == nil) {
|
||||
filter = tailcfg.FilterAllowAll
|
||||
filter = noRulesFilter(pm.users, pm.nodes)
|
||||
} else {
|
||||
filter = globalFilterRules(pm.compiledGrants)
|
||||
}
|
||||
|
||||
@@ -74,8 +74,8 @@ func TestPolicyManager(t *testing.T) {
|
||||
name: "empty-policy",
|
||||
pol: "{}",
|
||||
nodes: types.Nodes{},
|
||||
wantFilter: tailcfg.FilterAllowAll,
|
||||
wantMatchers: matcher.MatchesFromFilterRules(tailcfg.FilterAllowAll),
|
||||
wantFilter: noRulesFilter(users, types.Nodes{}.ViewSlice()),
|
||||
wantMatchers: matcher.MatchesFromFilterRules(noRulesFilter(users, types.Nodes{}.ViewSlice())),
|
||||
},
|
||||
{
|
||||
name: "empty-acls-denies-all",
|
||||
|
||||
@@ -208,15 +208,6 @@ func loadACLTestFile(t *testing.T, path string) *testcapture.Capture {
|
||||
// aclSkipReasons lists checks where headscale differs from SaaS, keyed
|
||||
// <test_id>[/<node>]/<check>.
|
||||
var aclSkipReasons = map[string]string{
|
||||
"acl-n01/beedrill/filter": allowAllForm,
|
||||
"acl-n01/bulbasaur/filter": allowAllForm,
|
||||
"acl-n01/charmander/filter": allowAllForm,
|
||||
"acl-n01/ivysaur/filter": allowAllForm,
|
||||
"acl-n01/kakuna/filter": allowAllForm,
|
||||
"acl-n01/squirtle/filter": allowAllForm,
|
||||
"acl-n01/venusaur/filter": allowAllForm,
|
||||
"acl-n01/weedle/filter": allowAllForm,
|
||||
|
||||
"acl-au06/beedrill/filter": srcAddressForm,
|
||||
"acl-k03/beedrill/filter": srcAddressForm,
|
||||
"acl-k03/bulbasaur/filter": srcAddressForm,
|
||||
@@ -246,7 +237,6 @@ var aclSkipReasons = map[string]string{
|
||||
}
|
||||
|
||||
const (
|
||||
allowAllForm = "ALLOW_ALL_FORM: SaaS writes the allow-all default's sources as the tailnet ranges, headscale as \"*\""
|
||||
srcAddressForm = "FILTER_ADDRESS_FORM: SaaS lists contiguous source addresses one by one, headscale as a range"
|
||||
srcAndSelfDstForm = "FILTER_ADDRESS_FORM+SELF_DST_REPEAT: sources listed one by one, the autogroup:self destination written once per source alias"
|
||||
aclDefaultRouteAccepted = "ACL_DEFAULT_ROUTE_ACCEPTED: headscale accepts literal 0.0.0.0/0 and ::/0 ACL destinations; SaaS rejects them and requires * or autogroup:internet"
|
||||
|
||||
@@ -339,7 +339,7 @@ func evaluateTests(pol *Policy, users []types.User, nodes views.Slice[types.Node
|
||||
|
||||
var filter []tailcfg.FilterRule
|
||||
if pol.ACLs == nil && pol.Grants == nil {
|
||||
filter = tailcfg.FilterAllowAll
|
||||
filter = noRulesFilter(users, nodes)
|
||||
} else {
|
||||
filter = globalFilterRules(grants)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user