mirror of
https://github.com/juanfont/headscale.git
synced 2024-11-26 08:53:05 +00:00
Initial work on ACLs
This commit is contained in:
parent
95fee5aa6f
commit
b161a92e58
6 changed files with 252 additions and 0 deletions
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…
Reference in a new issue