feat(license): Lizenz-System mit Ed25519-Verify gegen license.netcell-it.com
Portiert mail-gateway/internal/license (Verify, Cache, Trial, Signature) + DB-Mirror (internal/services/license) + REST-Handler (status/verify/key/clear) + UI-Page /license (Activate, Status, Limits, Features, Re-verify) + <LicenseBanner /> neben UpdateBanner (trial-expiring, expired, verify-failed) + Scheduler: täglich Re-verify (24h-Tick) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,8 @@ import (
|
||||
firewallrender "git.netcell-it.de/projekte/edgeguard-native/internal/firewall"
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/haproxy"
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/handlers"
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/license"
|
||||
licsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/license"
|
||||
chronyrender "git.netcell-it.de/projekte/edgeguard-native/internal/chrony"
|
||||
squidrender "git.netcell-it.de/projekte/edgeguard-native/internal/squid"
|
||||
unboundrender "git.netcell-it.de/projekte/edgeguard-native/internal/unbound"
|
||||
@@ -45,7 +47,7 @@ import (
|
||||
wgsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/wireguard"
|
||||
)
|
||||
|
||||
var version = "1.0.46"
|
||||
var version = "1.0.47"
|
||||
|
||||
func main() {
|
||||
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
||||
@@ -234,6 +236,19 @@ func main() {
|
||||
return chronyrender.New(pool).Render(ctx)
|
||||
}
|
||||
handlers.NewNTPHandler(ntpRepo, auditRepo, nodeID, withFW(chronyReloader)).Register(authed)
|
||||
|
||||
// License — node-local key store + DB-mirror of last verify
|
||||
// result. Real verify runs against license.netcell-it.com via
|
||||
// internal/license; the scheduler triggers daily re-verify.
|
||||
licRepo := licsvc.New(pool)
|
||||
licClient := license.NewClient()
|
||||
licKeyStore := license.NewKeyStore()
|
||||
handlers.NewLicenseHandler(licRepo, licKeyStore, licClient, auditRepo, nodeID).Register(authed)
|
||||
// Kick off periodic re-verify in this process so a long-running
|
||||
// api answers /license/status with fresh data even without the
|
||||
// scheduler. StartPeriodicVerification is a no-op when the key
|
||||
// is empty.
|
||||
licClient.StartPeriodicVerification(licKeyStore.Get())
|
||||
}
|
||||
|
||||
mountUI(r)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
var version = "1.0.46"
|
||||
var version = "1.0.47"
|
||||
|
||||
const usage = `edgeguard-ctl — EdgeGuard CLI
|
||||
|
||||
|
||||
@@ -10,18 +10,21 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/database"
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/license"
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/acme"
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/certrenewer"
|
||||
licsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/license"
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/setup"
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
||||
)
|
||||
|
||||
var version = "1.0.46"
|
||||
var version = "1.0.47"
|
||||
|
||||
const (
|
||||
// renewTickInterval — how often we re-evaluate expiring certs.
|
||||
@@ -32,6 +35,10 @@ const (
|
||||
// certDir matches handlers.NewTLSCertsHandler default — HAProxy
|
||||
// reads from this directory.
|
||||
certDir = "/etc/edgeguard/tls"
|
||||
|
||||
// licenseTickInterval — daily re-verify against
|
||||
// license.netcell-it.com. Result lands in the licenses table.
|
||||
licenseTickInterval = 24 * time.Hour
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -61,18 +68,65 @@ func main() {
|
||||
slog.Warn("scheduler: setup.acme_email empty — ACME renewal disabled until setup wizard ran")
|
||||
}
|
||||
|
||||
licRepo := licsvc.New(pool)
|
||||
licClient := license.NewClient()
|
||||
licKeyStore := license.NewKeyStore()
|
||||
nodeID := os.Getenv("EDGEGUARD_NODE_ID")
|
||||
slog.Info("scheduler: license re-verify enabled", "tick", licenseTickInterval)
|
||||
|
||||
if renewer != nil {
|
||||
runRenewer(ctx, renewer)
|
||||
}
|
||||
tick := time.NewTicker(renewTickInterval)
|
||||
defer tick.Stop()
|
||||
for range tick.C {
|
||||
if renewer != nil {
|
||||
runRenewer(ctx, renewer)
|
||||
runLicenseVerify(ctx, licClient, licKeyStore, licRepo, nodeID)
|
||||
|
||||
renewTick := time.NewTicker(renewTickInterval)
|
||||
defer renewTick.Stop()
|
||||
licTick := time.NewTicker(licenseTickInterval)
|
||||
defer licTick.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-renewTick.C:
|
||||
if renewer != nil {
|
||||
runRenewer(ctx, renewer)
|
||||
}
|
||||
case <-licTick.C:
|
||||
runLicenseVerify(ctx, licClient, licKeyStore, licRepo, nodeID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runLicenseVerify performs a single re-verify pass. Empty key = no-op
|
||||
// (box stays in trial), so this is safe to call on every tick.
|
||||
func runLicenseVerify(ctx context.Context, c *license.Client, ks *license.KeyStore,
|
||||
repo *licsvc.Repo, nodeID string) {
|
||||
key := ks.Get()
|
||||
if key == "" {
|
||||
slog.Debug("scheduler: license verify skipped — no key")
|
||||
return
|
||||
}
|
||||
res, err := c.Verify(key)
|
||||
if err != nil {
|
||||
_ = repo.MarkError(ctx, key, err.Error())
|
||||
slog.Warn("scheduler: license verify failed", "error", err)
|
||||
return
|
||||
}
|
||||
payload, _ := json.Marshal(res)
|
||||
status := "active"
|
||||
if !res.Valid {
|
||||
status = "expired"
|
||||
if res.Status == "revoked" {
|
||||
status = "invalid"
|
||||
}
|
||||
}
|
||||
if err := repo.Upsert(ctx, key, status, res.ExpiresAt, nodeID, 0, payload, ""); err != nil {
|
||||
slog.Warn("scheduler: license db upsert failed", "error", err)
|
||||
return
|
||||
}
|
||||
slog.Info("scheduler: license verified",
|
||||
"status", status, "valid", res.Valid, "expires_at", res.ExpiresAt)
|
||||
}
|
||||
|
||||
func runRenewer(ctx context.Context, r *certrenewer.Service) {
|
||||
res, err := r.Run(ctx)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user