From 3e353004b842c30e9823860036d85be8cf3adf80 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Wed, 8 Jun 2022 13:40:15 +0200 Subject: [PATCH] Migrate ACLs syntax to new Tailscale format Implements #617. Tailscale has changed the format of their ACLs to use a more firewall-y terms ("users" & "ports" -> "src" & "dst"). They have also started using all-lowercase tags. This PR applies these changes. --- acls.go | 22 +++---- acls_test.go | 28 ++++----- acls_types.go | 23 ++++---- machine_test.go | 4 +- tests/acls/acl_policy_1.hujson | 57 ++++++++++--------- tests/acls/acl_policy_basic_1.hujson | 10 ++-- tests/acls/acl_policy_basic_groups.hujson | 12 ++-- .../acl_policy_basic_namespace_as_user.hujson | 10 ++-- tests/acls/acl_policy_basic_range.hujson | 10 ++-- tests/acls/acl_policy_basic_wildcards.hujson | 8 +-- tests/acls/acl_policy_basic_wildcards.yaml | 10 ++-- tests/acls/acl_policy_invalid.hujson | 56 +++++++++--------- 12 files changed, 126 insertions(+), 124 deletions(-) diff --git a/acls.go b/acls.go index 2ca65393..c7376ee0 100644 --- a/acls.go +++ b/acls.go @@ -123,11 +123,11 @@ 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 } @@ -135,11 +135,11 @@ func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) { } 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) 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 } @@ -158,17 +158,17 @@ 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, ) ([]tailcfg.NetPortRange, error) { - tokens := strings.Split(d, ":") + tokens := strings.Split(dest, ":") if len(tokens) < expectedTokenItems || len(tokens) > 3 { return nil, errInvalidPortFormat } diff --git a/acls_test.go b/acls_test.go index e91e95d9..9a7d8a69 100644 --- a/acls_test.go +++ b/acls_test.go @@ -62,7 +62,7 @@ func (s *Suite) TestBasicRule(c *check.C) { func (s *Suite) TestInvalidAction(c *check.C) { app.aclPolicy = &ACLPolicy{ ACLs: []ACL{ - {Action: "invalidAction", Users: []string{"*"}, Ports: []string{"*:*"}}, + {Action: "invalidAction", Sources: []string{"*"}, Destinations: []string{"*:*"}}, }, } err := app.UpdateACLRules() @@ -70,14 +70,14 @@ func (s *Suite) TestInvalidAction(c *check.C) { } func (s *Suite) TestInvalidGroupInGroup(c *check.C) { - // this ACL is wrong because the group in users sections doesn't exist + // this ACL is wrong because the group in Sources sections doesn't exist app.aclPolicy = &ACLPolicy{ Groups: Groups{ "group:test": []string{"foo"}, "group:error": []string{"foo", "group:test"}, }, ACLs: []ACL{ - {Action: "accept", Users: []string{"group:error"}, Ports: []string{"*:*"}}, + {Action: "accept", Sources: []string{"group:error"}, Destinations: []string{"*:*"}}, }, } err := app.UpdateACLRules() @@ -88,7 +88,7 @@ func (s *Suite) TestInvalidTagOwners(c *check.C) { // this ACL is wrong because no tagOwners own the requested tag for the server app.aclPolicy = &ACLPolicy{ ACLs: []ACL{ - {Action: "accept", Users: []string{"tag:foo"}, Ports: []string{"*:*"}}, + {Action: "accept", Sources: []string{"tag:foo"}, Destinations: []string{"*:*"}}, }, } err := app.UpdateACLRules() @@ -97,8 +97,8 @@ func (s *Suite) TestInvalidTagOwners(c *check.C) { // this test should validate that we can expand a group in a TagOWner section and // match properly the IP's of the related hosts. The owner is valid and the tag is also valid. -// the tag is matched in the Users section. -func (s *Suite) TestValidExpandTagOwnersInUsers(c *check.C) { +// the tag is matched in the Sources section. +func (s *Suite) TestValidExpandTagOwnersInSources(c *check.C) { namespace, err := app.CreateNamespace("user1") c.Assert(err, check.IsNil) @@ -131,7 +131,7 @@ func (s *Suite) TestValidExpandTagOwnersInUsers(c *check.C) { Groups: Groups{"group:test": []string{"user1", "user2"}}, TagOwners: TagOwners{"tag:test": []string{"user3", "group:test"}}, ACLs: []ACL{ - {Action: "accept", Users: []string{"tag:test"}, Ports: []string{"*:*"}}, + {Action: "accept", Sources: []string{"tag:test"}, Destinations: []string{"*:*"}}, }, } err = app.UpdateACLRules() @@ -143,7 +143,7 @@ func (s *Suite) TestValidExpandTagOwnersInUsers(c *check.C) { // this test should validate that we can expand a group in a TagOWner section and // match properly the IP's of the related hosts. The owner is valid and the tag is also valid. -// the tag is matched in the Ports section. +// the tag is matched in the Destinations section. func (s *Suite) TestValidExpandTagOwnersInPorts(c *check.C) { namespace, err := app.CreateNamespace("user1") c.Assert(err, check.IsNil) @@ -177,7 +177,7 @@ func (s *Suite) TestValidExpandTagOwnersInPorts(c *check.C) { Groups: Groups{"group:test": []string{"user1", "user2"}}, TagOwners: TagOwners{"tag:test": []string{"user3", "group:test"}}, ACLs: []ACL{ - {Action: "accept", Users: []string{"*"}, Ports: []string{"tag:test:*"}}, + {Action: "accept", Sources: []string{"*"}, Destinations: []string{"tag:test:*"}}, }, } err = app.UpdateACLRules() @@ -222,7 +222,7 @@ func (s *Suite) TestInvalidTagValidNamespace(c *check.C) { app.aclPolicy = &ACLPolicy{ TagOwners: TagOwners{"tag:test": []string{"user1"}}, ACLs: []ACL{ - {Action: "accept", Users: []string{"user1"}, Ports: []string{"*:*"}}, + {Action: "accept", Sources: []string{"user1"}, Destinations: []string{"*:*"}}, }, } err = app.UpdateACLRules() @@ -287,9 +287,9 @@ func (s *Suite) TestValidTagInvalidNamespace(c *check.C) { TagOwners: TagOwners{"tag:webapp": []string{"user1"}}, ACLs: []ACL{ { - Action: "accept", - Users: []string{"user1"}, - Ports: []string{"tag:webapp:80,443"}, + Action: "accept", + Sources: []string{"user1"}, + Destinations: []string{"tag:webapp:80,443"}, }, }, } @@ -645,7 +645,7 @@ func Test_expandPorts(t *testing.T) { wantErr: false, }, { - name: "two ports", + name: "two Destinations", args: args{portsStr: "80,443"}, want: &[]tailcfg.PortRange{ {First: 80, Last: 80}, diff --git a/acls_types.go b/acls_types.go index fb869821..6434509c 100644 --- a/acls_types.go +++ b/acls_types.go @@ -11,18 +11,19 @@ import ( // ACLPolicy represents a Tailscale ACL Policy. type ACLPolicy struct { - Groups Groups `json:"Groups" yaml:"Groups"` - Hosts Hosts `json:"Hosts" yaml:"Hosts"` - TagOwners TagOwners `json:"TagOwners" yaml:"TagOwners"` - ACLs []ACL `json:"ACLs" yaml:"ACLs"` - Tests []ACLTest `json:"Tests" yaml:"Tests"` + Groups Groups `json:"groups" yaml:"groups"` + Hosts Hosts `json:"hosts" yaml:"hosts"` + TagOwners TagOwners `json:"tagOwners" yaml:"tagOwners"` + ACLs []ACL `json:"acls" yaml:"acls"` + Tests []ACLTest `json:"tests" yaml:"tests"` } // ACL is a basic rule for the ACL Policy. type ACL struct { - Action string `json:"Action" yaml:"Action"` - Users []string `json:"Users" yaml:"Users"` - Ports []string `json:"Ports" yaml:"Ports"` + Action string `json:"action" yaml:"action"` + Protocol string `json:"protocol" yaml:"protocol"` + Sources []string `json:"src" yaml:"src"` + Destinations []string `json:"dst" yaml:"dst"` } // Groups references a series of alias in the ACL rules. @@ -36,9 +37,9 @@ type TagOwners map[string][]string // ACLTest is not implemented, but should be use to check if a certain rule is allowed. type ACLTest struct { - User string `json:"User" yaml:"User"` - Allow []string `json:"Allow" yaml:"Allow"` - Deny []string `json:"Deny,omitempty" yaml:"Deny,omitempty"` + Source string `json:"src" yaml:"src"` + Accept []string `json:"accept" yaml:"accept"` + Deny []string `json:"deny,omitempty" yaml:"deny,omitempty"` } // UnmarshalJSON allows to parse the Hosts directly into netaddr objects. diff --git a/machine_test.go b/machine_test.go index bde96057..48ccb153 100644 --- a/machine_test.go +++ b/machine_test.go @@ -188,8 +188,8 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) { Hosts: map[string]netaddr.IPPrefix{}, TagOwners: map[string][]string{}, ACLs: []ACL{ - {Action: "accept", Users: []string{"admin"}, Ports: []string{"*:*"}}, - {Action: "accept", Users: []string{"test"}, Ports: []string{"test:*"}}, + {Action: "accept", Sources: []string{"admin"}, Destinations: []string{"*:*"}}, + {Action: "accept", Sources: []string{"test"}, Destinations: []string{"test:*"}}, }, Tests: []ACLTest{}, } diff --git a/tests/acls/acl_policy_1.hujson b/tests/acls/acl_policy_1.hujson index 8f70148b..3ef1a477 100644 --- a/tests/acls/acl_policy_1.hujson +++ b/tests/acls/acl_policy_1.hujson @@ -1,6 +1,6 @@ { // Declare static groups of users beyond those in the identity service. - "Groups": { + "groups": { "group:example": [ "user1@example.com", "user2@example.com", @@ -11,12 +11,12 @@ ], }, // Declare hostname aliases to use in place of IP addresses or subnets. - "Hosts": { + "hosts": { "example-host-1": "100.100.100.100", "example-host-2": "100.100.101.100/24", }, // Define who is allowed to use which tags. - "TagOwners": { + "tagOwners": { // Everyone in the montreal-admins or global-admins group are // allowed to tag servers as montreal-webserver. "tag:montreal-webserver": [ @@ -29,17 +29,18 @@ ], }, // Access control lists. - "ACLs": [ + "acls": [ // Engineering users, plus the president, can access port 22 (ssh) // and port 3389 (remote desktop protocol) on all servers, and all // ports on git-server or ci-server. { - "Action": "accept", - "Users": [ + "action": "accept", + "protocol": "tcp", + "src": [ "group:example2", "192.168.1.0/24" ], - "Ports": [ + "dst": [ "*:22,3389", "git-server:*", "ci-server:*" @@ -48,22 +49,22 @@ // Allow engineer users to access any port on a device tagged with // tag:production. { - "Action": "accept", - "Users": [ + "action": "accept", + "src": [ "group:example" ], - "Ports": [ + "dst": [ "tag:production:*" ], }, // Allow servers in the my-subnet host and 192.168.1.0/24 to access hosts // on both networks. { - "Action": "accept", - "Users": [ + "action": "accept", + "src": [ "example-host-2", ], - "Ports": [ + "dst": [ "example-host-1:*", "192.168.1.0/24:*" ], @@ -72,22 +73,22 @@ // Comment out this section if you want to define specific ACL // restrictions above. { - "Action": "accept", - "Users": [ + "action": "accept", + "src": [ "*" ], - "Ports": [ + "dst": [ "*:*" ], }, // All users in Montreal are allowed to access the Montreal web // servers. { - "Action": "accept", - "Users": [ + "action": "accept", + "src": [ "example-host-1" ], - "Ports": [ + "dst": [ "tag:montreal-webserver:80,443" ], }, @@ -96,30 +97,30 @@ // In contrast, this doesn't grant API servers the right to initiate // any connections. { - "Action": "accept", - "Users": [ + "action": "accept", + "src": [ "tag:montreal-webserver" ], - "Ports": [ + "dst": [ "tag:api-server:443" ], }, ], // Declare tests to check functionality of ACL rules - "Tests": [ + "tests": [ { - "User": "user1@example.com", - "Allow": [ + "src": "user1@example.com", + "accept": [ "example-host-1:22", "example-host-2:80" ], - "Deny": [ + "deny": [ "exapmle-host-2:100" ], }, { - "User": "user2@example.com", - "Allow": [ + "src": "user2@example.com", + "accept": [ "100.60.3.4:22" ], }, diff --git a/tests/acls/acl_policy_basic_1.hujson b/tests/acls/acl_policy_basic_1.hujson index 4f86af3d..db78ea9c 100644 --- a/tests/acls/acl_policy_basic_1.hujson +++ b/tests/acls/acl_policy_basic_1.hujson @@ -3,19 +3,19 @@ { - "Hosts": { + "hosts": { "host-1": "100.100.100.100", "subnet-1": "100.100.101.100/24", }, - "ACLs": [ + "acls": [ { - "Action": "accept", - "Users": [ + "action": "accept", + "src": [ "subnet-1", "192.168.1.0/24" ], - "Ports": [ + "dst": [ "*:22,3389", "host-1:*", ], diff --git a/tests/acls/acl_policy_basic_groups.hujson b/tests/acls/acl_policy_basic_groups.hujson index ed11a0d0..ecfcbfd1 100644 --- a/tests/acls/acl_policy_basic_groups.hujson +++ b/tests/acls/acl_policy_basic_groups.hujson @@ -1,24 +1,24 @@ // This ACL is used to test group expansion { - "Groups": { + "groups": { "group:example": [ "testnamespace", ], }, - "Hosts": { + "hosts": { "host-1": "100.100.100.100", "subnet-1": "100.100.101.100/24", }, - "ACLs": [ + "acls": [ { - "Action": "accept", - "Users": [ + "action": "accept", + "src": [ "group:example", ], - "Ports": [ + "dst": [ "host-1:*", ], }, diff --git a/tests/acls/acl_policy_basic_namespace_as_user.hujson b/tests/acls/acl_policy_basic_namespace_as_user.hujson index 881349fc..9a553b08 100644 --- a/tests/acls/acl_policy_basic_namespace_as_user.hujson +++ b/tests/acls/acl_policy_basic_namespace_as_user.hujson @@ -1,18 +1,18 @@ // This ACL is used to test namespace expansion { - "Hosts": { + "hosts": { "host-1": "100.100.100.100", "subnet-1": "100.100.101.100/24", }, - "ACLs": [ + "acls": [ { - "Action": "accept", - "Users": [ + "action": "accept", + "src": [ "testnamespace", ], - "Ports": [ + "dst": [ "host-1:*", ], }, diff --git a/tests/acls/acl_policy_basic_range.hujson b/tests/acls/acl_policy_basic_range.hujson index 8bcbc798..2a4208fb 100644 --- a/tests/acls/acl_policy_basic_range.hujson +++ b/tests/acls/acl_policy_basic_range.hujson @@ -1,18 +1,18 @@ // This ACL is used to test the port range expansion { - "Hosts": { + "hosts": { "host-1": "100.100.100.100", "subnet-1": "100.100.101.100/24", }, - "ACLs": [ + "acls": [ { - "Action": "accept", - "Users": [ + "action": "accept", + "src": [ "subnet-1", ], - "Ports": [ + "dst": [ "host-1:5400-5500", ], }, diff --git a/tests/acls/acl_policy_basic_wildcards.hujson b/tests/acls/acl_policy_basic_wildcards.hujson index ec5ce468..e1a1f714 100644 --- a/tests/acls/acl_policy_basic_wildcards.hujson +++ b/tests/acls/acl_policy_basic_wildcards.hujson @@ -1,18 +1,18 @@ // This ACL is used to test wildcards { - "Hosts": { + "hosts": { "host-1": "100.100.100.100", "subnet-1": "100.100.101.100/24", }, - "ACLs": [ + "acls": [ { "Action": "accept", - "Users": [ + "src": [ "*", ], - "Ports": [ + "dst": [ "host-1:*", ], }, diff --git a/tests/acls/acl_policy_basic_wildcards.yaml b/tests/acls/acl_policy_basic_wildcards.yaml index 8e7c817f..4318fcd2 100644 --- a/tests/acls/acl_policy_basic_wildcards.yaml +++ b/tests/acls/acl_policy_basic_wildcards.yaml @@ -1,10 +1,10 @@ --- -Hosts: +hosts: host-1: 100.100.100.100/32 subnet-1: 100.100.101.100/24 -ACLs: - - Action: accept - Users: +acls: + - action: accept + src: - "*" - Ports: + dst: - host-1:* diff --git a/tests/acls/acl_policy_invalid.hujson b/tests/acls/acl_policy_invalid.hujson index ad640dfe..3684b1f1 100644 --- a/tests/acls/acl_policy_invalid.hujson +++ b/tests/acls/acl_policy_invalid.hujson @@ -1,18 +1,18 @@ { // Declare static groups of users beyond those in the identity service. - "Groups": { + "groups": { "group:example": [ "user1@example.com", "user2@example.com", ], }, // Declare hostname aliases to use in place of IP addresses or subnets. - "Hosts": { + "hosts": { "example-host-1": "100.100.100.100", "example-host-2": "100.100.101.100/24", }, // Define who is allowed to use which tags. - "TagOwners": { + "tagOwners": { // Everyone in the montreal-admins or global-admins group are // allowed to tag servers as montreal-webserver. "tag:montreal-webserver": [ @@ -26,17 +26,17 @@ ], }, // Access control lists. - "ACLs": [ + "acls": [ // Engineering users, plus the president, can access port 22 (ssh) // and port 3389 (remote desktop protocol) on all servers, and all // ports on git-server or ci-server. { - "Action": "accept", - "Users": [ + "action": "accept", + "src": [ "group:engineering", "president@example.com" ], - "Ports": [ + "dst": [ "*:22,3389", "git-server:*", "ci-server:*" @@ -45,23 +45,23 @@ // Allow engineer users to access any port on a device tagged with // tag:production. { - "Action": "accept", - "Users": [ + "action": "accept", + "src": [ "group:engineers" ], - "Ports": [ + "dst": [ "tag:production:*" ], }, // Allow servers in the my-subnet host and 192.168.1.0/24 to access hosts // on both networks. { - "Action": "accept", - "Users": [ + "action": "accept", + "src": [ "my-subnet", "192.168.1.0/24" ], - "Ports": [ + "dst": [ "my-subnet:*", "192.168.1.0/24:*" ], @@ -70,22 +70,22 @@ // Comment out this section if you want to define specific ACL // restrictions above. { - "Action": "accept", - "Users": [ + "action": "accept", + "src": [ "*" ], - "Ports": [ + "dst": [ "*:*" ], }, // All users in Montreal are allowed to access the Montreal web // servers. { - "Action": "accept", - "Users": [ + "action": "accept", + "src": [ "group:montreal-users" ], - "Ports": [ + "dst": [ "tag:montreal-webserver:80,443" ], }, @@ -94,30 +94,30 @@ // In contrast, this doesn't grant API servers the right to initiate // any connections. { - "Action": "accept", - "Users": [ + "action": "accept", + "src": [ "tag:montreal-webserver" ], - "Ports": [ + "dst": [ "tag:api-server:443" ], }, ], // Declare tests to check functionality of ACL rules - "Tests": [ + "tests": [ { - "User": "user1@example.com", - "Allow": [ + "src": "user1@example.com", + "accept": [ "example-host-1:22", "example-host-2:80" ], - "Deny": [ + "deny": [ "exapmle-host-2:100" ], }, { - "User": "user2@example.com", - "Allow": [ + "src": "user2@example.com", + "accept": [ "100.60.3.4:22" ], },