| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Review 2.2: swap_document forgot every signature. After dropping
changes taken unjudged (or a rejoin into the same epoch), the node
could vouch for none of the changes it kept: under signatures required,
those building on an actor waited for that actor's next signature, and
for an actor that stopped writing, forever.
Signatures::kept_for keeps the signatures of the changes the new
document holds, checks each actor's latest again, and keeps those
waiting for their changes. This node's replayed changes are signed at
once, as before.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review finding 5: vouching by (actor, seq) isn't bound to a hash. A
member can make a change under another member's actor at a seq that
member's signature covers. The change is then taken as vouched, and if
it breaks a rule, the honest member is refused as a whole and logged as
the one whose own change broke the rules.
A signature now vouches for its change and the changes of its actor it
builds on: in the document, as before; among received changes, those
it reaches through the batch's dependencies. A received change under an
actor at a seq whose change the document holds (a signature verified up
to it) is a fork: refused as forged, never as its actor's node's, not
told back in REFUSALS, and logged with the peer that brought it.
|
| |
|
|
|
|
|
| |
a_swarm_judging_every_change waited for c's forged write to be refused
by a, b and d each. The first to judge it tells c, which drops it before
the others may have it: wait for a refusal on any of them, then for c to
drop it.
|
| |
|
|
|
|
|
|
| |
In a_strict_swarm and sealed_values, c's forged writes were waited for
to be refused by a and by b. Whichever judges them first tells c, which
drops them before the other may have judged them (it waits for their
signature, or never gets them): wait for a refusal on either, then for
c to drop them.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
A node refusing a member as a whole sent it nothing, and lifted that
only when a change of the member's was taken again. But a member that
dropped what was refused (REFUSALS) starts its sync sessions over, and
a fresh session sends no changes until the other side answers: the two
waited for each other, and the member stayed refused.
The refusal now notes the member's run and epoch_seq when it began; a
member with another document since (it dropped what was refused, or
rejoined) is synced with again.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A switching or rejoining node replays late writes as fresh changes
naming each write's user, and nothing judged them before they left: a
replay every node would refuse (a revoked capability, a second
envelope at a signed path, readers changed) was refused everywhere,
and with it everything the node wrote after it.
Under a policy, replay_newest now judges each group it commits as
every node will (rules::judge_held, in the new epoch); one they would
refuse is taken back (forked at the heads before it, same actor) and
its paths quarantined with the reason. replay_newest takes the epoch.
Review finding 3 (remake replays).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The API checks the path a request asks for; the rules judge everything
the change does. Where they disagreed, the honest node's change was
refused by every other node, and with signatures required the node was
refused as a whole. The plan had both gates run the same judgement.
Under a policy, each change a node makes (its own writes and those
through the API) is now judged by rules::judge as receivers will judge
it, before it is signed or sent; one every node would refuse is rolled
back (the document forked at the heads before it, same actor: the
change never left) and the request refused with 403 (StoreError::
Refused). rules::judge_held, judging a change the document holds,
moves from swarm::refusals to access::rules for both.
The simulations that play a member past its own checks write with a
helper that bypasses them (forge).
Review finding 3 (the same judgement at both gates).
|
| |
|
|
|
|
|
|
|
|
| |
POST /admin/quarantine/{id}/replay needs write on _continuum, which
never covers user data, and replayed every path of the entry as the
user who asked: every node refused the user data in it. It now
replays only the paths the user may write and keeps the rest in the
entry (left), for one who may; 403 if the user may write none.
Review finding 3 (quarantine replays).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Two writes the API lets a user make were refused by the rules:
* DELETE /peers/{pk}, with write on _continuum/forgotten alone, writes
the forget record and removes the member's entry in one change; the
rules wanted write on the entry. Removing an entry is now also taken
in a change forgetting that member, from a user who may forget it.
* DELETE /admin/users/{name}/tokens by a user who manages users writes
_continuum/logouts/<name>; the rules wanted write there. One who may
write _continuum/users may now write anyone's logouts entry.
Review finding 3 (DELETE /peers, revoking tokens).
|
| |
|
|
|
|
|
|
|
|
|
|
| |
A write makes the maps on its way where there are none
(store::write_path); the rules judged each made map as a write at its
own path, so a user whose capability lies below a map not made yet
(write on servers/eu, a first PUT at servers/eu/0) had its first write
taken by its node and refused by every other. A map made where there
was none, in a change writing within it, is now judged by what is
written within it.
Review finding 3 (parent maps created on the way).
|
| |
|
|
|
|
|
|
|
|
|
| |
Under a strict policy the rules read user data: the value at a signed
place (prev) and what holds one. A batch judged at its frontier missed
a signed write earlier in the same batch, so two successive signed
writes at one path received together were refused on some nodes and
taken on others. A change at, within or above a signed path now goes
in before the next is judged.
Review finding 2.
|
| |
|
|
|
|
|
|
|
|
| |
The rules read the policy at the change's dependencies only for strict
mode, so history from before the policy was judged by today's rules
when a late node caught up, or when REFUSALS had it judged again. A
change written while the document had no policy, or one this node
doesn't know, is now judged by none.
Review finding 6.
|
| |
|
|
|
|
|
|
|
|
| |
A node forgotten swarm-wide while alive cancels its forget record with
a change naming no user; the rules judged _continuum/forgotten by the
registry alone, so every node refused the cancellation and everything
the node wrote after it. A node may now write its own
_continuum/forgotten/<id>, as it may its member entry.
Review finding 4.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A REFUSALS naming the swarm's first change made the node receiving it
judge that change again in a document that isn't empty, refuse it as
"a change without dependencies that isn't the epoch's root", and drop
it with everything built on it: its whole document.
The first epoch's root is now the first change a document holds, as it
comes to an empty one or as it is in one; a change judged again on a
peer's word is never refused for having no dependencies; and
drop_taken refuses to drop an epoch's root, or anything that would
leave nothing kept, logging an error instead.
Review finding 1 (critical).
|
| |
|
|
| |
Flashes are drawn beneath the nodes' labels.
|
| |
|
|
|
| |
messages: off / dots / flash: a message expands at its own speed and
leaves an afterglow fading out in the direction it went.
|
| |
|
|
|
|
|
|
| |
A member writing past its API's checks is refused, told, drops its
change and is taken again; a node under a policy it doesn't know
takes no writes and syncs on.
Auth plan M6 (tests).
|
| |
|
|
|
|
|
|
| |
Opening another member's dashboard passes a token reading only, for
five minutes, in the URL's fragment; its login page begins a session
with it. Tokens may be asked for with a shorter lifetime.
Auth plan M3 (hand-off).
|
| |
|
|
|
|
|
|
|
| |
A node refusing changes a peer brought tells it which broke a rule; the
peer judges them itself, and if its rules refuse them too, drops them
with all built on them (its document forked without them, a fresh
actor), quarantining its own writes among them. Wire version 42.
Auth plan M6 (rejoin on taint).
|
| |
|
|
|
|
|
|
|
| |
With signatures required, a member whose own change broke a rule is
refused as a whole: sent nothing, shown in /peers (refused), in stats
(sync.refusedPeers) and on the dashboard, until a change of its is
taken again or the epoch changes.
Auth plan M6 (diverged standing).
|
| |
|
|
|
|
|
|
| |
The cookie lets in GET, HEAD and the session's own requests; anything
changing something brings the token itself, which no page of another
site or port can make a browser send. The dashboard's writes send it.
Auth plan M3 (browsers).
|
| |
|
|
|
|
|
|
| |
A refused change is remembered by its hash: offered again it is refused
without decoding, judging or logging, so is one built on it, and a peer
offering nothing else is answered later each time, up to a minute.
Auth plan M6 (re-sending).
|
| |
|
|
|
|
|
| |
trust.md: no policy, a policy, strict, sealed values; what members are
trusted with at each step, and the logins.
Auth plan (docs).
|
| |
|
|
|
|
|
|
|
| |
GET /login without authentication: log in by password or with a token
(from a login by key). POST/DELETE /auth/session keep the token as an
HttpOnly, SameSite=Strict cookie; the dashboard sends a browser
without a user there and renews the session while open.
Auth plan M2/M3 (browsers).
|
| |
|
|
|
|
|
| |
Clients checking signatures or sealing read the registry and
_continuum/access: operators grant reading them.
Auth plan M7, M8 (docs).
|
| |
|
|
|
|
|
| |
A value sealed for its declared readers goes everywhere; one sealed
for a stranger too, or plain, gets nothing past the others.
Auth plan M8.
|
| |
|
|
|
|
|
| |
continuum-signed seal and open, with age, for the readers declared or
CONTINUUM_READERS.
Auth plan M8.
|
| |
|
|
|
|
|
|
|
|
| |
cs1.<age file>: a value encrypted with age to its readers' SSH keys,
which no node decrypts. _continuum/access/sealed declares paths whose
values must be sealed, for named readers or anyone; nodes check the
recipients by age's key tags, at the API and on receipt.
Auth plan M8, reworked: sealed values instead of containers, readers
as users only (nodes hold no keys).
|
| |
|
|
|
|
|
|
| |
Signed values everywhere, carried over by a remake and replaced after
it; a member writing the registry or a signed path plain gets nothing
past the others.
Auth plan M7.
|
| |
|
|
|
|
|
|
| |
scripts/continuum-signed (ssh-keygen, curl, jq): login by key, put and
delete signed values, get envelopes, verify one against its user's
keys.
Auth plan M7.
|
| |
|
|
|
|
|
| |
The first users' SSH keys, recorded at genesis with their entries, so a
swarm can be strict from its start.
Auth plan M7.
|
| |
|
|
|
|
|
|
| |
POST /auth/challenge and /auth/login/key: a node-signed challenge,
valid five minutes and taken once, signed with one of the user's SSH
keys (ssh-keygen -Y sign -n continuum-login), for a token.
Auth plan M7 (key login, M2b).
|
| |
|
|
|
|
|
|
|
| |
Inspection gives each operation's kind; under strict every node
refuses, at a signed place, anything but an envelope that checks and
replaces the one there (deleting first as replacing does), anything
within one, and above one anything replacing what holds one.
Auth plan M7.
|
| |
|
|
|
|
|
|
|
| |
Under a policy, a change by a node that isn't a member is refused with
all built on it: a node's registration is written before any write of
its own or through the API, not after (the registration itself
exempt).
Auth plan M6 (fix).
|
| |
|
|
|
|
|
|
|
| |
PUT with application/continuum-signed writes an envelope where values
are signed (403/409 saying why); reads open envelopes and name the
signer (x-continuum-signed-by), ?raw=true shows them. Under strict,
unsigned writes reaching a signed value and the users API are 409.
Auth plan M7.
|
| |
|
|
|
|
|
|
|
| |
"values": "signed" (strict, requiring signatures too): the registry,
_continuum/access, bans and the paths in _continuum/access/userSigned
(* for any key) hold values their users signed. Envelopes read as the
values they hold where the node reads protocol data.
Auth plan M7.
|
| |
|
|
|
|
|
|
| |
_continuum/logouts/<name> holds when a user's tokens were last revoked,
written by the user itself or by one who may manage users, so users'
entries can be signed by their managers alone.
Auth plan M7.
|
| |
|
|
|
|
|
|
|
| |
access::envelope: cv1.<payload>.<signature>, the payload naming the
path, value, user, time and the envelope it replaces (prev), signed
with one of the user's SSH keys from the registry (keys). Checked
against the registry as the writer saw it.
Auth plan M7.
|
| |
|
|
|
|
|
| |
access::sshsig: OpenSSH's SSHSIG, verified and made natively (Ed25519,
sha512/sha256), checked against OpenSSH's own test vector.
Auth plan M7.
|
| |
|
|
| |
future-work brought up to date with M6.
|
| |
|
|
|
|
|
|
| |
_continuum/banned/<node ID>: every node refuses the banned node's
changes from then on (judged as the ban stood) and leaves its
handshakes unanswered.
Auth plan M6.
|
| |
|
|
|
|
|
|
| |
A swarm judging every change: honest writes everywhere, a joining node
registers, a forged write stays where it was made; a compaction under
the policy refuses nothing honest.
Auth plan M6.
|
| |
|
|
|
|
|
|
| |
"signatures": "required": a change counts only once a signature by its
committer vouches for it, and waits until then; every node then signs
every change it makes.
Auth plan M6.
|
| |
|
|
|
|
|
|
| |
_continuum/access/policy ({"version": 1}) has every node screen every
change it receives, on every path changes come in. A policy a node
doesn't know stops its writes through the API. GET /admin/refused.
Auth plan M6.
|
| |
|
|
|
|
|
|
|
| |
access::screen: a batch of received changes judged change by change,
those that change what rules read applied before the next is judged;
a refused change stays out with whatever builds on it, kept aside for
operators; changes missing dependencies wait.
Auth plan M6.
|
| |
|
|
|
|
|
|
| |
A remake's replay writes each value as the user who wrote it,
splitting where several users wrote under a path, so replays are
judged as the original writes.
Auth plan M6 (remakes, 2.4).
|
| |
|
|
|
|
|
| |
Forget records and released quarantines name the user who made them,
so they are judged by its capabilities.
Auth plan M6.
|
| |
|
|
|
|
|
|
|
|
| |
access::rules::judge: a change judged as its writer saw the document
(at its dependencies): committer a member or registering itself;
members' entries and votes their own; epochs and swarm ID from roots;
a remake's decision by its coordinator; the rest by the registry for
the user the change names.
Auth plan M6.
|
| |
|
|
|
|
|
|
| |
document::inspect: each change's paths read from its operations before
it is applied, the same on every node: an object's place as made, cut
at lists and text; unknown objects at the root.
Auth plan M6.
|
| |
|
|
|
|
|
| |
Finding the paths changes touch, from documents and from raw change
bytes, measured before M6 is built.
Auth plan M6 (measure first).
|