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(
|
||||
cfg *types.Config,
|
||||
node *types.Node,
|
||||
nodeAttrs []string,
|
||||
) *tailcfg.DNSConfig {
|
||||
if cfg.TailcfgDNSConfig == nil {
|
||||
return nil
|
||||
@ -122,7 +123,7 @@ func generateDNSConfig(
|
||||
|
||||
dnsConfig := cfg.TailcfgDNSConfig.Clone()
|
||||
|
||||
addNextDNSMetadata(dnsConfig.Resolvers, node)
|
||||
addNextDNSMetadata(dnsConfig.Resolvers, node, nodeAttrs)
|
||||
|
||||
return dnsConfig
|
||||
}
|
||||
@ -134,9 +135,21 @@ func generateDNSConfig(
|
||||
//
|
||||
// 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`
|
||||
func addNextDNSMetadata(resolvers []*dnstype.Resolver, node *types.Node) {
|
||||
func addNextDNSMetadata(resolvers []*dnstype.Resolver, node *types.Node, nodeAttrs []string) {
|
||||
for _, resolver := range resolvers {
|
||||
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{
|
||||
"device_name": []string{node.Hostname},
|
||||
"device_model": []string{node.Hostinfo.OS},
|
||||
@ -158,7 +171,13 @@ func (m *Mapper) fullMapResponse(
|
||||
peers types.Nodes,
|
||||
capVer tailcfg.CapabilityVersion,
|
||||
) (*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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -171,6 +190,7 @@ func (m *Mapper) fullMapResponse(
|
||||
capVer,
|
||||
peers,
|
||||
m.cfg,
|
||||
nodeAttrs,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -206,7 +226,13 @@ func (m *Mapper) ReadOnlyMapResponse(
|
||||
node *types.Node,
|
||||
messages ...string,
|
||||
) ([]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 {
|
||||
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(
|
||||
&resp,
|
||||
false, // partial change
|
||||
@ -276,6 +307,7 @@ func (m *Mapper) PeerChangedResponse(
|
||||
mapRequest.Version,
|
||||
changedNodes,
|
||||
m.cfg,
|
||||
nodeAttrs,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -300,7 +332,7 @@ func (m *Mapper) PeerChangedResponse(
|
||||
|
||||
// Add the node itself, it might have changed, and particularly
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -444,10 +476,11 @@ func (m *Mapper) baseMapResponse() tailcfg.MapResponse {
|
||||
func (m *Mapper) baseWithConfigMapResponse(
|
||||
node *types.Node,
|
||||
capVer tailcfg.CapabilityVersion,
|
||||
nodeAttrs []string,
|
||||
) (*tailcfg.MapResponse, error) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -505,6 +538,7 @@ func appendPeerChanges(
|
||||
capVer tailcfg.CapabilityVersion,
|
||||
changed types.Nodes,
|
||||
cfg *types.Config,
|
||||
attrs []string,
|
||||
) error {
|
||||
filter := polMan.Filter()
|
||||
|
||||
@ -521,7 +555,7 @@ func appendPeerChanges(
|
||||
|
||||
profiles := generateUserProfiles(node, changed)
|
||||
|
||||
dnsConfig := generateDNSConfig(cfg, node)
|
||||
dnsConfig := generateDNSConfig(cfg, node, attrs)
|
||||
|
||||
tailPeers, err := tailNodes(changed, capVer, polMan, cfg)
|
||||
if err != nil {
|
||||
|
@ -120,6 +120,7 @@ func TestDNSConfigMapResponse(t *testing.T) {
|
||||
TailcfgDNSConfig: &dnsConfigOrig,
|
||||
},
|
||||
nodeInShared1,
|
||||
[]string{},
|
||||
)
|
||||
|
||||
if diff := cmp.Diff(tt.want, got, cmpopts.EquateEmpty()); diff != "" {
|
||||
|
@ -20,11 +20,18 @@ func tailNodes(
|
||||
tNodes := make([]*tailcfg.Node, len(nodes))
|
||||
|
||||
for index, node := range nodes {
|
||||
|
||||
nodeAttrs, err := polMan.NodeAttributes(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node, err := tailNode(
|
||||
node,
|
||||
capVer,
|
||||
polMan,
|
||||
cfg,
|
||||
nodeAttrs,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -42,6 +49,7 @@ func tailNode(
|
||||
capVer tailcfg.CapabilityVersion,
|
||||
polMan policy.PolicyManager,
|
||||
cfg *types.Config,
|
||||
nodeAttrs []string,
|
||||
) (*tailcfg.Node, error) {
|
||||
addrs := node.Prefixes()
|
||||
|
||||
@ -124,6 +132,10 @@ func tailNode(
|
||||
tNode.CapMap[tailcfg.NodeAttrRandomizeClientPort] = []tailcfg.RawMessage{}
|
||||
}
|
||||
|
||||
for _, nodeAttr := range nodeAttrs {
|
||||
tNode.CapMap[tailcfg.NodeCapability(nodeAttr)] = []tailcfg.RawMessage{}
|
||||
}
|
||||
|
||||
if node.IsOnline == nil || !*node.IsOnline {
|
||||
// LastSeen is only set when node is
|
||||
// not connected to the control server.
|
||||
|
@ -195,6 +195,7 @@ func TestTailNode(t *testing.T) {
|
||||
0,
|
||||
polMan,
|
||||
cfg,
|
||||
[]string{},
|
||||
)
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
@ -248,6 +249,7 @@ func TestNodeExpiry(t *testing.T) {
|
||||
0,
|
||||
&policy.PolicyManagerV1{},
|
||||
&types.Config{},
|
||||
[]string{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("nodeExpiry() error = %v", err)
|
||||
|
@ -442,6 +442,42 @@ func (pol *ACLPolicy) CompileSSHPolicy(
|
||||
}, 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.
|
||||
func ipSetAll(ipSet *netipx.IPSet) iter.Seq[netip.Addr] {
|
||||
return func(yield func(netip.Addr) bool) {
|
||||
|
@ -10,13 +10,14 @@ import (
|
||||
|
||||
// ACLPolicy represents a Tailscale ACL Policy.
|
||||
type ACLPolicy struct {
|
||||
Groups Groups `json:"groups"`
|
||||
Hosts Hosts `json:"hosts"`
|
||||
TagOwners TagOwners `json:"tagOwners"`
|
||||
ACLs []ACL `json:"acls"`
|
||||
Tests []ACLTest `json:"tests"`
|
||||
AutoApprovers AutoApprovers `json:"autoApprovers"`
|
||||
SSHs []SSH `json:"ssh"`
|
||||
Groups Groups `json:"groups"`
|
||||
Hosts Hosts `json:"hosts"`
|
||||
TagOwners TagOwners `json:"tagOwners"`
|
||||
ACLs []ACL `json:"acls"`
|
||||
Tests []ACLTest `json:"tests"`
|
||||
AutoApprovers AutoApprovers `json:"autoApprovers"`
|
||||
SSHs []SSH `json:"ssh"`
|
||||
NodeAttributes []NodeAttributes `json:"nodeAttrs"`
|
||||
}
|
||||
|
||||
// ACL is a basic rule for the ACL Policy.
|
||||
@ -50,6 +51,12 @@ type AutoApprovers struct {
|
||||
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.
|
||||
type SSH struct {
|
||||
Action string `json:"action"`
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
type PolicyManager interface {
|
||||
Filter() []tailcfg.FilterRule
|
||||
SSHPolicy(*types.Node) (*tailcfg.SSHPolicy, error)
|
||||
NodeAttributes(node *types.Node) ([]string, error)
|
||||
Tags(*types.Node) []string
|
||||
ApproversForRoute(netip.Prefix) []string
|
||||
ExpandAlias(string) (*netipx.IPSet, error)
|
||||
@ -140,6 +141,13 @@ func (pm *PolicyManagerV1) SetPolicy(polB []byte) (bool, error) {
|
||||
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.
|
||||
func (pm *PolicyManagerV1) SetUsers(users []types.User) (bool, error) {
|
||||
pm.mu.Lock()
|
||||
|
Loading…
Reference in New Issue
Block a user