firewall.enabled (default true) is a cluster setting, applied live: the leader sends a Firewall message to every agent when it registers and when the setting or firewall_networks changes, and the agent (root) takes llmgw's nft table out or installs it. A control plane writes the state to /var/lib/llmgw/firewall.json, which llmgw-firewall-sync.path applies as root with `llmgw firewall sync`; server init and server add install the units, `llmgw firewall sync --install` on one set up before. node add and server add put no firewall on a machine while the setting is off. `llmgw wipe` removes llmgw from a machine after listing what goes and asking for its name: units, engines' processes, the nft table, /etc/llmgw, /var/lib/llmgw, the agent's state, the sudo grant, the CA from the trust store, keepalived's configuration when llmgw wrote it, and the users. --models, --store (Patroni member, etcd, or the local database) and --all take more. The console has it under Do. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
90 lines
2.5 KiB
Go
90 lines
2.5 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"net/netip"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
llmgwv1 "gitea.mynodes.xyz/ilker/llmgw/api/gen/llmgw/v1"
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/firewall"
|
|
)
|
|
|
|
// applyFirewall keeps llmgw's firewall on this node as the cluster's
|
|
// settings say: the engines' ports and the node exporter's for the networks
|
|
// the node reaches its control planes on, and those named, or no table at
|
|
// all. Off the receive loop, one at a time.
|
|
func (a *Agent) applyFirewall(m *llmgwv1.Firewall) {
|
|
a.fwMu.Lock()
|
|
defer a.fwMu.Unlock()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
|
defer cancel()
|
|
r := a.fwRunner
|
|
if r == nil {
|
|
r = firewall.Local{}
|
|
}
|
|
if !m.GetEnabled() {
|
|
had, err := firewall.Disable(ctx, r)
|
|
switch {
|
|
case err != nil:
|
|
a.log.Warn("could not take llmgw's firewall off this node", "err", err)
|
|
case had:
|
|
a.log.Info("llmgw's firewall is off on this node, as the cluster's settings say")
|
|
}
|
|
return
|
|
}
|
|
a.mu.Lock()
|
|
servers := append([]string(nil), a.serverList...)
|
|
a.mu.Unlock()
|
|
nets, err := firewall.Networks(ctx, r, controlPlaneIPs(ctx, servers))
|
|
if err != nil {
|
|
a.log.Warn("could not tell which networks reach the control planes; the firewall is left as it is", "err", err)
|
|
return
|
|
}
|
|
for _, n := range m.GetNetworks() {
|
|
if p, err := netip.ParsePrefix(n); err == nil {
|
|
nets = append(nets, p)
|
|
} else if ad, err := netip.ParseAddr(n); err == nil {
|
|
nets = append(nets, netip.PrefixFrom(ad, ad.BitLen()))
|
|
}
|
|
}
|
|
from, to := int(m.GetEnginePortsFrom()), int(m.GetEnginePortsTo())
|
|
ports := []firewall.Port{firewall.NodeExporter}
|
|
if from > 0 && to >= from {
|
|
ports = append(ports, firewall.Engines(from, to))
|
|
}
|
|
changed, err := firewall.Install(ctx, r, firewall.Spec{Ports: ports, Allow: nets})
|
|
switch {
|
|
case err != nil:
|
|
a.log.Warn("could not put llmgw's firewall on this node", "err", err)
|
|
case changed:
|
|
a.log.Info("llmgw's firewall is on this node", "networks", len(nets))
|
|
}
|
|
}
|
|
|
|
// controlPlaneIPs are the addresses of the control planes an agent dials.
|
|
func controlPlaneIPs(ctx context.Context, servers []string) []string {
|
|
var out []string
|
|
for _, s := range servers {
|
|
host := s
|
|
if u, err := url.Parse(s); err == nil && u.Host != "" {
|
|
host = u.Hostname()
|
|
}
|
|
host = strings.Trim(host, "[]")
|
|
if a, err := netip.ParseAddr(host); err == nil {
|
|
out = append(out, a.String())
|
|
continue
|
|
}
|
|
ips, err := net.DefaultResolver.LookupNetIP(ctx, "ip", host)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
for _, ip := range ips {
|
|
out = append(out, ip.Unmap().String())
|
|
}
|
|
}
|
|
return out
|
|
}
|