mirror of
https://github.com/juanfont/headscale.git
synced 2024-12-24 05:05:53 -05:00
Merge branch 'main' into registration-simplification
This commit is contained in:
commit
d34d617935
@ -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)
|
- 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).
|
- 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
|
### 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)
|
- 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)
|
||||||
|
40
acls.go
40
acls.go
@ -5,11 +5,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/tailscale/hujson"
|
"github.com/tailscale/hujson"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
@ -53,16 +55,36 @@ func (h *Headscale) LoadACLPolicy(path string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ast, err := hujson.Parse(policyBytes)
|
switch filepath.Ext(path) {
|
||||||
if err != nil {
|
case ".yml", ".yaml":
|
||||||
return err
|
log.Debug().
|
||||||
}
|
Str("path", path).
|
||||||
ast.Standardize()
|
Bytes("file", policyBytes).
|
||||||
policyBytes = ast.Pack()
|
Msg("Loading ACLs from YAML")
|
||||||
err = json.Unmarshal(policyBytes, &policy)
|
|
||||||
if err != nil {
|
err := yaml.Unmarshal(policyBytes, &policy)
|
||||||
return err
|
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() {
|
if policy.IsZero() {
|
||||||
return errEmptyPolicy
|
return errEmptyPolicy
|
||||||
}
|
}
|
||||||
|
16
acls_test.go
16
acls_test.go
@ -323,6 +323,22 @@ func (s *Suite) TestPortWildcard(c *check.C) {
|
|||||||
c.Assert(rules[0].SrcIPs[0], check.Equals, "*")
|
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) {
|
func (s *Suite) TestPortNamespace(c *check.C) {
|
||||||
namespace, err := app.CreateNamespace("testnamespace")
|
namespace, err := app.CreateNamespace("testnamespace")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
@ -5,23 +5,24 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tailscale/hujson"
|
"github.com/tailscale/hujson"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ACLPolicy represents a Tailscale ACL Policy.
|
// ACLPolicy represents a Tailscale ACL Policy.
|
||||||
type ACLPolicy struct {
|
type ACLPolicy struct {
|
||||||
Groups Groups `json:"Groups"`
|
Groups Groups `json:"Groups" yaml:"Groups"`
|
||||||
Hosts Hosts `json:"Hosts"`
|
Hosts Hosts `json:"Hosts" yaml:"Hosts"`
|
||||||
TagOwners TagOwners `json:"TagOwners"`
|
TagOwners TagOwners `json:"TagOwners" yaml:"TagOwners"`
|
||||||
ACLs []ACL `json:"ACLs"`
|
ACLs []ACL `json:"ACLs" yaml:"ACLs"`
|
||||||
Tests []ACLTest `json:"Tests"`
|
Tests []ACLTest `json:"Tests" yaml:"Tests"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACL is a basic rule for the ACL Policy.
|
// ACL is a basic rule for the ACL Policy.
|
||||||
type ACL struct {
|
type ACL struct {
|
||||||
Action string `json:"Action"`
|
Action string `json:"Action" yaml:"Action"`
|
||||||
Users []string `json:"Users"`
|
Users []string `json:"Users" yaml:"Users"`
|
||||||
Ports []string `json:"Ports"`
|
Ports []string `json:"Ports" yaml:"Ports"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Groups references a series of alias in the ACL rules.
|
// 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.
|
// ACLTest is not implemented, but should be use to check if a certain rule is allowed.
|
||||||
type ACLTest struct {
|
type ACLTest struct {
|
||||||
User string `json:"User"`
|
User string `json:"User" yaml:"User"`
|
||||||
Allow []string `json:"Allow"`
|
Allow []string `json:"Allow" yaml:"Allow"`
|
||||||
Deny []string `json:"Deny,omitempty"`
|
Deny []string `json:"Deny,omitempty" yaml:"Deny,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON allows to parse the Hosts directly into netaddr objects.
|
// UnmarshalJSON allows to parse the Hosts directly into netaddr objects.
|
||||||
@ -69,6 +70,27 @@ func (hosts *Hosts) UnmarshalJSON(data []byte) error {
|
|||||||
return nil
|
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.
|
// IsZero is perhaps a bit naive here.
|
||||||
func (policy ACLPolicy) IsZero() bool {
|
func (policy ACLPolicy) IsZero() bool {
|
||||||
if len(policy.Groups) == 0 && len(policy.Hosts) == 0 && len(policy.ACLs) == 0 {
|
if len(policy.Groups) == 0 && len(policy.Hosts) == 0 && len(policy.ACLs) == 0 {
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
textTemplate "text/template"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
@ -30,7 +31,7 @@ func (h *Headscale) AppleMobileConfig(ctx *gin.Context) {
|
|||||||
<p><code>curl {{.Url}}/apple/ios</code></p>
|
<p><code>curl {{.Url}}/apple/ios</code></p>
|
||||||
-->
|
-->
|
||||||
<p><code>curl {{.Url}}/apple/macos</code></p>
|
<p><code>curl {{.Url}}/apple/macos</code></p>
|
||||||
|
|
||||||
<h2>Profiles</h2>
|
<h2>Profiles</h2>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@ -39,7 +40,7 @@ func (h *Headscale) AppleMobileConfig(ctx *gin.Context) {
|
|||||||
<a href="/apple/ios" download="headscale_ios.mobileconfig">iOS profile</a>
|
<a href="/apple/ios" download="headscale_ios.mobileconfig">iOS profile</a>
|
||||||
</p>
|
</p>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<h3>macOS</h3>
|
<h3>macOS</h3>
|
||||||
<p>Headscale can be set to the default server by installing a Headscale configuration profile:</p>
|
<p>Headscale can be set to the default server by installing a Headscale configuration profile:</p>
|
||||||
<p>
|
<p>
|
||||||
@ -58,7 +59,7 @@ func (h *Headscale) AppleMobileConfig(ctx *gin.Context) {
|
|||||||
<code>defaults write io.tailscale.ipn.macos ControlURL {{.URL}}</code>
|
<code>defaults write io.tailscale.ipn.macos ControlURL {{.URL}}</code>
|
||||||
|
|
||||||
<p>Restart Tailscale.app and log in.</p>
|
<p>Restart Tailscale.app and log in.</p>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>`))
|
</html>`))
|
||||||
|
|
||||||
@ -202,8 +203,8 @@ type AppleMobilePlatformConfig struct {
|
|||||||
URL string
|
URL string
|
||||||
}
|
}
|
||||||
|
|
||||||
var commonTemplate = template.Must(
|
var commonTemplate = textTemplate.Must(
|
||||||
template.New("mobileconfig").Parse(`<?xml version="1.0" encoding="UTF-8"?>
|
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">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
@ -229,7 +230,7 @@ var commonTemplate = template.Must(
|
|||||||
</plist>`),
|
</plist>`),
|
||||||
)
|
)
|
||||||
|
|
||||||
var iosTemplate = template.Must(template.New("iosTemplate").Parse(`
|
var iosTemplate = textTemplate.Must(textTemplate.New("iosTemplate").Parse(`
|
||||||
<dict>
|
<dict>
|
||||||
<key>PayloadType</key>
|
<key>PayloadType</key>
|
||||||
<string>io.tailscale.ipn.ios</string>
|
<string>io.tailscale.ipn.ios</string>
|
||||||
|
@ -132,7 +132,8 @@ tls_key_path: ""
|
|||||||
log_level: info
|
log_level: info
|
||||||
|
|
||||||
# Path to a file containg ACL policies.
|
# 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: ""
|
acl_policy_path: ""
|
||||||
|
|
||||||
## DNS
|
## DNS
|
||||||
|
10
tests/acls/acl_policy_basic_wildcards.yaml
Normal file
10
tests/acls/acl_policy_basic_wildcards.yaml
Normal file
@ -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
utils.go
8
utils.go
@ -196,10 +196,6 @@ func (h *Headscale) getUsedIPs() (*netaddr.IPSet, error) {
|
|||||||
var addressesSlices []string
|
var addressesSlices []string
|
||||||
h.db.Model(&Machine{}).Pluck("ip_addresses", &addressesSlices)
|
h.db.Model(&Machine{}).Pluck("ip_addresses", &addressesSlices)
|
||||||
|
|
||||||
log.Trace().
|
|
||||||
Strs("addresses", addressesSlices).
|
|
||||||
Msg("Got allocated ip addresses from databases")
|
|
||||||
|
|
||||||
var ips netaddr.IPSetBuilder
|
var ips netaddr.IPSetBuilder
|
||||||
for _, slice := range addressesSlices {
|
for _, slice := range addressesSlices {
|
||||||
var machineAddresses MachineAddresses
|
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()
|
ipSet, err := ips.IPSet()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &netaddr.IPSet{}, fmt.Errorf(
|
return &netaddr.IPSet{}, fmt.Errorf(
|
||||||
|
Loading…
Reference in New Issue
Block a user