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__`, 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) }