From b2a41e7b9f0883dff992c4d91193648f977c266e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Wed, 4 Feb 2026 16:48:36 +0100 Subject: [PATCH] incusd/cluster: Restrict join token to database servers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This avoids generating a huge token on very large clusters. Closes #2886 Signed-off-by: Stéphane Graber --- cmd/incusd/api_cluster.go | 55 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/cmd/incusd/api_cluster.go b/cmd/incusd/api_cluster.go index 6ecdf6100..ecd5595c8 100644 --- a/cmd/incusd/api_cluster.go +++ b/cmd/incusd/api_cluster.go @@ -1366,24 +1366,79 @@ func clusterNodesPost(d *Daemon, r *http.Request) response.Response { // retrieving remote operations. onlineNodeAddresses := make([]any, 0) + // Get cluster database state. + leaderAddress, err := s.Cluster.LeaderAddress() + if err != nil { + return response.InternalError(err) + } + + var raftNodes []db.RaftNode + err = s.DB.Node.Transaction(r.Context(), func(ctx context.Context, tx *db.NodeTx) error { + raftNodes, err = tx.GetRaftNodes(ctx) + if err != nil { + return fmt.Errorf("Failed loading RAFT nodes: %w", err) + } + + return nil + }) + if err != nil { + return response.SmartError(err) + } + err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error { + // Get global cluster state. + failureDomains, err := tx.GetFailureDomainsNames(ctx) + if err != nil { + return fmt.Errorf("Failed loading failure domains names: %w", err) + } + + memberFailureDomains, err := tx.GetNodesFailureDomains(ctx) + if err != nil { + return fmt.Errorf("Failed loading member failure domains: %w", err) + } + + maxVersion, err := tx.GetNodeMaxVersion(ctx) + if err != nil { + return fmt.Errorf("Failed getting max member version: %w", err) + } + // Get the nodes. members, err := tx.GetNodes(ctx) if err != nil { return fmt.Errorf("Failed getting cluster members: %w", err) } + args := db.NodeInfoArgs{ + LeaderAddress: leaderAddress, + FailureDomains: failureDomains, + MemberFailureDomains: memberFailureDomains, + OfflineThreshold: s.GlobalConfig.OfflineThreshold(), + MaxMemberVersion: maxVersion, + RaftNodes: raftNodes, + } + // Filter to online members. for _, member := range members { + memberInfo, err := member.ToAPI(ctx, tx, args) + if err != nil { + return err + } + // Verify if a node with the same name already exists in the cluster. if member.Name == req.ServerName { return fmt.Errorf("The cluster already has a member with name: %s", req.ServerName) } + // Skip servers that are offline. if member.State == db.ClusterMemberStateEvacuated || member.IsOffline(s.GlobalConfig.OfflineThreshold()) { continue } + // Only include servers that have a one of the database roles. + if !slices.Contains(memberInfo.Roles, "database") && !slices.Contains(memberInfo.Roles, "database-standby") { + continue + } + onlineNodeAddresses = append(onlineNodeAddresses, member.Address) }