a container may name its resolvers and its own address
Mailu's 2024.06 admin refuses to serve behind a resolver that does not validate DNSSEC, and the runtime's own forwarder (127.0.0.11) validates nothing — so a module shipping its own validating resolver had a resolver nothing could be pointed at. Found live, blocking a cutover: the admin sat unhealthy, submission answered 454, and the declaration language had no words for the fix. Two fields on a container, both handed to the runtime verbatim: dns — the resolvers it asks — and ip, its static address on its user-defined network, which exists for exactly one shape: a container others must reach before name resolution works, the resolver itself being the case that forced it. Both take only addresses and are refused on arrival otherwise — a name here would reach the runtime verbatim and be refused at create, after the old container was already gone.
This commit is contained in:
@@ -11,10 +11,12 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -807,6 +809,23 @@ type Container struct {
|
||||
// worse failure mode.
|
||||
Network string `json:"network,omitempty"`
|
||||
|
||||
// Dns is the resolvers this container asks, passed to the runtime unchanged.
|
||||
//
|
||||
// **Because some software refuses to run behind the runtime's forwarding resolver.** A mail
|
||||
// server's admin demands a DNSSEC-validating resolver, and the runtime's own (127.0.0.11)
|
||||
// forwards to whatever the machine has — so a module that ships its own validating resolver
|
||||
// must be able to point its other containers at it. Addresses, not names: the runtime's flag
|
||||
// takes only addresses, which is also why IP below exists — the resolver has to be somewhere
|
||||
// its siblings can name before any of them can resolve anything.
|
||||
Dns []string `json:"dns,omitempty"`
|
||||
|
||||
// IP is this container's address on its network, passed to the runtime unchanged.
|
||||
//
|
||||
// Only meaningful on a user-defined network, and refused by the runtime elsewhere. Exists for
|
||||
// exactly one shape: a container others must reach *before* name resolution works — a
|
||||
// module's own DNS resolver being the case that forced it (see Dns).
|
||||
IP string `json:"ip,omitempty"`
|
||||
|
||||
// RestartOn names resources whose change means this container must be recreated — the same
|
||||
// field a service has, for the same reason (novox/hq 04-ISSUES/009). A container reads a
|
||||
// mounted file once at start; a changed file leaves the running process holding the old value,
|
||||
@@ -875,6 +894,23 @@ func (c *Container) validate(where string, _ bool) []string {
|
||||
problems = append(problems, where+": "+err.Error())
|
||||
}
|
||||
}
|
||||
// The runtime's flags take addresses, and a name here would be handed to it verbatim and
|
||||
// refused at create — after the old container was already removed. Refused on arrival instead.
|
||||
for _, d := range c.Dns {
|
||||
if net.ParseIP(d) == nil {
|
||||
problems = append(problems, where+": dns "+strconv.Quote(d)+" is not an address; "+
|
||||
"the runtime's resolver flag takes only addresses")
|
||||
}
|
||||
}
|
||||
if c.IP != "" {
|
||||
if net.ParseIP(c.IP) == nil {
|
||||
problems = append(problems, where+": ip "+strconv.Quote(c.IP)+" is not an address")
|
||||
}
|
||||
if c.Network == "" {
|
||||
problems = append(problems, where+": an ip needs a network; the runtime refuses a "+
|
||||
"static address anywhere but a user-defined one")
|
||||
}
|
||||
}
|
||||
return append(problems, checkImage(where, c.Image)...)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user