mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-11-04 01:51:04 +01:00 
			
		
		
		
	remove DB dependency of tailNode conversion, add test
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
		
							parent
							
								
									bce8427423
								
							
						
					
					
						commit
						5bad48a24e
					
				@ -5,25 +5,20 @@ import (
 | 
				
			|||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"net/netip"
 | 
						"net/netip"
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
	"strconv"
 | 
					 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/juanfont/headscale/hscontrol/policy"
 | 
					 | 
				
			||||||
	"github.com/juanfont/headscale/hscontrol/types"
 | 
						"github.com/juanfont/headscale/hscontrol/types"
 | 
				
			||||||
	"github.com/juanfont/headscale/hscontrol/util"
 | 
						"github.com/juanfont/headscale/hscontrol/util"
 | 
				
			||||||
	"github.com/patrickmn/go-cache"
 | 
						"github.com/patrickmn/go-cache"
 | 
				
			||||||
	"github.com/rs/zerolog/log"
 | 
						"github.com/rs/zerolog/log"
 | 
				
			||||||
	"github.com/samber/lo"
 | 
					 | 
				
			||||||
	"gorm.io/gorm"
 | 
						"gorm.io/gorm"
 | 
				
			||||||
	"tailscale.com/tailcfg"
 | 
					 | 
				
			||||||
	"tailscale.com/types/key"
 | 
						"tailscale.com/types/key"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	MachineGivenNameHashLength = 8
 | 
						MachineGivenNameHashLength = 8
 | 
				
			||||||
	MachineGivenNameTrimSize   = 2
 | 
						MachineGivenNameTrimSize   = 2
 | 
				
			||||||
	MaxHostnameLength          = 255
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
@ -33,7 +28,6 @@ var (
 | 
				
			|||||||
		"machine not found in registration cache",
 | 
							"machine not found in registration cache",
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	ErrCouldNotConvertMachineInterface = errors.New("failed to convert machine interface")
 | 
						ErrCouldNotConvertMachineInterface = errors.New("failed to convert machine interface")
 | 
				
			||||||
	ErrHostnameTooLong                 = errors.New("hostname too long")
 | 
					 | 
				
			||||||
	ErrDifferentRegisteredUser         = errors.New(
 | 
						ErrDifferentRegisteredUser         = errors.New(
 | 
				
			||||||
		"machine was previously registered with a different user",
 | 
							"machine was previously registered with a different user",
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
@ -471,7 +465,7 @@ func (hsdb *HSDatabase) RegisterMachine(machine types.Machine,
 | 
				
			|||||||
	log.Trace().
 | 
						log.Trace().
 | 
				
			||||||
		Caller().
 | 
							Caller().
 | 
				
			||||||
		Str("machine", machine.Hostname).
 | 
							Str("machine", machine.Hostname).
 | 
				
			||||||
		Str("ip", strings.Join(ips.ToStringSlice(), ",")).
 | 
							Str("ip", strings.Join(ips.StringSlice(), ",")).
 | 
				
			||||||
		Msg("Machine registered with the database")
 | 
							Msg("Machine registered with the database")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &machine, nil
 | 
						return &machine, nil
 | 
				
			||||||
@ -785,169 +779,3 @@ func (hsdb *HSDatabase) ExpireExpiredMachines(lastChange time.Time) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
func (hsdb *HSDatabase) TailNodes(
 | 
					 | 
				
			||||||
	machines types.Machines,
 | 
					 | 
				
			||||||
	pol *policy.ACLPolicy,
 | 
					 | 
				
			||||||
	dnsConfig *tailcfg.DNSConfig,
 | 
					 | 
				
			||||||
) ([]*tailcfg.Node, error) {
 | 
					 | 
				
			||||||
	nodes := make([]*tailcfg.Node, len(machines))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for index, machine := range machines {
 | 
					 | 
				
			||||||
		node, err := hsdb.TailNode(machine, pol, dnsConfig)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		nodes[index] = node
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nodes, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// TailNode converts a Machine into a Tailscale Node. includeRoutes is false for shared nodes
 | 
					 | 
				
			||||||
// as per the expected behaviour in the official SaaS.
 | 
					 | 
				
			||||||
func (hsdb *HSDatabase) TailNode(
 | 
					 | 
				
			||||||
	machine types.Machine,
 | 
					 | 
				
			||||||
	pol *policy.ACLPolicy,
 | 
					 | 
				
			||||||
	dnsConfig *tailcfg.DNSConfig,
 | 
					 | 
				
			||||||
) (*tailcfg.Node, error) {
 | 
					 | 
				
			||||||
	var nodeKey key.NodePublic
 | 
					 | 
				
			||||||
	err := nodeKey.UnmarshalText([]byte(util.NodePublicKeyEnsurePrefix(machine.NodeKey)))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Trace().
 | 
					 | 
				
			||||||
			Caller().
 | 
					 | 
				
			||||||
			Str("node_key", machine.NodeKey).
 | 
					 | 
				
			||||||
			Msgf("Failed to parse node public key from hex")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("failed to parse node public key: %w", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var machineKey key.MachinePublic
 | 
					 | 
				
			||||||
	// MachineKey is only used in the legacy protocol
 | 
					 | 
				
			||||||
	if machine.MachineKey != "" {
 | 
					 | 
				
			||||||
		err = machineKey.UnmarshalText(
 | 
					 | 
				
			||||||
			[]byte(util.MachinePublicKeyEnsurePrefix(machine.MachineKey)),
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, fmt.Errorf("failed to parse machine public key: %w", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var discoKey key.DiscoPublic
 | 
					 | 
				
			||||||
	if machine.DiscoKey != "" {
 | 
					 | 
				
			||||||
		err := discoKey.UnmarshalText(
 | 
					 | 
				
			||||||
			[]byte(util.DiscoPublicKeyEnsurePrefix(machine.DiscoKey)),
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, fmt.Errorf("failed to parse disco public key: %w", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		discoKey = key.DiscoPublic{}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	addrs := []netip.Prefix{}
 | 
					 | 
				
			||||||
	for _, machineAddress := range machine.IPAddresses {
 | 
					 | 
				
			||||||
		ip := netip.PrefixFrom(machineAddress, machineAddress.BitLen())
 | 
					 | 
				
			||||||
		addrs = append(addrs, ip)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	allowedIPs := append(
 | 
					 | 
				
			||||||
		[]netip.Prefix{},
 | 
					 | 
				
			||||||
		addrs...) // we append the node own IP, as it is required by the clients
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	primaryRoutes, err := hsdb.GetMachinePrimaryRoutes(&machine)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	primaryPrefixes := primaryRoutes.Prefixes()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	machineRoutes, err := hsdb.GetMachineRoutes(&machine)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	for _, route := range machineRoutes {
 | 
					 | 
				
			||||||
		if route.Enabled && (route.IsPrimary || route.IsExitRoute()) {
 | 
					 | 
				
			||||||
			allowedIPs = append(allowedIPs, netip.Prefix(route.Prefix))
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var derp string
 | 
					 | 
				
			||||||
	if machine.HostInfo.NetInfo != nil {
 | 
					 | 
				
			||||||
		derp = fmt.Sprintf("127.3.3.40:%d", machine.HostInfo.NetInfo.PreferredDERP)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		derp = "127.3.3.40:0" // Zero means disconnected or unknown.
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var keyExpiry time.Time
 | 
					 | 
				
			||||||
	if machine.Expiry != nil {
 | 
					 | 
				
			||||||
		keyExpiry = *machine.Expiry
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		keyExpiry = time.Time{}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var hostname string
 | 
					 | 
				
			||||||
	if dnsConfig != nil && dnsConfig.Proxied { // MagicDNS
 | 
					 | 
				
			||||||
		hostname = fmt.Sprintf(
 | 
					 | 
				
			||||||
			"%s.%s.%s",
 | 
					 | 
				
			||||||
			machine.GivenName,
 | 
					 | 
				
			||||||
			machine.User.Name,
 | 
					 | 
				
			||||||
			hsdb.baseDomain,
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
		if len(hostname) > MaxHostnameLength {
 | 
					 | 
				
			||||||
			return nil, fmt.Errorf(
 | 
					 | 
				
			||||||
				"hostname %q is too long it cannot except 255 ASCII chars: %w",
 | 
					 | 
				
			||||||
				hostname,
 | 
					 | 
				
			||||||
				ErrHostnameTooLong,
 | 
					 | 
				
			||||||
			)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		hostname = machine.GivenName
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	hostInfo := machine.GetHostInfo()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	online := machine.IsOnline()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	tags, _ := pol.GetTagsOfMachine(machine, hsdb.stripEmailDomain)
 | 
					 | 
				
			||||||
	tags = lo.Uniq(append(tags, machine.ForcedTags...))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	node := tailcfg.Node{
 | 
					 | 
				
			||||||
		ID: tailcfg.NodeID(machine.ID), // this is the actual ID
 | 
					 | 
				
			||||||
		StableID: tailcfg.StableNodeID(
 | 
					 | 
				
			||||||
			strconv.FormatUint(machine.ID, util.Base10),
 | 
					 | 
				
			||||||
		), // in headscale, unlike tailcontrol server, IDs are permanent
 | 
					 | 
				
			||||||
		Name: hostname,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		User: tailcfg.UserID(machine.UserID),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		Key:       nodeKey,
 | 
					 | 
				
			||||||
		KeyExpiry: keyExpiry,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		Machine:    machineKey,
 | 
					 | 
				
			||||||
		DiscoKey:   discoKey,
 | 
					 | 
				
			||||||
		Addresses:  addrs,
 | 
					 | 
				
			||||||
		AllowedIPs: allowedIPs,
 | 
					 | 
				
			||||||
		Endpoints:  machine.Endpoints,
 | 
					 | 
				
			||||||
		DERP:       derp,
 | 
					 | 
				
			||||||
		Hostinfo:   hostInfo.View(),
 | 
					 | 
				
			||||||
		Created:    machine.CreatedAt,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		Tags: tags,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		PrimaryRoutes: primaryPrefixes,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		LastSeen:          machine.LastSeen,
 | 
					 | 
				
			||||||
		Online:            &online,
 | 
					 | 
				
			||||||
		KeepAlive:         true,
 | 
					 | 
				
			||||||
		MachineAuthorized: !machine.IsExpired(),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		Capabilities: []string{
 | 
					 | 
				
			||||||
			tailcfg.CapabilityFileSharing,
 | 
					 | 
				
			||||||
			tailcfg.CapabilityAdmin,
 | 
					 | 
				
			||||||
			tailcfg.CapabilitySSH,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &node, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -69,27 +69,11 @@ func NewMapper(
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (m Mapper) fullMapResponse(
 | 
					func (m *Mapper) tempWrap(
 | 
				
			||||||
	mapRequest tailcfg.MapRequest,
 | 
						mapRequest tailcfg.MapRequest,
 | 
				
			||||||
	machine *types.Machine,
 | 
						machine *types.Machine,
 | 
				
			||||||
	pol *policy.ACLPolicy,
 | 
						pol *policy.ACLPolicy,
 | 
				
			||||||
) (*tailcfg.MapResponse, error) {
 | 
					) (*tailcfg.MapResponse, error) {
 | 
				
			||||||
	log.Trace().
 | 
					 | 
				
			||||||
		Caller().
 | 
					 | 
				
			||||||
		Str("machine", mapRequest.Hostinfo.Hostname).
 | 
					 | 
				
			||||||
		Msg("Creating Map response")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// TODO(kradalby): Decouple this from DB?
 | 
					 | 
				
			||||||
	node, err := m.db.TailNode(*machine, pol, m.dnsCfg)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Error().
 | 
					 | 
				
			||||||
			Caller().
 | 
					 | 
				
			||||||
			Err(err).
 | 
					 | 
				
			||||||
			Msg("Cannot convert to node")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	peers, err := m.db.ListPeers(machine)
 | 
						peers, err := m.db.ListPeers(machine)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error().
 | 
							log.Error().
 | 
				
			||||||
@ -100,7 +84,39 @@ func (m Mapper) fullMapResponse(
 | 
				
			|||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rules, sshPolicy, err := policy.GenerateFilterRules(pol, peers, m.stripEmailDomain)
 | 
						return fullMapResponse(
 | 
				
			||||||
 | 
							mapRequest,
 | 
				
			||||||
 | 
							pol,
 | 
				
			||||||
 | 
							machine,
 | 
				
			||||||
 | 
							peers,
 | 
				
			||||||
 | 
							m.stripEmailDomain,
 | 
				
			||||||
 | 
							m.baseDomain,
 | 
				
			||||||
 | 
							m.dnsCfg,
 | 
				
			||||||
 | 
							m.derpMap,
 | 
				
			||||||
 | 
							m.logtail,
 | 
				
			||||||
 | 
							m.randomClientPort,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fullMapResponse(
 | 
				
			||||||
 | 
						mapRequest tailcfg.MapRequest,
 | 
				
			||||||
 | 
						pol *policy.ACLPolicy,
 | 
				
			||||||
 | 
						machine *types.Machine,
 | 
				
			||||||
 | 
						peers types.Machines,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stripEmailDomain bool,
 | 
				
			||||||
 | 
						baseDomain string,
 | 
				
			||||||
 | 
						dnsCfg *tailcfg.DNSConfig,
 | 
				
			||||||
 | 
						derpMap *tailcfg.DERPMap,
 | 
				
			||||||
 | 
						logtail bool,
 | 
				
			||||||
 | 
						randomClientPort bool,
 | 
				
			||||||
 | 
					) (*tailcfg.MapResponse, error) {
 | 
				
			||||||
 | 
						tailnode, err := tailNode(*machine, pol, dnsCfg, baseDomain, stripEmailDomain)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rules, sshPolicy, err := policy.GenerateFilterRules(pol, peers, stripEmailDomain)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -109,38 +125,31 @@ func (m Mapper) fullMapResponse(
 | 
				
			|||||||
		peers = policy.FilterMachinesByACL(machine, peers, rules)
 | 
							peers = policy.FilterMachinesByACL(machine, peers, rules)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	profiles := generateUserProfiles(machine, peers, m.baseDomain)
 | 
						profiles := generateUserProfiles(machine, peers, baseDomain)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO(kradalby): Decouple this from DB?
 | 
					 | 
				
			||||||
	nodePeers, err := m.db.TailNodes(peers, pol, m.dnsCfg)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Error().
 | 
					 | 
				
			||||||
			Caller().
 | 
					 | 
				
			||||||
			Err(err).
 | 
					 | 
				
			||||||
			Msg("Failed to convert peers to Tailscale nodes")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// TODO(kradalby): Shold this mutation happen before TailNode(s) is called?
 | 
					 | 
				
			||||||
	dnsConfig := generateDNSConfig(
 | 
						dnsConfig := generateDNSConfig(
 | 
				
			||||||
		m.dnsCfg,
 | 
							dnsCfg,
 | 
				
			||||||
		m.baseDomain,
 | 
							baseDomain,
 | 
				
			||||||
		*machine,
 | 
							*machine,
 | 
				
			||||||
		peers,
 | 
							peers,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tailPeers, err := tailNodes(peers, pol, dnsCfg, baseDomain, stripEmailDomain)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	now := time.Now()
 | 
						now := time.Now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	resp := tailcfg.MapResponse{
 | 
						resp := tailcfg.MapResponse{
 | 
				
			||||||
		KeepAlive: false,
 | 
							KeepAlive: false,
 | 
				
			||||||
		Node:      node,
 | 
							Node:      tailnode,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TODO: Only send if updated
 | 
							// TODO: Only send if updated
 | 
				
			||||||
		DERPMap: m.derpMap,
 | 
							DERPMap: derpMap,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TODO: Only send if updated
 | 
							// TODO: Only send if updated
 | 
				
			||||||
		Peers: nodePeers,
 | 
							Peers: tailPeers,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TODO(kradalby): Implement:
 | 
							// TODO(kradalby): Implement:
 | 
				
			||||||
		// https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L1351-L1374
 | 
							// https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L1351-L1374
 | 
				
			||||||
@ -154,7 +163,7 @@ func (m Mapper) fullMapResponse(
 | 
				
			|||||||
		DNSConfig: dnsConfig,
 | 
							DNSConfig: dnsConfig,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TODO: Only send if updated
 | 
							// TODO: Only send if updated
 | 
				
			||||||
		Domain: m.baseDomain,
 | 
							Domain: baseDomain,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Do not instruct clients to collect services, we do not
 | 
							// Do not instruct clients to collect services, we do not
 | 
				
			||||||
		// support or do anything with them
 | 
							// support or do anything with them
 | 
				
			||||||
@ -171,8 +180,8 @@ func (m Mapper) fullMapResponse(
 | 
				
			|||||||
		ControlTime: &now,
 | 
							ControlTime: &now,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Debug: &tailcfg.Debug{
 | 
							Debug: &tailcfg.Debug{
 | 
				
			||||||
			DisableLogTail:      !m.logtail,
 | 
								DisableLogTail:      !logtail,
 | 
				
			||||||
			RandomizeClientPort: m.randomClientPort,
 | 
								RandomizeClientPort: randomClientPort,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -283,7 +292,7 @@ func (m Mapper) CreateMapResponse(
 | 
				
			|||||||
	machine *types.Machine,
 | 
						machine *types.Machine,
 | 
				
			||||||
	pol *policy.ACLPolicy,
 | 
						pol *policy.ACLPolicy,
 | 
				
			||||||
) ([]byte, error) {
 | 
					) ([]byte, error) {
 | 
				
			||||||
	mapResponse, err := m.fullMapResponse(mapRequest, machine, pol)
 | 
						mapResponse, err := m.tempWrap(mapRequest, machine, pol)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -124,7 +124,7 @@ func TestDNSConfigMapResponse(t *testing.T) {
 | 
				
			|||||||
			)
 | 
								)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if diff := cmp.Diff(tt.want, got, cmpopts.EquateEmpty()); diff != "" {
 | 
								if diff := cmp.Diff(tt.want, got, cmpopts.EquateEmpty()); diff != "" {
 | 
				
			||||||
				t.Errorf("expandAlias() = %v, want %v", got, tt.want)
 | 
									t.Errorf("expandAlias() unexpected result (-want +got):\n%s", diff)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										151
									
								
								hscontrol/mapper/tail.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								hscontrol/mapper/tail.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,151 @@
 | 
				
			|||||||
 | 
					package mapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net/netip"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/juanfont/headscale/hscontrol/policy"
 | 
				
			||||||
 | 
						"github.com/juanfont/headscale/hscontrol/types"
 | 
				
			||||||
 | 
						"github.com/juanfont/headscale/hscontrol/util"
 | 
				
			||||||
 | 
						"github.com/samber/lo"
 | 
				
			||||||
 | 
						"tailscale.com/tailcfg"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func tailNodes(
 | 
				
			||||||
 | 
						machines types.Machines,
 | 
				
			||||||
 | 
						pol *policy.ACLPolicy,
 | 
				
			||||||
 | 
						dnsConfig *tailcfg.DNSConfig,
 | 
				
			||||||
 | 
						baseDomain string,
 | 
				
			||||||
 | 
						stripEmailDomain bool,
 | 
				
			||||||
 | 
					) ([]*tailcfg.Node, error) {
 | 
				
			||||||
 | 
						nodes := make([]*tailcfg.Node, len(machines))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for index, machine := range machines {
 | 
				
			||||||
 | 
							node, err := tailNode(
 | 
				
			||||||
 | 
								machine,
 | 
				
			||||||
 | 
								pol,
 | 
				
			||||||
 | 
								dnsConfig,
 | 
				
			||||||
 | 
								baseDomain,
 | 
				
			||||||
 | 
								stripEmailDomain,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							nodes[index] = node
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nodes, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// tailNode converts a Machine into a Tailscale Node. includeRoutes is false for shared nodes
 | 
				
			||||||
 | 
					// as per the expected behaviour in the official SaaS.
 | 
				
			||||||
 | 
					func tailNode(
 | 
				
			||||||
 | 
						machine types.Machine,
 | 
				
			||||||
 | 
						pol *policy.ACLPolicy,
 | 
				
			||||||
 | 
						dnsConfig *tailcfg.DNSConfig,
 | 
				
			||||||
 | 
						baseDomain string,
 | 
				
			||||||
 | 
						stripEmailDomain bool,
 | 
				
			||||||
 | 
					) (*tailcfg.Node, error) {
 | 
				
			||||||
 | 
						nodeKey, err := machine.NodePublicKey()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// MachineKey is only used in the legacy protocol
 | 
				
			||||||
 | 
						machineKey, err := machine.MachinePublicKey()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						discoKey, err := machine.DiscoPublicKey()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						addrs := machine.IPAddresses.Prefixes()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						allowedIPs := append(
 | 
				
			||||||
 | 
							[]netip.Prefix{},
 | 
				
			||||||
 | 
							addrs...) // we append the node own IP, as it is required by the clients
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						primaryPrefixes := []netip.Prefix{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, route := range machine.Routes {
 | 
				
			||||||
 | 
							if route.Enabled {
 | 
				
			||||||
 | 
								if route.IsPrimary {
 | 
				
			||||||
 | 
									allowedIPs = append(allowedIPs, netip.Prefix(route.Prefix))
 | 
				
			||||||
 | 
									primaryPrefixes = append(primaryPrefixes, netip.Prefix(route.Prefix))
 | 
				
			||||||
 | 
								} else if route.IsExitRoute() {
 | 
				
			||||||
 | 
									allowedIPs = append(allowedIPs, netip.Prefix(route.Prefix))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var derp string
 | 
				
			||||||
 | 
						if machine.HostInfo.NetInfo != nil {
 | 
				
			||||||
 | 
							derp = fmt.Sprintf("127.3.3.40:%d", machine.HostInfo.NetInfo.PreferredDERP)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							derp = "127.3.3.40:0" // Zero means disconnected or unknown.
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var keyExpiry time.Time
 | 
				
			||||||
 | 
						if machine.Expiry != nil {
 | 
				
			||||||
 | 
							keyExpiry = *machine.Expiry
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							keyExpiry = time.Time{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hostname, err := machine.GetFQDN(dnsConfig, baseDomain)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hostInfo := machine.GetHostInfo()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						online := machine.IsOnline()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tags, _ := pol.GetTagsOfMachine(machine, stripEmailDomain)
 | 
				
			||||||
 | 
						tags = lo.Uniq(append(tags, machine.ForcedTags...))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						node := tailcfg.Node{
 | 
				
			||||||
 | 
							ID: tailcfg.NodeID(machine.ID), // this is the actual ID
 | 
				
			||||||
 | 
							StableID: tailcfg.StableNodeID(
 | 
				
			||||||
 | 
								strconv.FormatUint(machine.ID, util.Base10),
 | 
				
			||||||
 | 
							), // in headscale, unlike tailcontrol server, IDs are permanent
 | 
				
			||||||
 | 
							Name: hostname,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							User: tailcfg.UserID(machine.UserID),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Key:       nodeKey,
 | 
				
			||||||
 | 
							KeyExpiry: keyExpiry,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Machine:    machineKey,
 | 
				
			||||||
 | 
							DiscoKey:   discoKey,
 | 
				
			||||||
 | 
							Addresses:  addrs,
 | 
				
			||||||
 | 
							AllowedIPs: allowedIPs,
 | 
				
			||||||
 | 
							Endpoints:  machine.Endpoints,
 | 
				
			||||||
 | 
							DERP:       derp,
 | 
				
			||||||
 | 
							Hostinfo:   hostInfo.View(),
 | 
				
			||||||
 | 
							Created:    machine.CreatedAt,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Tags: tags,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							PrimaryRoutes: primaryPrefixes,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							LastSeen:          machine.LastSeen,
 | 
				
			||||||
 | 
							Online:            &online,
 | 
				
			||||||
 | 
							KeepAlive:         true,
 | 
				
			||||||
 | 
							MachineAuthorized: !machine.IsExpired(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Capabilities: []string{
 | 
				
			||||||
 | 
								tailcfg.CapabilityFileSharing,
 | 
				
			||||||
 | 
								tailcfg.CapabilityAdmin,
 | 
				
			||||||
 | 
								tailcfg.CapabilitySSH,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &node, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										183
									
								
								hscontrol/mapper/tail_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								hscontrol/mapper/tail_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,183 @@
 | 
				
			|||||||
 | 
					package mapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"net/netip"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/google/go-cmp/cmp"
 | 
				
			||||||
 | 
						"github.com/google/go-cmp/cmp/cmpopts"
 | 
				
			||||||
 | 
						"github.com/juanfont/headscale/hscontrol/policy"
 | 
				
			||||||
 | 
						"github.com/juanfont/headscale/hscontrol/types"
 | 
				
			||||||
 | 
						"tailscale.com/tailcfg"
 | 
				
			||||||
 | 
						"tailscale.com/types/key"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestTailNode(t *testing.T) {
 | 
				
			||||||
 | 
						mustNK := func(str string) key.NodePublic {
 | 
				
			||||||
 | 
							var k key.NodePublic
 | 
				
			||||||
 | 
							_ = k.UnmarshalText([]byte(str))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return k
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mustDK := func(str string) key.DiscoPublic {
 | 
				
			||||||
 | 
							var k key.DiscoPublic
 | 
				
			||||||
 | 
							_ = k.UnmarshalText([]byte(str))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return k
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mustMK := func(str string) key.MachinePublic {
 | 
				
			||||||
 | 
							var k key.MachinePublic
 | 
				
			||||||
 | 
							_ = k.UnmarshalText([]byte(str))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return k
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hiview := func(hoin tailcfg.Hostinfo) tailcfg.HostinfoView {
 | 
				
			||||||
 | 
							return hoin.View()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						created := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
 | 
				
			||||||
 | 
						lastSeen := time.Date(2009, time.November, 10, 23, 9, 0, 0, time.UTC)
 | 
				
			||||||
 | 
						expire := time.Date(2500, time.November, 11, 23, 0, 0, 0, time.UTC)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name             string
 | 
				
			||||||
 | 
							machine          types.Machine
 | 
				
			||||||
 | 
							pol              *policy.ACLPolicy
 | 
				
			||||||
 | 
							dnsConfig        *tailcfg.DNSConfig
 | 
				
			||||||
 | 
							baseDomain       string
 | 
				
			||||||
 | 
							stripEmailDomain bool
 | 
				
			||||||
 | 
							want             *tailcfg.Node
 | 
				
			||||||
 | 
							wantErr          bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:             "empty-machine",
 | 
				
			||||||
 | 
								machine:          types.Machine{},
 | 
				
			||||||
 | 
								pol:              &policy.ACLPolicy{},
 | 
				
			||||||
 | 
								dnsConfig:        &tailcfg.DNSConfig{},
 | 
				
			||||||
 | 
								baseDomain:       "",
 | 
				
			||||||
 | 
								stripEmailDomain: false,
 | 
				
			||||||
 | 
								want:             nil,
 | 
				
			||||||
 | 
								wantErr:          true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "minimal-machine",
 | 
				
			||||||
 | 
								machine: types.Machine{
 | 
				
			||||||
 | 
									ID:         0,
 | 
				
			||||||
 | 
									MachineKey: "mkey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507",
 | 
				
			||||||
 | 
									NodeKey:    "nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe",
 | 
				
			||||||
 | 
									DiscoKey:   "discokey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084",
 | 
				
			||||||
 | 
									IPAddresses: []netip.Addr{
 | 
				
			||||||
 | 
										netip.MustParseAddr("100.64.0.1"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Hostname:  "mini",
 | 
				
			||||||
 | 
									GivenName: "mini",
 | 
				
			||||||
 | 
									UserID:    0,
 | 
				
			||||||
 | 
									User: types.User{
 | 
				
			||||||
 | 
										Name: "mini",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									ForcedTags: []string{},
 | 
				
			||||||
 | 
									AuthKeyID:  0,
 | 
				
			||||||
 | 
									AuthKey:    &types.PreAuthKey{},
 | 
				
			||||||
 | 
									LastSeen:   &lastSeen,
 | 
				
			||||||
 | 
									Expiry:     &expire,
 | 
				
			||||||
 | 
									HostInfo:   types.HostInfo{},
 | 
				
			||||||
 | 
									Endpoints:  []string{},
 | 
				
			||||||
 | 
									Routes: []types.Route{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Prefix:     types.IPPrefix(netip.MustParsePrefix("0.0.0.0/0")),
 | 
				
			||||||
 | 
											Advertised: true,
 | 
				
			||||||
 | 
											Enabled:    true,
 | 
				
			||||||
 | 
											IsPrimary:  false,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Prefix:     types.IPPrefix(netip.MustParsePrefix("192.168.0.0/24")),
 | 
				
			||||||
 | 
											Advertised: true,
 | 
				
			||||||
 | 
											Enabled:    true,
 | 
				
			||||||
 | 
											IsPrimary:  true,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									CreatedAt: created,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								pol:              &policy.ACLPolicy{},
 | 
				
			||||||
 | 
								dnsConfig:        &tailcfg.DNSConfig{},
 | 
				
			||||||
 | 
								baseDomain:       "",
 | 
				
			||||||
 | 
								stripEmailDomain: false,
 | 
				
			||||||
 | 
								want: &tailcfg.Node{
 | 
				
			||||||
 | 
									ID:       0,
 | 
				
			||||||
 | 
									StableID: "0",
 | 
				
			||||||
 | 
									Name:     "mini",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									User: 0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									Key: mustNK(
 | 
				
			||||||
 | 
										"nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe",
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									KeyExpiry: expire,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									Machine: mustMK(
 | 
				
			||||||
 | 
										"mkey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507",
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									DiscoKey: mustDK(
 | 
				
			||||||
 | 
										"discokey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084",
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									Addresses: []netip.Prefix{netip.MustParsePrefix("100.64.0.1/32")},
 | 
				
			||||||
 | 
									AllowedIPs: []netip.Prefix{
 | 
				
			||||||
 | 
										netip.MustParsePrefix("100.64.0.1/32"),
 | 
				
			||||||
 | 
										netip.MustParsePrefix("0.0.0.0/0"),
 | 
				
			||||||
 | 
										netip.MustParsePrefix("192.168.0.0/24"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Endpoints: []string{},
 | 
				
			||||||
 | 
									DERP:      "127.3.3.40:0",
 | 
				
			||||||
 | 
									Hostinfo:  hiview(tailcfg.Hostinfo{}),
 | 
				
			||||||
 | 
									Created:   created,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									Tags: []string{},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									PrimaryRoutes: []netip.Prefix{
 | 
				
			||||||
 | 
										netip.MustParsePrefix("192.168.0.0/24"),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									LastSeen:          &lastSeen,
 | 
				
			||||||
 | 
									Online:            new(bool),
 | 
				
			||||||
 | 
									KeepAlive:         true,
 | 
				
			||||||
 | 
									MachineAuthorized: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									Capabilities: []string{
 | 
				
			||||||
 | 
										tailcfg.CapabilityFileSharing,
 | 
				
			||||||
 | 
										tailcfg.CapabilityAdmin,
 | 
				
			||||||
 | 
										tailcfg.CapabilitySSH,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							// TODO: Add tests to check other aspects of the node conversion:
 | 
				
			||||||
 | 
							// - With tags and policy
 | 
				
			||||||
 | 
							// - dnsconfig and basedomain
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								got, err := tailNode(
 | 
				
			||||||
 | 
									tt.machine,
 | 
				
			||||||
 | 
									tt.pol,
 | 
				
			||||||
 | 
									tt.dnsConfig,
 | 
				
			||||||
 | 
									tt.baseDomain,
 | 
				
			||||||
 | 
									tt.stripEmailDomain,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (err != nil) != tt.wantErr {
 | 
				
			||||||
 | 
									t.Errorf("tailNode() error = %v, wantErr %v", err, tt.wantErr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if diff := cmp.Diff(tt.want, got, cmpopts.EquateEmpty()); diff != "" {
 | 
				
			||||||
 | 
									t.Errorf("tailNode() unexpected result (-want +got):\n%s", diff)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1332,7 +1332,7 @@ func Test_expandAlias(t *testing.T) {
 | 
				
			|||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if diff := cmp.Diff(test.want, got); diff != "" {
 | 
								if diff := cmp.Diff(test.want, got); diff != "" {
 | 
				
			||||||
				t.Errorf("expandAlias() = %v, want %v", got, test.want)
 | 
									t.Errorf("expandAlias() unexpected result (-want +got):\n%s", diff)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -1711,7 +1711,7 @@ func TestACLPolicy_generateFilterRules(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			if diff := cmp.Diff(tt.want, got); diff != "" {
 | 
								if diff := cmp.Diff(tt.want, got); diff != "" {
 | 
				
			||||||
				log.Trace().Interface("got", got).Msg("result")
 | 
									log.Trace().Interface("got", got).Msg("result")
 | 
				
			||||||
				t.Errorf("ACLgenerateFilterRules() = %v, want %v", got, tt.want)
 | 
									t.Errorf("ACLgenerateFilterRules() unexpected result (-want +got):\n%s", diff)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -516,7 +516,7 @@ func (h *Headscale) handleAuthKeyCommon(
 | 
				
			|||||||
		Str("func", "handleAuthKeyCommon").
 | 
							Str("func", "handleAuthKeyCommon").
 | 
				
			||||||
		Bool("noise", isNoise).
 | 
							Bool("noise", isNoise).
 | 
				
			||||||
		Str("machine", registerRequest.Hostinfo.Hostname).
 | 
							Str("machine", registerRequest.Hostinfo.Hostname).
 | 
				
			||||||
		Str("ips", strings.Join(machine.IPAddresses.ToStringSlice(), ", ")).
 | 
							Str("ips", strings.Join(machine.IPAddresses.StringSlice(), ", ")).
 | 
				
			||||||
		Msg("Successfully authenticated via AuthKey")
 | 
							Msg("Successfully authenticated via AuthKey")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -10,17 +10,23 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
 | 
						v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
 | 
				
			||||||
	"github.com/juanfont/headscale/hscontrol/policy/matcher"
 | 
						"github.com/juanfont/headscale/hscontrol/policy/matcher"
 | 
				
			||||||
 | 
						"github.com/juanfont/headscale/hscontrol/util"
 | 
				
			||||||
	"go4.org/netipx"
 | 
						"go4.org/netipx"
 | 
				
			||||||
	"google.golang.org/protobuf/types/known/timestamppb"
 | 
						"google.golang.org/protobuf/types/known/timestamppb"
 | 
				
			||||||
	"tailscale.com/tailcfg"
 | 
						"tailscale.com/tailcfg"
 | 
				
			||||||
 | 
						"tailscale.com/types/key"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	// TODO(kradalby): Move out of here when we got circdeps under control.
 | 
						// TODO(kradalby): Move out of here when we got circdeps under control.
 | 
				
			||||||
	keepAliveInterval = 60 * time.Second
 | 
						keepAliveInterval = 60 * time.Second
 | 
				
			||||||
 | 
						MaxHostnameLength = 255
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var ErrMachineAddressesInvalid = errors.New("failed to parse machine addresses")
 | 
					var (
 | 
				
			||||||
 | 
						ErrMachineAddressesInvalid = errors.New("failed to parse machine addresses")
 | 
				
			||||||
 | 
						ErrHostnameTooLong         = errors.New("hostname too long")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Machine is a Headscale client.
 | 
					// Machine is a Headscale client.
 | 
				
			||||||
type Machine struct {
 | 
					type Machine struct {
 | 
				
			||||||
@ -73,7 +79,7 @@ type (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
type MachineAddresses []netip.Addr
 | 
					type MachineAddresses []netip.Addr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ma MachineAddresses) ToStringSlice() []string {
 | 
					func (ma MachineAddresses) StringSlice() []string {
 | 
				
			||||||
	strSlice := make([]string, 0, len(ma))
 | 
						strSlice := make([]string, 0, len(ma))
 | 
				
			||||||
	for _, addr := range ma {
 | 
						for _, addr := range ma {
 | 
				
			||||||
		strSlice = append(strSlice, addr.String())
 | 
							strSlice = append(strSlice, addr.String())
 | 
				
			||||||
@ -125,7 +131,7 @@ func (ma *MachineAddresses) Scan(destination interface{}) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Value return json value, implement driver.Valuer interface.
 | 
					// Value return json value, implement driver.Valuer interface.
 | 
				
			||||||
func (ma MachineAddresses) Value() (driver.Value, error) {
 | 
					func (ma MachineAddresses) Value() (driver.Value, error) {
 | 
				
			||||||
	addresses := strings.Join(ma.ToStringSlice(), ",")
 | 
						addresses := strings.Join(ma.StringSlice(), ",")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return addresses, nil
 | 
						return addresses, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -201,7 +207,7 @@ func (machine *Machine) Proto() *v1.Machine {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		NodeKey:     machine.NodeKey,
 | 
							NodeKey:     machine.NodeKey,
 | 
				
			||||||
		DiscoKey:    machine.DiscoKey,
 | 
							DiscoKey:    machine.DiscoKey,
 | 
				
			||||||
		IpAddresses: machine.IPAddresses.ToStringSlice(),
 | 
							IpAddresses: machine.IPAddresses.StringSlice(),
 | 
				
			||||||
		Name:        machine.Hostname,
 | 
							Name:        machine.Hostname,
 | 
				
			||||||
		GivenName:   machine.GivenName,
 | 
							GivenName:   machine.GivenName,
 | 
				
			||||||
		User:        machine.User.Proto(),
 | 
							User:        machine.User.Proto(),
 | 
				
			||||||
@ -240,6 +246,70 @@ func (machine *Machine) GetHostInfo() tailcfg.Hostinfo {
 | 
				
			|||||||
	return tailcfg.Hostinfo(machine.HostInfo)
 | 
						return tailcfg.Hostinfo(machine.HostInfo)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (machine *Machine) GetFQDN(dnsConfig *tailcfg.DNSConfig, baseDomain string) (string, error) {
 | 
				
			||||||
 | 
						var hostname string
 | 
				
			||||||
 | 
						if dnsConfig != nil && dnsConfig.Proxied { // MagicDNS
 | 
				
			||||||
 | 
							hostname = fmt.Sprintf(
 | 
				
			||||||
 | 
								"%s.%s.%s",
 | 
				
			||||||
 | 
								machine.GivenName,
 | 
				
			||||||
 | 
								machine.User.Name,
 | 
				
			||||||
 | 
								baseDomain,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							if len(hostname) > MaxHostnameLength {
 | 
				
			||||||
 | 
								return "", fmt.Errorf(
 | 
				
			||||||
 | 
									"hostname %q is too long it cannot except 255 ASCII chars: %w",
 | 
				
			||||||
 | 
									hostname,
 | 
				
			||||||
 | 
									ErrHostnameTooLong,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							hostname = machine.GivenName
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return hostname, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (machine *Machine) MachinePublicKey() (key.MachinePublic, error) {
 | 
				
			||||||
 | 
						var machineKey key.MachinePublic
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if machine.MachineKey != "" {
 | 
				
			||||||
 | 
							err := machineKey.UnmarshalText(
 | 
				
			||||||
 | 
								[]byte(util.MachinePublicKeyEnsurePrefix(machine.MachineKey)),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return key.MachinePublic{}, fmt.Errorf("failed to parse machine public key: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return machineKey, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (machine *Machine) DiscoPublicKey() (key.DiscoPublic, error) {
 | 
				
			||||||
 | 
						var discoKey key.DiscoPublic
 | 
				
			||||||
 | 
						if machine.DiscoKey != "" {
 | 
				
			||||||
 | 
							err := discoKey.UnmarshalText(
 | 
				
			||||||
 | 
								[]byte(util.DiscoPublicKeyEnsurePrefix(machine.DiscoKey)),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return key.DiscoPublic{}, fmt.Errorf("failed to parse disco public key: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							discoKey = key.DiscoPublic{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return discoKey, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (machine *Machine) NodePublicKey() (key.NodePublic, error) {
 | 
				
			||||||
 | 
						var nodeKey key.NodePublic
 | 
				
			||||||
 | 
						err := nodeKey.UnmarshalText([]byte(util.NodePublicKeyEnsurePrefix(machine.NodeKey)))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return key.NodePublic{}, fmt.Errorf("failed to parse node public key: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nodeKey, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (machine Machine) String() string {
 | 
					func (machine Machine) String() string {
 | 
				
			||||||
	return machine.Hostname
 | 
						return machine.Hostname
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user