mirror of
https://github.com/jsiebens/ionscale.git
synced 2026-03-31 15:07:49 +01:00
feat: add support for node attributes
This commit is contained in:
@@ -34,6 +34,7 @@ type ACLPolicy struct {
|
||||
TagOwners map[string][]string `json:"tagowners,omitempty"`
|
||||
AutoApprovers *AutoApprovers `json:"autoApprovers,omitempty"`
|
||||
SSHRules []SSHRule `json:"ssh,omitempty"`
|
||||
NodeAttrs []NodeAttr `json:"nodeAttrs,omitempty"`
|
||||
}
|
||||
|
||||
type ACL struct {
|
||||
@@ -50,6 +51,11 @@ type SSHRule struct {
|
||||
CheckPeriod string `json:"checkPeriod,omitempty"`
|
||||
}
|
||||
|
||||
type NodeAttr struct {
|
||||
Target []string `json:"target"`
|
||||
Attr []string `json:"attr"`
|
||||
}
|
||||
|
||||
func DefaultACLPolicy() ACLPolicy {
|
||||
return ACLPolicy{
|
||||
ACLs: []ACL{
|
||||
@@ -201,6 +207,50 @@ func (a ACLPolicy) IsValidPeer(src *Machine, dest *Machine) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (a ACLPolicy) NodeCapabilities(m *Machine) []tailcfg.NodeCapability {
|
||||
var result = &StringSet{}
|
||||
|
||||
matches := func(targets []string) bool {
|
||||
for _, alias := range targets {
|
||||
if alias == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.Contains(alias, "@") && !m.HasTags() && m.HasUser(alias) {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(alias, "tag:") && m.HasTag(alias) {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(alias, "group:") && !m.HasTags() {
|
||||
for _, u := range a.Groups[alias] {
|
||||
if m.HasUser(u) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
for _, nodeAddr := range a.NodeAttrs {
|
||||
if matches(nodeAddr.Target) {
|
||||
result.Add(nodeAddr.Attr...)
|
||||
}
|
||||
}
|
||||
|
||||
items := result.Items()
|
||||
caps := make([]tailcfg.NodeCapability, len(items))
|
||||
for i, c := range items {
|
||||
caps[i] = tailcfg.NodeCapability(c)
|
||||
}
|
||||
|
||||
return caps
|
||||
}
|
||||
|
||||
func (a ACLPolicy) BuildFilterRules(srcs []Machine, dst *Machine) []tailcfg.FilterRule {
|
||||
var rules []tailcfg.FilterRule
|
||||
|
||||
|
||||
@@ -9,6 +9,101 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestACLPolicy_NodeAttributesWithWildcards(t *testing.T) {
|
||||
p1 := createMachine("john@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
NodeAttrs: []NodeAttr{
|
||||
{
|
||||
Target: []string{"*"},
|
||||
Attr: []string{
|
||||
"attr1",
|
||||
"attr2",
|
||||
},
|
||||
},
|
||||
{
|
||||
Target: []string{"*"},
|
||||
Attr: []string{
|
||||
"attr3",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actualAttrs := policy.NodeCapabilities(p1)
|
||||
expectedAttrs := []tailcfg.NodeCapability{
|
||||
tailcfg.NodeCapability("attr1"),
|
||||
tailcfg.NodeCapability("attr2"),
|
||||
tailcfg.NodeCapability("attr3"),
|
||||
}
|
||||
|
||||
assert.Equal(t, expectedAttrs, actualAttrs)
|
||||
}
|
||||
|
||||
func TestACLPolicy_NodeAttributesWithUserAndGroups(t *testing.T) {
|
||||
p1 := createMachine("john@example.com")
|
||||
|
||||
policy := ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:admins": []string{"john@example.com"},
|
||||
},
|
||||
NodeAttrs: []NodeAttr{
|
||||
{
|
||||
Target: []string{"john@example.com"},
|
||||
Attr: []string{
|
||||
"attr1",
|
||||
"attr2",
|
||||
},
|
||||
},
|
||||
{
|
||||
Target: []string{"jane@example.com", "group:analytics", "group:admins"},
|
||||
Attr: []string{
|
||||
"attr3",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actualAttrs := policy.NodeCapabilities(p1)
|
||||
expectedAttrs := []tailcfg.NodeCapability{
|
||||
tailcfg.NodeCapability("attr1"),
|
||||
tailcfg.NodeCapability("attr2"),
|
||||
tailcfg.NodeCapability("attr3"),
|
||||
}
|
||||
|
||||
assert.Equal(t, expectedAttrs, actualAttrs)
|
||||
}
|
||||
|
||||
func TestACLPolicy_NodeAttributesWithUserAndTags(t *testing.T) {
|
||||
p1 := createMachine("john@example.com", "tag:web")
|
||||
|
||||
policy := ACLPolicy{
|
||||
Groups: map[string][]string{
|
||||
"group:admins": []string{"john@example.com"},
|
||||
},
|
||||
NodeAttrs: []NodeAttr{
|
||||
{
|
||||
Target: []string{"john@example.com"},
|
||||
Attr: []string{
|
||||
"attr1",
|
||||
"attr2",
|
||||
},
|
||||
},
|
||||
{
|
||||
Target: []string{"jane@example.com", "tag:web"},
|
||||
Attr: []string{
|
||||
"attr3",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actualAttrs := policy.NodeCapabilities(p1)
|
||||
expectedAttrs := []tailcfg.NodeCapability{tailcfg.NodeCapability("attr3")}
|
||||
|
||||
assert.Equal(t, expectedAttrs, actualAttrs)
|
||||
}
|
||||
|
||||
func TestACLPolicy_BuildFilterRulesWildcards(t *testing.T) {
|
||||
p1 := createMachine("john@example.com")
|
||||
p2 := createMachine("jane@example.com")
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/jsiebens/ionscale/internal/domain"
|
||||
"github.com/jsiebens/ionscale/internal/util"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strconv"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/dnstype"
|
||||
@@ -176,6 +177,12 @@ func ToNode(capVer tailcfg.CapabilityVersion, m *domain.Machine, tailnet *domain
|
||||
if !peer {
|
||||
var capabilities []tailcfg.NodeCapability
|
||||
capMap := make(tailcfg.NodeCapMap)
|
||||
|
||||
for _, c := range tailnet.ACLPolicy.NodeCapabilities(m) {
|
||||
capabilities = append(capabilities, c)
|
||||
capMap[c] = []tailcfg.RawMessage{}
|
||||
}
|
||||
|
||||
if !m.HasTags() && role == domain.UserRoleAdmin {
|
||||
capabilities = append(capabilities, tailcfg.CapabilityAdmin)
|
||||
capMap[tailcfg.CapabilityAdmin] = []tailcfg.RawMessage{}
|
||||
@@ -196,6 +203,12 @@ func ToNode(capVer tailcfg.CapabilityVersion, m *domain.Machine, tailnet *domain
|
||||
capMap[tailcfg.CapabilityHTTPS] = []tailcfg.RawMessage{}
|
||||
}
|
||||
|
||||
// ionscale has no support for Funnel yet, so remove Funnel attribute if set via ACL policy
|
||||
{
|
||||
slices.DeleteFunc(capabilities, func(c tailcfg.NodeCapability) bool { return c == tailcfg.NodeAttrFunnel })
|
||||
delete(capMap, tailcfg.NodeAttrFunnel)
|
||||
}
|
||||
|
||||
if capVer >= 74 {
|
||||
n.CapMap = capMap
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user