Skip to content

Commit

Permalink
remove DB dependency of tailNode conversion, add test
Browse files Browse the repository at this point in the history
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
  • Loading branch information
kradalby committed Jun 8, 2023
1 parent bce8427 commit 5bad48a
Show file tree
Hide file tree
Showing 8 changed files with 460 additions and 219 deletions.
174 changes: 1 addition & 173 deletions hscontrol/db/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,20 @@ import (
"fmt"
"net/netip"
"sort"
"strconv"
"strings"
"time"

"github.com/juanfont/headscale/hscontrol/policy"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/patrickmn/go-cache"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
"gorm.io/gorm"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
)

const (
MachineGivenNameHashLength = 8
MachineGivenNameTrimSize = 2
MaxHostnameLength = 255
)

var (
Expand All @@ -33,7 +28,6 @@ var (
"machine not found in registration cache",
)
ErrCouldNotConvertMachineInterface = errors.New("failed to convert machine interface")
ErrHostnameTooLong = errors.New("hostname too long")
ErrDifferentRegisteredUser = errors.New(
"machine was previously registered with a different user",
)
Expand Down Expand Up @@ -471,7 +465,7 @@ func (hsdb *HSDatabase) RegisterMachine(machine types.Machine,
log.Trace().
Caller().
Str("machine", machine.Hostname).
Str("ip", strings.Join(ips.ToStringSlice(), ",")).
Str("ip", strings.Join(ips.StringSlice(), ",")).
Msg("Machine registered with the database")

return &machine, nil
Expand Down Expand Up @@ -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
}
85 changes: 47 additions & 38 deletions hscontrol/mapper/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,38 +69,54 @@ func NewMapper(
}
}

func (m Mapper) fullMapResponse(
func (m *Mapper) tempWrap(
mapRequest tailcfg.MapRequest,
machine *types.Machine,
pol *policy.ACLPolicy,
) (*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)
peers, err := m.db.ListPeers(machine)
if err != nil {
log.Error().
Caller().
Err(err).
Msg("Cannot convert to node")
Msg("Cannot fetch peers")

return nil, err
}

peers, err := m.db.ListPeers(machine)
if err != nil {
log.Error().
Caller().
Err(err).
Msg("Cannot fetch peers")
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, m.stripEmailDomain)
rules, sshPolicy, err := policy.GenerateFilterRules(pol, peers, stripEmailDomain)
if err != nil {
return nil, err
}
Expand All @@ -109,38 +125,31 @@ func (m Mapper) fullMapResponse(
peers = policy.FilterMachinesByACL(machine, peers, rules)
}

profiles := generateUserProfiles(machine, peers, m.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
}
profiles := generateUserProfiles(machine, peers, baseDomain)

// TODO(kradalby): Shold this mutation happen before TailNode(s) is called?
dnsConfig := generateDNSConfig(
m.dnsCfg,
m.baseDomain,
dnsCfg,
baseDomain,
*machine,
peers,
)

tailPeers, err := tailNodes(peers, pol, dnsCfg, baseDomain, stripEmailDomain)
if err != nil {
return nil, err
}

now := time.Now()

resp := tailcfg.MapResponse{
KeepAlive: false,
Node: node,
Node: tailnode,

// TODO: Only send if updated
DERPMap: m.derpMap,
DERPMap: derpMap,

// TODO: Only send if updated
Peers: nodePeers,
Peers: tailPeers,

// TODO(kradalby): Implement:
// https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L1351-L1374
Expand All @@ -154,7 +163,7 @@ func (m Mapper) fullMapResponse(
DNSConfig: dnsConfig,

// TODO: Only send if updated
Domain: m.baseDomain,
Domain: baseDomain,

// Do not instruct clients to collect services, we do not
// support or do anything with them
Expand All @@ -171,8 +180,8 @@ func (m Mapper) fullMapResponse(
ControlTime: &now,

Debug: &tailcfg.Debug{
DisableLogTail: !m.logtail,
RandomizeClientPort: m.randomClientPort,
DisableLogTail: !logtail,
RandomizeClientPort: randomClientPort,
},
}

Expand Down Expand Up @@ -283,7 +292,7 @@ func (m Mapper) CreateMapResponse(
machine *types.Machine,
pol *policy.ACLPolicy,
) ([]byte, error) {
mapResponse, err := m.fullMapResponse(mapRequest, machine, pol)
mapResponse, err := m.tempWrap(mapRequest, machine, pol)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion hscontrol/mapper/mapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func TestDNSConfigMapResponse(t *testing.T) {
)

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)
}
})
}
Expand Down
Loading

0 comments on commit 5bad48a

Please sign in to comment.