mirror of
https://github.com/juanfont/headscale.git
synced 2025-08-11 14:47:36 +00:00
Rename Machine to Node (#1553)
This commit is contained in:
@@ -220,16 +220,16 @@ func (h *Headscale) redirect(w http.ResponseWriter, req *http.Request) {
|
||||
http.Redirect(w, req, target, http.StatusFound)
|
||||
}
|
||||
|
||||
// expireEphemeralNodes deletes ephemeral machine records that have not been
|
||||
// expireEphemeralNodes deletes ephemeral node records that have not been
|
||||
// seen for longer than h.cfg.EphemeralNodeInactivityTimeout.
|
||||
func (h *Headscale) expireEphemeralNodes(milliSeconds int64) {
|
||||
ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
|
||||
for range ticker.C {
|
||||
h.db.ExpireEphemeralMachines(h.cfg.EphemeralNodeInactivityTimeout)
|
||||
h.db.ExpireEphemeralNodes(h.cfg.EphemeralNodeInactivityTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
// expireExpiredMachines expires machines that have an explicit expiry set
|
||||
// expireExpiredMachines expires nodes that have an explicit expiry set
|
||||
// after that expiry time has passed.
|
||||
func (h *Headscale) expireExpiredMachines(intervalMs int64) {
|
||||
interval := time.Duration(intervalMs) * time.Millisecond
|
||||
@@ -238,7 +238,7 @@ func (h *Headscale) expireExpiredMachines(intervalMs int64) {
|
||||
lastCheck := time.Unix(0, 0)
|
||||
|
||||
for range ticker.C {
|
||||
lastCheck = h.db.ExpireExpiredMachines(lastCheck)
|
||||
lastCheck = h.db.ExpireExpiredNodes(lastCheck)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -27,9 +27,9 @@ func (h *Headscale) handleRegister(
|
||||
isNoise bool,
|
||||
) {
|
||||
now := time.Now().UTC()
|
||||
machine, err := h.db.GetMachineByAnyKey(machineKey, registerRequest.NodeKey, registerRequest.OldNodeKey)
|
||||
node, err := h.db.GetNodeByAnyKey(machineKey, registerRequest.NodeKey, registerRequest.OldNodeKey)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// If the machine has AuthKey set, handle registration via PreAuthKeys
|
||||
// If the node has AuthKey set, handle registration via PreAuthKeys
|
||||
if registerRequest.Auth.AuthKey != "" {
|
||||
h.handleAuthKey(writer, registerRequest, machineKey, isNoise)
|
||||
|
||||
@@ -40,7 +40,7 @@ func (h *Headscale) handleRegister(
|
||||
//
|
||||
// TODO(juan): We could use this field to improve our protocol implementation,
|
||||
// and hold the request until the client closes it, or the interactive
|
||||
// login is completed (i.e., the user registers the machine).
|
||||
// login is completed (i.e., the user registers the node).
|
||||
// This is not implemented yet, as it is no strictly required. The only side-effect
|
||||
// is that the client will hammer headscale with requests until it gets a
|
||||
// successful RegisterResponse.
|
||||
@@ -48,19 +48,19 @@ func (h *Headscale) handleRegister(
|
||||
if _, ok := h.registrationCache.Get(util.NodePublicKeyStripPrefix(registerRequest.NodeKey)); ok {
|
||||
log.Debug().
|
||||
Caller().
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("node", registerRequest.Hostinfo.Hostname).
|
||||
Str("machine_key", machineKey.ShortString()).
|
||||
Str("node_key", registerRequest.NodeKey.ShortString()).
|
||||
Str("node_key_old", registerRequest.OldNodeKey.ShortString()).
|
||||
Str("follow_up", registerRequest.Followup).
|
||||
Bool("noise", isNoise).
|
||||
Msg("Machine is waiting for interactive login")
|
||||
Msg("Node is waiting for interactive login")
|
||||
|
||||
select {
|
||||
case <-req.Context().Done():
|
||||
return
|
||||
case <-time.After(registrationHoldoff):
|
||||
h.handleNewMachine(writer, registerRequest, machineKey, isNoise)
|
||||
h.handleNewNode(writer, registerRequest, machineKey, isNoise)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -69,13 +69,13 @@ func (h *Headscale) handleRegister(
|
||||
|
||||
log.Info().
|
||||
Caller().
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("node", registerRequest.Hostinfo.Hostname).
|
||||
Str("machine_key", machineKey.ShortString()).
|
||||
Str("node_key", registerRequest.NodeKey.ShortString()).
|
||||
Str("node_key_old", registerRequest.OldNodeKey.ShortString()).
|
||||
Str("follow_up", registerRequest.Followup).
|
||||
Bool("noise", isNoise).
|
||||
Msg("New machine not yet in the database")
|
||||
Msg("New node not yet in the database")
|
||||
|
||||
givenName, err := h.db.GenerateGivenName(
|
||||
machineKey.String(),
|
||||
@@ -92,11 +92,11 @@ func (h *Headscale) handleRegister(
|
||||
return
|
||||
}
|
||||
|
||||
// The machine did not have a key to authenticate, which means
|
||||
// The node did not have a key to authenticate, which means
|
||||
// that we rely on a method that calls back some how (OpenID or CLI)
|
||||
// We create the machine and then keep it around until a callback
|
||||
// We create the node and then keep it around until a callback
|
||||
// happens
|
||||
newMachine := types.Machine{
|
||||
newNode := types.Node{
|
||||
MachineKey: util.MachinePublicKeyStripPrefix(machineKey),
|
||||
Hostname: registerRequest.Hostinfo.Hostname,
|
||||
GivenName: givenName,
|
||||
@@ -109,41 +109,41 @@ func (h *Headscale) handleRegister(
|
||||
log.Trace().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("node", registerRequest.Hostinfo.Hostname).
|
||||
Time("expiry", registerRequest.Expiry).
|
||||
Msg("Non-zero expiry time requested")
|
||||
newMachine.Expiry = ®isterRequest.Expiry
|
||||
newNode.Expiry = ®isterRequest.Expiry
|
||||
}
|
||||
|
||||
h.registrationCache.Set(
|
||||
newMachine.NodeKey,
|
||||
newMachine,
|
||||
newNode.NodeKey,
|
||||
newNode,
|
||||
registerCacheExpiration,
|
||||
)
|
||||
|
||||
h.handleNewMachine(writer, registerRequest, machineKey, isNoise)
|
||||
h.handleNewNode(writer, registerRequest, machineKey, isNoise)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// The machine is already in the DB. This could mean one of the following:
|
||||
// - The machine is authenticated and ready to /map
|
||||
// The node is already in the DB. This could mean one of the following:
|
||||
// - The node is authenticated and ready to /map
|
||||
// - We are doing a key refresh
|
||||
// - The machine is logged out (or expired) and pending to be authorized. TODO(juan): We need to keep alive the connection here
|
||||
if machine != nil {
|
||||
// - The node is logged out (or expired) and pending to be authorized. TODO(juan): We need to keep alive the connection here
|
||||
if node != nil {
|
||||
// (juan): For a while we had a bug where we were not storing the MachineKey for the nodes using the TS2021,
|
||||
// due to a misunderstanding of the protocol https://github.com/juanfont/headscale/issues/1054
|
||||
// So if we have a not valid MachineKey (but we were able to fetch the machine with the NodeKeys), we update it.
|
||||
// So if we have a not valid MachineKey (but we were able to fetch the node with the NodeKeys), we update it.
|
||||
var storedMachineKey key.MachinePublic
|
||||
err = storedMachineKey.UnmarshalText(
|
||||
[]byte(util.MachinePublicKeyEnsurePrefix(machine.MachineKey)),
|
||||
[]byte(util.MachinePublicKeyEnsurePrefix(node.MachineKey)),
|
||||
)
|
||||
if err != nil || storedMachineKey.IsZero() {
|
||||
if err := h.db.MachineSetMachineKey(machine, machineKey); err != nil {
|
||||
if err := h.db.NodeSetMachineKey(node, machineKey); err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "RegistrationHandler").
|
||||
Str("machine", machine.Hostname).
|
||||
Str("node", node.Hostname).
|
||||
Err(err).
|
||||
Msg("Error saving machine key to database")
|
||||
|
||||
@@ -154,34 +154,34 @@ func (h *Headscale) handleRegister(
|
||||
// If the NodeKey stored in headscale is the same as the key presented in a registration
|
||||
// request, then we have a node that is either:
|
||||
// - Trying to log out (sending a expiry in the past)
|
||||
// - A valid, registered machine, looking for /map
|
||||
// - Expired machine wanting to reauthenticate
|
||||
if machine.NodeKey == util.NodePublicKeyStripPrefix(registerRequest.NodeKey) {
|
||||
// - A valid, registered node, looking for /map
|
||||
// - Expired node wanting to reauthenticate
|
||||
if node.NodeKey == util.NodePublicKeyStripPrefix(registerRequest.NodeKey) {
|
||||
// The client sends an Expiry in the past if the client is requesting to expire the key (aka logout)
|
||||
// https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L648
|
||||
if !registerRequest.Expiry.IsZero() &&
|
||||
registerRequest.Expiry.UTC().Before(now) {
|
||||
h.handleMachineLogOut(writer, *machine, machineKey, isNoise)
|
||||
h.handleNodeLogOut(writer, *node, machineKey, isNoise)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If machine is not expired, and it is register, we have a already accepted this machine,
|
||||
// If node is not expired, and it is register, we have a already accepted this node,
|
||||
// let it proceed with a valid registration
|
||||
if !machine.IsExpired() {
|
||||
h.handleMachineWithValidRegistration(writer, *machine, machineKey, isNoise)
|
||||
if !node.IsExpired() {
|
||||
h.handleNodeWithValidRegistration(writer, *node, machineKey, isNoise)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// The NodeKey we have matches OldNodeKey, which means this is a refresh after a key expiration
|
||||
if machine.NodeKey == util.NodePublicKeyStripPrefix(registerRequest.OldNodeKey) &&
|
||||
!machine.IsExpired() {
|
||||
h.handleMachineKeyRefresh(
|
||||
if node.NodeKey == util.NodePublicKeyStripPrefix(registerRequest.OldNodeKey) &&
|
||||
!node.IsExpired() {
|
||||
h.handleNodeKeyRefresh(
|
||||
writer,
|
||||
registerRequest,
|
||||
*machine,
|
||||
*node,
|
||||
machineKey,
|
||||
isNoise,
|
||||
)
|
||||
@@ -197,20 +197,20 @@ func (h *Headscale) handleRegister(
|
||||
}
|
||||
}
|
||||
|
||||
// The machine has expired or it is logged out
|
||||
h.handleMachineExpiredOrLoggedOut(writer, registerRequest, *machine, machineKey, isNoise)
|
||||
// The node has expired or it is logged out
|
||||
h.handleNodeExpiredOrLoggedOut(writer, registerRequest, *node, machineKey, isNoise)
|
||||
|
||||
// TODO(juan): RegisterRequest includes an Expiry time, that we could optionally use
|
||||
machine.Expiry = &time.Time{}
|
||||
node.Expiry = &time.Time{}
|
||||
|
||||
// If we are here it means the client needs to be reauthorized,
|
||||
// we need to make sure the NodeKey matches the one in the request
|
||||
// TODO(juan): What happens when using fast user switching between two
|
||||
// headscale-managed tailnets?
|
||||
machine.NodeKey = util.NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||
node.NodeKey = util.NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||
h.registrationCache.Set(
|
||||
util.NodePublicKeyStripPrefix(registerRequest.NodeKey),
|
||||
*machine,
|
||||
*node,
|
||||
registerCacheExpiration,
|
||||
)
|
||||
|
||||
@@ -231,7 +231,7 @@ func (h *Headscale) handleAuthKey(
|
||||
) {
|
||||
log.Debug().
|
||||
Caller().
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("node", registerRequest.Hostinfo.Hostname).
|
||||
Bool("noise", isNoise).
|
||||
Msgf("Processing auth key for %s", registerRequest.Hostinfo.Hostname)
|
||||
resp := tailcfg.RegisterResponse{}
|
||||
@@ -241,7 +241,7 @@ func (h *Headscale) handleAuthKey(
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("node", registerRequest.Hostinfo.Hostname).
|
||||
Err(err).
|
||||
Msg("Failed authentication via AuthKey")
|
||||
resp.MachineAuthorized = false
|
||||
@@ -251,11 +251,11 @@ func (h *Headscale) handleAuthKey(
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("node", registerRequest.Hostinfo.Hostname).
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
machineRegistrations.WithLabelValues("new", util.RegisterMethodAuthKey, "error", pak.User.Name).
|
||||
nodeRegistrations.WithLabelValues("new", util.RegisterMethodAuthKey, "error", pak.User.Name).
|
||||
Inc()
|
||||
|
||||
return
|
||||
@@ -275,14 +275,14 @@ func (h *Headscale) handleAuthKey(
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("node", registerRequest.Hostinfo.Hostname).
|
||||
Msg("Failed authentication via AuthKey")
|
||||
|
||||
if pak != nil {
|
||||
machineRegistrations.WithLabelValues("new", util.RegisterMethodAuthKey, "error", pak.User.Name).
|
||||
nodeRegistrations.WithLabelValues("new", util.RegisterMethodAuthKey, "error", pak.User.Name).
|
||||
Inc()
|
||||
} else {
|
||||
machineRegistrations.WithLabelValues("new", util.RegisterMethodAuthKey, "error", "unknown").Inc()
|
||||
nodeRegistrations.WithLabelValues("new", util.RegisterMethodAuthKey, "error", "unknown").Inc()
|
||||
}
|
||||
|
||||
return
|
||||
@@ -291,33 +291,33 @@ func (h *Headscale) handleAuthKey(
|
||||
log.Debug().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("node", registerRequest.Hostinfo.Hostname).
|
||||
Msg("Authentication key was valid, proceeding to acquire IP addresses")
|
||||
|
||||
nodeKey := util.NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||
|
||||
// retrieve machine information if it exist
|
||||
// retrieve node information if it exist
|
||||
// The error is not important, because if it does not
|
||||
// exist, then this is a new machine and we will move
|
||||
// exist, then this is a new node and we will move
|
||||
// on to registration.
|
||||
machine, _ := h.db.GetMachineByAnyKey(machineKey, registerRequest.NodeKey, registerRequest.OldNodeKey)
|
||||
if machine != nil {
|
||||
node, _ := h.db.GetNodeByAnyKey(machineKey, registerRequest.NodeKey, registerRequest.OldNodeKey)
|
||||
if node != nil {
|
||||
log.Trace().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("machine was already registered before, refreshing with new auth key")
|
||||
Str("node", node.Hostname).
|
||||
Msg("node was already registered before, refreshing with new auth key")
|
||||
|
||||
machine.NodeKey = nodeKey
|
||||
machine.AuthKeyID = uint(pak.ID)
|
||||
err := h.db.MachineSetExpiry(machine, registerRequest.Expiry)
|
||||
node.NodeKey = nodeKey
|
||||
node.AuthKeyID = uint(pak.ID)
|
||||
err := h.db.NodeSetExpiry(node, registerRequest.Expiry)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("node", node.Hostname).
|
||||
Err(err).
|
||||
Msg("Failed to refresh machine")
|
||||
Msg("Failed to refresh node")
|
||||
|
||||
return
|
||||
}
|
||||
@@ -325,16 +325,16 @@ func (h *Headscale) handleAuthKey(
|
||||
aclTags := pak.Proto().AclTags
|
||||
if len(aclTags) > 0 {
|
||||
// This conditional preserves the existing behaviour, although SaaS would reset the tags on auth-key login
|
||||
err = h.db.SetTags(machine, aclTags)
|
||||
err = h.db.SetTags(node, aclTags)
|
||||
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("node", node.Hostname).
|
||||
Strs("aclTags", aclTags).
|
||||
Err(err).
|
||||
Msg("Failed to set tags after refreshing machine")
|
||||
Msg("Failed to set tags after refreshing node")
|
||||
|
||||
return
|
||||
}
|
||||
@@ -355,7 +355,7 @@ func (h *Headscale) handleAuthKey(
|
||||
return
|
||||
}
|
||||
|
||||
machineToRegister := types.Machine{
|
||||
nodeToRegister := types.Node{
|
||||
Hostname: registerRequest.Hostinfo.Hostname,
|
||||
GivenName: givenName,
|
||||
UserID: pak.User.ID,
|
||||
@@ -368,16 +368,16 @@ func (h *Headscale) handleAuthKey(
|
||||
ForcedTags: pak.Proto().AclTags,
|
||||
}
|
||||
|
||||
machine, err = h.db.RegisterMachine(
|
||||
machineToRegister,
|
||||
node, err = h.db.RegisterNode(
|
||||
nodeToRegister,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Err(err).
|
||||
Msg("could not register machine")
|
||||
machineRegistrations.WithLabelValues("new", util.RegisterMethodAuthKey, "error", pak.User.Name).
|
||||
Msg("could not register node")
|
||||
nodeRegistrations.WithLabelValues("new", util.RegisterMethodAuthKey, "error", pak.User.Name).
|
||||
Inc()
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
@@ -392,7 +392,7 @@ func (h *Headscale) handleAuthKey(
|
||||
Bool("noise", isNoise).
|
||||
Err(err).
|
||||
Msg("Failed to use pre-auth key")
|
||||
machineRegistrations.WithLabelValues("new", util.RegisterMethodAuthKey, "error", pak.User.Name).
|
||||
nodeRegistrations.WithLabelValues("new", util.RegisterMethodAuthKey, "error", pak.User.Name).
|
||||
Inc()
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
@@ -410,16 +410,16 @@ func (h *Headscale) handleAuthKey(
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("node", registerRequest.Hostinfo.Hostname).
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
machineRegistrations.WithLabelValues("new", util.RegisterMethodAuthKey, "error", pak.User.Name).
|
||||
nodeRegistrations.WithLabelValues("new", util.RegisterMethodAuthKey, "error", pak.User.Name).
|
||||
Inc()
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
machineRegistrations.WithLabelValues("new", util.RegisterMethodAuthKey, "success", pak.User.Name).
|
||||
nodeRegistrations.WithLabelValues("new", util.RegisterMethodAuthKey, "success", pak.User.Name).
|
||||
Inc()
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
@@ -434,14 +434,14 @@ func (h *Headscale) handleAuthKey(
|
||||
|
||||
log.Info().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("ips", strings.Join(machine.IPAddresses.StringSlice(), ", ")).
|
||||
Str("node", registerRequest.Hostinfo.Hostname).
|
||||
Str("ips", strings.Join(node.IPAddresses.StringSlice(), ", ")).
|
||||
Msg("Successfully authenticated via AuthKey")
|
||||
}
|
||||
|
||||
// handleNewMachine exposes for both legacy and Noise the functionality to get a URL
|
||||
// for authorizing the machine. This url is then showed to the user by the local Tailscale client.
|
||||
func (h *Headscale) handleNewMachine(
|
||||
// handleNewNode exposes for both legacy and Noise the functionality to get a URL
|
||||
// for authorizing the node. This url is then showed to the user by the local Tailscale client.
|
||||
func (h *Headscale) handleNewNode(
|
||||
writer http.ResponseWriter,
|
||||
registerRequest tailcfg.RegisterRequest,
|
||||
machineKey key.MachinePublic,
|
||||
@@ -449,11 +449,11 @@ func (h *Headscale) handleNewMachine(
|
||||
) {
|
||||
resp := tailcfg.RegisterResponse{}
|
||||
|
||||
// The machine registration is new, redirect the client to the registration URL
|
||||
// The node registration is new, redirect the client to the registration URL
|
||||
log.Debug().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("node", registerRequest.Hostinfo.Hostname).
|
||||
Msg("The node seems to be new, sending auth url")
|
||||
|
||||
if h.oauth2Config != nil {
|
||||
@@ -495,13 +495,13 @@ func (h *Headscale) handleNewMachine(
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("AuthURL", resp.AuthURL).
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("node", registerRequest.Hostinfo.Hostname).
|
||||
Msg("Successfully sent auth url")
|
||||
}
|
||||
|
||||
func (h *Headscale) handleMachineLogOut(
|
||||
func (h *Headscale) handleNodeLogOut(
|
||||
writer http.ResponseWriter,
|
||||
machine types.Machine,
|
||||
node types.Node,
|
||||
machineKey key.MachinePublic,
|
||||
isNoise bool,
|
||||
) {
|
||||
@@ -509,17 +509,17 @@ func (h *Headscale) handleMachineLogOut(
|
||||
|
||||
log.Info().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("node", node.Hostname).
|
||||
Msg("Client requested logout")
|
||||
|
||||
now := time.Now()
|
||||
err := h.db.MachineSetExpiry(&machine, now)
|
||||
err := h.db.NodeSetExpiry(&node, now)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Err(err).
|
||||
Msg("Failed to expire machine")
|
||||
Msg("Failed to expire node")
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
@@ -528,7 +528,7 @@ func (h *Headscale) handleMachineLogOut(
|
||||
resp.AuthURL = ""
|
||||
resp.MachineAuthorized = false
|
||||
resp.NodeKeyExpired = true
|
||||
resp.User = *machine.User.TailscaleUser()
|
||||
resp.User = *node.User.TailscaleUser()
|
||||
respBody, err := mapper.MarshalResponse(resp, isNoise, h.privateKey2019, machineKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
@@ -554,13 +554,13 @@ func (h *Headscale) handleMachineLogOut(
|
||||
return
|
||||
}
|
||||
|
||||
if machine.IsEphemeral() {
|
||||
err = h.db.DeleteMachine(&machine)
|
||||
if node.IsEphemeral() {
|
||||
err = h.db.DeleteNode(&node)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Cannot delete ephemeral machine from the database")
|
||||
Str("node", node.Hostname).
|
||||
Msg("Cannot delete ephemeral node from the database")
|
||||
}
|
||||
|
||||
return
|
||||
@@ -569,29 +569,29 @@ func (h *Headscale) handleMachineLogOut(
|
||||
log.Info().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("node", node.Hostname).
|
||||
Msg("Successfully logged out")
|
||||
}
|
||||
|
||||
func (h *Headscale) handleMachineWithValidRegistration(
|
||||
func (h *Headscale) handleNodeWithValidRegistration(
|
||||
writer http.ResponseWriter,
|
||||
machine types.Machine,
|
||||
node types.Node,
|
||||
machineKey key.MachinePublic,
|
||||
isNoise bool,
|
||||
) {
|
||||
resp := tailcfg.RegisterResponse{}
|
||||
|
||||
// The machine registration is valid, respond with redirect to /map
|
||||
// The node registration is valid, respond with redirect to /map
|
||||
log.Debug().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("node", node.Hostname).
|
||||
Msg("Client is registered and we have the current NodeKey. All clear to /map")
|
||||
|
||||
resp.AuthURL = ""
|
||||
resp.MachineAuthorized = true
|
||||
resp.User = *machine.User.TailscaleUser()
|
||||
resp.Login = *machine.User.TailscaleLogin()
|
||||
resp.User = *node.User.TailscaleUser()
|
||||
resp.Login = *node.User.TailscaleLogin()
|
||||
|
||||
respBody, err := mapper.MarshalResponse(resp, isNoise, h.privateKey2019, machineKey)
|
||||
if err != nil {
|
||||
@@ -600,13 +600,13 @@ func (h *Headscale) handleMachineWithValidRegistration(
|
||||
Bool("noise", isNoise).
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
machineRegistrations.WithLabelValues("update", "web", "error", machine.User.Name).
|
||||
nodeRegistrations.WithLabelValues("update", "web", "error", node.User.Name).
|
||||
Inc()
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
machineRegistrations.WithLabelValues("update", "web", "success", machine.User.Name).
|
||||
nodeRegistrations.WithLabelValues("update", "web", "success", node.User.Name).
|
||||
Inc()
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
@@ -623,14 +623,14 @@ func (h *Headscale) handleMachineWithValidRegistration(
|
||||
log.Info().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Machine successfully authorized")
|
||||
Str("node", node.Hostname).
|
||||
Msg("Node successfully authorized")
|
||||
}
|
||||
|
||||
func (h *Headscale) handleMachineKeyRefresh(
|
||||
func (h *Headscale) handleNodeKeyRefresh(
|
||||
writer http.ResponseWriter,
|
||||
registerRequest tailcfg.RegisterRequest,
|
||||
machine types.Machine,
|
||||
node types.Node,
|
||||
machineKey key.MachinePublic,
|
||||
isNoise bool,
|
||||
) {
|
||||
@@ -639,10 +639,10 @@ func (h *Headscale) handleMachineKeyRefresh(
|
||||
log.Info().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("node", node.Hostname).
|
||||
Msg("We have the OldNodeKey in the database. This is a key refresh")
|
||||
|
||||
err := h.db.MachineSetNodeKey(&machine, registerRequest.NodeKey)
|
||||
err := h.db.NodeSetNodeKey(&node, registerRequest.NodeKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
@@ -654,7 +654,7 @@ func (h *Headscale) handleMachineKeyRefresh(
|
||||
}
|
||||
|
||||
resp.AuthURL = ""
|
||||
resp.User = *machine.User.TailscaleUser()
|
||||
resp.User = *node.User.TailscaleUser()
|
||||
respBody, err := mapper.MarshalResponse(resp, isNoise, h.privateKey2019, machineKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
@@ -683,14 +683,14 @@ func (h *Headscale) handleMachineKeyRefresh(
|
||||
Bool("noise", isNoise).
|
||||
Str("node_key", registerRequest.NodeKey.ShortString()).
|
||||
Str("old_node_key", registerRequest.OldNodeKey.ShortString()).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("node", node.Hostname).
|
||||
Msg("Node key successfully refreshed")
|
||||
}
|
||||
|
||||
func (h *Headscale) handleMachineExpiredOrLoggedOut(
|
||||
func (h *Headscale) handleNodeExpiredOrLoggedOut(
|
||||
writer http.ResponseWriter,
|
||||
registerRequest tailcfg.RegisterRequest,
|
||||
machine types.Machine,
|
||||
node types.Node,
|
||||
machineKey key.MachinePublic,
|
||||
isNoise bool,
|
||||
) {
|
||||
@@ -706,11 +706,11 @@ func (h *Headscale) handleMachineExpiredOrLoggedOut(
|
||||
log.Trace().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("node", node.Hostname).
|
||||
Str("machine_key", machineKey.ShortString()).
|
||||
Str("node_key", registerRequest.NodeKey.ShortString()).
|
||||
Str("node_key_old", registerRequest.OldNodeKey.ShortString()).
|
||||
Msg("Machine registration has expired or logged out. Sending a auth url to register")
|
||||
Msg("Node registration has expired or logged out. Sending a auth url to register")
|
||||
|
||||
if h.oauth2Config != nil {
|
||||
resp.AuthURL = fmt.Sprintf("%s/oidc/register/%s",
|
||||
@@ -729,13 +729,13 @@ func (h *Headscale) handleMachineExpiredOrLoggedOut(
|
||||
Bool("noise", isNoise).
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
machineRegistrations.WithLabelValues("reauth", "web", "error", machine.User.Name).
|
||||
nodeRegistrations.WithLabelValues("reauth", "web", "error", node.User.Name).
|
||||
Inc()
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
machineRegistrations.WithLabelValues("reauth", "web", "success", machine.User.Name).
|
||||
nodeRegistrations.WithLabelValues("reauth", "web", "success", node.User.Name).
|
||||
Inc()
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
@@ -755,6 +755,6 @@ func (h *Headscale) handleMachineExpiredOrLoggedOut(
|
||||
Str("machine_key", machineKey.ShortString()).
|
||||
Str("node_key", registerRequest.NodeKey.ShortString()).
|
||||
Str("node_key_old", registerRequest.OldNodeKey.ShortString()).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Machine logged out. Sent AuthURL for reauthentication")
|
||||
Str("node", node.Hostname).
|
||||
Msg("Node logged out. Sent AuthURL for reauthentication")
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ func (h *Headscale) RegistrationHandler(
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot parse machine key")
|
||||
machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc()
|
||||
nodeRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc()
|
||||
http.Error(writer, "Cannot parse machine key", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
@@ -51,7 +51,7 @@ func (h *Headscale) RegistrationHandler(
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot decode message")
|
||||
machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc()
|
||||
nodeRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc()
|
||||
http.Error(writer, "Cannot decode message", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
|
@@ -9,7 +9,7 @@ import (
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
// // NoiseRegistrationHandler handles the actual registration process of a machine.
|
||||
// // NoiseRegistrationHandler handles the actual registration process of a node.
|
||||
func (ns *noiseServer) NoiseRegistrationHandler(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
@@ -33,7 +33,7 @@ func (ns *noiseServer) NoiseRegistrationHandler(
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot parse RegisterRequest")
|
||||
machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc()
|
||||
nodeRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc()
|
||||
http.Error(writer, "Internal error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
|
@@ -17,8 +17,8 @@ import (
|
||||
|
||||
var ErrCouldNotAllocateIP = errors.New("could not find any suitable IP")
|
||||
|
||||
func (hsdb *HSDatabase) getAvailableIPs() (types.MachineAddresses, error) {
|
||||
var ips types.MachineAddresses
|
||||
func (hsdb *HSDatabase) getAvailableIPs() (types.NodeAddresses, error) {
|
||||
var ips types.NodeAddresses
|
||||
var err error
|
||||
for _, ipPrefix := range hsdb.ipPrefixes {
|
||||
var ip *netip.Addr
|
||||
@@ -69,11 +69,11 @@ func (hsdb *HSDatabase) getUsedIPs() (*netipx.IPSet, error) {
|
||||
// but this was quick to get running and it should be enough
|
||||
// to begin experimenting with a dual stack tailnet.
|
||||
var addressesSlices []string
|
||||
hsdb.db.Model(&types.Machine{}).Pluck("ip_addresses", &addressesSlices)
|
||||
hsdb.db.Model(&types.Node{}).Pluck("ip_addresses", &addressesSlices)
|
||||
|
||||
var ips netipx.IPSetBuilder
|
||||
for _, slice := range addressesSlices {
|
||||
var machineAddresses types.MachineAddresses
|
||||
var machineAddresses types.NodeAddresses
|
||||
err := machineAddresses.Scan(slice)
|
||||
if err != nil {
|
||||
return &netipx.IPSet{}, fmt.Errorf(
|
||||
|
@@ -30,21 +30,21 @@ func (s *Suite) TestGetUsedIps(c *check.C) {
|
||||
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachine("test", "testmachine")
|
||||
_, err = db.GetNode("test", "testnode")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machine := types.Machine{
|
||||
node := types.Node{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
Hostname: "testnode",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
IPAddresses: ips,
|
||||
}
|
||||
db.db.Save(&machine)
|
||||
db.db.Save(&node)
|
||||
|
||||
usedIps, err := db.getUsedIPs()
|
||||
|
||||
@@ -58,11 +58,11 @@ func (s *Suite) TestGetUsedIps(c *check.C) {
|
||||
c.Assert(usedIps.Equal(expectedIPSet), check.Equals, true)
|
||||
c.Assert(usedIps.Contains(expected), check.Equals, true)
|
||||
|
||||
machine1, err := db.GetMachineByID(0)
|
||||
node1, err := db.GetNodeByID(0)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
c.Assert(len(machine1.IPAddresses), check.Equals, 1)
|
||||
c.Assert(machine1.IPAddresses[0], check.Equals, expected)
|
||||
c.Assert(len(node1.IPAddresses), check.Equals, 1)
|
||||
c.Assert(node1.IPAddresses[0], check.Equals, expected)
|
||||
}
|
||||
|
||||
func (s *Suite) TestGetMultiIp(c *check.C) {
|
||||
@@ -78,21 +78,21 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
|
||||
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachine("test", "testmachine")
|
||||
_, err = db.GetNode("test", "testnode")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machine := types.Machine{
|
||||
node := types.Node{
|
||||
ID: uint64(index),
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
Hostname: "testnode",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
IPAddresses: ips,
|
||||
}
|
||||
db.db.Save(&machine)
|
||||
db.db.Save(&node)
|
||||
|
||||
db.ipAllocationMutex.Unlock()
|
||||
}
|
||||
@@ -119,20 +119,20 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
|
||||
c.Assert(usedIps.Contains(expected300), check.Equals, true)
|
||||
|
||||
// Check that we can read back the IPs
|
||||
machine1, err := db.GetMachineByID(1)
|
||||
node1, err := db.GetNodeByID(1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(machine1.IPAddresses), check.Equals, 1)
|
||||
c.Assert(len(node1.IPAddresses), check.Equals, 1)
|
||||
c.Assert(
|
||||
machine1.IPAddresses[0],
|
||||
node1.IPAddresses[0],
|
||||
check.Equals,
|
||||
netip.MustParseAddr("10.27.0.1"),
|
||||
)
|
||||
|
||||
machine50, err := db.GetMachineByID(50)
|
||||
node50, err := db.GetNodeByID(50)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(machine50.IPAddresses), check.Equals, 1)
|
||||
c.Assert(len(node50.IPAddresses), check.Equals, 1)
|
||||
c.Assert(
|
||||
machine50.IPAddresses[0],
|
||||
node50.IPAddresses[0],
|
||||
check.Equals,
|
||||
netip.MustParseAddr("10.27.0.50"),
|
||||
)
|
||||
@@ -153,7 +153,7 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
|
||||
c.Assert(nextIP2[0].String(), check.Equals, expectedNextIP.String())
|
||||
}
|
||||
|
||||
func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) {
|
||||
func (s *Suite) TestGetAvailableIpNodeWithoutIP(c *check.C) {
|
||||
ips, err := db.getAvailableIPs()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
@@ -168,20 +168,20 @@ func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) {
|
||||
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachine("test", "testmachine")
|
||||
_, err = db.GetNode("test", "testnode")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machine := types.Machine{
|
||||
node := types.Node{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
Hostname: "testnode",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
db.db.Save(&machine)
|
||||
db.db.Save(&node)
|
||||
|
||||
ips2, err := db.getAvailableIPs()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
@@ -78,49 +78,53 @@ func NewHeadscaleDatabase(
|
||||
|
||||
_ = dbConn.Migrator().RenameTable("namespaces", "users")
|
||||
|
||||
// the big rename from Machine to Node
|
||||
_ = dbConn.Migrator().RenameTable("machines", "nodes")
|
||||
_ = dbConn.Migrator().RenameColumn(&types.Route{}, "machine_id", "node_id")
|
||||
|
||||
err = dbConn.AutoMigrate(types.User{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = dbConn.Migrator().RenameColumn(&types.Machine{}, "namespace_id", "user_id")
|
||||
_ = dbConn.Migrator().RenameColumn(&types.Node{}, "namespace_id", "user_id")
|
||||
_ = dbConn.Migrator().RenameColumn(&types.PreAuthKey{}, "namespace_id", "user_id")
|
||||
|
||||
_ = dbConn.Migrator().RenameColumn(&types.Machine{}, "ip_address", "ip_addresses")
|
||||
_ = dbConn.Migrator().RenameColumn(&types.Machine{}, "name", "hostname")
|
||||
_ = dbConn.Migrator().RenameColumn(&types.Node{}, "ip_address", "ip_addresses")
|
||||
_ = dbConn.Migrator().RenameColumn(&types.Node{}, "name", "hostname")
|
||||
|
||||
// GivenName is used as the primary source of DNS names, make sure
|
||||
// the field is populated and normalized if it was not when the
|
||||
// machine was registered.
|
||||
_ = dbConn.Migrator().RenameColumn(&types.Machine{}, "nickname", "given_name")
|
||||
// node was registered.
|
||||
_ = dbConn.Migrator().RenameColumn(&types.Node{}, "nickname", "given_name")
|
||||
|
||||
// If the Machine table has a column for registered,
|
||||
// If the MacNodehine table has a column for registered,
|
||||
// find all occourences of "false" and drop them. Then
|
||||
// remove the column.
|
||||
if dbConn.Migrator().HasColumn(&types.Machine{}, "registered") {
|
||||
if dbConn.Migrator().HasColumn(&types.Node{}, "registered") {
|
||||
log.Info().
|
||||
Msg(`Database has legacy "registered" column in machine, removing...`)
|
||||
Msg(`Database has legacy "registered" column in node, removing...`)
|
||||
|
||||
machines := types.Machines{}
|
||||
if err := dbConn.Not("registered").Find(&machines).Error; err != nil {
|
||||
nodes := types.Nodes{}
|
||||
if err := dbConn.Not("registered").Find(&nodes).Error; err != nil {
|
||||
log.Error().Err(err).Msg("Error accessing db")
|
||||
}
|
||||
|
||||
for _, machine := range machines {
|
||||
for _, node := range nodes {
|
||||
log.Info().
|
||||
Str("machine", machine.Hostname).
|
||||
Str("machine_key", machine.MachineKey).
|
||||
Msg("Deleting unregistered machine")
|
||||
if err := dbConn.Delete(&types.Machine{}, machine.ID).Error; err != nil {
|
||||
Str("node", node.Hostname).
|
||||
Str("machine_key", node.MachineKey).
|
||||
Msg("Deleting unregistered node")
|
||||
if err := dbConn.Delete(&types.Node{}, node.ID).Error; err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("machine_key", machine.MachineKey).
|
||||
Msg("Error deleting unregistered machine")
|
||||
Str("node", node.Hostname).
|
||||
Str("machine_key", node.MachineKey).
|
||||
Msg("Error deleting unregistered node")
|
||||
}
|
||||
}
|
||||
|
||||
err := dbConn.Migrator().DropColumn(&types.Machine{}, "registered")
|
||||
err := dbConn.Migrator().DropColumn(&types.Node{}, "registered")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error dropping registered column")
|
||||
}
|
||||
@@ -131,21 +135,21 @@ func NewHeadscaleDatabase(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dbConn.Migrator().HasColumn(&types.Machine{}, "enabled_routes") {
|
||||
log.Info().Msgf("Database has legacy enabled_routes column in machine, migrating...")
|
||||
if dbConn.Migrator().HasColumn(&types.Node{}, "enabled_routes") {
|
||||
log.Info().Msgf("Database has legacy enabled_routes column in node, migrating...")
|
||||
|
||||
type MachineAux struct {
|
||||
type NodeAux struct {
|
||||
ID uint64
|
||||
EnabledRoutes types.IPPrefixes
|
||||
}
|
||||
|
||||
machinesAux := []MachineAux{}
|
||||
err := dbConn.Table("machines").Select("id, enabled_routes").Scan(&machinesAux).Error
|
||||
nodesAux := []NodeAux{}
|
||||
err := dbConn.Table("nodes").Select("id, enabled_routes").Scan(&nodesAux).Error
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error accessing db")
|
||||
}
|
||||
for _, machine := range machinesAux {
|
||||
for _, prefix := range machine.EnabledRoutes {
|
||||
for _, node := range nodesAux {
|
||||
for _, prefix := range node.EnabledRoutes {
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
@@ -155,8 +159,8 @@ func NewHeadscaleDatabase(
|
||||
continue
|
||||
}
|
||||
|
||||
err = dbConn.Preload("Machine").
|
||||
Where("machine_id = ? AND prefix = ?", machine.ID, types.IPPrefix(prefix)).
|
||||
err = dbConn.Preload("Node").
|
||||
Where("node_id = ? AND prefix = ?", node.ID, types.IPPrefix(prefix)).
|
||||
First(&types.Route{}).
|
||||
Error
|
||||
if err == nil {
|
||||
@@ -168,7 +172,7 @@ func NewHeadscaleDatabase(
|
||||
}
|
||||
|
||||
route := types.Route{
|
||||
MachineID: machine.ID,
|
||||
NodeID: node.ID,
|
||||
Advertised: true,
|
||||
Enabled: true,
|
||||
Prefix: types.IPPrefix(prefix),
|
||||
@@ -177,50 +181,50 @@ func NewHeadscaleDatabase(
|
||||
log.Error().Err(err).Msg("Error creating route")
|
||||
} else {
|
||||
log.Info().
|
||||
Uint64("machine_id", route.MachineID).
|
||||
Uint64("node_id", route.NodeID).
|
||||
Str("prefix", prefix.String()).
|
||||
Msg("Route migrated")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = dbConn.Migrator().DropColumn(&types.Machine{}, "enabled_routes")
|
||||
err = dbConn.Migrator().DropColumn(&types.Node{}, "enabled_routes")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error dropping enabled_routes column")
|
||||
}
|
||||
}
|
||||
|
||||
err = dbConn.AutoMigrate(&types.Machine{})
|
||||
err = dbConn.AutoMigrate(&types.Node{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dbConn.Migrator().HasColumn(&types.Machine{}, "given_name") {
|
||||
machines := types.Machines{}
|
||||
if err := dbConn.Find(&machines).Error; err != nil {
|
||||
if dbConn.Migrator().HasColumn(&types.Node{}, "given_name") {
|
||||
nodes := types.Nodes{}
|
||||
if err := dbConn.Find(&nodes).Error; err != nil {
|
||||
log.Error().Err(err).Msg("Error accessing db")
|
||||
}
|
||||
|
||||
for item, machine := range machines {
|
||||
if machine.GivenName == "" {
|
||||
for item, node := range nodes {
|
||||
if node.GivenName == "" {
|
||||
normalizedHostname, err := util.NormalizeToFQDNRulesConfigFromViper(
|
||||
machine.Hostname,
|
||||
node.Hostname,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("hostname", machine.Hostname).
|
||||
Str("hostname", node.Hostname).
|
||||
Err(err).
|
||||
Msg("Failed to normalize machine hostname in DB migration")
|
||||
Msg("Failed to normalize node hostname in DB migration")
|
||||
}
|
||||
|
||||
err = db.RenameMachine(machines[item], normalizedHostname)
|
||||
err = db.RenameNode(nodes[item], normalizedHostname)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("hostname", machine.Hostname).
|
||||
Str("hostname", node.Hostname).
|
||||
Err(err).
|
||||
Msg("Failed to save normalized machine name in DB migration")
|
||||
Msg("Failed to save normalized node name in DB migration")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,887 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gorm.io/gorm"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
const (
|
||||
MachineGivenNameHashLength = 8
|
||||
MachineGivenNameTrimSize = 2
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMachineNotFound = errors.New("machine not found")
|
||||
ErrMachineRouteIsNotAvailable = errors.New("route is not available on machine")
|
||||
ErrMachineNotFoundRegistrationCache = errors.New(
|
||||
"machine not found in registration cache",
|
||||
)
|
||||
ErrCouldNotConvertMachineInterface = errors.New("failed to convert machine interface")
|
||||
ErrDifferentRegisteredUser = errors.New(
|
||||
"machine was previously registered with a different user",
|
||||
)
|
||||
)
|
||||
|
||||
// ListPeers returns all peers of machine, regardless of any Policy or if the node is expired.
|
||||
func (hsdb *HSDatabase) ListPeers(machine *types.Machine) (types.Machines, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
return hsdb.listPeers(machine)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) listPeers(machine *types.Machine) (types.Machines, error) {
|
||||
log.Trace().
|
||||
Caller().
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Finding direct peers")
|
||||
|
||||
machines := types.Machines{}
|
||||
if err := hsdb.db.
|
||||
Preload("AuthKey").
|
||||
Preload("AuthKey.User").
|
||||
Preload("User").
|
||||
Preload("Routes").
|
||||
Where("node_key <> ?",
|
||||
machine.NodeKey).Find(&machines).Error; err != nil {
|
||||
return types.Machines{}, err
|
||||
}
|
||||
|
||||
sort.Slice(machines, func(i, j int) bool { return machines[i].ID < machines[j].ID })
|
||||
|
||||
log.Trace().
|
||||
Caller().
|
||||
Str("machine", machine.Hostname).
|
||||
Msgf("Found peers: %s", machines.String())
|
||||
|
||||
return machines, nil
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) ListMachines() ([]types.Machine, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
return hsdb.listMachines()
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) listMachines() ([]types.Machine, error) {
|
||||
machines := []types.Machine{}
|
||||
if err := hsdb.db.
|
||||
Preload("AuthKey").
|
||||
Preload("AuthKey.User").
|
||||
Preload("User").
|
||||
Preload("Routes").
|
||||
Find(&machines).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return machines, nil
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) ListMachinesByGivenName(givenName string) (types.Machines, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
return hsdb.listMachinesByGivenName(givenName)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) listMachinesByGivenName(givenName string) (types.Machines, error) {
|
||||
machines := types.Machines{}
|
||||
if err := hsdb.db.
|
||||
Preload("AuthKey").
|
||||
Preload("AuthKey.User").
|
||||
Preload("User").
|
||||
Preload("Routes").
|
||||
Where("given_name = ?", givenName).Find(&machines).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return machines, nil
|
||||
}
|
||||
|
||||
// GetMachine finds a Machine by name and user and returns the Machine struct.
|
||||
func (hsdb *HSDatabase) GetMachine(user string, name string) (*types.Machine, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
machines, err := hsdb.ListMachinesByUser(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, m := range machines {
|
||||
if m.Hostname == name {
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrMachineNotFound
|
||||
}
|
||||
|
||||
// GetMachineByGivenName finds a Machine by given name and user and returns the Machine struct.
|
||||
func (hsdb *HSDatabase) GetMachineByGivenName(
|
||||
user string,
|
||||
givenName string,
|
||||
) (*types.Machine, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
machine := types.Machine{}
|
||||
if err := hsdb.db.
|
||||
Preload("AuthKey").
|
||||
Preload("AuthKey.User").
|
||||
Preload("User").
|
||||
Preload("Routes").
|
||||
Where("given_name = ?", givenName).First(&machine).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, ErrMachineNotFound
|
||||
}
|
||||
|
||||
// GetMachineByID finds a Machine by ID and returns the Machine struct.
|
||||
func (hsdb *HSDatabase) GetMachineByID(id uint64) (*types.Machine, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
mach := types.Machine{}
|
||||
if result := hsdb.db.
|
||||
Preload("AuthKey").
|
||||
Preload("AuthKey.User").
|
||||
Preload("User").
|
||||
Preload("Routes").
|
||||
Find(&types.Machine{ID: id}).First(&mach); result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
return &mach, nil
|
||||
}
|
||||
|
||||
// GetMachineByMachineKey finds a Machine by its MachineKey and returns the Machine struct.
|
||||
func (hsdb *HSDatabase) GetMachineByMachineKey(
|
||||
machineKey key.MachinePublic,
|
||||
) (*types.Machine, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
mach := types.Machine{}
|
||||
if result := hsdb.db.
|
||||
Preload("AuthKey").
|
||||
Preload("AuthKey.User").
|
||||
Preload("User").
|
||||
Preload("Routes").
|
||||
First(&mach, "machine_key = ?", util.MachinePublicKeyStripPrefix(machineKey)); result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
return &mach, nil
|
||||
}
|
||||
|
||||
// GetMachineByNodeKey finds a Machine by its current NodeKey.
|
||||
func (hsdb *HSDatabase) GetMachineByNodeKey(
|
||||
nodeKey key.NodePublic,
|
||||
) (*types.Machine, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
machine := types.Machine{}
|
||||
if result := hsdb.db.
|
||||
Preload("AuthKey").
|
||||
Preload("AuthKey.User").
|
||||
Preload("User").
|
||||
Preload("Routes").
|
||||
First(&machine, "node_key = ?",
|
||||
util.NodePublicKeyStripPrefix(nodeKey)); result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
return &machine, nil
|
||||
}
|
||||
|
||||
// GetMachineByAnyNodeKey finds a Machine by its MachineKey, its current NodeKey or the old one, and returns the Machine struct.
|
||||
func (hsdb *HSDatabase) GetMachineByAnyKey(
|
||||
machineKey key.MachinePublic, nodeKey key.NodePublic, oldNodeKey key.NodePublic,
|
||||
) (*types.Machine, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
machine := types.Machine{}
|
||||
if result := hsdb.db.
|
||||
Preload("AuthKey").
|
||||
Preload("AuthKey.User").
|
||||
Preload("User").
|
||||
Preload("Routes").
|
||||
First(&machine, "machine_key = ? OR node_key = ? OR node_key = ?",
|
||||
util.MachinePublicKeyStripPrefix(machineKey),
|
||||
util.NodePublicKeyStripPrefix(nodeKey),
|
||||
util.NodePublicKeyStripPrefix(oldNodeKey)); result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
return &machine, nil
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) MachineReloadFromDatabase(machine *types.Machine) error {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
if result := hsdb.db.Find(machine).First(&machine); result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTags takes a Machine struct pointer and update the forced tags.
|
||||
func (hsdb *HSDatabase) SetTags(
|
||||
machine *types.Machine,
|
||||
tags []string,
|
||||
) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
newTags := []string{}
|
||||
for _, tag := range tags {
|
||||
if !util.StringOrPrefixListContains(newTags, tag) {
|
||||
newTags = append(newTags, tag)
|
||||
}
|
||||
}
|
||||
|
||||
if err := hsdb.db.Model(machine).Updates(types.Machine{
|
||||
ForcedTags: newTags,
|
||||
}).Error; err != nil {
|
||||
return fmt.Errorf("failed to update tags for machine in the database: %w", err)
|
||||
}
|
||||
|
||||
hsdb.notifier.NotifyWithIgnore(types.StateUpdate{
|
||||
Type: types.StatePeerChanged,
|
||||
Changed: types.Machines{machine},
|
||||
}, machine.MachineKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenameMachine takes a Machine struct and a new GivenName for the machines
|
||||
// and renames it.
|
||||
func (hsdb *HSDatabase) RenameMachine(machine *types.Machine, newName string) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
err := util.CheckForFQDNRules(
|
||||
newName,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "RenameMachine").
|
||||
Str("machine", machine.Hostname).
|
||||
Str("newName", newName).
|
||||
Err(err).
|
||||
Msg("failed to rename machine")
|
||||
|
||||
return err
|
||||
}
|
||||
machine.GivenName = newName
|
||||
|
||||
if err := hsdb.db.Model(machine).Updates(types.Machine{
|
||||
GivenName: newName,
|
||||
}).Error; err != nil {
|
||||
return fmt.Errorf("failed to rename machine in the database: %w", err)
|
||||
}
|
||||
|
||||
hsdb.notifier.NotifyWithIgnore(types.StateUpdate{
|
||||
Type: types.StatePeerChanged,
|
||||
Changed: types.Machines{machine},
|
||||
}, machine.MachineKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MachineSetExpiry takes a Machine struct and a new expiry time.
|
||||
func (hsdb *HSDatabase) MachineSetExpiry(machine *types.Machine, expiry time.Time) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
return hsdb.machineSetExpiry(machine, expiry)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) machineSetExpiry(machine *types.Machine, expiry time.Time) error {
|
||||
if err := hsdb.db.Model(machine).Updates(types.Machine{
|
||||
Expiry: &expiry,
|
||||
}).Error; err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to refresh machine (update expiration) in the database: %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
hsdb.notifier.NotifyWithIgnore(types.StateUpdate{
|
||||
Type: types.StatePeerChanged,
|
||||
Changed: types.Machines{machine},
|
||||
}, machine.MachineKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteMachine deletes a Machine from the database.
|
||||
func (hsdb *HSDatabase) DeleteMachine(machine *types.Machine) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
return hsdb.deleteMachine(machine)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) deleteMachine(machine *types.Machine) error {
|
||||
err := hsdb.deleteMachineRoutes(machine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unscoped causes the machine to be fully removed from the database.
|
||||
if err := hsdb.db.Unscoped().Delete(&machine).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hsdb.notifier.NotifyAll(types.StateUpdate{
|
||||
Type: types.StatePeerRemoved,
|
||||
Removed: []tailcfg.NodeID{tailcfg.NodeID(machine.ID)},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateLastSeen sets a machine's last seen field indicating that we
|
||||
// have recently communicating with this machine.
|
||||
// This is mostly used to indicate if a machine is online and is not
|
||||
// extremely important to make sure is fully correct and to avoid
|
||||
// holding up the hot path, does not contain any locks and isnt
|
||||
// concurrency safe. But that should be ok.
|
||||
func (hsdb *HSDatabase) UpdateLastSeen(machine *types.Machine) error {
|
||||
return hsdb.db.Model(machine).Updates(types.Machine{
|
||||
LastSeen: machine.LastSeen,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) RegisterMachineFromAuthCallback(
|
||||
cache *cache.Cache,
|
||||
nodeKeyStr string,
|
||||
userName string,
|
||||
machineExpiry *time.Time,
|
||||
registrationMethod string,
|
||||
) (*types.Machine, error) {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
nodeKey := key.NodePublic{}
|
||||
err := nodeKey.UnmarshalText([]byte(nodeKeyStr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Str("nodeKey", nodeKey.ShortString()).
|
||||
Str("userName", userName).
|
||||
Str("registrationMethod", registrationMethod).
|
||||
Str("expiresAt", fmt.Sprintf("%v", machineExpiry)).
|
||||
Msg("Registering machine from API/CLI or auth callback")
|
||||
|
||||
if machineInterface, ok := cache.Get(util.NodePublicKeyStripPrefix(nodeKey)); ok {
|
||||
if registrationMachine, ok := machineInterface.(types.Machine); ok {
|
||||
user, err := hsdb.getUser(userName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to find user in register machine from auth callback, %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
// Registration of expired machine with different user
|
||||
if registrationMachine.ID != 0 &&
|
||||
registrationMachine.UserID != user.ID {
|
||||
return nil, ErrDifferentRegisteredUser
|
||||
}
|
||||
|
||||
registrationMachine.UserID = user.ID
|
||||
registrationMachine.RegisterMethod = registrationMethod
|
||||
|
||||
if machineExpiry != nil {
|
||||
registrationMachine.Expiry = machineExpiry
|
||||
}
|
||||
|
||||
machine, err := hsdb.registerMachine(
|
||||
registrationMachine,
|
||||
)
|
||||
|
||||
if err == nil {
|
||||
cache.Delete(nodeKeyStr)
|
||||
}
|
||||
|
||||
return machine, err
|
||||
} else {
|
||||
return nil, ErrCouldNotConvertMachineInterface
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrMachineNotFoundRegistrationCache
|
||||
}
|
||||
|
||||
// RegisterMachine is executed from the CLI to register a new Machine using its MachineKey.
|
||||
func (hsdb *HSDatabase) RegisterMachine(machine types.Machine) (*types.Machine, error) {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
return hsdb.registerMachine(machine)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) registerMachine(machine types.Machine) (*types.Machine, error) {
|
||||
log.Debug().
|
||||
Str("machine", machine.Hostname).
|
||||
Str("machine_key", machine.MachineKey).
|
||||
Str("node_key", machine.NodeKey).
|
||||
Str("user", machine.User.Name).
|
||||
Msg("Registering machine")
|
||||
|
||||
// If the machine exists and we had already IPs for it, we just save it
|
||||
// so we store the machine.Expire and machine.Nodekey that has been set when
|
||||
// adding it to the registrationCache
|
||||
if len(machine.IPAddresses) > 0 {
|
||||
if err := hsdb.db.Save(&machine).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed register existing machine in the database: %w", err)
|
||||
}
|
||||
|
||||
log.Trace().
|
||||
Caller().
|
||||
Str("machine", machine.Hostname).
|
||||
Str("machine_key", machine.MachineKey).
|
||||
Str("node_key", machine.NodeKey).
|
||||
Str("user", machine.User.Name).
|
||||
Msg("Machine authorized again")
|
||||
|
||||
return &machine, nil
|
||||
}
|
||||
|
||||
hsdb.ipAllocationMutex.Lock()
|
||||
defer hsdb.ipAllocationMutex.Unlock()
|
||||
|
||||
ips, err := hsdb.getAvailableIPs()
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Could not find IP for the new machine")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
machine.IPAddresses = ips
|
||||
|
||||
if err := hsdb.db.Save(&machine).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed register(save) machine in the database: %w", err)
|
||||
}
|
||||
|
||||
log.Trace().
|
||||
Caller().
|
||||
Str("machine", machine.Hostname).
|
||||
Str("ip", strings.Join(ips.StringSlice(), ",")).
|
||||
Msg("Machine registered with the database")
|
||||
|
||||
return &machine, nil
|
||||
}
|
||||
|
||||
// MachineSetNodeKey sets the node key of a machine and saves it to the database.
|
||||
func (hsdb *HSDatabase) MachineSetNodeKey(machine *types.Machine, nodeKey key.NodePublic) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
if err := hsdb.db.Model(machine).Updates(types.Machine{
|
||||
NodeKey: util.NodePublicKeyStripPrefix(nodeKey),
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MachineSetMachineKey sets the machine key of a machine and saves it to the database.
|
||||
func (hsdb *HSDatabase) MachineSetMachineKey(
|
||||
machine *types.Machine,
|
||||
machineKey key.MachinePublic,
|
||||
) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
if err := hsdb.db.Model(machine).Updates(types.Machine{
|
||||
MachineKey: util.MachinePublicKeyStripPrefix(machineKey),
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MachineSave saves a machine object to the database, prefer to use a specific save method rather
|
||||
// than this. It is intended to be used when we are changing or.
|
||||
func (hsdb *HSDatabase) MachineSave(machine *types.Machine) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
if err := hsdb.db.Save(machine).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAdvertisedRoutes returns the routes that are be advertised by the given machine.
|
||||
func (hsdb *HSDatabase) GetAdvertisedRoutes(machine *types.Machine) ([]netip.Prefix, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
return hsdb.getAdvertisedRoutes(machine)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) getAdvertisedRoutes(machine *types.Machine) ([]netip.Prefix, error) {
|
||||
routes := types.Routes{}
|
||||
|
||||
err := hsdb.db.
|
||||
Preload("Machine").
|
||||
Where("machine_id = ? AND advertised = ?", machine.ID, true).Find(&routes).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Could not get advertised routes for machine")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prefixes := []netip.Prefix{}
|
||||
for _, route := range routes {
|
||||
prefixes = append(prefixes, netip.Prefix(route.Prefix))
|
||||
}
|
||||
|
||||
return prefixes, nil
|
||||
}
|
||||
|
||||
// GetEnabledRoutes returns the routes that are enabled for the machine.
|
||||
func (hsdb *HSDatabase) GetEnabledRoutes(machine *types.Machine) ([]netip.Prefix, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
return hsdb.getEnabledRoutes(machine)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) getEnabledRoutes(machine *types.Machine) ([]netip.Prefix, error) {
|
||||
routes := types.Routes{}
|
||||
|
||||
err := hsdb.db.
|
||||
Preload("Machine").
|
||||
Where("machine_id = ? AND advertised = ? AND enabled = ?", machine.ID, true, true).
|
||||
Find(&routes).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Could not get enabled routes for machine")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prefixes := []netip.Prefix{}
|
||||
for _, route := range routes {
|
||||
prefixes = append(prefixes, netip.Prefix(route.Prefix))
|
||||
}
|
||||
|
||||
return prefixes, nil
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) IsRoutesEnabled(machine *types.Machine, routeStr string) bool {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
route, err := netip.ParsePrefix(routeStr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
enabledRoutes, err := hsdb.getEnabledRoutes(machine)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Could not get enabled routes")
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
for _, enabledRoute := range enabledRoutes {
|
||||
if route == enabledRoute {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func OnlineMachineMap(peers types.Machines) map[tailcfg.NodeID]bool {
|
||||
ret := make(map[tailcfg.NodeID]bool)
|
||||
|
||||
for _, peer := range peers {
|
||||
ret[tailcfg.NodeID(peer.ID)] = peer.IsOnline()
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) ListOnlineMachines(
|
||||
machine *types.Machine,
|
||||
) (map[tailcfg.NodeID]bool, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
peers, err := hsdb.listPeers(machine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return OnlineMachineMap(peers), nil
|
||||
}
|
||||
|
||||
// enableRoutes enables new routes based on a list of new routes.
|
||||
func (hsdb *HSDatabase) enableRoutes(machine *types.Machine, routeStrs ...string) error {
|
||||
newRoutes := make([]netip.Prefix, len(routeStrs))
|
||||
for index, routeStr := range routeStrs {
|
||||
route, err := netip.ParsePrefix(routeStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newRoutes[index] = route
|
||||
}
|
||||
|
||||
advertisedRoutes, err := hsdb.getAdvertisedRoutes(machine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, newRoute := range newRoutes {
|
||||
if !util.StringOrPrefixListContains(advertisedRoutes, newRoute) {
|
||||
return fmt.Errorf(
|
||||
"route (%s) is not available on node %s: %w",
|
||||
machine.Hostname,
|
||||
newRoute, ErrMachineRouteIsNotAvailable,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Separate loop so we don't leave things in a half-updated state
|
||||
for _, prefix := range newRoutes {
|
||||
route := types.Route{}
|
||||
err := hsdb.db.Preload("Machine").
|
||||
Where("machine_id = ? AND prefix = ?", machine.ID, types.IPPrefix(prefix)).
|
||||
First(&route).Error
|
||||
if err == nil {
|
||||
route.Enabled = true
|
||||
|
||||
// Mark already as primary if there is only this node offering this subnet
|
||||
// (and is not an exit route)
|
||||
if !route.IsExitRoute() {
|
||||
route.IsPrimary = hsdb.isUniquePrefix(route)
|
||||
}
|
||||
|
||||
err = hsdb.db.Save(&route).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to enable route: %w", err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("failed to find route: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
hsdb.notifier.NotifyWithIgnore(types.StateUpdate{
|
||||
Type: types.StatePeerChanged,
|
||||
Changed: types.Machines{machine},
|
||||
}, machine.MachineKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateGivenName(suppliedName string, randomSuffix bool) (string, error) {
|
||||
normalizedHostname, err := util.NormalizeToFQDNRulesConfigFromViper(
|
||||
suppliedName,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if randomSuffix {
|
||||
// Trim if a hostname will be longer than 63 chars after adding the hash.
|
||||
trimmedHostnameLength := util.LabelHostnameLength - MachineGivenNameHashLength - MachineGivenNameTrimSize
|
||||
if len(normalizedHostname) > trimmedHostnameLength {
|
||||
normalizedHostname = normalizedHostname[:trimmedHostnameLength]
|
||||
}
|
||||
|
||||
suffix, err := util.GenerateRandomStringDNSSafe(MachineGivenNameHashLength)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
normalizedHostname += "-" + suffix
|
||||
}
|
||||
|
||||
return normalizedHostname, nil
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) GenerateGivenName(machineKey string, suppliedName string) (string, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
givenName, err := generateGivenName(suppliedName, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Tailscale rules (may differ) https://tailscale.com/kb/1098/machine-names/
|
||||
machines, err := hsdb.listMachinesByGivenName(givenName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, machine := range machines {
|
||||
if machine.MachineKey != machineKey && machine.GivenName == givenName {
|
||||
postfixedName, err := generateGivenName(suppliedName, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
givenName = postfixedName
|
||||
}
|
||||
}
|
||||
|
||||
return givenName, nil
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) ExpireEphemeralMachines(inactivityThreshhold time.Duration) {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
users, err := hsdb.listUsers()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error listing users")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
machines, err := hsdb.listMachinesByUser(user.Name)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("user", user.Name).
|
||||
Msg("Error listing machines in user")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
expired := make([]tailcfg.NodeID, 0)
|
||||
for idx, machine := range machines {
|
||||
if machine.IsEphemeral() && machine.LastSeen != nil &&
|
||||
time.Now().
|
||||
After(machine.LastSeen.Add(inactivityThreshhold)) {
|
||||
expired = append(expired, tailcfg.NodeID(machine.ID))
|
||||
|
||||
log.Info().
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Ephemeral client removed from database")
|
||||
|
||||
err = hsdb.deleteMachine(machines[idx])
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("🤮 Cannot delete ephemeral machine from the database")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(expired) > 0 {
|
||||
hsdb.notifier.NotifyAll(types.StateUpdate{
|
||||
Type: types.StatePeerRemoved,
|
||||
Removed: expired,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) ExpireExpiredMachines(lastCheck time.Time) time.Time {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
// use the time of the start of the function to ensure we
|
||||
// dont miss some machines by returning it _after_ we have
|
||||
// checked everything.
|
||||
started := time.Now()
|
||||
|
||||
users, err := hsdb.listUsers()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error listing users")
|
||||
|
||||
return time.Unix(0, 0)
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
machines, err := hsdb.listMachinesByUser(user.Name)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("user", user.Name).
|
||||
Msg("Error listing machines in user")
|
||||
|
||||
return time.Unix(0, 0)
|
||||
}
|
||||
|
||||
expired := make([]tailcfg.NodeID, 0)
|
||||
for index, machine := range machines {
|
||||
if machine.IsExpired() &&
|
||||
machine.Expiry.After(lastCheck) {
|
||||
expired = append(expired, tailcfg.NodeID(machine.ID))
|
||||
|
||||
now := time.Now()
|
||||
err := hsdb.machineSetExpiry(machines[index], now)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("name", machine.GivenName).
|
||||
Msg("🤮 Cannot expire machine")
|
||||
} else {
|
||||
log.Info().
|
||||
Str("machine", machine.Hostname).
|
||||
Str("name", machine.GivenName).
|
||||
Msg("Machine successfully expired")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(expired) > 0 {
|
||||
hsdb.notifier.NotifyAll(types.StateUpdate{
|
||||
Type: types.StatePeerRemoved,
|
||||
Removed: expired,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return started
|
||||
}
|
887
hscontrol/db/node.go
Normal file
887
hscontrol/db/node.go
Normal file
@@ -0,0 +1,887 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gorm.io/gorm"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
const (
|
||||
NodeGivenNameHashLength = 8
|
||||
NodeGivenNameTrimSize = 2
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNodeNotFound = errors.New("node not found")
|
||||
ErrNodeRouteIsNotAvailable = errors.New("route is not available on node")
|
||||
ErrNodeNotFoundRegistrationCache = errors.New(
|
||||
"node not found in registration cache",
|
||||
)
|
||||
ErrCouldNotConvertNodeInterface = errors.New("failed to convert node interface")
|
||||
ErrDifferentRegisteredUser = errors.New(
|
||||
"node was previously registered with a different user",
|
||||
)
|
||||
)
|
||||
|
||||
// ListPeers returns all peers of node, regardless of any Policy or if the node is expired.
|
||||
func (hsdb *HSDatabase) ListPeers(node *types.Node) (types.Nodes, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
return hsdb.listPeers(node)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) listPeers(node *types.Node) (types.Nodes, error) {
|
||||
log.Trace().
|
||||
Caller().
|
||||
Str("node", node.Hostname).
|
||||
Msg("Finding direct peers")
|
||||
|
||||
nodes := types.Nodes{}
|
||||
if err := hsdb.db.
|
||||
Preload("AuthKey").
|
||||
Preload("AuthKey.User").
|
||||
Preload("User").
|
||||
Preload("Routes").
|
||||
Where("node_key <> ?",
|
||||
node.NodeKey).Find(&nodes).Error; err != nil {
|
||||
return types.Nodes{}, err
|
||||
}
|
||||
|
||||
sort.Slice(nodes, func(i, j int) bool { return nodes[i].ID < nodes[j].ID })
|
||||
|
||||
log.Trace().
|
||||
Caller().
|
||||
Str("node", node.Hostname).
|
||||
Msgf("Found peers: %s", nodes.String())
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) ListNodes() ([]types.Node, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
return hsdb.listNodes()
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) listNodes() ([]types.Node, error) {
|
||||
nodes := []types.Node{}
|
||||
if err := hsdb.db.
|
||||
Preload("AuthKey").
|
||||
Preload("AuthKey.User").
|
||||
Preload("User").
|
||||
Preload("Routes").
|
||||
Find(&nodes).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) ListNodesByGivenName(givenName string) (types.Nodes, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
return hsdb.listNodesByGivenName(givenName)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) listNodesByGivenName(givenName string) (types.Nodes, error) {
|
||||
nodes := types.Nodes{}
|
||||
if err := hsdb.db.
|
||||
Preload("AuthKey").
|
||||
Preload("AuthKey.User").
|
||||
Preload("User").
|
||||
Preload("Routes").
|
||||
Where("given_name = ?", givenName).Find(&nodes).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// GetNode finds a Node by name and user and returns the Node struct.
|
||||
func (hsdb *HSDatabase) GetNode(user string, name string) (*types.Node, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
nodes, err := hsdb.ListNodesByUser(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, m := range nodes {
|
||||
if m.Hostname == name {
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrNodeNotFound
|
||||
}
|
||||
|
||||
// GetNodeByGivenName finds a Node by given name and user and returns the Node struct.
|
||||
func (hsdb *HSDatabase) GetNodeByGivenName(
|
||||
user string,
|
||||
givenName string,
|
||||
) (*types.Node, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
node := types.Node{}
|
||||
if err := hsdb.db.
|
||||
Preload("AuthKey").
|
||||
Preload("AuthKey.User").
|
||||
Preload("User").
|
||||
Preload("Routes").
|
||||
Where("given_name = ?", givenName).First(&node).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, ErrNodeNotFound
|
||||
}
|
||||
|
||||
// GetNodeByID finds a Node by ID and returns the Node struct.
|
||||
func (hsdb *HSDatabase) GetNodeByID(id uint64) (*types.Node, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
mach := types.Node{}
|
||||
if result := hsdb.db.
|
||||
Preload("AuthKey").
|
||||
Preload("AuthKey.User").
|
||||
Preload("User").
|
||||
Preload("Routes").
|
||||
Find(&types.Node{ID: id}).First(&mach); result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
return &mach, nil
|
||||
}
|
||||
|
||||
// GetNodeByMachineKey finds a Node by its MachineKey and returns the Node struct.
|
||||
func (hsdb *HSDatabase) GetNodeByMachineKey(
|
||||
machineKey key.MachinePublic,
|
||||
) (*types.Node, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
mach := types.Node{}
|
||||
if result := hsdb.db.
|
||||
Preload("AuthKey").
|
||||
Preload("AuthKey.User").
|
||||
Preload("User").
|
||||
Preload("Routes").
|
||||
First(&mach, "machine_key = ?", util.MachinePublicKeyStripPrefix(machineKey)); result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
return &mach, nil
|
||||
}
|
||||
|
||||
// GetNodeByNodeKey finds a Node by its current NodeKey.
|
||||
func (hsdb *HSDatabase) GetNodeByNodeKey(
|
||||
nodeKey key.NodePublic,
|
||||
) (*types.Node, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
node := types.Node{}
|
||||
if result := hsdb.db.
|
||||
Preload("AuthKey").
|
||||
Preload("AuthKey.User").
|
||||
Preload("User").
|
||||
Preload("Routes").
|
||||
First(&node, "node_key = ?",
|
||||
util.NodePublicKeyStripPrefix(nodeKey)); result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
return &node, nil
|
||||
}
|
||||
|
||||
// GetNodeByAnyKey finds a Node by its MachineKey, its current NodeKey or the old one, and returns the Node struct.
|
||||
func (hsdb *HSDatabase) GetNodeByAnyKey(
|
||||
machineKey key.MachinePublic, nodeKey key.NodePublic, oldNodeKey key.NodePublic,
|
||||
) (*types.Node, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
node := types.Node{}
|
||||
if result := hsdb.db.
|
||||
Preload("AuthKey").
|
||||
Preload("AuthKey.User").
|
||||
Preload("User").
|
||||
Preload("Routes").
|
||||
First(&node, "machine_key = ? OR node_key = ? OR node_key = ?",
|
||||
util.MachinePublicKeyStripPrefix(machineKey),
|
||||
util.NodePublicKeyStripPrefix(nodeKey),
|
||||
util.NodePublicKeyStripPrefix(oldNodeKey)); result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
return &node, nil
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) NodeReloadFromDatabase(node *types.Node) error {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
if result := hsdb.db.Find(node).First(&node); result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTags takes a Node struct pointer and update the forced tags.
|
||||
func (hsdb *HSDatabase) SetTags(
|
||||
node *types.Node,
|
||||
tags []string,
|
||||
) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
newTags := []string{}
|
||||
for _, tag := range tags {
|
||||
if !util.StringOrPrefixListContains(newTags, tag) {
|
||||
newTags = append(newTags, tag)
|
||||
}
|
||||
}
|
||||
|
||||
if err := hsdb.db.Model(node).Updates(types.Node{
|
||||
ForcedTags: newTags,
|
||||
}).Error; err != nil {
|
||||
return fmt.Errorf("failed to update tags for node in the database: %w", err)
|
||||
}
|
||||
|
||||
hsdb.notifier.NotifyWithIgnore(types.StateUpdate{
|
||||
Type: types.StatePeerChanged,
|
||||
Changed: types.Nodes{node},
|
||||
}, node.MachineKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenameNode takes a Node struct and a new GivenName for the nodes
|
||||
// and renames it.
|
||||
func (hsdb *HSDatabase) RenameNode(node *types.Node, newName string) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
err := util.CheckForFQDNRules(
|
||||
newName,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "RenameNode").
|
||||
Str("node", node.Hostname).
|
||||
Str("newName", newName).
|
||||
Err(err).
|
||||
Msg("failed to rename node")
|
||||
|
||||
return err
|
||||
}
|
||||
node.GivenName = newName
|
||||
|
||||
if err := hsdb.db.Model(node).Updates(types.Node{
|
||||
GivenName: newName,
|
||||
}).Error; err != nil {
|
||||
return fmt.Errorf("failed to rename node in the database: %w", err)
|
||||
}
|
||||
|
||||
hsdb.notifier.NotifyWithIgnore(types.StateUpdate{
|
||||
Type: types.StatePeerChanged,
|
||||
Changed: types.Nodes{node},
|
||||
}, node.MachineKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NodeSetExpiry takes a Node struct and a new expiry time.
|
||||
func (hsdb *HSDatabase) NodeSetExpiry(node *types.Node, expiry time.Time) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
return hsdb.nodeSetExpiry(node, expiry)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) nodeSetExpiry(node *types.Node, expiry time.Time) error {
|
||||
if err := hsdb.db.Model(node).Updates(types.Node{
|
||||
Expiry: &expiry,
|
||||
}).Error; err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to refresh node (update expiration) in the database: %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
hsdb.notifier.NotifyWithIgnore(types.StateUpdate{
|
||||
Type: types.StatePeerChanged,
|
||||
Changed: types.Nodes{node},
|
||||
}, node.MachineKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteNode deletes a Node from the database.
|
||||
func (hsdb *HSDatabase) DeleteNode(node *types.Node) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
return hsdb.deleteNode(node)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) deleteNode(node *types.Node) error {
|
||||
err := hsdb.deleteNodeRoutes(node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unscoped causes the node to be fully removed from the database.
|
||||
if err := hsdb.db.Unscoped().Delete(&node).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hsdb.notifier.NotifyAll(types.StateUpdate{
|
||||
Type: types.StatePeerRemoved,
|
||||
Removed: []tailcfg.NodeID{tailcfg.NodeID(node.ID)},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateLastSeen sets a node's last seen field indicating that we
|
||||
// have recently communicating with this node.
|
||||
// This is mostly used to indicate if a node is online and is not
|
||||
// extremely important to make sure is fully correct and to avoid
|
||||
// holding up the hot path, does not contain any locks and isnt
|
||||
// concurrency safe. But that should be ok.
|
||||
func (hsdb *HSDatabase) UpdateLastSeen(node *types.Node) error {
|
||||
return hsdb.db.Model(node).Updates(types.Node{
|
||||
LastSeen: node.LastSeen,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) RegisterNodeFromAuthCallback(
|
||||
cache *cache.Cache,
|
||||
nodeKeyStr string,
|
||||
userName string,
|
||||
nodeExpiry *time.Time,
|
||||
registrationMethod string,
|
||||
) (*types.Node, error) {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
nodeKey := key.NodePublic{}
|
||||
err := nodeKey.UnmarshalText([]byte(nodeKeyStr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Str("nodeKey", nodeKey.ShortString()).
|
||||
Str("userName", userName).
|
||||
Str("registrationMethod", registrationMethod).
|
||||
Str("expiresAt", fmt.Sprintf("%v", nodeExpiry)).
|
||||
Msg("Registering node from API/CLI or auth callback")
|
||||
|
||||
if nodeInterface, ok := cache.Get(util.NodePublicKeyStripPrefix(nodeKey)); ok {
|
||||
if registrationNode, ok := nodeInterface.(types.Node); ok {
|
||||
user, err := hsdb.getUser(userName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to find user in register node from auth callback, %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
// Registration of expired node with different user
|
||||
if registrationNode.ID != 0 &&
|
||||
registrationNode.UserID != user.ID {
|
||||
return nil, ErrDifferentRegisteredUser
|
||||
}
|
||||
|
||||
registrationNode.UserID = user.ID
|
||||
registrationNode.RegisterMethod = registrationMethod
|
||||
|
||||
if nodeExpiry != nil {
|
||||
registrationNode.Expiry = nodeExpiry
|
||||
}
|
||||
|
||||
node, err := hsdb.registerNode(
|
||||
registrationNode,
|
||||
)
|
||||
|
||||
if err == nil {
|
||||
cache.Delete(nodeKeyStr)
|
||||
}
|
||||
|
||||
return node, err
|
||||
} else {
|
||||
return nil, ErrCouldNotConvertNodeInterface
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrNodeNotFoundRegistrationCache
|
||||
}
|
||||
|
||||
// RegisterNode is executed from the CLI to register a new Node using its MachineKey.
|
||||
func (hsdb *HSDatabase) RegisterNode(node types.Node) (*types.Node, error) {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
return hsdb.registerNode(node)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) registerNode(node types.Node) (*types.Node, error) {
|
||||
log.Debug().
|
||||
Str("node", node.Hostname).
|
||||
Str("machine_key", node.MachineKey).
|
||||
Str("node_key", node.NodeKey).
|
||||
Str("user", node.User.Name).
|
||||
Msg("Registering node")
|
||||
|
||||
// If the node exists and we had already IPs for it, we just save it
|
||||
// so we store the node.Expire and node.Nodekey that has been set when
|
||||
// adding it to the registrationCache
|
||||
if len(node.IPAddresses) > 0 {
|
||||
if err := hsdb.db.Save(&node).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed register existing node in the database: %w", err)
|
||||
}
|
||||
|
||||
log.Trace().
|
||||
Caller().
|
||||
Str("node", node.Hostname).
|
||||
Str("machine_key", node.MachineKey).
|
||||
Str("node_key", node.NodeKey).
|
||||
Str("user", node.User.Name).
|
||||
Msg("Node authorized again")
|
||||
|
||||
return &node, nil
|
||||
}
|
||||
|
||||
hsdb.ipAllocationMutex.Lock()
|
||||
defer hsdb.ipAllocationMutex.Unlock()
|
||||
|
||||
ips, err := hsdb.getAvailableIPs()
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Str("node", node.Hostname).
|
||||
Msg("Could not find IP for the new node")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node.IPAddresses = ips
|
||||
|
||||
if err := hsdb.db.Save(&node).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed register(save) node in the database: %w", err)
|
||||
}
|
||||
|
||||
log.Trace().
|
||||
Caller().
|
||||
Str("node", node.Hostname).
|
||||
Str("ip", strings.Join(ips.StringSlice(), ",")).
|
||||
Msg("Node registered with the database")
|
||||
|
||||
return &node, nil
|
||||
}
|
||||
|
||||
// NodeSetNodeKey sets the node key of a node and saves it to the database.
|
||||
func (hsdb *HSDatabase) NodeSetNodeKey(node *types.Node, nodeKey key.NodePublic) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
if err := hsdb.db.Model(node).Updates(types.Node{
|
||||
NodeKey: util.NodePublicKeyStripPrefix(nodeKey),
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NodeSetMachineKey sets the node key of a node and saves it to the database.
|
||||
func (hsdb *HSDatabase) NodeSetMachineKey(
|
||||
node *types.Node,
|
||||
machineKey key.MachinePublic,
|
||||
) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
if err := hsdb.db.Model(node).Updates(types.Node{
|
||||
MachineKey: util.MachinePublicKeyStripPrefix(machineKey),
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NodeSave saves a node object to the database, prefer to use a specific save method rather
|
||||
// than this. It is intended to be used when we are changing or.
|
||||
func (hsdb *HSDatabase) NodeSave(node *types.Node) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
if err := hsdb.db.Save(node).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAdvertisedRoutes returns the routes that are be advertised by the given node.
|
||||
func (hsdb *HSDatabase) GetAdvertisedRoutes(node *types.Node) ([]netip.Prefix, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
return hsdb.getAdvertisedRoutes(node)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) getAdvertisedRoutes(node *types.Node) ([]netip.Prefix, error) {
|
||||
routes := types.Routes{}
|
||||
|
||||
err := hsdb.db.
|
||||
Preload("Node").
|
||||
Where("node_id = ? AND advertised = ?", node.ID, true).Find(&routes).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Str("node", node.Hostname).
|
||||
Msg("Could not get advertised routes for node")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prefixes := []netip.Prefix{}
|
||||
for _, route := range routes {
|
||||
prefixes = append(prefixes, netip.Prefix(route.Prefix))
|
||||
}
|
||||
|
||||
return prefixes, nil
|
||||
}
|
||||
|
||||
// GetEnabledRoutes returns the routes that are enabled for the node.
|
||||
func (hsdb *HSDatabase) GetEnabledRoutes(node *types.Node) ([]netip.Prefix, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
return hsdb.getEnabledRoutes(node)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) getEnabledRoutes(node *types.Node) ([]netip.Prefix, error) {
|
||||
routes := types.Routes{}
|
||||
|
||||
err := hsdb.db.
|
||||
Preload("Node").
|
||||
Where("node_id = ? AND advertised = ? AND enabled = ?", node.ID, true, true).
|
||||
Find(&routes).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Str("node", node.Hostname).
|
||||
Msg("Could not get enabled routes for node")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prefixes := []netip.Prefix{}
|
||||
for _, route := range routes {
|
||||
prefixes = append(prefixes, netip.Prefix(route.Prefix))
|
||||
}
|
||||
|
||||
return prefixes, nil
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) IsRoutesEnabled(node *types.Node, routeStr string) bool {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
route, err := netip.ParsePrefix(routeStr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
enabledRoutes, err := hsdb.getEnabledRoutes(node)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Could not get enabled routes")
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
for _, enabledRoute := range enabledRoutes {
|
||||
if route == enabledRoute {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func OnlineNodeMap(peers types.Nodes) map[tailcfg.NodeID]bool {
|
||||
ret := make(map[tailcfg.NodeID]bool)
|
||||
|
||||
for _, peer := range peers {
|
||||
ret[tailcfg.NodeID(peer.ID)] = peer.IsOnline()
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) ListOnlineNodes(
|
||||
node *types.Node,
|
||||
) (map[tailcfg.NodeID]bool, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
peers, err := hsdb.listPeers(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return OnlineNodeMap(peers), nil
|
||||
}
|
||||
|
||||
// enableRoutes enables new routes based on a list of new routes.
|
||||
func (hsdb *HSDatabase) enableRoutes(node *types.Node, routeStrs ...string) error {
|
||||
newRoutes := make([]netip.Prefix, len(routeStrs))
|
||||
for index, routeStr := range routeStrs {
|
||||
route, err := netip.ParsePrefix(routeStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newRoutes[index] = route
|
||||
}
|
||||
|
||||
advertisedRoutes, err := hsdb.getAdvertisedRoutes(node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, newRoute := range newRoutes {
|
||||
if !util.StringOrPrefixListContains(advertisedRoutes, newRoute) {
|
||||
return fmt.Errorf(
|
||||
"route (%s) is not available on node %s: %w",
|
||||
node.Hostname,
|
||||
newRoute, ErrNodeRouteIsNotAvailable,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Separate loop so we don't leave things in a half-updated state
|
||||
for _, prefix := range newRoutes {
|
||||
route := types.Route{}
|
||||
err := hsdb.db.Preload("Node").
|
||||
Where("node_id = ? AND prefix = ?", node.ID, types.IPPrefix(prefix)).
|
||||
First(&route).Error
|
||||
if err == nil {
|
||||
route.Enabled = true
|
||||
|
||||
// Mark already as primary if there is only this node offering this subnet
|
||||
// (and is not an exit route)
|
||||
if !route.IsExitRoute() {
|
||||
route.IsPrimary = hsdb.isUniquePrefix(route)
|
||||
}
|
||||
|
||||
err = hsdb.db.Save(&route).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to enable route: %w", err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("failed to find route: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
hsdb.notifier.NotifyWithIgnore(types.StateUpdate{
|
||||
Type: types.StatePeerChanged,
|
||||
Changed: types.Nodes{node},
|
||||
}, node.MachineKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateGivenName(suppliedName string, randomSuffix bool) (string, error) {
|
||||
normalizedHostname, err := util.NormalizeToFQDNRulesConfigFromViper(
|
||||
suppliedName,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if randomSuffix {
|
||||
// Trim if a hostname will be longer than 63 chars after adding the hash.
|
||||
trimmedHostnameLength := util.LabelHostnameLength - NodeGivenNameHashLength - NodeGivenNameTrimSize
|
||||
if len(normalizedHostname) > trimmedHostnameLength {
|
||||
normalizedHostname = normalizedHostname[:trimmedHostnameLength]
|
||||
}
|
||||
|
||||
suffix, err := util.GenerateRandomStringDNSSafe(NodeGivenNameHashLength)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
normalizedHostname += "-" + suffix
|
||||
}
|
||||
|
||||
return normalizedHostname, nil
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) GenerateGivenName(machineKey string, suppliedName string) (string, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
givenName, err := generateGivenName(suppliedName, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Tailscale rules (may differ) https://tailscale.com/kb/1098/machine-names/
|
||||
nodes, err := hsdb.listNodesByGivenName(givenName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, node := range nodes {
|
||||
if node.MachineKey != machineKey && node.GivenName == givenName {
|
||||
postfixedName, err := generateGivenName(suppliedName, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
givenName = postfixedName
|
||||
}
|
||||
}
|
||||
|
||||
return givenName, nil
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) ExpireEphemeralNodes(inactivityThreshhold time.Duration) {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
users, err := hsdb.listUsers()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error listing users")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
nodes, err := hsdb.listNodesByUser(user.Name)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("user", user.Name).
|
||||
Msg("Error listing nodes in user")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
expired := make([]tailcfg.NodeID, 0)
|
||||
for idx, node := range nodes {
|
||||
if node.IsEphemeral() && node.LastSeen != nil &&
|
||||
time.Now().
|
||||
After(node.LastSeen.Add(inactivityThreshhold)) {
|
||||
expired = append(expired, tailcfg.NodeID(node.ID))
|
||||
|
||||
log.Info().
|
||||
Str("node", node.Hostname).
|
||||
Msg("Ephemeral client removed from database")
|
||||
|
||||
err = hsdb.deleteNode(nodes[idx])
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("node", node.Hostname).
|
||||
Msg("🤮 Cannot delete ephemeral node from the database")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(expired) > 0 {
|
||||
hsdb.notifier.NotifyAll(types.StateUpdate{
|
||||
Type: types.StatePeerRemoved,
|
||||
Removed: expired,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) ExpireExpiredNodes(lastCheck time.Time) time.Time {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
// use the time of the start of the function to ensure we
|
||||
// dont miss some nodes by returning it _after_ we have
|
||||
// checked everything.
|
||||
started := time.Now()
|
||||
|
||||
users, err := hsdb.listUsers()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error listing users")
|
||||
|
||||
return time.Unix(0, 0)
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
nodes, err := hsdb.listNodesByUser(user.Name)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("user", user.Name).
|
||||
Msg("Error listing nodes in user")
|
||||
|
||||
return time.Unix(0, 0)
|
||||
}
|
||||
|
||||
expired := make([]tailcfg.NodeID, 0)
|
||||
for index, node := range nodes {
|
||||
if node.IsExpired() &&
|
||||
node.Expiry.After(lastCheck) {
|
||||
expired = append(expired, tailcfg.NodeID(node.ID))
|
||||
|
||||
now := time.Now()
|
||||
err := hsdb.nodeSetExpiry(nodes[index], now)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("node", node.Hostname).
|
||||
Str("name", node.GivenName).
|
||||
Msg("🤮 Cannot expire node")
|
||||
} else {
|
||||
log.Info().
|
||||
Str("node", node.Hostname).
|
||||
Str("name", node.GivenName).
|
||||
Msg("Node successfully expired")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(expired) > 0 {
|
||||
hsdb.notifier.NotifyAll(types.StateUpdate{
|
||||
Type: types.StatePeerRemoved,
|
||||
Removed: expired,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return started
|
||||
}
|
@@ -15,95 +15,95 @@ import (
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
func (s *Suite) TestGetMachine(c *check.C) {
|
||||
func (s *Suite) TestGetNode(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachine("test", "testmachine")
|
||||
_, err = db.GetNode("test", "testnode")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machine := &types.Machine{
|
||||
node := &types.Node{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
Hostname: "testnode",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
db.db.Save(machine)
|
||||
db.db.Save(node)
|
||||
|
||||
_, err = db.GetMachine("test", "testmachine")
|
||||
_, err = db.GetNode("test", "testnode")
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
||||
func (s *Suite) TestGetMachineByID(c *check.C) {
|
||||
func (s *Suite) TestGetNodeByID(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachineByID(0)
|
||||
_, err = db.GetNodeByID(0)
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machine := types.Machine{
|
||||
node := types.Node{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
Hostname: "testnode",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
db.db.Save(&machine)
|
||||
db.db.Save(&node)
|
||||
|
||||
_, err = db.GetMachineByID(0)
|
||||
_, err = db.GetNodeByID(0)
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
||||
func (s *Suite) TestGetMachineByNodeKey(c *check.C) {
|
||||
func (s *Suite) TestGetNodeByNodeKey(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachineByID(0)
|
||||
_, err = db.GetNodeByID(0)
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
nodeKey := key.NewNode()
|
||||
machineKey := key.NewMachine()
|
||||
|
||||
machine := types.Machine{
|
||||
node := types.Node{
|
||||
ID: 0,
|
||||
MachineKey: util.MachinePublicKeyStripPrefix(machineKey.Public()),
|
||||
NodeKey: util.NodePublicKeyStripPrefix(nodeKey.Public()),
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
Hostname: "testnode",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
db.db.Save(&machine)
|
||||
db.db.Save(&node)
|
||||
|
||||
_, err = db.GetMachineByNodeKey(nodeKey.Public())
|
||||
_, err = db.GetNodeByNodeKey(nodeKey.Public())
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
||||
func (s *Suite) TestGetMachineByAnyNodeKey(c *check.C) {
|
||||
func (s *Suite) TestGetNodeByAnyNodeKey(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachineByID(0)
|
||||
_, err = db.GetNodeByID(0)
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
nodeKey := key.NewNode()
|
||||
@@ -111,41 +111,41 @@ func (s *Suite) TestGetMachineByAnyNodeKey(c *check.C) {
|
||||
|
||||
machineKey := key.NewMachine()
|
||||
|
||||
machine := types.Machine{
|
||||
node := types.Node{
|
||||
ID: 0,
|
||||
MachineKey: util.MachinePublicKeyStripPrefix(machineKey.Public()),
|
||||
NodeKey: util.NodePublicKeyStripPrefix(nodeKey.Public()),
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
Hostname: "testnode",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
db.db.Save(&machine)
|
||||
db.db.Save(&node)
|
||||
|
||||
_, err = db.GetMachineByAnyKey(machineKey.Public(), nodeKey.Public(), oldNodeKey.Public())
|
||||
_, err = db.GetNodeByAnyKey(machineKey.Public(), nodeKey.Public(), oldNodeKey.Public())
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
||||
func (s *Suite) TestHardDeleteMachine(c *check.C) {
|
||||
func (s *Suite) TestHardDeleteNode(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
machine := types.Machine{
|
||||
node := types.Node{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine3",
|
||||
Hostname: "testnode3",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(1),
|
||||
}
|
||||
db.db.Save(&machine)
|
||||
db.db.Save(&node)
|
||||
|
||||
err = db.DeleteMachine(&machine)
|
||||
err = db.DeleteNode(&node)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachine(user.Name, "testmachine3")
|
||||
_, err = db.GetNode(user.Name, "testnode3")
|
||||
c.Assert(err, check.NotNil)
|
||||
}
|
||||
|
||||
@@ -156,33 +156,33 @@ func (s *Suite) TestListPeers(c *check.C) {
|
||||
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachineByID(0)
|
||||
_, err = db.GetNodeByID(0)
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
for index := 0; index <= 10; index++ {
|
||||
machine := types.Machine{
|
||||
node := types.Node{
|
||||
ID: uint64(index),
|
||||
MachineKey: "foo" + strconv.Itoa(index),
|
||||
NodeKey: "bar" + strconv.Itoa(index),
|
||||
DiscoKey: "faa" + strconv.Itoa(index),
|
||||
Hostname: "testmachine" + strconv.Itoa(index),
|
||||
Hostname: "testnode" + strconv.Itoa(index),
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
db.db.Save(&machine)
|
||||
db.db.Save(&node)
|
||||
}
|
||||
|
||||
machine0ByID, err := db.GetMachineByID(0)
|
||||
node0ByID, err := db.GetNodeByID(0)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
peersOfMachine0, err := db.ListPeers(machine0ByID)
|
||||
peersOfNode0, err := db.ListPeers(node0ByID)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
c.Assert(len(peersOfMachine0), check.Equals, 9)
|
||||
c.Assert(peersOfMachine0[0].Hostname, check.Equals, "testmachine2")
|
||||
c.Assert(peersOfMachine0[5].Hostname, check.Equals, "testmachine7")
|
||||
c.Assert(peersOfMachine0[8].Hostname, check.Equals, "testmachine10")
|
||||
c.Assert(len(peersOfNode0), check.Equals, 9)
|
||||
c.Assert(peersOfNode0[0].Hostname, check.Equals, "testnode2")
|
||||
c.Assert(peersOfNode0[5].Hostname, check.Equals, "testnode7")
|
||||
c.Assert(peersOfNode0[8].Hostname, check.Equals, "testnode10")
|
||||
}
|
||||
|
||||
func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
|
||||
@@ -201,24 +201,24 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
|
||||
stor = append(stor, base{user, pak})
|
||||
}
|
||||
|
||||
_, err := db.GetMachineByID(0)
|
||||
_, err := db.GetNodeByID(0)
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
for index := 0; index <= 10; index++ {
|
||||
machine := types.Machine{
|
||||
node := types.Node{
|
||||
ID: uint64(index),
|
||||
MachineKey: "foo" + strconv.Itoa(index),
|
||||
NodeKey: "bar" + strconv.Itoa(index),
|
||||
DiscoKey: "faa" + strconv.Itoa(index),
|
||||
IPAddresses: types.MachineAddresses{
|
||||
IPAddresses: types.NodeAddresses{
|
||||
netip.MustParseAddr(fmt.Sprintf("100.64.0.%v", strconv.Itoa(index+1))),
|
||||
},
|
||||
Hostname: "testmachine" + strconv.Itoa(index),
|
||||
Hostname: "testnode" + strconv.Itoa(index),
|
||||
UserID: stor[index%2].user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(stor[index%2].key.ID),
|
||||
}
|
||||
db.db.Save(&machine)
|
||||
db.db.Save(&node)
|
||||
}
|
||||
|
||||
aclPolicy := &policy.ACLPolicy{
|
||||
@@ -242,80 +242,80 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
|
||||
Tests: []policy.ACLTest{},
|
||||
}
|
||||
|
||||
adminMachine, err := db.GetMachineByID(1)
|
||||
c.Logf("Machine(%v), user: %v", adminMachine.Hostname, adminMachine.User)
|
||||
adminNode, err := db.GetNodeByID(1)
|
||||
c.Logf("Node(%v), user: %v", adminNode.Hostname, adminNode.User)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
testMachine, err := db.GetMachineByID(2)
|
||||
c.Logf("Machine(%v), user: %v", testMachine.Hostname, testMachine.User)
|
||||
testNode, err := db.GetNodeByID(2)
|
||||
c.Logf("Node(%v), user: %v", testNode.Hostname, testNode.User)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
adminPeers, err := db.ListPeers(adminMachine)
|
||||
adminPeers, err := db.ListPeers(adminNode)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
testPeers, err := db.ListPeers(testMachine)
|
||||
testPeers, err := db.ListPeers(testNode)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
adminRules, _, err := policy.GenerateFilterAndSSHRules(aclPolicy, adminMachine, adminPeers)
|
||||
adminRules, _, err := policy.GenerateFilterAndSSHRules(aclPolicy, adminNode, adminPeers)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
testRules, _, err := policy.GenerateFilterAndSSHRules(aclPolicy, testMachine, testPeers)
|
||||
testRules, _, err := policy.GenerateFilterAndSSHRules(aclPolicy, testNode, testPeers)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
peersOfAdminMachine := policy.FilterMachinesByACL(adminMachine, adminPeers, adminRules)
|
||||
peersOfTestMachine := policy.FilterMachinesByACL(testMachine, testPeers, testRules)
|
||||
peersOfAdminNode := policy.FilterNodesByACL(adminNode, adminPeers, adminRules)
|
||||
peersOfTestNode := policy.FilterNodesByACL(testNode, testPeers, testRules)
|
||||
|
||||
c.Log(peersOfTestMachine)
|
||||
c.Assert(len(peersOfTestMachine), check.Equals, 9)
|
||||
c.Assert(peersOfTestMachine[0].Hostname, check.Equals, "testmachine1")
|
||||
c.Assert(peersOfTestMachine[1].Hostname, check.Equals, "testmachine3")
|
||||
c.Assert(peersOfTestMachine[3].Hostname, check.Equals, "testmachine5")
|
||||
c.Log(peersOfTestNode)
|
||||
c.Assert(len(peersOfTestNode), check.Equals, 9)
|
||||
c.Assert(peersOfTestNode[0].Hostname, check.Equals, "testnode1")
|
||||
c.Assert(peersOfTestNode[1].Hostname, check.Equals, "testnode3")
|
||||
c.Assert(peersOfTestNode[3].Hostname, check.Equals, "testnode5")
|
||||
|
||||
c.Log(peersOfAdminMachine)
|
||||
c.Assert(len(peersOfAdminMachine), check.Equals, 9)
|
||||
c.Assert(peersOfAdminMachine[0].Hostname, check.Equals, "testmachine2")
|
||||
c.Assert(peersOfAdminMachine[2].Hostname, check.Equals, "testmachine4")
|
||||
c.Assert(peersOfAdminMachine[5].Hostname, check.Equals, "testmachine7")
|
||||
c.Log(peersOfAdminNode)
|
||||
c.Assert(len(peersOfAdminNode), check.Equals, 9)
|
||||
c.Assert(peersOfAdminNode[0].Hostname, check.Equals, "testnode2")
|
||||
c.Assert(peersOfAdminNode[2].Hostname, check.Equals, "testnode4")
|
||||
c.Assert(peersOfAdminNode[5].Hostname, check.Equals, "testnode7")
|
||||
}
|
||||
|
||||
func (s *Suite) TestExpireMachine(c *check.C) {
|
||||
func (s *Suite) TestExpireNode(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachine("test", "testmachine")
|
||||
_, err = db.GetNode("test", "testnode")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machine := &types.Machine{
|
||||
node := &types.Node{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
Hostname: "testnode",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
Expiry: &time.Time{},
|
||||
}
|
||||
db.db.Save(machine)
|
||||
db.db.Save(node)
|
||||
|
||||
machineFromDB, err := db.GetMachine("test", "testmachine")
|
||||
nodeFromDB, err := db.GetNode("test", "testnode")
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(machineFromDB, check.NotNil)
|
||||
c.Assert(nodeFromDB, check.NotNil)
|
||||
|
||||
c.Assert(machineFromDB.IsExpired(), check.Equals, false)
|
||||
c.Assert(nodeFromDB.IsExpired(), check.Equals, false)
|
||||
|
||||
now := time.Now()
|
||||
err = db.MachineSetExpiry(machineFromDB, now)
|
||||
err = db.NodeSetExpiry(nodeFromDB, now)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
c.Assert(machineFromDB.IsExpired(), check.Equals, true)
|
||||
c.Assert(nodeFromDB.IsExpired(), check.Equals, true)
|
||||
}
|
||||
|
||||
func (s *Suite) TestSerdeAddressStrignSlice(c *check.C) {
|
||||
input := types.MachineAddresses([]netip.Addr{
|
||||
input := types.NodeAddresses([]netip.Addr{
|
||||
netip.MustParseAddr("192.0.2.1"),
|
||||
netip.MustParseAddr("2001:db8::1"),
|
||||
})
|
||||
@@ -325,7 +325,7 @@ func (s *Suite) TestSerdeAddressStrignSlice(c *check.C) {
|
||||
c.Assert(serial, check.Equals, "192.0.2.1,2001:db8::1")
|
||||
}
|
||||
|
||||
var deserialized types.MachineAddresses
|
||||
var deserialized types.NodeAddresses
|
||||
err = deserialized.Scan(serialized)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
@@ -342,12 +342,12 @@ func (s *Suite) TestGenerateGivenName(c *check.C) {
|
||||
pak, err := db.CreatePreAuthKey(user1.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachine("user-1", "testmachine")
|
||||
_, err = db.GetNode("user-1", "testnode")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machine := &types.Machine{
|
||||
node := &types.Node{
|
||||
ID: 0,
|
||||
MachineKey: "machine-key-1",
|
||||
MachineKey: "node-key-1",
|
||||
NodeKey: "node-key-1",
|
||||
DiscoKey: "disco-key-1",
|
||||
Hostname: "hostname-1",
|
||||
@@ -356,27 +356,27 @@ func (s *Suite) TestGenerateGivenName(c *check.C) {
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
db.db.Save(machine)
|
||||
db.db.Save(node)
|
||||
|
||||
givenName, err := db.GenerateGivenName("machine-key-2", "hostname-2")
|
||||
comment := check.Commentf("Same user, unique machines, unique hostnames, no conflict")
|
||||
givenName, err := db.GenerateGivenName("node-key-2", "hostname-2")
|
||||
comment := check.Commentf("Same user, unique nodes, unique hostnames, no conflict")
|
||||
c.Assert(err, check.IsNil, comment)
|
||||
c.Assert(givenName, check.Equals, "hostname-2", comment)
|
||||
|
||||
givenName, err = db.GenerateGivenName("machine-key-1", "hostname-1")
|
||||
comment = check.Commentf("Same user, same machine, same hostname, no conflict")
|
||||
givenName, err = db.GenerateGivenName("node-key-1", "hostname-1")
|
||||
comment = check.Commentf("Same user, same node, same hostname, no conflict")
|
||||
c.Assert(err, check.IsNil, comment)
|
||||
c.Assert(givenName, check.Equals, "hostname-1", comment)
|
||||
|
||||
givenName, err = db.GenerateGivenName("machine-key-2", "hostname-1")
|
||||
comment = check.Commentf("Same user, unique machines, same hostname, conflict")
|
||||
givenName, err = db.GenerateGivenName("node-key-2", "hostname-1")
|
||||
comment = check.Commentf("Same user, unique nodes, same hostname, conflict")
|
||||
c.Assert(err, check.IsNil, comment)
|
||||
c.Assert(givenName, check.Matches, fmt.Sprintf("^hostname-1-[a-z0-9]{%d}$", MachineGivenNameHashLength), comment)
|
||||
c.Assert(givenName, check.Matches, fmt.Sprintf("^hostname-1-[a-z0-9]{%d}$", NodeGivenNameHashLength), comment)
|
||||
|
||||
givenName, err = db.GenerateGivenName("machine-key-2", "hostname-1")
|
||||
comment = check.Commentf("Unique users, unique machines, same hostname, conflict")
|
||||
givenName, err = db.GenerateGivenName("node-key-2", "hostname-1")
|
||||
comment = check.Commentf("Unique users, unique nodes, same hostname, conflict")
|
||||
c.Assert(err, check.IsNil, comment)
|
||||
c.Assert(givenName, check.Matches, fmt.Sprintf("^hostname-1-[a-z0-9]{%d}$", MachineGivenNameHashLength), comment)
|
||||
c.Assert(givenName, check.Matches, fmt.Sprintf("^hostname-1-[a-z0-9]{%d}$", NodeGivenNameHashLength), comment)
|
||||
}
|
||||
|
||||
func (s *Suite) TestSetTags(c *check.C) {
|
||||
@@ -386,37 +386,37 @@ func (s *Suite) TestSetTags(c *check.C) {
|
||||
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachine("test", "testmachine")
|
||||
_, err = db.GetNode("test", "testnode")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machine := &types.Machine{
|
||||
node := &types.Node{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
Hostname: "testnode",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
db.db.Save(machine)
|
||||
db.db.Save(node)
|
||||
|
||||
// assign simple tags
|
||||
sTags := []string{"tag:test", "tag:foo"}
|
||||
err = db.SetTags(machine, sTags)
|
||||
err = db.SetTags(node, sTags)
|
||||
c.Assert(err, check.IsNil)
|
||||
machine, err = db.GetMachine("test", "testmachine")
|
||||
node, err = db.GetNode("test", "testnode")
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(machine.ForcedTags, check.DeepEquals, types.StringList(sTags))
|
||||
c.Assert(node.ForcedTags, check.DeepEquals, types.StringList(sTags))
|
||||
|
||||
// assign duplicat tags, expect no errors but no doubles in DB
|
||||
eTags := []string{"tag:bar", "tag:test", "tag:unknown", "tag:test"}
|
||||
err = db.SetTags(machine, eTags)
|
||||
err = db.SetTags(node, eTags)
|
||||
c.Assert(err, check.IsNil)
|
||||
machine, err = db.GetMachine("test", "testmachine")
|
||||
node, err = db.GetNode("test", "testnode")
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(
|
||||
machine.ForcedTags,
|
||||
node.ForcedTags,
|
||||
check.DeepEquals,
|
||||
types.StringList([]string{"tag:bar", "tag:test", "tag:unknown"}),
|
||||
)
|
||||
@@ -434,16 +434,16 @@ func TestHeadscale_generateGivenName(t *testing.T) {
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "simple machine name generation",
|
||||
name: "simple node name generation",
|
||||
args: args{
|
||||
suppliedName: "testmachine",
|
||||
suppliedName: "testnode",
|
||||
randomSuffix: false,
|
||||
},
|
||||
want: regexp.MustCompile("^testmachine$"),
|
||||
want: regexp.MustCompile("^testnode$"),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "machine name with 53 chars",
|
||||
name: "node name with 53 chars",
|
||||
args: args{
|
||||
suppliedName: "testmaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaachine",
|
||||
randomSuffix: false,
|
||||
@@ -452,48 +452,48 @@ func TestHeadscale_generateGivenName(t *testing.T) {
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "machine name with 63 chars",
|
||||
name: "node name with 63 chars",
|
||||
args: args{
|
||||
suppliedName: "machineeee12345678901234567890123456789012345678901234567890123",
|
||||
suppliedName: "nodeeeeeee12345678901234567890123456789012345678901234567890123",
|
||||
randomSuffix: false,
|
||||
},
|
||||
want: regexp.MustCompile("^machineeee12345678901234567890123456789012345678901234567890123$"),
|
||||
want: regexp.MustCompile("^nodeeeeeee12345678901234567890123456789012345678901234567890123$"),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "machine name with 64 chars",
|
||||
name: "node name with 64 chars",
|
||||
args: args{
|
||||
suppliedName: "machineeee123456789012345678901234567890123456789012345678901234",
|
||||
suppliedName: "nodeeeeeee123456789012345678901234567890123456789012345678901234",
|
||||
randomSuffix: false,
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "machine name with 73 chars",
|
||||
name: "node name with 73 chars",
|
||||
args: args{
|
||||
suppliedName: "machineeee123456789012345678901234567890123456789012345678901234567890123",
|
||||
suppliedName: "nodeeeeeee123456789012345678901234567890123456789012345678901234567890123",
|
||||
randomSuffix: false,
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "machine name with random suffix",
|
||||
name: "node name with random suffix",
|
||||
args: args{
|
||||
suppliedName: "test",
|
||||
randomSuffix: true,
|
||||
},
|
||||
want: regexp.MustCompile(fmt.Sprintf("^test-[a-z0-9]{%d}$", MachineGivenNameHashLength)),
|
||||
want: regexp.MustCompile(fmt.Sprintf("^test-[a-z0-9]{%d}$", NodeGivenNameHashLength)),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "machine name with 63 chars with random suffix",
|
||||
name: "node name with 63 chars with random suffix",
|
||||
args: args{
|
||||
suppliedName: "machineeee12345678901234567890123456789012345678901234567890123",
|
||||
suppliedName: "nodeeee12345678901234567890123456789012345678901234567890123",
|
||||
randomSuffix: true,
|
||||
},
|
||||
want: regexp.MustCompile(fmt.Sprintf("^machineeee1234567890123456789012345678901234567890123-[a-z0-9]{%d}$", MachineGivenNameHashLength)),
|
||||
want: regexp.MustCompile(fmt.Sprintf("^nodeeee1234567890123456789012345678901234567890123456-[a-z0-9]{%d}$", NodeGivenNameHashLength)),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
@@ -572,7 +572,7 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) {
|
||||
// Check if a subprefix of an autoapproved route is approved
|
||||
route2 := netip.MustParsePrefix("10.11.0.0/24")
|
||||
|
||||
machine := types.Machine{
|
||||
node := types.Node{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
NodeKey: util.NodePublicKeyStripPrefix(nodeKey.Public()),
|
||||
@@ -588,18 +588,18 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) {
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")},
|
||||
}
|
||||
|
||||
db.db.Save(&machine)
|
||||
db.db.Save(&node)
|
||||
|
||||
err = db.SaveMachineRoutes(&machine)
|
||||
err = db.SaveNodeRoutes(&node)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machine0ByID, err := db.GetMachineByID(0)
|
||||
node0ByID, err := db.GetNodeByID(0)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = db.EnableAutoApprovedRoutes(pol, machine0ByID)
|
||||
err = db.EnableAutoApprovedRoutes(pol, node0ByID)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enabledRoutes, err := db.GetEnabledRoutes(machine0ByID)
|
||||
enabledRoutes, err := db.GetEnabledRoutes(node0ByID)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(enabledRoutes, check.HasLen, 4)
|
||||
}
|
@@ -203,15 +203,15 @@ func (hsdb *HSDatabase) ValidatePreAuthKey(k string) (*types.PreAuthKey, error)
|
||||
return &pak, nil
|
||||
}
|
||||
|
||||
machines := types.Machines{}
|
||||
nodes := types.Nodes{}
|
||||
if err := hsdb.db.
|
||||
Preload("AuthKey").
|
||||
Where(&types.Machine{AuthKeyID: uint(pak.ID)}).
|
||||
Find(&machines).Error; err != nil {
|
||||
Where(&types.Node{AuthKeyID: uint(pak.ID)}).
|
||||
Find(&nodes).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(machines) != 0 || pak.Used {
|
||||
if len(nodes) != 0 || pak.Used {
|
||||
return nil, ErrSingleUseAuthKeyHasBeenUsed
|
||||
}
|
||||
|
||||
|
@@ -75,7 +75,7 @@ func (*Suite) TestAlreadyUsedKey(c *check.C) {
|
||||
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machine := types.Machine{
|
||||
node := types.Node{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
@@ -85,7 +85,7 @@ func (*Suite) TestAlreadyUsedKey(c *check.C) {
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
db.db.Save(&machine)
|
||||
db.db.Save(&node)
|
||||
|
||||
key, err := db.ValidatePreAuthKey(pak.Key)
|
||||
c.Assert(err, check.Equals, ErrSingleUseAuthKeyHasBeenUsed)
|
||||
@@ -99,7 +99,7 @@ func (*Suite) TestReusableBeingUsedKey(c *check.C) {
|
||||
pak, err := db.CreatePreAuthKey(user.Name, true, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machine := types.Machine{
|
||||
node := types.Node{
|
||||
ID: 1,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
@@ -109,7 +109,7 @@ func (*Suite) TestReusableBeingUsedKey(c *check.C) {
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
db.db.Save(&machine)
|
||||
db.db.Save(&node)
|
||||
|
||||
key, err := db.ValidatePreAuthKey(pak.Key)
|
||||
c.Assert(err, check.IsNil)
|
||||
@@ -136,7 +136,7 @@ func (*Suite) TestEphemeralKey(c *check.C) {
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
now := time.Now().Add(-time.Second * 30)
|
||||
machine := types.Machine{
|
||||
node := types.Node{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
@@ -147,19 +147,19 @@ func (*Suite) TestEphemeralKey(c *check.C) {
|
||||
LastSeen: &now,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
db.db.Save(&machine)
|
||||
db.db.Save(&node)
|
||||
|
||||
_, err = db.ValidatePreAuthKey(pak.Key)
|
||||
// Ephemeral keys are by definition reusable
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachine("test7", "testest")
|
||||
_, err = db.GetNode("test7", "testest")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
db.ExpireEphemeralMachines(time.Second * 20)
|
||||
db.ExpireEphemeralNodes(time.Second * 20)
|
||||
|
||||
// The machine record should have been deleted
|
||||
_, err = db.GetMachine("test7", "testest")
|
||||
_, err = db.GetNode("test7", "testest")
|
||||
c.Assert(err, check.NotNil)
|
||||
}
|
||||
|
||||
|
@@ -21,7 +21,7 @@ func (hsdb *HSDatabase) GetRoutes() (types.Routes, error) {
|
||||
|
||||
func (hsdb *HSDatabase) getRoutes() (types.Routes, error) {
|
||||
var routes types.Routes
|
||||
err := hsdb.db.Preload("Machine").Find(&routes).Error
|
||||
err := hsdb.db.Preload("Node").Find(&routes).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -29,18 +29,18 @@ func (hsdb *HSDatabase) getRoutes() (types.Routes, error) {
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) GetMachineAdvertisedRoutes(machine *types.Machine) (types.Routes, error) {
|
||||
func (hsdb *HSDatabase) GetNodeAdvertisedRoutes(node *types.Node) (types.Routes, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
return hsdb.getMachineAdvertisedRoutes(machine)
|
||||
return hsdb.getNodeAdvertisedRoutes(node)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) getMachineAdvertisedRoutes(machine *types.Machine) (types.Routes, error) {
|
||||
func (hsdb *HSDatabase) getNodeAdvertisedRoutes(node *types.Node) (types.Routes, error) {
|
||||
var routes types.Routes
|
||||
err := hsdb.db.
|
||||
Preload("Machine").
|
||||
Where("machine_id = ? AND advertised = true", machine.ID).
|
||||
Preload("Node").
|
||||
Where("node_id = ? AND advertised = true", node.ID).
|
||||
Find(&routes).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -49,18 +49,18 @@ func (hsdb *HSDatabase) getMachineAdvertisedRoutes(machine *types.Machine) (type
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) GetMachineRoutes(m *types.Machine) (types.Routes, error) {
|
||||
func (hsdb *HSDatabase) GetNodeRoutes(node *types.Node) (types.Routes, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
return hsdb.getMachineRoutes(m)
|
||||
return hsdb.getNodeRoutes(node)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) getMachineRoutes(m *types.Machine) (types.Routes, error) {
|
||||
func (hsdb *HSDatabase) getNodeRoutes(node *types.Node) (types.Routes, error) {
|
||||
var routes types.Routes
|
||||
err := hsdb.db.
|
||||
Preload("Machine").
|
||||
Where("machine_id = ?", m.ID).
|
||||
Preload("Node").
|
||||
Where("node_id = ?", node.ID).
|
||||
Find(&routes).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
@@ -78,7 +78,7 @@ func (hsdb *HSDatabase) GetRoute(id uint64) (*types.Route, error) {
|
||||
|
||||
func (hsdb *HSDatabase) getRoute(id uint64) (*types.Route, error) {
|
||||
var route types.Route
|
||||
err := hsdb.db.Preload("Machine").First(&route, id).Error
|
||||
err := hsdb.db.Preload("Node").First(&route, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -104,13 +104,13 @@ func (hsdb *HSDatabase) enableRoute(id uint64) error {
|
||||
// https://github.com/juanfont/headscale/issues/804#issuecomment-1399314002
|
||||
if route.IsExitRoute() {
|
||||
return hsdb.enableRoutes(
|
||||
&route.Machine,
|
||||
&route.Node,
|
||||
types.ExitRouteV4.String(),
|
||||
types.ExitRouteV6.String(),
|
||||
)
|
||||
}
|
||||
|
||||
return hsdb.enableRoutes(&route.Machine, netip.Prefix(route.Prefix).String())
|
||||
return hsdb.enableRoutes(&route.Node, netip.Prefix(route.Prefix).String())
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) DisableRoute(id uint64) error {
|
||||
@@ -136,7 +136,7 @@ func (hsdb *HSDatabase) DisableRoute(id uint64) error {
|
||||
return hsdb.handlePrimarySubnetFailover()
|
||||
}
|
||||
|
||||
routes, err := hsdb.getMachineRoutes(&route.Machine)
|
||||
routes, err := hsdb.getNodeRoutes(&route.Node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -175,7 +175,7 @@ func (hsdb *HSDatabase) DeleteRoute(id uint64) error {
|
||||
return hsdb.handlePrimarySubnetFailover()
|
||||
}
|
||||
|
||||
routes, err := hsdb.getMachineRoutes(&route.Machine)
|
||||
routes, err := hsdb.getNodeRoutes(&route.Node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -194,8 +194,8 @@ func (hsdb *HSDatabase) DeleteRoute(id uint64) error {
|
||||
return hsdb.handlePrimarySubnetFailover()
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) deleteMachineRoutes(m *types.Machine) error {
|
||||
routes, err := hsdb.getMachineRoutes(m)
|
||||
func (hsdb *HSDatabase) deleteNodeRoutes(node *types.Node) error {
|
||||
routes, err := hsdb.getNodeRoutes(node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -209,14 +209,14 @@ func (hsdb *HSDatabase) deleteMachineRoutes(m *types.Machine) error {
|
||||
return hsdb.handlePrimarySubnetFailover()
|
||||
}
|
||||
|
||||
// isUniquePrefix returns if there is another machine providing the same route already.
|
||||
// isUniquePrefix returns if there is another node providing the same route already.
|
||||
func (hsdb *HSDatabase) isUniquePrefix(route types.Route) bool {
|
||||
var count int64
|
||||
hsdb.db.
|
||||
Model(&types.Route{}).
|
||||
Where("prefix = ? AND machine_id != ? AND advertised = ? AND enabled = ?",
|
||||
Where("prefix = ? AND node_id != ? AND advertised = ? AND enabled = ?",
|
||||
route.Prefix,
|
||||
route.MachineID,
|
||||
route.NodeID,
|
||||
true, true).Count(&count)
|
||||
|
||||
return count == 0
|
||||
@@ -225,7 +225,7 @@ func (hsdb *HSDatabase) isUniquePrefix(route types.Route) bool {
|
||||
func (hsdb *HSDatabase) getPrimaryRoute(prefix netip.Prefix) (*types.Route, error) {
|
||||
var route types.Route
|
||||
err := hsdb.db.
|
||||
Preload("Machine").
|
||||
Preload("Node").
|
||||
Where("prefix = ? AND advertised = ? AND enabled = ? AND is_primary = ?", types.IPPrefix(prefix), true, true, true).
|
||||
First(&route).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -239,16 +239,16 @@ func (hsdb *HSDatabase) getPrimaryRoute(prefix netip.Prefix) (*types.Route, erro
|
||||
return &route, nil
|
||||
}
|
||||
|
||||
// getMachinePrimaryRoutes returns the routes that are enabled and marked as primary (for subnet failover)
|
||||
// getNodePrimaryRoutes returns the routes that are enabled and marked as primary (for subnet failover)
|
||||
// Exit nodes are not considered for this, as they are never marked as Primary.
|
||||
func (hsdb *HSDatabase) GetMachinePrimaryRoutes(machine *types.Machine) (types.Routes, error) {
|
||||
func (hsdb *HSDatabase) GetNodePrimaryRoutes(node *types.Node) (types.Routes, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
var routes types.Routes
|
||||
err := hsdb.db.
|
||||
Preload("Machine").
|
||||
Where("machine_id = ? AND advertised = ? AND enabled = ? AND is_primary = ?", machine.ID, true, true, true).
|
||||
Preload("Node").
|
||||
Where("node_id = ? AND advertised = ? AND enabled = ? AND is_primary = ?", node.ID, true, true, true).
|
||||
Find(&routes).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -257,29 +257,29 @@ func (hsdb *HSDatabase) GetMachinePrimaryRoutes(machine *types.Machine) (types.R
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
// SaveMachineRoutes takes a machine and updates the database with
|
||||
// SaveNodeRoutes takes a node and updates the database with
|
||||
// the new routes.
|
||||
func (hsdb *HSDatabase) SaveMachineRoutes(machine *types.Machine) error {
|
||||
func (hsdb *HSDatabase) SaveNodeRoutes(node *types.Node) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
return hsdb.saveMachineRoutes(machine)
|
||||
return hsdb.saveNodeRoutes(node)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) saveMachineRoutes(machine *types.Machine) error {
|
||||
func (hsdb *HSDatabase) saveNodeRoutes(node *types.Node) error {
|
||||
currentRoutes := types.Routes{}
|
||||
err := hsdb.db.Where("machine_id = ?", machine.ID).Find(¤tRoutes).Error
|
||||
err := hsdb.db.Where("node_id = ?", node.ID).Find(¤tRoutes).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
advertisedRoutes := map[netip.Prefix]bool{}
|
||||
for _, prefix := range machine.HostInfo.RoutableIPs {
|
||||
for _, prefix := range node.HostInfo.RoutableIPs {
|
||||
advertisedRoutes[prefix] = false
|
||||
}
|
||||
|
||||
log.Trace().
|
||||
Str("machine", machine.Hostname).
|
||||
Str("node", node.Hostname).
|
||||
Interface("advertisedRoutes", advertisedRoutes).
|
||||
Interface("currentRoutes", currentRoutes).
|
||||
Msg("updating routes")
|
||||
@@ -307,7 +307,7 @@ func (hsdb *HSDatabase) saveMachineRoutes(machine *types.Machine) error {
|
||||
for prefix, exists := range advertisedRoutes {
|
||||
if !exists {
|
||||
route := types.Route{
|
||||
MachineID: machine.ID,
|
||||
NodeID: node.ID,
|
||||
Prefix: types.IPPrefix(prefix),
|
||||
Advertised: true,
|
||||
Enabled: false,
|
||||
@@ -333,27 +333,27 @@ func (hsdb *HSDatabase) handlePrimarySubnetFailover() error {
|
||||
// first, get all the enabled routes
|
||||
var routes types.Routes
|
||||
err := hsdb.db.
|
||||
Preload("Machine").
|
||||
Preload("Node").
|
||||
Where("advertised = ? AND enabled = ?", true, true).
|
||||
Find(&routes).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Error().Err(err).Msg("error getting routes")
|
||||
}
|
||||
|
||||
changedMachines := make(types.Machines, 0)
|
||||
changedNodes := make(types.Nodes, 0)
|
||||
for pos, route := range routes {
|
||||
if route.IsExitRoute() {
|
||||
continue
|
||||
}
|
||||
|
||||
machine := &route.Machine
|
||||
node := &route.Node
|
||||
|
||||
if !route.IsPrimary {
|
||||
_, err := hsdb.getPrimaryRoute(netip.Prefix(route.Prefix))
|
||||
if hsdb.isUniquePrefix(route) || errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Info().
|
||||
Str("prefix", netip.Prefix(route.Prefix).String()).
|
||||
Str("machine", route.Machine.GivenName).
|
||||
Str("node", route.Node.GivenName).
|
||||
Msg("Setting primary route")
|
||||
routes[pos].IsPrimary = true
|
||||
err := hsdb.db.Save(&routes[pos]).Error
|
||||
@@ -363,30 +363,30 @@ func (hsdb *HSDatabase) handlePrimarySubnetFailover() error {
|
||||
return err
|
||||
}
|
||||
|
||||
changedMachines = append(changedMachines, machine)
|
||||
changedNodes = append(changedNodes, node)
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if route.IsPrimary {
|
||||
if route.Machine.IsOnline() {
|
||||
if route.Node.IsOnline() {
|
||||
continue
|
||||
}
|
||||
|
||||
// machine offline, find a new primary
|
||||
// node offline, find a new primary
|
||||
log.Info().
|
||||
Str("machine", route.Machine.Hostname).
|
||||
Str("node", route.Node.Hostname).
|
||||
Str("prefix", netip.Prefix(route.Prefix).String()).
|
||||
Msgf("machine offline, finding a new primary subnet")
|
||||
Msgf("node offline, finding a new primary subnet")
|
||||
|
||||
// find a new primary route
|
||||
var newPrimaryRoutes types.Routes
|
||||
err := hsdb.db.
|
||||
Preload("Machine").
|
||||
Where("prefix = ? AND machine_id != ? AND advertised = ? AND enabled = ?",
|
||||
Preload("Node").
|
||||
Where("prefix = ? AND node_id != ? AND advertised = ? AND enabled = ?",
|
||||
route.Prefix,
|
||||
route.MachineID,
|
||||
route.NodeID,
|
||||
true, true).
|
||||
Find(&newPrimaryRoutes).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -397,7 +397,7 @@ func (hsdb *HSDatabase) handlePrimarySubnetFailover() error {
|
||||
|
||||
var newPrimaryRoute *types.Route
|
||||
for pos, r := range newPrimaryRoutes {
|
||||
if r.Machine.IsOnline() {
|
||||
if r.Node.IsOnline() {
|
||||
newPrimaryRoute = &newPrimaryRoutes[pos]
|
||||
|
||||
break
|
||||
@@ -406,7 +406,7 @@ func (hsdb *HSDatabase) handlePrimarySubnetFailover() error {
|
||||
|
||||
if newPrimaryRoute == nil {
|
||||
log.Warn().
|
||||
Str("machine", route.Machine.Hostname).
|
||||
Str("node", route.Node.Hostname).
|
||||
Str("prefix", netip.Prefix(route.Prefix).String()).
|
||||
Msgf("no alternative primary route found")
|
||||
|
||||
@@ -414,9 +414,9 @@ func (hsdb *HSDatabase) handlePrimarySubnetFailover() error {
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("old_machine", route.Machine.Hostname).
|
||||
Str("old_node", route.Node.Hostname).
|
||||
Str("prefix", netip.Prefix(route.Prefix).String()).
|
||||
Str("new_machine", newPrimaryRoute.Machine.Hostname).
|
||||
Str("new_node", newPrimaryRoute.Node.Hostname).
|
||||
Msgf("found new primary route")
|
||||
|
||||
// disable the old primary route
|
||||
@@ -437,39 +437,39 @@ func (hsdb *HSDatabase) handlePrimarySubnetFailover() error {
|
||||
return err
|
||||
}
|
||||
|
||||
changedMachines = append(changedMachines, machine)
|
||||
changedNodes = append(changedNodes, node)
|
||||
}
|
||||
}
|
||||
|
||||
if len(changedMachines) > 0 {
|
||||
if len(changedNodes) > 0 {
|
||||
hsdb.notifier.NotifyAll(types.StateUpdate{
|
||||
Type: types.StatePeerChanged,
|
||||
Changed: changedMachines,
|
||||
Changed: changedNodes,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnableAutoApprovedRoutes enables any routes advertised by a machine that match the ACL autoApprovers policy.
|
||||
// EnableAutoApprovedRoutes enables any routes advertised by a node that match the ACL autoApprovers policy.
|
||||
func (hsdb *HSDatabase) EnableAutoApprovedRoutes(
|
||||
aclPolicy *policy.ACLPolicy,
|
||||
machine *types.Machine,
|
||||
node *types.Node,
|
||||
) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
if len(machine.IPAddresses) == 0 {
|
||||
return nil // This machine has no IPAddresses, so can't possibly match any autoApprovers ACLs
|
||||
if len(node.IPAddresses) == 0 {
|
||||
return nil // This node has no IPAddresses, so can't possibly match any autoApprovers ACLs
|
||||
}
|
||||
|
||||
routes, err := hsdb.getMachineAdvertisedRoutes(machine)
|
||||
routes, err := hsdb.getNodeAdvertisedRoutes(node)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Could not get advertised routes for machine")
|
||||
Str("node", node.Hostname).
|
||||
Msg("Could not get advertised routes for node")
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -487,18 +487,18 @@ func (hsdb *HSDatabase) EnableAutoApprovedRoutes(
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Str("advertisedRoute", advertisedRoute.String()).
|
||||
Uint64("machineId", machine.ID).
|
||||
Uint64("nodeId", node.ID).
|
||||
Msg("Failed to resolve autoApprovers for advertised route")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
for _, approvedAlias := range routeApprovers {
|
||||
if approvedAlias == machine.User.Name {
|
||||
if approvedAlias == node.User.Name {
|
||||
approvedRoutes = append(approvedRoutes, advertisedRoute)
|
||||
} else {
|
||||
// TODO(kradalby): figure out how to get this to depend on less stuff
|
||||
approvedIps, err := aclPolicy.ExpandAlias(types.Machines{machine}, approvedAlias)
|
||||
approvedIps, err := aclPolicy.ExpandAlias(types.Nodes{node}, approvedAlias)
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Str("alias", approvedAlias).
|
||||
@@ -507,8 +507,8 @@ func (hsdb *HSDatabase) EnableAutoApprovedRoutes(
|
||||
return err
|
||||
}
|
||||
|
||||
// approvedIPs should contain all of machine's IPs if it matches the rule, so check for first
|
||||
if approvedIps.Contains(machine.IPAddresses[0]) {
|
||||
// approvedIPs should contain all of node's IPs if it matches the rule, so check for first
|
||||
if approvedIps.Contains(node.IPAddresses[0]) {
|
||||
approvedRoutes = append(approvedRoutes, advertisedRoute)
|
||||
}
|
||||
}
|
||||
@@ -520,7 +520,7 @@ func (hsdb *HSDatabase) EnableAutoApprovedRoutes(
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Str("approvedRoute", approvedRoute.String()).
|
||||
Uint64("machineId", machine.ID).
|
||||
Uint64("nodeId", node.ID).
|
||||
Msg("Failed to enable approved route")
|
||||
|
||||
return err
|
||||
|
@@ -17,7 +17,7 @@ func (s *Suite) TestGetRoutes(c *check.C) {
|
||||
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachine("test", "test_get_route_machine")
|
||||
_, err = db.GetNode("test", "test_get_route_node")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
route, err := netip.ParsePrefix("10.0.0.0/24")
|
||||
@@ -27,30 +27,30 @@ func (s *Suite) TestGetRoutes(c *check.C) {
|
||||
RoutableIPs: []netip.Prefix{route},
|
||||
}
|
||||
|
||||
machine := types.Machine{
|
||||
node := types.Node{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "test_get_route_machine",
|
||||
Hostname: "test_get_route_node",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
HostInfo: types.HostInfo(hostInfo),
|
||||
}
|
||||
db.db.Save(&machine)
|
||||
db.db.Save(&node)
|
||||
|
||||
err = db.SaveMachineRoutes(&machine)
|
||||
err = db.SaveNodeRoutes(&node)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
advertisedRoutes, err := db.GetAdvertisedRoutes(&machine)
|
||||
advertisedRoutes, err := db.GetAdvertisedRoutes(&node)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(advertisedRoutes), check.Equals, 1)
|
||||
|
||||
err = db.enableRoutes(&machine, "192.168.0.0/24")
|
||||
err = db.enableRoutes(&node, "192.168.0.0/24")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
err = db.enableRoutes(&machine, "10.0.0.0/24")
|
||||
err = db.enableRoutes(&node, "10.0.0.0/24")
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) {
|
||||
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachine("test", "test_enable_route_machine")
|
||||
_, err = db.GetNode("test", "test_enable_route_node")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
route, err := netip.ParsePrefix(
|
||||
@@ -78,53 +78,53 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) {
|
||||
RoutableIPs: []netip.Prefix{route, route2},
|
||||
}
|
||||
|
||||
machine := types.Machine{
|
||||
node := types.Node{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "test_enable_route_machine",
|
||||
Hostname: "test_enable_route_node",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
HostInfo: types.HostInfo(hostInfo),
|
||||
}
|
||||
db.db.Save(&machine)
|
||||
db.db.Save(&node)
|
||||
|
||||
err = db.SaveMachineRoutes(&machine)
|
||||
err = db.SaveNodeRoutes(&node)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
availableRoutes, err := db.GetAdvertisedRoutes(&machine)
|
||||
availableRoutes, err := db.GetAdvertisedRoutes(&node)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(availableRoutes), check.Equals, 2)
|
||||
|
||||
noEnabledRoutes, err := db.GetEnabledRoutes(&machine)
|
||||
noEnabledRoutes, err := db.GetEnabledRoutes(&node)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(noEnabledRoutes), check.Equals, 0)
|
||||
|
||||
err = db.enableRoutes(&machine, "192.168.0.0/24")
|
||||
err = db.enableRoutes(&node, "192.168.0.0/24")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
err = db.enableRoutes(&machine, "10.0.0.0/24")
|
||||
err = db.enableRoutes(&node, "10.0.0.0/24")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enabledRoutes, err := db.GetEnabledRoutes(&machine)
|
||||
enabledRoutes, err := db.GetEnabledRoutes(&node)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutes), check.Equals, 1)
|
||||
|
||||
// Adding it twice will just let it pass through
|
||||
err = db.enableRoutes(&machine, "10.0.0.0/24")
|
||||
err = db.enableRoutes(&node, "10.0.0.0/24")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enableRoutesAfterDoubleApply, err := db.GetEnabledRoutes(&machine)
|
||||
enableRoutesAfterDoubleApply, err := db.GetEnabledRoutes(&node)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enableRoutesAfterDoubleApply), check.Equals, 1)
|
||||
|
||||
err = db.enableRoutes(&machine, "150.0.10.0/25")
|
||||
err = db.enableRoutes(&node, "150.0.10.0/25")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enabledRoutesWithAdditionalRoute, err := db.GetEnabledRoutes(&machine)
|
||||
enabledRoutesWithAdditionalRoute, err := db.GetEnabledRoutes(&node)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutesWithAdditionalRoute), check.Equals, 2)
|
||||
}
|
||||
@@ -136,7 +136,7 @@ func (s *Suite) TestIsUniquePrefix(c *check.C) {
|
||||
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachine("test", "test_enable_route_machine")
|
||||
_, err = db.GetNode("test", "test_enable_route_node")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
route, err := netip.ParsePrefix(
|
||||
@@ -152,63 +152,63 @@ func (s *Suite) TestIsUniquePrefix(c *check.C) {
|
||||
hostInfo1 := tailcfg.Hostinfo{
|
||||
RoutableIPs: []netip.Prefix{route, route2},
|
||||
}
|
||||
machine1 := types.Machine{
|
||||
node1 := types.Node{
|
||||
ID: 1,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "test_enable_route_machine",
|
||||
Hostname: "test_enable_route_node",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
HostInfo: types.HostInfo(hostInfo1),
|
||||
}
|
||||
db.db.Save(&machine1)
|
||||
db.db.Save(&node1)
|
||||
|
||||
err = db.SaveMachineRoutes(&machine1)
|
||||
err = db.SaveNodeRoutes(&node1)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = db.enableRoutes(&machine1, route.String())
|
||||
err = db.enableRoutes(&node1, route.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = db.enableRoutes(&machine1, route2.String())
|
||||
err = db.enableRoutes(&node1, route2.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
hostInfo2 := tailcfg.Hostinfo{
|
||||
RoutableIPs: []netip.Prefix{route2},
|
||||
}
|
||||
machine2 := types.Machine{
|
||||
node2 := types.Node{
|
||||
ID: 2,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "test_enable_route_machine",
|
||||
Hostname: "test_enable_route_node",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
HostInfo: types.HostInfo(hostInfo2),
|
||||
}
|
||||
db.db.Save(&machine2)
|
||||
db.db.Save(&node2)
|
||||
|
||||
err = db.SaveMachineRoutes(&machine2)
|
||||
err = db.SaveNodeRoutes(&node2)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = db.enableRoutes(&machine2, route2.String())
|
||||
err = db.enableRoutes(&node2, route2.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enabledRoutes1, err := db.GetEnabledRoutes(&machine1)
|
||||
enabledRoutes1, err := db.GetEnabledRoutes(&node1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutes1), check.Equals, 2)
|
||||
|
||||
enabledRoutes2, err := db.GetEnabledRoutes(&machine2)
|
||||
enabledRoutes2, err := db.GetEnabledRoutes(&node2)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutes2), check.Equals, 1)
|
||||
|
||||
routes, err := db.GetMachinePrimaryRoutes(&machine1)
|
||||
routes, err := db.GetNodePrimaryRoutes(&node1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 2)
|
||||
|
||||
routes, err = db.GetMachinePrimaryRoutes(&machine2)
|
||||
routes, err = db.GetNodePrimaryRoutes(&node2)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 0)
|
||||
}
|
||||
@@ -220,7 +220,7 @@ func (s *Suite) TestSubnetFailover(c *check.C) {
|
||||
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachine("test", "test_enable_route_machine")
|
||||
_, err = db.GetNode("test", "test_enable_route_node")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
prefix, err := netip.ParsePrefix(
|
||||
@@ -238,119 +238,119 @@ func (s *Suite) TestSubnetFailover(c *check.C) {
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
machine1 := types.Machine{
|
||||
node1 := types.Node{
|
||||
ID: 1,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "test_enable_route_machine",
|
||||
Hostname: "test_enable_route_node",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
HostInfo: types.HostInfo(hostInfo1),
|
||||
LastSeen: &now,
|
||||
}
|
||||
db.db.Save(&machine1)
|
||||
db.db.Save(&node1)
|
||||
|
||||
err = db.SaveMachineRoutes(&machine1)
|
||||
err = db.SaveNodeRoutes(&node1)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = db.enableRoutes(&machine1, prefix.String())
|
||||
err = db.enableRoutes(&node1, prefix.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = db.enableRoutes(&machine1, prefix2.String())
|
||||
err = db.enableRoutes(&node1, prefix2.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = db.HandlePrimarySubnetFailover()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enabledRoutes1, err := db.GetEnabledRoutes(&machine1)
|
||||
enabledRoutes1, err := db.GetEnabledRoutes(&node1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutes1), check.Equals, 2)
|
||||
|
||||
route, err := db.getPrimaryRoute(prefix)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(route.MachineID, check.Equals, machine1.ID)
|
||||
c.Assert(route.NodeID, check.Equals, node1.ID)
|
||||
|
||||
hostInfo2 := tailcfg.Hostinfo{
|
||||
RoutableIPs: []netip.Prefix{prefix2},
|
||||
}
|
||||
machine2 := types.Machine{
|
||||
node2 := types.Node{
|
||||
ID: 2,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "test_enable_route_machine",
|
||||
Hostname: "test_enable_route_node",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
HostInfo: types.HostInfo(hostInfo2),
|
||||
LastSeen: &now,
|
||||
}
|
||||
db.db.Save(&machine2)
|
||||
db.db.Save(&node2)
|
||||
|
||||
err = db.SaveMachineRoutes(&machine2)
|
||||
err = db.saveNodeRoutes(&node2)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = db.enableRoutes(&machine2, prefix2.String())
|
||||
err = db.enableRoutes(&node2, prefix2.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = db.HandlePrimarySubnetFailover()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enabledRoutes1, err = db.GetEnabledRoutes(&machine1)
|
||||
enabledRoutes1, err = db.GetEnabledRoutes(&node1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutes1), check.Equals, 2)
|
||||
|
||||
enabledRoutes2, err := db.GetEnabledRoutes(&machine2)
|
||||
enabledRoutes2, err := db.GetEnabledRoutes(&node2)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutes2), check.Equals, 1)
|
||||
|
||||
routes, err := db.GetMachinePrimaryRoutes(&machine1)
|
||||
routes, err := db.GetNodePrimaryRoutes(&node1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 2)
|
||||
|
||||
routes, err = db.GetMachinePrimaryRoutes(&machine2)
|
||||
routes, err = db.GetNodePrimaryRoutes(&node2)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 0)
|
||||
|
||||
// lets make machine1 lastseen 10 mins ago
|
||||
// lets make node1 lastseen 10 mins ago
|
||||
before := now.Add(-10 * time.Minute)
|
||||
machine1.LastSeen = &before
|
||||
err = db.db.Save(&machine1).Error
|
||||
node1.LastSeen = &before
|
||||
err = db.db.Save(&node1).Error
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = db.HandlePrimarySubnetFailover()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
routes, err = db.GetMachinePrimaryRoutes(&machine1)
|
||||
routes, err = db.GetNodePrimaryRoutes(&node1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 1)
|
||||
|
||||
routes, err = db.GetMachinePrimaryRoutes(&machine2)
|
||||
routes, err = db.GetNodePrimaryRoutes(&node2)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 1)
|
||||
|
||||
machine2.HostInfo = types.HostInfo(tailcfg.Hostinfo{
|
||||
node2.HostInfo = types.HostInfo(tailcfg.Hostinfo{
|
||||
RoutableIPs: []netip.Prefix{prefix, prefix2},
|
||||
})
|
||||
err = db.db.Save(&machine2).Error
|
||||
err = db.db.Save(&node2).Error
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = db.SaveMachineRoutes(&machine2)
|
||||
err = db.SaveNodeRoutes(&node2)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = db.enableRoutes(&machine2, prefix.String())
|
||||
err = db.enableRoutes(&node2, prefix.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = db.HandlePrimarySubnetFailover()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
routes, err = db.GetMachinePrimaryRoutes(&machine1)
|
||||
routes, err = db.GetNodePrimaryRoutes(&node1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 0)
|
||||
|
||||
routes, err = db.GetMachinePrimaryRoutes(&machine2)
|
||||
routes, err = db.GetNodePrimaryRoutes(&node2)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 2)
|
||||
}
|
||||
@@ -362,7 +362,7 @@ func (s *Suite) TestDeleteRoutes(c *check.C) {
|
||||
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.GetMachine("test", "test_enable_route_machine")
|
||||
_, err = db.GetNode("test", "test_enable_route_node")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
prefix, err := netip.ParsePrefix(
|
||||
@@ -380,36 +380,36 @@ func (s *Suite) TestDeleteRoutes(c *check.C) {
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
machine1 := types.Machine{
|
||||
node1 := types.Node{
|
||||
ID: 1,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "test_enable_route_machine",
|
||||
Hostname: "test_enable_route_node",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
HostInfo: types.HostInfo(hostInfo1),
|
||||
LastSeen: &now,
|
||||
}
|
||||
db.db.Save(&machine1)
|
||||
db.db.Save(&node1)
|
||||
|
||||
err = db.SaveMachineRoutes(&machine1)
|
||||
err = db.SaveNodeRoutes(&node1)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = db.enableRoutes(&machine1, prefix.String())
|
||||
err = db.enableRoutes(&node1, prefix.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = db.enableRoutes(&machine1, prefix2.String())
|
||||
err = db.enableRoutes(&node1, prefix2.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
routes, err := db.GetMachineRoutes(&machine1)
|
||||
routes, err := db.GetNodeRoutes(&node1)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = db.DeleteRoute(uint64(routes[0].ID))
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enabledRoutes1, err := db.GetEnabledRoutes(&machine1)
|
||||
enabledRoutes1, err := db.GetEnabledRoutes(&node1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutes1), check.Equals, 1)
|
||||
}
|
||||
|
@@ -43,7 +43,7 @@ func (hsdb *HSDatabase) CreateUser(name string) (*types.User, error) {
|
||||
}
|
||||
|
||||
// DestroyUser destroys a User. Returns error if the User does
|
||||
// not exist or if there are machines associated with it.
|
||||
// not exist or if there are nodes associated with it.
|
||||
func (hsdb *HSDatabase) DestroyUser(name string) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
@@ -53,11 +53,11 @@ func (hsdb *HSDatabase) DestroyUser(name string) error {
|
||||
return ErrUserNotFound
|
||||
}
|
||||
|
||||
machines, err := hsdb.listMachinesByUser(name)
|
||||
nodes, err := hsdb.listNodesByUser(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(machines) > 0 {
|
||||
if len(nodes) > 0 {
|
||||
return ErrUserStillHasNodes
|
||||
}
|
||||
|
||||
@@ -148,15 +148,15 @@ func (hsdb *HSDatabase) listUsers() ([]types.User, error) {
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// ListMachinesByUser gets all the nodes in a given user.
|
||||
func (hsdb *HSDatabase) ListMachinesByUser(name string) (types.Machines, error) {
|
||||
// ListNodesByUser gets all the nodes in a given user.
|
||||
func (hsdb *HSDatabase) ListNodesByUser(name string) (types.Nodes, error) {
|
||||
hsdb.mu.RLock()
|
||||
defer hsdb.mu.RUnlock()
|
||||
|
||||
return hsdb.listMachinesByUser(name)
|
||||
return hsdb.listNodesByUser(name)
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) listMachinesByUser(name string) (types.Machines, error) {
|
||||
func (hsdb *HSDatabase) listNodesByUser(name string) (types.Nodes, error) {
|
||||
err := util.CheckForFQDNRules(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -166,16 +166,16 @@ func (hsdb *HSDatabase) listMachinesByUser(name string) (types.Machines, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
machines := types.Machines{}
|
||||
if err := hsdb.db.Preload("AuthKey").Preload("AuthKey.User").Preload("User").Where(&types.Machine{UserID: user.ID}).Find(&machines).Error; err != nil {
|
||||
nodes := types.Nodes{}
|
||||
if err := hsdb.db.Preload("AuthKey").Preload("AuthKey.User").Preload("User").Where(&types.Node{UserID: user.ID}).Find(&nodes).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return machines, nil
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// AssignMachineToUser assigns a Machine to a user.
|
||||
func (hsdb *HSDatabase) AssignMachineToUser(machine *types.Machine, username string) error {
|
||||
// AssignNodeToUser assigns a Node to a user.
|
||||
func (hsdb *HSDatabase) AssignNodeToUser(node *types.Node, username string) error {
|
||||
hsdb.mu.Lock()
|
||||
defer hsdb.mu.Unlock()
|
||||
|
||||
@@ -187,8 +187,8 @@ func (hsdb *HSDatabase) AssignMachineToUser(machine *types.Machine, username str
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
machine.User = *user
|
||||
if result := hsdb.db.Save(&machine); result.Error != nil {
|
||||
node.User = *user
|
||||
if result := hsdb.db.Save(&node); result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
|
@@ -46,17 +46,17 @@ func (s *Suite) TestDestroyUserErrors(c *check.C) {
|
||||
pak, err = db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machine := types.Machine{
|
||||
node := types.Node{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
Hostname: "testnode",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
db.db.Save(&machine)
|
||||
db.db.Save(&node)
|
||||
|
||||
err = db.DestroyUser("test")
|
||||
c.Assert(err, check.Equals, ErrUserStillHasNodes)
|
||||
@@ -101,29 +101,29 @@ func (s *Suite) TestSetMachineUser(c *check.C) {
|
||||
pak, err := db.CreatePreAuthKey(oldUser.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machine := types.Machine{
|
||||
node := types.Node{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
Hostname: "testnode",
|
||||
UserID: oldUser.ID,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
db.db.Save(&machine)
|
||||
c.Assert(machine.UserID, check.Equals, oldUser.ID)
|
||||
db.db.Save(&node)
|
||||
c.Assert(node.UserID, check.Equals, oldUser.ID)
|
||||
|
||||
err = db.AssignMachineToUser(&machine, newUser.Name)
|
||||
err = db.AssignNodeToUser(&node, newUser.Name)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(machine.UserID, check.Equals, newUser.ID)
|
||||
c.Assert(machine.User.Name, check.Equals, newUser.Name)
|
||||
c.Assert(node.UserID, check.Equals, newUser.ID)
|
||||
c.Assert(node.User.Name, check.Equals, newUser.Name)
|
||||
|
||||
err = db.AssignMachineToUser(&machine, "non-existing-user")
|
||||
err = db.AssignNodeToUser(&node, "non-existing-user")
|
||||
c.Assert(err, check.Equals, ErrUserNotFound)
|
||||
|
||||
err = db.AssignMachineToUser(&machine, newUser.Name)
|
||||
err = db.AssignNodeToUser(&node, newUser.Name)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(machine.UserID, check.Equals, newUser.ID)
|
||||
c.Assert(machine.User.Name, check.Equals, newUser.Name)
|
||||
c.Assert(node.UserID, check.Equals, newUser.ID)
|
||||
c.Assert(node.User.Name, check.Equals, newUser.Name)
|
||||
}
|
||||
|
@@ -166,16 +166,16 @@ func (api headscaleV1APIServer) ListPreAuthKeys(
|
||||
return &v1.ListPreAuthKeysResponse{PreAuthKeys: response}, nil
|
||||
}
|
||||
|
||||
func (api headscaleV1APIServer) RegisterMachine(
|
||||
func (api headscaleV1APIServer) RegisterNode(
|
||||
ctx context.Context,
|
||||
request *v1.RegisterMachineRequest,
|
||||
) (*v1.RegisterMachineResponse, error) {
|
||||
request *v1.RegisterNodeRequest,
|
||||
) (*v1.RegisterNodeResponse, error) {
|
||||
log.Trace().
|
||||
Str("user", request.GetUser()).
|
||||
Str("node_key", request.GetKey()).
|
||||
Msg("Registering machine")
|
||||
Msg("Registering node")
|
||||
|
||||
machine, err := api.h.db.RegisterMachineFromAuthCallback(
|
||||
node, err := api.h.db.RegisterNodeFromAuthCallback(
|
||||
api.h.registrationCache,
|
||||
request.GetKey(),
|
||||
request.GetUser(),
|
||||
@@ -186,26 +186,26 @@ func (api headscaleV1APIServer) RegisterMachine(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v1.RegisterMachineResponse{Machine: machine.Proto()}, nil
|
||||
return &v1.RegisterNodeResponse{Node: node.Proto()}, nil
|
||||
}
|
||||
|
||||
func (api headscaleV1APIServer) GetMachine(
|
||||
func (api headscaleV1APIServer) GetNode(
|
||||
ctx context.Context,
|
||||
request *v1.GetMachineRequest,
|
||||
) (*v1.GetMachineResponse, error) {
|
||||
machine, err := api.h.db.GetMachineByID(request.GetMachineId())
|
||||
request *v1.GetNodeRequest,
|
||||
) (*v1.GetNodeResponse, error) {
|
||||
node, err := api.h.db.GetNodeByID(request.GetNodeId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v1.GetMachineResponse{Machine: machine.Proto()}, nil
|
||||
return &v1.GetNodeResponse{Node: node.Proto()}, nil
|
||||
}
|
||||
|
||||
func (api headscaleV1APIServer) SetTags(
|
||||
ctx context.Context,
|
||||
request *v1.SetTagsRequest,
|
||||
) (*v1.SetTagsResponse, error) {
|
||||
machine, err := api.h.db.GetMachineByID(request.GetMachineId())
|
||||
node, err := api.h.db.GetNodeByID(request.GetNodeId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -214,24 +214,24 @@ func (api headscaleV1APIServer) SetTags(
|
||||
err := validateTag(tag)
|
||||
if err != nil {
|
||||
return &v1.SetTagsResponse{
|
||||
Machine: nil,
|
||||
Node: nil,
|
||||
}, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
err = api.h.db.SetTags(machine, request.GetTags())
|
||||
err = api.h.db.SetTags(node, request.GetTags())
|
||||
if err != nil {
|
||||
return &v1.SetTagsResponse{
|
||||
Machine: nil,
|
||||
Node: nil,
|
||||
}, status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
log.Trace().
|
||||
Str("machine", machine.Hostname).
|
||||
Str("node", node.Hostname).
|
||||
Strs("tags", request.GetTags()).
|
||||
Msg("Changing tags of machine")
|
||||
Msg("Changing tags of node")
|
||||
|
||||
return &v1.SetTagsResponse{Machine: machine.Proto()}, nil
|
||||
return &v1.SetTagsResponse{Node: node.Proto()}, nil
|
||||
}
|
||||
|
||||
func validateTag(tag string) error {
|
||||
@@ -247,60 +247,60 @@ func validateTag(tag string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api headscaleV1APIServer) DeleteMachine(
|
||||
func (api headscaleV1APIServer) DeleteNode(
|
||||
ctx context.Context,
|
||||
request *v1.DeleteMachineRequest,
|
||||
) (*v1.DeleteMachineResponse, error) {
|
||||
machine, err := api.h.db.GetMachineByID(request.GetMachineId())
|
||||
request *v1.DeleteNodeRequest,
|
||||
) (*v1.DeleteNodeResponse, error) {
|
||||
node, err := api.h.db.GetNodeByID(request.GetNodeId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = api.h.db.DeleteMachine(
|
||||
machine,
|
||||
err = api.h.db.DeleteNode(
|
||||
node,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v1.DeleteMachineResponse{}, nil
|
||||
return &v1.DeleteNodeResponse{}, nil
|
||||
}
|
||||
|
||||
func (api headscaleV1APIServer) ExpireMachine(
|
||||
func (api headscaleV1APIServer) ExpireNode(
|
||||
ctx context.Context,
|
||||
request *v1.ExpireMachineRequest,
|
||||
) (*v1.ExpireMachineResponse, error) {
|
||||
machine, err := api.h.db.GetMachineByID(request.GetMachineId())
|
||||
request *v1.ExpireNodeRequest,
|
||||
) (*v1.ExpireNodeResponse, error) {
|
||||
node, err := api.h.db.GetNodeByID(request.GetNodeId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
api.h.db.MachineSetExpiry(
|
||||
machine,
|
||||
api.h.db.NodeSetExpiry(
|
||||
node,
|
||||
now,
|
||||
)
|
||||
|
||||
log.Trace().
|
||||
Str("machine", machine.Hostname).
|
||||
Time("expiry", *machine.Expiry).
|
||||
Msg("machine expired")
|
||||
Str("node", node.Hostname).
|
||||
Time("expiry", *node.Expiry).
|
||||
Msg("node expired")
|
||||
|
||||
return &v1.ExpireMachineResponse{Machine: machine.Proto()}, nil
|
||||
return &v1.ExpireNodeResponse{Node: node.Proto()}, nil
|
||||
}
|
||||
|
||||
func (api headscaleV1APIServer) RenameMachine(
|
||||
func (api headscaleV1APIServer) RenameNode(
|
||||
ctx context.Context,
|
||||
request *v1.RenameMachineRequest,
|
||||
) (*v1.RenameMachineResponse, error) {
|
||||
machine, err := api.h.db.GetMachineByID(request.GetMachineId())
|
||||
request *v1.RenameNodeRequest,
|
||||
) (*v1.RenameNodeResponse, error) {
|
||||
node, err := api.h.db.GetNodeByID(request.GetNodeId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = api.h.db.RenameMachine(
|
||||
machine,
|
||||
err = api.h.db.RenameNode(
|
||||
node,
|
||||
request.GetNewName(),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -308,65 +308,65 @@ func (api headscaleV1APIServer) RenameMachine(
|
||||
}
|
||||
|
||||
log.Trace().
|
||||
Str("machine", machine.Hostname).
|
||||
Str("node", node.Hostname).
|
||||
Str("new_name", request.GetNewName()).
|
||||
Msg("machine renamed")
|
||||
Msg("node renamed")
|
||||
|
||||
return &v1.RenameMachineResponse{Machine: machine.Proto()}, nil
|
||||
return &v1.RenameNodeResponse{Node: node.Proto()}, nil
|
||||
}
|
||||
|
||||
func (api headscaleV1APIServer) ListMachines(
|
||||
func (api headscaleV1APIServer) ListNodes(
|
||||
ctx context.Context,
|
||||
request *v1.ListMachinesRequest,
|
||||
) (*v1.ListMachinesResponse, error) {
|
||||
request *v1.ListNodesRequest,
|
||||
) (*v1.ListNodesResponse, error) {
|
||||
if request.GetUser() != "" {
|
||||
machines, err := api.h.db.ListMachinesByUser(request.GetUser())
|
||||
nodes, err := api.h.db.ListNodesByUser(request.GetUser())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := make([]*v1.Machine, len(machines))
|
||||
for index, machine := range machines {
|
||||
response[index] = machine.Proto()
|
||||
response := make([]*v1.Node, len(nodes))
|
||||
for index, node := range nodes {
|
||||
response[index] = node.Proto()
|
||||
}
|
||||
|
||||
return &v1.ListMachinesResponse{Machines: response}, nil
|
||||
return &v1.ListNodesResponse{Nodes: response}, nil
|
||||
}
|
||||
|
||||
machines, err := api.h.db.ListMachines()
|
||||
nodes, err := api.h.db.ListNodes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := make([]*v1.Machine, len(machines))
|
||||
for index, machine := range machines {
|
||||
m := machine.Proto()
|
||||
validTags, invalidTags := api.h.ACLPolicy.TagsOfMachine(
|
||||
&machine,
|
||||
response := make([]*v1.Node, len(nodes))
|
||||
for index, node := range nodes {
|
||||
m := node.Proto()
|
||||
validTags, invalidTags := api.h.ACLPolicy.TagsOfNode(
|
||||
&node,
|
||||
)
|
||||
m.InvalidTags = invalidTags
|
||||
m.ValidTags = validTags
|
||||
response[index] = m
|
||||
}
|
||||
|
||||
return &v1.ListMachinesResponse{Machines: response}, nil
|
||||
return &v1.ListNodesResponse{Nodes: response}, nil
|
||||
}
|
||||
|
||||
func (api headscaleV1APIServer) MoveMachine(
|
||||
func (api headscaleV1APIServer) MoveNode(
|
||||
ctx context.Context,
|
||||
request *v1.MoveMachineRequest,
|
||||
) (*v1.MoveMachineResponse, error) {
|
||||
machine, err := api.h.db.GetMachineByID(request.GetMachineId())
|
||||
request *v1.MoveNodeRequest,
|
||||
) (*v1.MoveNodeResponse, error) {
|
||||
node, err := api.h.db.GetNodeByID(request.GetNodeId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = api.h.db.AssignMachineToUser(machine, request.GetUser())
|
||||
err = api.h.db.AssignNodeToUser(node, request.GetUser())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v1.MoveMachineResponse{Machine: machine.Proto()}, nil
|
||||
return &v1.MoveNodeResponse{Node: node.Proto()}, nil
|
||||
}
|
||||
|
||||
func (api headscaleV1APIServer) GetRoutes(
|
||||
@@ -407,21 +407,21 @@ func (api headscaleV1APIServer) DisableRoute(
|
||||
return &v1.DisableRouteResponse{}, nil
|
||||
}
|
||||
|
||||
func (api headscaleV1APIServer) GetMachineRoutes(
|
||||
func (api headscaleV1APIServer) GetNodeRoutes(
|
||||
ctx context.Context,
|
||||
request *v1.GetMachineRoutesRequest,
|
||||
) (*v1.GetMachineRoutesResponse, error) {
|
||||
machine, err := api.h.db.GetMachineByID(request.GetMachineId())
|
||||
request *v1.GetNodeRoutesRequest,
|
||||
) (*v1.GetNodeRoutesResponse, error) {
|
||||
node, err := api.h.db.GetNodeByID(request.GetNodeId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
routes, err := api.h.db.GetMachineRoutes(machine)
|
||||
routes, err := api.h.db.GetNodeRoutes(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v1.GetMachineRoutesResponse{
|
||||
return &v1.GetNodeRoutesResponse{
|
||||
Routes: types.Routes(routes).Proto(),
|
||||
}, nil
|
||||
}
|
||||
@@ -495,10 +495,10 @@ func (api headscaleV1APIServer) ListApiKeys(
|
||||
}
|
||||
|
||||
// The following service calls are for testing and debugging
|
||||
func (api headscaleV1APIServer) DebugCreateMachine(
|
||||
func (api headscaleV1APIServer) DebugCreateNode(
|
||||
ctx context.Context,
|
||||
request *v1.DebugCreateMachineRequest,
|
||||
) (*v1.DebugCreateMachineResponse, error) {
|
||||
request *v1.DebugCreateNodeRequest,
|
||||
) (*v1.DebugCreateNodeResponse, error) {
|
||||
user, err := api.h.db.GetUser(request.GetUser())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -518,7 +518,7 @@ func (api headscaleV1APIServer) DebugCreateMachine(
|
||||
hostinfo := tailcfg.Hostinfo{
|
||||
RoutableIPs: routes,
|
||||
OS: "TestOS",
|
||||
Hostname: "DebugTestMachine",
|
||||
Hostname: "DebugTestNode",
|
||||
}
|
||||
|
||||
givenName, err := api.h.db.GenerateGivenName(request.GetKey(), request.GetName())
|
||||
@@ -526,7 +526,7 @@ func (api headscaleV1APIServer) DebugCreateMachine(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newMachine := types.Machine{
|
||||
newNode := types.Node{
|
||||
MachineKey: request.GetKey(),
|
||||
Hostname: request.GetName(),
|
||||
GivenName: givenName,
|
||||
@@ -541,16 +541,16 @@ func (api headscaleV1APIServer) DebugCreateMachine(
|
||||
nodeKey := key.NodePublic{}
|
||||
err = nodeKey.UnmarshalText([]byte(request.GetKey()))
|
||||
if err != nil {
|
||||
log.Panic().Msg("can not add machine for debug. invalid node key")
|
||||
log.Panic().Msg("can not add node for debug. invalid node key")
|
||||
}
|
||||
|
||||
api.h.registrationCache.Set(
|
||||
util.NodePublicKeyStripPrefix(nodeKey),
|
||||
newMachine,
|
||||
newNode,
|
||||
registerCacheExpiration,
|
||||
)
|
||||
|
||||
return &v1.DebugCreateMachineResponse{Machine: newMachine.Proto()}, nil
|
||||
return &v1.DebugCreateNodeResponse{Node: newNode.Proto()}, nil
|
||||
}
|
||||
|
||||
func (api headscaleV1APIServer) mustEmbedUnimplementedHeadscaleServiceServer() {}
|
||||
|
@@ -41,7 +41,7 @@ var debugDumpMapResponsePath = envknob.String("HEADSCALE_DEBUG_DUMP_MAPRESPONSE_
|
||||
// TODO: Optimise
|
||||
// As this work continues, the idea is that there will be one Mapper instance
|
||||
// per node, attached to the open stream between the control and client.
|
||||
// This means that this can hold a state per machine and we can use that to
|
||||
// This means that this can hold a state per node and we can use that to
|
||||
// improve the mapresponses sent.
|
||||
// We could:
|
||||
// - Keep information about the previous mapresponse so we can send a diff
|
||||
@@ -67,12 +67,12 @@ type Mapper struct {
|
||||
// Map isnt concurrency safe, so we need to ensure
|
||||
// only one func is accessing it over time.
|
||||
mu sync.Mutex
|
||||
peers map[uint64]*types.Machine
|
||||
peers map[uint64]*types.Node
|
||||
}
|
||||
|
||||
func NewMapper(
|
||||
machine *types.Machine,
|
||||
peers types.Machines,
|
||||
node *types.Node,
|
||||
peers types.Nodes,
|
||||
privateKey *key.MachinePrivate,
|
||||
isNoise bool,
|
||||
derpMap *tailcfg.DERPMap,
|
||||
@@ -84,7 +84,7 @@ func NewMapper(
|
||||
log.Debug().
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("node", node.Hostname).
|
||||
Msg("creating new mapper")
|
||||
|
||||
uid, _ := util.GenerateRandomStringDNSSafe(mapperIDLength)
|
||||
@@ -113,12 +113,12 @@ func (m *Mapper) String() string {
|
||||
}
|
||||
|
||||
func generateUserProfiles(
|
||||
machine *types.Machine,
|
||||
peers types.Machines,
|
||||
node *types.Node,
|
||||
peers types.Nodes,
|
||||
baseDomain string,
|
||||
) []tailcfg.UserProfile {
|
||||
userMap := make(map[string]types.User)
|
||||
userMap[machine.User.Name] = machine.User
|
||||
userMap[node.User.Name] = node.User
|
||||
for _, peer := range peers {
|
||||
userMap[peer.User.Name] = peer.User // not worth checking if already is there
|
||||
}
|
||||
@@ -145,8 +145,8 @@ func generateUserProfiles(
|
||||
func generateDNSConfig(
|
||||
base *tailcfg.DNSConfig,
|
||||
baseDomain string,
|
||||
machine *types.Machine,
|
||||
peers types.Machines,
|
||||
node *types.Node,
|
||||
peers types.Nodes,
|
||||
) *tailcfg.DNSConfig {
|
||||
dnsConfig := base.Clone()
|
||||
|
||||
@@ -158,13 +158,13 @@ func generateDNSConfig(
|
||||
dnsConfig.Domains,
|
||||
fmt.Sprintf(
|
||||
"%s.%s",
|
||||
machine.User.Name,
|
||||
node.User.Name,
|
||||
baseDomain,
|
||||
),
|
||||
)
|
||||
|
||||
userSet := mapset.NewSet[types.User]()
|
||||
userSet.Add(machine.User)
|
||||
userSet.Add(node.User)
|
||||
for _, p := range peers {
|
||||
userSet.Add(p.User)
|
||||
}
|
||||
@@ -176,28 +176,28 @@ func generateDNSConfig(
|
||||
dnsConfig = base
|
||||
}
|
||||
|
||||
addNextDNSMetadata(dnsConfig.Resolvers, machine)
|
||||
addNextDNSMetadata(dnsConfig.Resolvers, node)
|
||||
|
||||
return dnsConfig
|
||||
}
|
||||
|
||||
// If any nextdns DoH resolvers are present in the list of resolvers it will
|
||||
// take metadata from the machine metadata and instruct tailscale to add it
|
||||
// take metadata from the node metadata and instruct tailscale to add it
|
||||
// to the requests. This makes it possible to identify from which device the
|
||||
// requests come in the NextDNS dashboard.
|
||||
//
|
||||
// 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, machine *types.Machine) {
|
||||
func addNextDNSMetadata(resolvers []*dnstype.Resolver, node *types.Node) {
|
||||
for _, resolver := range resolvers {
|
||||
if strings.HasPrefix(resolver.Addr, nextDNSDoHPrefix) {
|
||||
attrs := url.Values{
|
||||
"device_name": []string{machine.Hostname},
|
||||
"device_model": []string{machine.HostInfo.OS},
|
||||
"device_name": []string{node.Hostname},
|
||||
"device_model": []string{node.HostInfo.OS},
|
||||
}
|
||||
|
||||
if len(machine.IPAddresses) > 0 {
|
||||
attrs.Add("device_ip", machine.IPAddresses[0].String())
|
||||
if len(node.IPAddresses) > 0 {
|
||||
attrs.Add("device_ip", node.IPAddresses[0].String())
|
||||
}
|
||||
|
||||
resolver.Addr = fmt.Sprintf("%s?%s", resolver.Addr, attrs.Encode())
|
||||
@@ -208,23 +208,23 @@ func addNextDNSMetadata(resolvers []*dnstype.Resolver, machine *types.Machine) {
|
||||
// fullMapResponse creates a complete MapResponse for a node.
|
||||
// It is a separate function to make testing easier.
|
||||
func (m *Mapper) fullMapResponse(
|
||||
machine *types.Machine,
|
||||
node *types.Node,
|
||||
pol *policy.ACLPolicy,
|
||||
) (*tailcfg.MapResponse, error) {
|
||||
peers := machineMapToList(m.peers)
|
||||
peers := nodeMapToList(m.peers)
|
||||
|
||||
resp, err := m.baseWithConfigMapResponse(machine, pol)
|
||||
resp, err := m.baseWithConfigMapResponse(node, pol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(kradalby): Move this into appendPeerChanges?
|
||||
resp.OnlineChange = db.OnlineMachineMap(peers)
|
||||
resp.OnlineChange = db.OnlineNodeMap(peers)
|
||||
|
||||
err = appendPeerChanges(
|
||||
resp,
|
||||
pol,
|
||||
machine,
|
||||
node,
|
||||
peers,
|
||||
peers,
|
||||
m.baseDomain,
|
||||
@@ -237,72 +237,72 @@ func (m *Mapper) fullMapResponse(
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// FullMapResponse returns a MapResponse for the given machine.
|
||||
// FullMapResponse returns a MapResponse for the given node.
|
||||
func (m *Mapper) FullMapResponse(
|
||||
mapRequest tailcfg.MapRequest,
|
||||
machine *types.Machine,
|
||||
node *types.Node,
|
||||
pol *policy.ACLPolicy,
|
||||
) ([]byte, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
resp, err := m.fullMapResponse(machine, pol)
|
||||
resp, err := m.fullMapResponse(node, pol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if m.isNoise {
|
||||
return m.marshalMapResponse(mapRequest, resp, machine, mapRequest.Compress)
|
||||
return m.marshalMapResponse(mapRequest, resp, node, mapRequest.Compress)
|
||||
}
|
||||
|
||||
return m.marshalMapResponse(mapRequest, resp, machine, mapRequest.Compress)
|
||||
return m.marshalMapResponse(mapRequest, resp, node, mapRequest.Compress)
|
||||
}
|
||||
|
||||
// LiteMapResponse returns a MapResponse for the given machine.
|
||||
// LiteMapResponse returns a MapResponse for the given node.
|
||||
// Lite means that the peers has been omitted, this is intended
|
||||
// to be used to answer MapRequests with OmitPeers set to true.
|
||||
func (m *Mapper) LiteMapResponse(
|
||||
mapRequest tailcfg.MapRequest,
|
||||
machine *types.Machine,
|
||||
node *types.Node,
|
||||
pol *policy.ACLPolicy,
|
||||
) ([]byte, error) {
|
||||
resp, err := m.baseWithConfigMapResponse(machine, pol)
|
||||
resp, err := m.baseWithConfigMapResponse(node, pol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if m.isNoise {
|
||||
return m.marshalMapResponse(mapRequest, resp, machine, mapRequest.Compress)
|
||||
return m.marshalMapResponse(mapRequest, resp, node, mapRequest.Compress)
|
||||
}
|
||||
|
||||
return m.marshalMapResponse(mapRequest, resp, machine, mapRequest.Compress)
|
||||
return m.marshalMapResponse(mapRequest, resp, node, mapRequest.Compress)
|
||||
}
|
||||
|
||||
func (m *Mapper) KeepAliveResponse(
|
||||
mapRequest tailcfg.MapRequest,
|
||||
machine *types.Machine,
|
||||
node *types.Node,
|
||||
) ([]byte, error) {
|
||||
resp := m.baseMapResponse()
|
||||
resp.KeepAlive = true
|
||||
|
||||
return m.marshalMapResponse(mapRequest, &resp, machine, mapRequest.Compress)
|
||||
return m.marshalMapResponse(mapRequest, &resp, node, mapRequest.Compress)
|
||||
}
|
||||
|
||||
func (m *Mapper) DERPMapResponse(
|
||||
mapRequest tailcfg.MapRequest,
|
||||
machine *types.Machine,
|
||||
node *types.Node,
|
||||
derpMap tailcfg.DERPMap,
|
||||
) ([]byte, error) {
|
||||
resp := m.baseMapResponse()
|
||||
resp.DERPMap = &derpMap
|
||||
|
||||
return m.marshalMapResponse(mapRequest, &resp, machine, mapRequest.Compress)
|
||||
return m.marshalMapResponse(mapRequest, &resp, node, mapRequest.Compress)
|
||||
}
|
||||
|
||||
func (m *Mapper) PeerChangedResponse(
|
||||
mapRequest tailcfg.MapRequest,
|
||||
machine *types.Machine,
|
||||
changed types.Machines,
|
||||
node *types.Node,
|
||||
changed types.Nodes,
|
||||
pol *policy.ACLPolicy,
|
||||
) ([]byte, error) {
|
||||
m.mu.Lock()
|
||||
@@ -311,11 +311,11 @@ func (m *Mapper) PeerChangedResponse(
|
||||
lastSeen := make(map[tailcfg.NodeID]bool)
|
||||
|
||||
// Update our internal map.
|
||||
for _, machine := range changed {
|
||||
m.peers[machine.ID] = machine
|
||||
for _, node := range changed {
|
||||
m.peers[node.ID] = node
|
||||
|
||||
// We have just seen the node, let the peers update their list.
|
||||
lastSeen[tailcfg.NodeID(machine.ID)] = true
|
||||
lastSeen[tailcfg.NodeID(node.ID)] = true
|
||||
}
|
||||
|
||||
resp := m.baseMapResponse()
|
||||
@@ -323,8 +323,8 @@ func (m *Mapper) PeerChangedResponse(
|
||||
err := appendPeerChanges(
|
||||
&resp,
|
||||
pol,
|
||||
machine,
|
||||
machineMapToList(m.peers),
|
||||
node,
|
||||
nodeMapToList(m.peers),
|
||||
changed,
|
||||
m.baseDomain,
|
||||
m.dnsCfg,
|
||||
@@ -335,12 +335,12 @@ func (m *Mapper) PeerChangedResponse(
|
||||
|
||||
// resp.PeerSeenChange = lastSeen
|
||||
|
||||
return m.marshalMapResponse(mapRequest, &resp, machine, mapRequest.Compress)
|
||||
return m.marshalMapResponse(mapRequest, &resp, node, mapRequest.Compress)
|
||||
}
|
||||
|
||||
func (m *Mapper) PeerRemovedResponse(
|
||||
mapRequest tailcfg.MapRequest,
|
||||
machine *types.Machine,
|
||||
node *types.Node,
|
||||
removed []tailcfg.NodeID,
|
||||
) ([]byte, error) {
|
||||
m.mu.Lock()
|
||||
@@ -354,19 +354,19 @@ func (m *Mapper) PeerRemovedResponse(
|
||||
resp := m.baseMapResponse()
|
||||
resp.PeersRemoved = removed
|
||||
|
||||
return m.marshalMapResponse(mapRequest, &resp, machine, mapRequest.Compress)
|
||||
return m.marshalMapResponse(mapRequest, &resp, node, mapRequest.Compress)
|
||||
}
|
||||
|
||||
func (m *Mapper) marshalMapResponse(
|
||||
mapRequest tailcfg.MapRequest,
|
||||
resp *tailcfg.MapResponse,
|
||||
machine *types.Machine,
|
||||
node *types.Node,
|
||||
compression string,
|
||||
) ([]byte, error) {
|
||||
atomic.AddUint64(&m.seq, 1)
|
||||
|
||||
var machineKey key.MachinePublic
|
||||
err := machineKey.UnmarshalText([]byte(util.MachinePublicKeyEnsurePrefix(machine.MachineKey)))
|
||||
err := machineKey.UnmarshalText([]byte(util.MachinePublicKeyEnsurePrefix(node.MachineKey)))
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
@@ -399,7 +399,7 @@ func (m *Mapper) marshalMapResponse(
|
||||
}
|
||||
|
||||
perms := fs.FileMode(debugMapResponsePerm)
|
||||
mPath := path.Join(debugDumpMapResponsePath, machine.Hostname)
|
||||
mPath := path.Join(debugDumpMapResponsePath, node.Hostname)
|
||||
err = os.MkdirAll(mPath, perms)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -509,12 +509,12 @@ func (m *Mapper) baseMapResponse() tailcfg.MapResponse {
|
||||
// It is used in for bigger updates, such as full and lite, not
|
||||
// incremental.
|
||||
func (m *Mapper) baseWithConfigMapResponse(
|
||||
machine *types.Machine,
|
||||
node *types.Node,
|
||||
pol *policy.ACLPolicy,
|
||||
) (*tailcfg.MapResponse, error) {
|
||||
resp := m.baseMapResponse()
|
||||
|
||||
tailnode, err := tailNode(machine, pol, m.dnsCfg, m.baseDomain)
|
||||
tailnode, err := tailNode(node, pol, m.dnsCfg, m.baseDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -538,18 +538,18 @@ func (m *Mapper) baseWithConfigMapResponse(
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func machineMapToList(machines map[uint64]*types.Machine) types.Machines {
|
||||
ret := make(types.Machines, 0)
|
||||
func nodeMapToList(nodes map[uint64]*types.Node) types.Nodes {
|
||||
ret := make(types.Nodes, 0)
|
||||
|
||||
for _, machine := range machines {
|
||||
ret = append(ret, machine)
|
||||
for _, node := range nodes {
|
||||
ret = append(ret, node)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func filterExpiredAndNotReady(peers types.Machines) types.Machines {
|
||||
return lo.Filter(peers, func(item *types.Machine, index int) bool {
|
||||
func filterExpiredAndNotReady(peers types.Nodes) types.Nodes {
|
||||
return lo.Filter(peers, func(item *types.Node, index int) bool {
|
||||
// Filter out nodes that are expired OR
|
||||
// nodes that has no endpoints, this typically means they have
|
||||
// registered, but are not configured.
|
||||
@@ -563,9 +563,9 @@ func appendPeerChanges(
|
||||
resp *tailcfg.MapResponse,
|
||||
|
||||
pol *policy.ACLPolicy,
|
||||
machine *types.Machine,
|
||||
peers types.Machines,
|
||||
changed types.Machines,
|
||||
node *types.Node,
|
||||
peers types.Nodes,
|
||||
changed types.Nodes,
|
||||
baseDomain string,
|
||||
dnsCfg *tailcfg.DNSConfig,
|
||||
) error {
|
||||
@@ -573,7 +573,7 @@ func appendPeerChanges(
|
||||
|
||||
rules, sshPolicy, err := policy.GenerateFilterAndSSHRules(
|
||||
pol,
|
||||
machine,
|
||||
node,
|
||||
peers,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -583,18 +583,18 @@ func appendPeerChanges(
|
||||
// Filter out peers that have expired.
|
||||
changed = filterExpiredAndNotReady(changed)
|
||||
|
||||
// If there are filter rules present, see if there are any machines that cannot
|
||||
// If there are filter rules present, see if there are any nodes that cannot
|
||||
// access eachother at all and remove them from the peers.
|
||||
if len(rules) > 0 {
|
||||
changed = policy.FilterMachinesByACL(machine, changed, rules)
|
||||
changed = policy.FilterNodesByACL(node, changed, rules)
|
||||
}
|
||||
|
||||
profiles := generateUserProfiles(machine, changed, baseDomain)
|
||||
profiles := generateUserProfiles(node, changed, baseDomain)
|
||||
|
||||
dnsConfig := generateDNSConfig(
|
||||
dnsCfg,
|
||||
baseDomain,
|
||||
machine,
|
||||
node,
|
||||
peers,
|
||||
)
|
||||
|
||||
@@ -614,7 +614,7 @@ func appendPeerChanges(
|
||||
resp.PeersChanged = tailPeers
|
||||
}
|
||||
resp.DNSConfig = dnsConfig
|
||||
resp.PacketFilter = policy.ReduceFilterRules(machine, rules)
|
||||
resp.PacketFilter = policy.ReduceFilterRules(node, rules)
|
||||
resp.UserProfiles = profiles
|
||||
resp.SSHPolicy = sshPolicy
|
||||
|
||||
|
@@ -18,8 +18,8 @@ import (
|
||||
)
|
||||
|
||||
func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
||||
mach := func(hostname, username string, userid uint) *types.Machine {
|
||||
return &types.Machine{
|
||||
mach := func(hostname, username string, userid uint) *types.Node {
|
||||
return &types.Node{
|
||||
Hostname: hostname,
|
||||
UserID: userid,
|
||||
User: types.User{
|
||||
@@ -28,15 +28,15 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
||||
}
|
||||
}
|
||||
|
||||
machineInShared1 := mach("test_get_shared_nodes_1", "user1", 1)
|
||||
machineInShared2 := mach("test_get_shared_nodes_2", "user2", 2)
|
||||
machineInShared3 := mach("test_get_shared_nodes_3", "user3", 3)
|
||||
machine2InShared1 := mach("test_get_shared_nodes_4", "user1", 1)
|
||||
nodeInShared1 := mach("test_get_shared_nodes_1", "user1", 1)
|
||||
nodeInShared2 := mach("test_get_shared_nodes_2", "user2", 2)
|
||||
nodeInShared3 := mach("test_get_shared_nodes_3", "user3", 3)
|
||||
node2InShared1 := mach("test_get_shared_nodes_4", "user1", 1)
|
||||
|
||||
userProfiles := generateUserProfiles(
|
||||
machineInShared1,
|
||||
types.Machines{
|
||||
machineInShared2, machineInShared3, machine2InShared1,
|
||||
nodeInShared1,
|
||||
types.Nodes{
|
||||
nodeInShared2, nodeInShared3, node2InShared1,
|
||||
},
|
||||
"",
|
||||
)
|
||||
@@ -91,8 +91,8 @@ func TestDNSConfigMapResponse(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(fmt.Sprintf("with-magicdns-%v", tt.magicDNS), func(t *testing.T) {
|
||||
mach := func(hostname, username string, userid uint) *types.Machine {
|
||||
return &types.Machine{
|
||||
mach := func(hostname, username string, userid uint) *types.Node {
|
||||
return &types.Node{
|
||||
Hostname: hostname,
|
||||
UserID: userid,
|
||||
User: types.User{
|
||||
@@ -109,23 +109,23 @@ func TestDNSConfigMapResponse(t *testing.T) {
|
||||
Proxied: tt.magicDNS,
|
||||
}
|
||||
|
||||
machineInShared1 := mach("test_get_shared_nodes_1", "shared1", 1)
|
||||
machineInShared2 := mach("test_get_shared_nodes_2", "shared2", 2)
|
||||
machineInShared3 := mach("test_get_shared_nodes_3", "shared3", 3)
|
||||
machine2InShared1 := mach("test_get_shared_nodes_4", "shared1", 1)
|
||||
nodeInShared1 := mach("test_get_shared_nodes_1", "shared1", 1)
|
||||
nodeInShared2 := mach("test_get_shared_nodes_2", "shared2", 2)
|
||||
nodeInShared3 := mach("test_get_shared_nodes_3", "shared3", 3)
|
||||
node2InShared1 := mach("test_get_shared_nodes_4", "shared1", 1)
|
||||
|
||||
peersOfMachineInShared1 := types.Machines{
|
||||
machineInShared1,
|
||||
machineInShared2,
|
||||
machineInShared3,
|
||||
machine2InShared1,
|
||||
peersOfNodeInShared1 := types.Nodes{
|
||||
nodeInShared1,
|
||||
nodeInShared2,
|
||||
nodeInShared3,
|
||||
node2InShared1,
|
||||
}
|
||||
|
||||
got := generateDNSConfig(
|
||||
&dnsConfigOrig,
|
||||
baseDomain,
|
||||
machineInShared1,
|
||||
peersOfMachineInShared1,
|
||||
nodeInShared1,
|
||||
peersOfNodeInShared1,
|
||||
)
|
||||
|
||||
if diff := cmp.Diff(tt.want, got, cmpopts.EquateEmpty()); diff != "" {
|
||||
@@ -165,7 +165,7 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
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)
|
||||
|
||||
mini := &types.Machine{
|
||||
mini := &types.Node{
|
||||
ID: 0,
|
||||
MachineKey: "mkey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507",
|
||||
NodeKey: "nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe",
|
||||
@@ -243,7 +243,7 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
peer1 := &types.Machine{
|
||||
peer1 := &types.Node{
|
||||
ID: 1,
|
||||
MachineKey: "mkey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507",
|
||||
NodeKey: "nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe",
|
||||
@@ -295,7 +295,7 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
peer2 := &types.Machine{
|
||||
peer2 := &types.Node{
|
||||
ID: 2,
|
||||
MachineKey: "mkey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507",
|
||||
NodeKey: "nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe",
|
||||
@@ -315,10 +315,10 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pol *policy.ACLPolicy
|
||||
machine *types.Machine
|
||||
peers types.Machines
|
||||
name string
|
||||
pol *policy.ACLPolicy
|
||||
node *types.Node
|
||||
peers types.Nodes
|
||||
|
||||
baseDomain string
|
||||
dnsConfig *tailcfg.DNSConfig
|
||||
@@ -329,8 +329,8 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
wantErr bool
|
||||
}{
|
||||
// {
|
||||
// name: "empty-machine",
|
||||
// machine: types.Machine{},
|
||||
// name: "empty-node",
|
||||
// node: types.Node{},
|
||||
// pol: &policy.ACLPolicy{},
|
||||
// dnsConfig: &tailcfg.DNSConfig{},
|
||||
// baseDomain: "",
|
||||
@@ -340,8 +340,8 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
{
|
||||
name: "no-pol-no-peers-map-response",
|
||||
pol: &policy.ACLPolicy{},
|
||||
machine: mini,
|
||||
peers: types.Machines{},
|
||||
node: mini,
|
||||
peers: types.Nodes{},
|
||||
baseDomain: "",
|
||||
dnsConfig: &tailcfg.DNSConfig{},
|
||||
derpMap: &tailcfg.DERPMap{},
|
||||
@@ -366,10 +366,10 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "no-pol-with-peer-map-response",
|
||||
pol: &policy.ACLPolicy{},
|
||||
machine: mini,
|
||||
peers: types.Machines{
|
||||
name: "no-pol-with-peer-map-response",
|
||||
pol: &policy.ACLPolicy{},
|
||||
node: mini,
|
||||
peers: types.Nodes{
|
||||
peer1,
|
||||
},
|
||||
baseDomain: "",
|
||||
@@ -409,8 +409,8 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
machine: mini,
|
||||
peers: types.Machines{
|
||||
node: mini,
|
||||
peers: types.Nodes{
|
||||
peer1,
|
||||
peer2,
|
||||
},
|
||||
@@ -457,7 +457,7 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mappy := NewMapper(
|
||||
tt.machine,
|
||||
tt.node,
|
||||
tt.peers,
|
||||
nil,
|
||||
false,
|
||||
@@ -469,7 +469,7 @@ func Test_fullMapResponse(t *testing.T) {
|
||||
)
|
||||
|
||||
got, err := mappy.fullMapResponse(
|
||||
tt.machine,
|
||||
tt.node,
|
||||
tt.pol,
|
||||
)
|
||||
|
||||
|
@@ -14,16 +14,16 @@ import (
|
||||
)
|
||||
|
||||
func tailNodes(
|
||||
machines types.Machines,
|
||||
nodes types.Nodes,
|
||||
pol *policy.ACLPolicy,
|
||||
dnsConfig *tailcfg.DNSConfig,
|
||||
baseDomain string,
|
||||
) ([]*tailcfg.Node, error) {
|
||||
nodes := make([]*tailcfg.Node, len(machines))
|
||||
tNodes := make([]*tailcfg.Node, len(nodes))
|
||||
|
||||
for index, machine := range machines {
|
||||
for index, node := range nodes {
|
||||
node, err := tailNode(
|
||||
machine,
|
||||
node,
|
||||
pol,
|
||||
dnsConfig,
|
||||
baseDomain,
|
||||
@@ -32,37 +32,36 @@ func tailNodes(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nodes[index] = node
|
||||
tNodes[index] = node
|
||||
}
|
||||
|
||||
return nodes, nil
|
||||
return tNodes, nil
|
||||
}
|
||||
|
||||
// tailNode converts a Machine into a Tailscale Node. includeRoutes is false for shared nodes
|
||||
// tailNode converts a Node into a Tailscale Node. includeRoutes is false for shared nodes
|
||||
// as per the expected behaviour in the official SaaS.
|
||||
func tailNode(
|
||||
machine *types.Machine,
|
||||
node *types.Node,
|
||||
pol *policy.ACLPolicy,
|
||||
dnsConfig *tailcfg.DNSConfig,
|
||||
baseDomain string,
|
||||
) (*tailcfg.Node, error) {
|
||||
nodeKey, err := machine.NodePublicKey()
|
||||
nodeKey, err := node.NodePublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// MachineKey is only used in the legacy protocol
|
||||
machineKey, err := machine.MachinePublicKey()
|
||||
machineKey, err := node.MachinePublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
discoKey, err := machine.DiscoPublicKey()
|
||||
discoKey, err := node.DiscoPublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addrs := machine.IPAddresses.Prefixes()
|
||||
addrs := node.IPAddresses.Prefixes()
|
||||
|
||||
allowedIPs := append(
|
||||
[]netip.Prefix{},
|
||||
@@ -70,7 +69,7 @@ func tailNode(
|
||||
|
||||
primaryPrefixes := []netip.Prefix{}
|
||||
|
||||
for _, route := range machine.Routes {
|
||||
for _, route := range node.Routes {
|
||||
if route.Enabled {
|
||||
if route.IsPrimary {
|
||||
allowedIPs = append(allowedIPs, netip.Prefix(route.Prefix))
|
||||
@@ -82,39 +81,39 @@ func tailNode(
|
||||
}
|
||||
|
||||
var derp string
|
||||
if machine.HostInfo.NetInfo != nil {
|
||||
derp = fmt.Sprintf("127.3.3.40:%d", machine.HostInfo.NetInfo.PreferredDERP)
|
||||
if node.HostInfo.NetInfo != nil {
|
||||
derp = fmt.Sprintf("127.3.3.40:%d", node.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
|
||||
if node.Expiry != nil {
|
||||
keyExpiry = *node.Expiry
|
||||
} else {
|
||||
keyExpiry = time.Time{}
|
||||
}
|
||||
|
||||
hostname, err := machine.GetFQDN(dnsConfig, baseDomain)
|
||||
hostname, err := node.GetFQDN(dnsConfig, baseDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostInfo := machine.GetHostInfo()
|
||||
hostInfo := node.GetHostInfo()
|
||||
|
||||
online := machine.IsOnline()
|
||||
online := node.IsOnline()
|
||||
|
||||
tags, _ := pol.TagsOfMachine(machine)
|
||||
tags = lo.Uniq(append(tags, machine.ForcedTags...))
|
||||
tags, _ := pol.TagsOfNode(node)
|
||||
tags = lo.Uniq(append(tags, node.ForcedTags...))
|
||||
|
||||
node := tailcfg.Node{
|
||||
ID: tailcfg.NodeID(machine.ID), // this is the actual ID
|
||||
tNode := tailcfg.Node{
|
||||
ID: tailcfg.NodeID(node.ID), // this is the actual ID
|
||||
StableID: tailcfg.StableNodeID(
|
||||
strconv.FormatUint(machine.ID, util.Base10),
|
||||
strconv.FormatUint(node.ID, util.Base10),
|
||||
), // in headscale, unlike tailcontrol server, IDs are permanent
|
||||
Name: hostname,
|
||||
|
||||
User: tailcfg.UserID(machine.UserID),
|
||||
User: tailcfg.UserID(node.UserID),
|
||||
|
||||
Key: nodeKey,
|
||||
KeyExpiry: keyExpiry,
|
||||
@@ -123,19 +122,19 @@ func tailNode(
|
||||
DiscoKey: discoKey,
|
||||
Addresses: addrs,
|
||||
AllowedIPs: allowedIPs,
|
||||
Endpoints: machine.Endpoints,
|
||||
Endpoints: node.Endpoints,
|
||||
DERP: derp,
|
||||
Hostinfo: hostInfo.View(),
|
||||
Created: machine.CreatedAt,
|
||||
Created: node.CreatedAt,
|
||||
|
||||
Tags: tags,
|
||||
|
||||
PrimaryRoutes: primaryPrefixes,
|
||||
|
||||
LastSeen: machine.LastSeen,
|
||||
LastSeen: node.LastSeen,
|
||||
Online: &online,
|
||||
KeepAlive: true,
|
||||
MachineAuthorized: !machine.IsExpired(),
|
||||
MachineAuthorized: !node.IsExpired(),
|
||||
|
||||
Capabilities: []string{
|
||||
tailcfg.CapabilityFileSharing,
|
||||
@@ -144,5 +143,5 @@ func tailNode(
|
||||
},
|
||||
}
|
||||
|
||||
return &node, nil
|
||||
return &tNode, nil
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ func TestTailNode(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
machine *types.Machine
|
||||
node *types.Node
|
||||
pol *policy.ACLPolicy
|
||||
dnsConfig *tailcfg.DNSConfig
|
||||
baseDomain string
|
||||
@@ -53,8 +53,8 @@ func TestTailNode(t *testing.T) {
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty-machine",
|
||||
machine: &types.Machine{},
|
||||
name: "empty-node",
|
||||
node: &types.Node{},
|
||||
pol: &policy.ACLPolicy{},
|
||||
dnsConfig: &tailcfg.DNSConfig{},
|
||||
baseDomain: "",
|
||||
@@ -62,8 +62,8 @@ func TestTailNode(t *testing.T) {
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "minimal-machine",
|
||||
machine: &types.Machine{
|
||||
name: "minimal-node",
|
||||
node: &types.Node{
|
||||
ID: 0,
|
||||
MachineKey: "mkey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507",
|
||||
NodeKey: "nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe",
|
||||
@@ -165,7 +165,7 @@ func TestTailNode(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tailNode(
|
||||
tt.machine,
|
||||
tt.node,
|
||||
tt.pol,
|
||||
tt.dnsConfig,
|
||||
tt.baseDomain,
|
||||
|
@@ -8,18 +8,18 @@ import (
|
||||
const prometheusNamespace = "headscale"
|
||||
|
||||
var (
|
||||
// This is a high cardinality metric (user x machines), we might want to make this
|
||||
// This is a high cardinality metric (user x node), we might want to make this
|
||||
// configurable/opt-in in the future.
|
||||
machineRegistrations = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
nodeRegistrations = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: prometheusNamespace,
|
||||
Name: "machine_registrations_total",
|
||||
Help: "The total amount of registered machine attempts",
|
||||
Name: "node_registrations_total",
|
||||
Help: "The total amount of registered node attempts",
|
||||
}, []string{"action", "auth", "status", "user"})
|
||||
|
||||
updateRequestsSentToNode = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: prometheusNamespace,
|
||||
Name: "update_request_sent_to_node_total",
|
||||
Help: "The number of calls/messages issued on a specific nodes update channel",
|
||||
}, []string{"user", "machine", "status"})
|
||||
}, []string{"user", "node", "status"})
|
||||
// TODO(kradalby): This is very debugging, we might want to remove it.
|
||||
)
|
||||
|
@@ -37,8 +37,8 @@ var (
|
||||
errOIDCAllowedUsers = errors.New(
|
||||
"authenticated principal does not match any allowed user",
|
||||
)
|
||||
errOIDCInvalidMachineState = errors.New(
|
||||
"requested machine state key expired before authorisation completed",
|
||||
errOIDCInvalidNodeState = errors.New(
|
||||
"requested node state key expired before authorisation completed",
|
||||
)
|
||||
errOIDCNodeKeyMissing = errors.New("could not get node key from cache")
|
||||
)
|
||||
@@ -184,9 +184,9 @@ var oidcCallbackTemplate = template.Must(
|
||||
)
|
||||
|
||||
// OIDCCallback handles the callback from the OIDC endpoint
|
||||
// Retrieves the nkey from the state cache and adds the machine to the users email user
|
||||
// TODO: A confirmation page for new machines should be added to avoid phishing vulnerabilities
|
||||
// TODO: Add groups information from OIDC tokens into machine HostInfo
|
||||
// Retrieves the nkey from the state cache and adds the node to the users email user
|
||||
// TODO: A confirmation page for new nodes should be added to avoid phishing vulnerabilities
|
||||
// TODO: Add groups information from OIDC tokens into node HostInfo
|
||||
// Listens in /oidc/callback.
|
||||
func (h *Headscale) OIDCCallback(
|
||||
writer http.ResponseWriter,
|
||||
@@ -232,13 +232,13 @@ func (h *Headscale) OIDCCallback(
|
||||
return
|
||||
}
|
||||
|
||||
nodeKey, machineExists, err := h.validateMachineForOIDCCallback(
|
||||
nodeKey, nodeExists, err := h.validateNodeForOIDCCallback(
|
||||
writer,
|
||||
state,
|
||||
claims,
|
||||
idTokenExpiry,
|
||||
)
|
||||
if err != nil || machineExists {
|
||||
if err != nil || nodeExists {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -247,15 +247,15 @@ func (h *Headscale) OIDCCallback(
|
||||
return
|
||||
}
|
||||
|
||||
// register the machine if it's new
|
||||
log.Debug().Msg("Registering new machine after successful callback")
|
||||
// register the node if it's new
|
||||
log.Debug().Msg("Registering new node after successful callback")
|
||||
|
||||
user, err := h.findOrCreateNewUserForOIDCCallback(writer, userName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.registerMachineForOIDCCallback(writer, user, nodeKey, idTokenExpiry); err != nil {
|
||||
if err := h.registerNodeForOIDCCallback(writer, user, nodeKey, idTokenExpiry); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -453,21 +453,21 @@ func validateOIDCAllowedUsers(
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateMachine retrieves machine information if it exist
|
||||
// validateNode retrieves node information if it exist
|
||||
// The error is not important, because if it does not
|
||||
// exist, then this is a new machine and we will move
|
||||
// exist, then this is a new node and we will move
|
||||
// on to registration.
|
||||
func (h *Headscale) validateMachineForOIDCCallback(
|
||||
func (h *Headscale) validateNodeForOIDCCallback(
|
||||
writer http.ResponseWriter,
|
||||
state string,
|
||||
claims *IDTokenClaims,
|
||||
expiry time.Time,
|
||||
) (*key.NodePublic, bool, error) {
|
||||
// retrieve machinekey from state cache
|
||||
// retrieve nodekey from state cache
|
||||
nodeKeyIf, nodeKeyFound := h.registrationCache.Get(state)
|
||||
if !nodeKeyFound {
|
||||
log.Trace().
|
||||
Msg("requested machine state key expired before authorisation completed")
|
||||
Msg("requested node state key expired before authorisation completed")
|
||||
writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
_, err := writer.Write([]byte("state has expired"))
|
||||
@@ -482,7 +482,7 @@ func (h *Headscale) validateMachineForOIDCCallback(
|
||||
nodeKeyFromCache, nodeKeyOK := nodeKeyIf.(string)
|
||||
if !nodeKeyOK {
|
||||
log.Trace().
|
||||
Msg("requested machine state key is not a string")
|
||||
Msg("requested node state key is not a string")
|
||||
writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
_, err := writer.Write([]byte("state is invalid"))
|
||||
@@ -490,7 +490,7 @@ func (h *Headscale) validateMachineForOIDCCallback(
|
||||
util.LogErr(err, "Failed to write response")
|
||||
}
|
||||
|
||||
return nil, false, errOIDCInvalidMachineState
|
||||
return nil, false, errOIDCInvalidNodeState
|
||||
}
|
||||
|
||||
err := nodeKey.UnmarshalText(
|
||||
@@ -511,33 +511,33 @@ func (h *Headscale) validateMachineForOIDCCallback(
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// retrieve machine information if it exist
|
||||
// retrieve node information if it exist
|
||||
// The error is not important, because if it does not
|
||||
// exist, then this is a new machine and we will move
|
||||
// exist, then this is a new node and we will move
|
||||
// on to registration.
|
||||
machine, _ := h.db.GetMachineByNodeKey(nodeKey)
|
||||
node, _ := h.db.GetNodeByNodeKey(nodeKey)
|
||||
|
||||
if machine != nil {
|
||||
if node != nil {
|
||||
log.Trace().
|
||||
Caller().
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("machine already registered, reauthenticating")
|
||||
Str("node", node.Hostname).
|
||||
Msg("node already registered, reauthenticating")
|
||||
|
||||
err := h.db.MachineSetExpiry(machine, expiry)
|
||||
err := h.db.NodeSetExpiry(node, expiry)
|
||||
if err != nil {
|
||||
util.LogErr(err, "Failed to refresh machine")
|
||||
util.LogErr(err, "Failed to refresh node")
|
||||
http.Error(
|
||||
writer,
|
||||
"Failed to refresh machine",
|
||||
"Failed to refresh node",
|
||||
http.StatusInternalServerError,
|
||||
)
|
||||
|
||||
return nil, true, err
|
||||
}
|
||||
log.Debug().
|
||||
Str("machine", machine.Hostname).
|
||||
Str("node", node.Hostname).
|
||||
Str("expiresAt", fmt.Sprintf("%v", expiry)).
|
||||
Msg("successfully refreshed machine")
|
||||
Msg("successfully refreshed node")
|
||||
|
||||
var content bytes.Buffer
|
||||
if err := oidcCallbackTemplate.Execute(&content, oidcCallbackTemplateConfig{
|
||||
@@ -638,13 +638,13 @@ func (h *Headscale) findOrCreateNewUserForOIDCCallback(
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (h *Headscale) registerMachineForOIDCCallback(
|
||||
func (h *Headscale) registerNodeForOIDCCallback(
|
||||
writer http.ResponseWriter,
|
||||
user *types.User,
|
||||
nodeKey *key.NodePublic,
|
||||
expiry time.Time,
|
||||
) error {
|
||||
if _, err := h.db.RegisterMachineFromAuthCallback(
|
||||
if _, err := h.db.RegisterNodeFromAuthCallback(
|
||||
// TODO(kradalby): find a better way to use the cache across modules
|
||||
h.registrationCache,
|
||||
nodeKey.String(),
|
||||
@@ -652,10 +652,10 @@ func (h *Headscale) registerMachineForOIDCCallback(
|
||||
&expiry,
|
||||
util.RegisterMethodOIDC,
|
||||
); err != nil {
|
||||
util.LogErr(err, "could not register machine")
|
||||
util.LogErr(err, "could not register node")
|
||||
writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
_, werr := writer.Write([]byte("could not register machine"))
|
||||
_, werr := writer.Write([]byte("could not register node"))
|
||||
if werr != nil {
|
||||
util.LogErr(err, "Failed to write response")
|
||||
}
|
||||
|
@@ -116,30 +116,30 @@ func LoadACLPolicyFromBytes(acl []byte, format string) (*ACLPolicy, error) {
|
||||
|
||||
func GenerateFilterAndSSHRules(
|
||||
policy *ACLPolicy,
|
||||
machine *types.Machine,
|
||||
peers types.Machines,
|
||||
node *types.Node,
|
||||
peers types.Nodes,
|
||||
) ([]tailcfg.FilterRule, *tailcfg.SSHPolicy, error) {
|
||||
// If there is no policy defined, we default to allow all
|
||||
if policy == nil {
|
||||
return tailcfg.FilterAllowAll, &tailcfg.SSHPolicy{}, nil
|
||||
}
|
||||
|
||||
rules, err := policy.generateFilterRules(machine, peers)
|
||||
rules, err := policy.generateFilterRules(node, peers)
|
||||
if err != nil {
|
||||
return []tailcfg.FilterRule{}, &tailcfg.SSHPolicy{}, err
|
||||
}
|
||||
|
||||
log.Trace().Interface("ACL", rules).Str("machine", machine.GivenName).Msg("ACL rules")
|
||||
log.Trace().Interface("ACL", rules).Str("node", node.GivenName).Msg("ACL rules")
|
||||
|
||||
var sshPolicy *tailcfg.SSHPolicy
|
||||
sshRules, err := policy.generateSSHRules(machine, peers)
|
||||
sshRules, err := policy.generateSSHRules(node, peers)
|
||||
if err != nil {
|
||||
return []tailcfg.FilterRule{}, &tailcfg.SSHPolicy{}, err
|
||||
}
|
||||
|
||||
log.Trace().
|
||||
Interface("SSH", sshRules).
|
||||
Str("machine", machine.GivenName).
|
||||
Str("node", node.GivenName).
|
||||
Msg("SSH rules")
|
||||
|
||||
if sshPolicy == nil {
|
||||
@@ -150,14 +150,14 @@ func GenerateFilterAndSSHRules(
|
||||
return rules, sshPolicy, nil
|
||||
}
|
||||
|
||||
// generateFilterRules takes a set of machines and an ACLPolicy and generates a
|
||||
// generateFilterRules takes a set of nodes and an ACLPolicy and generates a
|
||||
// set of Tailscale compatible FilterRules used to allow traffic on clients.
|
||||
func (pol *ACLPolicy) generateFilterRules(
|
||||
machine *types.Machine,
|
||||
peers types.Machines,
|
||||
node *types.Node,
|
||||
peers types.Nodes,
|
||||
) ([]tailcfg.FilterRule, error) {
|
||||
rules := []tailcfg.FilterRule{}
|
||||
machines := append(peers, machine)
|
||||
nodes := append(peers, node)
|
||||
|
||||
for index, acl := range pol.ACLs {
|
||||
if acl.Action != "accept" {
|
||||
@@ -166,7 +166,7 @@ func (pol *ACLPolicy) generateFilterRules(
|
||||
|
||||
srcIPs := []string{}
|
||||
for srcIndex, src := range acl.Sources {
|
||||
srcs, err := pol.expandSource(src, machines)
|
||||
srcs, err := pol.expandSource(src, nodes)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Interface("src", src).
|
||||
@@ -195,7 +195,7 @@ func (pol *ACLPolicy) generateFilterRules(
|
||||
}
|
||||
|
||||
expanded, err := pol.ExpandAlias(
|
||||
machines,
|
||||
nodes,
|
||||
alias,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -230,13 +230,13 @@ func (pol *ACLPolicy) generateFilterRules(
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
// ReduceFilterRules takes a machine and a set of rules and removes all rules and destinations
|
||||
// ReduceFilterRules takes a node and a set of rules and removes all rules and destinations
|
||||
// that are not relevant to that particular node.
|
||||
func ReduceFilterRules(machine *types.Machine, rules []tailcfg.FilterRule) []tailcfg.FilterRule {
|
||||
func ReduceFilterRules(node *types.Node, rules []tailcfg.FilterRule) []tailcfg.FilterRule {
|
||||
ret := []tailcfg.FilterRule{}
|
||||
|
||||
for _, rule := range rules {
|
||||
// record if the rule is actually relevant for the given machine.
|
||||
// record if the rule is actually relevant for the given node.
|
||||
dests := []tailcfg.NetPortRange{}
|
||||
|
||||
for _, dest := range rule.DstPorts {
|
||||
@@ -247,7 +247,7 @@ func ReduceFilterRules(machine *types.Machine, rules []tailcfg.FilterRule) []tai
|
||||
continue
|
||||
}
|
||||
|
||||
if machine.IPAddresses.InIPSet(expanded) {
|
||||
if node.IPAddresses.InIPSet(expanded) {
|
||||
dests = append(dests, dest)
|
||||
}
|
||||
}
|
||||
@@ -265,8 +265,8 @@ func ReduceFilterRules(machine *types.Machine, rules []tailcfg.FilterRule) []tai
|
||||
}
|
||||
|
||||
func (pol *ACLPolicy) generateSSHRules(
|
||||
machine *types.Machine,
|
||||
peers types.Machines,
|
||||
node *types.Node,
|
||||
peers types.Nodes,
|
||||
) ([]*tailcfg.SSHRule, error) {
|
||||
rules := []*tailcfg.SSHRule{}
|
||||
|
||||
@@ -293,7 +293,7 @@ func (pol *ACLPolicy) generateSSHRules(
|
||||
for index, sshACL := range pol.SSHs {
|
||||
var dest netipx.IPSetBuilder
|
||||
for _, src := range sshACL.Destinations {
|
||||
expanded, err := pol.ExpandAlias(append(peers, machine), src)
|
||||
expanded, err := pol.ExpandAlias(append(peers, node), src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -305,7 +305,7 @@ func (pol *ACLPolicy) generateSSHRules(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !machine.IPAddresses.InIPSet(destSet) {
|
||||
if !node.IPAddresses.InIPSet(destSet) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -501,9 +501,9 @@ func parseProtocol(protocol string) ([]int, bool, error) {
|
||||
// with the given src alias.
|
||||
func (pol *ACLPolicy) expandSource(
|
||||
src string,
|
||||
machines types.Machines,
|
||||
nodes types.Nodes,
|
||||
) ([]string, error) {
|
||||
ipSet, err := pol.ExpandAlias(machines, src)
|
||||
ipSet, err := pol.ExpandAlias(nodes, src)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
@@ -526,7 +526,7 @@ func (pol *ACLPolicy) expandSource(
|
||||
// - a cidr
|
||||
// and transform these in IPAddresses.
|
||||
func (pol *ACLPolicy) ExpandAlias(
|
||||
machines types.Machines,
|
||||
nodes types.Nodes,
|
||||
alias string,
|
||||
) (*netipx.IPSet, error) {
|
||||
if isWildcard(alias) {
|
||||
@@ -541,16 +541,16 @@ func (pol *ACLPolicy) ExpandAlias(
|
||||
|
||||
// if alias is a group
|
||||
if isGroup(alias) {
|
||||
return pol.expandIPsFromGroup(alias, machines)
|
||||
return pol.expandIPsFromGroup(alias, nodes)
|
||||
}
|
||||
|
||||
// if alias is a tag
|
||||
if isTag(alias) {
|
||||
return pol.expandIPsFromTag(alias, machines)
|
||||
return pol.expandIPsFromTag(alias, nodes)
|
||||
}
|
||||
|
||||
// if alias is a user
|
||||
if ips, err := pol.expandIPsFromUser(alias, machines); ips != nil {
|
||||
if ips, err := pol.expandIPsFromUser(alias, nodes); ips != nil {
|
||||
return ips, err
|
||||
}
|
||||
|
||||
@@ -559,17 +559,17 @@ func (pol *ACLPolicy) ExpandAlias(
|
||||
if h, ok := pol.Hosts[alias]; ok {
|
||||
log.Trace().Str("host", h.String()).Msg("ExpandAlias got hosts entry")
|
||||
|
||||
return pol.ExpandAlias(machines, h.String())
|
||||
return pol.ExpandAlias(nodes, h.String())
|
||||
}
|
||||
|
||||
// if alias is an IP
|
||||
if ip, err := netip.ParseAddr(alias); err == nil {
|
||||
return pol.expandIPsFromSingleIP(ip, machines)
|
||||
return pol.expandIPsFromSingleIP(ip, nodes)
|
||||
}
|
||||
|
||||
// if alias is an IP Prefix (CIDR)
|
||||
if prefix, err := netip.ParsePrefix(alias); err == nil {
|
||||
return pol.expandIPsFromIPPrefix(prefix, machines)
|
||||
return pol.expandIPsFromIPPrefix(prefix, nodes)
|
||||
}
|
||||
|
||||
log.Warn().Msgf("No IPs found with the alias %v", alias)
|
||||
@@ -582,10 +582,10 @@ func (pol *ACLPolicy) ExpandAlias(
|
||||
// we assume in this function that we only have nodes from 1 user.
|
||||
func excludeCorrectlyTaggedNodes(
|
||||
aclPolicy *ACLPolicy,
|
||||
nodes types.Machines,
|
||||
nodes types.Nodes,
|
||||
user string,
|
||||
) types.Machines {
|
||||
out := types.Machines{}
|
||||
) types.Nodes {
|
||||
out := types.Nodes{}
|
||||
tags := []string{}
|
||||
for tag := range aclPolicy.TagOwners {
|
||||
owners, _ := expandOwnersFromTag(aclPolicy, user)
|
||||
@@ -594,9 +594,9 @@ func excludeCorrectlyTaggedNodes(
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
}
|
||||
// for each machine if tag is in tags list, don't append it.
|
||||
for _, machine := range nodes {
|
||||
hi := machine.GetHostInfo()
|
||||
// for each node if tag is in tags list, don't append it.
|
||||
for _, node := range nodes {
|
||||
hi := node.GetHostInfo()
|
||||
|
||||
found := false
|
||||
for _, t := range hi.RequestTags {
|
||||
@@ -606,11 +606,11 @@ func excludeCorrectlyTaggedNodes(
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(machine.ForcedTags) > 0 {
|
||||
if len(node.ForcedTags) > 0 {
|
||||
found = true
|
||||
}
|
||||
if !found {
|
||||
out = append(out, machine)
|
||||
out = append(out, node)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,7 +733,7 @@ func (pol *ACLPolicy) expandUsersFromGroup(
|
||||
|
||||
func (pol *ACLPolicy) expandIPsFromGroup(
|
||||
group string,
|
||||
machines types.Machines,
|
||||
nodes types.Nodes,
|
||||
) (*netipx.IPSet, error) {
|
||||
build := netipx.IPSetBuilder{}
|
||||
|
||||
@@ -742,9 +742,9 @@ func (pol *ACLPolicy) expandIPsFromGroup(
|
||||
return &netipx.IPSet{}, err
|
||||
}
|
||||
for _, user := range users {
|
||||
filteredMachines := filterMachinesByUser(machines, user)
|
||||
for _, machine := range filteredMachines {
|
||||
machine.IPAddresses.AppendToIPSet(&build)
|
||||
filteredNodes := filterNodesByUser(nodes, user)
|
||||
for _, node := range filteredNodes {
|
||||
node.IPAddresses.AppendToIPSet(&build)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -753,14 +753,14 @@ func (pol *ACLPolicy) expandIPsFromGroup(
|
||||
|
||||
func (pol *ACLPolicy) expandIPsFromTag(
|
||||
alias string,
|
||||
machines types.Machines,
|
||||
nodes types.Nodes,
|
||||
) (*netipx.IPSet, error) {
|
||||
build := netipx.IPSetBuilder{}
|
||||
|
||||
// check for forced tags
|
||||
for _, machine := range machines {
|
||||
if util.StringOrPrefixListContains(machine.ForcedTags, alias) {
|
||||
machine.IPAddresses.AppendToIPSet(&build)
|
||||
for _, node := range nodes {
|
||||
if util.StringOrPrefixListContains(node.ForcedTags, alias) {
|
||||
node.IPAddresses.AppendToIPSet(&build)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,13 +783,13 @@ func (pol *ACLPolicy) expandIPsFromTag(
|
||||
}
|
||||
}
|
||||
|
||||
// filter out machines per tag owner
|
||||
// filter out nodes per tag owner
|
||||
for _, user := range owners {
|
||||
machines := filterMachinesByUser(machines, user)
|
||||
for _, machine := range machines {
|
||||
hi := machine.GetHostInfo()
|
||||
nodes := filterNodesByUser(nodes, user)
|
||||
for _, node := range nodes {
|
||||
hi := node.GetHostInfo()
|
||||
if util.StringOrPrefixListContains(hi.RequestTags, alias) {
|
||||
machine.IPAddresses.AppendToIPSet(&build)
|
||||
node.IPAddresses.AppendToIPSet(&build)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -799,20 +799,20 @@ func (pol *ACLPolicy) expandIPsFromTag(
|
||||
|
||||
func (pol *ACLPolicy) expandIPsFromUser(
|
||||
user string,
|
||||
machines types.Machines,
|
||||
nodes types.Nodes,
|
||||
) (*netipx.IPSet, error) {
|
||||
build := netipx.IPSetBuilder{}
|
||||
|
||||
filteredMachines := filterMachinesByUser(machines, user)
|
||||
filteredMachines = excludeCorrectlyTaggedNodes(pol, filteredMachines, user)
|
||||
filteredNodes := filterNodesByUser(nodes, user)
|
||||
filteredNodes = excludeCorrectlyTaggedNodes(pol, filteredNodes, user)
|
||||
|
||||
// shortcurcuit if we have no machines to get ips from.
|
||||
if len(filteredMachines) == 0 {
|
||||
// shortcurcuit if we have no nodes to get ips from.
|
||||
if len(filteredNodes) == 0 {
|
||||
return nil, nil //nolint
|
||||
}
|
||||
|
||||
for _, machine := range filteredMachines {
|
||||
machine.IPAddresses.AppendToIPSet(&build)
|
||||
for _, node := range filteredNodes {
|
||||
node.IPAddresses.AppendToIPSet(&build)
|
||||
}
|
||||
|
||||
return build.IPSet()
|
||||
@@ -820,17 +820,17 @@ func (pol *ACLPolicy) expandIPsFromUser(
|
||||
|
||||
func (pol *ACLPolicy) expandIPsFromSingleIP(
|
||||
ip netip.Addr,
|
||||
machines types.Machines,
|
||||
nodes types.Nodes,
|
||||
) (*netipx.IPSet, error) {
|
||||
log.Trace().Str("ip", ip.String()).Msg("ExpandAlias got ip")
|
||||
|
||||
matches := machines.FilterByIP(ip)
|
||||
matches := nodes.FilterByIP(ip)
|
||||
|
||||
build := netipx.IPSetBuilder{}
|
||||
build.Add(ip)
|
||||
|
||||
for _, machine := range matches {
|
||||
machine.IPAddresses.AppendToIPSet(&build)
|
||||
for _, node := range matches {
|
||||
node.IPAddresses.AppendToIPSet(&build)
|
||||
}
|
||||
|
||||
return build.IPSet()
|
||||
@@ -838,7 +838,7 @@ func (pol *ACLPolicy) expandIPsFromSingleIP(
|
||||
|
||||
func (pol *ACLPolicy) expandIPsFromIPPrefix(
|
||||
prefix netip.Prefix,
|
||||
machines types.Machines,
|
||||
nodes types.Nodes,
|
||||
) (*netipx.IPSet, error) {
|
||||
log.Trace().Str("prefix", prefix.String()).Msg("expandAlias got prefix")
|
||||
build := netipx.IPSetBuilder{}
|
||||
@@ -846,12 +846,12 @@ func (pol *ACLPolicy) expandIPsFromIPPrefix(
|
||||
|
||||
// This is suboptimal and quite expensive, but if we only add the prefix, we will miss all the relevant IPv6
|
||||
// addresses for the hosts that belong to tailscale. This doesnt really affect stuff like subnet routers.
|
||||
for _, machine := range machines {
|
||||
for _, ip := range machine.IPAddresses {
|
||||
for _, node := range nodes {
|
||||
for _, ip := range node.IPAddresses {
|
||||
// log.Trace().
|
||||
// Msgf("checking if machine ip (%s) is part of prefix (%s): %v, is single ip prefix (%v), addr: %s", ip.String(), prefix.String(), prefix.Contains(ip), prefix.IsSingleIP(), prefix.Addr().String())
|
||||
// Msgf("checking if node ip (%s) is part of prefix (%s): %v, is single ip prefix (%v), addr: %s", ip.String(), prefix.String(), prefix.Contains(ip), prefix.IsSingleIP(), prefix.Addr().String())
|
||||
if prefix.Contains(ip) {
|
||||
machine.IPAddresses.AppendToIPSet(&build)
|
||||
node.IPAddresses.AppendToIPSet(&build)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -871,18 +871,18 @@ func isTag(str string) bool {
|
||||
return strings.HasPrefix(str, "tag:")
|
||||
}
|
||||
|
||||
// TagsOfMachine will return the tags of the current machine.
|
||||
// TagsOfNode will return the tags of the current node.
|
||||
// Invalid tags are tags added by a user on a node, and that user doesn't have authority to add this tag.
|
||||
// Valid tags are tags added by a user that is allowed in the ACL policy to add this tag.
|
||||
func (pol *ACLPolicy) TagsOfMachine(
|
||||
machine *types.Machine,
|
||||
func (pol *ACLPolicy) TagsOfNode(
|
||||
node *types.Node,
|
||||
) ([]string, []string) {
|
||||
validTags := make([]string, 0)
|
||||
invalidTags := make([]string, 0)
|
||||
|
||||
validTagMap := make(map[string]bool)
|
||||
invalidTagMap := make(map[string]bool)
|
||||
for _, tag := range machine.HostInfo.RequestTags {
|
||||
for _, tag := range node.HostInfo.RequestTags {
|
||||
owners, err := expandOwnersFromTag(pol, tag)
|
||||
if errors.Is(err, ErrInvalidTag) {
|
||||
invalidTagMap[tag] = true
|
||||
@@ -891,7 +891,7 @@ func (pol *ACLPolicy) TagsOfMachine(
|
||||
}
|
||||
var found bool
|
||||
for _, owner := range owners {
|
||||
if machine.User.Name == owner {
|
||||
if node.User.Name == owner {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
@@ -911,31 +911,31 @@ func (pol *ACLPolicy) TagsOfMachine(
|
||||
return validTags, invalidTags
|
||||
}
|
||||
|
||||
func filterMachinesByUser(machines types.Machines, user string) types.Machines {
|
||||
out := types.Machines{}
|
||||
for _, machine := range machines {
|
||||
if machine.User.Name == user {
|
||||
out = append(out, machine)
|
||||
func filterNodesByUser(nodes types.Nodes, user string) types.Nodes {
|
||||
out := types.Nodes{}
|
||||
for _, node := range nodes {
|
||||
if node.User.Name == user {
|
||||
out = append(out, node)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// FilterMachinesByACL returns the list of peers authorized to be accessed from a given machine.
|
||||
func FilterMachinesByACL(
|
||||
machine *types.Machine,
|
||||
machines types.Machines,
|
||||
// FilterNodesByACL returns the list of peers authorized to be accessed from a given node.
|
||||
func FilterNodesByACL(
|
||||
node *types.Node,
|
||||
nodes types.Nodes,
|
||||
filter []tailcfg.FilterRule,
|
||||
) types.Machines {
|
||||
result := types.Machines{}
|
||||
) types.Nodes {
|
||||
result := types.Nodes{}
|
||||
|
||||
for index, peer := range machines {
|
||||
if peer.ID == machine.ID {
|
||||
for index, peer := range nodes {
|
||||
if peer.ID == node.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
if machine.CanAccess(filter, machines[index]) || peer.CanAccess(filter, machine) {
|
||||
if node.CanAccess(filter, nodes[index]) || peer.CanAccess(filter, node) {
|
||||
result = append(result, peer)
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -19,13 +19,13 @@ const (
|
||||
|
||||
type contextKey string
|
||||
|
||||
const machineNameContextKey = contextKey("machineName")
|
||||
const nodeNameContextKey = contextKey("nodeName")
|
||||
|
||||
type UpdateNode func()
|
||||
|
||||
func logPollFunc(
|
||||
mapRequest tailcfg.MapRequest,
|
||||
machine *types.Machine,
|
||||
node *types.Node,
|
||||
isNoise bool,
|
||||
) (func(string), func(error, string)) {
|
||||
return func(msg string) {
|
||||
@@ -35,8 +35,8 @@ func logPollFunc(
|
||||
Bool("readOnly", mapRequest.ReadOnly).
|
||||
Bool("omitPeers", mapRequest.OmitPeers).
|
||||
Bool("stream", mapRequest.Stream).
|
||||
Str("node_key", machine.NodeKey).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("node_key", node.NodeKey).
|
||||
Str("node", node.Hostname).
|
||||
Msg(msg)
|
||||
},
|
||||
func(err error, msg string) {
|
||||
@@ -46,8 +46,8 @@ func logPollFunc(
|
||||
Bool("readOnly", mapRequest.ReadOnly).
|
||||
Bool("omitPeers", mapRequest.OmitPeers).
|
||||
Bool("stream", mapRequest.Stream).
|
||||
Str("node_key", machine.NodeKey).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("node_key", node.NodeKey).
|
||||
Str("node", node.Hostname).
|
||||
Err(err).
|
||||
Msg(msg)
|
||||
}
|
||||
@@ -60,11 +60,11 @@ func logPollFunc(
|
||||
func (h *Headscale) handlePoll(
|
||||
writer http.ResponseWriter,
|
||||
ctx context.Context,
|
||||
machine *types.Machine,
|
||||
node *types.Node,
|
||||
mapRequest tailcfg.MapRequest,
|
||||
isNoise bool,
|
||||
) {
|
||||
logInfo, logErr := logPollFunc(mapRequest, machine, isNoise)
|
||||
logInfo, logErr := logPollFunc(mapRequest, node, isNoise)
|
||||
|
||||
// This is the mechanism where the node gives us inforamtion about its
|
||||
// current configuration.
|
||||
@@ -81,28 +81,28 @@ func (h *Headscale) handlePoll(
|
||||
Bool("readOnly", mapRequest.ReadOnly).
|
||||
Bool("omitPeers", mapRequest.OmitPeers).
|
||||
Bool("stream", mapRequest.Stream).
|
||||
Str("node_key", machine.NodeKey).
|
||||
Str("machine", machine.Hostname).
|
||||
Strs("endpoints", machine.Endpoints).
|
||||
Str("node_key", node.NodeKey).
|
||||
Str("node", node.Hostname).
|
||||
Strs("endpoints", node.Endpoints).
|
||||
Msg("Received endpoint update")
|
||||
|
||||
now := time.Now().UTC()
|
||||
machine.LastSeen = &now
|
||||
machine.Hostname = mapRequest.Hostinfo.Hostname
|
||||
machine.HostInfo = types.HostInfo(*mapRequest.Hostinfo)
|
||||
machine.DiscoKey = util.DiscoPublicKeyStripPrefix(mapRequest.DiscoKey)
|
||||
machine.Endpoints = mapRequest.Endpoints
|
||||
node.LastSeen = &now
|
||||
node.Hostname = mapRequest.Hostinfo.Hostname
|
||||
node.HostInfo = types.HostInfo(*mapRequest.Hostinfo)
|
||||
node.DiscoKey = util.DiscoPublicKeyStripPrefix(mapRequest.DiscoKey)
|
||||
node.Endpoints = mapRequest.Endpoints
|
||||
|
||||
if err := h.db.MachineSave(machine); err != nil {
|
||||
logErr(err, "Failed to persist/update machine in the database")
|
||||
if err := h.db.NodeSave(node); err != nil {
|
||||
logErr(err, "Failed to persist/update node in the database")
|
||||
http.Error(writer, "", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err := h.db.SaveMachineRoutes(machine)
|
||||
err := h.db.SaveNodeRoutes(node)
|
||||
if err != nil {
|
||||
logErr(err, "Error processing machine routes")
|
||||
logErr(err, "Error processing node routes")
|
||||
http.Error(writer, "", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
@@ -111,9 +111,9 @@ func (h *Headscale) handlePoll(
|
||||
h.nodeNotifier.NotifyWithIgnore(
|
||||
types.StateUpdate{
|
||||
Type: types.StatePeerChanged,
|
||||
Changed: types.Machines{machine},
|
||||
Changed: types.Nodes{node},
|
||||
},
|
||||
machine.MachineKey)
|
||||
node.MachineKey)
|
||||
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
if f, ok := writer.(http.Flusher); ok {
|
||||
@@ -130,7 +130,7 @@ func (h *Headscale) handlePoll(
|
||||
// The intended use is for clients to discover the DERP map at
|
||||
// start-up before their first real endpoint update.
|
||||
} else if mapRequest.OmitPeers && !mapRequest.Stream && mapRequest.ReadOnly {
|
||||
h.handleLiteRequest(writer, machine, mapRequest, isNoise)
|
||||
h.handleLiteRequest(writer, node, mapRequest, isNoise)
|
||||
|
||||
return
|
||||
} else if mapRequest.OmitPeers && mapRequest.Stream {
|
||||
@@ -143,23 +143,23 @@ func (h *Headscale) handlePoll(
|
||||
// TODO(kradalby): I am not sure if this has any function based on
|
||||
// incoming requests from clients.
|
||||
if mapRequest.ReadOnly && !mapRequest.Stream {
|
||||
h.handleReadOnly(writer, machine, mapRequest, isNoise)
|
||||
h.handleReadOnly(writer, node, mapRequest, isNoise)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
machine.LastSeen = &now
|
||||
machine.Hostname = mapRequest.Hostinfo.Hostname
|
||||
machine.HostInfo = types.HostInfo(*mapRequest.Hostinfo)
|
||||
machine.DiscoKey = util.DiscoPublicKeyStripPrefix(mapRequest.DiscoKey)
|
||||
machine.Endpoints = mapRequest.Endpoints
|
||||
node.LastSeen = &now
|
||||
node.Hostname = mapRequest.Hostinfo.Hostname
|
||||
node.HostInfo = types.HostInfo(*mapRequest.Hostinfo)
|
||||
node.DiscoKey = util.DiscoPublicKeyStripPrefix(mapRequest.DiscoKey)
|
||||
node.Endpoints = mapRequest.Endpoints
|
||||
|
||||
// When a node connects to control, list the peers it has at
|
||||
// that given point, further updates are kept in memory in
|
||||
// the Mapper, which lives for the duration of the polling
|
||||
// session.
|
||||
peers, err := h.db.ListPeers(machine)
|
||||
peers, err := h.db.ListPeers(node)
|
||||
if err != nil {
|
||||
logErr(err, "Failed to list peers when opening poller")
|
||||
http.Error(writer, "", http.StatusInternalServerError)
|
||||
@@ -168,7 +168,7 @@ func (h *Headscale) handlePoll(
|
||||
}
|
||||
|
||||
mapp := mapper.NewMapper(
|
||||
machine,
|
||||
node,
|
||||
peers,
|
||||
h.privateKey2019,
|
||||
isNoise,
|
||||
@@ -179,23 +179,23 @@ func (h *Headscale) handlePoll(
|
||||
h.cfg.RandomizeClientPort,
|
||||
)
|
||||
|
||||
err = h.db.SaveMachineRoutes(machine)
|
||||
err = h.db.SaveNodeRoutes(node)
|
||||
if err != nil {
|
||||
logErr(err, "Error processing machine routes")
|
||||
logErr(err, "Error processing node routes")
|
||||
}
|
||||
|
||||
// update ACLRules with peer informations (to update server tags if necessary)
|
||||
if h.ACLPolicy != nil {
|
||||
// update routes with peer information
|
||||
err = h.db.EnableAutoApprovedRoutes(h.ACLPolicy, machine)
|
||||
err = h.db.EnableAutoApprovedRoutes(h.ACLPolicy, node)
|
||||
if err != nil {
|
||||
logErr(err, "Error running auto approved routes")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(kradalby): Save specific stuff, not whole object.
|
||||
if err := h.db.MachineSave(machine); err != nil {
|
||||
logErr(err, "Failed to persist/update machine in the database")
|
||||
if err := h.db.NodeSave(node); err != nil {
|
||||
logErr(err, "Failed to persist/update node in the database")
|
||||
http.Error(writer, "", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
@@ -203,7 +203,7 @@ func (h *Headscale) handlePoll(
|
||||
|
||||
logInfo("Sending initial map")
|
||||
|
||||
mapResp, err := mapp.FullMapResponse(mapRequest, machine, h.ACLPolicy)
|
||||
mapResp, err := mapp.FullMapResponse(mapRequest, node, h.ACLPolicy)
|
||||
if err != nil {
|
||||
logErr(err, "Failed to create MapResponse")
|
||||
http.Error(writer, "", http.StatusInternalServerError)
|
||||
@@ -228,24 +228,24 @@ func (h *Headscale) handlePoll(
|
||||
h.nodeNotifier.NotifyWithIgnore(
|
||||
types.StateUpdate{
|
||||
Type: types.StatePeerChanged,
|
||||
Changed: types.Machines{machine},
|
||||
Changed: types.Nodes{node},
|
||||
},
|
||||
machine.MachineKey)
|
||||
node.MachineKey)
|
||||
|
||||
// Set up the client stream
|
||||
h.pollNetMapStreamWG.Add(1)
|
||||
defer h.pollNetMapStreamWG.Done()
|
||||
|
||||
updateChan := make(chan types.StateUpdate)
|
||||
defer closeChanWithLog(updateChan, machine.Hostname, "updateChan")
|
||||
defer closeChanWithLog(updateChan, node.Hostname, "updateChan")
|
||||
|
||||
// Register the node's update channel
|
||||
h.nodeNotifier.AddNode(machine.MachineKey, updateChan)
|
||||
defer h.nodeNotifier.RemoveNode(machine.MachineKey)
|
||||
h.nodeNotifier.AddNode(node.MachineKey, updateChan)
|
||||
defer h.nodeNotifier.RemoveNode(node.MachineKey)
|
||||
|
||||
keepAliveTicker := time.NewTicker(keepAliveInterval)
|
||||
|
||||
ctx = context.WithValue(ctx, machineNameContextKey, machine.Hostname)
|
||||
ctx = context.WithValue(ctx, nodeNameContextKey, node.Hostname)
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
@@ -254,7 +254,7 @@ func (h *Headscale) handlePoll(
|
||||
logInfo("Waiting for update on stream channel")
|
||||
select {
|
||||
case <-keepAliveTicker.C:
|
||||
data, err := mapp.KeepAliveResponse(mapRequest, machine)
|
||||
data, err := mapp.KeepAliveResponse(mapRequest, node)
|
||||
if err != nil {
|
||||
logErr(err, "Error generating the keep alive msg")
|
||||
|
||||
@@ -280,9 +280,9 @@ func (h *Headscale) handlePoll(
|
||||
// goroutines, but then you might have a problem without a lock
|
||||
// if a keepalive is written at the same time as an update.
|
||||
go func() {
|
||||
err = h.db.UpdateLastSeen(machine)
|
||||
err = h.db.UpdateLastSeen(node)
|
||||
if err != nil {
|
||||
logErr(err, "Cannot update machine LastSeen")
|
||||
logErr(err, "Cannot update node LastSeen")
|
||||
|
||||
return
|
||||
}
|
||||
@@ -298,16 +298,16 @@ func (h *Headscale) handlePoll(
|
||||
switch update.Type {
|
||||
case types.StatePeerChanged:
|
||||
logInfo("Sending PeerChanged MapResponse")
|
||||
data, err = mapp.PeerChangedResponse(mapRequest, machine, update.Changed, h.ACLPolicy)
|
||||
data, err = mapp.PeerChangedResponse(mapRequest, node, update.Changed, h.ACLPolicy)
|
||||
case types.StatePeerRemoved:
|
||||
logInfo("Sending PeerRemoved MapResponse")
|
||||
data, err = mapp.PeerRemovedResponse(mapRequest, machine, update.Removed)
|
||||
data, err = mapp.PeerRemovedResponse(mapRequest, node, update.Removed)
|
||||
case types.StateDERPUpdated:
|
||||
logInfo("Sending DERPUpdate MapResponse")
|
||||
data, err = mapp.DERPMapResponse(mapRequest, machine, update.DERPMap)
|
||||
data, err = mapp.DERPMapResponse(mapRequest, node, update.DERPMap)
|
||||
case types.StateFullUpdate:
|
||||
logInfo("Sending Full MapResponse")
|
||||
data, err = mapp.FullMapResponse(mapRequest, machine, h.ACLPolicy)
|
||||
data, err = mapp.FullMapResponse(mapRequest, node, h.ACLPolicy)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -320,7 +320,7 @@ func (h *Headscale) handlePoll(
|
||||
if err != nil {
|
||||
logErr(err, "Could not write the map response")
|
||||
|
||||
updateRequestsSentToNode.WithLabelValues(machine.User.Name, machine.Hostname, "failed").
|
||||
updateRequestsSentToNode.WithLabelValues(node.User.Name, node.Hostname, "failed").
|
||||
Inc()
|
||||
|
||||
return
|
||||
@@ -336,9 +336,9 @@ func (h *Headscale) handlePoll(
|
||||
|
||||
// See comment in keepAliveTicker
|
||||
go func() {
|
||||
err = h.db.UpdateLastSeen(machine)
|
||||
err = h.db.UpdateLastSeen(node)
|
||||
if err != nil {
|
||||
logErr(err, "Cannot update machine LastSeen")
|
||||
logErr(err, "Cannot update node LastSeen")
|
||||
|
||||
return
|
||||
}
|
||||
@@ -350,17 +350,17 @@ func (h *Headscale) handlePoll(
|
||||
Bool("readOnly", mapRequest.ReadOnly).
|
||||
Bool("omitPeers", mapRequest.OmitPeers).
|
||||
Bool("stream", mapRequest.Stream).
|
||||
Str("node_key", machine.NodeKey).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("node_key", node.NodeKey).
|
||||
Str("node", node.Hostname).
|
||||
TimeDiff("timeSpent", time.Now(), now).
|
||||
Msg("update sent")
|
||||
case <-ctx.Done():
|
||||
logInfo("The client has closed the connection")
|
||||
|
||||
go func() {
|
||||
err = h.db.UpdateLastSeen(machine)
|
||||
err = h.db.UpdateLastSeen(node)
|
||||
if err != nil {
|
||||
logErr(err, "Cannot update machine LastSeen")
|
||||
logErr(err, "Cannot update node LastSeen")
|
||||
|
||||
return
|
||||
}
|
||||
@@ -377,10 +377,10 @@ func (h *Headscale) handlePoll(
|
||||
}
|
||||
}
|
||||
|
||||
func closeChanWithLog[C chan []byte | chan struct{} | chan types.StateUpdate](channel C, machine, name string) {
|
||||
func closeChanWithLog[C chan []byte | chan struct{} | chan types.StateUpdate](channel C, node, name string) {
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMap").
|
||||
Str("machine", machine).
|
||||
Str("node", node).
|
||||
Str("channel", "Done").
|
||||
Msg(fmt.Sprintf("Closing %s channel", name))
|
||||
|
||||
@@ -392,17 +392,17 @@ func closeChanWithLog[C chan []byte | chan struct{} | chan types.StateUpdate](ch
|
||||
// is not.
|
||||
func (h *Headscale) handleReadOnly(
|
||||
writer http.ResponseWriter,
|
||||
machine *types.Machine,
|
||||
node *types.Node,
|
||||
mapRequest tailcfg.MapRequest,
|
||||
isNoise bool,
|
||||
) {
|
||||
logInfo, logErr := logPollFunc(mapRequest, machine, isNoise)
|
||||
logInfo, logErr := logPollFunc(mapRequest, node, isNoise)
|
||||
|
||||
mapp := mapper.NewMapper(
|
||||
machine,
|
||||
node,
|
||||
// TODO(kradalby): It might not be acceptable to send
|
||||
// an empty peer list here.
|
||||
types.Machines{},
|
||||
types.Nodes{},
|
||||
h.privateKey2019,
|
||||
isNoise,
|
||||
h.DERPMap,
|
||||
@@ -413,7 +413,7 @@ func (h *Headscale) handleReadOnly(
|
||||
)
|
||||
logInfo("Client is starting up. Probably interested in a DERP map")
|
||||
|
||||
mapResp, err := mapp.FullMapResponse(mapRequest, machine, h.ACLPolicy)
|
||||
mapResp, err := mapp.FullMapResponse(mapRequest, node, h.ACLPolicy)
|
||||
if err != nil {
|
||||
logErr(err, "Failed to create MapResponse")
|
||||
http.Error(writer, "", http.StatusInternalServerError)
|
||||
@@ -435,17 +435,17 @@ func (h *Headscale) handleReadOnly(
|
||||
|
||||
func (h *Headscale) handleLiteRequest(
|
||||
writer http.ResponseWriter,
|
||||
machine *types.Machine,
|
||||
node *types.Node,
|
||||
mapRequest tailcfg.MapRequest,
|
||||
isNoise bool,
|
||||
) {
|
||||
logInfo, logErr := logPollFunc(mapRequest, machine, isNoise)
|
||||
logInfo, logErr := logPollFunc(mapRequest, node, isNoise)
|
||||
|
||||
mapp := mapper.NewMapper(
|
||||
machine,
|
||||
node,
|
||||
// TODO(kradalby): It might not be acceptable to send
|
||||
// an empty peer list here.
|
||||
types.Machines{},
|
||||
types.Nodes{},
|
||||
h.privateKey2019,
|
||||
isNoise,
|
||||
h.DERPMap,
|
||||
@@ -457,7 +457,7 @@ func (h *Headscale) handleLiteRequest(
|
||||
|
||||
logInfo("Client asked for a lite update, responding without peers")
|
||||
|
||||
mapResp, err := mapp.LiteMapResponse(mapRequest, machine, h.ACLPolicy)
|
||||
mapResp, err := mapp.LiteMapResponse(mapRequest, node, h.ACLPolicy)
|
||||
if err != nil {
|
||||
logErr(err, "Failed to create MapResponse")
|
||||
http.Error(writer, "", http.StatusInternalServerError)
|
||||
|
@@ -68,12 +68,12 @@ func (h *Headscale) PollNetMapHandler(
|
||||
return
|
||||
}
|
||||
|
||||
machine, err := h.db.GetMachineByMachineKey(machineKey)
|
||||
node, err := h.db.GetNodeByMachineKey(machineKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Warn().
|
||||
Str("handler", "PollNetMap").
|
||||
Msgf("Ignoring request, cannot find machine with key %s", machineKey.String())
|
||||
Msgf("Ignoring request, cannot find node with key %s", machineKey.String())
|
||||
|
||||
http.Error(writer, "", http.StatusUnauthorized)
|
||||
|
||||
@@ -81,7 +81,7 @@ func (h *Headscale) PollNetMapHandler(
|
||||
}
|
||||
log.Error().
|
||||
Str("handler", "PollNetMap").
|
||||
Msgf("Failed to fetch machine from the database with Machine key: %s", machineKey.String())
|
||||
Msgf("Failed to fetch node from the database with Machine key: %s", machineKey.String())
|
||||
http.Error(writer, "", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
@@ -90,8 +90,8 @@ func (h *Headscale) PollNetMapHandler(
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMap").
|
||||
Str("id", machineKeyStr).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("A machine is sending a MapRequest via legacy protocol")
|
||||
Str("node", node.Hostname).
|
||||
Msg("A node is sending a MapRequest via legacy protocol")
|
||||
|
||||
h.handlePoll(writer, req.Context(), machine, mapRequest, false)
|
||||
h.handlePoll(writer, req.Context(), node, mapRequest, false)
|
||||
}
|
||||
|
@@ -49,7 +49,7 @@ func (ns *noiseServer) NoisePollNetMapHandler(
|
||||
|
||||
ns.nodeKey = mapRequest.NodeKey
|
||||
|
||||
machine, err := ns.headscale.db.GetMachineByAnyKey(
|
||||
node, err := ns.headscale.db.GetNodeByAnyKey(
|
||||
ns.conn.Peer(),
|
||||
mapRequest.NodeKey,
|
||||
key.NodePublic{},
|
||||
@@ -58,22 +58,22 @@ func (ns *noiseServer) NoisePollNetMapHandler(
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Warn().
|
||||
Str("handler", "NoisePollNetMap").
|
||||
Msgf("Ignoring request, cannot find machine with key %s", mapRequest.NodeKey.String())
|
||||
Msgf("Ignoring request, cannot find node with key %s", mapRequest.NodeKey.String())
|
||||
http.Error(writer, "Internal error", http.StatusNotFound)
|
||||
|
||||
return
|
||||
}
|
||||
log.Error().
|
||||
Str("handler", "NoisePollNetMap").
|
||||
Msgf("Failed to fetch machine from the database with node key: %s", mapRequest.NodeKey.String())
|
||||
Msgf("Failed to fetch node from the database with node key: %s", mapRequest.NodeKey.String())
|
||||
http.Error(writer, "Internal error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
log.Debug().
|
||||
Str("handler", "NoisePollNetMap").
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("A machine sending a MapRequest with Noise protocol")
|
||||
Str("node", node.Hostname).
|
||||
Msg("A node sending a MapRequest with Noise protocol")
|
||||
|
||||
ns.headscale.handlePoll(writer, req.Context(), machine, mapRequest, true)
|
||||
ns.headscale.handlePoll(writer, req.Context(), node, mapRequest, true)
|
||||
}
|
||||
|
@@ -28,7 +28,7 @@ func (hi *HostInfo) Scan(destination interface{}) error {
|
||||
return json.Unmarshal([]byte(value), hi)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("%w: unexpected data type %T", ErrMachineAddressesInvalid, destination)
|
||||
return fmt.Errorf("%w: unexpected data type %T", ErrNodeAddressesInvalid, destination)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ func (i *IPPrefixes) Scan(destination interface{}) error {
|
||||
return json.Unmarshal([]byte(value), i)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("%w: unexpected data type %T", ErrMachineAddressesInvalid, destination)
|
||||
return fmt.Errorf("%w: unexpected data type %T", ErrNodeAddressesInvalid, destination)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func (i *StringList) Scan(destination interface{}) error {
|
||||
return json.Unmarshal([]byte(value), i)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("%w: unexpected data type %T", ErrMachineAddressesInvalid, destination)
|
||||
return fmt.Errorf("%w: unexpected data type %T", ErrNodeAddressesInvalid, destination)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,8 +123,8 @@ type StateUpdate struct {
|
||||
Type StateUpdateType
|
||||
|
||||
// Changed must be set when Type is StatePeerChanged and
|
||||
// contain the Machine IDs of machines that has changed.
|
||||
Changed Machines
|
||||
// contain the Node IDs of nodes that have changed.
|
||||
Changed Nodes
|
||||
|
||||
// Removed must be set when Type is StatePeerRemoved and
|
||||
// contain a list of the nodes that has been removed from
|
||||
|
@@ -1,346 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol/policy/matcher"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"go4.org/netipx"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMachineAddressesInvalid = errors.New("failed to parse machine addresses")
|
||||
ErrHostnameTooLong = errors.New("hostname too long")
|
||||
)
|
||||
|
||||
// Machine is a Headscale client.
|
||||
type Machine struct {
|
||||
ID uint64 `gorm:"primary_key"`
|
||||
MachineKey string `gorm:"type:varchar(64);unique_index"`
|
||||
NodeKey string
|
||||
DiscoKey string
|
||||
IPAddresses MachineAddresses
|
||||
|
||||
// Hostname represents the name given by the Tailscale
|
||||
// client during registration
|
||||
Hostname string
|
||||
|
||||
// Givenname represents either:
|
||||
// a DNS normalized version of Hostname
|
||||
// a valid name set by the User
|
||||
//
|
||||
// GivenName is the name used in all DNS related
|
||||
// parts of headscale.
|
||||
GivenName string `gorm:"type:varchar(63);unique_index"`
|
||||
UserID uint
|
||||
User User `gorm:"foreignKey:UserID"`
|
||||
|
||||
RegisterMethod string
|
||||
|
||||
ForcedTags StringList
|
||||
|
||||
// TODO(kradalby): This seems like irrelevant information?
|
||||
AuthKeyID uint
|
||||
AuthKey *PreAuthKey
|
||||
|
||||
LastSeen *time.Time
|
||||
Expiry *time.Time
|
||||
|
||||
HostInfo HostInfo
|
||||
Endpoints StringList
|
||||
|
||||
Routes []Route
|
||||
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
}
|
||||
|
||||
type (
|
||||
Machines []*Machine
|
||||
)
|
||||
|
||||
type MachineAddresses []netip.Addr
|
||||
|
||||
func (ma MachineAddresses) Sort() {
|
||||
sort.Slice(ma, func(index1, index2 int) bool {
|
||||
if ma[index1].Is4() && ma[index2].Is6() {
|
||||
return true
|
||||
}
|
||||
if ma[index1].Is6() && ma[index2].Is4() {
|
||||
return false
|
||||
}
|
||||
|
||||
return ma[index1].Compare(ma[index2]) < 0
|
||||
})
|
||||
}
|
||||
|
||||
func (ma MachineAddresses) StringSlice() []string {
|
||||
ma.Sort()
|
||||
strSlice := make([]string, 0, len(ma))
|
||||
for _, addr := range ma {
|
||||
strSlice = append(strSlice, addr.String())
|
||||
}
|
||||
|
||||
return strSlice
|
||||
}
|
||||
|
||||
func (ma MachineAddresses) Prefixes() []netip.Prefix {
|
||||
addrs := []netip.Prefix{}
|
||||
for _, machineAddress := range ma {
|
||||
ip := netip.PrefixFrom(machineAddress, machineAddress.BitLen())
|
||||
addrs = append(addrs, ip)
|
||||
}
|
||||
|
||||
return addrs
|
||||
}
|
||||
|
||||
func (ma MachineAddresses) InIPSet(set *netipx.IPSet) bool {
|
||||
for _, machineAddr := range ma {
|
||||
if set.Contains(machineAddr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// AppendToIPSet adds the individual ips in MachineAddresses to a
|
||||
// given netipx.IPSetBuilder.
|
||||
func (ma MachineAddresses) AppendToIPSet(build *netipx.IPSetBuilder) {
|
||||
for _, ip := range ma {
|
||||
build.Add(ip)
|
||||
}
|
||||
}
|
||||
|
||||
func (ma *MachineAddresses) Scan(destination interface{}) error {
|
||||
switch value := destination.(type) {
|
||||
case string:
|
||||
addresses := strings.Split(value, ",")
|
||||
*ma = (*ma)[:0]
|
||||
for _, addr := range addresses {
|
||||
if len(addr) < 1 {
|
||||
continue
|
||||
}
|
||||
parsed, err := netip.ParseAddr(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*ma = append(*ma, parsed)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("%w: unexpected data type %T", ErrMachineAddressesInvalid, destination)
|
||||
}
|
||||
}
|
||||
|
||||
// Value return json value, implement driver.Valuer interface.
|
||||
func (ma MachineAddresses) Value() (driver.Value, error) {
|
||||
addresses := strings.Join(ma.StringSlice(), ",")
|
||||
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
// IsExpired returns whether the machine registration has expired.
|
||||
func (machine Machine) IsExpired() bool {
|
||||
// If Expiry is not set, the client has not indicated that
|
||||
// it wants an expiry time, it is therefor considered
|
||||
// to mean "not expired"
|
||||
if machine.Expiry == nil || machine.Expiry.IsZero() {
|
||||
return false
|
||||
}
|
||||
|
||||
return time.Now().UTC().After(*machine.Expiry)
|
||||
}
|
||||
|
||||
// IsOnline returns if the machine 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.
|
||||
func (machine *Machine) IsOnline() bool {
|
||||
if machine.LastSeen == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if machine.IsExpired() {
|
||||
return false
|
||||
}
|
||||
|
||||
return machine.LastSeen.After(time.Now().Add(-KeepAliveInterval))
|
||||
}
|
||||
|
||||
// IsEphemeral returns if the machine is registered as an Ephemeral node.
|
||||
// https://tailscale.com/kb/1111/ephemeral-nodes/
|
||||
func (machine *Machine) IsEphemeral() bool {
|
||||
return machine.AuthKey != nil && machine.AuthKey.Ephemeral
|
||||
}
|
||||
|
||||
func (machine *Machine) CanAccess(filter []tailcfg.FilterRule, machine2 *Machine) bool {
|
||||
for _, rule := range filter {
|
||||
// TODO(kradalby): Cache or pregen this
|
||||
matcher := matcher.MatchFromFilterRule(rule)
|
||||
|
||||
if !matcher.SrcsContainsIPs([]netip.Addr(machine.IPAddresses)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if matcher.DestsContainsIP([]netip.Addr(machine2.IPAddresses)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (machines Machines) FilterByIP(ip netip.Addr) Machines {
|
||||
found := make(Machines, 0)
|
||||
|
||||
for _, machine := range machines {
|
||||
for _, mIP := range machine.IPAddresses {
|
||||
if ip == mIP {
|
||||
found = append(found, machine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
func (machine *Machine) Proto() *v1.Machine {
|
||||
machineProto := &v1.Machine{
|
||||
Id: machine.ID,
|
||||
MachineKey: machine.MachineKey,
|
||||
|
||||
NodeKey: machine.NodeKey,
|
||||
DiscoKey: machine.DiscoKey,
|
||||
IpAddresses: machine.IPAddresses.StringSlice(),
|
||||
Name: machine.Hostname,
|
||||
GivenName: machine.GivenName,
|
||||
User: machine.User.Proto(),
|
||||
ForcedTags: machine.ForcedTags,
|
||||
Online: machine.IsOnline(),
|
||||
|
||||
// TODO(kradalby): Implement register method enum converter
|
||||
// RegisterMethod: ,
|
||||
|
||||
CreatedAt: timestamppb.New(machine.CreatedAt),
|
||||
}
|
||||
|
||||
if machine.AuthKey != nil {
|
||||
machineProto.PreAuthKey = machine.AuthKey.Proto()
|
||||
}
|
||||
|
||||
if machine.LastSeen != nil {
|
||||
machineProto.LastSeen = timestamppb.New(*machine.LastSeen)
|
||||
}
|
||||
|
||||
if machine.Expiry != nil {
|
||||
machineProto.Expiry = timestamppb.New(*machine.Expiry)
|
||||
}
|
||||
|
||||
return machineProto
|
||||
}
|
||||
|
||||
// GetHostInfo returns a Hostinfo struct for the machine.
|
||||
func (machine *Machine) GetHostInfo() tailcfg.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 {
|
||||
return machine.Hostname
|
||||
}
|
||||
|
||||
func (machines Machines) String() string {
|
||||
temp := make([]string, len(machines))
|
||||
|
||||
for index, machine := range machines {
|
||||
temp[index] = machine.Hostname
|
||||
}
|
||||
|
||||
return fmt.Sprintf("[ %s ](%d)", strings.Join(temp, ", "), len(temp))
|
||||
}
|
||||
|
||||
func (machines Machines) IDMap() map[uint64]*Machine {
|
||||
ret := map[uint64]*Machine{}
|
||||
|
||||
for _, machine := range machines {
|
||||
ret[machine.ID] = machine
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
346
hscontrol/types/node.go
Normal file
346
hscontrol/types/node.go
Normal file
@@ -0,0 +1,346 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol/policy/matcher"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"go4.org/netipx"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNodeAddressesInvalid = errors.New("failed to parse node addresses")
|
||||
ErrHostnameTooLong = errors.New("hostname too long")
|
||||
)
|
||||
|
||||
// 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
|
||||
IPAddresses NodeAddresses
|
||||
|
||||
// Hostname represents the name given by the Tailscale
|
||||
// client during registration
|
||||
Hostname string
|
||||
|
||||
// Givenname represents either:
|
||||
// a DNS normalized version of Hostname
|
||||
// a valid name set by the User
|
||||
//
|
||||
// GivenName is the name used in all DNS related
|
||||
// parts of headscale.
|
||||
GivenName string `gorm:"type:varchar(63);unique_index"`
|
||||
UserID uint
|
||||
User User `gorm:"foreignKey:UserID"`
|
||||
|
||||
RegisterMethod string
|
||||
|
||||
ForcedTags StringList
|
||||
|
||||
// TODO(kradalby): This seems like irrelevant information?
|
||||
AuthKeyID uint
|
||||
AuthKey *PreAuthKey
|
||||
|
||||
LastSeen *time.Time
|
||||
Expiry *time.Time
|
||||
|
||||
HostInfo HostInfo
|
||||
Endpoints StringList
|
||||
|
||||
Routes []Route
|
||||
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
}
|
||||
|
||||
type (
|
||||
Nodes []*Node
|
||||
)
|
||||
|
||||
type NodeAddresses []netip.Addr
|
||||
|
||||
func (na NodeAddresses) Sort() {
|
||||
sort.Slice(na, func(index1, index2 int) bool {
|
||||
if na[index1].Is4() && na[index2].Is6() {
|
||||
return true
|
||||
}
|
||||
if na[index1].Is6() && na[index2].Is4() {
|
||||
return false
|
||||
}
|
||||
|
||||
return na[index1].Compare(na[index2]) < 0
|
||||
})
|
||||
}
|
||||
|
||||
func (na NodeAddresses) StringSlice() []string {
|
||||
na.Sort()
|
||||
strSlice := make([]string, 0, len(na))
|
||||
for _, addr := range na {
|
||||
strSlice = append(strSlice, addr.String())
|
||||
}
|
||||
|
||||
return strSlice
|
||||
}
|
||||
|
||||
func (na NodeAddresses) Prefixes() []netip.Prefix {
|
||||
addrs := []netip.Prefix{}
|
||||
for _, nodeAddress := range na {
|
||||
ip := netip.PrefixFrom(nodeAddress, nodeAddress.BitLen())
|
||||
addrs = append(addrs, ip)
|
||||
}
|
||||
|
||||
return addrs
|
||||
}
|
||||
|
||||
func (na NodeAddresses) InIPSet(set *netipx.IPSet) bool {
|
||||
for _, nodeAddr := range na {
|
||||
if set.Contains(nodeAddr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// AppendToIPSet adds the individual ips in NodeAddresses to a
|
||||
// given netipx.IPSetBuilder.
|
||||
func (na NodeAddresses) AppendToIPSet(build *netipx.IPSetBuilder) {
|
||||
for _, ip := range na {
|
||||
build.Add(ip)
|
||||
}
|
||||
}
|
||||
|
||||
func (na *NodeAddresses) Scan(destination interface{}) error {
|
||||
switch value := destination.(type) {
|
||||
case string:
|
||||
addresses := strings.Split(value, ",")
|
||||
*na = (*na)[:0]
|
||||
for _, addr := range addresses {
|
||||
if len(addr) < 1 {
|
||||
continue
|
||||
}
|
||||
parsed, err := netip.ParseAddr(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*na = append(*na, parsed)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("%w: unexpected data type %T", ErrNodeAddressesInvalid, destination)
|
||||
}
|
||||
}
|
||||
|
||||
// Value return json value, implement driver.Valuer interface.
|
||||
func (na NodeAddresses) Value() (driver.Value, error) {
|
||||
addresses := strings.Join(na.StringSlice(), ",")
|
||||
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
// IsExpired returns whether the node registration has expired.
|
||||
func (node Node) IsExpired() bool {
|
||||
// If Expiry is not set, the client has not indicated that
|
||||
// it wants an expiry time, it is therefor considered
|
||||
// to mean "not expired"
|
||||
if node.Expiry == nil || node.Expiry.IsZero() {
|
||||
return false
|
||||
}
|
||||
|
||||
return time.Now().UTC().After(*node.Expiry)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (node *Node) IsOnline() bool {
|
||||
if node.LastSeen == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if node.IsExpired() {
|
||||
return false
|
||||
}
|
||||
|
||||
return node.LastSeen.After(time.Now().Add(-KeepAliveInterval))
|
||||
}
|
||||
|
||||
// IsEphemeral returns if the node is registered as an Ephemeral node.
|
||||
// https://tailscale.com/kb/1111/ephemeral-nodes/
|
||||
func (node *Node) IsEphemeral() bool {
|
||||
return node.AuthKey != nil && node.AuthKey.Ephemeral
|
||||
}
|
||||
|
||||
func (node *Node) CanAccess(filter []tailcfg.FilterRule, node2 *Node) bool {
|
||||
for _, rule := range filter {
|
||||
// TODO(kradalby): Cache or pregen this
|
||||
matcher := matcher.MatchFromFilterRule(rule)
|
||||
|
||||
if !matcher.SrcsContainsIPs([]netip.Addr(node.IPAddresses)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if matcher.DestsContainsIP([]netip.Addr(node2.IPAddresses)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (nodes Nodes) FilterByIP(ip netip.Addr) Nodes {
|
||||
found := make(Nodes, 0)
|
||||
|
||||
for _, node := range nodes {
|
||||
for _, mIP := range node.IPAddresses {
|
||||
if ip == mIP {
|
||||
found = append(found, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
func (node *Node) Proto() *v1.Node {
|
||||
nodeProto := &v1.Node{
|
||||
Id: node.ID,
|
||||
MachineKey: node.MachineKey,
|
||||
|
||||
NodeKey: node.NodeKey,
|
||||
DiscoKey: node.DiscoKey,
|
||||
IpAddresses: node.IPAddresses.StringSlice(),
|
||||
Name: node.Hostname,
|
||||
GivenName: node.GivenName,
|
||||
User: node.User.Proto(),
|
||||
ForcedTags: node.ForcedTags,
|
||||
Online: node.IsOnline(),
|
||||
|
||||
// TODO(kradalby): Implement register method enum converter
|
||||
// RegisterMethod: ,
|
||||
|
||||
CreatedAt: timestamppb.New(node.CreatedAt),
|
||||
}
|
||||
|
||||
if node.AuthKey != nil {
|
||||
nodeProto.PreAuthKey = node.AuthKey.Proto()
|
||||
}
|
||||
|
||||
if node.LastSeen != nil {
|
||||
nodeProto.LastSeen = timestamppb.New(*node.LastSeen)
|
||||
}
|
||||
|
||||
if node.Expiry != nil {
|
||||
nodeProto.Expiry = timestamppb.New(*node.Expiry)
|
||||
}
|
||||
|
||||
return nodeProto
|
||||
}
|
||||
|
||||
// GetHostInfo returns a Hostinfo struct for the node.
|
||||
func (node *Node) GetHostInfo() tailcfg.Hostinfo {
|
||||
return tailcfg.Hostinfo(node.HostInfo)
|
||||
}
|
||||
|
||||
func (node *Node) GetFQDN(dnsConfig *tailcfg.DNSConfig, baseDomain string) (string, error) {
|
||||
var hostname string
|
||||
if dnsConfig != nil && dnsConfig.Proxied { // MagicDNS
|
||||
hostname = fmt.Sprintf(
|
||||
"%s.%s.%s",
|
||||
node.GivenName,
|
||||
node.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 = node.GivenName
|
||||
}
|
||||
|
||||
return hostname, nil
|
||||
}
|
||||
|
||||
func (node *Node) MachinePublicKey() (key.MachinePublic, error) {
|
||||
var machineKey key.MachinePublic
|
||||
|
||||
if node.MachineKey != "" {
|
||||
err := machineKey.UnmarshalText(
|
||||
[]byte(util.MachinePublicKeyEnsurePrefix(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(util.DiscoPublicKeyEnsurePrefix(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(util.NodePublicKeyEnsurePrefix(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
|
||||
}
|
||||
|
||||
func (nodes Nodes) String() string {
|
||||
temp := make([]string, len(nodes))
|
||||
|
||||
for index, node := range nodes {
|
||||
temp[index] = node.Hostname
|
||||
}
|
||||
|
||||
return fmt.Sprintf("[ %s ](%d)", strings.Join(temp, ", "), len(temp))
|
||||
}
|
||||
|
||||
func (nodes Nodes) IDMap() map[uint64]*Node {
|
||||
ret := map[uint64]*Node{}
|
||||
|
||||
for _, node := range nodes {
|
||||
ret[node.ID] = node
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
@@ -7,20 +7,20 @@ import (
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
func Test_MachineCanAccess(t *testing.T) {
|
||||
func Test_NodeCanAccess(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
machine1 Machine
|
||||
machine2 Machine
|
||||
rules []tailcfg.FilterRule
|
||||
want bool
|
||||
name string
|
||||
node1 Node
|
||||
node2 Node
|
||||
rules []tailcfg.FilterRule
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "no-rules",
|
||||
machine1: Machine{
|
||||
node1: Node{
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("10.0.0.1")},
|
||||
},
|
||||
machine2: Machine{
|
||||
node2: Node{
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("10.0.0.2")},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{},
|
||||
@@ -28,10 +28,10 @@ func Test_MachineCanAccess(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "wildcard",
|
||||
machine1: Machine{
|
||||
node1: Node{
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("10.0.0.1")},
|
||||
},
|
||||
machine2: Machine{
|
||||
node2: Node{
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("10.0.0.2")},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{
|
||||
@@ -49,10 +49,10 @@ func Test_MachineCanAccess(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "other-cant-access-src",
|
||||
machine1: Machine{
|
||||
node1: Node{
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")},
|
||||
},
|
||||
machine2: Machine{
|
||||
node2: Node{
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{
|
||||
@@ -67,10 +67,10 @@ func Test_MachineCanAccess(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "dest-cant-access-src",
|
||||
machine1: Machine{
|
||||
node1: Node{
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")},
|
||||
},
|
||||
machine2: Machine{
|
||||
node2: Node{
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{
|
||||
@@ -85,10 +85,10 @@ func Test_MachineCanAccess(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "src-can-access-dest",
|
||||
machine1: Machine{
|
||||
node1: Node{
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")},
|
||||
},
|
||||
machine2: Machine{
|
||||
node2: Node{
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{
|
||||
@@ -105,7 +105,7 @@ func Test_MachineCanAccess(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.machine1.CanAccess(tt.rules, &tt.machine2)
|
||||
got := tt.node1.CanAccess(tt.rules, &tt.node2)
|
||||
|
||||
if got != tt.want {
|
||||
t.Errorf("canAccess() failed: want (%t), got (%t)", tt.want, got)
|
||||
@@ -114,8 +114,8 @@ func Test_MachineCanAccess(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMachineAddressesOrder(t *testing.T) {
|
||||
machineAddresses := MachineAddresses{
|
||||
func TestNodeAddressesOrder(t *testing.T) {
|
||||
machineAddresses := NodeAddresses{
|
||||
netip.MustParseAddr("2001:db8::2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
netip.MustParseAddr("2001:db8::1"),
|
@@ -17,9 +17,9 @@ var (
|
||||
type Route struct {
|
||||
gorm.Model
|
||||
|
||||
MachineID uint64
|
||||
Machine Machine
|
||||
Prefix IPPrefix
|
||||
NodeID uint64
|
||||
Node Node
|
||||
Prefix IPPrefix
|
||||
|
||||
Advertised bool
|
||||
Enabled bool
|
||||
@@ -29,7 +29,7 @@ type Route struct {
|
||||
type Routes []Route
|
||||
|
||||
func (r *Route) String() string {
|
||||
return fmt.Sprintf("%s:%s", r.Machine, netip.Prefix(r.Prefix).String())
|
||||
return fmt.Sprintf("%s:%s", r.Node, netip.Prefix(r.Prefix).String())
|
||||
}
|
||||
|
||||
func (r *Route) IsExitRoute() bool {
|
||||
@@ -51,7 +51,7 @@ func (rs Routes) Proto() []*v1.Route {
|
||||
for _, route := range rs {
|
||||
protoRoute := v1.Route{
|
||||
Id: uint64(route.ID),
|
||||
Machine: route.Machine.Proto(),
|
||||
Node: route.Node.Proto(),
|
||||
Prefix: netip.Prefix(route.Prefix).String(),
|
||||
Advertised: route.Advertised,
|
||||
Enabled: route.Enabled,
|
||||
|
Reference in New Issue
Block a user