From 67d6c8f9465d52401666b27a5f96aff2bf17b5f9 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 27 Feb 2022 09:04:27 +0100 Subject: [PATCH 1/6] Remove oversensitive tracing output --- utils.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/utils.go b/utils.go index 004bf306..af267eb3 100644 --- a/utils.go +++ b/utils.go @@ -196,10 +196,6 @@ func (h *Headscale) getUsedIPs() (*netaddr.IPSet, error) { var addressesSlices []string h.db.Model(&Machine{}).Pluck("ip_addresses", &addressesSlices) - log.Trace(). - Strs("addresses", addressesSlices). - Msg("Got allocated ip addresses from databases") - var ips netaddr.IPSetBuilder for _, slice := range addressesSlices { var machineAddresses MachineAddresses @@ -216,10 +212,6 @@ func (h *Headscale) getUsedIPs() (*netaddr.IPSet, error) { } } - log.Trace(). - Interface("addresses", ips). - Msg("Parsed ip addresses that has been allocated from databases") - ipSet, err := ips.IPSet() if err != nil { return &netaddr.IPSet{}, fmt.Errorf( From 8a3a0b64037bea8c15700943d37a16f02375cf28 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 27 Feb 2022 09:04:48 +0100 Subject: [PATCH 2/6] Add YAML support to ACLs --- acls.go | 40 +++++++++++++++++++++++++++++++--------- acls_types.go | 44 +++++++++++++++++++++++++++++++++----------- 2 files changed, 64 insertions(+), 20 deletions(-) diff --git a/acls.go b/acls.go index ce14a89a..1a597c24 100644 --- a/acls.go +++ b/acls.go @@ -5,11 +5,13 @@ import ( "fmt" "io" "os" + "path/filepath" "strconv" "strings" "github.com/rs/zerolog/log" "github.com/tailscale/hujson" + "gopkg.in/yaml.v3" "inet.af/netaddr" "tailscale.com/tailcfg" ) @@ -53,16 +55,36 @@ func (h *Headscale) LoadACLPolicy(path string) error { return err } - ast, err := hujson.Parse(policyBytes) - if err != nil { - return err - } - ast.Standardize() - policyBytes = ast.Pack() - err = json.Unmarshal(policyBytes, &policy) - if err != nil { - return err + switch filepath.Ext(path) { + case ".yml", ".yaml": + log.Debug(). + Str("path", path). + Bytes("file", policyBytes). + Msg("Loading ACLs from YAML") + + err := yaml.Unmarshal(policyBytes, &policy) + if err != nil { + return err + } + + log.Trace(). + Interface("policy", policy). + Msg("Loaded policy from YAML") + + default: + ast, err := hujson.Parse(policyBytes) + if err != nil { + return err + } + + ast.Standardize() + policyBytes = ast.Pack() + err = json.Unmarshal(policyBytes, &policy) + if err != nil { + return err + } } + if policy.IsZero() { return errEmptyPolicy } diff --git a/acls_types.go b/acls_types.go index 08e650ff..fb869821 100644 --- a/acls_types.go +++ b/acls_types.go @@ -5,23 +5,24 @@ import ( "strings" "github.com/tailscale/hujson" + "gopkg.in/yaml.v3" "inet.af/netaddr" ) // ACLPolicy represents a Tailscale ACL Policy. type ACLPolicy struct { - Groups Groups `json:"Groups"` - Hosts Hosts `json:"Hosts"` - TagOwners TagOwners `json:"TagOwners"` - ACLs []ACL `json:"ACLs"` - Tests []ACLTest `json:"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"` - Users []string `json:"Users"` - Ports []string `json:"Ports"` + Action string `json:"Action" yaml:"Action"` + Users []string `json:"Users" yaml:"Users"` + Ports []string `json:"Ports" yaml:"Ports"` } // Groups references a series of alias in the ACL rules. @@ -35,9 +36,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"` - Allow []string `json:"Allow"` - Deny []string `json:"Deny,omitempty"` + User string `json:"User" yaml:"User"` + Allow []string `json:"Allow" yaml:"Allow"` + Deny []string `json:"Deny,omitempty" yaml:"Deny,omitempty"` } // UnmarshalJSON allows to parse the Hosts directly into netaddr objects. @@ -69,6 +70,27 @@ func (hosts *Hosts) UnmarshalJSON(data []byte) error { return nil } +// UnmarshalYAML allows to parse the Hosts directly into netaddr objects. +func (hosts *Hosts) UnmarshalYAML(data []byte) error { + newHosts := Hosts{} + hostIPPrefixMap := make(map[string]string) + + err := yaml.Unmarshal(data, &hostIPPrefixMap) + if err != nil { + return err + } + for host, prefixStr := range hostIPPrefixMap { + prefix, err := netaddr.ParseIPPrefix(prefixStr) + if err != nil { + return err + } + newHosts[host] = prefix + } + *hosts = newHosts + + return nil +} + // IsZero is perhaps a bit naive here. func (policy ACLPolicy) IsZero() bool { if len(policy.Groups) == 0 && len(policy.Hosts) == 0 && len(policy.ACLs) == 0 { From c159eb7541e18f3ed1c926826d5075a7bf54b5cf Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 27 Feb 2022 09:04:59 +0100 Subject: [PATCH 3/6] Add basic test of yaml parsing --- acls_test.go | 16 ++++++++++++++++ tests/acls/acl_policy_basic_wildcards.yaml | 10 ++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/acls/acl_policy_basic_wildcards.yaml diff --git a/acls_test.go b/acls_test.go index 5534257d..9f0432a7 100644 --- a/acls_test.go +++ b/acls_test.go @@ -328,6 +328,22 @@ func (s *Suite) TestPortWildcard(c *check.C) { c.Assert(rules[0].SrcIPs[0], check.Equals, "*") } +func (s *Suite) TestPortWildcardYAML(c *check.C) { + err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_wildcards.yaml") + c.Assert(err, check.IsNil) + + rules, err := app.generateACLRules() + c.Assert(err, check.IsNil) + c.Assert(rules, check.NotNil) + + c.Assert(rules, check.HasLen, 1) + c.Assert(rules[0].DstPorts, check.HasLen, 1) + c.Assert(rules[0].DstPorts[0].Ports.First, check.Equals, uint16(0)) + c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535)) + c.Assert(rules[0].SrcIPs, check.HasLen, 1) + c.Assert(rules[0].SrcIPs[0], check.Equals, "*") +} + func (s *Suite) TestPortNamespace(c *check.C) { namespace, err := app.CreateNamespace("testnamespace") c.Assert(err, check.IsNil) diff --git a/tests/acls/acl_policy_basic_wildcards.yaml b/tests/acls/acl_policy_basic_wildcards.yaml new file mode 100644 index 00000000..8e7c817f --- /dev/null +++ b/tests/acls/acl_policy_basic_wildcards.yaml @@ -0,0 +1,10 @@ +--- +Hosts: + host-1: 100.100.100.100/32 + subnet-1: 100.100.101.100/24 +ACLs: + - Action: accept + Users: + - "*" + Ports: + - host-1:* From e0b9a317f4c30ad07e275b179b5037d61b8bdc02 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 27 Feb 2022 09:05:08 +0100 Subject: [PATCH 4/6] Add note to config example --- config-example.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config-example.yaml b/config-example.yaml index bac3b0ca..e14d1a15 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -132,7 +132,8 @@ tls_key_path: "" log_level: info # Path to a file containg ACL policies. -# Recommended path: /etc/headscale/acl.hujson +# ACLs can be defined as YAML or HUJSON. +# https://tailscale.com/kb/1018/acls/ acl_policy_path: "" ## DNS From d6f6939c54dc0144fa9cc5e6ddf97e45652780ae Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 27 Feb 2022 09:08:29 +0100 Subject: [PATCH 5/6] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d44db566..b2b93ef6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ - Boundaries between Namespaces has been removed and all nodes can communicate by default [#357](https://github.com/juanfont/headscale/pull/357) - To limit access between nodes, use [ACLs](./docs/acls.md). +**Features**: + +- Add support for writing ACL files with YAML [#359](https://github.com/juanfont/headscale/pull/359) + **Changes**: - Fix a bug were the same IP could be assigned to multiple hosts if joined in quick succession [#346](https://github.com/juanfont/headscale/pull/346) From 5157f356cb5e1fdd83c29f7d6050450b9635d182 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 1 Mar 2022 07:30:35 +0000 Subject: [PATCH 6/6] Fix apple profile issue being generated with escaped characters --- apple_mobileconfig.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apple_mobileconfig.go b/apple_mobileconfig.go index e5d9eede..69f61a63 100644 --- a/apple_mobileconfig.go +++ b/apple_mobileconfig.go @@ -4,6 +4,7 @@ import ( "bytes" "html/template" "net/http" + textTemplate "text/template" "github.com/gin-gonic/gin" "github.com/gofrs/uuid" @@ -30,7 +31,7 @@ func (h *Headscale) AppleMobileConfig(ctx *gin.Context) {

curl {{.Url}}/apple/ios

-->

curl {{.Url}}/apple/macos

- +

Profiles

- +

macOS

Headscale can be set to the default server by installing a Headscale configuration profile:

@@ -58,7 +59,7 @@ func (h *Headscale) AppleMobileConfig(ctx *gin.Context) { defaults write io.tailscale.ipn.macos ControlURL {{.URL}}

Restart Tailscale.app and log in.

- + `)) @@ -202,8 +203,8 @@ type AppleMobilePlatformConfig struct { URL string } -var commonTemplate = template.Must( - template.New("mobileconfig").Parse(` +var commonTemplate = textTemplate.Must( + textTemplate.New("mobileconfig").Parse(` @@ -229,7 +230,7 @@ var commonTemplate = template.Must( `), ) -var iosTemplate = template.Must(template.New("iosTemplate").Parse(` +var iosTemplate = textTemplate.Must(textTemplate.New("iosTemplate").Parse(` PayloadType io.tailscale.ipn.ios