feat(cluster): Fix "joining" status + cross-node auth federation

1. preRegisterJoiner now runs SYNCHRONOUSLY before IssueCert responds,
   so nftables @peer_ipv4 is updated before the joiner calls autoRegister.
   Previously it was a goroutine → race → autoRegister failed → "joining"
   forever.

2. Stable node ID for pre-registered placeholder (prenode-{fqdn}) instead
   of time-based ID — re-joins are now idempotent.

3. AgentRegisterPeer sets status="online" immediately (peer proved it is
   online by connecting via mTLS) and deletes the prenode-{fqdn} placeholder.

4. autoRegister retries 3× with 2s delay in case of transient nftables lag.

5. Auth federation: cluster nodes forward failed logins to the primary via
   mTLS /agent/auth/check so users can log in on any node with primary
   credentials (no PG replication needed).
   - SystemHandler.AgentAuthCheck: new endpoint on :8443
   - AuthHandler.checkWithPrimary: mTLS call to primary when local auth fails
   - AuthHandler.WithClusterTLS: inject cluster TLS store
   - startAgentListener now uses the wired systemHdl with Users repo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-29 18:04:34 +02:00
parent 6bb1c5c6d3
commit 8d83de6b0f
8 changed files with 187 additions and 36 deletions

View File

@@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"io"
"log/slog"
"net"
"net/http"
"net/url"
@@ -122,8 +123,17 @@ func Join(req Request) error {
}
}
// Auto-register: best-effort — cert material is already written.
_ = autoRegister(primary, tlsDir, req.CommonName, req.Version, req.NodeID)
// Auto-register: retry a few times because the primary's nftables may
// need a moment to reload even though preRegisterJoiner is now
// synchronous on the primary side.
for i := 0; i < 3; i++ {
if err := autoRegister(primary, tlsDir, req.CommonName, req.Version, req.NodeID); err == nil {
break
} else if i < 2 {
slog.Warn("clusterjoin: autoRegister failed, retrying", "attempt", i+1, "error", err)
time.Sleep(2 * time.Second)
}
}
return nil
}