feat(config-preview): chrony + wireguard in Config-Preview verfügbar
- chrony.RenderToString(): rendert /etc/chrony/conf.d/edgeguard.conf ohne Datei-Write oder Service-Reload - wireguard.RenderToString(): kombiniert alle aktiven Interface-Configs; PrivateKey + PresharedKey werden als <redacted> ausgegeben (sicher für UI-Anzeige) - main.go: beide in WithConfigPreviewers eingetragen - Settings UI: chrony + wireguard im Generator-Dropdown 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.96"
|
var version = "1.1.97"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
||||||
@@ -268,10 +268,12 @@ func main() {
|
|||||||
systemHdl.WithAudit(auditRepo, nodeID)
|
systemHdl.WithAudit(auditRepo, nodeID)
|
||||||
systemHdl.WithDB(pool)
|
systemHdl.WithDB(pool)
|
||||||
systemHdl.WithConfigPreviewers(map[string]func(context.Context) (string, error){
|
systemHdl.WithConfigPreviewers(map[string]func(context.Context) (string, error){
|
||||||
"haproxy": haproxy.New(pool).RenderToString,
|
"haproxy": haproxy.New(pool).RenderToString,
|
||||||
"nftables": firewallrender.New(pool).RenderToString,
|
"nftables": firewallrender.New(pool).RenderToString,
|
||||||
"squid": squidrender.New(pool).RenderToString,
|
"squid": squidrender.New(pool).RenderToString,
|
||||||
"unbound": unboundrender.New(pool).RenderToString,
|
"unbound": unboundrender.New(pool).RenderToString,
|
||||||
|
"chrony": chronyrender.New(pool).RenderToString,
|
||||||
|
"wireguard": wgrender.New(pool, secretsBox).RenderToString,
|
||||||
})
|
})
|
||||||
setupHdl.WithAudit(auditRepo, nodeID)
|
setupHdl.WithAudit(auditRepo, nodeID)
|
||||||
usersRepo := usersvc.New(pool)
|
usersRepo := usersvc.New(pool)
|
||||||
|
|||||||
@@ -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.96"
|
var version = "1.1.97"
|
||||||
|
|
||||||
const usage = `edgeguard-ctl — EdgeGuard CLI
|
const usage = `edgeguard-ctl — EdgeGuard CLI
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,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.96"
|
var version = "1.1.97"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// renewTickInterval — how often we re-evaluate expiring certs.
|
// renewTickInterval — how often we re-evaluate expiring certs.
|
||||||
|
|||||||
@@ -49,6 +49,30 @@ func New(pool *pgxpool.Pool) *Generator {
|
|||||||
|
|
||||||
func (g *Generator) Name() string { return "chrony" }
|
func (g *Generator) Name() string { return "chrony" }
|
||||||
|
|
||||||
|
// RenderToString renders the chrony config to a string without writing
|
||||||
|
// to disk or reloading the service. Used by the config-preview endpoint.
|
||||||
|
func (g *Generator) RenderToString(ctx context.Context) (string, error) {
|
||||||
|
settings, err := g.Repo.GetSettings(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("settings: %w", err)
|
||||||
|
}
|
||||||
|
pools, err := g.Repo.ListPools(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("pools: %w", err)
|
||||||
|
}
|
||||||
|
view := View{
|
||||||
|
Settings: settings,
|
||||||
|
Pools: pools,
|
||||||
|
ListenAddresses: filterNonLoopback(splitCSV(settings.ListenAddresses)),
|
||||||
|
AllowACLs: splitCSV(settings.AllowACL),
|
||||||
|
}
|
||||||
|
var body bytes.Buffer
|
||||||
|
if err := tpl.Execute(&body, view); err != nil {
|
||||||
|
return "", fmt.Errorf("template: %w", err)
|
||||||
|
}
|
||||||
|
return body.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (g *Generator) Render(ctx context.Context) error {
|
func (g *Generator) Render(ctx context.Context) error {
|
||||||
settings, err := g.Repo.GetSettings(ctx)
|
settings, err := g.Repo.GetSettings(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -44,6 +44,78 @@ func New(pool *pgxpool.Pool, box *secrets.Box) *Generator {
|
|||||||
|
|
||||||
func (g *Generator) Name() string { return "wireguard" }
|
func (g *Generator) Name() string { return "wireguard" }
|
||||||
|
|
||||||
|
// RenderToString renders all active interface configs to a combined
|
||||||
|
// string for the config-preview endpoint. Private keys are redacted
|
||||||
|
// so the output is safe to display in the management UI.
|
||||||
|
func (g *Generator) RenderToString(ctx context.Context) (string, error) {
|
||||||
|
ifs, err := g.Ifaces.List(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("list ifaces: %w", err)
|
||||||
|
}
|
||||||
|
var combined strings.Builder
|
||||||
|
for _, ifc := range ifs {
|
||||||
|
if !ifc.Active {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&combined, "# ── %s (%s) ────────────────────────────────\n", ifc.Name, ifc.Mode)
|
||||||
|
combined.WriteString("[Interface]\n")
|
||||||
|
fmt.Fprintf(&combined, "Address = %s\n", ifc.AddressCIDR)
|
||||||
|
combined.WriteString("PrivateKey = <redacted>\n")
|
||||||
|
if ifc.ListenPort != nil {
|
||||||
|
fmt.Fprintf(&combined, "ListenPort = %d\n", *ifc.ListenPort)
|
||||||
|
}
|
||||||
|
if ifc.MTU != nil {
|
||||||
|
fmt.Fprintf(&combined, "MTU = %d\n", *ifc.MTU)
|
||||||
|
}
|
||||||
|
combined.WriteString("\n")
|
||||||
|
switch ifc.Mode {
|
||||||
|
case "client":
|
||||||
|
if ifc.PeerPublicKey != nil && ifc.PeerEndpoint != nil {
|
||||||
|
combined.WriteString("[Peer]\n")
|
||||||
|
fmt.Fprintf(&combined, "PublicKey = %s\n", *ifc.PeerPublicKey)
|
||||||
|
fmt.Fprintf(&combined, "Endpoint = %s\n", *ifc.PeerEndpoint)
|
||||||
|
if ifc.AllowedIPs != nil && *ifc.AllowedIPs != "" {
|
||||||
|
fmt.Fprintf(&combined, "AllowedIPs = %s\n", *ifc.AllowedIPs)
|
||||||
|
} else {
|
||||||
|
combined.WriteString("AllowedIPs = 0.0.0.0/0,::/0\n")
|
||||||
|
}
|
||||||
|
if ifc.PersistentKeepalive != nil {
|
||||||
|
fmt.Fprintf(&combined, "PersistentKeepalive = %d\n", *ifc.PersistentKeepalive)
|
||||||
|
}
|
||||||
|
if len(ifc.PeerPSKEnc) > 0 {
|
||||||
|
combined.WriteString("PresharedKey = <redacted>\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "server":
|
||||||
|
peers, err := g.Peers.ListForInterface(ctx, ifc.ID)
|
||||||
|
if err == nil {
|
||||||
|
sort.Slice(peers, func(i, j int) bool { return peers[i].Name < peers[j].Name })
|
||||||
|
for _, p := range peers {
|
||||||
|
if !p.Enabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
combined.WriteString("[Peer]\n")
|
||||||
|
fmt.Fprintf(&combined, "# %s\n", p.Name)
|
||||||
|
fmt.Fprintf(&combined, "PublicKey = %s\n", p.PublicKey)
|
||||||
|
fmt.Fprintf(&combined, "AllowedIPs = %s\n", p.AllowedIPs)
|
||||||
|
if p.Keepalive != nil {
|
||||||
|
fmt.Fprintf(&combined, "PersistentKeepalive = %d\n", *p.Keepalive)
|
||||||
|
}
|
||||||
|
if len(p.PSKEnc) > 0 {
|
||||||
|
combined.WriteString("PresharedKey = <redacted>\n")
|
||||||
|
}
|
||||||
|
combined.WriteString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
combined.WriteString("\n")
|
||||||
|
}
|
||||||
|
if combined.Len() == 0 {
|
||||||
|
return "# No active WireGuard interfaces configured.\n", nil
|
||||||
|
}
|
||||||
|
return combined.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (g *Generator) Render(ctx context.Context) error {
|
func (g *Generator) Render(ctx context.Context) error {
|
||||||
if err := os.MkdirAll(ConfDir, 0o700); err != nil {
|
if err := os.MkdirAll(ConfDir, 0o700); err != nil {
|
||||||
return fmt.Errorf("mkdir %s: %w", ConfDir, err)
|
return fmt.Errorf("mkdir %s: %w", ConfDir, err)
|
||||||
|
|||||||
@@ -706,6 +706,8 @@ export default function SettingsPage() {
|
|||||||
{ value: 'nftables', label: 'nftables' },
|
{ value: 'nftables', label: 'nftables' },
|
||||||
{ value: 'squid', label: 'squid' },
|
{ value: 'squid', label: 'squid' },
|
||||||
{ value: 'unbound', label: 'unbound' },
|
{ value: 'unbound', label: 'unbound' },
|
||||||
|
{ value: 'chrony', label: 'chrony' },
|
||||||
|
{ value: 'wireguard', label: 'wireguard' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Reference in New Issue
Block a user