Use tailscale key types instead of strings (#1609)

* upgrade tailscale

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* make Node object use actualy tailscale key types

This commit changes the Node struct to have both a field for strings
to store the keys in the database and a dedicated Key for each type
of key.

The keys are populated and stored with Gorm hooks to ensure the data
is stored in the db.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* use key types throughout the code

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* make sure machinekey is concistently used

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* use machine key in auth url

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* fix web register

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* use key type in notifier

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* fix relogin with webauth

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
Kristoffer Dalby 2023-11-19 22:37:04 +01:00 committed by GitHub
parent c0fd06e3f5
commit ed4e19996b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 550 additions and 471 deletions

View file

@ -13,6 +13,7 @@ import (
"github.com/juanfont/headscale/hscontrol/policy/matcher"
"go4.org/netipx"
"google.golang.org/protobuf/types/known/timestamppb"
"gorm.io/gorm"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
)
@ -24,10 +25,30 @@ var (
// Node is a Headscale client.
type Node struct {
ID uint64 `gorm:"primary_key"`
MachineKey string `gorm:"type:varchar(64);unique_index"`
NodeKey string
DiscoKey string
ID uint64 `gorm:"primary_key"`
// MachineKeyValue is the string representation of MachineKey
// it is _only_ used for reading and writing the key to the
// database and should not be used.
// Use MachineKey instead.
MachineKeyValue string `gorm:"column:machine_key;unique_index"`
// NodeKeyValue is the string representation of NodeKey
// it is _only_ used for reading and writing the key to the
// database and should not be used.
// Use NodeKey instead.
NodeKeyValue string `gorm:"column:node_key"`
// DiscoKeyValue is the string representation of DiscoKey
// it is _only_ used for reading and writing the key to the
// database and should not be used.
// Use DiscoKey instead.
DiscoKeyValue string `gorm:"column:disco_key"`
MachineKey key.MachinePublic `gorm:"-"`
NodeKey key.NodePublic `gorm:"-"`
DiscoKey key.DiscoPublic `gorm:"-"`
IPAddresses NodeAddresses
// Hostname represents the name given by the Tailscale
@ -174,6 +195,31 @@ func (node Node) IsExpired() bool {
return time.Now().UTC().After(*node.Expiry)
}
// TODO(kradalby): Try to replace the types in the DB to be correct.
func (node *Node) EndpointsToAddrPort() ([]netip.AddrPort, error) {
var ret []netip.AddrPort
for _, ep := range node.Endpoints {
addrPort, err := netip.ParseAddrPort(ep)
if err != nil {
return nil, err
}
ret = append(ret, addrPort)
}
return ret, nil
}
// TODO(kradalby): Try to replace the types in the DB to be correct.
func (node *Node) SetEndpointsFromAddrPorts(in []netip.AddrPort) {
var strs StringList
for _, addrPort := range in {
strs = append(strs, addrPort.String())
}
node.Endpoints = strs
}
// IsOnline returns if the node is connected to Headscale.
// This is really a naive implementation, as we don't really see
// if there is a working connection between the client and the server.
@ -226,13 +272,52 @@ func (nodes Nodes) FilterByIP(ip netip.Addr) Nodes {
return found
}
// BeforeSave is a hook that ensures that some values that
// cannot be directly marshalled into database values are stored
// correctly in the database.
// This currently means storing the keys as strings.
func (n *Node) BeforeSave(tx *gorm.DB) (err error) {
n.MachineKeyValue = n.MachineKey.String()
n.NodeKeyValue = n.NodeKey.String()
n.DiscoKeyValue = n.DiscoKey.String()
return
}
// AfterFind is a hook that ensures that Node objects fields that
// has a different type in the database is unwrapped and populated
// correctly.
// This currently unmarshals all the keys, stored as strings, into
// the proper types.
func (n *Node) AfterFind(tx *gorm.DB) (err error) {
var machineKey key.MachinePublic
if err := machineKey.UnmarshalText([]byte(n.MachineKeyValue)); err != nil {
return err
}
n.MachineKey = machineKey
var nodeKey key.NodePublic
if err := nodeKey.UnmarshalText([]byte(n.NodeKeyValue)); err != nil {
return err
}
n.NodeKey = nodeKey
var discoKey key.DiscoPublic
if err := discoKey.UnmarshalText([]byte(n.DiscoKeyValue)); err != nil {
return err
}
n.DiscoKey = discoKey
return
}
func (node *Node) Proto() *v1.Node {
nodeProto := &v1.Node{
Id: node.ID,
MachineKey: node.MachineKey,
MachineKey: node.MachineKey.String(),
NodeKey: node.NodeKey,
DiscoKey: node.DiscoKey,
NodeKey: node.NodeKey.String(),
DiscoKey: node.DiscoKey.String(),
IpAddresses: node.IPAddresses.StringSlice(),
Name: node.Hostname,
GivenName: node.GivenName,
@ -289,47 +374,6 @@ func (node *Node) GetFQDN(dnsConfig *tailcfg.DNSConfig, baseDomain string) (stri
return hostname, nil
}
func (node *Node) MachinePublicKey() (key.MachinePublic, error) {
var machineKey key.MachinePublic
if node.MachineKey != "" {
err := machineKey.UnmarshalText(
[]byte(node.MachineKey),
)
if err != nil {
return key.MachinePublic{}, fmt.Errorf("failed to parse machine public key: %w", err)
}
}
return machineKey, nil
}
func (node *Node) DiscoPublicKey() (key.DiscoPublic, error) {
var discoKey key.DiscoPublic
if node.DiscoKey != "" {
err := discoKey.UnmarshalText(
[]byte(node.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 (node *Node) NodePublicKey() (key.NodePublic, error) {
var nodeKey key.NodePublic
err := nodeKey.UnmarshalText([]byte(node.NodeKey))
if err != nil {
return key.NodePublic{}, fmt.Errorf("failed to parse node public key: %w", err)
}
return nodeKey, nil
}
func (node Node) String() string {
return node.Hostname
}