153 lines
6.4 KiB
Go
153 lines
6.4 KiB
Go
package contract
|
|
|
|
// What the web front keeps on the bus about the names it serves, and about the names it was asked for and does not
|
|
// serve (issue 774). The proxy writes it, on change, in its states `routes` and `unrouted`, announced readable; the
|
|
// module's tools, and any module that reads them, read it from there, so the question "is this site reachable, and
|
|
// is its certificate good" is answered from the bus rather than with curl.
|
|
//
|
|
// A key is `<node>.<name>`: the machine whose proxy answers, then the name as asked, lower-cased. A node's name holds
|
|
// no dot, so the first dot splits them.
|
|
|
|
import (
|
|
"net"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// The states route-proxy keeps, by their local names (the buckets are route-proxy_<name>).
|
|
const (
|
|
// RoutesState holds one key per served name: where it forwards, its certificate, the last answer it got.
|
|
RoutesState = "routes"
|
|
// UnroutedState holds one key per name the proxy answered "no route for" with, and when it was last asked.
|
|
UnroutedState = "unrouted"
|
|
// BoundState is the provider helper's bucket of consumer entries (mesh-sdk go/provider).
|
|
BoundState = "bound"
|
|
)
|
|
|
|
// The words a route's reach and a certificate's authority are said in.
|
|
const (
|
|
ReachPublic = "public"
|
|
ReachInternal = "internal"
|
|
)
|
|
|
|
// The words a certificate's outcome is said in.
|
|
const (
|
|
// Issued: the authority issued it while this proxy ran, at RenewedAt.
|
|
Issued = "issued"
|
|
// Cached: this proxy served it from what it kept, and saw no issuance of it since it started.
|
|
Cached = "cached"
|
|
// Failed: the last attempt to have one failed, Error says why.
|
|
Failed = "failed"
|
|
)
|
|
|
|
// Target is one rule a name is served by: what a request matching it reaches, or what it is answered with.
|
|
type Target struct {
|
|
// Path is the path prefix the rule is scoped to; empty is every path.
|
|
Path string `json:"path,omitempty"`
|
|
// Module and Node are the consumer that contributed the route and the machine it runs on.
|
|
Module string `json:"module,omitempty"`
|
|
Node string `json:"node,omitempty"`
|
|
// Target is where the request is forwarded, scheme://address:port; empty for a rule that answers itself.
|
|
Target string `json:"target,omitempty"`
|
|
Deny bool `json:"deny,omitempty"`
|
|
Redirect string `json:"redirect,omitempty"`
|
|
// Auth says the rule asks for credentials; Sealed says why it cannot check them, and so refuses everything.
|
|
Auth bool `json:"auth,omitempty"`
|
|
Sealed string `json:"sealed,omitempty"`
|
|
}
|
|
|
|
// Certificate is what this proxy knows of a name's certificate.
|
|
type Certificate struct {
|
|
// Authority is which issuer certifies the name: public (the ACME directory the module names) or internal (the
|
|
// mesh's own).
|
|
Authority string `json:"authority"`
|
|
Issuer string `json:"issuer,omitempty"`
|
|
NotAfter *time.Time `json:"not_after,omitempty"`
|
|
// RenewedAt is when an authority last issued it to this proxy.
|
|
RenewedAt *time.Time `json:"renewed_at,omitempty"`
|
|
// Outcome is issued, cached or failed; empty until a handshake or an issuance said anything.
|
|
Outcome string `json:"outcome,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
At *time.Time `json:"at,omitempty"`
|
|
}
|
|
|
|
// Answer is the last answer the proxy got from upstream for a name: the status it carried back, or the error that
|
|
// stood in for one (a 502).
|
|
type Answer struct {
|
|
Status int `json:"status"`
|
|
At time.Time `json:"at"`
|
|
Error string `json:"error,omitempty"`
|
|
Path string `json:"path,omitempty"`
|
|
}
|
|
|
|
// Route is one served name, the value of a key of RoutesState.
|
|
type Route struct {
|
|
Name string `json:"name"`
|
|
// Proxy is the machine whose proxy serves it.
|
|
Proxy string `json:"proxy"`
|
|
// Reach is public (a route's own name) or internal (only its internal-name, served inside the private network).
|
|
Reach string `json:"reach"`
|
|
Targets []Target `json:"targets"`
|
|
Certificate Certificate `json:"certificate"`
|
|
LastAnswer *Answer `json:"last_answer,omitempty"`
|
|
}
|
|
|
|
// Unrouted is one name the proxy answered "no route for", the value of a key of UnroutedState.
|
|
type Unrouted struct {
|
|
Name string `json:"name"`
|
|
Proxy string `json:"proxy"`
|
|
// AskedAt is when the name was last asked for. Who asked is not kept here: the journal keeps it, for the
|
|
// machine's intrusion prevention.
|
|
AskedAt time.Time `json:"asked_at"`
|
|
// Times is how often it was asked since the proxy last started.
|
|
Times int `json:"times"`
|
|
// InternalOnly says the name is served, but only inside the private network, and was asked from outside.
|
|
InternalOnly bool `json:"internal_only,omitempty"`
|
|
// Handshake says it was asked in a TLS handshake (the name a client sent), not in an HTTP request.
|
|
Handshake bool `json:"handshake,omitempty"`
|
|
}
|
|
|
|
// OtherName is the name of the one key per proxy that counts the refusals not kept by name: names under no domain
|
|
// the proxy serves (what a scan sends), and names past the limit of new names a minute. Its key is
|
|
// `<node>._other`; no host name begins with an underscore.
|
|
const OtherName = "_other"
|
|
|
|
// Other is the value of a proxy's OtherName key.
|
|
type Other struct {
|
|
Proxy string `json:"proxy"`
|
|
// Minute is the last whole minute counted, and LastMinute how many refusals it held.
|
|
Minute time.Time `json:"minute"`
|
|
LastMinute int `json:"last_minute"`
|
|
// SinceStart is every refusal not kept by name since the proxy started.
|
|
SinceStart int `json:"since_start"`
|
|
}
|
|
|
|
// Registrable is the domain a name is registered under, as its last two labels; "" for an address or a single
|
|
// label. Two labels is wrong for a public suffix of two (co.uk); it only decides which refused names are kept by name.
|
|
func Registrable(name string) string {
|
|
if net.ParseIP(strings.Trim(name, "[]")) != nil {
|
|
return ""
|
|
}
|
|
labels := strings.Split(name, ".")
|
|
if len(labels) < 2 {
|
|
return ""
|
|
}
|
|
return strings.Join(labels[len(labels)-2:], ".")
|
|
}
|
|
|
|
var keyable = regexp.MustCompile(`^[a-z0-9_]([a-z0-9_.-]{0,251}[a-z0-9_])?$`)
|
|
|
|
// Keyable says whether a name can be a key's name: what a host name or an address is. A name asked for in a request
|
|
// is whatever a client sent; one no key could hold is no site anybody runs.
|
|
func Keyable(name string) bool { return keyable.MatchString(name) && !strings.Contains(name, "..") }
|
|
|
|
// Key is the key of name served or asked at node's proxy.
|
|
func Key(node, name string) string { return node + "." + name }
|
|
|
|
// Split is a key's node and name.
|
|
func Split(key string) (node, name string) {
|
|
node, name, _ = strings.Cut(key, ".")
|
|
return node, name
|
|
}
|