| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The credentials dialog listed a user's keys whole, to add one or
remove one but the last, and any failure replaced the dialog with the
error, the form lost.
* A key is shown short: its type, the first and last four characters
of what tells it apart (an Ed25519 key's base64 after the part all of
them share), its comment; the whole line in its tooltip.
* A key can be edited, its name and its line: saved under the name, a
new name written first and the old one removed.
* A user with a password on this node (`--htpasswd`) may remove its
last key: it logs in by the password. GET /auth/keys says so
(`password`), and DELETE /auth/keys/{id} takes it then; the rules no
longer keep the last key on every node (no other node can tell who
has a password where; under a strict policy the signed entry keeps
one still).
* What fails says why beside where it was asked (the add form, the
edited row, the removed one), which stays as it was, to be set right
and tried again.
|
| |
|
|
|
|
|
|
|
| |
api.md's Signed changes says what sent, all and none do, what each
costs, and which to choose: sent for nearly every swarm, all where each
change should carry its own proof, none to save signatures where only
protocol data and listed paths need them; and that signatures travel
with their changes, a message carrying none taking none. access.md no
longer says signatures come on their own within a second.
|
| |
|
|
|
|
|
|
|
| |
Review 3, R5: a node given its root (--genesis-root) refused a peer
naming another only change by change while its document was empty,
each with an entry among the refused; the handshake went through, as
there was no root of its own yet to compare with. The root given is
now compared too: a peer naming another is refused at the handshake,
logged once, as one whose document starts from another root.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Review 3, R5: --refused-writes-per-minute bounds each user, but the
cost it guards, a document copy under the write lock, is the node's:
ten users, or ten accounts of one, are ten times the budget.
The node now takes six times a user's limit within a minute in all
(refused::NODE_FACTOR); past it, every user with a write refused
within the minute is answered 429 untried, until the oldest leave it.
A user none of whose writes was refused writes on, so honest writers
aren't held up by others' refusals.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review 3, R4: whether a batch was judged followed the receiving
document's policy now (and whether the batch writes one). After the
policy was removed, or replaced by a version the node doesn't know,
changes written under it and received late went in unjudged, while
nodes that had them earlier judged them; REFUSALS gave up likewise.
Screening now remembers that the document held a policy, known or not
(Screening::policed), and judges what it receives while it does, each
change by the policy at its dependencies; REFUSALS judges then too
(not under a policy the node doesn't know, as before). Not across a
restart, which access.md says.
|
| |
|
|
|
|
|
|
| |
Review 3, R3, the replace side: a replace or import remake writes its
content as it is, at signed places too, so under a strict policy a
member majority can put plain values where envelopes were. access.md
says that remakes are outside the rule; keeping envelopes at signed
places in every kind is postponed to future-work.md.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review 3, R3: 1186 kept honest nodes from proposing or voting for a
strict remake of a swarm strict already, but a majority of members,
each with keys of its own, could still commit one, and switch and
catchup followed it without asking what kind of remake made the
epoch: the registry, policy and bans of a strict swarm changing by a
member majority, the honest minority following.
A node now refuses to switch to such an epoch (its document's policy
strict, the remake's kind strict), and catchup refuses to rejoin one
whose chain of epochs holds a strict remake after the swarm was strict
(as the old document is, or once a remake since made it so). access.md
says so.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review 3, R2: the root pin was set by the first HELLO_REPLY, and the
first sync, a round trip later, took that root; a peer naming another
afterwards was refused as another history. So the seed that answered
first decided, not "none while they disagree", and a malicious seed
makes itself fastest.
A node joining with an empty document of the first epoch now takes no
root (RootPin::Awaiting, roots left for later) until each of its seeds
named one, or ROOT_WAIT (10 s) passed since the first did; then the
one they name, none while they differ, as before. With --genesis-root
it takes that one alone, at once. access.md, the option's help and
example.toml say so; switching roots later, while the joiner holds
nothing of its own, is postponed.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review 3, R1: what a node received was vouched for only while its own
document's policy required signatures. A joiner's first sync, and
every node's message bringing `required`, went in by the rules alone:
a malicious first peer could plant changes under other members' actors
at their next seqs, their real ones then failing as duplicates for
good.
The signatures a message carries are always offered now, and the
screen always has them: a change waits for one where the policy at the
heads it is judged at requires it (rules::Reads::required, read once
per heads), whatever the receiving document holds. Changes written
under a policy not requiring signatures wait for none, as on every
other node; the check for forged changes runs always. Changes waiting
for a signature are judged again whenever signatures come.
The test of signatures required now takes the history into an empty
document; a new one takes changes written under a policy not requiring
them without any.
|
| |
|
|
|
|
|
|
|
|
| |
A write every node would refuse is rolled back by forking the document
at the heads before it, under the write lock, at a cost that grows with
the document's history. A user could ask for such writes in a loop.
Count each user's refused writes over the last minute. Past
--refused-writes-per-minute (default 10), its writes are answered 429
without being tried, until the oldest refusal leaves the minute.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N7, the care it raised: a joining node's empty document took
as the first epoch's root whatever dependency-less change came first,
from whichever peer it joined through; the first epoch's ID is all
zeros, so nothing pinned it. A remake's epoch has its ID, the snapshot
change's hash, which the rules hold its root to.
* Every node names its epoch's root in HELLO and HELLO_REPLY (the
epoch's ID for a remake's, its genesis change for the first; wire
version 45). A node whose document starts from another root of the
same epoch refuses the handshake, logging it: another history under
the swarm's ID, a forged root or a genesis started again.
* A joining node with an empty document takes as its root only the one
it was given (--genesis-root, new), else the one its peers name, and
none while they name different ones (access::screen::RootPin, kept
in Screening across documents): one peer hands it another root only
if it is all the joiner hears, or never, with --genesis-root. A root
refused is listed with the refused, what builds on it with it.
* The genesis node logs the root (`The swarm's root`); GET
/admin/epochs shows it (`root`).
A genesis started again under a remembered swarm ID used to merge with
the nodes of it still running; they now refuse it (another root), and
the docs say to join them with seeds instead.
The epoch's identity stays as it is (generation 0 all zeros): records
and epoch metadata name it so, and every comparison of epochs with
them.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N7: a node screened received changes only while its own
document had a policy it enforces. A joiner's empty document has none,
so its first sync, as much history as the node it joins through chose,
was applied unjudged, and under signatures required unvouched; the same
for any node while the policy's own change arrived with later ones.
Sync messages, deltas and downloaded changes are now always screened,
each change judged by the policy at its dependencies (1135), so the
verdicts are the swarm's. While neither the document nor the changes
hold a policy (none touches a "policy" key: inspect::touch_key), only
an epoch's root is judged and nothing is placed: bench_screening, after
1188, measured placing everything at about as much again as applying
(564 ms + 543 ms for 100 000 changes), judging by the rules at about
five times (+2.5 s), which a document with a policy pays anyway.
access.md no longer lists the joiner's first sync, nor nodes agreeing
only as the policy reaches them (judged at the dependencies, they
agree), as left to do; changes::load_delta is for tests now.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N9: the API checked a write's capability on the path asked
(servers/list/3/name), the rules on the list it is in (servers/list):
a user whose capability lay inside a list had every such write pass the
API and refused by the node's own judgement (403, after rolling the
document back).
inspect::placed places a write's path in the document by the rule the
inspector places operations by (Place::child: cut after the first list
or text on the way), and the store's writes check capabilities there.
One rule for both; a test checks placed against the paths the inspector
finds in the change such a write makes.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N2: nothing checked that a strict remake's base wasn't strict
already. In a strict swarm the registry, the policy and the bans change
only as a user who may change them signs; a strict remake committed by
a majority of members, each with keys of its own, could drop any of
them (unban, remove managers, empty userSigned and the sealed
declarations), and the honest minority would follow.
A node now takes a strict remake (proposing or voting) only in a swarm
not strict yet: turning strict is done once, by a remake; after it, by
signatures.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N8: a node with one neighbour (a swarm of two, a leaf) never
hears a second member name the change: its big drop was held for good,
an error logged at every REFUSALS. It now waits only where it has
another neighbour than the one telling it; with one, one word is all
there is.
access.md also says what the wait is: it guards against one peer, not
two members saying the same (any node with the swarm key registers),
and never makes a node drop what its own rules take.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N3: 1150 let any member write an abort decision once a remake
had expired a grace period ago, judged by the change's own time, the
writer's word. A member could race the coordinator's legitimate commit
with an abort dated as it liked; both taken, Automerge shows
one, and nodes that acted on the commit split from those that didn't.
The two writers of such aborts are operator requests whose endpoints
already need write on _continuum/remakes: aborting an expired remake
(DELETE /admin/remakes/{id}) and superseding one (POST
/admin/remakes?force=supersede). Both now commit as the user who asks
(the proposal and its vote with them, for a supersede), which the rules
take as one who may write the decision. The clause judging an abort by
its time goes, and with it the time the rules carried. An expired
remake is still aborted through any member, by an operator.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review 2, N1: a strict manifest wasn't bound to its base. Any member
could propose an old, legitimately signed one again at the current
heads; nodes would verify it and vote, and every value at a signed
place changed or added since (a ban, a new user, a rotated key, a
changed userSigned or sealed declaration) would be deleted, nothing
after the base being left to replay.
The manifest now names the epoch and the heads it was made at, and a
node takes it (proposing or voting) only for a remake of that epoch
with exactly that base. GET /admin/strict-manifest makes it so;
continuum-signed strict proposes at those heads already.
|
| |
|
|
|
|
|
|
|
| |
Review 2, N11: a value at _continuum/logouts/<name> that isn't a time
(read as damaged: every token revoked), or one far ahead, refuses every
token of the user, new ones too, until it is replaced. Any member can
write it (finding 10). The rules can't see the value (only that it is
written), so access.md says it, and how it is set right: the user logs
out again by password, or one who manages users revokes its tokens.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A strict remake takes a majority of members whose --strict-keys take
its manifest's signer: no node votes for one without, so
--follow-strict-remakes (1173) goes. A node that doesn't vote still
switches once the others commit, as with any remake.
So that an operator can tell why one fails: a strict remake aborted
for want of a majority (the voting period over, or a majority out of
reach) says in its reason (GET /admin/remakes/{id}, and the
coordinator's log) which members didn't vote, and that a member votes
for one only with its manifest signed by a key of its --strict-keys.
Each of those logs why itself (`Not voting for a strict remake`).
The sim a_strict_remake_too_few_take: only a takes the manifest; the
remake aborts, naming b and c.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Until a swarm is strict, the user a change names is its node's word, so
any member could have planted registry entries, policy, bans or
user-signed values as any user, plain or signed by a key it put there
itself; writing the strict policy trusted all of it as it was.
A remake of a new kind, `strict`, turns a swarm strict instead:
* Its content is a manifest (access::strict): the swarm, the strict
policy, and each value kept at the places values are signed, by the
SHA-256 of its JSON, signed by an operator (ssh-keygen -Y sign -n
continuum-strict).
* Every node builds the next epoch as compaction does, keeping at the
signed places only the values the manifest names, as they were at
the base, and writing its policy (RemakeKind::protocol, the one kind
that changes protocol data). Writes after the base reach the new
epoch only by replays, judged by the strict policy.
* A node votes for one only with its manifest signed by one of
--strict-keys, keys given out of the document; without any, for none
(logged once, `Not voting for a strict remake`), unless
--follow-strict-remakes. A coordinator proposes only what it would
vote for (403). Once a majority commits one, every node switches, as
with any remake: refusing to switch would only cut a node off.
* GET /admin/strict-manifest gives the manifest keeping everything held
now, the values for review and the heads they are at;
continuum-signed strict shows them, signs once confirmed and proposes
at those heads.
The sim a_swarm_turning_strict_through_a_remake: a member plants a
registry entry as a manager; the operator's manifest leaves it out; a
and b, given the key, commit; c, without, follows; the entry is gone
and the policy strict everywhere.
|
| |
|
|
|
|
|
|
| |
A user managing its own keys (1168) could remove the last, and then
neither sign nor log in by key. It can't now: DELETE /auth/keys/{id}
of the last answers 409, the rules take a user's change of its own keys
only with one left after it, and under a strict policy its own entry
signed only with one key at least. One who may manage users still may.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A node told (REFUSALS) that it holds a change the swarm's rules refuse,
and whose own rules refuse it too, dropped it and everything built on
it on one peer's word, however much that was; its own writes among them
went to the quarantine, the others' nowhere.
* A drop taking more than 100 changes, or more than ten and a tenth of
the document, now
waits until a second member names the same change: the node's rules
and one peer's word could both be off. Logged as an error, counted in
/admin/stats (sync.dropsHeld).
* The others' changes dropped are listed with the refused (GET
/admin/refused, "taken unjudged, dropped"), for operators to see.
The cheap half of asking the others (future-work.md, Telling a
malicious change from a wrong policy).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Only a user who may manage users could change a user's keys: a user
couldn't add a new laptop's key, or remove a lost one.
A user now manages its own keys, whatever else it may do:
* GET /auth/keys: its keys, its entry, and the envelope holding it
under a strict policy (`signed`), to sign against.
* PUT /auth/keys/{id} with an authorized_keys line, DELETE
/auth/keys/{id}: one change, as the user. The rules take a write of
_continuum/users/<name>/keys/… from the user it names.
* Under a strict policy, the user signs its entry with only its keys
changed, with a key it holds already (envelope::check takes that from
the user itself), PUT to its own entry even without write on the
registry; continuum-signed key-add and key-remove do so.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Review 6: a node remembers the challenges used until they expire, 10 000
at most, and refused every login by key once full. One user holding a
key could make 10 000 logins within five minutes and keep everyone else
out.
A user may now log in by key at most --key-logins-per-user times (100)
on a node within a challenge's lifetime, past which that user waits and
others don't; the 10 000 stay as the cap for all users together. The
lifetime, five minutes until now, is --challenge-lifetime.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review findings 10 and 12, and 3.1/3.3: what stays as it is, said.
* Any member can log any user out: the user a logout names is its
node's word, and a logout is no deciding path, even under strict.
* Used challenges are remembered in memory: after a restart, one
captured within its five minutes could be taken once more.
* A registry turns strict as it is: whatever it holds then counts,
plain values and envelopes, checked or not. Look it over first.
* A value that isn't an envelope starts its chain anew (prev empty).
* A strict registry whose only manager lost its keys changes no more:
keep two managers, or two keys.
* The handed-over token stays in the address bar and history where the
other dashboard never loads, for its five minutes.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Review 3.1, part of finding 12: a signed value's payload bound path,
user, time and prev, but not the swarm. A user with the same name and
key in two swarms could have an envelope replayed from one into the
other, wherever the path holds no envelope yet (prev "").
The payload now names the swarm (`swarm`), and a node takes an envelope
only for its own. /auth/whoami says the swarm, and continuum-signed and
the example in api.md sign it. Wire version 44: nodes before this
refuse such envelopes, and those after refuse the old ones.
|
| |
|
|
|
|
|
|
|
| |
Review finding 11: token::verify checked only that the issuer is a
member, and a ban leaves the member entry. A compromised node's tokens
stayed valid after it was banned, until it was forgotten too.
A token issued by a banned node is now refused (BannedIssuer), as soon
as the ban reaches the node judging it.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Review finding 8, second part: opening another member's dashboard
handed it a token reading everything (READ_ALL). A member writes its
own dashboard URL, so a malicious one received a read-everything token
under the operator's identity: /admin/config, /admin/changes,
/admin/log and the rest, on every node the user reaches.
The token handed over now reads the member list only (READ_MEMBERS, as
sent to subscribed nodes), which is enough to draw the map; the user
logs in at the other dashboard to see more. With 1151 it lasts five
minutes and isn't refreshed past them.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Review finding 8, first part: a refreshed token's lifetime came from
the refresh request, and it kept the session's end. So the five-minute
read-all token a dashboard hands to another member's refreshed into a
12-hour one, renewable for seven days.
A token asked to live less (`lifetime`, at login or refresh) now ends
its session with it: nobody refreshes it past its end, its holder
included, and a session begun from it (POST /auth/session) ends then
too.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Under a policy, a remake's decision was taken from its coordinator only.
A node aborting an expired remake whose coordinator is gone
(DELETE /admin/remakes, after abortableAt) was refused by its own
judgement (403), so such a remake could never be decided.
The decision is now also taken from a user who may write it (the
protocol writers), and an abort from any member once the remake expired
a grace period ago (abortableAt, by the coordinator's grace period in
the proposal). The rules have no clock of their own, so that is judged
by the time the change says it was made: the writer's word, so a member
could abort a remake early, as it could ask its coordinator to, but
never commit one. Before then, an abort stays a request the coordinator
applies.
|
| |
|
|
|
|
|
| |
Review 1.3: access.md said a user who may write _continuum/members may
only remove another member's entry, but the rule lets them write any of
it (addresses, dashboard). Those are operators, who may set an entry
right through the store, so the doc now says what the rule does.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Review finding 7, second part: a record carried into a new epoch whole
(proposal and decision in one change) was taken from any member, for
any remake ID. A member could write a forged record, decision included,
for a fresh ID in a single change.
The snapshot's epoch metadata now names the remake's coordinator, as
the quorum built it. The carried-record exception holds only in a
later epoch, only for the remake that made it, and only from the
coordinator its snapshot names, where there is no record yet. Wire
version 43: the snapshot, and so the epoch ID, changes.
The review suggested carrying the record inside the snapshot instead.
The snapshot can't hold the decision, which names the snapshot's own
hash. So the snapshot vouches for who may carry the record instead.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Review finding 7, first part: the protocol-map rules looked at paths,
not actions. Any member could delete _continuum/members, /votes or
/remakes whole, or rewrite another remake's proposal (its coordinator)
and then write its decision.
What holds the members' entries, votes and remake records, and a
remake's record itself, are now made by a member only where there is
none; a proposal is written only where there is none. Replacing or
removing any of them is left to a user who may write there (the
protocol writers). An abort asked for stays any member's.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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 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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
| |
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.
|