Files
llmgw/internal/mirror/firewall.go
Ilker ManapandClaude Opus 5.5 8df78a4d41 The firewall setting reaches the mirror machine and the store's members
The leading control plane tells the mirror over its API (PUT /v1/firewall,
kept as .firewall.json beside the models and applied by the same path unit
control planes use), and keeps the state in the store's cluster_state for
members that run no llmgw, which a root timer reads from the member's own
copy. The mirror also lets in the network of every node and control plane
the leader knows. Tried on the PoC: off, on and a network added, on every
kind of machine.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 08:53:17 +02:00

66 lines
2.1 KiB
Go

package mirror
import (
"bytes"
"context"
"encoding/json"
"net/http"
"os"
"path/filepath"
)
// The mirror's machine keeps llmgw's firewall as the cluster's settings
// say, as every other machine does. The mirror runs unprivileged and cannot
// change the ruleset: the control plane tells it what the settings say, it
// writes that beside its models, and a root path unit on the machine
// applies it with `llmgw firewall sync`.
// FirewallState is what the cluster's settings say of a machine's firewall.
type FirewallState struct {
Enabled bool `json:"enabled"`
// Networks are let in besides those the machine reaches others on.
Networks []string `json:"networks,omitempty"`
// Toward are the cluster's machines, by address: each one's network is
// let in, as the machine's route to it says.
Toward []string `json:"toward,omitempty"`
}
// FirewallFile is where, in the mirror's directory, the state is written.
// Hidden, so it is never taken for a model.
const FirewallFile = ".firewall.json"
func (a *API) putFirewall(w http.ResponseWriter, r *http.Request) {
var st FirewallState
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&st); err != nil {
fail(w, http.StatusBadRequest, err)
return
}
b, _ := json.Marshal(st)
p := filepath.Join(a.m.root, FirewallFile)
changed := false
if old, err := os.ReadFile(p); err != nil || !bytes.Equal(old, b) {
// Renamed into place: the path unit sees one change, of a whole file.
tmp := p + ".new"
if err := os.WriteFile(tmp, b, 0o644); err != nil {
fail(w, http.StatusInternalServerError, err)
return
}
if err := os.Rename(tmp, p); err != nil {
fail(w, http.StatusInternalServerError, err)
return
}
changed = true
}
writeJSON(w, http.StatusOK, map[string]bool{"changed": changed})
}
// SetFirewall tells the mirror what the cluster's settings say of its
// machine's firewall; it answers whether that changed anything.
func (c *Client) SetFirewall(ctx context.Context, st FirewallState) (bool, error) {
var out struct {
Changed bool `json:"changed"`
}
err := c.do(ctx, http.MethodPut, "/v1/firewall", st, &out)
return out.Changed, err
}