Page:
OCM Federation
No results
1
OCM Federation
cairn-ci edited this page 2026-09-07 23:31:01 +00:00
Table of Contents
OCM Federation
How Open Cloud Mesh works in Cairn (#117, v0.6.139). Design record:
OCM.md in the repo; operator/user guide: docs/handbook/federation-ocm.md.
The whole flow
flowchart TD
subgraph ADMIN [Operator establishes trust]
A1[Enable Open Cloud Mesh] --> A2[Check server: live discovery,\nproduct + dialect preview]
A2 --> A3[Allow server\npolicy: invited contacts only]
A3 -.->|peers without invites\nvanilla NC, Seafile| A4[Relax to any user\nstamped + audited]
end
subgraph CONTACT [Users establish contact - optional, user-owned]
B1[Generate invite\none-time, 7 days] -->|out of band| B2[Other person pastes it\non their server]
B2 --> B3[POST /ocm/invite-accepted\nsigned, allow-list gated,\ntoken consumed once]
B3 --> B4[Mutual contacts]
end
subgraph SHARE [Share creation]
C1[Share dialog:\nuser at their-server] --> C2[Discover peer:\nwell-known -> ocm-provider\n-> trailing slash]
C2 --> C3[POST peer /shares\nsigned in the peer's dialect:\n9421 or draft-cavage]
C3 --> C4[Recipient sees it in THEIR product\nNC: mounted folder / OpenCloud: shares list]
D1[Inbound POST /ocm/shares] --> D2{allow-list?}
D2 -->|no| D3[403 naming the server]
D2 -->|yes| D4{signature verifies?\nkey from jwks or legacy PEM}
D4 -->|no| D5[401 naming what failed]
D4 -->|yes| D6{policy satisfied?\ncontacts-only -> invited?}
D6 -->|no| D7[403 naming the policy]
D6 -->|yes| D8[Ledger: pending ->\nRequests tab card]
end
subgraph ACCESS [Access - bytes move over WebDAV]
E1[User accepts] --> E2[Browse live through Cairn\nas WebDAV client + bearer]
E2 -.->|explicit only| E3[Save a copy to Personal\nencrypted at rest like any upload]
F1[Peer pulls our share via\n/dav/ocm/providerId/] --> F2[bearer check + subtree jail\n+ read-only unless granted write]
F2 --> F3[reads flow through the live driver:\naudit trail + on-the-fly decryption]
end
subgraph REVOKE [Revocation - always honest]
G1[End share / decline /\nadmin removes server] --> G2[Ledger flips + SHARE_UNSHARED\nnotification best-effort]
G2 --> G3[Grant bearer dies instantly;\nrows collapse to ended-by-owner,\nnever a broken listing]
end
ADMIN --> CONTACT --> SHARE --> ACCESS --> REVOKE
Key properties
- Allow-list gates both directions - checked at every inbound endpoint and before every outbound call. No open federation, no lookup server.
- Strictness is per server: invited-contacts-only by default; relaxation is a stamped, audited act for peers that cannot do invites.
- No key ever crosses the wire: the owning side decrypts on the fly and serves ready bytes over per-share credentials.
- Remote shares are web-UI-only access grants: never mounted into sync trees, never copied without an explicit save.
- Refusals are readable on the wire and in the UI - a failing peer admin sees why (the ocis lesson).
- Verified black-box:
ocm-interop.ymlboots the real binary and drives discovery (both dialects), jwks, inbound share, named refusal, grant jail, and the everything-404s-while-disabled guarantee.ocm-live-nc.ymlis the lab-Nextcloud live test (operator secrets required).
Dev-loop lessons collected on this build
- gofmt realigns struct fields: splice anchors on formatted Go files must be whitespace-tolerant regexes.
- RFC 9421
@target-uribuilt fromreq.URL.String()differs between the signing client (absolute) and the parsing server (path) - sign the request-target. Caught by endpoint tests, invisible to same-object unit round-trips. - The #433 verb scanner caught this very feature's non-literal audit carrier - the tripwire pays for itself.
- NEVER write
${'{'}{'{'} ... {'}'}{'}'}-style Actions expressions inside a run-block heredoc that emits files: the runner templates them before bash runs, which both breaks the emitted YAML and can write an (ephemeral, job-scoped) token into the repo. Emit kept workflows via the MCP file API instead. - The existing i18n block stores umlauts as \uXXXX escapes: anchors against it must match those bytes, not raw UTF-8.