No description
  • Rust 97.4%
  • Nix 2.1%
  • Just 0.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Teyla 1db600ac26 openbao-plugin: authorize resources pairwise too, one kind and id per ask
Projection::{permitted_repositories, permitted_operations} and
IssueRequest::resource_repositories authorized the same way Vedanta's own
LifecycleCommand used to: two flat sets, unioned across every resource the
grant named. Projection now carries permitted_resources: Vec<ResourceGrant>
(Vedanta's own type) and IssueRequest names one resource_kind/resource_id
pair — a lease request always asks for exactly one resource — so authorize
looks up that one (kind, id) entry and holds the requested operations to
its own set, never the grant's total. build_command and the mirrored
DeployedGrant/Delegation wire type (crate::vedanta) carry the same shape
through to the signed command. The live bao_dev acceptance test still
passes un-ignored.

Co-Authored-By: Teyla <[email protected]>
2026-09-06 11:03:01 +02:00
fixtures projection, private_api: the grant's own resource vocabulary, checked pairwise 2026-09-06 11:02:30 +02:00
openbao-plugin openbao-plugin: authorize resources pairwise too, one kind and id per ask 2026-09-06 11:03:01 +02:00
openspec openspec: join the shared contracts store 2026-09-04 20:45:23 +02:00
src native: route by resource kind, never by an empty repository set 2026-09-06 11:02:47 +02:00
.gitignore add openspec 2026-08-31 17:46:07 +02:00
AGENTS.md docs: record the seat-taking effect, ruling D2b's authority exception (#50) 2026-09-06 08:17:13 +02:00
Cargo.lock session 48: the plugin OpenBao can actually spawn — go-plugin's protocol, in Rust (#48) 2026-09-06 07:50:14 +00:00
Cargo.toml serve: the private lifecycle listener — a unix socket whose caller is its uid 2026-09-05 22:59:17 +02:00
CONTEXT.md native: route by resource kind, never by an empty repository set 2026-09-06 11:02:47 +02:00
devenv.lock devenv: follow the doctor to nix/ting, and drop the fastlane seam 2026-09-04 23:40:57 +02:00
devenv.nix devenv: follow the doctor to nix/ting, and drop the fastlane seam 2026-09-04 23:40:57 +02:00
devenv.yaml devenv: resolve Vedanta's OpenSpec store through Jostoph (#25) 2026-08-30 06:35:29 +02:00
flake.lock vendor: jostoph-rs comes from midgard, not from this tree 2026-08-10 10:07:06 +02:00
flake.nix add openspec 2026-08-31 17:46:07 +02:00
justfile devenv: follow the doctor to nix/ting, and drop the fastlane seam 2026-09-04 23:40:57 +02:00
module.nix module.nix: wire the seat projection and tenure ledger (#50) 2026-09-06 08:17:13 +02:00
README.md docs: align queen and token boundaries with contract 0.1.4 2026-08-31 17:46:07 +02:00

Vedanta

The private identity and resource-credential effector for the Ting. Vedanta owns additive principal operations and performs bounded credential lifecycle effects only when commanded by OpenBao; sibling Ting/Jostoph owns subtractive principal lifecycle. Named and scoped by Ting/Ting#2, whose first open scope item was literally "Naming. This service needs a name (sibling to 'Jostoph' the steward)."

Built on jostoph-rs, the same way Jostoph itself is: an Event in, a Ruling per gate, gates written as ascent! Datalog programs (via Deduction), resolved deny-biased into one Decision, audited. A ruling declares effects; it never performs one — the substrate has no credentials and no knowledge of Kanidm, Forgejo, or OpenBao. Executing an allowed effect is this binary's job, not the substrate's. Private lifecycle commands additionally cross the authenticated, signed, deny-biased boundary in src/private_api.rs before any adapter can mutate a native issuer.

Why this exists

Ting/Ting#2's invariant: agent identity is pooled, not one-shot-per-spawn — a small set of reusable slots reassigned across many ephemeral drone containers, so a persona looks longer-lived than any one container's life. A slot is only available for reissue if there is no live previous holder — liveness-checked mutual exclusion, not a fixed TTL.

Privilege is split on purpose. Providers and flake-ops realize the durable drone pool and immutable AssignmentGrants; OpenBao authorizes and owns leases; Vedanta validates OpenBao's deployed projection and performs the narrow native credential effect. Jostoph owns subtractive principal lifecycle. A queen seat authorizes a drone grant but neither queen nor drone calls Vedanta or native token administration directly.

For the swarm-alpha pilot, all ten UUID-pinned pool principals have grants delegated to the runtime swarm-alpha/queen seat. A leased drone has no Forgejo account. Vedanta causes Forgejo to mint a distinct read-only, repository-restricted token under the active queen holder's account (agent.teyla initially), and OpenBao delivers it once to the drone. The queen never receives the credential. Renewal creates a successor; revocation deletes exact token IDs and blocks worker reuse while deletion is unverified.

What's already settled, from lar.ad/flake-ops

The two consumer-side schemas this service mints against are written, in lar.ad/flake-ops's docs/:

  • docs/identity-pool-schema.md — the pool. uid = worker-<uuid> (a UUID, not a sequential N — no pre-sized range for this service to coordinate against, no collision to avoid minting concurrently), one group (agent-pool) as the pool's only enumeration, three custom lldap attributes (pool_holder, pool_checkout_at, pool_generation). Display name convention: 4+ words, generated, deliberately whimsical (John the Third of the Root Beer) — a human scanning lldap can tell a rotating slot from a persistent identity by word count alone.
  • docs/librefang-hand-identity-schema.md — a second, DELIBERATELY SEPARATE schema for long-lived named personas (librefang's hands — not pool slots, no checkout/reissue cycle). uid = "a+<hand>", one mutable attribute (librefang_node) recording current assignment, not part of the identity. Display name: 1-2 words, a real name (Freki).
  • The a+/sa+ prefix split: a+ marks an agent/hand identity (persistent, has a persona); sa+ marks a service account (a machine credential with no persona attached — e.g. a default/ad-hoc identity for a deployment with no more specific principal). Surfaced by lar.ad/flake-ops's own need for exactly this (LIBREFANG_SA_TOKEN, sub = "sa+librefang") before this repo had it — recorded there first, landing here now.
  • The JWT claims contract (modules/hosts/modron/configuration.nix): sub (principal), groups (comma-joined STRING, not a JSON array — the gate's meta claims carry primitives only), gen (pool tenancy generation — omitted entirely for hands/service accounts, which have no checkout to name a generation of), aud (the gate this token is for), exp.
  • Authentication-token lifetime, for non-pool principals: pool checkout tokens are ~24h, reissued at every checkout. Hands and service accounts have no checkout event to hang a short expiry off — owner call (2026-08-09, via lar.ad/flake-ops) is ~30 days, manually reminted, until this service exists to do it on a schedule instead.

lar.ad/flake-ops's secrets/mint-identity-token.nu is the manual stand-in for the older principal-authentication mint path — a hand-run step crypto jwt sign wrapper, same claims contract, same signing key (secrets/identity-pool.yaml, operator-held). What Vedanta replaces is the "operator runs a script by hand" part, not that claims contract. It is not the Forge capability lease: the pilot's Forge token lasts at most 30 minutes and is issued only through OpenBao's private command.

What this repo does not decide yet

  • The liveness-check mechanism for pool reissue (heartbeat, TTL, or a read against the container runtime — docs/identity-pool-schema.md's answer: the runtime is authoritative, lldap records attribution only, with a 4×-lifetime disaster-recovery backstop; this service is what acts on that answer, not what re-derives it).
  • Where the principal-authentication JWT signing key actually lives once this is a standing service — today it is operator-held and decrypted by hand per mint. This is separate from private lifecycle command authentication: OpenBao signs those commands and Vedanta consumes only deployed public verification keys.
  • Integration with "start a swarm" (queen's bootstrap workflow) — this service owns only the identity slice of that workflow, not the whole thing.

Development

cargo test
cargo clippy --all-targets -- -D warnings