Instead of repairing downgrades, automatic migration could fail.
This occurs when attempting to load signed references, and a downgrade
is detected while coalescing `RepositoryInfo`.
Similarly to how fetch recovers, automatic migration must also recover
by detecting for the downgrade error, and allowing the migration to
happen in this case.
Teach the `rad sync` and `rad clone` commands to accept the
`--signed-refs-feature-level` option. This option configures that
fetch to use the minimum feature level provided when fetching from
other nodes. This overrides the value of
`node.fetch.signedReferences.featureLevel.minimum`, and should only be
used in scenarios where it is necessary to downgrade the security of a
fetch for backwards compatibility.
To achieve this, the `Handle::fetch` method introduces an optional
parameter for threading through the `FeatureLevel`.
The configuration of the fetch process was only achieved at the setup
of the of the node.
This limits the ability to pass configuration options to the fetch
worker while the node service is running.
This change introduces `radicle_protocol::fetcher::state::FetchConfig`
to allow configuration options to be kept track of. This includes the
previous `timeout` option, but now also includes the
`radicle_fetch::Config`.
The service now produces the `FetchConfig` and passes it through to
the fetch worker.
After feature levels are introduced to Signed References, and downgrade
attack protections are implemented, new errors are encountered when
reading Signed References that are in a downgraded state.
Such downgrade errors are recoverable by advancing history in a way that
restores the feature level that the history requires.
However, the implementation in `radicle-fetch` must be adjusted to not
abort fetching on downgraded histories, but rather inspect the error in
this case and decide whether continuing the fetching process would
actually help recover from the downgraded state.
The two cases are treated slightly differently for non-delegates and
delegates. Since delegates are crucial for evaluating repository
identity, also the fact that they cannot recover from a downgraded state
is logged at the "info" level.
Add a configuration knob `node.fetch.signedReferences.featureLevel.minimum` to
allow node operators to enforce more strict verification when fetching
Signed References.
In order to allow automatic migration to the feature level 'parent',
introduce a way to force writing signed references, even if the
user-controlled refs are unchanged.
To detect the special case of a Signed References history with a single
and root commit (which would otherwise be detected as feature level
'parent', even though the `refs/rad/sigrefs-parent` ref cannot be
written), keep information about the parent commit in `SignedRefs`.
The output of `rad inspect --sigrefs` is changed to match the more
strict interpretation of the feature level for histories with a single
and root commit.
In hopes of nudging users to upgrade, print a warning if older feature
levels are encountered. However, only do this for delegates, as these
are more critical for the repository, to not log too many warnings.
In order for users to be able to inspect the feature level of Signed
References in a repository, have `rad inspect --sigrefs` attempt to
load Signed References, and, if successful, print the feature level.
To prevent from inability to write sigrefs in case of a verification
error, swallow verification errors about the head commit, log, and
carry on writing.
In order to respond to the caller with the correct feature level in
case sigrefs are to be saved but unchanged, the write side needs to
care about feature levels. This is done by sharing more code with the
read side.
To be able to reason about backwards compatibility, downgrade attacks,
and future configurable enforcement of minimum required (security)
features, introduce the concept of a "feature level" in Signed
References.
In this implementation feature levels are assumed to be monotonic, i.e.,
that higher feature levels behave like lower ones, and only add
semantics on top.
Implementing this as an `enum` that is `Ord` flows nicely.
Treatment of the write-side is left for the next commit. Currently,
the write side "promises" `FeatureLevel::Parent`, but is not able
to "keep the promise", since it does not actually inspect the
feature level of the head in case it is unchanged.
CHANGELOG
The scenario A₁ ← B₁ ← A₂ ← B₂ would previously have resulted in A₁
being loaded. This is undesriable, since the latest non-replayed commit
is B₁.
The information regarding the order of duplicates, which is preserved by
`seen`, can be used to recover the first commit.
Due to a bug introduced in `df8e4e6c88a8bfb6c1ec6b07dcda64093b477cbe`, IPv6
addresses in the configuration file might have ended up in the database
tagged with `type = "dns"`, which is incorrect. The bug was fixed in
`a2e72b48e79d090d33f6c13c485239947de0522e`.
However, Radicle 1.7.0 was released in between those two versions, so a
this migration to repair the entries is added.
Tests are added in `test::migration_8` module. This requires some test
setup, mainly a `Database::memory_up_to_migration` constructor that is only
available to test code.
More sigrefs on the network than previously thought actually are missing
the reference `refs/rad/root`. Revert the change to make this an error,
which was part of commit `d3bc868e84c334f113806df1737f52cc57c5453d`.
IPv6 addresses are already persisted in `config.json` files.
The parsing of these old addresses, e.g. `FE80::0202:B3FF:FE1E:8329:5976` should remain valid.
Emit a warning when they are found.
The output of the address will still enclose the host in `[]`.
Previously, trying to load `SignedRefs` for any given remote would
result in a fetch failure.
Teach the fetch to be more resilient by pruning remotes that result in
an error when loading `SignedRefs`, and add the error to the
validation failures.
Split up signed references into its read and write components.
On the write side:
- Preserve the old behavior of writing references to the blob `/refs`
and sign over the blob.
- Ensure `refs/rad/root` is contained in the `/refs` blob.
- Ensure `refs/rad/sigrefs` is *not* contained in the `/refs` blob.
- Introduce a new (internal) reference `refs/rad/sigrefs-parent`
so that no two `/refs` blob are equal, even if they contain
the same set of (non-internal) refs.
On the read side:
- Preserve the verification of the signature in `/signature` and
the reference `refs/rad/root` (if present).
- Fail verification of `refs/rad/root` is not present.
- Protect against replay attacks by walking the history of the
head of `refs/rad/sigrefs`, skipping interpretation of `/refs`
blobs in case they are identical to a previous `/refs` blob.
This is achieved by searching for repeated contents of the
`/signature` blob.
The reference `refs/rad/sigrefs-parent` is never read from or written to
the Git repository in storage.
The pre-existing implementation of signed references did not include
a nonce, thus duplicate but legitimate sets of references could not
be distinguished from maliciously replayed sets of references.
The new implementation uses `radicle-git-metadata` which is moved from
`dev-dependencies` to `dependencies`.
In issue
29c6c6fc8171287faa0079798ba2d6e3e7fd86f3
was noted that it would be nice to use value parsers for the timeouts in
the CLI.
This patch implements this.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
Co-Authored-by: Fintan Halpenny <fintan.halpenny@gmail.com>