mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-11-04 10:01:05 +01:00 
			
		
		
		
	Initial work on ACLs
This commit is contained in:
		
							parent
							
								
									95fee5aa6f
								
							
						
					
					
						commit
						b161a92e58
					
				
							
								
								
									
										30
									
								
								acls.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								acls.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
package headscale
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/tailscale/hujson"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const errorInvalidPolicy = Error("invalid policy")
 | 
			
		||||
 | 
			
		||||
func (h *Headscale) ParsePolicy(path string) (*ACLPolicy, error) {
 | 
			
		||||
	policyFile, err := os.Open(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer policyFile.Close()
 | 
			
		||||
 | 
			
		||||
	var policy ACLPolicy
 | 
			
		||||
	b, err := io.ReadAll(policyFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	err = hujson.Unmarshal(b, &policy)
 | 
			
		||||
	if policy.IsZero() {
 | 
			
		||||
		return nil, errorInvalidPolicy
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &policy, err
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								acls_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								acls_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
package headscale
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"gopkg.in/check.v1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (s *Suite) TestWrongPath(c *check.C) {
 | 
			
		||||
	_, err := h.ParsePolicy("asdfg")
 | 
			
		||||
	c.Assert(err, check.NotNil)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Suite) TestBrokenHuJson(c *check.C) {
 | 
			
		||||
	_, err := h.ParsePolicy("./tests/acls/broken.hujson")
 | 
			
		||||
	c.Assert(err, check.NotNil)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Suite) TestInvalidPolicyHuson(c *check.C) {
 | 
			
		||||
	_, err := h.ParsePolicy("./tests/acls/invalid.hujson")
 | 
			
		||||
	c.Assert(err, check.NotNil)
 | 
			
		||||
	c.Assert(err, check.Equals, errorInvalidPolicy)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Suite) TestValidCheckHosts(c *check.C) {
 | 
			
		||||
	p, err := h.ParsePolicy("./tests/acls/acl_policy_1.hujson")
 | 
			
		||||
	c.Assert(err, check.IsNil)
 | 
			
		||||
	c.Assert(p, check.NotNil)
 | 
			
		||||
	c.Assert(p.IsZero(), check.Equals, false)
 | 
			
		||||
 | 
			
		||||
	hosts, err := p.GetHosts()
 | 
			
		||||
	c.Assert(err, check.IsNil)
 | 
			
		||||
	c.Assert(*hosts, check.HasLen, 2)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										59
									
								
								acls_types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								acls_types.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
			
		||||
package headscale
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"inet.af/netaddr"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ACLPolicy struct {
 | 
			
		||||
	Groups    Groups    `json:"Groups"`
 | 
			
		||||
	Hosts     Hosts     `json:"Hosts"`
 | 
			
		||||
	TagOwners TagOwners `json:"TagOwners"`
 | 
			
		||||
	ACLs      []ACL     `json:"ACLs"`
 | 
			
		||||
	Tests     []ACLTest `json:"Tests"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ACL struct {
 | 
			
		||||
	Action string   `json:"Action"`
 | 
			
		||||
	Users  []string `json:"Users"`
 | 
			
		||||
	Ports  []string `json:"Ports"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Groups map[string][]string
 | 
			
		||||
 | 
			
		||||
type Hosts map[string]string
 | 
			
		||||
 | 
			
		||||
type TagOwners struct {
 | 
			
		||||
	TagMontrealWebserver []string `json:"tag:montreal-webserver"`
 | 
			
		||||
	TagAPIServer         []string `json:"tag:api-server"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ACLTest struct {
 | 
			
		||||
	User  string   `json:"User"`
 | 
			
		||||
	Allow []string `json:"Allow"`
 | 
			
		||||
	Deny  []string `json:"Deny,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsZero is perhaps a bit naive here
 | 
			
		||||
func (p ACLPolicy) IsZero() bool {
 | 
			
		||||
	if len(p.Groups) == 0 && len(p.Hosts) == 0 && len(p.ACLs) == 0 {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p ACLPolicy) GetHosts() (*map[string]netaddr.IPPrefix, error) {
 | 
			
		||||
	hosts := make(map[string]netaddr.IPPrefix)
 | 
			
		||||
	for k, v := range p.Hosts {
 | 
			
		||||
		if !strings.Contains(v, "/") {
 | 
			
		||||
			v = v + "/32"
 | 
			
		||||
		}
 | 
			
		||||
		prefix, err := netaddr.ParseIPPrefix(v)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		hosts[k] = prefix
 | 
			
		||||
	}
 | 
			
		||||
	return &hosts, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										125
									
								
								tests/acls/acl_policy_1.hujson
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								tests/acls/acl_policy_1.hujson
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,125 @@
 | 
			
		||||
{
 | 
			
		||||
    // Declare static groups of users beyond those in the identity service.
 | 
			
		||||
    "Groups": {
 | 
			
		||||
        "group:example": [
 | 
			
		||||
            "user1@example.com",
 | 
			
		||||
            "user2@example.com",
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    // Declare hostname aliases to use in place of IP addresses or subnets.
 | 
			
		||||
    "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": {
 | 
			
		||||
        // Everyone in the montreal-admins or global-admins group are
 | 
			
		||||
        // allowed to tag servers as montreal-webserver.
 | 
			
		||||
        "tag:montreal-webserver": [
 | 
			
		||||
            "group:montreal-admins",
 | 
			
		||||
            "group:global-admins",
 | 
			
		||||
        ],
 | 
			
		||||
        // Only a few admins are allowed to create API servers.
 | 
			
		||||
        "tag:api-server": [
 | 
			
		||||
            "group:global-admins",
 | 
			
		||||
            "president@example.com",
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    // Access control lists.
 | 
			
		||||
    "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": [
 | 
			
		||||
                "group:engineering",
 | 
			
		||||
                "president@example.com"
 | 
			
		||||
            ],
 | 
			
		||||
            "Ports": [
 | 
			
		||||
                "*:22,3389",
 | 
			
		||||
                "git-server:*",
 | 
			
		||||
                "ci-server:*"
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
        // Allow engineer users to access any port on a device tagged with
 | 
			
		||||
        // tag:production.
 | 
			
		||||
        {
 | 
			
		||||
            "Action": "accept",
 | 
			
		||||
            "Users": [
 | 
			
		||||
                "group:engineers"
 | 
			
		||||
            ],
 | 
			
		||||
            "Ports": [
 | 
			
		||||
                "tag:production:*"
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
        // Allow servers in the my-subnet host and 192.168.1.0/24 to access hosts
 | 
			
		||||
        // on both networks.
 | 
			
		||||
        {
 | 
			
		||||
            "Action": "accept",
 | 
			
		||||
            "Users": [
 | 
			
		||||
                "my-subnet",
 | 
			
		||||
                "192.168.1.0/24"
 | 
			
		||||
            ],
 | 
			
		||||
            "Ports": [
 | 
			
		||||
                "my-subnet:*",
 | 
			
		||||
                "192.168.1.0/24:*"
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
        // Allow every user of your network to access anything on the network.
 | 
			
		||||
        // Comment out this section if you want to define specific ACL
 | 
			
		||||
        // restrictions above.
 | 
			
		||||
        {
 | 
			
		||||
            "Action": "accept",
 | 
			
		||||
            "Users": [
 | 
			
		||||
                "*"
 | 
			
		||||
            ],
 | 
			
		||||
            "Ports": [
 | 
			
		||||
                "*:*"
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
        // All users in Montreal are allowed to access the Montreal web
 | 
			
		||||
        // servers.
 | 
			
		||||
        {
 | 
			
		||||
            "Action": "accept",
 | 
			
		||||
            "Users": [
 | 
			
		||||
                "group:montreal-users"
 | 
			
		||||
            ],
 | 
			
		||||
            "Ports": [
 | 
			
		||||
                "tag:montreal-webserver:80,443"
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
        // Montreal web servers are allowed to make outgoing connections to
 | 
			
		||||
        // the API servers, but only on https port 443.
 | 
			
		||||
        // In contrast, this doesn't grant API servers the right to initiate
 | 
			
		||||
        // any connections.
 | 
			
		||||
        {
 | 
			
		||||
            "Action": "accept",
 | 
			
		||||
            "Users": [
 | 
			
		||||
                "tag:montreal-webserver"
 | 
			
		||||
            ],
 | 
			
		||||
            "Ports": [
 | 
			
		||||
                "tag:api-server:443"
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    // Declare tests to check functionality of ACL rules
 | 
			
		||||
    "Tests": [
 | 
			
		||||
        {
 | 
			
		||||
            "User": "user1@example.com",
 | 
			
		||||
            "Allow": [
 | 
			
		||||
                "example-host-1:22",
 | 
			
		||||
                "example-host-2:80"
 | 
			
		||||
            ],
 | 
			
		||||
            "Deny": [
 | 
			
		||||
                "exapmle-host-2:100"
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "User": "user2@example.com",
 | 
			
		||||
            "Allow": [
 | 
			
		||||
                "100.60.3.4:22"
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								tests/acls/broken.hujson
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/acls/broken.hujson
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
{
 | 
			
		||||
							
								
								
									
										4
									
								
								tests/acls/invalid.hujson
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								tests/acls/invalid.hujson
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
			
		||||
{
 | 
			
		||||
    "valid_json": true,
 | 
			
		||||
    "but_a_policy_though": false
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user