macula_group_keys (macula v13.2.1)

View Source

A sealed group's distributor (plans/DESIGN_E2E_SEALED_PUBSUB.md §3, §5): the handler of <org>/group_keys_v1 and the epochs of every group it serves.

The application advertises the handler as any provider advertises one (its mcl_om capability, or macula_response:advertise_direct/7 kept alive with reuse_sup), with advertise_opts/1 merged into its options: the policy {realm_member_required, OrgKeyId, <<"group_keys">>}, so the org's group_keys UCAN is checked by macula before the handler runs, matched to the org key by its key id, AND confidential => required, so the provider's link answers a clear call sealed_required and a key never travels clear. With the node's kem_advertise switched off those options refuse to advertise at all (kem_advertise_disabled), rather than advertise a keyless distributor. The member side refuses a keyless distributor too (macula_group_keyring pulls with confidential => required): each side enforces it on its own. The handler then decides from the caller's wire-authenticated node id: a node in the application's removed set is refused every epoch, and so is a node that is not a live realm member, read from the realm's slot for its endorsement (macula_record:realm_member_endorsement_key/2 and macula_hyparview_endorsement:slot_endorsement/4, which answers withdrawn for the realm's tombstone). Membership is checked at the time of the call, for a past epoch too: a subscriber catching up on one it missed is still a member, and one that is not has no business reading the group.

A call names the group (prefix) and the epoch it wants: current, or a past one by id. The reply carries the group's policy and the epochs: the current one, and the next one too inside the current's ahead window (macula_group_epoch). Epoch keys are kept in memory only. A refusal is {error, Reason}, which macula answers as a sealed provider error with code handler_error and the reason as its detail: not_a_member, membership_unknown (the lookup failed; retry), unknown_epoch, epoch_expired or unknown_group (a prefix this distributor's org does not own).

Summary

Functions

The options the distributor's procedure is advertised with, merged into the application's own: the org's grant, checked before the handler runs, and sealed calls only.

The <org>/group_keys_v1 handler for a distributor, to advertise. It waits for the distributor longer than a membership lookup can take (5 s) and less than a member's pull deadline (15 s), so a slow lookup answers membership_unknown, and no answer is made that no member still waits for.

Start a distributor for org's groups. membership defaults to the realm slot read over pool, which then needs realm, realm_key_id and profile; removed defaults to nobody removed. The application keeps its removed set across restarts: a set lost to a restart re-admits every removed member whose grant still verifies.

Types

options/0

-type options() ::
          #{org := binary(),
            policy := policy(),
            rotate_after_ms => pos_integer(),
            now => fun(() -> integer()),
            membership => fun((<<_:256>>) -> ok | {error, not_a_member | membership_unknown}),
            removed => fun((<<_:256>>) -> boolean()),
            pool => pid(),
            realm => <<_:256>>,
            realm_key_id => <<_:256>>,
            profile => macula_crypto_profile:profile()}.

policy/0

-type policy() :: required | preferred | off.

Functions

handler(Pid)

-spec handler(pid()) -> fun((map()) -> map() | {error, atom()}).

The <org>/group_keys_v1 handler for a distributor, to advertise. It waits for the distributor longer than a membership lookup can take (5 s) and less than a member's pull deadline (15 s), so a slow lookup answers membership_unknown, and no answer is made that no member still waits for.

start_link(Opts)

-spec start_link(options()) -> {ok, pid()} | {error, term()}.

Start a distributor for org's groups. membership defaults to the realm slot read over pool, which then needs realm, realm_key_id and profile; removed defaults to nobody removed. The application keeps its removed set across restarts: a set lost to a restart re-admits every removed member whose grant still verifies.