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
|
// 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
|
// which of those hosts is a public name — the second is `name`, ACME-eligible; a host reached
|
||||||
// only through `internal-name` never appears there.
|
// 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) {
|
func routesFrom(path string) (map[string][]rule, map[string]bool, error) {
|
||||||
raw, err := os.ReadFile(path)
|
raw, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -730,12 +736,18 @@ func routesFrom(path string) (map[string][]rule, map[string]bool, error) {
|
|||||||
public := map[string]bool{}
|
public := map[string]bool{}
|
||||||
for _, c := range said.Given {
|
for _, c := range said.Given {
|
||||||
name, _ := c.Values["name"].(string)
|
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)
|
log.Printf("%s on %s asked for a route and named nothing; skipped", c.From, c.Node)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
host := strings.ToLower(name)
|
// What the route is called in a log line: its public name when it has one.
|
||||||
public[host] = true
|
called := name
|
||||||
|
if called == "" {
|
||||||
|
called = internal
|
||||||
|
}
|
||||||
|
|
||||||
made := rule{path: asPath(c.Values["path"])}
|
made := rule{path: asPath(c.Values["path"])}
|
||||||
if p, ok := asWhole(c.Values["priority"]); ok {
|
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) {
|
if looksLikeACredential(named) {
|
||||||
log.Printf("%s on %s declared route %q with a credential in the declaration rather "+
|
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)",
|
"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
|
continue
|
||||||
}
|
}
|
||||||
users, err := usersFrom(named)
|
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"])
|
port, ok := asPort(c.Values["port"])
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("%s on %s asked for route %q and gave no usable port; skipped",
|
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
|
continue
|
||||||
}
|
}
|
||||||
// Where the mesh says that machine is. Empty means it is this one — a workload beside
|
// 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" {
|
if scheme != "http" && scheme != "https" {
|
||||||
log.Printf("%s on %s asked for route %q with scheme %q, which is neither http "+
|
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
|
continue
|
||||||
}
|
}
|
||||||
made.insecure, _ = c.Values["insecure"].(bool)
|
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)
|
bytes, whole := asWhole(asked)
|
||||||
if !whole || bytes <= 0 {
|
if !whole || bytes <= 0 {
|
||||||
log.Printf("%s on %s asked for route %q with a max-request-body of %v, which is "+
|
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
|
continue
|
||||||
}
|
}
|
||||||
made.maxRequestBody = int64(bytes)
|
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)
|
made.target = fmt.Sprintf("%s://%s:%d", scheme, at, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
host := strings.ToLower(name)
|
||||||
out[host] = append(out[host], made)
|
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
|
// 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
|
// 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
|
// 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
|
// 0056's internalDomain half) — the same "nothing to join a label to" case the public name
|
||||||
// already has.
|
// already has. And the only name, when the endpoint reaches no further than the private
|
||||||
if internal, _ := c.Values["internal-name"].(string); strings.TrimSpace(internal) != "" {
|
// network.
|
||||||
|
if internal != "" {
|
||||||
out[strings.ToLower(internal)] = append(out[strings.ToLower(internal)], made)
|
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.
|
// A route with no internal-name composed gets no second host — the ordinary case, unchanged.
|
||||||
func TestARouteWithNoInternalNameGetsNoAlias(t *testing.T) {
|
func TestARouteWithNoInternalNameGetsNoAlias(t *testing.T) {
|
||||||
routes, _, err := routesFrom(write(t, `{"given":[
|
routes, _, err := routesFrom(write(t, `{"given":[
|
||||||
|
|||||||
Reference in New Issue
Block a user