Converge openings through the firewall an adopted node was found with, and retire it only when the node converges (hq ADR 0100)

This commit is contained in:
2026-09-22 17:19:49 +02:00
parent 3a613113be
commit 3c90d155b3
10 changed files with 1522 additions and 7 deletions
+89 -1
View File
@@ -78,6 +78,12 @@ const (
// cadence. Tools, hooks and event consumers are not separate modes: they are loaded by a tool
// host, which is itself a process that stays up.
TypeProcess Type = "process"
// TypeOpening is a port the mesh needs reachable on an adopted node, converged through the
// firewall found there in that firewall's own terms (novox/hq ADR 0100). A state, not a
// command: the host adds the rule it marks as the mesh's when it is missing, and removes only
// what it marked — which is what lets it travel over the link.
TypeOpening Type = "opening"
)
// Resource is one thing that should be true of the machine.
@@ -603,6 +609,74 @@ func (s *Service) validate(where string, _ bool) []string {
return problems
}
// Opening is a port reachable on an adopted node, from where, and on which path.
//
// **From** is everywhere or mesh — the private network, by its interface. **Path** is incoming,
// for something listening on the machine, or forwarded, for a published container port: the found
// firewall sees a published port after the runtime has translated it, so a forwarded opening names
// the container's own port in To as well as the machine's in Port.
type Opening struct {
ID string `json:"id"`
Type Type `json:"type"`
Port int `json:"port"`
Protocol string `json:"protocol"`
From string `json:"from"`
Path string `json:"path"`
To int `json:"to,omitempty"`
}
// Where an opening admits from, and the path it is on.
const (
FromEverywhere = "everywhere"
FromMesh = "mesh"
PathIncoming = "incoming"
PathForwarded = "forwarded"
)
func (o *Opening) Identity() string { return o.ID }
func (o *Opening) Kind() Type { return TypeOpening }
func (o *Opening) Target() string {
if o.Path == PathForwarded {
return fmt.Sprintf("%s/%d forwarded to %d from %s", o.Protocol, o.Port, o.To, o.From)
}
return fmt.Sprintf("%s/%d %s from %s", o.Protocol, o.Port, o.Path, o.From)
}
func (o *Opening) validate(where string, _ bool) []string {
var problems []string
if o.Port < 1 || o.Port > 65535 {
problems = append(problems, fmt.Sprintf("%s: an opening's port is 1-65535, not %d", where, o.Port))
}
if o.Protocol != "tcp" && o.Protocol != "udp" {
problems = append(problems, fmt.Sprintf("%s: an opening is tcp or udp, not %q", where, o.Protocol))
}
if o.From != FromEverywhere && o.From != FromMesh {
problems = append(problems, fmt.Sprintf(
"%s: an opening is from %q or %q, not %q", where, FromEverywhere, FromMesh, o.From))
}
switch o.Path {
case PathIncoming:
if o.To != 0 {
problems = append(problems, where+
": an incoming opening names no container port; only a forwarded one does")
}
case PathForwarded:
if o.To < 1 || o.To > 65535 {
problems = append(problems, where+
": a forwarded opening names the container's port it reaches, as to, 1-65535")
}
default:
problems = append(problems, fmt.Sprintf(
"%s: an opening's path is %q or %q, not %q", where, PathIncoming, PathForwarded, o.Path))
}
if !strings.HasPrefix(o.ID, AdoptionPrefix) {
problems = append(problems, fmt.Sprintf(
"%s: an opening is the mesh's own, so its id starts %q", where, AdoptionPrefix))
}
return problems
}
// Package is a package that should be present.
//
// Present is the whole of what it asserts, never a version: version is the package manager's
@@ -810,6 +884,8 @@ func newOf(t Type) Resource {
return &Access{}
case TypeProcess:
return &Process{}
case TypeOpening:
return &Opening{}
}
return nil
}
@@ -818,7 +894,7 @@ func newOf(t Type) Resource {
func Vocabulary() []Type {
return []Type{
TypeAccess, TypeAction, TypeArchive, TypeContainer, TypeDirectory, TypeFile,
TypeNetwork, TypePackage, TypeProcess, TypeService, TypeUser,
TypeNetwork, TypeOpening, TypePackage, TypeProcess, TypeService, TypeUser,
}
}
@@ -1051,6 +1127,18 @@ func parse(raw []byte, allowActions bool) (*Declaration, error) {
d.Resources = append(d.Resources, resource)
}
problems = append(problems, checkAdoption(env.Adoption, d.Resources, allowActions)...)
if env.Adoption == nil {
for _, r := range d.Resources {
if r.Kind() == TypeOpening {
// On a converged node the mesh's own filter admits what is declared, and the
// found firewall is retired; an opening there would be a rule in a firewall the
// mesh has disabled (novox/hq ADR 0100).
problems = append(problems, fmt.Sprintf(
"resource %q: an opening is for an adopted node, and this declaration does not "+
"say the node is adopted", r.Identity()))
}
}
}
if len(problems) > 0 {
return nil, &RefusalError{Problems: problems}