Skip to content

Commit

Permalink
Merge branch 'main' into registration-simplification
Browse files Browse the repository at this point in the history
  • Loading branch information
kradalby authored Mar 1, 2022
2 parents 7c63412 + 4c74043 commit d34d617
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 35 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,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)
Expand Down
40 changes: 31 additions & 9 deletions acls.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
}
Expand Down
16 changes: 16 additions & 0 deletions acls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,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)
Expand Down
44 changes: 33 additions & 11 deletions acls_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
13 changes: 7 additions & 6 deletions apple_mobileconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"html/template"
"net/http"
textTemplate "text/template"

"github.com/gin-gonic/gin"
"github.com/gofrs/uuid"
Expand All @@ -30,7 +31,7 @@ func (h *Headscale) AppleMobileConfig(ctx *gin.Context) {
<p><code>curl {{.Url}}/apple/ios</code></p>
-->
<p><code>curl {{.Url}}/apple/macos</code></p>
<h2>Profiles</h2>
<!--
Expand All @@ -39,7 +40,7 @@ func (h *Headscale) AppleMobileConfig(ctx *gin.Context) {
<a href="/apple/ios" download="headscale_ios.mobileconfig">iOS profile</a>
</p>
-->
<h3>macOS</h3>
<p>Headscale can be set to the default server by installing a Headscale configuration profile:</p>
<p>
Expand All @@ -58,7 +59,7 @@ func (h *Headscale) AppleMobileConfig(ctx *gin.Context) {
<code>defaults write io.tailscale.ipn.macos ControlURL {{.URL}}</code>
<p>Restart Tailscale.app and log in.</p>
</body>
</html>`))

Expand Down Expand Up @@ -202,8 +203,8 @@ type AppleMobilePlatformConfig struct {
URL string
}

var commonTemplate = template.Must(
template.New("mobileconfig").Parse(`<?xml version="1.0" encoding="UTF-8"?>
var commonTemplate = textTemplate.Must(
textTemplate.New("mobileconfig").Parse(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
Expand All @@ -229,7 +230,7 @@ var commonTemplate = template.Must(
</plist>`),
)

var iosTemplate = template.Must(template.New("iosTemplate").Parse(`
var iosTemplate = textTemplate.Must(textTemplate.New("iosTemplate").Parse(`
<dict>
<key>PayloadType</key>
<string>io.tailscale.ipn.ios</string>
Expand Down
3 changes: 2 additions & 1 deletion config-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions tests/acls/acl_policy_basic_wildcards.yaml
Original file line number Diff line number Diff line change
@@ -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:*
8 changes: 0 additions & 8 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand Down

0 comments on commit d34d617

Please sign in to comment.