headscale/hscontrol/policy/acls.go

1044 lines
25 KiB
Go
Raw Normal View History

package policy
2021-07-03 05:55:32 -04:00
import (
"encoding/json"
"errors"
2021-07-03 11:31:32 -04:00
"fmt"
2021-07-03 05:55:32 -04:00
"io"
2022-09-03 17:46:14 -04:00
"net/netip"
2021-07-03 05:55:32 -04:00
"os"
"slices"
"strconv"
2021-07-03 11:31:32 -04:00
"strings"
"time"
2021-07-03 05:55:32 -04:00
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
2021-08-05 13:18:18 -04:00
"github.com/rs/zerolog/log"
2021-07-03 05:55:32 -04:00
"github.com/tailscale/hujson"
"go4.org/netipx"
"tailscale.com/net/tsaddr"
2021-07-03 11:31:32 -04:00
"tailscale.com/tailcfg"
2021-07-03 05:55:32 -04:00
)
var (
ErrEmptyPolicy = errors.New("empty policy")
ErrInvalidAction = errors.New("invalid action")
ErrInvalidGroup = errors.New("invalid group")
ErrInvalidTag = errors.New("invalid tag")
ErrInvalidPortFormat = errors.New("invalid port format")
ErrWildcardIsNeeded = errors.New("wildcard as port is required for the protocol")
)
2021-07-03 05:55:32 -04:00
const (
portRangeBegin = 0
portRangeEnd = 65535
expectedTokenItems = 2
)
var theInternetSet *netipx.IPSet
// theInternet returns the IPSet for the Internet.
// https://www.youtube.com/watch?v=iDbyYGrswtg
func theInternet() *netipx.IPSet {
if theInternetSet != nil {
return theInternetSet
}
var internetBuilder netipx.IPSetBuilder
internetBuilder.AddPrefix(netip.MustParsePrefix("2000::/3"))
internetBuilder.AddPrefix(tsaddr.AllIPv4())
// Delete Private network addresses
// https://datatracker.ietf.org/doc/html/rfc1918
internetBuilder.RemovePrefix(netip.MustParsePrefix("fc00::/7"))
internetBuilder.RemovePrefix(netip.MustParsePrefix("10.0.0.0/8"))
internetBuilder.RemovePrefix(netip.MustParsePrefix("172.16.0.0/12"))
internetBuilder.RemovePrefix(netip.MustParsePrefix("192.168.0.0/16"))
// Delete Tailscale networks
internetBuilder.RemovePrefix(tsaddr.TailscaleULARange())
internetBuilder.RemovePrefix(tsaddr.CGNATRange())
// Delete "cant find DHCP networks"
internetBuilder.RemovePrefix(netip.MustParsePrefix("fe80::/10")) // link-loca
internetBuilder.RemovePrefix(netip.MustParsePrefix("169.254.0.0/16"))
theInternetSet, _ := internetBuilder.IPSet()
return theInternetSet
}
2022-06-26 05:43:17 -04:00
// For some reason golang.org/x/net/internal/iana is an internal package.
2022-06-11 08:09:08 -04:00
const (
protocolICMP = 1 // Internet Control Message
protocolIGMP = 2 // Internet Group Management
protocolIPv4 = 4 // IPv4 encapsulation
protocolTCP = 6 // Transmission Control
protocolEGP = 8 // Exterior Gateway Protocol
protocolIGP = 9 // any private interior gateway (used by Cisco for their IGRP)
protocolUDP = 17 // User Datagram
protocolGRE = 47 // Generic Routing Encapsulation
protocolESP = 50 // Encap Security Payload
protocolAH = 51 // Authentication Header
protocolIPv6ICMP = 58 // ICMP for IPv6
protocolSCTP = 132 // Stream Control Transmission Protocol
ProtocolFC = 133 // Fibre Channel
)
// LoadACLPolicyFromPath loads the ACL policy from the specify path, and generates the ACL rules.
func LoadACLPolicyFromPath(path string) (*ACLPolicy, error) {
log.Debug().
Str("func", "LoadACLPolicy").
Str("path", path).
Msg("Loading ACL policy from path")
2021-07-03 05:55:32 -04:00
policyFile, err := os.Open(path)
if err != nil {
return nil, err
2021-07-03 05:55:32 -04:00
}
defer policyFile.Close()
policyBytes, err := io.ReadAll(policyFile)
2021-07-03 05:55:32 -04:00
if err != nil {
return nil, err
2021-07-03 05:55:32 -04:00
}
2021-11-05 03:24:00 -04:00
log.Debug().
Str("path", path).
Bytes("file", policyBytes).
Msg("Loading ACLs")
return LoadACLPolicyFromBytes(policyBytes)
}
func LoadACLPolicyFromBytes(acl []byte) (*ACLPolicy, error) {
var policy ACLPolicy
2022-02-27 03:04:48 -05:00
ast, err := hujson.Parse(acl)
if err != nil {
return nil, fmt.Errorf("parsing hujson, err: %w", err)
}
2022-02-27 03:04:48 -05:00
ast.Standardize()
acl = ast.Pack()
if err := json.Unmarshal(acl, &policy); err != nil {
return nil, fmt.Errorf("unmarshalling policy, err: %w", err)
2021-07-04 07:33:00 -04:00
}
2022-02-27 03:04:48 -05:00
2021-07-03 05:55:32 -04:00
if policy.IsZero() {
return nil, ErrEmptyPolicy
2021-07-03 05:55:32 -04:00
}
return &policy, nil
2022-02-03 14:00:41 -05:00
}
func GenerateFilterAndSSHRulesForTests(
policy *ACLPolicy,
2023-09-24 07:42:05 -04:00
node *types.Node,
peers types.Nodes,
users []types.User,
) ([]tailcfg.FilterRule, *tailcfg.SSHPolicy, error) {
// If there is no policy defined, we default to allow all
if policy == nil {
return tailcfg.FilterAllowAll, &tailcfg.SSHPolicy{}, nil
}
rules, err := policy.CompileFilterRules(users, append(peers, node))
2021-07-04 07:24:05 -04:00
if err != nil {
return []tailcfg.FilterRule{}, &tailcfg.SSHPolicy{}, err
2021-07-04 07:24:05 -04:00
}
2023-09-24 07:42:05 -04:00
log.Trace().Interface("ACL", rules).Str("node", node.GivenName).Msg("ACL rules")
sshPolicy, err := policy.CompileSSHPolicy(node, users, peers)
if err != nil {
return []tailcfg.FilterRule{}, &tailcfg.SSHPolicy{}, err
}
return rules, sshPolicy, nil
2021-07-03 11:31:32 -04:00
}
// CompileFilterRules takes a set of nodes and an ACLPolicy and generates a
// set of Tailscale compatible FilterRules used to allow traffic on clients.
func (pol *ACLPolicy) CompileFilterRules(
users []types.User,
nodes types.Nodes,
) ([]tailcfg.FilterRule, error) {
if pol == nil {
return tailcfg.FilterAllowAll, nil
}
var rules []tailcfg.FilterRule
2021-07-03 11:31:32 -04:00
for index, acl := range pol.ACLs {
if acl.Action != "accept" {
return nil, ErrInvalidAction
2021-07-03 11:31:32 -04:00
}
var srcIPs []string
for srcIndex, src := range acl.Sources {
srcs, err := pol.expandSource(src, users, nodes)
2021-07-03 11:31:32 -04:00
if err != nil {
return nil, fmt.Errorf(
"parsing policy, acl index: %d->%d: %w",
index,
srcIndex,
err,
)
2021-07-03 11:31:32 -04:00
}
srcIPs = append(srcIPs, srcs...)
2021-07-03 11:31:32 -04:00
}
protocols, isWildcard, err := parseProtocol(acl.Protocol)
if err != nil {
2024-04-12 09:57:43 -04:00
return nil, fmt.Errorf("parsing policy, protocol err: %w ", err)
}
destPorts := []tailcfg.NetPortRange{}
for _, dest := range acl.Destinations {
alias, port, err := parseDestination(dest)
if err != nil {
return nil, err
}
expanded, err := pol.ExpandAlias(
2023-09-24 07:42:05 -04:00
nodes,
users,
alias,
2022-08-04 04:47:00 -04:00
)
if err != nil {
return nil, err
}
2021-11-14 10:46:09 -05:00
ports, err := expandPorts(port, isWildcard)
if err != nil {
return nil, err
}
var dests []tailcfg.NetPortRange
for _, dest := range expanded.Prefixes() {
for _, port := range *ports {
pr := tailcfg.NetPortRange{
IP: dest.String(),
Ports: port,
}
dests = append(dests, pr)
}
}
destPorts = append(destPorts, dests...)
}
rules = append(rules, tailcfg.FilterRule{
SrcIPs: srcIPs,
DstPorts: destPorts,
IPProto: protocols,
})
2021-07-03 11:31:32 -04:00
}
return rules, nil
2021-07-03 11:31:32 -04:00
}
2023-09-24 07:42:05 -04:00
// ReduceFilterRules takes a node and a set of rules and removes all rules and destinations
// that are not relevant to that particular node.
2023-09-24 07:42:05 -04:00
func ReduceFilterRules(node *types.Node, rules []tailcfg.FilterRule) []tailcfg.FilterRule {
ret := []tailcfg.FilterRule{}
for _, rule := range rules {
2023-09-24 07:42:05 -04:00
// record if the rule is actually relevant for the given node.
var dests []tailcfg.NetPortRange
DEST_LOOP:
for _, dest := range rule.DstPorts {
expanded, err := util.ParseIPSet(dest.IP, nil)
// Fail closed, if we cant parse it, then we should not allow
// access.
if err != nil {
continue DEST_LOOP
}
if node.InIPSet(expanded) {
dests = append(dests, dest)
continue DEST_LOOP
}
// If the node exposes routes, ensure they are note removed
// when the filters are reduced.
if node.Hostinfo != nil {
if len(node.Hostinfo.RoutableIPs) > 0 {
for _, routableIP := range node.Hostinfo.RoutableIPs {
if expanded.OverlapsPrefix(routableIP) {
dests = append(dests, dest)
continue DEST_LOOP
}
}
}
}
}
if len(dests) > 0 {
ret = append(ret, tailcfg.FilterRule{
SrcIPs: rule.SrcIPs,
DstPorts: dests,
IPProto: rule.IPProto,
})
}
}
return ret
}
func (pol *ACLPolicy) CompileSSHPolicy(
2023-09-24 07:42:05 -04:00
node *types.Node,
users []types.User,
2023-09-24 07:42:05 -04:00
peers types.Nodes,
) (*tailcfg.SSHPolicy, error) {
if pol == nil {
return nil, nil
}
var rules []*tailcfg.SSHRule
acceptAction := tailcfg.SSHAction{
Message: "",
Reject: false,
Accept: true,
SessionDuration: 0,
AllowAgentForwarding: true,
HoldAndDelegate: "",
AllowLocalPortForwarding: true,
}
rejectAction := tailcfg.SSHAction{
Message: "",
Reject: true,
Accept: false,
SessionDuration: 0,
AllowAgentForwarding: false,
HoldAndDelegate: "",
AllowLocalPortForwarding: false,
}
for index, sshACL := range pol.SSHs {
var dest netipx.IPSetBuilder
for _, src := range sshACL.Destinations {
expanded, err := pol.ExpandAlias(append(peers, node), users, src)
if err != nil {
return nil, err
}
dest.AddSet(expanded)
}
destSet, err := dest.IPSet()
if err != nil {
return nil, err
}
if !node.InIPSet(destSet) {
continue
}
action := rejectAction
switch sshACL.Action {
case "accept":
action = acceptAction
case "check":
checkAction, err := sshCheckAction(sshACL.CheckPeriod)
if err != nil {
return nil, fmt.Errorf(
"parsing SSH policy, parsing check duration, index: %d: %w",
index,
err,
)
} else {
action = *checkAction
}
default:
return nil, fmt.Errorf(
"parsing SSH policy, unknown action %q, index: %d: %w",
sshACL.Action,
index,
err,
)
}
principals := make([]*tailcfg.SSHPrincipal, 0, len(sshACL.Sources))
for innerIndex, rawSrc := range sshACL.Sources {
if isWildcard(rawSrc) {
principals = append(principals, &tailcfg.SSHPrincipal{
Any: true,
})
} else if isGroup(rawSrc) {
users, err := pol.expandUsersFromGroup(rawSrc)
if err != nil {
2024-04-12 09:57:43 -04:00
return nil, fmt.Errorf("parsing SSH policy, expanding user from group, index: %d->%d: %w", index, innerIndex, err)
}
for _, user := range users {
principals = append(principals, &tailcfg.SSHPrincipal{
UserLogin: user,
})
}
} else {
expandedSrcs, err := pol.ExpandAlias(
peers,
users,
rawSrc,
)
if err != nil {
2024-04-12 09:57:43 -04:00
return nil, fmt.Errorf("parsing SSH policy, expanding alias, index: %d->%d: %w", index, innerIndex, err)
}
for _, expandedSrc := range expandedSrcs.Prefixes() {
principals = append(principals, &tailcfg.SSHPrincipal{
NodeIP: expandedSrc.Addr().String(),
})
}
}
}
userMap := make(map[string]string, len(sshACL.Users))
for _, user := range sshACL.Users {
userMap[user] = "="
}
rules = append(rules, &tailcfg.SSHRule{
Principals: principals,
SSHUsers: userMap,
Action: &action,
})
}
return &tailcfg.SSHPolicy{
Rules: rules,
}, nil
}
func sshCheckAction(duration string) (*tailcfg.SSHAction, error) {
sessionLength, err := time.ParseDuration(duration)
if err != nil {
return nil, err
}
return &tailcfg.SSHAction{
Message: "",
Reject: false,
Accept: true,
SessionDuration: sessionLength,
AllowAgentForwarding: true,
HoldAndDelegate: "",
AllowLocalPortForwarding: true,
}, nil
}
func parseDestination(dest string) (string, string, error) {
var tokens []string
2023-04-16 06:26:35 -04:00
// Check if there is a IPv4/6:Port combination, IPv6 has more than
// three ":".
tokens = strings.Split(dest, ":")
if len(tokens) < expectedTokenItems || len(tokens) > 3 {
2023-04-16 06:26:35 -04:00
port := tokens[len(tokens)-1]
maybeIPv6Str := strings.TrimSuffix(dest, ":"+port)
log.Trace().Str("maybeIPv6Str", maybeIPv6Str).Msg("")
2023-05-20 05:53:01 -04:00
filteredMaybeIPv6Str := maybeIPv6Str
if strings.Contains(maybeIPv6Str, "/") {
networkParts := strings.Split(maybeIPv6Str, "/")
filteredMaybeIPv6Str = networkParts[0]
}
if maybeIPv6, err := netip.ParseAddr(filteredMaybeIPv6Str); err != nil && !maybeIPv6.Is6() {
2023-04-16 06:26:35 -04:00
log.Trace().Err(err).Msg("trying to parse as IPv6")
return "", "", fmt.Errorf(
2023-04-16 06:26:35 -04:00
"failed to parse destination, tokens %v: %w",
tokens,
ErrInvalidPortFormat,
2023-04-16 06:26:35 -04:00
)
} else {
tokens = []string{maybeIPv6Str, port}
}
}
var alias string
// We can have here stuff like:
// git-server:*
// 192.168.1.0/24:22
2023-04-16 06:26:35 -04:00
// fd7a:115c:a1e0::2:22
// fd7a:115c:a1e0::2/128:22
// tag:montreal-webserver:80,443
// tag:api-server:443
// example-host-1:*
if len(tokens) == expectedTokenItems {
alias = tokens[0]
} else {
alias = fmt.Sprintf("%s:%s", tokens[0], tokens[1])
}
return alias, tokens[len(tokens)-1], nil
}
// parseProtocol reads the proto field of the ACL and generates a list of
// protocols that will be allowed, following the IANA IP protocol number
// https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
//
// If the ACL proto field is empty, it allows ICMPv4, ICMPv6, TCP, and UDP,
// as per Tailscale behaviour (see tailcfg.FilterRule).
//
// Also returns a boolean indicating if the protocol
// requires all the destinations to use wildcard as port number (only TCP,
// UDP and SCTP support specifying ports).
func parseProtocol(protocol string) ([]int, bool, error) {
switch protocol {
case "":
return nil, false, nil
case "igmp":
2022-06-11 08:09:08 -04:00
return []int{protocolIGMP}, true, nil
case "ipv4", "ip-in-ip":
2022-06-11 08:09:08 -04:00
return []int{protocolIPv4}, true, nil
case "tcp":
2022-06-11 08:09:08 -04:00
return []int{protocolTCP}, false, nil
case "egp":
2022-06-11 08:09:08 -04:00
return []int{protocolEGP}, true, nil
case "igp":
2022-06-11 08:09:08 -04:00
return []int{protocolIGP}, true, nil
case "udp":
2022-06-11 08:09:08 -04:00
return []int{protocolUDP}, false, nil
case "gre":
2022-06-11 08:09:08 -04:00
return []int{protocolGRE}, true, nil
case "esp":
2022-06-11 08:09:08 -04:00
return []int{protocolESP}, true, nil
case "ah":
2022-06-11 08:09:08 -04:00
return []int{protocolAH}, true, nil
case "sctp":
2022-06-11 08:09:08 -04:00
return []int{protocolSCTP}, false, nil
case "icmp":
2022-06-11 08:09:08 -04:00
return []int{protocolICMP, protocolIPv6ICMP}, true, nil
default:
protocolNumber, err := strconv.Atoi(protocol)
if err != nil {
2024-04-12 09:57:43 -04:00
return nil, false, fmt.Errorf("parsing protocol number: %w", err)
}
2022-08-04 04:47:00 -04:00
needsWildcard := protocolNumber != protocolTCP &&
protocolNumber != protocolUDP &&
protocolNumber != protocolSCTP
return []int{protocolNumber}, needsWildcard, nil
}
}
// expandSource returns a set of Source IPs that would be associated
// with the given src alias.
func (pol *ACLPolicy) expandSource(
src string,
users []types.User,
2023-09-24 07:42:05 -04:00
nodes types.Nodes,
) ([]string, error) {
ipSet, err := pol.ExpandAlias(nodes, users, src)
if err != nil {
return []string{}, err
}
var prefixes []string
for _, prefix := range ipSet.Prefixes() {
prefixes = append(prefixes, prefix.String())
}
return prefixes, nil
}
// expandalias has an input of either
// - a user
// - a group
// - a tag
// - a host
2023-04-16 06:26:35 -04:00
// - an ip
// - a cidr
// - an autogroup
// and transform these in IPAddresses.
func (pol *ACLPolicy) ExpandAlias(
2023-09-24 07:42:05 -04:00
nodes types.Nodes,
users []types.User,
2022-02-14 09:54:51 -05:00
alias string,
) (*netipx.IPSet, error) {
if isWildcard(alias) {
return util.ParseIPSet("*", nil)
2021-07-03 11:31:32 -04:00
}
build := netipx.IPSetBuilder{}
log.Debug().
Str("alias", alias).
Msg("Expanding")
// if alias is a group
if isGroup(alias) {
return pol.expandIPsFromGroup(alias, users, nodes)
2021-07-03 11:31:32 -04:00
}
// if alias is a tag
if isTag(alias) {
return pol.expandIPsFromTag(alias, users, nodes)
2021-07-03 11:31:32 -04:00
}
if isAutoGroup(alias) {
return expandAutoGroup(alias)
}
// if alias is a user
if ips, err := pol.expandIPsFromUser(alias, users, nodes); ips != nil {
return ips, err
2021-07-03 11:31:32 -04:00
}
// if alias is an host
// Note, this is recursive.
if h, ok := pol.Hosts[alias]; ok {
log.Trace().Str("host", h.String()).Msg("ExpandAlias got hosts entry")
2023-04-16 06:26:35 -04:00
return pol.ExpandAlias(nodes, users, h.String())
2021-07-03 11:31:32 -04:00
}
// if alias is an IP
2023-04-16 06:26:35 -04:00
if ip, err := netip.ParseAddr(alias); err == nil {
2023-09-24 07:42:05 -04:00
return pol.expandIPsFromSingleIP(ip, nodes)
2021-07-03 11:31:32 -04:00
}
// if alias is an IP Prefix (CIDR)
if prefix, err := netip.ParsePrefix(alias); err == nil {
2023-09-24 07:42:05 -04:00
return pol.expandIPsFromIPPrefix(prefix, nodes)
2021-07-03 11:31:32 -04:00
}
log.Warn().Msgf("No IPs found with the alias %v", alias)
return build.IPSet()
2021-07-03 05:55:32 -04:00
}
// excludeCorrectlyTaggedNodes will remove from the list of input nodes the ones
// that are correctly tagged since they should not be listed as being in the user
// we assume in this function that we only have nodes from 1 user.
//
// TODO(kradalby): It is quite hard to understand what this function is doing,
// it seems like it trying to ensure that we dont include nodes that are tagged
// when we look up the nodes owned by a user.
// This should be refactored to be more clear as part of the Tags work in #1369.
2022-02-14 09:54:51 -05:00
func excludeCorrectlyTaggedNodes(
aclPolicy *ACLPolicy,
2023-09-24 07:42:05 -04:00
nodes types.Nodes,
user string,
2023-09-24 07:42:05 -04:00
) types.Nodes {
var out types.Nodes
var tags []string
for tag := range aclPolicy.TagOwners {
owners, _ := expandOwnersFromTag(aclPolicy, user)
ns := append(owners, user)
if slices.Contains(ns, user) {
tags = append(tags, tag)
}
}
2023-09-24 07:42:05 -04:00
// for each node if tag is in tags list, don't append it.
for _, node := range nodes {
found := false
if node.Hostinfo != nil {
for _, t := range node.Hostinfo.RequestTags {
if slices.Contains(tags, t) {
found = true
break
}
}
}
2023-09-24 07:42:05 -04:00
if len(node.ForcedTags) > 0 {
found = true
}
if !found {
2023-09-24 07:42:05 -04:00
out = append(out, node)
}
}
2022-03-02 03:15:14 -05:00
return out
}
func expandPorts(portsStr string, isWild bool) (*[]tailcfg.PortRange, error) {
if isWildcard(portsStr) {
return &[]tailcfg.PortRange{
{First: portRangeBegin, Last: portRangeEnd},
}, nil
}
if isWild {
return nil, ErrWildcardIsNeeded
}
var ports []tailcfg.PortRange
for _, portStr := range strings.Split(portsStr, ",") {
2023-04-16 06:26:35 -04:00
log.Trace().Msgf("parsing portstring: %s", portStr)
rang := strings.Split(portStr, "-")
2021-11-14 12:44:37 -05:00
switch len(rang) {
case 1:
port, err := strconv.ParseUint(rang[0], util.Base10, util.BitSize16)
if err != nil {
return nil, err
}
ports = append(ports, tailcfg.PortRange{
First: uint16(port),
Last: uint16(port),
})
2021-11-14 12:44:37 -05:00
case expectedTokenItems:
start, err := strconv.ParseUint(rang[0], util.Base10, util.BitSize16)
if err != nil {
return nil, err
}
last, err := strconv.ParseUint(rang[1], util.Base10, util.BitSize16)
if err != nil {
return nil, err
}
ports = append(ports, tailcfg.PortRange{
First: uint16(start),
Last: uint16(last),
})
2021-11-14 12:44:37 -05:00
default:
return nil, ErrInvalidPortFormat
}
}
2021-11-14 10:46:09 -05:00
return &ports, nil
}
// expandOwnersFromTag will return a list of user. An owner can be either a user or a group
// a group cannot be composed of groups.
func expandOwnersFromTag(
pol *ACLPolicy,
2022-03-01 15:01:46 -05:00
tag string,
) ([]string, error) {
noTagErr := fmt.Errorf(
"%w. %v isn't owned by a TagOwner. Please add one first. https://tailscale.com/kb/1018/acls/#tag-owners",
ErrInvalidTag,
tag,
)
if pol == nil {
return []string{}, noTagErr
}
var owners []string
ows, ok := pol.TagOwners[tag]
if !ok {
return []string{}, noTagErr
}
for _, owner := range ows {
if isGroup(owner) {
gs, err := pol.expandUsersFromGroup(owner)
if err != nil {
return []string{}, err
}
owners = append(owners, gs...)
} else {
owners = append(owners, owner)
}
}
return owners, nil
}
// expandUsersFromGroup will return the list of user inside the group
// after some validation.
func (pol *ACLPolicy) expandUsersFromGroup(
2022-03-01 15:01:46 -05:00
group string,
) ([]string, error) {
var users []string
log.Trace().Caller().Interface("pol", pol).Msg("test")
aclGroups, ok := pol.Groups[group]
if !ok {
2022-02-14 09:54:51 -05:00
return []string{}, fmt.Errorf(
"group %v isn't registered. %w",
group,
ErrInvalidGroup,
2022-02-14 09:54:51 -05:00
)
}
2022-03-01 15:01:46 -05:00
for _, group := range aclGroups {
if isGroup(group) {
2022-02-14 09:54:51 -05:00
return []string{}, fmt.Errorf(
"%w. A group cannot be composed of groups. https://tailscale.com/kb/1018/acls/#groups",
ErrInvalidGroup,
2022-02-14 09:54:51 -05:00
)
}
Redo OIDC configuration (#2020) expand user, add claims to user This commit expands the user table with additional fields that can be retrieved from OIDC providers (and other places) and uses this data in various tailscale response objects if it is available. This is the beginning of implementing https://docs.google.com/document/d/1X85PMxIaVWDF6T_UPji3OeeUqVBcGj_uHRM5CI-AwlY/edit trying to make OIDC more coherant and maintainable in addition to giving the user a better experience and integration with a provider. remove usernames in magic dns, normalisation of emails this commit removes the option to have usernames as part of MagicDNS domains and headscale will now align with Tailscale, where there is a root domain, and the machine name. In addition, the various normalisation functions for dns names has been made lighter not caring about username and special character that wont occur. Email are no longer normalised as part of the policy processing. untagle oidc and regcache, use typed cache This commits stops reusing the registration cache for oidc purposes and switches the cache to be types and not use any allowing the removal of a bunch of casting. try to make reauth/register branches clearer in oidc Currently there was a function that did a bunch of stuff, finding the machine key, trying to find the node, reauthing the node, returning some status, and it was called validate which was very confusing. This commit tries to split this into what to do if the node exists, if it needs to register etc. Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-10-02 08:50:17 -04:00
users = append(users, group)
}
return users, nil
}
func (pol *ACLPolicy) expandIPsFromGroup(
group string,
users []types.User,
2023-09-24 07:42:05 -04:00
nodes types.Nodes,
) (*netipx.IPSet, error) {
var build netipx.IPSetBuilder
userTokens, err := pol.expandUsersFromGroup(group)
if err != nil {
return &netipx.IPSet{}, err
}
for _, user := range userTokens {
filteredNodes := filterNodesByUser(nodes, users, user)
2023-09-24 07:42:05 -04:00
for _, node := range filteredNodes {
node.AppendToIPSet(&build)
}
}
return build.IPSet()
}
func (pol *ACLPolicy) expandIPsFromTag(
alias string,
users []types.User,
2023-09-24 07:42:05 -04:00
nodes types.Nodes,
) (*netipx.IPSet, error) {
var build netipx.IPSetBuilder
// check for forced tags
2023-09-24 07:42:05 -04:00
for _, node := range nodes {
if slices.Contains(node.ForcedTags, alias) {
node.AppendToIPSet(&build)
}
}
// find tag owners
owners, err := expandOwnersFromTag(pol, alias)
if err != nil {
if errors.Is(err, ErrInvalidTag) {
ipSet, _ := build.IPSet()
if len(ipSet.Prefixes()) == 0 {
return ipSet, fmt.Errorf(
"%w. %v isn't owned by a TagOwner and no forced tags are defined",
ErrInvalidTag,
alias,
)
}
return build.IPSet()
} else {
return nil, err
}
}
2023-09-24 07:42:05 -04:00
// filter out nodes per tag owner
for _, user := range owners {
nodes := filterNodesByUser(nodes, users, user)
2023-09-24 07:42:05 -04:00
for _, node := range nodes {
if node.Hostinfo == nil {
continue
}
if slices.Contains(node.Hostinfo.RequestTags, alias) {
node.AppendToIPSet(&build)
}
}
}
return build.IPSet()
}
func (pol *ACLPolicy) expandIPsFromUser(
user string,
users []types.User,
2023-09-24 07:42:05 -04:00
nodes types.Nodes,
) (*netipx.IPSet, error) {
var build netipx.IPSetBuilder
filteredNodes := filterNodesByUser(nodes, users, user)
2023-09-24 07:42:05 -04:00
filteredNodes = excludeCorrectlyTaggedNodes(pol, filteredNodes, user)
2023-09-24 07:42:05 -04:00
// shortcurcuit if we have no nodes to get ips from.
if len(filteredNodes) == 0 {
return nil, nil // nolint
}
2023-09-24 07:42:05 -04:00
for _, node := range filteredNodes {
node.AppendToIPSet(&build)
}
return build.IPSet()
}
func (pol *ACLPolicy) expandIPsFromSingleIP(
ip netip.Addr,
2023-09-24 07:42:05 -04:00
nodes types.Nodes,
) (*netipx.IPSet, error) {
log.Trace().Str("ip", ip.String()).Msg("ExpandAlias got ip")
2023-09-24 07:42:05 -04:00
matches := nodes.FilterByIP(ip)
var build netipx.IPSetBuilder
build.Add(ip)
2023-09-24 07:42:05 -04:00
for _, node := range matches {
node.AppendToIPSet(&build)
}
return build.IPSet()
}
func (pol *ACLPolicy) expandIPsFromIPPrefix(
prefix netip.Prefix,
2023-09-24 07:42:05 -04:00
nodes types.Nodes,
) (*netipx.IPSet, error) {
log.Trace().Str("prefix", prefix.String()).Msg("expandAlias got prefix")
var build netipx.IPSetBuilder
build.AddPrefix(prefix)
// This is suboptimal and quite expensive, but if we only add the prefix, we will miss all the relevant IPv6
// addresses for the hosts that belong to tailscale. This doesnt really affect stuff like subnet routers.
2023-09-24 07:42:05 -04:00
for _, node := range nodes {
for _, ip := range node.IPs() {
// log.Trace().
2023-09-24 07:42:05 -04:00
// Msgf("checking if node ip (%s) is part of prefix (%s): %v, is single ip prefix (%v), addr: %s", ip.String(), prefix.String(), prefix.Contains(ip), prefix.IsSingleIP(), prefix.Addr().String())
if prefix.Contains(ip) {
node.AppendToIPSet(&build)
}
}
}
return build.IPSet()
}
func expandAutoGroup(alias string) (*netipx.IPSet, error) {
switch {
case strings.HasPrefix(alias, "autogroup:internet"):
return theInternet(), nil
default:
return nil, fmt.Errorf("unknown autogroup %q", alias)
}
}
func isWildcard(str string) bool {
return str == "*"
}
func isGroup(str string) bool {
return strings.HasPrefix(str, "group:")
}
func isTag(str string) bool {
return strings.HasPrefix(str, "tag:")
}
func isAutoGroup(str string) bool {
return strings.HasPrefix(str, "autogroup:")
}
2023-09-24 07:42:05 -04:00
// TagsOfNode will return the tags of the current node.
// Invalid tags are tags added by a user on a node, and that user doesn't have authority to add this tag.
// Valid tags are tags added by a user that is allowed in the ACL policy to add this tag.
2023-09-24 07:42:05 -04:00
func (pol *ACLPolicy) TagsOfNode(
node *types.Node,
) ([]string, []string) {
var validTags []string
var invalidTags []string
// TODO(kradalby): Why is this sometimes nil? coming from tailNode?
if node == nil {
return validTags, invalidTags
}
validTagMap := make(map[string]bool)
invalidTagMap := make(map[string]bool)
if node.Hostinfo != nil {
for _, tag := range node.Hostinfo.RequestTags {
owners, err := expandOwnersFromTag(pol, tag)
if errors.Is(err, ErrInvalidTag) {
invalidTagMap[tag] = true
continue
}
var found bool
for _, owner := range owners {
Redo OIDC configuration (#2020) expand user, add claims to user This commit expands the user table with additional fields that can be retrieved from OIDC providers (and other places) and uses this data in various tailscale response objects if it is available. This is the beginning of implementing https://docs.google.com/document/d/1X85PMxIaVWDF6T_UPji3OeeUqVBcGj_uHRM5CI-AwlY/edit trying to make OIDC more coherant and maintainable in addition to giving the user a better experience and integration with a provider. remove usernames in magic dns, normalisation of emails this commit removes the option to have usernames as part of MagicDNS domains and headscale will now align with Tailscale, where there is a root domain, and the machine name. In addition, the various normalisation functions for dns names has been made lighter not caring about username and special character that wont occur. Email are no longer normalised as part of the policy processing. untagle oidc and regcache, use typed cache This commits stops reusing the registration cache for oidc purposes and switches the cache to be types and not use any allowing the removal of a bunch of casting. try to make reauth/register branches clearer in oidc Currently there was a function that did a bunch of stuff, finding the machine key, trying to find the node, reauthing the node, returning some status, and it was called validate which was very confusing. This commit tries to split this into what to do if the node exists, if it needs to register etc. Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-10-02 08:50:17 -04:00
if node.User.Username() == owner {
found = true
}
}
if found {
validTagMap[tag] = true
} else {
invalidTagMap[tag] = true
}
}
for tag := range invalidTagMap {
invalidTags = append(invalidTags, tag)
}
for tag := range validTagMap {
validTags = append(validTags, tag)
}
}
return validTags, invalidTags
}
// filterNodesByUser returns a list of nodes that match the given userToken from a
// policy.
// Matching nodes are determined by first matching the user token to a user by checking:
// - If it is an ID that mactches the user database ID
// - It is the Provider Identifier from OIDC
// - It matches the username or email of a user
//
// If the token matches more than one user, zero nodes will returned.
func filterNodesByUser(nodes types.Nodes, users []types.User, userToken string) types.Nodes {
var out types.Nodes
var potentialUsers []types.User
for _, user := range users {
if user.ProviderIdentifier.Valid && user.ProviderIdentifier.String == userToken {
// If a user is matching with a known unique field,
// disgard all other users and only keep the current
// user.
potentialUsers = []types.User{user}
break
}
if user.Email == userToken {
potentialUsers = append(potentialUsers, user)
}
if user.Name == userToken {
potentialUsers = append(potentialUsers, user)
}
}
if len(potentialUsers) != 1 {
return nil
}
user := potentialUsers[0]
2023-09-24 07:42:05 -04:00
for _, node := range nodes {
if node.User.ID == user.ID {
2023-09-24 07:42:05 -04:00
out = append(out, node)
}
}
return out
}
2023-09-24 07:42:05 -04:00
// FilterNodesByACL returns the list of peers authorized to be accessed from a given node.
func FilterNodesByACL(
node *types.Node,
nodes types.Nodes,
filter []tailcfg.FilterRule,
2023-09-24 07:42:05 -04:00
) types.Nodes {
var result types.Nodes
2023-09-24 07:42:05 -04:00
for index, peer := range nodes {
if peer.ID == node.ID {
continue
}
2023-09-24 07:42:05 -04:00
if node.CanAccess(filter, nodes[index]) || peer.CanAccess(filter, node) {
result = append(result, peer)
}
}
return result
}