Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notify when updates are available (Mac & Windows) #189

Closed
deeplow opened this issue Aug 17, 2022 · 8 comments · Fixed by #466
Closed

Notify when updates are available (Mac & Windows) #189

deeplow opened this issue Aug 17, 2022 · 8 comments · Fixed by #466

Comments

@deeplow
Copy link
Contributor

deeplow commented Aug 17, 2022

Notify the user when another release is available.

Evidence / Notes

On Windows and Mac, given the distribution method, there is currently no way for the user to be automatically informed about updates

@deeplow deeplow changed the title Notify when updates are available (Mac) Notify when updates are available (Mac & Windows) Aug 17, 2022
@deeplow
Copy link
Contributor Author

deeplow commented Aug 18, 2022

ProtonVPN has a built-in update process. Maybe for mac we'd want to take inspiration from their implementation. Or maybe this is something build-in to macOS. Will have to investigate.

proton

update: this is done with the help of the Sparkle Project.

@keywordnew
Copy link
Contributor

I suggest that disabling update checks should prevent a built-in update feature from creating network requests, in addition to not displaying notifications that an update is available.

This flow would account for the usecase where users prefer using a package manager, like Brew, to handle updates and rely on firewalls to control outbound network requests. A built-in update feature that triggers the firewall, despite the update feature being disabled, would be avoidably noisy and may create concern.

@sssoleileraaa sssoleileraaa added this to the 0.5.0 milestone Jun 1, 2023
@apyrgio
Copy link
Contributor

apyrgio commented Jun 21, 2023

@deeplow mentioned something interesting, that I was not aware of. There's a class of software, like Sparkle Project, that deals solely with safely updating an already installed application. So, before diving into how can Dangerzone notify users for updates, it would help laying down what's the current state with these update frameworks:

Update Frameworks

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: sparkle-project/Sparkle#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.

@apyrgio
Copy link
Contributor

apyrgio commented Jun 22, 2023

Integrating with an update framework will certainly take a bit of work, especially given that we target both Windows and MacOS systems. In the meantime though, we can't have releases out without including at least a notification channel, since staying behind on updates has a huge negative impact on the users' safety.

So, we can keep this issue scoped to just the notification part, and punt the problem of installing updates to a subsequent release.

Here's a suggestion on how we can proceed.

Design overview

EDIT 2023-07-19: After some discussions, which you can read below, and attempts to implement this update notification scheme, we decided to make some changes to the original design. One of those is to move away from the intrusive dialog pop-up route and instead introduce a hamburger menu that will allow notifications. The following design overview reflects these discussions.

This feature introduces a hamburger icon that will be visible across almost all of the Dangerzone windows. This will be used to notify the users about updates.

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": None: Whether to check for updates or not. None means that the user has not decided yet, and is the default.
  • "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_version": "": 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.

Note:

  • If on Linux or Homebrew, make "updater_check": False, since we normally have other update channels for these platforms.

Second run

We detect it's the second time Dangerzone runs because settings["updater_check"] is not None and settings["updater_last_check"] is None.

Before starting up the main window, show this window:

  • Title: Dangerzone Updater

  • Body:

    Do you want to be notified about subsequent releases?

    By clicking on "Yes", Dangerzone will check GitHub for new releases on startup. By clicking on "No", Dangerzone will make no network requests and won't prompt you for releases.

    If you prefer another way of getting notified about new releases, we suggest adding to your RSS reader our Mastodon feed. For more information about updates, check our wiki page.

  • Buttons:

    • Yes: Store settings["updater_check"] = True
    • No: Store settings["updater_check"] = False

Note:

  • Users will be able to change their choice from the hamburger menu, which will contain an entry called "Check for updates", that users can check and uncheck.

Subsequent runs

We perform the following only if settings["updater_check"] == 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. 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 version of Dangerzone has been released. Please visit our downloads page to install it.

      (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:

      (Show the latest error message in a scrollable, copyable text box)

      You are strongly advised to visit our downloads page and check for new updates manually, or consult our wiki 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.

    • Buttons:

      • OK: Return

@apyrgio
Copy link
Contributor

apyrgio commented Jun 22, 2023

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. If a user initially does not want to enable update checks, but then changes their mind, they need to visit our wiki page to find out how.
  2. 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.
  3. We don't have a way to remind users about a new Dangerzone release, say after a week from the previous notification, unless they click on "Remind me later".

@deeplow
Copy link
Contributor Author

deeplow commented Jun 22, 2023

Thanks for digging into this. It certainly feels a bit like reinventing the wheel. But from your research nothing seems to be as simple as this solution. So I think I prefer it.

Fallback mechanism

Hit the GitHub releases API and get the latest release.

Fingers crossed that that API never ever changes. We should probably have our own fallback URL (even if generally inactive), just in case the user ever gets anything other than 200 OK. Considering Github outages, we may not want to rely fully on them.

Strict version increase checking

Check if the latest release matches settings["updater"]["latest_version"]. If yes, return.

GitHub releases can serve an older, vulnerable version of Dangerzone, instead of a new update.

Let's probably check if it's superior. Just as a sanity check?

Check interval?

We probably don't need to check every time the app starts. Maybe we a last_check date setting that only asks if it's hasn't in X amount of time. Otherwise, we are providing github with an uneeded level of "telemtry" of app startups (kind of apple-style).

Way to change setting

If a user initially does not want to enable update checks, but then changes their mind, they need to visit our wiki page to find out how.

Why not just add a discrete button to the bottom of the "select suspicious docs" window?
deeee

Other notes

GitHub pages can alter the contents of our main site (https://dangerzone.rocks/)

I think it's under FPF's control at the moment. But still any push will be mirrored so that probably still applies.

@eloquence
Copy link
Member

Thanks much for the detailed research and thoughts :)

  • Agree with keeping installation of updates out of scope.
  • I'd suggest pointing to https://fosstodon.org/@dangerzone (RSS: https://fosstodon.org/@dangerzone.rss) instead of the GitHub feed as a way for end users to stay up-to-date. We're able to include non-release announcements there, and it's generally more human-friendly. (If we migrate servers at a future time, Mastodon supports account migration with redirects.)
  • UX-wise, I'd suggest considering an inline notification (e.g., a green bar "Dangerzone update available [Details]") at the top of the window) over a pop-up, as a way to minimize user disruption. Especially if we check updates in the background after the application has started, initiating a pop-up while the user is in the middle of a task flow can be disruptive.

@apyrgio
Copy link
Contributor

apyrgio commented Jun 22, 2023

Fallback mechanism

Hit the GitHub releases API and get the latest release.

Fingers crossed that that API never ever changes. We should probably have our own fallback URL (even if generally inactive), just in case the user ever gets anything other than 200 OK. Considering Github outages, we may not want to rely fully on them.

I had thought of something similar as well. If I get what you're saying, you talk about serving a file, probably with a different schema, under https://dangerzone.rocks, right? It makes sense, but:

  • https://dangerzone.rocks is hosted on GitHub Pages,
  • the uptime of GitHub API requests from the start of the year till May is ~99% (39 hours of outages in 5 months)
  • we'd need to store the same info in two places

I think this approach will add complexity with diminishing returns.

Strict version increase checking

Check if the latest release matches settings["updater"]["latest_version"]. If yes, return.

GitHub releases can serve an older, vulnerable version of Dangerzone, instead of a new update.

Let's probably check if it's superior. Just as a sanity check?

Doesn't matter much, because the attacker can say that they serve a later release, but in reality the binary may be an older one. There is no way to detect this unless you download the binary.

Check interval?

We probably don't need to check every time the app starts. Maybe we a last_check date setting that only asks if it's hasn't in X amount of time. Otherwise, we are providing github with an uneeded level of "telemtry" of app startups (kind of apple-style).

Pretty reasonable, I agree.

Way to change setting

If a user initially does not want to enable update checks, but then changes their mind, they need to visit our wiki page to find out how.

Why not just add a discrete button to the bottom of the "select suspicious docs" window? deeee

Oh, I hadn't considered touching our UI, before we have a UX discussion! I think what you're suggesting would tie really nice with @eloquence's suggestion for an inline notification in our GUI. I really like that

Other notes

GitHub pages can alter the contents of our main site (https://dangerzone.rocks/)

I think it's under FPF's control at the moment. But still any push will be mirrored so that probably still applies.

Mentioned it above; it's served by GitHub Pages actually.

@apyrgio apyrgio modified the milestones: 0.5.0, 0.4.2 Jun 22, 2023
apyrgio added a commit that referenced this issue Jul 4, 2023
Add a new Python module called "updater", which contains the logic for
prompting the user to enable updates, and checking our GitHub releases
for new updates.

This class has some light dependency to Qt functionality, since it needs
to:

* Show a prompt to the user,
* Run update checks asynchronously in a Qt thread,
* Provide the main window with the result of the update check

Refs #189
apyrgio added a commit that referenced this issue Jul 20, 2023
Add a new Python module called "updater", which contains the logic for
prompting the user to enable updates, and checking our GitHub releases
for new updates.

This class has some light dependency to Qt functionality, since it needs
to:

* Show a prompt to the user,
* Run update checks asynchronously in a Qt thread,
* Provide the main window with the result of the update check

Refs #189
apyrgio added a commit that referenced this issue Jul 24, 2023
Add a new Python module called "updater", which contains the logic for
prompting the user to enable updates, and checking our GitHub releases
for new updates.

This class has some light dependency to Qt functionality, since it needs
to:

* Show a prompt to the user,
* Run update checks asynchronously in a Qt thread,
* Provide the main window with the result of the update check

Refs #189
apyrgio added a commit that referenced this issue Jul 24, 2023
Add a new Python module called "updater", which contains the logic for
prompting the user to enable updates, and checking our GitHub releases
for new updates.

This class has some light dependency to Qt functionality, since it needs
to:

* Show a prompt to the user,
* Run update checks asynchronously in a Qt thread,
* Provide the main window with the result of the update check

Refs #189
apyrgio added a commit that referenced this issue May 23, 2024
Add a design document for the update notifications mechanism, adapted
from the write-up in the original GitHub issue.

Refs #189
apyrgio added a commit that referenced this issue Jun 12, 2024
Add a design document for the update notifications mechanism, adapted
from the write-up in the original GitHub issue.

Refs #189
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants