Nix service: webhook + reconciliation timer + lldap access + OTLP #2
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Ting/Vedanta!2
Loading…
Reference in a new issue
No description provided.
Delete branch "tango/1-nix-service"
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?
Closes Ting/Vedanta#1.
Vedanta now runs as a NixOS service, not just
cargo run:vedanta serve— the webhook responder (jostoph-rs'sPOST /v1/evaluateand friends), kept alive byvedanta.service(Restart=on-failure).vedanta reconcile— a single sweep, invoked byvedanta-reconcile.service(Type=oneshot) behindvedanta-reconcile.timer(OnUnitActiveSec, default 15m). No reconciliation rules exist yet — this proves the lldap wiring (connect + bind) and stops there. The owner named Matrix specifically as something that will drift silently and needs a periodic sweep, not just the webhook path.vedanta mint-agent <name> --display-name <display>(sub = a+<name>) andvedanta mint-service <name> --display-name <display>(sub = sa+<name>) — the first real effector.principal-shapenow also derives anidentity.minteffect alongside its Allow conclusion; on Allow,lldap::AdminClient(lldap's GraphQL admin API, confirmed live by introspecting heimdallr's actual schema — the raw LDAP protocol has no write path for user creation) actually creates the lldap user. Scope deliberately stops at user creation: no group membership, no custom attributes, no password —CreateUserInputhas no password field at all, and these accounts are meant to be passwordless (owner, 2026-08-09): they authenticate through modron's token contract, not an Authelia login window.src/lldap.rs—connect()(raw LDAP bind, used byreconcile) andAdminClient(GraphQL, used bymint-*), both defaulting to heimdallr's own loopback listener.src/telemetry.rs— a localtracing-subscriberfmt layer always installs; an OTLP span exporter additionally activates whenOTEL_EXPORTER_OTLP_ENDPOINTis set (the standard OTel env var). No bundled collector-side module — Vedanta pushes its own telemetry, same posture asmodron/modules/telemetry.nix's minimalotlpEndpointknob.DOTENV_FILE(dotenv-rs): fleet-wide secrets convention per owner ruling 2026-08-09 — sops-nix decrypts to a path, the unit setsDOTENV_FILE, the binary loads it before anything else touches the environment. Optional; unset is a legitimate state, not a misconfiguration.flake.nix:packages.default+nixosModules.default, built from the samemkVedantaso the devShell/package/module can't drift apart.services.vedantaoptions:enable,user(static, notDynamicUser—sops.secrets.<>.ownerneeds a fixed name),listen,reconcileInterval,dotenvFile,lldap.url,telemetry.{enable,otlpEndpoint}.just try-code-in-production-like-a-gigachad: builds this working tree,nix copys the result onto heimdallr's own Nix store over ssh, and runs it there with the real admin bind credential — decrypted and consumed entirely inside the remote shell, never crossing back to the caller. Defaults toreconcile; pass anything else vedanta takes as ARGS to run that instead. This is how everything in this PR was actually verified.Provisional:
jostoph-rsis nowvendor/jostoph-rs, a path dependency instead of a git one.jo.et0.pwis private, and Nix's sandboxed fixed-output git fetch has no credentials to reach it with (nix buildfailed on exactly that before this). The real distribution model — deploy key, makingjostoph-rspublic, a shared-library host underTing/, sourcehut as canonical upstream with--override-inputfor internalfastlaneuse — is still undecided (owner, 2026-08-09). This unblocks the ticket without deciding between them; bump by re-copyingvendor/jostoph-rsfrom a fresh clone at the new rev.Out of scope here (per Ting/Vedanta#1): heimdallr's actual live
services.vedanta.enable = truewiring — this PR is the binary and the module it ships, not flake-ops's standing consumption of it. Everything was verified with manualnix copy+ direct runs, not a deployed systemd unit.Verification
cargo test/cargo clippy --all-targets -- -D warnings— clean.vedanta serveby hand, on heimdallr:/healthzand/v1/manifestanswer, clean shutdown on SIGINT.vedanta reconcile, on heimdallr: actually bound against the live lldap with the real admin credential and returned the no-op result. Also verified locally: correct, distinct errors for a missing env var, an unreadableDOTENV_FILE, and a refused lldap connection; doesn't hang withOTEL_EXPORTER_OTLP_ENDPOINTset and no collector listening.vedanta mint-service minttest --display-name "Mint Test (disposable)", on heimdallr: actually createduid=sa+minttestin the live directory (confirmed via a direct GraphQL query against heimdallr), then deleted to leave the directory clean.nix build .#default— produces a working binary.nix evalagainst a minimalnixosSystemwithservices.vedanta.enable = true— confirmed both units'ExecStart, the timer interval, and the environment all wire through as declared.First time this code has actually been run through clippy — its own doc comment said as much ("not yet cargo built"). Construct PrincipalShapeProgram with its fields set at initialization instead of Default::default() followed by two field assignments.Everything before this declared an Allow and stopped; nothing in this binary ever actually wrote to lldap. This is the first effect that gets executed, not just declared: - principal-shape's ascent program now also derives an `identity.mint` effect (kind + `{"sub":...}` params) alongside its Allow conclusion, joined by (key, reason) per Derived::effects' own contract. `Derived` no longer discards effects with `..Default::default()`. - src/lldap.rs: AdminClient, a session against lldap's GraphQL admin API — NOT the raw LDAP protocol `connect()` already used for reconcile. Confirmed live, by introspecting heimdallr's actual running lldap (Mutation type, CreateUserInput), not assumed from docs: lldap has no LDAP-protocol write path for user creation, and CreateUserInput has no password field at all — credential-setting is a separate, unbuilt flow. These accounts are meant to be passwordless (owner, 2026-08-09): they authenticate through modron's token contract, not an Authelia login window, so the missing password field is the correct end state here, not a gap to fill in later. - `vedanta mint-agent <name> --display-name <display>` (sub = a+<name>) and `vedanta mint-service <name> --display-name <display>` (sub = sa+<name>) run the SAME principal-shape gate `serve` would — no side channel that skips it — and only execute the declared effect on Allow. `worker-` (pool slots) deliberately stays out: that's the pool-manager's checkout/reissue cycle, not a one-shot mint. - reqwest pulled in via --no-default-features: this only ever talks http://127.0.0.1 (lldap's own loopback listener), and the default TLS backend (rustls + aws-lc-rs) would be dead weight in both the binary and the Nix build closure for a connection that is never TLS. Scope, deliberately: user creation only — no group membership, no custom attributes (librefang_node, pool_*), no password. Each of those is its own undecided question already flagged in the schema docs (default groups for a freshly minted identity, credential rotation) — this effector executes what's actually settled and stops there, same posture principal-shape itself took. Verified against the real thing, not a mock: `nix build` + `nix copy` to heimdallr + `vedanta mint-service minttest --display-name "Mint Test (disposable)"` actually created uid=sa+minttest in the live directory (confirmed via a direct GraphQL query), then deleted to leave the directory clean. cargo test / clippy --all-targets -- -D warnings clean.