Stub raus, vollständig implementiert:
* Migration 0014: dns_settings (single-row) + dns_zones.forward_to.
Default-Settings sind sinnvoll für die typische LAN-Resolver-Rolle
(1.1.1.1 + 9.9.9.9 upstream, localnet allow, DNSSEC + qname-min on).
* internal/services/dns: CRUD-Repo für zones, records, settings.
* internal/handlers/dns.go: REST /api/v1/dns/zones, /records, /settings
mit Auto-Reload nach jeder Mutation.
* internal/unbound/unbound.cfg.tpl + unbound.go: Renderer schreibt
/etc/unbound/unbound.conf.d/edgeguard.conf direkt (kein Symlink-
Dance, weil AppArmor unbound nur /etc/unbound erlaubt). Local-zones
authoritativ aus dns_records; forward-zones per stub-zone; default-
forwarders catchen alles sonst.
* main.go: dnsRepo + unbound-Reloader injiziert.
* render.go: unbound.New() bekommt Pool.
* postinst:
- Conf-Datei /etc/unbound/unbound.conf.d/edgeguard.conf wird als
edgeguard:edgeguard 0644 angelegt damit Renderer schreiben kann.
- /etc/edgeguard + Service-Subdirs auf 0755 (Squid + Unbound laufen
NICHT als edgeguard, brauchen Read-Traversal).
- Sudoers: systemctl reload unbound.service whitelisted.
* Template: chroot:"" (Conf liegt außerhalb /var/lib/unbound default-
chroot), DNSSEC-Trust-Anchor NICHT setzen (Distro hat schon
root-auto-trust-anchor-file.conf — sonst doppelter Anchor → start
failure).
* Frontend /dns: PageHeader + zwei Tabs (Zones + Resolver-Settings).
Zones-Tab mit Drawer für Records (CRUD pro Zone, A/AAAA/CNAME/TXT/
MX/SRV/NS/PTR/CAA). Sidebar-Eintrag unter Network.
* i18n DE/EN für dns.* Block.
Verified end-to-end: render → unbound restart → dig @127.0.0.1
example.com → 104.20.23.154 / 172.66.147.243.
Version 1.0.34 (mehrere Iterationen wegen AppArmor + chroot + perms).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
102 lines
2.8 KiB
Go
102 lines
2.8 KiB
Go
// Package squid renders /etc/edgeguard/squid/squid.conf from
|
|
// forward_proxy_acls and reloads squid.service. Cache directory
|
|
// + in-memory cache are hard-coded sensible defaults; the operator
|
|
// scope is just "what can pass through the proxy".
|
|
package squid
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
_ "embed"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"text/template"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"git.netcell-it.de/projekte/edgeguard-native/internal/configgen"
|
|
"git.netcell-it.de/projekte/edgeguard-native/internal/models"
|
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/forwardproxy"
|
|
)
|
|
|
|
const (
|
|
confPath = "/etc/edgeguard/squid/squid.conf"
|
|
listenPort = 3128
|
|
)
|
|
|
|
//go:embed squid.cfg.tpl
|
|
var cfgTpl string
|
|
|
|
var tpl = template.Must(template.New("squid").Parse(cfgTpl))
|
|
|
|
type View struct {
|
|
ListenPort int
|
|
ACLs []models.ForwardProxyACL
|
|
}
|
|
|
|
type Generator struct {
|
|
Pool *pgxpool.Pool
|
|
Repo *forwardproxy.Repo
|
|
SkipReload bool
|
|
}
|
|
|
|
func New(pool *pgxpool.Pool) *Generator {
|
|
return &Generator{Pool: pool, Repo: forwardproxy.New(pool)}
|
|
}
|
|
|
|
func (g *Generator) Name() string { return "squid" }
|
|
|
|
func (g *Generator) Render(ctx context.Context) error {
|
|
acls, err := g.Repo.List(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("list acls: %w", err)
|
|
}
|
|
view := View{ListenPort: listenPort, ACLs: acls}
|
|
var body bytes.Buffer
|
|
if err := tpl.Execute(&body, view); err != nil {
|
|
return fmt.Errorf("template: %w", err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(confPath), 0o755); err != nil {
|
|
return fmt.Errorf("mkdir: %w", err)
|
|
}
|
|
tmp := confPath + ".tmp"
|
|
if err := os.WriteFile(tmp, body.Bytes(), 0o644); err != nil {
|
|
return fmt.Errorf("write %s: %w", tmp, err)
|
|
}
|
|
if err := os.Rename(tmp, confPath); err != nil {
|
|
return fmt.Errorf("rename: %w", err)
|
|
}
|
|
if err := ensureDistroSymlink(); err != nil {
|
|
return fmt.Errorf("symlink: %w", err)
|
|
}
|
|
if g.SkipReload {
|
|
return nil
|
|
}
|
|
return configgen.ReloadService("squid")
|
|
}
|
|
|
|
// ensureDistroSymlink prüft ob /etc/squid/squid.conf auf unsere
|
|
// managed conf zeigt. Setup ist Postinst-Verantwortung (Renderer
|
|
// hat als edgeguard-User kein Schreibrecht in /etc/squid). Wenn
|
|
// Symlink fehlt → Warnung, aber kein Fehler — squid liest dann
|
|
// noch die Distro-Default und der Operator merkt's beim nächsten
|
|
// reload.
|
|
func ensureDistroSymlink() error {
|
|
const link = "/etc/squid/squid.conf"
|
|
if cur, err := os.Readlink(link); err == nil && cur == confPath {
|
|
return nil
|
|
}
|
|
// Versuch zu setzen — bei permission-denied (= edgeguard-User
|
|
// hat keinen Schreibrecht in /etc/squid) warnen + ok melden.
|
|
if _, err := os.Stat(link); err == nil {
|
|
_ = os.Rename(link, link+".distro-bak")
|
|
}
|
|
if err := os.Symlink(confPath, link); err != nil {
|
|
// Postinst hat den Symlink schon angelegt oder soll's beim
|
|
// Upgrade nachholen. Renderer sollte hier nicht failen.
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|