mirror of
https://github.com/jsiebens/ionscale.git
synced 2026-03-31 15:07:49 +01:00
feat: acl grants
This commit is contained in:
+24
-225
@@ -8,6 +8,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/schema"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -35,6 +36,7 @@ type ACLPolicy struct {
|
||||
AutoApprovers *AutoApprovers `json:"autoApprovers,omitempty"`
|
||||
SSHRules []SSHRule `json:"ssh,omitempty"`
|
||||
NodeAttrs []NodeAttr `json:"nodeAttrs,omitempty"`
|
||||
Grants []Grant `json:"grants,omitempty"`
|
||||
}
|
||||
|
||||
type ACL struct {
|
||||
@@ -57,6 +59,13 @@ type NodeAttr struct {
|
||||
Attr []string `json:"attr"`
|
||||
}
|
||||
|
||||
type Grant struct {
|
||||
Src []string `json:"src"`
|
||||
Dst []string `json:"dst"`
|
||||
IP []tailcfg.ProtoPortRange `json:"ip"`
|
||||
App tailcfg.PeerCapMap `json:"app"`
|
||||
}
|
||||
|
||||
func DefaultACLPolicy() ACLPolicy {
|
||||
return ACLPolicy{
|
||||
ACLs: []ACL{
|
||||
@@ -109,7 +118,7 @@ func (a ACLPolicy) FindAutoApprovedIPs(routableIPs []netip.Prefix, tags []string
|
||||
return false
|
||||
}
|
||||
|
||||
autoApprovedIPs := []netip.Prefix{}
|
||||
var autoApprovedIPs []netip.Prefix
|
||||
for route, autoApprovers := range a.AutoApprovers.Routes {
|
||||
candidate, err := netip.ParsePrefix(route)
|
||||
if err != nil {
|
||||
@@ -121,7 +130,7 @@ func (a ACLPolicy) FindAutoApprovedIPs(routableIPs []netip.Prefix, tags []string
|
||||
}
|
||||
}
|
||||
|
||||
result := []netip.Prefix{}
|
||||
var result []netip.Prefix
|
||||
for _, c := range routableIPs {
|
||||
if c.Bits() == 0 && matches(a.AutoApprovers.ExitNode) {
|
||||
result = append(result, c)
|
||||
@@ -134,15 +143,6 @@ func (a ACLPolicy) FindAutoApprovedIPs(routableIPs []netip.Prefix, tags []string
|
||||
return result
|
||||
}
|
||||
|
||||
func (a ACLPolicy) IsTagOwner(tags []string, p *User) bool {
|
||||
for _, t := range tags {
|
||||
if a.isTagOwner(t, p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a ACLPolicy) CheckTagOwners(tags []string, p *User) error {
|
||||
var result *multierror.Error
|
||||
for _, t := range tags {
|
||||
@@ -158,53 +158,18 @@ func (a ACLPolicy) isTagOwner(tag string, p *User) bool {
|
||||
return true
|
||||
}
|
||||
if tagOwners, ok := a.TagOwners[tag]; ok {
|
||||
return a.validateTagOwners(tagOwners, p)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a ACLPolicy) validateTagOwners(tagOwners []string, p *User) bool {
|
||||
for _, alias := range tagOwners {
|
||||
if strings.HasPrefix(alias, "group:") {
|
||||
if group, ok := a.Groups[alias]; ok {
|
||||
for _, groupMember := range group {
|
||||
if groupMember == p.Name {
|
||||
return true
|
||||
}
|
||||
for _, alias := range tagOwners {
|
||||
if strings.HasPrefix(alias, "group:") {
|
||||
if group, ok := a.Groups[alias]; ok {
|
||||
return slices.Contains(group, p.Name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if alias == p.Name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a ACLPolicy) IsValidPeer(src *Machine, dest *Machine) bool {
|
||||
if !src.HasTags() && !dest.HasTags() && dest.HasUser(src.User.Name) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, acl := range a.ACLs {
|
||||
selfDestPorts, allDestPorts := a.expandMachineToDstPorts(dest, acl.Dst)
|
||||
if len(selfDestPorts) != 0 {
|
||||
for _, alias := range acl.Src {
|
||||
if len(a.expandMachineAlias(src, alias, true, &dest.User)) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(allDestPorts) != 0 {
|
||||
for _, alias := range acl.Src {
|
||||
if len(a.expandMachineAlias(src, alias, true, nil)) != 0 {
|
||||
} else {
|
||||
if alias == p.Name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -252,182 +217,12 @@ func (a ACLPolicy) NodeCapabilities(m *Machine) []tailcfg.NodeCapability {
|
||||
return caps
|
||||
}
|
||||
|
||||
func (a ACLPolicy) BuildFilterRules(srcs []Machine, dst *Machine) []tailcfg.FilterRule {
|
||||
var rules = make([]tailcfg.FilterRule, 0)
|
||||
|
||||
appendRules := func(rules []tailcfg.FilterRule, proto string, src []string, destPorts []tailcfg.NetPortRange, u *User) []tailcfg.FilterRule {
|
||||
var allSrcIPsSet = &StringSet{}
|
||||
for _, alias := range src {
|
||||
for _, src := range srcs {
|
||||
srcIPs := a.expandMachineAlias(&src, alias, true, u)
|
||||
allSrcIPsSet.Add(srcIPs...)
|
||||
}
|
||||
}
|
||||
|
||||
allSrcIPs := allSrcIPsSet.Items()
|
||||
|
||||
if len(allSrcIPs) == 0 {
|
||||
return rules
|
||||
}
|
||||
|
||||
return append(rules, tailcfg.FilterRule{
|
||||
SrcIPs: allSrcIPs,
|
||||
DstPorts: destPorts,
|
||||
IPProto: parseProtocol(proto),
|
||||
})
|
||||
}
|
||||
|
||||
for _, acl := range a.ACLs {
|
||||
selfDestPorts, allDestPorts := a.expandMachineToDstPorts(dst, acl.Dst)
|
||||
if len(selfDestPorts) != 0 {
|
||||
rules = appendRules(rules, acl.Proto, acl.Src, selfDestPorts, &dst.User)
|
||||
}
|
||||
if len(allDestPorts) != 0 {
|
||||
rules = appendRules(rules, acl.Proto, acl.Src, allDestPorts, nil)
|
||||
}
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
func (a ACLPolicy) expandMachineToDstPorts(m *Machine, ports []string) ([]tailcfg.NetPortRange, []tailcfg.NetPortRange) {
|
||||
selfDestRanges := []tailcfg.NetPortRange{}
|
||||
otherDestRanges := []tailcfg.NetPortRange{}
|
||||
for _, d := range ports {
|
||||
self, ranges := a.expandMachineDestToNetPortRanges(m, d)
|
||||
if self {
|
||||
selfDestRanges = append(selfDestRanges, ranges...)
|
||||
} else {
|
||||
otherDestRanges = append(otherDestRanges, ranges...)
|
||||
}
|
||||
}
|
||||
return selfDestRanges, otherDestRanges
|
||||
}
|
||||
|
||||
func (a ACLPolicy) expandMachineDestToNetPortRanges(m *Machine, dest string) (bool, []tailcfg.NetPortRange) {
|
||||
lastInd := strings.LastIndex(dest, ":")
|
||||
if lastInd == -1 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
alias := dest[:lastInd]
|
||||
portRange := dest[lastInd+1:]
|
||||
|
||||
ports, err := a.expandValuePortToPortRange(portRange)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
ips := a.expandMachineAlias(m, alias, false, nil)
|
||||
if len(ips) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var netPortRanges []tailcfg.NetPortRange
|
||||
for _, d := range ips {
|
||||
for _, p := range ports {
|
||||
pr := tailcfg.NetPortRange{
|
||||
IP: d,
|
||||
Ports: p,
|
||||
}
|
||||
netPortRanges = append(netPortRanges, pr)
|
||||
}
|
||||
}
|
||||
|
||||
return alias == AutoGroupSelf, netPortRanges
|
||||
}
|
||||
|
||||
func (a ACLPolicy) expandMachineAlias(m *Machine, alias string, src bool, u *User) []string {
|
||||
if u != nil && m.HasTags() {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
if u != nil && !m.HasUser(u.Name) {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
if alias == "*" && u != nil {
|
||||
return m.IPs()
|
||||
}
|
||||
|
||||
if alias == "*" {
|
||||
return []string{"*"}
|
||||
}
|
||||
|
||||
if alias == AutoGroupMember || alias == AutoGroupMembers || alias == AutoGroupSelf {
|
||||
if !m.HasTags() {
|
||||
return m.IPs()
|
||||
} else {
|
||||
return []string{}
|
||||
}
|
||||
}
|
||||
|
||||
if alias == AutoGroupTagged {
|
||||
if m.HasTags() {
|
||||
return m.IPs()
|
||||
} else {
|
||||
return []string{}
|
||||
}
|
||||
}
|
||||
|
||||
if alias == AutoGroupInternet && m.IsExitNode() {
|
||||
return autogroupInternetRanges()
|
||||
}
|
||||
|
||||
if strings.Contains(alias, "@") && !m.HasTags() && m.HasUser(alias) {
|
||||
return m.IPs()
|
||||
}
|
||||
|
||||
if strings.HasPrefix(alias, "group:") && !m.HasTags() {
|
||||
users, ok := a.Groups[alias]
|
||||
|
||||
if !ok {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
if m.HasUser(u) {
|
||||
return m.IPs()
|
||||
}
|
||||
}
|
||||
|
||||
return []string{}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(alias, "tag:") && m.HasTag(alias) {
|
||||
return m.IPs()
|
||||
}
|
||||
|
||||
if h, ok := a.Hosts[alias]; ok {
|
||||
alias = h
|
||||
}
|
||||
|
||||
if src {
|
||||
ip, err := netip.ParseAddr(alias)
|
||||
if err == nil && m.HasIP(ip) {
|
||||
return []string{ip.String()}
|
||||
}
|
||||
} else {
|
||||
ip, err := netip.ParseAddr(alias)
|
||||
if err == nil && m.IsAllowedIP(ip) {
|
||||
return []string{ip.String()}
|
||||
}
|
||||
|
||||
prefix, err := netip.ParsePrefix(alias)
|
||||
if err == nil && m.IsAllowedIPPrefix(prefix) {
|
||||
return []string{prefix.String()}
|
||||
}
|
||||
}
|
||||
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (a ACLPolicy) expandValuePortToPortRange(s string) ([]tailcfg.PortRange, error) {
|
||||
func (a ACLPolicy) parsePortRanges(s string) ([]tailcfg.PortRange, error) {
|
||||
if s == "*" {
|
||||
return []tailcfg.PortRange{{First: 0, Last: 65535}}, nil
|
||||
return []tailcfg.PortRange{tailcfg.PortRangeAny}, nil
|
||||
}
|
||||
|
||||
ports := []tailcfg.PortRange{}
|
||||
var ports []tailcfg.PortRange
|
||||
for _, p := range strings.Split(s, ",") {
|
||||
rang := strings.Split(p, "-")
|
||||
if len(rang) == 1 {
|
||||
@@ -582,6 +377,10 @@ func (s *StringSet) Items() []string {
|
||||
return items
|
||||
}
|
||||
|
||||
func (s *StringSet) Empty() bool {
|
||||
return len(s.items) == 0
|
||||
}
|
||||
|
||||
func autogroupInternetRanges() []string {
|
||||
return []string{
|
||||
"0.0.0.0/5",
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"strings"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
func (a ACLPolicy) IsValidPeer(src *Machine, dest *Machine) bool {
|
||||
if !src.HasTags() && !dest.HasTags() && dest.HasUser(src.User.Name) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, acl := range a.ACLs {
|
||||
selfDestPorts, allDestPorts := a.translateDestinationAliasesToMachineNetPortRanges(acl.Dst, dest)
|
||||
if len(selfDestPorts) != 0 {
|
||||
for _, alias := range acl.Src {
|
||||
if len(a.translateSourceAliasToMachineIPs(alias, src, &dest.User)) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(allDestPorts) != 0 {
|
||||
for _, alias := range acl.Src {
|
||||
if len(a.translateSourceAliasToMachineIPs(alias, src, nil)) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, grant := range a.Grants {
|
||||
selfIps, otherIps := a.translateDestinationAliasesToMachineIPs(grant.Dst, dest)
|
||||
if len(selfIps) != 0 {
|
||||
for _, alias := range grant.Src {
|
||||
if len(a.translateSourceAliasToMachineIPs(alias, src, &dest.User)) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(otherIps) != 0 {
|
||||
for _, alias := range grant.Src {
|
||||
if len(a.translateSourceAliasToMachineIPs(alias, src, nil)) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (a ACLPolicy) BuildFilterRules(peers []Machine, dst *Machine) []tailcfg.FilterRule {
|
||||
var rules = make([]tailcfg.FilterRule, 0)
|
||||
|
||||
matchSourceAndAppendRule := func(rules []tailcfg.FilterRule, aliases []string, preparedRules []tailcfg.FilterRule, u *User) []tailcfg.FilterRule {
|
||||
if len(preparedRules) == 0 {
|
||||
return rules
|
||||
}
|
||||
|
||||
var allSrcIPsSet = &StringSet{}
|
||||
for _, alias := range aliases {
|
||||
for _, peer := range peers {
|
||||
allSrcIPsSet.Add(a.translateSourceAliasToMachineIPs(alias, &peer, u)...)
|
||||
}
|
||||
}
|
||||
|
||||
if allSrcIPsSet.Empty() {
|
||||
return rules
|
||||
}
|
||||
|
||||
allSrcIPs := allSrcIPsSet.Items()
|
||||
|
||||
if len(allSrcIPs) == 0 {
|
||||
return rules
|
||||
}
|
||||
|
||||
for _, pr := range preparedRules {
|
||||
rules = append(rules, tailcfg.FilterRule{
|
||||
SrcIPs: allSrcIPs,
|
||||
DstPorts: pr.DstPorts,
|
||||
IPProto: pr.IPProto,
|
||||
CapGrant: pr.CapGrant,
|
||||
})
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
for _, acl := range a.ACLs {
|
||||
self, other := a.prepareFilterRulesFromACL(dst, acl)
|
||||
rules = matchSourceAndAppendRule(rules, acl.Src, self, &dst.User)
|
||||
rules = matchSourceAndAppendRule(rules, acl.Src, other, nil)
|
||||
}
|
||||
|
||||
for _, acl := range a.Grants {
|
||||
self, other := a.prepareFilterRulesFromGrant(dst, acl)
|
||||
rules = matchSourceAndAppendRule(rules, acl.Src, self, &dst.User)
|
||||
rules = matchSourceAndAppendRule(rules, acl.Src, other, nil)
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
func (a ACLPolicy) prepareFilterRulesFromACL(candidate *Machine, acl ACL) ([]tailcfg.FilterRule, []tailcfg.FilterRule) {
|
||||
proto := parseProtocol(acl.Proto)
|
||||
|
||||
selfDstPorts, otherDstPorts := a.translateDestinationAliasesToMachineNetPortRanges(acl.Dst, candidate)
|
||||
|
||||
var selfFilterRules []tailcfg.FilterRule
|
||||
var otherFilterRules []tailcfg.FilterRule
|
||||
|
||||
if len(selfDstPorts) != 0 {
|
||||
selfFilterRules = append(selfFilterRules, tailcfg.FilterRule{IPProto: proto, DstPorts: selfDstPorts})
|
||||
}
|
||||
|
||||
if len(otherDstPorts) != 0 {
|
||||
otherFilterRules = append(otherFilterRules, tailcfg.FilterRule{IPProto: proto, DstPorts: otherDstPorts})
|
||||
}
|
||||
|
||||
return selfFilterRules, otherFilterRules
|
||||
}
|
||||
|
||||
func (a ACLPolicy) prepareFilterRulesFromGrant(candidate *Machine, grant Grant) ([]tailcfg.FilterRule, []tailcfg.FilterRule) {
|
||||
selfIPs, otherIPs := a.translateDestinationAliasesToMachineIPs(grant.Dst, candidate)
|
||||
|
||||
var selfFilterRules []tailcfg.FilterRule
|
||||
var otherFilterRules []tailcfg.FilterRule
|
||||
|
||||
for _, ip := range grant.IP {
|
||||
if len(selfIPs) != 0 {
|
||||
ranges := make([]tailcfg.NetPortRange, len(selfIPs))
|
||||
for i, s := range selfIPs {
|
||||
ranges[i] = tailcfg.NetPortRange{IP: s, Ports: ip.Ports}
|
||||
}
|
||||
|
||||
rule := tailcfg.FilterRule{DstPorts: ranges}
|
||||
if ip.Proto != 0 {
|
||||
rule.IPProto = []int{ip.Proto}
|
||||
}
|
||||
|
||||
selfFilterRules = append(selfFilterRules, rule)
|
||||
}
|
||||
|
||||
if len(otherIPs) != 0 {
|
||||
ranges := make([]tailcfg.NetPortRange, len(otherIPs))
|
||||
for i, s := range otherIPs {
|
||||
ranges[i] = tailcfg.NetPortRange{IP: s, Ports: ip.Ports}
|
||||
}
|
||||
|
||||
rule := tailcfg.FilterRule{DstPorts: ranges}
|
||||
if ip.Proto != 0 {
|
||||
rule.IPProto = []int{ip.Proto}
|
||||
}
|
||||
|
||||
otherFilterRules = append(otherFilterRules, rule)
|
||||
}
|
||||
}
|
||||
|
||||
if len(grant.App) != 0 {
|
||||
selfPrefixes, otherPrefixes := appGrantDstIpsToPrefixes(candidate, selfIPs, otherIPs)
|
||||
if len(selfPrefixes) != 0 {
|
||||
rule := tailcfg.FilterRule{CapGrant: []tailcfg.CapGrant{{Dsts: selfPrefixes, CapMap: grant.App}}}
|
||||
selfFilterRules = append(selfFilterRules, rule)
|
||||
}
|
||||
|
||||
if len(otherPrefixes) != 0 {
|
||||
rule := tailcfg.FilterRule{CapGrant: []tailcfg.CapGrant{{Dsts: otherPrefixes, CapMap: grant.App}}}
|
||||
otherFilterRules = append(otherFilterRules, rule)
|
||||
}
|
||||
}
|
||||
|
||||
return selfFilterRules, otherFilterRules
|
||||
}
|
||||
|
||||
func appGrantDstIpsToPrefixes(m *Machine, self []string, other []string) ([]netip.Prefix, []netip.Prefix) {
|
||||
translate := func(ips []string) []netip.Prefix {
|
||||
var prefixes []netip.Prefix
|
||||
for _, ip := range ips {
|
||||
if ip == "*" {
|
||||
prefixes = append(prefixes, netip.PrefixFrom(*m.IPv4.Addr, 32))
|
||||
prefixes = append(prefixes, netip.PrefixFrom(*m.IPv6.Addr, 128))
|
||||
} else {
|
||||
addr, err := netip.ParseAddr(ip)
|
||||
if err == nil && m.HasIP(addr) {
|
||||
if addr.Is4() {
|
||||
prefixes = append(prefixes, netip.PrefixFrom(addr, 32))
|
||||
} else {
|
||||
prefixes = append(prefixes, netip.PrefixFrom(addr, 128))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return prefixes
|
||||
}
|
||||
|
||||
return translate(self), translate(other)
|
||||
}
|
||||
|
||||
func (a ACLPolicy) translateDestinationAliasesToMachineIPs(aliases []string, m *Machine) ([]string, []string) {
|
||||
var self = &StringSet{}
|
||||
var other = &StringSet{}
|
||||
for _, alias := range aliases {
|
||||
ips := a.translateDestinationAliasToMachineIPs(alias, m)
|
||||
if alias == AutoGroupSelf {
|
||||
self.Add(ips...)
|
||||
} else {
|
||||
other.Add(ips...)
|
||||
}
|
||||
}
|
||||
return self.Items(), other.Items()
|
||||
}
|
||||
|
||||
func (a ACLPolicy) translateDestinationAliasesToMachineNetPortRanges(aliases []string, m *Machine) ([]tailcfg.NetPortRange, []tailcfg.NetPortRange) {
|
||||
var self []tailcfg.NetPortRange
|
||||
var other []tailcfg.NetPortRange
|
||||
for _, alias := range aliases {
|
||||
ranges := a.translationDestinationAliasToMachineNetPortRanges(alias, m)
|
||||
if strings.HasPrefix(alias, AutoGroupSelf) {
|
||||
self = append(self, ranges...)
|
||||
} else {
|
||||
other = append(other, ranges...)
|
||||
}
|
||||
}
|
||||
return self, other
|
||||
}
|
||||
|
||||
func (a ACLPolicy) translationDestinationAliasToMachineNetPortRanges(alias string, m *Machine) []tailcfg.NetPortRange {
|
||||
lastInd := strings.LastIndex(alias, ":")
|
||||
if lastInd == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ports := alias[lastInd+1:]
|
||||
alias = alias[:lastInd]
|
||||
|
||||
portRanges, err := a.parsePortRanges(ports)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ips := a.translateDestinationAliasToMachineIPs(alias, m)
|
||||
if len(ips) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var netPortRanges []tailcfg.NetPortRange
|
||||
for _, d := range ips {
|
||||
for _, p := range portRanges {
|
||||
pr := tailcfg.NetPortRange{
|
||||
IP: d,
|
||||
Ports: p,
|
||||
}
|
||||
netPortRanges = append(netPortRanges, pr)
|
||||
}
|
||||
}
|
||||
|
||||
return netPortRanges
|
||||
}
|
||||
|
||||
func (a ACLPolicy) translateDestinationAliasToMachineIPs(alias string, m *Machine) []string {
|
||||
f := func(alias string, m *Machine) []string {
|
||||
ip, err := netip.ParseAddr(alias)
|
||||
if err == nil && m.IsAllowedIP(ip) {
|
||||
return []string{ip.String()}
|
||||
}
|
||||
|
||||
prefix, err := netip.ParsePrefix(alias)
|
||||
if err == nil && m.IsAllowedIPPrefix(prefix) {
|
||||
return []string{prefix.String()}
|
||||
}
|
||||
|
||||
return make([]string, 0)
|
||||
}
|
||||
|
||||
return a.translateAliasToMachineIPs(alias, m, nil, f)
|
||||
}
|
||||
|
||||
func (a ACLPolicy) translateSourceAliasToMachineIPs(alias string, m *Machine, u *User) []string {
|
||||
f := func(alias string, m *Machine) []string {
|
||||
ip, err := netip.ParseAddr(alias)
|
||||
if err == nil && m.HasIP(ip) {
|
||||
return []string{ip.String()}
|
||||
}
|
||||
|
||||
return make([]string, 0)
|
||||
}
|
||||
|
||||
return a.translateAliasToMachineIPs(alias, m, u, f)
|
||||
}
|
||||
|
||||
func (a ACLPolicy) translateAliasToMachineIPs(alias string, m *Machine, u *User, f func(string, *Machine) []string) []string {
|
||||
if u != nil && m.HasTags() {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
if u != nil && !m.HasUser(u.Name) {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
if alias == "*" && u != nil {
|
||||
return m.IPs()
|
||||
}
|
||||
|
||||
if alias == "*" {
|
||||
return []string{"*"}
|
||||
}
|
||||
|
||||
if alias == AutoGroupMember || alias == AutoGroupMembers || alias == AutoGroupSelf {
|
||||
if !m.HasTags() {
|
||||
return m.IPs()
|
||||
} else {
|
||||
return []string{}
|
||||
}
|
||||
}
|
||||
|
||||
if alias == AutoGroupTagged {
|
||||
if m.HasTags() {
|
||||
return m.IPs()
|
||||
} else {
|
||||
return []string{}
|
||||
}
|
||||
}
|
||||
|
||||
if alias == AutoGroupInternet && m.IsExitNode() {
|
||||
return autogroupInternetRanges()
|
||||
}
|
||||
|
||||
if strings.Contains(alias, "@") && !m.HasTags() && m.HasUser(alias) {
|
||||
return m.IPs()
|
||||
}
|
||||
|
||||
if strings.HasPrefix(alias, "group:") && !m.HasTags() && a.isGroupMember(alias, m) {
|
||||
return m.IPs()
|
||||
}
|
||||
|
||||
if strings.HasPrefix(alias, "tag:") && m.HasTag(alias) {
|
||||
return m.IPs()
|
||||
}
|
||||
|
||||
if h, ok := a.Hosts[alias]; ok {
|
||||
alias = h
|
||||
}
|
||||
|
||||
return f(alias, m)
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/jsiebens/ionscale/internal/addr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"net/netip"
|
||||
"sort"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -841,7 +843,7 @@ func TestACLPolicy_FindAutoApprovedIPs(t *testing.T) {
|
||||
name: "no match",
|
||||
userName: "nick@example.com",
|
||||
routableIPs: []netip.Prefix{route1, route2, route3},
|
||||
expected: []netip.Prefix{},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "exit",
|
||||
@@ -853,7 +855,7 @@ func TestACLPolicy_FindAutoApprovedIPs(t *testing.T) {
|
||||
name: "exit no match",
|
||||
userName: "john@example.com",
|
||||
routableIPs: []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0")},
|
||||
expected: []netip.Prefix{},
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -900,3 +902,87 @@ func TestACLPolicy_BuildFilterRulesWithAdvertisedRoutes(t *testing.T) {
|
||||
|
||||
assert.Equal(t, expectedRules, actualRules)
|
||||
}
|
||||
|
||||
func TestACLPolicy_BuildFilterRulesWildcardGrants(t *testing.T) {
|
||||
ranges, err := tailcfg.ParseProtoPortRanges([]string{"*"})
|
||||
require.NoError(t, err)
|
||||
|
||||
p1 := createMachine("john@example.com")
|
||||
p2 := createMachine("jane@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
Grants: []Grant{
|
||||
{
|
||||
Src: []string{"*"},
|
||||
Dst: []string{"*"},
|
||||
IP: ranges,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
dst := createMachine("john@example.com")
|
||||
|
||||
actualRules := policy.BuildFilterRules([]Machine{*p1, *p2}, dst)
|
||||
expectedRules := []tailcfg.FilterRule{
|
||||
{
|
||||
SrcIPs: []string{"*"},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{
|
||||
IP: "*",
|
||||
Ports: tailcfg.PortRange{
|
||||
First: 0,
|
||||
Last: 65535,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, expectedRules, actualRules)
|
||||
}
|
||||
|
||||
func TestACLPolicy_BuildFilterRulesWithAppGrants(t *testing.T) {
|
||||
p1 := createMachine("john@example.com")
|
||||
p2 := createMachine("jane@example.com")
|
||||
|
||||
dst := createMachine("john@example.com")
|
||||
|
||||
mycap := map[string]interface{}{
|
||||
"channel": "alpha",
|
||||
"ids": []string{"1", "2", "3"},
|
||||
}
|
||||
|
||||
marshal, _ := json.Marshal(mycap)
|
||||
|
||||
policy := ACLPolicy{
|
||||
Grants: []Grant{
|
||||
{
|
||||
Src: []string{"*"},
|
||||
Dst: []string{"*"},
|
||||
App: map[tailcfg.PeerCapability][]tailcfg.RawMessage{
|
||||
tailcfg.PeerCapability("localtest.me/cap/test"): {tailcfg.RawMessage(marshal)},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actualRules := policy.BuildFilterRules([]Machine{*p1, *p2}, dst)
|
||||
expectedRules := []tailcfg.FilterRule{
|
||||
{
|
||||
SrcIPs: []string{"*"},
|
||||
CapGrant: []tailcfg.CapGrant{
|
||||
{
|
||||
Dsts: []netip.Prefix{
|
||||
netip.PrefixFrom(*dst.IPv4.Addr, 32),
|
||||
netip.PrefixFrom(*dst.IPv6.Addr, 128),
|
||||
},
|
||||
CapMap: map[tailcfg.PeerCapability][]tailcfg.RawMessage{
|
||||
tailcfg.PeerCapability("localtest.me/cap/test"): {tailcfg.RawMessage(marshal)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, expectedRules, actualRules)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user