Add per-device config compiler and agent config endpoint
Project the fleet-global model through a device's bindings into a rendered,
interface-agnostic config: rules compile to saddr/daddr forward matches with no
iif/oif so they are correct under FRR/ECMP. Firewalls always enforce; routers
enforce only when their fabric opts into defense-in-depth. Referenced address
groups are emitted as named sets carrying their source (static CIDRs, dns FQDNs,
or asn numbers) so membership churns out-of-band without a rule reload. Wire
GET /devices/{name}/config to compile and serve YAML, generation-stamped. Add
portgroups/policies/settings store methods and portgroup CRUD. Pure Render is
unit-tested for enforcement gating, ASN set emission, and resolver precedence.
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"git.unkin.net/unkin/tomswallapi/internal/compiler"
|
||||
"git.unkin.net/unkin/tomswallapi/internal/model"
|
||||
"git.unkin.net/unkin/tomswallapi/internal/store"
|
||||
)
|
||||
@@ -36,6 +37,10 @@ func (s *Server) mountResources(r chi.Router) {
|
||||
r.Get("/{name}/bindings", s.listBindings)
|
||||
r.Put("/{name}/bindings/{zone}", s.putBinding)
|
||||
})
|
||||
r.Route("/portgroups", func(r chi.Router) {
|
||||
r.Get("/", s.listPortGroups)
|
||||
r.Put("/{name}", s.putPortGroup)
|
||||
})
|
||||
r.Route("/rules", func(r chi.Router) {
|
||||
r.Get("/", s.listRules)
|
||||
r.Post("/", s.createRule)
|
||||
@@ -43,6 +48,24 @@ func (s *Server) mountResources(r chi.Router) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) listPortGroups(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := s.store.ListPortGroups(r.Context())
|
||||
respondList(w, list, err)
|
||||
}
|
||||
|
||||
func (s *Server) putPortGroup(w http.ResponseWriter, r *http.Request) {
|
||||
var p model.PortGroup
|
||||
if !decode(w, r, &p) {
|
||||
return
|
||||
}
|
||||
p.Name = chi.URLParam(r, "name")
|
||||
if err := s.store.UpsertPortGroup(r.Context(), p); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, p)
|
||||
}
|
||||
|
||||
func (s *Server) handleGeneration(w http.ResponseWriter, r *http.Request) {
|
||||
g, err := s.store.Generation(r.Context())
|
||||
if err != nil {
|
||||
@@ -201,8 +224,24 @@ func (s *Server) deleteRule(w http.ResponseWriter, r *http.Request) {
|
||||
// ---- Agent endpoints -------------------------------------------------------
|
||||
|
||||
func (s *Server) handleDeviceConfig(w http.ResponseWriter, r *http.Request) {
|
||||
// Rendering is implemented by the compiler (see task: compiler + agent endpoint).
|
||||
notImplemented(w)
|
||||
cfg, err := compiler.Compile(r.Context(), s.store, chi.URLParam(r, "name"))
|
||||
if err != nil {
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
writeError(w, http.StatusNotFound, "device not found")
|
||||
return
|
||||
}
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
body, err := cfg.Marshal()
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/yaml")
|
||||
w.Header().Set("X-Tomswall-Generation", strconv.FormatInt(cfg.Generation, 10))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(body)
|
||||
}
|
||||
|
||||
func (s *Server) handleDeviceStatus(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -248,7 +287,3 @@ func respondList[T any](w http.ResponseWriter, list []T, err error) {
|
||||
}
|
||||
writeJSON(w, http.StatusOK, list)
|
||||
}
|
||||
|
||||
func notImplemented(w http.ResponseWriter) {
|
||||
writeError(w, http.StatusNotImplemented, "not implemented yet")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user