Peering recipient picker: opt-in live expansion of group receivers (implements #234 decision D) #243
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: Cordy/Cairn#243
Loading…
Add table
Add a link
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?
Implements the ratified decision from #234 (Option D): group-receiver discovery is off by default, enabled per-instance by the receiver admin, live and source-gated, never materialized, never via
Manager.Users. Authorization is unchanged (already correct via liveCanReceive).Contract
ListRecipientsbehaves exactly as today (explicitReceivers.Users∩ residents). Safe floor.ListRecipientsadditionally resolves eachReceivers.Groupsentry to its members live, unions them with the explicit users, and keeps only those ∩ resident ∩CanReceive(peer, u, groups)— so the picker never suggests anyone the delivery gate would refuse.ListRecipientstime, never a stored/materialized member list (would reopen the #123/#125 revocation window).Manager.Users(whole realm) — it would suggest non-members and users who'd be refused at delivery.Work
GroupMembers(group string) ([]string, bool)(or similar) capability, implemented for:GET /admin/realms/{realm}/groups/{id}/members) via the same admin client that backs #226 — confirm the client's role covers it (view-users/query-groups); document the role requirement.member/memberOfattribute read.Registry.ReceiverGroups(peer)— expose the peer's inbound group allow-list (mirrors the existingReceiverUsers).peeringExpandGroups(default false), persisted in the state-backend like the other runtime settings; surfaced as an admin toggle.Service.ListRecipients— when the setting is on and a resolver exists, union explicit users with live-expanded members, then ∩ residents ∩CanReceive. Keep the singlepeer-recipient-listaudit event (record expanded vs not in the detail).Out of scope / parked
group@domain-as-address model): the cleaner long-term "send to a team" primitive, but a much larger change (delivery is per-username into/home/<recipient>/Inbox/<peer>/). Track separately if wanted.Milestone: v0.7 (closed beta / hardening), same as #234.
Refs: #234 (decision), #125, #123, #96, #226.
Clarification from the follow-up discussion, relevant to the "openbao/app-owned has no source → must degrade" line:
That's true only for IdP groups (
Receivers.Groups= Keycloak/LDAP group names). openbao/app-owned instances do have a native group→members source for app-owned Spaces (internal/spacestore, ADR 0001 —{name, owner, members[]}in the state-backend, works under every custody mode). It just isn't the same thing as an IdP group and isn't what this issue'sReceivers.Groupsreferences.So #243 stays scoped to IdP-group receivers (needs Keycloak/LDAP; app-owned degrades). The app-owned-friendly and privacy-clean path for "deliver to a team" is #244 (Space as a peering recipient → shared Space inbox, no member disclosure, works in all modes). The two are complementary: #243 = a group of individuals each receiving personally; #244 = a team receiving into a shared space.
Shipped as v0.6.55 (PR #291), live on both dogfoods. Implements the contract exactly: default never-expand; receiver opt-in
settings.peeringExpandGroups(Peering → Global switch, off by default, live-read); liveManager.GroupMembersover the Keycloak read client (findGroup+/groups/{id}/members, view-users/query-groups — notManager.Users); every expanded name still passesRecipients.Exists; never materialized;peer-recipient-listaudit gainsexpanded; unit tests green; peering.md documents the disclosure trade-off.Follow-up (left open on this issue): LDAP group-member resolver — LDAP instances currently degrade to free-text (nil resolver), matching the safe floor. Implement via a group-entry
memberlookup when an LDAP deployment needs it.Dogfood prerequisite for the original finding (nikola-test not listed when sending enc → bao): files-bao is the receiver and needs a group→members source plus the opt-in —
cordyceps): create a confidential client (e.g.cairn-bao-groups), service accounts ON, standard flow OFF; Service accounts roles → assign realm-management → view-users + query-groups (NOT manage-users); copy the secret.cairn-openbao-configSecret (untracked, manual edit): inside"auth": { … }add thenkubectl -n cairn rollout restart deployment/cairn-openbao.bao→ picker listsnikola-test(cairn-admins member) under "Listed recipients at this peer".Side effects of the read client on bao: live group resolution (#96) — membership changes apply within ~1 min — and the admin panel's group-only receivers warning (#129) resolves.
Closing — verified live by Nikola 2026-08-20. Record of what fixed the original finding and what was done along the way:
Root cause.
nikola-testnever appeared in enc's send picker because bao's receive allow-list for the enc peering names thecairn-adminsgroup, andListRecipientsdeliberately never expanded groups (#125 degrade) — the picker fell back to "No listed recipients — type a username" while delivery itself worked.Fix (shipped as v0.6.55, PR #291). Decision-D expansion exactly per this issue's contract: receiver opt-in
settings.peeringExpandGroups(Peering → Global switch, off by default, live-read),Registry.ReceiverGroups,Service.recipientListwith liveManager.GroupMembersover the Keycloak read client (one group at a time viafindGroup+/groups/{id}/members; neverManager.Users; never materialized), every candidate still passing theRecipients.Existsdelivery gate, audit detailexpanded, unit tests, and the disclosure note indocs/handbook/peering.md.Receiver-side enablement (files-bao, applied + verified). Keycloak client
cairn-bao-groups(confidential, service accounts, realm-management view-users + query-groups only —manageoff);auth.groupsblock added to the untrackedcairn-openbao-configSecret; rollout restart; toggle on. Result: enc's picker listsnikola-testunder "Listed recipients at this peer". Side effects on bao: live group resolution (#96) and the #129 group-only-receivers warning resolved.Incidental fix (documented). The Mac had no kubeconfig — kubectl defaulted to
localhost:8080(connection refused). Admin kubeconfig pulled from Pi1 (/etc/rancher/k3s/k3s.yaml, server rewritten to192.168.10.173), now at~/.kube/configmode 600; procedure documented in Wiki.jshomelab/k3s→ "Cluster access from the Mac" (the read-onlymcp-viewerkubeconfig cannot read Secrets by design).Remaining (tracked, not blocking): LDAP group-member resolver — LDAP instances degrade to free-text; implement a group-entry
memberlookup when an LDAP deployment needs it. Cut a fresh issue if/when that becomes real.Also recorded in the repo wiki working log (2026-08-20 entry).