kanidm: the effector ports off lldap — service accounts, one api-token, no root #5
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Ting/Vedanta#5
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Stable ID: VE-KANIDM-PORT
Source: owner ruling, 2026-08-11 (in-session, HITL) — filed retroactively after the work, per AGENTS.md § Tickets, and when one is required
Linked: lar.ad/flake-ops#51 (the cutover that stranded us), #72 (personas are kanidm service accounts), #91 (what flake-ops declares for this service), #97 (the off-repo spike this lifts from)
The problem
Every directory line in this repo addressed lldap, which left the fleet on 2026-08-10 (
flake-ops#51; rollback material retired by #69 the same day; thelldap/*sops keys deleted at phase 5 on 2026-08-11).src/lldap.rs,services.vedanta.lldap.url,VEDANTA_LLDAP_*and thetry-code-in-production-like-a-gigachadrecipe all named a daemon that no longer exists — the recipe's/run/secrets/lldap/admin_passwordwas already a deleted path, so it was broken rather than merely stale.The ruling: how Vedanta reaches kanidm
Vedanta holds its own kanidm identity — service account
sa-vedanta, one read-write api-token, delivered throughDOTENV_FILE.The alternative on the table was the operator path
vedanta-spike/mint.nuuses: ssh to heimdallr as root andkanidmd recover-account idm_admin. Refused, and worth keeping written down so it is not re-litigated:idm_adminat will is admin — it is not the narrow grant it appears to be;module.nix'sNoNewPrivilegesandProtectSystem = "strict";Rights are granted by group membership at bootstrap and deliberately not declared in the flake (owner: "no admin in the flake"). A privileged helper standing behind Vedanta was also refused: Vedanta IS the privileged component.
What the live instance taught us
Probed against kanidm 1.11 on heimdallr before any Rust was written — the same introspect-first standard the lldap effector was held to. None of this is in the docs and some of it is actively misleading:
+in aname—400 {"schemaviolation":{"invalidattributesyntax":"name"}}, both prefixes — but accepts it inmail. Soa+/sa+stays a JWTsubcontract, the directory entity isa-freki/sa-librefang, the plus-addressed mailbox survives intact, and the two are joined by UUID. This is why the spike names its accountssa-<persona>— a workaround nobody had recorded as one.-wapi-token inidm_service_account_adminscan create a service account (200, bodynull) and write a group's members (200). That is the premise of the whole design, and it is true.500 MissingClass("service_account"). Personas-as-persons would mean a standing password each;provision.personscould have declared them, but it cannot declare credentials.into-personis deprecated migration cruft.403until it is removed from that group first.kanidmd recover-accounthas no-o json; the machine-parsable interface iskanidmd scripting recover-account, whose field isoutput, notpassword. The CLI flag is-w/--readwrite, not--rw— with the wrong flag the CLI returns empty stdout rather than a usage error, so the failure reads as "no token".What changed
src/kanidm.rsreplacessrc/lldap.rs(deleted). Bearer api-token;POST /v1/service_account(name + displayname + mail in one call), read back the UUID,POST /v1/group/<g>/_attr/member.GET /v1/selfforreconcile, which now names the SPN it was green as.src/main.rs—mint()maps principal → directory name, creates, then joins the roster group. Partial success is reported, not rolled back: if the group join fails the account exists, the UUID is already logged, and the error says exactly that. Nothing here grows a revoke path.Cargo.toml—ldap3out;reqwestgains TLS. The feature isrustls, not the pre-0.13rustls-tlsspelling, and rustls rather than OpenSSL so compiled-in roots cannot fail underProtectSystem = "strict".module.nix—lldap.url→kanidm.url(the public origin, not kanidm's self-signed loopback:8443) pluskanidm.group. Hardening untouched.justfile— the gigachad recipe passes one bearer token instead of a bind DN and a deleted sops path.CONTEXT.md/AGENTS.md— the hazard section becomes a landed port; the "bind account over-grant" open question becomes "sa-vedanta's rights are sufficient, not minimal."just checkgreen (3 tests, up from 1),nix build .#defaultgreen. Note for the next contributor:src = selfsees tracked files only, so a new module must begit added beforenix buildcan see it, whilecargoin the devShell compiles it happily.Not done here
sa-vedantadoes not exist yet — nothing has minted end to end. Blocked on the flake-ops bootstrap act.agentsis undeployed and will be wiped byoverwriteMembersonce deployed. Both are flake-ops'.Bootstrapped by lar.ad/flake-ops#98 (mints
sa-vedantaand its token — blocks the first end-to-end mint) and blocked on lar.ad/flake-ops#99 (agentsundeployed, andoverwriteMemberswipes it). Governance for how this ticket came to be written after the work: #6.