rmm2/backend/main.go
cynfo3000 6120d0cffa Initial commit: RMM Agent + Backend
- Go Agent (FreeBSD/amd64) fuer OPNsense
- Go Backend (Linux/amd64) mit REST API + SQLite
- Collector: Hardware, CPU, Memory, Disks, Network, Services,
  WireGuard, DHCP (KEA/ISC/dnsmasq), Routes, Gateways,
  Certificates, Plugins, Updates
- Persistente Agent-ID
- TLS + API-Key Auth
- WebSocket-Infrastruktur (WIP): bidirektionaler Command-Kanal,
  TCP-Tunnel fuer Remote-Zugriff auf Firewall-WebUI
- Lastenheft und README
2026-02-28 07:38:14 +01:00

133 lines
3.3 KiB
Go

package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"log"
"math/big"
"net"
"net/http"
"os"
"path/filepath"
"time"
"github.com/cynfo/rmm-backend/api"
"github.com/cynfo/rmm-backend/config"
"github.com/cynfo/rmm-backend/db"
)
func main() {
cfgPath := "config.yaml"
if len(os.Args) > 1 {
cfgPath = os.Args[1]
}
cfg, err := config.Load(cfgPath)
if err != nil {
log.Fatalf("Konfiguration laden fehlgeschlagen: %v", err)
}
// TLS-Zertifikate pruefen/generieren
if err := ensureTLS(cfg.TLSCert, cfg.TLSKey); err != nil {
log.Fatalf("TLS-Setup fehlgeschlagen: %v", err)
}
// Datenbank initialisieren
database, err := db.New(cfg.DBPath)
if err != nil {
log.Fatalf("Datenbank-Fehler: %v", err)
}
defer database.Close()
// Router aufbauen
mux := http.NewServeMux()
handler := api.NewHandler(database)
handler.SetupRoutes(mux)
// Middleware-Chain: Logging -> Auth -> Handler
var chain http.Handler = mux
chain = api.APIKeyAuth(cfg.APIKeys, chain)
chain = api.Logging(chain)
log.Printf("RMM Backend startet auf %s (TLS)", cfg.ListenAddr)
log.Printf("API-Keys konfiguriert: %d", len(cfg.APIKeys))
if err := http.ListenAndServeTLS(cfg.ListenAddr, cfg.TLSCert, cfg.TLSKey, chain); err != nil {
log.Fatalf("Server-Fehler: %v", err)
}
}
// ensureTLS generiert self-signed Zertifikate wenn keine vorhanden
func ensureTLS(certPath, keyPath string) error {
if _, err := os.Stat(certPath); err == nil {
if _, err := os.Stat(keyPath); err == nil {
log.Println("TLS-Zertifikate gefunden")
return nil
}
}
log.Println("Generiere self-signed TLS-Zertifikat...")
// Verzeichnis erstellen
if dir := filepath.Dir(certPath); dir != "" && dir != "." {
os.MkdirAll(dir, 0755)
}
if dir := filepath.Dir(keyPath); dir != "" && dir != "." {
os.MkdirAll(dir, 0755)
}
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return fmt.Errorf("Key generieren: %w", err)
}
serial, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
template := x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{
Organization: []string{"RMM Backend"},
CommonName: "rmm-backend",
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(365 * 24 * time.Hour * 10), // 10 Jahre
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("192.168.85.13")},
DNSNames: []string{"localhost", "rmm-backend"},
}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
return fmt.Errorf("Zertifikat erstellen: %w", err)
}
certFile, err := os.Create(certPath)
if err != nil {
return err
}
defer certFile.Close()
pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: certDER})
keyBytes, err := x509.MarshalECPrivateKey(key)
if err != nil {
return err
}
keyFile, err := os.OpenFile(keyPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer keyFile.Close()
pem.Encode(keyFile, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes})
log.Printf("TLS-Zertifikat generiert: %s, %s", certPath, keyPath)
return nil
}