dangerzone/docs/developer/updates.md
Alexis Métaireau 61c8f2a6ad
Replace the updater_check setting by updater_check_all
This new setting triggers the same user prompts, but the actual meaning of
it differs, since users will now be accepting to upgrade the container image
rather than just checking for new releases.

Changing the name of the setting will trigger this prompt for all users, effectively
ensuring they want their image to be automatically upgraded.
2025-04-22 12:55:44 +02:00

196 lines
9.6 KiB
Markdown

# Update notifications
This design document explains how the notification mechanism for Dangerzone
updates works, what are its benefits and limitations, and what other
alternatives we have considered. It has been adapted by discussions on GitHub
issue [#189](https://github.com/freedomofpress/dangerzone/issues/189), and has
been updated to reflect the current design.
A user-facing document on how update notifications work can be found in
https://github.com/freedomofpress/dangerzone/wiki/Updates
## Design overview
A hamburger icon is visible across almost all of the Dangerzone windows, and is used to notify the users when there are new releases.
### First run
_We detect it's the first time Dangerzone runs because the
`settings["updater_last_check"] is None`._
Add the following keys in our `settings.json` file.
* `"updater_check_all": True`: Whether or not to check and apply independent container updates and check for new releases.
* `"updater_last_check": None`: The last time we checked for updates (in seconds
from Unix epoch). None means that we haven't checked yet.
* `"updater_latest_version": "0.4.2"`: The latest version that the Dangerzone
updater has detected. By default it's the current version.
* `"updater_latest_changelog": ""`: The latest changelog that the Dangerzone
updater has detected. By default it's empty.
* `"updater_errors: 0`: The number of update check errors that we have
encountered in a row.
Previously, `"updater_check"` was used to determine if we should check for new releases, and has been replaced by `"updater_check_all"` when adding support for independent container updates.
### Second run
_We detect it's the second time Dangerzone runs because
`settings["updater_check_all"] is not None and settings["updater_last_check"] is
None`._
Before starting up the main window, the user is prompted if they want to enable update checks.
### Subsequent runs
_We perform the following only if `settings["updater_check_all"] == True`._
1. Spawn a new thread so that we don't block the main window.
2. Check if we have cached information about a release (version and changelog).
If yes, return those immediately.
3. Check if the last time we checked for new releases was less than 12 hours
ago. In that case, skip this update check so that we don't leak telemetry
stats to GitHub.
4. Hit the GitHub releases API and get the [latest release](https://api.github.com/repos/freedomofpress/dangerzone/releases/latest).
Store the current time as the last check time, even if the call fails.
5. Check if the latest release matches `settings["updater_latest_version"]`. If
yes, return an empty update report.
6. If a new update has been detected, return the version number and the
changelog.
7. Add a green bubble in the notification icon, and a menu entry called "New
version available".
8. Users who click on this entry will see a dialog with more info:
* Title: "Dangerzone v0.5.0 has been released"
* Body:
> A new Dangerzone version been released. Please visit our [downloads page](https://dangerzone.rocks#downloads) to install this update.
>
> (Show changelog rendered from Markdown in a collapsible text box)
* Buttons:
- OK: Return
Notes:
* Any successful attempt to fetch info from GitHub will result in clearing the
`settings["updater_errors"]` key.
### Error handling
_We trigger error handling when the updater thread encounters an error (either
due to an HTTPS failure or a Python exception) and does not complete
successfully._
1. Bump the number of errors we've encountered in a row
(`settings["updater_errors"] += 1`)
2. Return an update report with the error we've encountered.
3. Update the hamburger menu with a red notification bubble, and add a menu
entry called "Update error".
4. If a user clicks on this menu entry, show a dialog window:
* Title: "Update check error"
* Body:
> Something went wrong while checking for Dangerzone updates:
>
> You are strongly advised to visit our [downloads page](https://dangerzone.rocks#downloads) and check for new updates manually, or consult [this page](https://github.com/freedomofpress/dangerzone/wiki/Updates) for common causes of errors . Alternatively, you can uncheck "Check for updates", if you are in an air-gapped environment and have another way of learning about updates.
>
> (Show the latest error message in a scrollable, copyable text box)
* Buttons:
- Close: Return
## Key Benefits
1. The above approach future-proofs Dangerzone against API changes or bugs in
the update check process, by asking users to manually visit
https://dangerzone.rocks.
2. If we want to draw the attention of users to immediately install a release,
we can do so in the release body, which we will show in a pop-up window.
3. If we are aware of issues that prevent updates, we can add them in the wiki
page that we show in the error popup. Wiki pages are not versioned, so we can
add useful info even after a release.
## Security Considerations
Because this approach does not download binaries / auto-updates, it **does not
add any more security issues** than the existing, manual way of installing
updates. These issues have to do with a compromised/malicous GitHub service, and
are the following:
1. GitHub pages can alter the contents of our main site
(https://dangerzone.rocks)
2. GitHub releases can serve an older, vulnerable version of Dangerzone, instead
of a new update.
3. GitHub releases can serve a malicious binary (requires a joint operation from
a malicious CA as well, for extra legitimacy).
4. GitHub releases can silently drop updates.
5. GitHub releases can know which users download Dangerzone updates.
6. Network attackers can know that a user has Dangerzone installed (because we ask the user to visit https://dangerzone.rocks)
A good update framework would probably defend against 1,2,3. This is not to say
that our users are currently unprotected, since 1-4 can be detected by the
general public and the developers (unless GitHub specifically targets an
individual, but that's another story).
## Usability Considerations
1. We do not have an update story for users that only use the Dangerzone CLI. A
good assumption is that they are on Linux, so they auto-update.
## Alternatives
We researched a bit on this subject and found out that there are update
frameworks that do this job for us. While working on this issue, we decided that
integrating with one framework will certainly take a bit of work, especially
given that we target both Windows and MacOS systems. In the meantime though, we
didn't want to have releases out without including at least a notification
channel, since staying behind on updates has a huge negative impact on the
users' safety.
The update frameworks that we learned about are:
## Sparkle Project
[Sparkle project](https://sparkle-project.org) seems to be the de-facto update
framework in MacOS. Integrators in practice need to care about two things:
creating a proper `Appcast.xml` file on the server-side, and calling the Sparkle
code from the client-side. These are covered in the project's
[documentation](https://sparkle-project.org/documentation/).
The client-side part is not very straight-forward, since Sparkle is written in
Objective-C. Thankfully, there are others who have ventured into this before:
https://fman.io/blog/codesigning-and-automatic-updates-for-pyqt-apps/
The server-side part is also not very straight-forward. For integrators that use
GitHub releases (like us), this issue may be of help:
https://github.com/sparkle-project/Sparkle/issues/648
The Windows platform is not covered by Sparkle itself, but there are other
projects, such as [WinSparkle](https://winsparkle.org/), that follow a similar
approach. I see that there's a [Python library (`pywinsparkle`)](https://pypi.org/project/pywinsparkle/)
for interacting with WinSparkle, so this may alleviate some pains.
Note that the Sparkle project is not a silver bullet. Development missteps can
happen, and users can be left without updates. Here's an [example issue](https://github.com/sparkle-project/Sparkle/issues/345) that showcases this.
## The Update Framework
[The Update Framework](https://theupdateframework.io/) is a graduated CNCF
project hosted by Linux Foundation. It's based on the
[Thandy](https://chromium.googlesource.com/chromium/src.git/+/master/docs/updater/protocol_3_1.md)
updater for Tor. It's [not widely adopted](https://github.com/sparkle-project/Sparkle/issues/345), but some of its
adopters are high-profile, and it has passed security audits.
It's more of a [specification](https://github.com/sparkle-project/Sparkle/issues/345)
and less of a software project, although a well-maintained
[reference implementation](https://github.com/sparkle-project/Sparkle/issues/345)
in Python exists. Also, a [Python project (`tufup`)](https://doc.qt.io/qtinstallerframework/ifw-updates.html)
that builds upon this implementation makes it even easier to generate the
required keys and files.
Regardless of whether we use it, knowing about the [threat vectors](https://theupdateframework.io/security/) that it's protecting against is very important.
## Other Projects
* Qt has some updater framework as well: https://doc.qt.io/qtinstallerframework/ifw-updates.html
* Google Chrome has it's own updater framework: https://chromium.googlesource.com/chromium/src.git/+/master/docs/updater/protocol_3_1.md
* Keepass rolls out its own way to update: https://github.com/keepassxreboot/keepassxc/blob/develop/src/updatecheck/UpdateChecker.cpp
* [PyUpdater](https://github.com/Digital-Sapphire/PyUpdater) was another popular updater project for Python, but is now archived.