kanidm: the effector ports off lldap — service accounts, one api-token, no root #7
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Meta/Campaign
Meta/Epic
Meta/Session
Priority/Critical
Priority/High
Priority/Low
Priority/Medium
Reviewed/Confirmed
Reviewed/Curated
Reviewed/Duplicate
Reviewed/Invalid
Reviewed/Won't Fix
Scope/Campaign
Status/Abandoned
Status/Blocked
Status/Conflicted
Status/In Progress
Status/In Review
Status/Need Grooming
Status/Need More Info
Status/Ready
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Ting/Vedanta!7
Loading…
Reference in a new issue
No description provided.
Delete branch "tango/5-kanidm-effector"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Ports Vedanta's effector off the lldap this fleet no longer runs, onto kanidm — and settles as what Vedanta writes.
The ruling
Vedanta holds its own kanidm identity: service account
sa-vedanta, one read-write api-token, delivered throughDOTENV_FILE. The alternative was the operator path the off-repo spike uses — ssh as root,kanidmd recover-account idm_admin— refused (owner, 2026-08-11) because recovering idm_admin at will is admin, it forces this unit to run as root and discardmodule.nix's hardening, and a token's rights can be read out of the directory where root's cannot. Rights are granted at bootstrap, never in the flake; a privileged helper behind Vedanta was refused too — Vedanta is the privileged component.Probed before written
Against kanidm 1.11 on heimdallr, the standard the lldap effector set. None of this is in the docs:
+in aname(400 schemaviolation) but accepts it inmail— soa+/sa+stays a JWTsubcontract, the entity isa-freki, the plus-addressed mailbox survives, and the two are joined by UUID. This is why the spike writessa-<persona>: a workaround nobody had recorded as one.MissingClass("service_account")) — the service-account choice is forced, not preferred.-wtoken inidm_service_account_adminscreates accounts and writes group members. The premise holds.What is here
src/kanidm.rsreplacessrc/lldap.rs(deleted) — name + displayname + mail in onePOST, read back the UUID, join the roster group.GET /v1/selfforreconcile, which now names the SPN it was green as.mint()still routes throughSteward::evaluate. Partial success is reported, not rolled back — a failed group join leaves the account, logs the UUID, and says so. Nothing grows a revoke path.ldap3out;reqwestgains TLS (featurerustls), aimed at the public origin rather than kanidm's self-signed loopback.module.nix:lldap.url→kanidm.url+kanidm.group, hardening untouched.justfile: the gigachad recipe stops reading a sops path that was deleted atflake-ops#51phase 5.AGENTS.mdandCONTEXT.mdenter the tree — they were untracked through the whole port, invisible tonix buildand to reviewers — carrying two governance rulings (ticket #6) and the hazard section rewritten as a landed port.just checkgreen (3 tests, up from 1);nix build .#defaultgreen.Reviewer's attention, please
directory_name()diverges from the spike deliberately. It is+→-and nothing else, soa+freki→a-freki.mint.nunames every personasa-<persona>, which collapses the hand/service distinction the prefix law exists to draw. If that flattening was intentional upstream, this is the line to argue with.sa-vedantadoes not exist yet — blocked onlar.ad/flake-ops#98, and the roster group is separately broken (lar.ad/flake-ops#99: undeployed, andoverwriteMemberswill wipe it once it lands)..gitignorefix for operator files AGENTS.md always claimed were ignored but never were.Closes #5
Closes #6
lldap left the fleet at flake-ops#51 on 2026-08-10; its sops keys went at phase 5 the day after. Every directory line here addressed it, so the gigachad recipe was not stale but broken — it read a deleted secret path. Vedanta now holds its OWN kanidm identity: service account `sa-vedanta`, one read-write api-token, delivered through DOTENV_FILE. The alternative was the operator path the off-repo spike uses — ssh as root, `kanidmd recover-account idm_admin` — and it was refused (owner, 2026-08-11) for reasons worth not re-arguing: recovering idm_admin at will IS admin, so it is not the narrow grant it looks like; it forces this unit to run as root and discard module.nix's NoNewPrivileges and ProtectSystem=strict; and a token's rights can be read out of the directory, where root's cannot. The rights themselves are granted at bootstrap, never in the flake (owner: "no admin in the flake"), by lar.ad/flake-ops#98. A privileged helper standing behind Vedanta was refused too: Vedanta IS the privileged component. Every call was made against the live instance before it was written, the standard the lldap effector set. What that bought, none of it in the docs: - kanidm refuses `+` in a NAME (400 schemaviolation) but accepts it in MAIL. So a+/sa+ stays a JWT `sub` contract, the entity is a-freki, the plus-addressed mailbox survives where it is load-bearing, and the two are joined by UUID. This is why the spike writes `sa-<persona>` — a workaround nobody had recorded as one. - A person cannot hold an api-token (MissingClass("service_account")), so the service-account choice flake-ops#72 ruled is forced, not preferred. - A `-w` token in idm_service_account_admins creates accounts and writes group members. That premise is true, which is the whole reason this shape works. The gate is untouched: mint() still routes through Steward::evaluate and only then effects. Partial success is reported, not rolled back — if the roster group join fails the account exists and the error says so, with the uuid already logged. Nothing here grows a revoke path. reqwest gains TLS (feature `rustls`, not the pre-0.13 `rustls-tls` spelling), aimed at the public origin rather than kanidm's self-signed loopback, so no client here is taught to accept an unverified peer. Refs Ting/Vedanta#5, lar.ad/flake-ops#51, #72, #97, #98`/run/secrets/vedanta/kanidm_token` is not created by deploying — sops-nix materializes DECLARED secrets and nothing else, and flake-ops declares no `vedanta/*` yet (lar.ad/flake-ops#91). This recipe exists precisely to run code in production BEFORE that lands, so it now falls back to the sops file the token was minted into (lar.ad/flake-ops#98) and uses the host path when something finally does declare one. The credential goes over STDIN, never the command line: `ssh host "TOKEN=abc vedanta"` puts it in heimdallr's process table for the life of the call, readable by anyone on the box. And a missing credential now fails at 78 (EX_CONFIG) instead of limping. Observed for real: `VEDANTA_KANIDM_TOKEN="$(cat missing)"` does not stop a shell — `cat` writes to stderr, the substitution yields an EMPTY string, and kanidm answers `401 notauthenticated`, which reads as "your token is wrong" when the truth is "you have no token". Verified end to end against the live directory: INFO vedanta: reconcile: kanidm reachable, no reconciliation rules defined yet — no-op [email protected] Refs Ting/Vedanta#5, lar.ad/flake-ops#91, #98Verified in production, 2026-08-11
sa-vedantawas minted by the freeholder (lar.ad/flake-ops#98) and this branch now runs green against the real directory:That is the packaged binary, copied to heimdallr, authenticating as the minted service account — and
reconcilenaming who it was green as, which the lldap bind it replaces could never report.Added one commit (
5003add) off the back of the first live run. Two findings:/run/secrets/vedanta/kanidm_tokenis not created by deploying. sops-nix materializes declared secrets and nothing else, and flake-ops declares novedanta/*— that is #91, still untouched. So the recipe falls back to the sops file the token was minted into, and uses the host path once something declares one. The credential travels over stdin, never the command line, so it never appears in heimdallr's process table.VEDANTA_KANIDM_TOKEN="$(cat missing)"does not stop a shell:catwrites to stderr, the substitution yields"", and kanidm answers401 notauthenticated— which reads as "your token is wrong" when the truth is "you have no token". Both paths now refuse at78(EX_CONFIG) and name the file they wanted.Still true: no persona has been minted end to end, because #99 (
agentsundeployed, andoverwriteMemberswill wipe it) is unfixed.