Files
mesh-controller/internal/catalogue/identity.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)
}