headscale/acls.go

299 lines
6.2 KiB
Go
Raw Normal View History

2021-07-03 09:55:32 +00:00
package headscale
import (
"encoding/json"
2021-07-03 15:31:32 +00:00
"fmt"
2021-07-03 09:55:32 +00:00
"io"
"os"
"strconv"
2021-07-03 15:31:32 +00:00
"strings"
2021-07-03 09:55:32 +00:00
2021-08-05 17:18:18 +00:00
"github.com/rs/zerolog/log"
2021-07-03 09:55:32 +00:00
"github.com/tailscale/hujson"
2021-07-03 15:31:32 +00:00
"inet.af/netaddr"
"tailscale.com/tailcfg"
2021-07-03 09:55:32 +00:00
)
const (
errorEmptyPolicy = Error("empty policy")
errorInvalidAction = Error("invalid action")
errorInvalidUserSection = Error("invalid user section")
errorInvalidGroup = Error("invalid group")
errorInvalidTag = Error("invalid tag")
errorInvalidNamespace = Error("invalid namespace")
errorInvalidPortFormat = Error("invalid port format")
)
2021-07-03 09:55:32 +00:00
const (
PORT_RANGE_BEGIN = 0
PORT_RANGE_END = 65535
BASE_10 = 10
BIT_SIZE_16 = 16
EXPECTED_TOKEN_ITEMS = 2
)
2021-11-13 08:39:04 +00:00
// LoadACLPolicy loads the ACL policy from the specify path, and generates the ACL rules.
2021-07-04 11:33:00 +00:00
func (h *Headscale) LoadACLPolicy(path string) error {
2021-07-03 09:55:32 +00:00
policyFile, err := os.Open(path)
if err != nil {
2021-07-03 15:31:32 +00:00
return err
2021-07-03 09:55:32 +00:00
}
defer policyFile.Close()
var policy ACLPolicy
policyBytes, err := io.ReadAll(policyFile)
2021-07-03 09:55:32 +00:00
if err != nil {
2021-07-03 15:31:32 +00:00
return err
2021-07-03 09:55:32 +00:00
}
2021-11-05 07:24:00 +00:00
ast, err := hujson.Parse(policyBytes)
2021-11-05 07:24:00 +00:00
if err != nil {
return err
}
ast.Standardize()
policyBytes = ast.Pack()
err = json.Unmarshal(policyBytes, &policy)
2021-07-04 11:33:00 +00:00
if err != nil {
return err
}
2021-07-03 09:55:32 +00:00
if policy.IsZero() {
2021-07-03 15:31:32 +00:00
return errorEmptyPolicy
2021-07-03 09:55:32 +00:00
}
2021-07-03 15:31:32 +00:00
h.aclPolicy = &policy
2021-07-04 11:24:05 +00:00
rules, err := h.generateACLRules()
if err != nil {
return err
}
h.aclRules = rules
2021-11-14 15:46:09 +00:00
2021-07-04 11:24:05 +00:00
return nil
2021-07-03 15:31:32 +00:00
}
func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) {
2021-07-03 15:31:32 +00:00
rules := []tailcfg.FilterRule{}
for index, acl := range h.aclPolicy.ACLs {
if acl.Action != "accept" {
2021-07-03 15:31:32 +00:00
return nil, errorInvalidAction
}
filterRule := tailcfg.FilterRule{}
2021-07-03 15:31:32 +00:00
srcIPs := []string{}
for innerIndex, user := range acl.Users {
srcs, err := h.generateACLPolicySrcIP(user)
2021-07-03 15:31:32 +00:00
if err != nil {
2021-08-05 17:18:18 +00:00
log.Error().
Msgf("Error parsing ACL %d, User %d", index, innerIndex)
2021-11-14 15:46:09 +00:00
2021-07-03 15:31:32 +00:00
return nil, err
}
srcIPs = append(srcIPs, srcs...)
2021-07-03 15:31:32 +00:00
}
filterRule.SrcIPs = srcIPs
2021-07-03 15:31:32 +00:00
destPorts := []tailcfg.NetPortRange{}
for innerIndex, ports := range acl.Ports {
dests, err := h.generateACLPolicyDestPorts(ports)
if err != nil {
2021-08-05 17:18:18 +00:00
log.Error().
Msgf("Error parsing ACL %d, Port %d", index, innerIndex)
2021-11-14 15:46:09 +00:00
return nil, err
}
destPorts = append(destPorts, dests...)
}
rules = append(rules, tailcfg.FilterRule{
SrcIPs: srcIPs,
DstPorts: destPorts,
})
2021-07-03 15:31:32 +00:00
}
return rules, nil
2021-07-03 15:31:32 +00:00
}
func (h *Headscale) generateACLPolicySrcIP(u string) ([]string, error) {
return h.expandAlias(u)
}
2021-11-13 08:36:45 +00:00
func (h *Headscale) generateACLPolicyDestPorts(
d string,
) ([]tailcfg.NetPortRange, error) {
tokens := strings.Split(d, ":")
if len(tokens) < EXPECTED_TOKEN_ITEMS || len(tokens) > 3 {
return nil, errorInvalidPortFormat
}
var alias string
// We can have here stuff like:
// git-server:*
// 192.168.1.0/24:22
// tag:montreal-webserver:80,443
// tag:api-server:443
// example-host-1:*
if len(tokens) == EXPECTED_TOKEN_ITEMS {
alias = tokens[0]
} else {
alias = fmt.Sprintf("%s:%s", tokens[0], tokens[1])
}
expanded, err := h.expandAlias(alias)
if err != nil {
return nil, err
}
ports, err := h.expandPorts(tokens[len(tokens)-1])
if err != nil {
return nil, err
}
dests := []tailcfg.NetPortRange{}
for _, d := range expanded {
for _, p := range *ports {
pr := tailcfg.NetPortRange{
IP: d,
Ports: p,
}
dests = append(dests, pr)
}
}
2021-11-14 15:46:09 +00:00
return dests, nil
}
func (h *Headscale) expandAlias(alias string) ([]string, error) {
if alias == "*" {
return []string{"*"}, nil
2021-07-03 15:31:32 +00:00
}
if strings.HasPrefix(alias, "group:") {
if _, ok := h.aclPolicy.Groups[alias]; !ok {
2021-07-03 15:31:32 +00:00
return nil, errorInvalidGroup
}
ips := []string{}
for _, n := range h.aclPolicy.Groups[alias] {
nodes, err := h.ListMachinesInNamespace(n)
if err != nil {
return nil, errorInvalidNamespace
}
for _, node := range nodes {
ips = append(ips, node.IPAddress)
}
}
2021-11-14 15:46:09 +00:00
return ips, nil
2021-07-03 15:31:32 +00:00
}
if strings.HasPrefix(alias, "tag:") {
if _, ok := h.aclPolicy.TagOwners[alias]; !ok {
return nil, errorInvalidTag
}
// This will have HORRIBLE performance.
// We need to change the data model to better store tags
machines := []Machine{}
2021-07-04 19:56:13 +00:00
if err := h.db.Where("registered").Find(&machines).Error; err != nil {
return nil, err
}
ips := []string{}
for _, machine := range machines {
hostinfo := tailcfg.Hostinfo{}
if len(machine.HostInfo) != 0 {
hi, err := machine.HostInfo.MarshalJSON()
if err != nil {
return nil, err
}
err = json.Unmarshal(hi, &hostinfo)
if err != nil {
return nil, err
}
// FIXME: Check TagOwners allows this
for _, t := range hostinfo.RequestTags {
if alias[4:] == t {
ips = append(ips, machine.IPAddress)
2021-11-14 15:46:09 +00:00
break
}
}
}
}
2021-11-14 15:46:09 +00:00
return ips, nil
2021-07-03 15:31:32 +00:00
}
n, err := h.GetNamespace(alias)
2021-07-03 15:31:32 +00:00
if err == nil {
nodes, err := h.ListMachinesInNamespace(n.Name)
if err != nil {
return nil, err
}
ips := []string{}
for _, n := range nodes {
2021-07-03 15:31:32 +00:00
ips = append(ips, n.IPAddress)
}
2021-11-14 15:46:09 +00:00
return ips, nil
2021-07-03 15:31:32 +00:00
}
if h, ok := h.aclPolicy.Hosts[alias]; ok {
return []string{h.String()}, nil
2021-07-03 15:31:32 +00:00
}
ip, err := netaddr.ParseIP(alias)
2021-07-03 15:31:32 +00:00
if err == nil {
return []string{ip.String()}, nil
2021-07-03 15:31:32 +00:00
}
cidr, err := netaddr.ParseIPPrefix(alias)
2021-07-03 15:31:32 +00:00
if err == nil {
return []string{cidr.String()}, nil
2021-07-03 15:31:32 +00:00
}
return nil, errorInvalidUserSection
2021-07-03 09:55:32 +00:00
}
func (h *Headscale) expandPorts(portsStr string) (*[]tailcfg.PortRange, error) {
if portsStr == "*" {
return &[]tailcfg.PortRange{
{First: PORT_RANGE_BEGIN, Last: PORT_RANGE_END},
}, nil
}
ports := []tailcfg.PortRange{}
for _, portStr := range strings.Split(portsStr, ",") {
rang := strings.Split(portStr, "-")
2021-11-14 17:44:37 +00:00
switch len(rang) {
case 1:
port, err := strconv.ParseUint(rang[0], BASE_10, BIT_SIZE_16)
if err != nil {
return nil, err
}
ports = append(ports, tailcfg.PortRange{
First: uint16(port),
Last: uint16(port),
})
2021-11-14 17:44:37 +00:00
case EXPECTED_TOKEN_ITEMS:
start, err := strconv.ParseUint(rang[0], BASE_10, BIT_SIZE_16)
if err != nil {
return nil, err
}
last, err := strconv.ParseUint(rang[1], BASE_10, BIT_SIZE_16)
if err != nil {
return nil, err
}
ports = append(ports, tailcfg.PortRange{
First: uint16(start),
Last: uint16(last),
})
2021-11-14 17:44:37 +00:00
default:
return nil, errorInvalidPortFormat
}
}
2021-11-14 15:46:09 +00:00
return &ports, nil
}