78 lines
4.4 KiB
Markdown
78 lines
4.4 KiB
Markdown
---
|
||
status: resolved
|
||
opened: 2026-09-05
|
||
located-in: [mesh-control, mesh-catalog]
|
||
fixed-by: ADR 0049 (a slug for the login) + a shorter minted secret (mesh-control)
|
||
amended-design: 0049-a-consumers-identity-fits-the-tightest-backend.md
|
||
---
|
||
|
||
# The mesh's derived login does not fit every backend's identity rules — S3 rejects it
|
||
|
||
## What was observed
|
||
|
||
Proving the provider/consumer contract per backend (ADR 0048), redis and postgres passed: a
|
||
consumer authenticated against the provider with the login the mesh derived and the password
|
||
the mesh minted. **minio failed**, and not on the credential — on the *name*:
|
||
|
||
```
|
||
mc: <ERROR> Unable to add a new service account. The access key is invalid.
|
||
(access key length should be between 3 and 20).
|
||
```
|
||
|
||
The mesh derives a consumer's login as `mesh_<node>_<module>` — here `mesh_anchor_bucketuser`,
|
||
22 characters. That is a valid postgres role and a valid redis ACL user, so those providers
|
||
create it verbatim. S3 access keys are capped at **20 characters**, so minio refuses to create
|
||
the service account under it, and the provisioner retries forever while the consumer, holding
|
||
that same too-long access key, could never present it either.
|
||
|
||
## Why it matters beyond this instance
|
||
|
||
ADR 0048 says a provider creates *exactly* the login the mesh derived, so that the two ends
|
||
agree by construction — the mesh hands the same name to the provider (to create) and the
|
||
consumer (to present). That only holds if the derived name is one every provider can accept.
|
||
It is not: the mesh's `as` is a single format with no knowledge of a backend's identity rules,
|
||
and S3's are stricter than a database's. Any provider whose backend constrains identifiers more
|
||
tightly than postgres — a length cap, a charset, a required prefix — inherits this, and the
|
||
failure lands at provision time, per consumer, as an infinite retry rather than a refusal at
|
||
assignment.
|
||
|
||
This also shows the seam is real, not cosmetic: `as` is doing two jobs — a stable per-consumer
|
||
identity the two ends must agree on, and a literal identifier a specific backend must accept —
|
||
and those are not always the same string.
|
||
|
||
## The shape of a fix (open, not decided)
|
||
|
||
- **Constrain the derivation** so `as` is broadly acceptable — short (≤ 20), a conservative
|
||
charset, deterministic. This keeps "the provider creates exactly what the mesh derived" true
|
||
everywhere, at the cost of a less legible name, and it is a mesh-wide identity change (every
|
||
provider that already created the longer name would see it change).
|
||
- **Let a provider map `as` to a backend-valid identifier** it derives the same way on create
|
||
and on the consumer's behalf — but the consumer is generic and cannot run minio's mapping, so
|
||
this only works if the mapped identifier is *delivered back* to the consumer. That is the
|
||
data-provision return path this era keeps meeting (umami's siteId, cloudflare's record) and
|
||
does not yet have.
|
||
- **Declare the constraint on the interface** (`s3-bucket` states its identifier bounds) and
|
||
have the mesh derive within them — the most honest, the most work.
|
||
|
||
## Open questions
|
||
|
||
- Is `as` meant to be human-legible, or is a short opaque token acceptable — i.e., can the
|
||
derivation simply be shortened without anyone minding?
|
||
- Do redis/postgres actually want the long name, or did it only survive because they are
|
||
permissive? If nothing needs it long, the cheap fix is to cap it.
|
||
- Does this fold into the same decision as the data-provision return path, or is it separate?
|
||
|
||
## Resolution
|
||
|
||
Accepted **ADR 0049** (option E): a module declares an optional short `slug`, and the mesh derives
|
||
`mesh_<node>_<slug|name>`, bounded by the tightest backend (an S3 access key's 20) and refused at
|
||
assignment — naming the slug as the remedy — when it still would not fit. The minio grant e2e proved
|
||
it: `bucketuser` declares `slug: bkt`, so its access key `mesh_anchor_bkt` (15) is accepted where
|
||
`mesh_anchor_bucketuser` (22) was refused.
|
||
|
||
Proving that surfaced a **second S3 length constraint on the same credential** — the secret. The
|
||
mesh minted a 43-character password (32 random bytes, base64url), and an S3 secret key is 8–40. Fixed
|
||
in `mesh-control` `internal/secrets/seal.go` by minting 30 bytes → exactly 40 characters (240 bits,
|
||
ample), which fits S3 and every other backend. Both halves of an S3 credential — the access key
|
||
(login) and the secret key (password) — now fit the tightest backend, by the same rule.
|