mirror of
https://github.com/juanfont/headscale.git
synced 2025-08-05 13:49:57 +02:00
node attributes begin
This commit is contained in:
parent
b81420bef1
commit
cd0c88f332
@ -115,6 +115,7 @@ func generateUserProfiles(
|
|||||||
func generateDNSConfig(
|
func generateDNSConfig(
|
||||||
cfg *types.Config,
|
cfg *types.Config,
|
||||||
node *types.Node,
|
node *types.Node,
|
||||||
|
nodeAttrs []string,
|
||||||
) *tailcfg.DNSConfig {
|
) *tailcfg.DNSConfig {
|
||||||
if cfg.TailcfgDNSConfig == nil {
|
if cfg.TailcfgDNSConfig == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -122,7 +123,7 @@ func generateDNSConfig(
|
|||||||
|
|
||||||
dnsConfig := cfg.TailcfgDNSConfig.Clone()
|
dnsConfig := cfg.TailcfgDNSConfig.Clone()
|
||||||
|
|
||||||
addNextDNSMetadata(dnsConfig.Resolvers, node)
|
addNextDNSMetadata(dnsConfig.Resolvers, node, nodeAttrs)
|
||||||
|
|
||||||
return dnsConfig
|
return dnsConfig
|
||||||
}
|
}
|
||||||
@ -134,9 +135,21 @@ func generateDNSConfig(
|
|||||||
//
|
//
|
||||||
// This will produce a resolver like:
|
// This will produce a resolver like:
|
||||||
// `https://dns.nextdns.io/<nextdns-id>?device_name=node-name&device_model=linux&device_ip=100.64.0.1`
|
// `https://dns.nextdns.io/<nextdns-id>?device_name=node-name&device_model=linux&device_ip=100.64.0.1`
|
||||||
func addNextDNSMetadata(resolvers []*dnstype.Resolver, node *types.Node) {
|
func addNextDNSMetadata(resolvers []*dnstype.Resolver, node *types.Node, nodeAttrs []string) {
|
||||||
for _, resolver := range resolvers {
|
for _, resolver := range resolvers {
|
||||||
if strings.HasPrefix(resolver.Addr, nextDNSDoHPrefix) {
|
if strings.HasPrefix(resolver.Addr, nextDNSDoHPrefix) {
|
||||||
|
|
||||||
|
idx := slices.IndexFunc(nodeAttrs, func(item string) bool { return strings.HasPrefix(item, "nextdns:") && item != "nextdns:no-device-info" })
|
||||||
|
|
||||||
|
if idx != -1 {
|
||||||
|
nextDNSProfile := strings.Split(nodeAttrs[idx], ":")[1]
|
||||||
|
resolver.Addr = fmt.Sprintf("%s/%s", nextDNSDoHPrefix, nextDNSProfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(nodeAttrs, "nextdns:no-device-info") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
attrs := url.Values{
|
attrs := url.Values{
|
||||||
"device_name": []string{node.Hostname},
|
"device_name": []string{node.Hostname},
|
||||||
"device_model": []string{node.Hostinfo.OS},
|
"device_model": []string{node.Hostinfo.OS},
|
||||||
@ -158,7 +171,13 @@ func (m *Mapper) fullMapResponse(
|
|||||||
peers types.Nodes,
|
peers types.Nodes,
|
||||||
capVer tailcfg.CapabilityVersion,
|
capVer tailcfg.CapabilityVersion,
|
||||||
) (*tailcfg.MapResponse, error) {
|
) (*tailcfg.MapResponse, error) {
|
||||||
resp, err := m.baseWithConfigMapResponse(node, capVer)
|
|
||||||
|
nodeAttrs, err := m.polMan.NodeAttributes(node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := m.baseWithConfigMapResponse(node, capVer, nodeAttrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -171,6 +190,7 @@ func (m *Mapper) fullMapResponse(
|
|||||||
capVer,
|
capVer,
|
||||||
peers,
|
peers,
|
||||||
m.cfg,
|
m.cfg,
|
||||||
|
nodeAttrs,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -206,7 +226,13 @@ func (m *Mapper) ReadOnlyMapResponse(
|
|||||||
node *types.Node,
|
node *types.Node,
|
||||||
messages ...string,
|
messages ...string,
|
||||||
) ([]byte, error) {
|
) ([]byte, error) {
|
||||||
resp, err := m.baseWithConfigMapResponse(node, mapRequest.Version)
|
|
||||||
|
nodeAttrs, err := m.polMan.NodeAttributes(node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := m.baseWithConfigMapResponse(node, mapRequest.Version, nodeAttrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -268,6 +294,11 @@ func (m *Mapper) PeerChangedResponse(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nodeAttrs, err := m.polMan.NodeAttributes(node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
err = appendPeerChanges(
|
err = appendPeerChanges(
|
||||||
&resp,
|
&resp,
|
||||||
false, // partial change
|
false, // partial change
|
||||||
@ -276,6 +307,7 @@ func (m *Mapper) PeerChangedResponse(
|
|||||||
mapRequest.Version,
|
mapRequest.Version,
|
||||||
changedNodes,
|
changedNodes,
|
||||||
m.cfg,
|
m.cfg,
|
||||||
|
nodeAttrs,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -300,7 +332,7 @@ func (m *Mapper) PeerChangedResponse(
|
|||||||
|
|
||||||
// Add the node itself, it might have changed, and particularly
|
// Add the node itself, it might have changed, and particularly
|
||||||
// if there are no patches or changes, this is a self update.
|
// if there are no patches or changes, this is a self update.
|
||||||
tailnode, err := tailNode(node, mapRequest.Version, m.polMan, m.cfg)
|
tailnode, err := tailNode(node, mapRequest.Version, m.polMan, m.cfg, nodeAttrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -444,10 +476,11 @@ func (m *Mapper) baseMapResponse() tailcfg.MapResponse {
|
|||||||
func (m *Mapper) baseWithConfigMapResponse(
|
func (m *Mapper) baseWithConfigMapResponse(
|
||||||
node *types.Node,
|
node *types.Node,
|
||||||
capVer tailcfg.CapabilityVersion,
|
capVer tailcfg.CapabilityVersion,
|
||||||
|
nodeAttrs []string,
|
||||||
) (*tailcfg.MapResponse, error) {
|
) (*tailcfg.MapResponse, error) {
|
||||||
resp := m.baseMapResponse()
|
resp := m.baseMapResponse()
|
||||||
|
|
||||||
tailnode, err := tailNode(node, capVer, m.polMan, m.cfg)
|
tailnode, err := tailNode(node, capVer, m.polMan, m.cfg, nodeAttrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -505,6 +538,7 @@ func appendPeerChanges(
|
|||||||
capVer tailcfg.CapabilityVersion,
|
capVer tailcfg.CapabilityVersion,
|
||||||
changed types.Nodes,
|
changed types.Nodes,
|
||||||
cfg *types.Config,
|
cfg *types.Config,
|
||||||
|
attrs []string,
|
||||||
) error {
|
) error {
|
||||||
filter := polMan.Filter()
|
filter := polMan.Filter()
|
||||||
|
|
||||||
@ -521,7 +555,7 @@ func appendPeerChanges(
|
|||||||
|
|
||||||
profiles := generateUserProfiles(node, changed)
|
profiles := generateUserProfiles(node, changed)
|
||||||
|
|
||||||
dnsConfig := generateDNSConfig(cfg, node)
|
dnsConfig := generateDNSConfig(cfg, node, attrs)
|
||||||
|
|
||||||
tailPeers, err := tailNodes(changed, capVer, polMan, cfg)
|
tailPeers, err := tailNodes(changed, capVer, polMan, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -120,6 +120,7 @@ func TestDNSConfigMapResponse(t *testing.T) {
|
|||||||
TailcfgDNSConfig: &dnsConfigOrig,
|
TailcfgDNSConfig: &dnsConfigOrig,
|
||||||
},
|
},
|
||||||
nodeInShared1,
|
nodeInShared1,
|
||||||
|
[]string{},
|
||||||
)
|
)
|
||||||
|
|
||||||
if diff := cmp.Diff(tt.want, got, cmpopts.EquateEmpty()); diff != "" {
|
if diff := cmp.Diff(tt.want, got, cmpopts.EquateEmpty()); diff != "" {
|
||||||
|
@ -20,11 +20,18 @@ func tailNodes(
|
|||||||
tNodes := make([]*tailcfg.Node, len(nodes))
|
tNodes := make([]*tailcfg.Node, len(nodes))
|
||||||
|
|
||||||
for index, node := range nodes {
|
for index, node := range nodes {
|
||||||
|
|
||||||
|
nodeAttrs, err := polMan.NodeAttributes(node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
node, err := tailNode(
|
node, err := tailNode(
|
||||||
node,
|
node,
|
||||||
capVer,
|
capVer,
|
||||||
polMan,
|
polMan,
|
||||||
cfg,
|
cfg,
|
||||||
|
nodeAttrs,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -42,6 +49,7 @@ func tailNode(
|
|||||||
capVer tailcfg.CapabilityVersion,
|
capVer tailcfg.CapabilityVersion,
|
||||||
polMan policy.PolicyManager,
|
polMan policy.PolicyManager,
|
||||||
cfg *types.Config,
|
cfg *types.Config,
|
||||||
|
nodeAttrs []string,
|
||||||
) (*tailcfg.Node, error) {
|
) (*tailcfg.Node, error) {
|
||||||
addrs := node.Prefixes()
|
addrs := node.Prefixes()
|
||||||
|
|
||||||
@ -124,6 +132,10 @@ func tailNode(
|
|||||||
tNode.CapMap[tailcfg.NodeAttrRandomizeClientPort] = []tailcfg.RawMessage{}
|
tNode.CapMap[tailcfg.NodeAttrRandomizeClientPort] = []tailcfg.RawMessage{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, nodeAttr := range nodeAttrs {
|
||||||
|
tNode.CapMap[tailcfg.NodeCapability(nodeAttr)] = []tailcfg.RawMessage{}
|
||||||
|
}
|
||||||
|
|
||||||
if node.IsOnline == nil || !*node.IsOnline {
|
if node.IsOnline == nil || !*node.IsOnline {
|
||||||
// LastSeen is only set when node is
|
// LastSeen is only set when node is
|
||||||
// not connected to the control server.
|
// not connected to the control server.
|
||||||
|
@ -195,6 +195,7 @@ func TestTailNode(t *testing.T) {
|
|||||||
0,
|
0,
|
||||||
polMan,
|
polMan,
|
||||||
cfg,
|
cfg,
|
||||||
|
[]string{},
|
||||||
)
|
)
|
||||||
|
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
@ -248,6 +249,7 @@ func TestNodeExpiry(t *testing.T) {
|
|||||||
0,
|
0,
|
||||||
&policy.PolicyManagerV1{},
|
&policy.PolicyManagerV1{},
|
||||||
&types.Config{},
|
&types.Config{},
|
||||||
|
[]string{},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("nodeExpiry() error = %v", err)
|
t.Fatalf("nodeExpiry() error = %v", err)
|
||||||
|
@ -442,6 +442,42 @@ func (pol *ACLPolicy) CompileSSHPolicy(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pol *ACLPolicy) GetAttributesForNode(
|
||||||
|
node *types.Node,
|
||||||
|
users []types.User,
|
||||||
|
peers types.Nodes,
|
||||||
|
) ([]string, error) {
|
||||||
|
if pol == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributes []string
|
||||||
|
|
||||||
|
for _, nodeAttr := range pol.NodeAttributes {
|
||||||
|
var dest netipx.IPSetBuilder
|
||||||
|
for _, target := range nodeAttr.Targets {
|
||||||
|
expanded, err := pol.ExpandAlias(append(peers, node), users, target)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dest.AddSet(expanded)
|
||||||
|
}
|
||||||
|
|
||||||
|
destSet, err := dest.IPSet()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !node.InIPSet(destSet) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes = append(attributes, nodeAttr.Attributes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ipSetAll returns a function that iterates over all the IPs in the IPSet.
|
// ipSetAll returns a function that iterates over all the IPs in the IPSet.
|
||||||
func ipSetAll(ipSet *netipx.IPSet) iter.Seq[netip.Addr] {
|
func ipSetAll(ipSet *netipx.IPSet) iter.Seq[netip.Addr] {
|
||||||
return func(yield func(netip.Addr) bool) {
|
return func(yield func(netip.Addr) bool) {
|
||||||
|
@ -10,13 +10,14 @@ import (
|
|||||||
|
|
||||||
// ACLPolicy represents a Tailscale ACL Policy.
|
// ACLPolicy represents a Tailscale ACL Policy.
|
||||||
type ACLPolicy struct {
|
type ACLPolicy struct {
|
||||||
Groups Groups `json:"groups"`
|
Groups Groups `json:"groups"`
|
||||||
Hosts Hosts `json:"hosts"`
|
Hosts Hosts `json:"hosts"`
|
||||||
TagOwners TagOwners `json:"tagOwners"`
|
TagOwners TagOwners `json:"tagOwners"`
|
||||||
ACLs []ACL `json:"acls"`
|
ACLs []ACL `json:"acls"`
|
||||||
Tests []ACLTest `json:"tests"`
|
Tests []ACLTest `json:"tests"`
|
||||||
AutoApprovers AutoApprovers `json:"autoApprovers"`
|
AutoApprovers AutoApprovers `json:"autoApprovers"`
|
||||||
SSHs []SSH `json:"ssh"`
|
SSHs []SSH `json:"ssh"`
|
||||||
|
NodeAttributes []NodeAttributes `json:"nodeAttrs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACL is a basic rule for the ACL Policy.
|
// ACL is a basic rule for the ACL Policy.
|
||||||
@ -50,6 +51,12 @@ type AutoApprovers struct {
|
|||||||
ExitNode []string `json:"exitNode"`
|
ExitNode []string `json:"exitNode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NodeAttributes is for applying additional attributes to specific devices
|
||||||
|
type NodeAttributes struct {
|
||||||
|
Targets []string `json:"target"`
|
||||||
|
Attributes []string `json:"attr"`
|
||||||
|
}
|
||||||
|
|
||||||
// SSH controls who can ssh into which machines.
|
// SSH controls who can ssh into which machines.
|
||||||
type SSH struct {
|
type SSH struct {
|
||||||
Action string `json:"action"`
|
Action string `json:"action"`
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
type PolicyManager interface {
|
type PolicyManager interface {
|
||||||
Filter() []tailcfg.FilterRule
|
Filter() []tailcfg.FilterRule
|
||||||
SSHPolicy(*types.Node) (*tailcfg.SSHPolicy, error)
|
SSHPolicy(*types.Node) (*tailcfg.SSHPolicy, error)
|
||||||
|
NodeAttributes(node *types.Node) ([]string, error)
|
||||||
Tags(*types.Node) []string
|
Tags(*types.Node) []string
|
||||||
ApproversForRoute(netip.Prefix) []string
|
ApproversForRoute(netip.Prefix) []string
|
||||||
ExpandAlias(string) (*netipx.IPSet, error)
|
ExpandAlias(string) (*netipx.IPSet, error)
|
||||||
@ -140,6 +141,13 @@ func (pm *PolicyManagerV1) SetPolicy(polB []byte) (bool, error) {
|
|||||||
return pm.updateLocked()
|
return pm.updateLocked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pm *PolicyManagerV1) NodeAttributes(node *types.Node) ([]string, error) {
|
||||||
|
pm.mu.Lock()
|
||||||
|
defer pm.mu.Unlock()
|
||||||
|
|
||||||
|
return pm.pol.GetAttributesForNode(node, pm.users, pm.nodes)
|
||||||
|
}
|
||||||
|
|
||||||
// SetUsers updates the users in the policy manager and updates the filter rules.
|
// SetUsers updates the users in the policy manager and updates the filter rules.
|
||||||
func (pm *PolicyManagerV1) SetUsers(users []types.User) (bool, error) {
|
func (pm *PolicyManagerV1) SetUsers(users []types.User) (bool, error) {
|
||||||
pm.mu.Lock()
|
pm.mu.Lock()
|
||||||
|
Loading…
Reference in New Issue
Block a user