Derive the guard from taken modules only: their published private-network ports and their manifests' guards (hq ADR 0103)
This commit is contained in:
@@ -138,6 +138,11 @@ type Rendering struct {
|
||||
// port the software uses (novox/hq ADR 0100) — the foundation's ports, as genesis chose them.
|
||||
// They win over anything the mesh would assign and over a manifest's own long-form mapping.
|
||||
Given map[string]map[int]int
|
||||
|
||||
// Taken is the modules taken on this adopted node (novox/hq ADR 0100). The guard is derived
|
||||
// from these only (ADR 0103): a port of a module assigned but not taken may still be the
|
||||
// predecessor's.
|
||||
Taken map[string]bool
|
||||
}
|
||||
|
||||
// machinePort is where a module's port lives on this machine, or the port itself when the mesh has
|
||||
@@ -586,50 +591,100 @@ func (r Resolution) compose(with Rendering, owner map[string]string) ([]map[stri
|
||||
// First, before anything a module declares: what the mesh needs reachable, then its guard.
|
||||
// The order a machine applies is the order written here.
|
||||
ours := Openings(rules, with.Foundation, Published(out))
|
||||
ours = append(ours, GuardResources(r.guarded(out, owner, with))...)
|
||||
ours = append(ours, GuardResources(r.guarded(out, owner, rules, with))...)
|
||||
out = append(ours, out...)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// guarded is the machine ports of every guarded port of the modules here: where each module's
|
||||
// container publishes it, as composed — or where the machine put it when no container does.
|
||||
func (r Resolution) guarded(out []map[string]any, owner map[string]string, with Rendering) []int {
|
||||
// guarded is what the mesh's guard refuses on an adopted node (novox/hq ADR 0103): derived, and
|
||||
// for taken modules only.
|
||||
//
|
||||
// For each taken module, every machine port its containers publish that the filter would admit
|
||||
// from the private network only — a published port is forwarded, not received, so a found
|
||||
// firewall filtering only what it receives never sees it — together with the ports the module's
|
||||
// manifest guards explicitly (the store's port, the broker's management port), wherever the
|
||||
// machine put them. A port of a module assigned but not taken is not guarded: it may still be the
|
||||
// predecessor's, serving the predecessor's other machines. The foundation's ports are admitted
|
||||
// from everywhere and are never guarded.
|
||||
func (r Resolution) guarded(out []map[string]any, owner map[string]string, rules []Rule,
|
||||
with Rendering) []int {
|
||||
meshOnly := map[int]bool{}
|
||||
for _, rule := range rules {
|
||||
if rule.Protocol == "tcp" && rule.From == FromMesh {
|
||||
meshOnly[rule.Port] = true
|
||||
}
|
||||
}
|
||||
for _, port := range with.Foundation {
|
||||
delete(meshOnly, port)
|
||||
}
|
||||
seen := map[int]bool{}
|
||||
var ports []int
|
||||
guard := func(at int) {
|
||||
if !seen[at] {
|
||||
seen[at] = true
|
||||
ports = append(ports, at)
|
||||
}
|
||||
}
|
||||
for _, m := range r.Modules {
|
||||
if !with.Taken[m.Module] {
|
||||
continue
|
||||
}
|
||||
var mine []map[string]any
|
||||
for _, resource := range out {
|
||||
if owner[fmt.Sprint(resource["id"])] == m.Module {
|
||||
mine = append(mine, resource)
|
||||
}
|
||||
}
|
||||
for outer := range Published(mine)["tcp"] {
|
||||
if meshOnly[outer] {
|
||||
guard(outer)
|
||||
}
|
||||
}
|
||||
for _, want := range m.Guards {
|
||||
at := with.machinePort(m.Module, want)
|
||||
for _, resource := range out {
|
||||
if owner[fmt.Sprint(resource["id"])] != m.Module ||
|
||||
fmt.Sprint(resource["type"]) != "container" {
|
||||
for _, resource := range mine {
|
||||
if fmt.Sprint(resource["type"]) != "container" {
|
||||
continue
|
||||
}
|
||||
listed, _ := resource["ports"].([]any)
|
||||
for _, entry := range listed {
|
||||
parts := strings.Split(strings.TrimSpace(fmt.Sprint(entry)), ":")
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
inner, err := strconv.Atoi(strings.SplitN(parts[len(parts)-1], "/", 2)[0])
|
||||
if err != nil || inner != want {
|
||||
continue
|
||||
}
|
||||
if outer, err := strconv.Atoi(parts[len(parts)-2]); err == nil {
|
||||
outer, inner, _, ok := mapping(fmt.Sprint(entry))
|
||||
if ok && inner == want {
|
||||
at = outer
|
||||
}
|
||||
}
|
||||
}
|
||||
if !seen[at] {
|
||||
seen[at] = true
|
||||
ports = append(ports, at)
|
||||
}
|
||||
guard(at)
|
||||
}
|
||||
}
|
||||
sort.Ints(ports)
|
||||
return ports
|
||||
}
|
||||
|
||||
// mapping reads a container's port mapping — `[address:]outer:inner[/protocol]`, the address
|
||||
// possibly an IPv6 one in brackets — indexing from the end, so an address's own colons never
|
||||
// shift the ports. Not ok for a short form or anything that is not a mapping.
|
||||
func mapping(written string) (outer, inner int, address string, ok bool) {
|
||||
written = strings.TrimSpace(written)
|
||||
if cut := strings.LastIndex(written, "/"); cut >= 0 {
|
||||
written = written[:cut]
|
||||
}
|
||||
parts := strings.Split(written, ":")
|
||||
if len(parts) < 2 {
|
||||
return 0, 0, "", false
|
||||
}
|
||||
inner, err := strconv.Atoi(parts[len(parts)-1])
|
||||
if err != nil {
|
||||
return 0, 0, "", false
|
||||
}
|
||||
outer, err = strconv.Atoi(parts[len(parts)-2])
|
||||
if err != nil {
|
||||
return 0, 0, "", false
|
||||
}
|
||||
return outer, inner, strings.Join(parts[:len(parts)-2], ":"), true
|
||||
}
|
||||
|
||||
// Rules is the rule set this node's filter is derived from: every module's listens, what was
|
||||
// computed for this machine, and each module's per-node exposure. The same answer whether the node
|
||||
// is adopted or converged — the one loads it as a filter, the other declares it as openings.
|
||||
|
||||
Reference in New Issue
Block a user