The proxy is told its routes and the mesh on the bus, and serves internal names to the mesh only (hq issue 191, ADR 0167) #207
@@ -716,6 +716,12 @@ func boolByte(b bool) byte {
|
||||
// routesFrom reads what the mesh wrote and turns it into host → the rules for that host, and
|
||||
// which of those hosts is a public name — the second is `name`, ACME-eligible; a host reached
|
||||
// only through `internal-name` never appears there.
|
||||
//
|
||||
// **A route may carry either name, or both** (novox/hq ADR 0138). How far an endpoint reaches
|
||||
// decides which names the mesh composes, so an endpoint that reaches only the private network
|
||||
// arrives with an `internal-name` and no `name`. That is a whole route, not a malformed one: it is
|
||||
// served under its internal name and certified by the internal authority. Only a route with
|
||||
// neither name has nothing to be served under (novox/hq issue 191).
|
||||
func routesFrom(path string) (map[string][]rule, map[string]bool, error) {
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
@@ -730,12 +736,18 @@ func routesFrom(path string) (map[string][]rule, map[string]bool, error) {
|
||||
public := map[string]bool{}
|
||||
for _, c := range said.Given {
|
||||
name, _ := c.Values["name"].(string)
|
||||
if name == "" {
|
||||
name = strings.TrimSpace(name)
|
||||
internal, _ := c.Values["internal-name"].(string)
|
||||
internal = strings.TrimSpace(internal)
|
||||
if name == "" && internal == "" {
|
||||
log.Printf("%s on %s asked for a route and named nothing; skipped", c.From, c.Node)
|
||||
continue
|
||||
}
|
||||
host := strings.ToLower(name)
|
||||
public[host] = true
|
||||
// What the route is called in a log line: its public name when it has one.
|
||||
called := name
|
||||
if called == "" {
|
||||
called = internal
|
||||
}
|
||||
|
||||
made := rule{path: asPath(c.Values["path"])}
|
||||
if p, ok := asWhole(c.Values["priority"]); ok {
|
||||
@@ -752,7 +764,7 @@ func routesFrom(path string) (map[string][]rule, map[string]bool, error) {
|
||||
if looksLikeACredential(named) {
|
||||
log.Printf("%s on %s declared route %q with a credential in the declaration rather "+
|
||||
"than the name of a secret; the whole route is refused (novox/hq ADR 0108)",
|
||||
c.From, c.Node, name)
|
||||
c.From, c.Node, called)
|
||||
continue
|
||||
}
|
||||
users, err := usersFrom(named)
|
||||
@@ -770,7 +782,7 @@ func routesFrom(path string) (map[string][]rule, map[string]bool, error) {
|
||||
port, ok := asPort(c.Values["port"])
|
||||
if !ok {
|
||||
log.Printf("%s on %s asked for route %q and gave no usable port; skipped",
|
||||
c.From, c.Node, name)
|
||||
c.From, c.Node, called)
|
||||
continue
|
||||
}
|
||||
// Where the mesh says that machine is. Empty means it is this one — a workload beside
|
||||
@@ -791,7 +803,7 @@ func routesFrom(path string) (map[string][]rule, map[string]bool, error) {
|
||||
}
|
||||
if scheme != "http" && scheme != "https" {
|
||||
log.Printf("%s on %s asked for route %q with scheme %q, which is neither http "+
|
||||
"nor https; skipped", c.From, c.Node, name, scheme)
|
||||
"nor https; skipped", c.From, c.Node, called, scheme)
|
||||
continue
|
||||
}
|
||||
made.insecure, _ = c.Values["insecure"].(bool)
|
||||
@@ -802,7 +814,7 @@ func routesFrom(path string) (map[string][]rule, map[string]bool, error) {
|
||||
bytes, whole := asWhole(asked)
|
||||
if !whole || bytes <= 0 {
|
||||
log.Printf("%s on %s asked for route %q with a max-request-body of %v, which is "+
|
||||
"not a whole positive number of bytes; skipped", c.From, c.Node, name, asked)
|
||||
"not a whole positive number of bytes; skipped", c.From, c.Node, called, asked)
|
||||
continue
|
||||
}
|
||||
made.maxRequestBody = int64(bytes)
|
||||
@@ -810,15 +822,20 @@ func routesFrom(path string) (map[string][]rule, map[string]bool, error) {
|
||||
made.target = fmt.Sprintf("%s://%s:%d", scheme, at, port)
|
||||
}
|
||||
|
||||
out[host] = append(out[host], made)
|
||||
if name != "" {
|
||||
host := strings.ToLower(name)
|
||||
out[host] = append(out[host], made)
|
||||
public[host] = true
|
||||
}
|
||||
|
||||
// The internal-network alias, the same rule under a second host — a predecessor proxy
|
||||
// The internal-network name, the same rule under a second host — a predecessor proxy
|
||||
// answered both for one route, as a convenience (reaching a service over the VPN without a
|
||||
// public TLS round trip), not as an access boundary; composing it here restores exactly
|
||||
// that, nothing more. Absent whenever the node composed no internal name (novox/hq ADR
|
||||
// 0056's internalDomain half) — the same "nothing to join a label to" case the public name
|
||||
// already has.
|
||||
if internal, _ := c.Values["internal-name"].(string); strings.TrimSpace(internal) != "" {
|
||||
// already has. And the only name, when the endpoint reaches no further than the private
|
||||
// network.
|
||||
if internal != "" {
|
||||
out[strings.ToLower(internal)] = append(out[strings.ToLower(internal)], made)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,50 @@ func TestARouteWithAnInternalNameIsReachableUnderBoth(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A route whose endpoint reaches only the private network carries an internal name and no public
|
||||
// one (novox/hq ADR 0138), and is served under that name rather than skipped as naming nothing —
|
||||
// skipping it left every internal-only module unreachable by name (novox/hq issue 191).
|
||||
func TestARouteWithOnlyAnInternalNameIsServed(t *testing.T) {
|
||||
routes, public, err := routesFrom(write(t, `{"given":[
|
||||
{"from":"app","node":"anchor","at":"anchor.internal",
|
||||
"values":{"internal-name":"App.Anchor.Internal","port":8443,"scheme":"https","insecure":true}}
|
||||
]}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if targetOf(routes, "app.anchor.internal") != "https://anchor.internal:8443" {
|
||||
t.Fatalf("the internal-only route is not served: %v", routes)
|
||||
}
|
||||
if len(routes) != 1 {
|
||||
t.Errorf("an internal-only route made hosts it never named: %v", routes)
|
||||
}
|
||||
if len(public) != 0 {
|
||||
t.Errorf("an internal-only route made a name eligible for a public certificate: %v", public)
|
||||
}
|
||||
|
||||
held := newTable()
|
||||
held.set(routes, public)
|
||||
if err := onlyInternalNamesTheMeshSaid(held)(context.Background(), "app.anchor.internal"); err != nil {
|
||||
t.Errorf("the internal authority refused the internal-only route's name: %v", err)
|
||||
}
|
||||
if err := onlyWhatTheMeshSaid(held)(context.Background(), "app.anchor.internal"); err == nil {
|
||||
t.Error("a public certificate was ordered for an internal-only name")
|
||||
}
|
||||
}
|
||||
|
||||
// A route with neither name has nothing to be served under, and is still skipped.
|
||||
func TestARouteWithNeitherNameIsSkipped(t *testing.T) {
|
||||
routes, public, err := routesFrom(write(t, `{"given":[
|
||||
{"from":"app","node":"anchor","at":"anchor.internal","values":{"internal-name":" ","port":8080}}
|
||||
]}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(routes) != 0 || len(public) != 0 {
|
||||
t.Errorf("a route that named nothing was served: %v %v", routes, public)
|
||||
}
|
||||
}
|
||||
|
||||
// A route with no internal-name composed gets no second host — the ordinary case, unchanged.
|
||||
func TestARouteWithNoInternalNameGetsNoAlias(t *testing.T) {
|
||||
routes, _, err := routesFrom(write(t, `{"given":[
|
||||
|
||||
Reference in New Issue
Block a user