Files
netmaker/mq/handlers.go
Vishal Dalwadi 132aee0bb3 NM-341-v6: AMB Multi-tenancy Management (#4101)
* wip(go): return auth token with user on create org owner;

* wip(go): prevent use of default tenant slug ;

* wip(go): upsert settings for tenant in ctx;

* wip(go): use passed ctx when updating user; remove password validation from ValidateUser (already validated in UpdateUser);

* wip(go): qualify all tenant id usages with table name;

* wip(go): use passed ctx

* wip(go): return old enrollment key type on create;

* wip(go): prevent null networks return;

* wip(go): wire ctx in dns/nameserver functions; wire ctx in host registration functions;

* wip(go): wire ctx in node functions;

* wip(go): wire ctx in node functions;

* wip(go): wire ctx in extclient functions;

* wip(go): remove not needed field;

* wip(go): use passed ctx;

* wip(go): use unique index instead of primary key;

* wip(go): wire ctx in extclient apis;

* wip(go): wire ctx in extclient apis;

* wip(go): remove org id check from global scoped get tenant api;

* wip(go): add scope slug to discover response;

* wip(go): wire ctx in get users;

* wip(go): set tenant id in posture check violations;

* wip(go): upsert membership instead of user;

* wip(go): pass correct len;

* wip(go): optimize (single loop);

* wip(go): unify todos;

* wip(go): add todo;

* wip(go): wire context for network get;

* wip(go): wire context;

* wip(go): wire context;

* wip(go): wire ctx when getting tenant superadmin;

* wip(go): add network creator to network admin group;

* wip(go): check for user membership on sync;

* wip(go): add methods with strict membership check;

* wip(go): remove unwanted feature flags;

* wip(go): report per-tenant usage during license validation;

* wip(go): move email setting from tenant to org;

* wip(go): pass org scope to email init;

* wip(go): remove old tenant email settings usages; use org settings to get email settings

* wip(go): use get email settings from org settings;

* wip(go): remove email settings migration;

* wip(go): migrate email settings from server to org settings;

* wip(go): assign global network admin group membership only for super-admin and admins;

* wip(go): assign global network admin group membership only for super-admin and admins;

* wip(go): add membership alias to filter;

* wip(go): skip clearing local license validation response cache;

* wip(go): extend license validation response for multi-tenancy;

* wip(go): add functions to get sole tenant and org;

* wip(go): use tenant-specific feature flags;

* wip(go): add tenant status type;

* wip(go): remove old hook comment;

* wip(go): expect ctx during license validation;

* wip(go): always create default org and tenant for ce;

* wip(go): add tenant-specific check for license validation error;

* wip(go): cache license validation response in memory;

* wip(go): add todo to handle multiple-tenant for integration pull;

* wip(go): pass ctx during license validation;

* wip(go): add todo for making changes later; run hook separately for each tenant;

* wip(go): return specific errors when license validation response not in cache;

* wip(go): use retryable.httpclient for calling license validation api;

* wip(go): process license validation;

* wip(go): license validation function;

* wip(go): migration on license validation;

* wip(go): org settings not expected;

* wip(go): remove expiry from license validation response;

* wip(go): add limits and limits enforcement to license validation response;

* wip(go): remove expiry usages;

* wip(go): add arg to clear cache;

* wip(go): add arg to clear cache;

* wip(go): skip license validation if cached resp;

* wip(go): allow org_id and tenant_id query params;

* wip(go): upsert tenant membership on sync;

* wip(go): pass ctx to delete network roles;

* wip(go): create tenant membership on org owner creation on ce;

* wip(go): add tenant orchestrator;

* wip(go): use tenant orchestrator;

* wip(go): add default settings for tenant;

* wip(go): make org owner superadmin on all tenants;

* wip(go): simplify SyncOrgAndTenants;

* wip(go): retry license validation only 3 times

* wip(go): store invalid err;

* wip(go): add function to check if limits are to be enforced on a tenant;

* wip(go): pass tenant scope if possible when checking for license validation error;

* wip(go): remove merge errors;

* wip(go): add functions for limit checks;

* wip(go): wire host limit check;

* wip(go): wire ingress limit check;

* wip(go): wire egress limit check;

* wip(go): wire client limit check;

* wip(go): wire network limit check;

* wip(go): wire user limit check;

* wip(go): use correct errors;

* wip(go): skip telemetry for msps;

* wip(go): disable telemetry on msp tenants;

* wip(go): err check;

* wip(go): uncomment log event;

* wip(go): get sso state to get scope for non-headless oauth login;

* wip(go): inline delete pending user;

* wip(go): allow both org and tenant scope for pending users table;

* wip(go): add migration for pending user table during set tenant id and rekey org/tenant id;

* wip(go): update queries to use new scope fields in pending users table;

* wip(go): allow both org and tenant scope for user invites table;

* wip(go): update queries to use new scope fields;

* wip(go): add migration for rekey-ing org/tenant id and setting tenant id;

* wip(go): inline delete user invite;

* wip(go): set scope when creating invite;

* wip(go): get user count with membership is ctx is tenant scoped;

* wip(go): add methods to upsert and delete org memberships;

* wip(go): add global valid tenant and org roles map;

* wip(go): add scope query in user invite get by email;

* wip(go): add support for upsert and delete of org and tenant membership both;

* wip(go): infer scope for user invite apis;

* wip(go): wire ctx in get user invite by email;

* wip(go): allow no scope headers to be passed in infer scope apis;

* wip(go): add validation and checks for org-scoped invite api handlers;

* wip(go): wire ctx to is pending user check;

* wip(go): infer scope for pending user apis;

* wip(go): set default role based on ctx scope;

* wip(go): move mfa to tenant membership table; move account status to membership tables;

* wip(go): migrate account status and mfa settings;

* wip(go): update account status on sync;

* wip(go): use update mfa to update mfa setting;

* wip(go): set new membership settings on user creation;

* wip(go): use membership where required;

* wip(go): wire ctx on key unlink refs;

* wip(go): use r.Context directly without reinitializing db;

* wip(go): wire ctx in acls;

* wip(go): add tenant scoped primary key constructor;

* wip(go): use tenant scoped primary key for acls, dns, extclients and tags;

* wip(go): wire ctx for get groups in network;

* wip(go): wire ctx for get groups;

* wip(go): scope non-uuid group ids by tenant id;

* wip(go): migrate non-uuid group ids to scoped ids;

* wip(go): don't expect scope id in invite validation and signup apis;

* wip(go): wire ctx for get matching user; allow org scope when querying by external idp id;

* wip(go): add mfa to org;

* wip(go): scope preauth token;

* wip(go): add fallback for older auth tokens;

* wip(go): wire ctx for preauth token creation;

* wip(go): migrate mfa settings;

* wip(go): resolve mfa settings for inherited auth;

* wip(go): update mfa for org;

* wip(go): infer scope for totp apis;

* wip(go): use ctx when passed;

* wip(go): infer scope for list user apis;

* wip(go): ensure set primary keys don't cause errors during fetch;

* wip(go): make primary key for integrations uuid and store provider in separate key;

* wip(go): migrate integration ids;

* wip(go): use provider instead of id everywhere;

* wip(go): remove gorm column;

* wip(go): wire ctx for igw egress creation;

* wip(go): wire ctx for igw egress update;

* wip(go): wire ctx for host registration;

* wip(go): wire ctx for host registration;

* wip(go): merge errors;

* wip(go): merge errors;

* wip(go): wire ctx; remove default scope usages;

* wip(go): use get with membership;

* wip(go): org scope license validation;

* wip(go): remove debug log;

* wip(go): remove scoping on list roles;

* wip(go): ignore invite creation if already exists;

* wip(go): add is msp func to check if deployment is msp deployment;

* wip(go): ensure username is email for msp users;

* wip(go): add email validated check on user table;

* wip(go): mark email validated when oauth user;

* wip(go): handle gorm error;

* wip(go): add tenant id to siem grpc schema;

* wip(go): read tenant from scoped ctx;

* wip(go): pass scoped ctx to siem grpc client;

* wip(go): remove unused publisher;

* wip(go): publish all tenant exporter updates on pull;

* wip(go): fix wg usage;

* wip(go): tenant scope the ctx when getting server info;

* wip(go): add tenant id to enrollment key token;

* wip(go): make host register tenant scope;

* wip(go): add default settings;

* wip(go): use correct checks for is msp tenant;

* wip(go): add email validation extension;

* wip(go): add pages for validation check;

* wip(go): use extension for validating user;

* wip(go): add invite type;

* wip(go): create only one validation invite per user email;

* wip(go): add validate email handler;

* wip(go): add email builder;

* wip(go): add user invite type migration logic;

* wip(go): only log email send failures;

* wip(go): add tenant id to flows table;

* wip(go): add func to check if connected to clickhouse;

* wip(go): migrate clickhouse;

* wip(go): add tenant id to mdm and edr state;

* wip(go): add delete methods for tables;

* wip(go): add tenant teardown method;

* wip(go): handle tenant deletion;

* wip(go): clean up flow logs on tenant deletion;

* wip(go): add update org membership role id method;

* wip(go): add transfer org ownership api;

* wip(go): move audit logs retention period to org settings;

* wip(go):get retention period from org settings;

* wip(go): remove old audit logs usage;

* wip(go): migrate audit logs retention period;

* wip(go): unmarshal decodes nums as float64;

* wip(go): run flow logs cleanup for all tenants in one go;

* wip(go): add audit logs retention period and smtp settings to tenant settings table for non-msp tenants;

* wip(go): resolve retention period and smtp settings correctly;

* wip(go): handle retention period and smtp settings updates and fetches correctly;

* wip(go): overlay retention period and smtp settings on server settings;;

* wip(go): upsert overlayed settings;

* wip(go): add azure tenant id to org settings;

* wip(go): tenant scope list roles;

* wip(go): add noop creds;

* wip(go): use first instead of find;

* wip(go): use correct roles when transfering ownership;

* wip(go): delete access tokens only if tenant scoped;

* wip(go): make delete user work for both org and tenant scope;

* wip(go): delete extclients only if tenant scoped;

* wip(go): check if caller is allowed to delete user;

* wip(go): infer scope for update user;

* wip(go): prevent org-owner elevation;

* wip(go): permissions check for update;

* wip(go): delete user access tokens only if tenant scope;

* wip(go): delete extclients only if tenant scope;

* wip(go): move upsert membership in upsert user;

* wip(go): set api host when constructing server config;

* wip(go): fix static errors;

* wip(go): fix test cases;

* wip(go): use correct get ns order;

* wip(go): use correct get pending host order;

* wip(go): use correct get access token order;

* wip(go): use correct get event order;

* wip(go): return error response on emqx user creation error;

* wip(go): store correct value in cache;

* wip(go): return better error response;

* wip(go): return better error response;

* wip(go): use cache when listing egress acls;

* wip(go): wire ctx in UserHasNetworkGroupAccess;

* wip(go): remove double scope wrapping;

* wip(go): add security check for list and get org;

* wip(go): ratelimit discover api;

* wip(go): expect sole org id in get and create org owner;

* wip(go): wrap create org owner in tx;
2026-08-05 21:25:02 +08:00

444 lines
14 KiB
Go

package mq
import (
"context"
"encoding/json"
"errors"
"net"
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/google/uuid"
"github.com/gravitl/netmaker/db"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/netclient/ncutils"
"github.com/gravitl/netmaker/schema"
"github.com/gravitl/netmaker/scope"
"github.com/gravitl/netmaker/servercfg"
"github.com/gravitl/netmaker/utils"
"golang.org/x/exp/slog"
"gorm.io/gorm"
)
// UpdateMetrics message Handler -- handles updates from client nodes for metrics
var UpdateMetrics = func(client mqtt.Client, msg mqtt.Message) {
}
var UpdateMetricsFallBack = func(ctx context.Context, nodeid string, newMetrics models.Metrics) {}
// DefaultHandler default message queue handler -- NOT USED
func DefaultHandler(client mqtt.Client, msg mqtt.Message) {
slog.Info("mqtt default handler", "topic", msg.Topic(), "message", msg.Payload())
}
// UpdateNode message Handler -- handles updates from client nodes
func UpdateNode(client mqtt.Client, msg mqtt.Message) {
id, err := GetID(msg.Topic())
if err != nil {
slog.Error("error getting node.ID ", "topic", msg.Topic(), "error", err)
return
}
currentNode, err := logic.GetNodeByID(id)
if err != nil {
slog.Error("error getting node", "id", id, "error", err)
return
}
decrypted, decryptErr := DecryptMsg(&currentNode, msg.Payload())
if decryptErr != nil {
slog.Error("failed to decrypt message for node", "id", id, "error", decryptErr)
return
}
var newNode models.Node
if err := json.Unmarshal(decrypted, &newNode); err != nil {
slog.Error("error unmarshaling payload", "error", err)
return
}
ifaceDelta := logic.IfaceDelta(&currentNode, &newNode)
newNode.SetLastCheckIn()
if err := logic.UpdateNode(&currentNode, &newNode); err != nil {
slog.Error("error saving node", "id", id, "error", err)
return
}
if ifaceDelta { // reduce number of unneeded updates, by only sending on iface changes
if !newNode.Connected {
host := &schema.Host{ID: newNode.HostID}
if err := host.Get(db.WithContext(context.TODO())); err != nil {
slog.Error("failed to get host for the node", "nodeid", newNode.ID.String(), "error", err)
return
}
ctx := scope.WithContext(db.WithContext(context.Background()), scope.TenantScope, host.TenantID)
err = PublishDeletedNodePeerUpdate(ctx, nil, &newNode)
allNodes, err := logic.GetAllNodes(ctx)
if err == nil {
PublishSingleHostPeerUpdate(ctx, host, allNodes, nil, nil, nil, false, nil)
}
} else {
host := &schema.Host{ID: currentNode.HostID}
if err = host.Get(db.WithContext(context.TODO())); err == nil {
ctx := scope.WithContext(db.WithContext(context.Background()), scope.TenantScope, host.TenantID)
err = PublishPeerUpdate(ctx, false)
}
}
if err != nil {
slog.Warn("error updating peers when node informed the server of an interface change", "nodeid", currentNode.ID, "error", err)
}
}
slog.Info("updated node", "id", id, "newnodeid", newNode.ID)
}
// UpdateHost message Handler -- handles host updates from clients
func UpdateHost(client mqtt.Client, msg mqtt.Message) {
id, err := GetID(msg.Topic())
if err != nil {
slog.Error("error getting host.ID sent on ", "topic", msg.Topic(), "error", err)
return
}
currentHost := &schema.Host{ID: uuid.MustParse(id)}
if err := currentHost.Get(db.WithContext(context.TODO())); err != nil {
slog.Error("error getting host", "id", id, "error", err)
return
}
decrypted, decryptErr := decryptMsgWithHost(currentHost, msg.Payload())
if decryptErr != nil {
slog.Error("failed to decrypt message for host", "id", id, "name", currentHost.Name, "error", decryptErr)
return
}
var hostUpdate models.HostUpdate
if err := json.Unmarshal(decrypted, &hostUpdate); err != nil {
slog.Error("error unmarshaling payload", "error", err)
return
}
slog.Info("recieved host update", "name", hostUpdate.Host.Name, "id", hostUpdate.Host.ID)
var sendPeerUpdate bool
var replacePeers bool
ctx := scope.WithContext(db.WithContext(context.Background()), scope.TenantScope, currentHost.TenantID)
switch hostUpdate.Action {
case models.CheckIn:
sendPeerUpdate = HandleHostCheckin(&hostUpdate.Host, currentHost)
case models.Acknowledgement:
nodes, err := logic.GetAllNodes(ctx)
if err != nil {
return
}
// send latest host update
HostUpdate(&models.HostUpdate{
Action: models.UpdateHost,
Host: *currentHost})
PublishSingleHostPeerUpdate(ctx, currentHost, nodes, nil, nil, nil, false, nil)
case models.UpdateHost:
if hostUpdate.Host.PublicKey != currentHost.PublicKey {
//remove old peer entry
replacePeers = true
}
var endpointChanged bool
endpointChanged, sendPeerUpdate = logic.UpdateHostFromClient(ctx, &hostUpdate.Host, currentHost)
if endpointChanged {
logic.CheckHostPorts(ctx, currentHost)
}
err := currentHost.Upsert(ctx)
if err != nil {
slog.Error("failed to update host", "id", currentHost.ID, "error", err)
return
}
case models.DeleteHost:
DeleteAndCleanupHost(currentHost)
sendPeerUpdate = true
case models.SignalHost:
SignalPeer(hostUpdate.Signal)
}
if sendPeerUpdate {
err := PublishPeerUpdate(ctx, replacePeers)
if err != nil {
slog.Error("failed to publish peer update", "error", err)
}
}
}
func DeleteAndCleanupHost(h *schema.Host) {
if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
// delete EMQX credentials for host
if err := emqx.DeleteEmqxUser(h.ID.String()); err != nil {
slog.Error("failed to remove host credentials from EMQX", "id", h.ID, "error", err)
}
}
// notify of deleted peer change
ctx := scope.WithContext(db.WithContext(context.Background()), scope.TenantScope, h.TenantID)
for _, nodeID := range h.Nodes {
node, err := logic.GetNodeByID(nodeID)
if err == nil {
PublishMqUpdatesForDeletedNode(ctx, h, node, false)
}
}
if err := logic.DisassociateAllNodesFromHost(ctx, h.ID.String()); err != nil {
slog.Error("failed to delete all nodes of host", "id", h.ID, "error", err)
return
}
if err := (&schema.Host{ID: h.ID}).Delete(db.WithContext(context.TODO())); err != nil {
slog.Error("failed to delete host", "id", h.ID, "error", err)
return
}
}
func SignalPeer(signal models.Signal) {
if signal.ToHostPubKey == "" {
msg := "insufficient data to signal peer"
logger.Log(0, msg)
return
}
node, err := logic.GetNodeByID(signal.FromNodeID)
if err != nil {
return
}
peer, err := logic.GetNodeByID(signal.ToNodeID)
if err != nil {
return
}
if node.Network != peer.Network {
return
}
signal.NetworkID = node.Network
signal.IsPro = servercfg.IsPro
peerHost := &schema.Host{ID: uuid.MustParse(signal.ToHostID)}
if err := peerHost.Get(db.WithContext(context.TODO())); err != nil {
slog.Error("failed to signal, peer host not found", "error", err)
return
}
err = HostUpdate(&models.HostUpdate{
Action: models.SignalHost,
Host: *peerHost,
Signal: signal,
})
if err != nil {
slog.Error("failed to publish signal to peer", "error", err)
}
}
// ClientPeerUpdate message handler -- handles updating peers after signal from client nodes
func ClientPeerUpdate(client mqtt.Client, msg mqtt.Message) {
id, err := GetID(msg.Topic())
if err != nil {
slog.Error("error getting node.ID sent on ", "topic", msg.Topic(), "error", err)
return
}
currentNode, err := logic.GetNodeByID(id)
if err != nil {
slog.Error("error getting node", "id", id, "error", err)
return
}
decrypted, decryptErr := DecryptMsg(&currentNode, msg.Payload())
if decryptErr != nil {
slog.Error("failed to decrypt message for node", "id", id, "error", decryptErr)
return
}
switch decrypted[0] {
case ncutils.ACK:
// do we still need this
case ncutils.DONE:
host := &schema.Host{ID: currentNode.HostID}
if err = host.Get(db.WithContext(context.TODO())); err != nil {
slog.Error("error getting host for node", "id", currentNode.ID, "error", err)
return
}
ctx := scope.WithContext(db.WithContext(context.Background()), scope.TenantScope, host.TenantID)
if err = PublishPeerUpdate(ctx, false); err != nil {
slog.Error("error publishing peer update for node", "id", currentNode.ID, "error", err)
return
}
}
slog.Info("sent peer updates after signal received from", "id", id)
}
func HandleHostCheckin(h, currentHost *schema.Host) bool {
if h == nil {
return false
}
for i := range currentHost.Nodes {
currNodeID := currentHost.Nodes[i]
node, err := logic.GetNodeByID(currNodeID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
fakeNode := models.Node{}
fakeNode.ID, _ = uuid.Parse(currNodeID)
fakeNode.Action = schema.NODE_DELETE
fakeNode.PendingDelete = true
if err := NodeUpdate(&fakeNode); err != nil {
slog.Warn("failed to inform host to remove node", "host", currentHost.Name, "hostid", currentHost.ID, "nodeid", currNodeID, "error", err)
}
}
continue
}
wasOffline := node.Status == schema.OfflineSt ||
(node.Connected && time.Since(node.LastCheckIn) > models.LastCheckInThreshold)
if err := logic.UpdateNodeCheckin(node.ID.String()); err != nil {
slog.Warn("failed to update node on checkin", "nodeid", node.ID, "error", err)
continue
}
if wasOffline {
if node.Status == schema.OfflineSt {
promoted := &schema.Node{
ID: node.ID.String(),
Status: schema.OnlineSt,
}
if err := promoted.UpdateStatus(db.WithContext(context.TODO())); err != nil {
slog.Warn("failed to promote node from offline on checkin", "nodeid", node.ID, "error", err)
}
}
go logic.TriggerCollectMetrics(currentHost.ID.String(), node.ID.String(), "checkin_recovered")
}
}
for i := range h.Interfaces {
h.Interfaces[i].AddressString = h.Interfaces[i].Address.String()
}
for i := range currentHost.Interfaces {
currentHost.Interfaces[i].AddressString = currentHost.Interfaces[i].Address.String()
}
utils.SortIfacesByName(h.Interfaces)
utils.SortIfacesByName(currentHost.Interfaces)
ifacesChanged := len(h.Interfaces) != len(currentHost.Interfaces) ||
!logic.CompareIfaceSlices(h.Interfaces, currentHost.Interfaces)
/// version or firewall in use change does not require a peerUpdate
if h.Version != currentHost.Version || h.FirewallInUse != currentHost.FirewallInUse || ifacesChanged {
currentHost.FirewallInUse = h.FirewallInUse
currentHost.Version = h.Version
currentHost.Interfaces = h.Interfaces
if err := currentHost.Upsert(db.WithContext(context.TODO())); err != nil {
slog.Error("failed to update host after check-in", "name", h.Name, "id", h.ID, "error", err)
return false
}
}
if h.Location == "" || h.CountryCode == "" {
var nodeIP net.IP
if h.EndpointIP != nil {
nodeIP = h.EndpointIP
} else if h.EndpointIPv6 != nil {
nodeIP = h.EndpointIPv6
}
if nodeIP != nil {
info, err := utils.GetGeoInfo(nodeIP)
if err == nil {
h.Location = info.Location
h.CountryCode = info.CountryCode
}
}
}
var ifaceDeltaReasons []string
if !currentHost.IsStatic {
if !h.EndpointIP.Equal(currentHost.EndpointIP) {
ifaceDeltaReasons = append(ifaceDeltaReasons, "endpoint_ip")
}
if !h.EndpointIPv6.Equal(currentHost.EndpointIPv6) {
ifaceDeltaReasons = append(ifaceDeltaReasons, "endpoint_ipv6")
}
}
if len(h.NatType) > 0 && h.NatType != currentHost.NatType {
ifaceDeltaReasons = append(ifaceDeltaReasons, "nat_type")
}
if h.DefaultInterface != currentHost.DefaultInterface {
ifaceDeltaReasons = append(ifaceDeltaReasons, "default_interface")
}
if !currentHost.IsStaticPort {
if h.ListenPort != 0 && h.ListenPort != currentHost.ListenPort {
ifaceDeltaReasons = append(ifaceDeltaReasons, "listen_port")
}
if h.WgPublicListenPort != 0 && h.WgPublicListenPort != currentHost.WgPublicListenPort {
ifaceDeltaReasons = append(ifaceDeltaReasons, "wg_public_listen_port")
}
}
if h.OSFamily != currentHost.OSFamily {
ifaceDeltaReasons = append(ifaceDeltaReasons, "os_family")
}
if h.OSVersion != currentHost.OSVersion {
ifaceDeltaReasons = append(ifaceDeltaReasons, "os_version")
}
if h.KernelVersion != currentHost.KernelVersion {
ifaceDeltaReasons = append(ifaceDeltaReasons, "kernel_version")
}
if h.Location != currentHost.Location {
ifaceDeltaReasons = append(ifaceDeltaReasons, "location")
}
if h.CountryCode != currentHost.CountryCode {
ifaceDeltaReasons = append(ifaceDeltaReasons, "country_code")
}
ifaceDelta := len(ifaceDeltaReasons) > 0
if ifaceDelta { // only save if something changes
slog.Debug("host check-in peer-relevant delta",
"host", h.Name,
"id", h.ID,
"reasons", ifaceDeltaReasons,
)
if !currentHost.IsStatic {
currentHost.EndpointIP = h.EndpointIP
currentHost.EndpointIPv6 = h.EndpointIPv6
}
currentHost.DefaultInterface = h.DefaultInterface
currentHost.NatType = h.NatType
currentHost.OSFamily = h.OSFamily
currentHost.OSVersion = h.OSVersion
currentHost.KernelVersion = h.KernelVersion
if h.Location != "" {
currentHost.Location = h.Location
}
if h.CountryCode != "" {
currentHost.CountryCode = h.CountryCode
}
if !currentHost.IsStaticPort {
if h.ListenPort != 0 {
currentHost.ListenPort = h.ListenPort
}
if h.WgPublicListenPort != 0 {
currentHost.WgPublicListenPort = h.WgPublicListenPort
}
}
if err := currentHost.Upsert(db.WithContext(context.TODO())); err != nil {
slog.Error("failed to update host after check-in", "name", h.Name, "id", h.ID, "error", err)
return false
}
slog.Info("updated host after check-in", "name", currentHost.Name, "id", currentHost.ID)
}
// Persist MDM device-matching identifiers if the netclient reported any
// new values. These don't affect peers, so don't roll into ifaceDelta.
mdmChanged := false
if h.EntraDeviceID != "" && h.EntraDeviceID != currentHost.EntraDeviceID {
currentHost.EntraDeviceID = h.EntraDeviceID
mdmChanged = true
}
if h.SerialNumber != "" && h.SerialNumber != currentHost.SerialNumber {
currentHost.SerialNumber = h.SerialNumber
mdmChanged = true
}
if h.HardwareUUID != "" && h.HardwareUUID != currentHost.HardwareUUID {
currentHost.HardwareUUID = h.HardwareUUID
mdmChanged = true
}
if mdmChanged {
if err := currentHost.Upsert(db.WithContext(context.TODO())); err != nil {
slog.Error("failed to update mdm identifiers after check-in", "name", h.Name, "id", h.ID, "error", err)
}
}
slog.Info("check-in processed for host", "name", h.Name, "id", h.ID)
return ifaceDelta
}
var HandleExporterIntegrationPull = func(client mqtt.Client, msg mqtt.Message) {}