Updater: - Separater Go-Daemon (rmm-updater) als eigener rc.d Service - Pollt alle 60s: Firmware vorhanden + Hash unterschiedlich + Flag gesetzt - Download -> SHA256 Verify -> Agent Stop -> Replace -> Agent Start - Automatisches Rollback bei Fehler - Deployed und getestet auf allen 3 Firewalls (.1, .19, .33) Backend: - Firmware-DB multi-plattform (freebsd/linux/windows) - update_requested Flag pro Agent in agents Tabelle - Heartbeat-Response liefert update_available wenn Flag gesetzt - Neue Endpoints: request-update, request-update-all, firmware/download - Auth: JWT + CombinedAuth Middleware (API-Key OR JWT) - Customers CRUD + Agent-Zuordnung - Users CRUD + Passwort-Aenderung Frontend: - Firmware-Seite: Multi-Plattform Upload, Agent-Liste mit Update-Trigger - Agent-Tab im AgentPanel: Update-Button, nuetzliche Befehle, Agent-Info - Zertifikate-Tab redesigned: Karten-Layout mit Restlaufzeit und Farbbalken - Sidebar: Firmware-Eintrag hinzugefuegt Plugin: - install.sh erweitert: Installiert Agent + Updater + rc.d Services - rmm_updater rc.d Service README: - Architektur-Diagramm erweitert (Frontend + Updater) - Auth, Customers, Users, Firmware API dokumentiert - Updater-Flow dokumentiert - Frontend-Seiten und Deploy beschrieben - DB-Schema aktualisiert (neue Tabellen) - Dateistruktur erweitert (updater/, frontend/, firmware.go, auth.go)
220 lines
6.6 KiB
Go
220 lines
6.6 KiB
Go
package api
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
|
|
"github.com/cynfo/rmm-backend/ws"
|
|
)
|
|
|
|
// POST /api/v1/firmware/upload — Neues Agent-Binary hochladen
|
|
func (h *Handler) uploadFirmware(w http.ResponseWriter, r *http.Request) {
|
|
version := r.URL.Query().Get("version")
|
|
if version == "" {
|
|
writeError(w, http.StatusBadRequest, "version Parameter erforderlich")
|
|
return
|
|
}
|
|
|
|
platform := r.URL.Query().Get("platform")
|
|
if platform == "" {
|
|
platform = "freebsd" // Default fuer OPNsense
|
|
}
|
|
// Validieren
|
|
validPlatforms := map[string]bool{"freebsd": true, "linux": true, "windows": true}
|
|
if !validPlatforms[platform] {
|
|
writeError(w, http.StatusBadRequest, "Ungueltige Plattform (freebsd, linux, windows)")
|
|
return
|
|
}
|
|
|
|
// Binary aus Body lesen (max 50MB)
|
|
r.Body = http.MaxBytesReader(w, r.Body, 50*1024*1024)
|
|
binary, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "Binary lesen fehlgeschlagen")
|
|
return
|
|
}
|
|
|
|
if len(binary) == 0 {
|
|
writeError(w, http.StatusBadRequest, "Leeres Binary")
|
|
return
|
|
}
|
|
|
|
hash := sha256.Sum256(binary)
|
|
hashStr := hex.EncodeToString(hash[:])
|
|
|
|
if err := h.db.SaveAgentFirmware(version, platform, hashStr, binary); err != nil {
|
|
log.Printf("Firmware speichern fehlgeschlagen: %v", err)
|
|
writeError(w, http.StatusInternalServerError, "Speichern fehlgeschlagen")
|
|
return
|
|
}
|
|
|
|
log.Printf("Agent-Firmware v%s (%s) hochgeladen (%d bytes, Hash: %s)", version, platform, len(binary), hashStr[:16])
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"version": version,
|
|
"platform": platform,
|
|
"hash": hashStr,
|
|
"size": len(binary),
|
|
"message": "Firmware hochgeladen",
|
|
})
|
|
}
|
|
|
|
// GET /api/v1/firmware — Firmware-Info (alle Plattformen oder spezifisch)
|
|
func (h *Handler) getFirmwareInfo(w http.ResponseWriter, r *http.Request) {
|
|
platform := r.URL.Query().Get("platform")
|
|
|
|
if platform == "" {
|
|
// Alle Plattformen auflisten
|
|
all, err := h.db.GetAllFirmwareInfo()
|
|
if err != nil || len(all) == 0 {
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"available": false,
|
|
"message": "Keine Firmware hochgeladen",
|
|
"platforms": []interface{}{},
|
|
})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"available": true,
|
|
"platforms": all,
|
|
})
|
|
return
|
|
}
|
|
|
|
version, hash, size, err := h.db.GetFirmwareInfo(platform)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"available": false,
|
|
"platform": platform,
|
|
"message": "Keine Firmware fuer diese Plattform",
|
|
})
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"available": true,
|
|
"platform": platform,
|
|
"version": version,
|
|
"hash": hash,
|
|
"size": size,
|
|
})
|
|
}
|
|
|
|
// GET /api/v1/firmware/download — Binary herunterladen (fuer Updater)
|
|
func (h *Handler) downloadFirmware(w http.ResponseWriter, r *http.Request) {
|
|
platform := r.URL.Query().Get("platform")
|
|
if platform == "" {
|
|
platform = "freebsd"
|
|
}
|
|
version, hash, binary, err := h.db.GetLatestFirmware(platform)
|
|
if err != nil {
|
|
writeError(w, http.StatusNotFound, "Keine Firmware verfuegbar")
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
|
w.Header().Set("Content-Disposition", "attachment; filename=rmm-agent")
|
|
w.Header().Set("X-Firmware-Version", version)
|
|
w.Header().Set("X-Firmware-Hash", hash)
|
|
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(binary)))
|
|
w.Write(binary)
|
|
}
|
|
|
|
// POST /api/v1/agents/{id}/request-update — Update-Flag setzen
|
|
func (h *Handler) requestUpdate(w http.ResponseWriter, r *http.Request) {
|
|
agentID := r.PathValue("id")
|
|
if err := h.db.SetUpdateRequest(agentID, true); err != nil {
|
|
writeError(w, http.StatusInternalServerError, "Fehler beim Setzen des Update-Flags")
|
|
return
|
|
}
|
|
log.Printf("Update angefordert fuer Agent %s", agentID)
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Update angefordert", "agent_id": agentID})
|
|
}
|
|
|
|
// DELETE /api/v1/agents/{id}/request-update — Update-Flag loeschen
|
|
func (h *Handler) cancelUpdate(w http.ResponseWriter, r *http.Request) {
|
|
agentID := r.PathValue("id")
|
|
h.db.ClearUpdateRequest(agentID)
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Update-Anforderung geloescht", "agent_id": agentID})
|
|
}
|
|
|
|
// POST /api/v1/agents/request-update-all — Update-Flag fuer alle setzen
|
|
func (h *Handler) requestUpdateAll(w http.ResponseWriter, r *http.Request) {
|
|
count, err := h.db.SetUpdateRequestAll(true)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "Fehler")
|
|
return
|
|
}
|
|
log.Printf("Update angefordert fuer %d Agents", count)
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{"message": fmt.Sprintf("Update fuer %d Agents angefordert", count), "count": count})
|
|
}
|
|
|
|
// POST /api/v1/agents/{id}/agent-update — Update an einzelnen Agent pushen
|
|
func (h *Handler) pushAgentUpdate(hub *ws.Hub) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
agentID := r.PathValue("id")
|
|
|
|
// TODO: Agent-Plattform aus DB lesen; fuer jetzt default freebsd
|
|
version, hash, binary, err := h.db.GetLatestFirmware("freebsd")
|
|
if err != nil {
|
|
writeError(w, http.StatusNotFound, "Keine Firmware verfuegbar")
|
|
return
|
|
}
|
|
|
|
// Agent-Version pruefen
|
|
agent, _, err := h.db.GetAgent(agentID)
|
|
if err != nil || agent == nil {
|
|
writeError(w, http.StatusNotFound, "Agent nicht gefunden")
|
|
return
|
|
}
|
|
|
|
if agent.AgentVersion == version {
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"message": "Agent ist bereits auf dem neuesten Stand",
|
|
"current_version": agent.AgentVersion,
|
|
"target_version": version,
|
|
"updated": false,
|
|
})
|
|
return
|
|
}
|
|
|
|
// Binary als Base64 an Agent senden
|
|
binaryB64 := base64.StdEncoding.EncodeToString(binary)
|
|
|
|
cmdID := fmt.Sprintf("agent-update-%s", agentID[:8])
|
|
cmd := map[string]interface{}{
|
|
"type": "command",
|
|
"id": cmdID,
|
|
"command": "agent_update",
|
|
"data": map[string]interface{}{
|
|
"binary": binaryB64,
|
|
"hash": hash,
|
|
"version": version,
|
|
"old_version": agent.AgentVersion,
|
|
},
|
|
}
|
|
|
|
cmdJSON, _ := json.Marshal(cmd)
|
|
if err := hub.SendToAgent(agentID, cmdJSON); err != nil {
|
|
writeError(w, http.StatusBadGateway, fmt.Sprintf("Agent nicht erreichbar: %v", err))
|
|
return
|
|
}
|
|
|
|
log.Printf("Agent-Update v%s an %s gesendet (%d bytes)", version, agent.Name, len(binary))
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"message": fmt.Sprintf("Update v%s an %s gesendet", version, agent.Name),
|
|
"current_version": agent.AgentVersion,
|
|
"target_version": version,
|
|
"updated": true,
|
|
"size": len(binary),
|
|
})
|
|
}
|
|
}
|