2021-07-03 09:55:32 +00:00
package headscale
import (
2021-07-04 10:35:18 +00:00
"encoding/json"
2022-02-05 16:18:39 +00:00
"errors"
2021-07-03 15:31:32 +00:00
"fmt"
2021-07-03 09:55:32 +00:00
"io"
"os"
2021-07-04 10:35:18 +00:00
"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
)
2021-11-04 22:16:56 +00:00
const (
2021-11-15 16:33:16 +00:00
errEmptyPolicy = Error ( "empty policy" )
errInvalidAction = Error ( "invalid action" )
errInvalidUserSection = Error ( "invalid user section" )
errInvalidGroup = Error ( "invalid group" )
errInvalidTag = Error ( "invalid tag" )
errInvalidNamespace = Error ( "invalid namespace" )
errInvalidPortFormat = Error ( "invalid port format" )
2021-11-04 22:16:56 +00:00
)
2021-07-03 09:55:32 +00:00
2021-11-14 17:31:51 +00:00
const (
2022-01-28 18:58:22 +00:00
Base8 = 8
2021-11-15 17:24:24 +00:00
Base10 = 10
BitSize16 = 16
2022-01-28 18:58:22 +00:00
BitSize32 = 32
BitSize64 = 64
2021-11-15 17:24:24 +00:00
portRangeBegin = 0
portRangeEnd = 65535
expectedTokenItems = 2
2021-11-14 17:31:51 +00:00
)
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-12-01 19:02:00 +00:00
log . Debug ( ) .
Str ( "func" , "LoadACLPolicy" ) .
Str ( "path" , path ) .
Msg ( "Loading ACL policy from path" )
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
2021-11-14 19:32:03 +00:00
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
2021-11-14 19:32:03 +00:00
ast , err := hujson . Parse ( policyBytes )
2021-11-05 07:24:00 +00:00
if err != nil {
return err
}
ast . Standardize ( )
2021-11-14 19:32:03 +00:00
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-11-15 16:33:16 +00:00
return errEmptyPolicy
2021-07-03 09:55:32 +00:00
}
2021-07-03 15:31:32 +00:00
h . aclPolicy = & policy
2022-02-03 19:00:41 +00:00
return h . UpdateACLRules ( )
}
func ( h * Headscale ) UpdateACLRules ( ) error {
2021-07-04 11:24:05 +00:00
rules , err := h . generateACLRules ( )
if err != nil {
return err
}
2021-12-01 19:02:00 +00:00
log . Trace ( ) . Interface ( "ACL" , rules ) . Msg ( "ACL rules generated" )
2022-02-03 19:00:41 +00:00
h . aclRules = rules
2021-07-04 11:24:05 +00:00
return nil
2021-07-03 15:31:32 +00:00
}
2021-11-04 22:16:56 +00:00
func ( h * Headscale ) generateACLRules ( ) ( [ ] tailcfg . FilterRule , error ) {
2021-07-03 15:31:32 +00:00
rules := [ ] tailcfg . FilterRule { }
2021-11-14 19:32:03 +00:00
for index , acl := range h . aclPolicy . ACLs {
if acl . Action != "accept" {
2021-11-15 16:33:16 +00:00
return nil , errInvalidAction
2021-07-03 15:31:32 +00:00
}
srcIPs := [ ] string { }
2021-11-14 19:32:03 +00:00
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 ( ) .
2021-11-14 19:32:03 +00:00
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
}
2021-11-04 22:16:56 +00:00
srcIPs = append ( srcIPs , srcs ... )
2021-07-03 15:31:32 +00:00
}
2021-07-04 10:35:18 +00:00
destPorts := [ ] tailcfg . NetPortRange { }
2021-11-14 19:32:03 +00:00
for innerIndex , ports := range acl . Ports {
dests , err := h . generateACLPolicyDestPorts ( ports )
2021-07-04 10:35:18 +00:00
if err != nil {
2021-08-05 17:18:18 +00:00
log . Error ( ) .
2021-11-14 19:32:03 +00:00
Msgf ( "Error parsing ACL %d, Port %d" , index , innerIndex )
2021-11-14 15:46:09 +00:00
2021-07-04 10:35:18 +00:00
return nil , err
}
2021-11-04 22:16:56 +00:00
destPorts = append ( destPorts , dests ... )
2021-07-04 10:35:18 +00:00
}
rules = append ( rules , tailcfg . FilterRule {
SrcIPs : srcIPs ,
DstPorts : destPorts ,
} )
2021-07-03 15:31:32 +00:00
}
2021-11-04 22:16:56 +00:00
return rules , nil
2021-07-03 15:31:32 +00:00
}
2021-11-04 22:16:56 +00:00
func ( h * Headscale ) generateACLPolicySrcIP ( u string ) ( [ ] string , error ) {
2021-07-04 10:35:18 +00:00
return h . expandAlias ( u )
}
2021-11-13 08:36:45 +00:00
func ( h * Headscale ) generateACLPolicyDestPorts (
d string ,
) ( [ ] tailcfg . NetPortRange , error ) {
2021-07-04 10:35:18 +00:00
tokens := strings . Split ( d , ":" )
2021-11-15 17:24:24 +00:00
if len ( tokens ) < expectedTokenItems || len ( tokens ) > 3 {
2021-11-15 16:33:16 +00:00
return nil , errInvalidPortFormat
2021-07-04 10:35:18 +00:00
}
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:*
2021-11-15 17:24:24 +00:00
if len ( tokens ) == expectedTokenItems {
2021-07-04 10:35:18 +00:00
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 { }
2021-11-04 22:16:56 +00:00
for _ , d := range expanded {
2021-07-04 10:35:18 +00:00
for _ , p := range * ports {
pr := tailcfg . NetPortRange {
IP : d ,
Ports : p ,
}
dests = append ( dests , pr )
}
}
2021-11-14 15:46:09 +00:00
2021-11-04 22:16:56 +00:00
return dests , nil
2021-07-04 10:35:18 +00:00
}
2022-02-05 16:18:39 +00:00
// expandalias has an input of either
// - a namespace
// - a group
// - a tag
// and transform these in IPAddresses
2021-11-14 19:32:03 +00:00
func ( h * Headscale ) expandAlias ( alias string ) ( [ ] string , error ) {
if alias == "*" {
2021-11-04 22:16:56 +00:00
return [ ] string { "*" } , nil
2021-07-03 15:31:32 +00:00
}
2021-11-14 19:32:03 +00:00
if strings . HasPrefix ( alias , "group:" ) {
2022-02-05 16:18:39 +00:00
namespaces , err := h . expandGroup ( alias )
if err != nil {
return nil , err
2021-07-03 15:31:32 +00:00
}
2021-07-04 10:35:18 +00:00
ips := [ ] string { }
2022-02-05 16:18:39 +00:00
for _ , n := range namespaces {
2021-07-04 10:35:18 +00:00
nodes , err := h . ListMachinesInNamespace ( n )
if err != nil {
2021-11-15 16:33:16 +00:00
return nil , errInvalidNamespace
2021-07-04 10:35:18 +00:00
}
2021-11-04 22:16:56 +00:00
for _ , node := range nodes {
2022-01-16 13:16:59 +00:00
ips = append ( ips , node . IPAddresses . ToStringSlice ( ) ... )
2021-07-04 10:35:18 +00:00
}
}
2021-11-14 15:46:09 +00:00
2021-11-04 22:16:56 +00:00
return ips , nil
2021-07-03 15:31:32 +00:00
}
2021-11-14 19:32:03 +00:00
if strings . HasPrefix ( alias , "tag:" ) {
2022-02-05 16:18:39 +00:00
var ips [ ] string
owners , err := h . expandTagOwners ( alias )
if err != nil {
2021-07-04 10:35:18 +00:00
return nil , err
}
2022-02-05 16:18:39 +00:00
for _ , namespace := range owners {
machines , err := h . ListMachinesInNamespace ( namespace )
if err != nil {
if errors . Is ( err , errNamespaceNotFound ) {
continue
} else {
2021-07-04 10:35:18 +00:00
return nil , err
}
2022-02-05 16:18:39 +00:00
}
for _ , machine := range machines {
if len ( machine . HostInfo ) == 0 {
continue
}
hi , err := machine . GetHostInfo ( )
2021-07-04 10:35:18 +00:00
if err != nil {
return nil , err
}
2022-02-05 16:18:39 +00:00
for _ , t := range hi . RequestTags {
if alias == t {
2022-01-16 13:16:59 +00:00
ips = append ( ips , machine . IPAddresses . ToStringSlice ( ) ... )
2021-07-04 10:35:18 +00:00
}
}
}
}
2021-11-04 22:16:56 +00:00
return ips , nil
2021-07-03 15:31:32 +00:00
}
2021-11-14 19:32:03 +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 { }
2021-11-04 22:16:56 +00:00
for _ , n := range nodes {
2022-01-16 13:16:59 +00:00
ips = append ( ips , n . IPAddresses . ToStringSlice ( ) ... )
2021-07-03 15:31:32 +00:00
}
2021-11-14 15:46:09 +00:00
2021-11-04 22:16:56 +00:00
return ips , nil
2021-07-03 15:31:32 +00:00
}
2021-11-14 19:32:03 +00:00
if h , ok := h . aclPolicy . Hosts [ alias ] ; ok {
2021-11-04 22:16:56 +00:00
return [ ] string { h . String ( ) } , nil
2021-07-03 15:31:32 +00:00
}
2021-11-14 19:32:03 +00:00
ip , err := netaddr . ParseIP ( alias )
2021-07-03 15:31:32 +00:00
if err == nil {
2021-11-04 22:16:56 +00:00
return [ ] string { ip . String ( ) } , nil
2021-07-03 15:31:32 +00:00
}
2021-11-14 19:32:03 +00:00
cidr , err := netaddr . ParseIPPrefix ( alias )
2021-07-03 15:31:32 +00:00
if err == nil {
2021-11-04 22:16:56 +00:00
return [ ] string { cidr . String ( ) } , nil
2021-07-03 15:31:32 +00:00
}
2021-11-15 16:33:16 +00:00
return nil , errInvalidUserSection
2021-07-03 09:55:32 +00:00
}
2021-07-04 10:35:18 +00:00
2022-02-05 16:18:39 +00:00
// expandTagOwners will return a list of namespace. An owner can be either a namespace or a group
// a group cannot be composed of groups
func ( h * Headscale ) expandTagOwners ( owner string ) ( [ ] string , error ) {
var owners [ ] string
ows , ok := h . aclPolicy . TagOwners [ owner ]
if ! ok {
return [ ] string { } , fmt . Errorf ( "%w. %v isn't owned by a TagOwner. Please add one first. https://tailscale.com/kb/1018/acls/#tag-owners" , errInvalidTag , owner )
}
for _ , ow := range ows {
if strings . HasPrefix ( ow , "group:" ) {
gs , err := h . expandGroup ( ow )
if err != nil {
return [ ] string { } , err
}
owners = append ( owners , gs ... )
} else {
owners = append ( owners , ow )
}
}
return owners , nil
}
// expandGroup will return the list of namespace inside the group
// after some validation
func ( h * Headscale ) expandGroup ( group string ) ( [ ] string , error ) {
gs , ok := h . aclPolicy . Groups [ group ]
if ! ok {
return [ ] string { } , fmt . Errorf ( "group %v isn't registered. %w" , group , errInvalidGroup )
}
for _ , g := range gs {
if strings . HasPrefix ( g , "group:" ) {
return [ ] string { } , fmt . Errorf ( "%w. A group cannot be composed of groups. https://tailscale.com/kb/1018/acls/#groups" , errInvalidGroup )
}
}
return gs , nil
}
2021-11-14 19:32:03 +00:00
func ( h * Headscale ) expandPorts ( portsStr string ) ( * [ ] tailcfg . PortRange , error ) {
if portsStr == "*" {
2021-11-14 17:31:51 +00:00
return & [ ] tailcfg . PortRange {
2021-11-15 17:24:24 +00:00
{ First : portRangeBegin , Last : portRangeEnd } ,
2021-11-14 17:31:51 +00:00
} , nil
2021-07-04 10:35:18 +00:00
}
ports := [ ] tailcfg . PortRange { }
2021-11-14 19:32:03 +00:00
for _ , portStr := range strings . Split ( portsStr , "," ) {
rang := strings . Split ( portStr , "-" )
2021-11-14 17:44:37 +00:00
switch len ( rang ) {
case 1 :
2021-11-15 17:24:24 +00:00
port , err := strconv . ParseUint ( rang [ 0 ] , Base10 , BitSize16 )
2021-07-04 10:35:18 +00:00
if err != nil {
return nil , err
}
ports = append ( ports , tailcfg . PortRange {
2021-11-14 19:32:03 +00:00
First : uint16 ( port ) ,
Last : uint16 ( port ) ,
2021-07-04 10:35:18 +00:00
} )
2021-11-14 17:44:37 +00:00
2021-11-15 17:24:24 +00:00
case expectedTokenItems :
start , err := strconv . ParseUint ( rang [ 0 ] , Base10 , BitSize16 )
2021-07-04 10:35:18 +00:00
if err != nil {
return nil , err
}
2021-11-15 17:24:24 +00:00
last , err := strconv . ParseUint ( rang [ 1 ] , Base10 , BitSize16 )
2021-07-04 10:35:18 +00:00
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 :
2021-11-15 16:33:16 +00:00
return nil , errInvalidPortFormat
2021-07-04 10:35:18 +00:00
}
}
2021-11-14 15:46:09 +00:00
2021-07-04 10:35:18 +00:00
return & ports , nil
}