82 lines
4.0 KiB
Go
82 lines
4.0 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// Who a consumer is, said once by the mesh (novox/hq 04-ISSUES/023).
|
|
//
|
|
// **The provisioner used to invent this and nothing else could derive it.** It made a role called
|
|
// `mesh_<node>_<module>`, which is a reasonable name and is knowable nowhere else: not by the
|
|
// control plane, not by the binding, and above all not by the consumer — which has to present it
|
|
// in order to authenticate. The one identifier needed to connect was the one thing no part of the
|
|
// mesh would say.
|
|
//
|
|
// So the mesh says it. It goes to the provider in the grant and to the consumer in its binding,
|
|
// from **one derivation**, which is what makes the two ends agree by construction rather than by
|
|
// two conventions that were the same on the day they were written.
|
|
//
|
|
// **It is still name-agnostic.** The mesh does not know what a role or an access key or a client
|
|
// is; it says who is asking, and each provisioner makes that true in whatever its own system
|
|
// calls an identity. What a provider does with it is the provider's business, as everything about
|
|
// a provision is.
|
|
|
|
// identityUnusable is every character that is not safe unquoted in the systems these names reach.
|
|
//
|
|
// Conservative on purpose: lower-case letters, digits and underscore reach a PostgreSQL role, a
|
|
// MinIO access key, an LDAP uid and a Keycloak client without quoting or escaping in any of them.
|
|
// A wider set would work in most and fail in one, discovered as a login that cannot be created.
|
|
var identityUnusable = regexp.MustCompile(`[^a-z0-9_]+`)
|
|
|
|
// IdentityPrefix marks what the mesh made, so a provisioner can find its own work and leave
|
|
// everything else alone. Withdrawal depends on it entirely.
|
|
const IdentityPrefix = "mesh_"
|
|
|
|
// IdentitySource is the name the mesh derives a consumer's identity from: the module's slug when it
|
|
// has declared one, otherwise its name (novox/hq ADR 0049). A module with a name short enough to fit
|
|
// the tightest backend needs no slug; one whose name would overflow declares a short legible one.
|
|
func IdentitySource(slug, name string) string {
|
|
if slug != "" {
|
|
return slug
|
|
}
|
|
return name
|
|
}
|
|
|
|
// ConsumerIdentity is what one module on one machine is called, wherever it authenticates. The
|
|
// `module` argument is the identity source — a slug or a name; see IdentitySource.
|
|
//
|
|
// A dot and a dash both become an underscore, so `home-server` and `home.server` would collide —
|
|
// which cannot happen, because a machine has one name and it is either.
|
|
func ConsumerIdentity(node, module string) string {
|
|
clean := func(s string) string {
|
|
return strings.Trim(identityUnusable.ReplaceAllString(strings.ToLower(s), "_"), "_")
|
|
}
|
|
return IdentityPrefix + clean(node) + "_" + clean(module)
|
|
}
|
|
|
|
// identityLimit is the shortest identifier limit among the systems these names reach: an S3 access
|
|
// key's 20 (novox/hq 04-ISSUES/010). PostgreSQL keeps 63 and MinIO 20, so 20 is the one that binds —
|
|
// the comment used to name PostgreSQL and was wrong. A name over it is refused, with the remedy a
|
|
// short slug (ADR 0049), not silently cut to fit.
|
|
const identityLimit = 20
|
|
|
|
// CheckIdentity refuses an identity that would not fit the tightest backend a consumer reaches.
|
|
//
|
|
// **Truncation is not an error in most of these systems** — a name past the limit is cut to fit and
|
|
// the statement succeeds, so two consumers agreeing for the first N bytes would become one login
|
|
// (04-ISSUES/022) — and S3 refuses outright. Refused here, at the mesh, because the mesh chose the
|
|
// name and is the only thing that can choose another. The remedy is a first-class one: give the
|
|
// module a short `slug` (ADR 0049), or shorten the machine's name.
|
|
func CheckIdentity(node, module string) error {
|
|
got := ConsumerIdentity(node, module)
|
|
if len(got) <= identityLimit {
|
|
return nil
|
|
}
|
|
return fmt.Errorf(
|
|
"%s on %s is identified as %q, %d characters where a backend (an S3 access key) keeps %d — "+
|
|
"give the module a shorter `slug` or shorten the machine's name",
|
|
module, node, got, len(got), identityLimit)
|
|
}
|