mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	Merge pull request #359 from kradalby/yaml-acls
Add YAML support to ACLs
This commit is contained in:
		
						commit
						4c74043f72
					
				| @ -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) | ||||
|  | ||||
							
								
								
									
										40
									
								
								acls.go
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								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 | ||||
| 	} | ||||
|  | ||||
							
								
								
									
										16
									
								
								acls_test.go
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								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) | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
							
								
								
									
										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 | ||||
| 	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( | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user