fix(cluster): Joiner-IP sofort in @peer_ipv4 — Henne-Ei-Problem gelöst
IssueCert registriert den beitretenden Node jetzt direkt in ha_nodes (public_ip = ClientIP, status='joining') und triggert den Firewall-Reload, bevor die Response zurückgeht. Damit ist Port 8443 schon offen wenn der Node im nächsten Schritt auto-register via mTLS versucht. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -60,7 +60,7 @@ import (
|
|||||||
usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users"
|
usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.1.152"
|
var version = "1.1.153"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/setup"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/setup"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.1.152"
|
var version = "1.1.153"
|
||||||
|
|
||||||
const usage = `edgeguard-ctl — EdgeGuard CLI
|
const usage = `edgeguard-ctl — EdgeGuard CLI
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import (
|
|||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.1.152"
|
var version = "1.1.153"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// renewTickInterval — how often we re-evaluate expiring certs.
|
// renewTickInterval — how often we re-evaluate expiring certs.
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -337,8 +340,8 @@ func (h *ClusterHandler) IssueCert(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
// consumedBy → Remote-IP. Audit-Trail wenn jemand Tokens stiehlt
|
// consumedBy → Remote-IP. Audit-Trail wenn jemand Tokens stiehlt
|
||||||
// und vom falschen Host einlöst.
|
// und vom falschen Host einlöst.
|
||||||
consumedBy := c.ClientIP()
|
clientIP := c.ClientIP()
|
||||||
if _, err := h.Tokens.Consume(c.Request.Context(), req.Token, consumedBy); err != nil {
|
if _, err := h.Tokens.Consume(c.Request.Context(), req.Token, clientIP); err != nil {
|
||||||
response.BadRequest(c, err)
|
response.BadRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -353,12 +356,69 @@ func (h *ClusterHandler) IssueCert(c *gin.Context) {
|
|||||||
response.Internal(c, err)
|
response.Internal(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre-register the joining node so its IP lands in @peer_ipv4
|
||||||
|
// immediately — otherwise port 8443 stays blocked and auto-register
|
||||||
|
// via mTLS can never succeed (chicken-and-egg).
|
||||||
|
if h.Store != nil && h.PeerReloader != nil {
|
||||||
|
go h.preRegisterJoiner(clientIP, req.CSR)
|
||||||
|
}
|
||||||
|
|
||||||
response.OK(c, issueCertResponse{
|
response.OK(c, issueCertResponse{
|
||||||
CACert: caPEM,
|
CACert: caPEM,
|
||||||
PeerCert: peerCert,
|
PeerCert: peerCert,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// preRegisterJoiner inserts a minimal ha_nodes row for the joining peer
|
||||||
|
// (using the CSR CN as FQDN and the HTTP client IP as public_ip), then
|
||||||
|
// triggers a firewall reload so @peer_ipv4 contains the new IP before
|
||||||
|
// the peer tries to call /agent/cluster/peers on port 8443.
|
||||||
|
func (h *ClusterHandler) preRegisterJoiner(clientIP, csrPEM string) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
fqdn := cnFromCSR(csrPEM)
|
||||||
|
if fqdn == "" {
|
||||||
|
fqdn = "joining-" + clientIP
|
||||||
|
}
|
||||||
|
nodeID := fmt.Sprintf("pre-%x", time.Now().UnixNano())
|
||||||
|
|
||||||
|
n := models.HANode{
|
||||||
|
ID: nodeID,
|
||||||
|
Name: fqdn,
|
||||||
|
FQDN: fqdn,
|
||||||
|
APIURL: "https://" + fqdn + ":3443",
|
||||||
|
Role: "peer",
|
||||||
|
Status: "joining",
|
||||||
|
}
|
||||||
|
n.PublicIP = &clientIP
|
||||||
|
|
||||||
|
if _, err := h.Store.UpsertSelf(ctx, n); err != nil {
|
||||||
|
slog.Warn("cluster: pre-register joiner failed", "fqdn", fqdn, "ip", clientIP, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.PeerReloader(ctx); err != nil {
|
||||||
|
slog.Warn("cluster: PeerReloader failed after pre-register", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.Info("cluster: joiner pre-registered, firewall updated", "fqdn", fqdn, "ip", clientIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cnFromCSR extracts the Subject Common Name from a PEM-encoded CSR.
|
||||||
|
// Returns empty string on any parse error.
|
||||||
|
func cnFromCSR(csrPEM string) string {
|
||||||
|
block, _ := pem.Decode([]byte(csrPEM))
|
||||||
|
if block == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
csr, err := x509.ParseCertificateRequest(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return csr.Subject.CommonName
|
||||||
|
}
|
||||||
|
|
||||||
var errInvalidJoinRequest = simpleError("missing token or csr")
|
var errInvalidJoinRequest = simpleError("missing token or csr")
|
||||||
|
|
||||||
type simpleError string
|
type simpleError string
|
||||||
|
|||||||
Reference in New Issue
Block a user