Verifizierte Bugs aus dem Code-Audit behoben (je mit Test/Build/nft -c geprüft):
- session: IssueWithRoleTTL mutierte geteiltes s.TTL (Data-Race + falsche TTL) → interne issue(); -race-Test.
- auth: Fallback/Federation leiteten role/TOTP nicht aus DB ab (2FA-Bypass auf Secondary, Rolle aus Remote) → viaDB-Flag + DB-Re-Lookup.
- waf: TrustedProxies waren No-op (bogus-Direktive) → XFF-Auflösung im SPOE-Agent (rightmostXFF/ipMatchesAny); RuleExclusions/TrustedProxies validiert (Direktiven-Injection); GetForHost via net.SplitHostPort.
- firewall: Auto-Rule mit IPv6-DstIP erzeugte 'ip daddr <v6>' → bricht ganzes nft-Ruleset; jetzt familienbewusst (ip/ip6, ungültige raus).
- kea: 'interfaces': null bei 0 Subnets → leeres Array.
- cluster_repair: nodeHasPublication schluckte DB-Fehler (Resync auf falschem Node) → (bool,error) fail-closed; IPv6-Primary-URL via net.JoinHostPort.
- cluster_replication: Replikations-Passwort via stdin statt psql -c (nicht mehr in argv/Logs).
- wireguard: Config (Private Key) jetzt configgen.AtomicWrite VOR Symlink/enable; SkipReload-Feld.
- render.go: --no-reload jetzt für alle Renderer (squid/unbound/chrony/wireguard).
- radius: leeres Secret/Passwort + Newlines abgelehnt; freeradius confEscape strippt CR/LF.
- configorch: continue-on-error + errors.Join statt Abbruch mitten in der Sequenz.
- i18n: fehlender Key common.status (de/en).
Verworfen als kein Bug: WAF detection-'blocked' (DetectionOnly liefert keine Interruption), render secrets.New('') (nutzt Default-Masterkey), FanOut-Sort (nur Kommentar), pg_hba (durch nft abgesichert).
Offen/bewusst zurückgestellt (low/risk): AlertWriter-Close (langlebiger Worker, vernachlässigbar), Rolling-Update-Kleinkram (sudoers-gebundener Script-Pfad / GET-State).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
79 lines
2.4 KiB
Go
79 lines
2.4 KiB
Go
// Package configorch fans Render() out across every per-service
|
|
// renderer in a stable order (haproxy → nftables → squid →
|
|
// wireguard → unbound). The orchestrator stops on the first hard
|
|
// error; ErrNotImplemented stubs are logged but do NOT abort the
|
|
// run (squid/wireguard/unbound are stubs in Phase 2).
|
|
package configorch
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"git.netcell-it.de/projekte/edgeguard-native/internal/configgen"
|
|
)
|
|
|
|
type Result struct {
|
|
Name string
|
|
Skipped bool
|
|
Err error
|
|
DurationS float64 // populated by callers if they care; orchestrator leaves it 0
|
|
}
|
|
|
|
func (r Result) Status() string {
|
|
switch {
|
|
case r.Err == nil:
|
|
return "ok"
|
|
case errors.Is(r.Err, configgen.ErrNotImplemented):
|
|
return "skipped (stub)"
|
|
default:
|
|
return "error: " + r.Err.Error()
|
|
}
|
|
}
|
|
|
|
// Run invokes Render on every generator in `gens`, returning a per-
|
|
// generator Result slice. Stops on the first hard error so a broken
|
|
// HAProxy config doesn't bleed into a partial nftables rewrite.
|
|
//
|
|
// only: optional whitelist of generator names — empty slice means
|
|
// "all". Useful for `render-config --only=haproxy` debugging.
|
|
func Run(ctx context.Context, gens []configgen.Generator, only []string) ([]Result, error) {
|
|
whitelist := map[string]bool{}
|
|
for _, n := range only {
|
|
whitelist[n] = true
|
|
}
|
|
out := make([]Result, 0, len(gens))
|
|
var errs []error
|
|
for _, g := range gens {
|
|
if len(whitelist) > 0 && !whitelist[g.Name()] {
|
|
out = append(out, Result{Name: g.Name(), Skipped: true})
|
|
continue
|
|
}
|
|
err := g.Render(ctx)
|
|
out = append(out, Result{Name: g.Name(), Err: err})
|
|
if err != nil && !errors.Is(err, configgen.ErrNotImplemented) {
|
|
// Weitermachen: die Generatoren sind unabhängig und reloaden
|
|
// inline (nft/Service-Reload sind atomar). Abbrechen würde die
|
|
// restlichen Dienste auf altem Stand lassen → halb angewandt.
|
|
// Stattdessen alle versuchen und Fehler gesammelt zurückgeben.
|
|
errs = append(errs, fmt.Errorf("%s: %w", g.Name(), err))
|
|
}
|
|
}
|
|
return out, errors.Join(errs...)
|
|
}
|
|
|
|
// Summarise turns the result slice into a human-readable multiline
|
|
// string. Used by `edgeguard-ctl render-config` to print to stdout.
|
|
func Summarise(results []Result) string {
|
|
var b strings.Builder
|
|
for _, r := range results {
|
|
if r.Skipped {
|
|
fmt.Fprintf(&b, " %-10s skipped (filtered)\n", r.Name)
|
|
continue
|
|
}
|
|
fmt.Fprintf(&b, " %-10s %s\n", r.Name, r.Status())
|
|
}
|
|
return b.String()
|
|
}
|