
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.
9.6 KiB
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, 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
.
-
Spawn a new thread so that we don't block the main window.
-
Check if we have cached information about a release (version and changelog). If yes, return those immediately.
-
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.
-
Hit the GitHub releases API and get the latest release. Store the current time as the last check time, even if the call fails.
-
Check if the latest release matches
settings["updater_latest_version"]
. If yes, return an empty update report. -
If a new update has been detected, return the version number and the changelog.
-
Add a green bubble in the notification icon, and a menu entry called "New version available".
-
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 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.
- Bump the number of errors we've encountered in a row
(
settings["updater_errors"] += 1
) - Return an update report with the error we've encountered.
- Update the hamburger menu with a red notification bubble, and add a menu entry called "Update error".
- 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 and check for new updates manually, or consult this page 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
- 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.
- 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.
- 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:
- GitHub pages can alter the contents of our main site (https://dangerzone.rocks)
- GitHub releases can serve an older, vulnerable version of Dangerzone, instead of a new update.
- GitHub releases can serve a malicious binary (requires a joint operation from a malicious CA as well, for extra legitimacy).
- GitHub releases can silently drop updates.
- GitHub releases can know which users download Dangerzone updates.
- 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
- 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 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.
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, that follow a similar
approach. I see that there's a Python library (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 that showcases this.
The Update Framework
The Update Framework is a graduated CNCF project hosted by Linux Foundation. It's based on the Thandy updater for Tor. It's not widely adopted, but some of its adopters are high-profile, and it has passed security audits.
It's more of a specification
and less of a software project, although a well-maintained
reference implementation
in Python exists. Also, a Python project (tufup
)
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 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 was another popular updater project for Python, but is now archived.