Skip to content

refactor(apple/macos): Use System Extension packaging mode for macOS Network Extension - #7344

Merged
jamilbk merged 1 commit into
mainfrom
feat/standalone-macos-app
Dec 4, 2024
Merged

jamilbk merged 1 commit into
mainfrom
feat/standalone-macos-app

Conversation

@jamilbk

@jamilbk jamilbk commented Nov 14, 2024

Copy link
Copy Markdown
Member

To allow macOS users to rollback, it would be helpful to distribute a standalone macOS app, similar to how we distribute the GUI client.

The first step in this process is to refactor the macOS client to use a System Extension -based Network Extension rather than an App Extension based one. This offers us the flexibility to distribute the macOS client outside the Mac App Store in addition to via the store.

For this PR I focused on making the minimal set of changes necessary to support this change. This PR intentionally doesn't update the CI pipeline to notarize and attach a standalone bundle that will run ad-hoc on other Macs. That will come in a subsequent PR.

One thing to note about System Extensions is that they're slightly more finicky when it comes to getting the signing and packaging right. Thus, the README.md is updated to account for the gotchas involved in developing System Extensions locally.

Related: #7071.

@vercel

vercel Bot commented Nov 14, 2024

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
firezone ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 4, 2024 5:21am

@thomaseizinger

Copy link
Copy Markdown
Member

Can we start this one in CI to do some smoke testing against staging for example?

@jamilbk

jamilbk commented Nov 15, 2024

Copy link
Copy Markdown
Member Author

Can we start this one in CI to do some smoke testing against staging for example?

No because it's still a network extension. We would need a replacement glue wrapper that uses the UNIX utun api.

@jamilbk
jamilbk force-pushed the feat/standalone-macos-app branch from 04fd5f9 to 2276913 Compare November 25, 2024 21:51
@jamilbk
jamilbk force-pushed the feat/standalone-macos-app branch from 6d2fda9 to aa5245c Compare December 1, 2024 15:49
@jamilbk
jamilbk force-pushed the feat/standalone-macos-app branch from aa5245c to 508700b Compare December 2, 2024 19:02
@jamilbk
jamilbk force-pushed the feat/standalone-macos-app branch from fe1def4 to 92d720a Compare December 3, 2024 20:16
@jamilbk
jamilbk force-pushed the feat/standalone-macos-app branch from 92d720a to ba09fe3 Compare December 3, 2024 22:50
@jamilbk jamilbk changed the title feat(apple): Standalone macOS app refactor(apple/macos): Use System Extension packaging mode for macOS Network Extension Dec 3, 2024
@jamilbk
jamilbk force-pushed the feat/standalone-macos-app branch from ba09fe3 to e146519 Compare December 3, 2024 23:01
@jamilbk
jamilbk force-pushed the feat/standalone-macos-app branch from e146519 to 4038a46 Compare December 3, 2024 23:10
@jamilbk
jamilbk force-pushed the feat/standalone-macos-app branch from 4038a46 to a73dcdb Compare December 3, 2024 23:14
Comment thread swift/apple/Firezone/xcconfig/debug.xcconfig
Comment thread swift/apple/FirezoneKit/Package.swift
options = ["token": token as NSObject]
}

#if os(macOS)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is worth understanding.

Comment thread swift/apple/FirezoneNetworkExtension/Info.plist

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the new entrypoint of the macOS app.

@jamilbk
jamilbk marked this pull request as ready for review December 3, 2024 23:16
Comment thread swift/apple/Firezone.xcodeproj/project.pbxproj

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the meat of it. This class provides a helper to install the SystemExtension which we use when starting the tunnel.

The remaining request functions are lifetime functions called by the system to update us on the status of the installation. Note the last one -- .replace tells the system that we'd like to replace the existing system extension if it's already installed. It will only be replaced if the marketing version of the replacement is newer than the existing extension. This will happen when a user upgrades his/her client, for example.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will only be replaced if the marketing version of the replacement is newer than the existing extension. This will happen when a user upgrades his/her client, for example.

So in order to downgrade, the user needs to completely uninstall Firezone first? Should we document this in the KB?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not exactly sure yet -- need to test how that works on a live system.

We'll be able to test that after the PR for the release builds is finalized. The behavior of the SystemExtension with developer mode enabled and not is quite different unfortunately.

@thomaseizinger thomaseizinger left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to split some of the unrelated stuff out into other PRs, just to make sure we have a minimal commit on main for the change.

What are the follow tasks from this?

  • We need to update firezone.dev/changelog to offer the new downloads.
  • We'll have to start making draft releases on GitHub.
  • Makefile docs for releasing will need to be updated.
  • Update KB on how to downgrade the client might be useful (as far as I understand, you can install an old version but need to remove the current one first?)

Comment on lines +242 to +246
if let error = error {
Log.app.error("\(#function): Installing system extension failed! \(error.localizedDescription)")
} else {
self.startTunnel(options: options)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if let error = error {
Log.app.error("\(#function): Installing system extension failed! \(error.localizedDescription)")
} else {
self.startTunnel(options: options)
}
if let error = error {
Log.app.error("\(#function): Installing system extension failed! \(error.localizedDescription)")
return
}
self.startTunnel(options: options)

Personally, I find that avoiding else makes code more readable because it visually separates happy-path from error paths through differences in indentation.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this just returns from the closure and not the outer function.

@jamilbk jamilbk Dec 4, 2024

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually that shouldn't matter in this case the closure is the final statement anyhow.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A closure should have a scoped control flow so you should be able to early return from that to avoid executing statements after the if.

Comment thread swift/apple/Firezone/xcconfig/debug.xcconfig
Comment on lines +1 to +6
//
// main.swift
// FirezoneNetworkExtension
//
// Created by Jamil Bou Kheir on 11/14/24.
//

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need these headers? Seems pretty redundant with what we know from Git.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're pretty standard in Swift / Xcode (Android too). However they weren't following our convention, so I updated them to do so.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will only be replaced if the marketing version of the replacement is newer than the existing extension. This will happen when a user upgrades his/her client, for example.

So in order to downgrade, the user needs to completely uninstall Firezone first? Should we document this in the KB?

public func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) {
completionHandler?(SystemExtensionError.NeedsUserApproval)

// TODO: Inform the user to approve the system extension in System Preferences > Security & Privacy.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need addressing?

@jamilbk jamilbk Dec 4, 2024

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not strictly required - the user is shown a macOS alert when the sysex is installed, so this might not be necessary.

We may need to handle the edge case where the user dismisses it, then tries to sign in anyway, so this TODO might not be in the relevant place here. Will circle back to it when I get the release / distributable build PR open.

@jamilbk
jamilbk added this pull request to the merge queue Dec 4, 2024
@thomaseizinger
thomaseizinger removed this pull request from the merge queue due to a manual request Dec 4, 2024
@thomaseizinger

thomaseizinger commented Dec 4, 2024

Copy link
Copy Markdown
Member

Removed from queue in case you want to address any of the comments, feel free to re-queue.

@jamilbk jamilbk left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed PR feedback!

Comment thread swift/apple/Firezone/xcconfig/debug.xcconfig

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not exactly sure yet -- need to test how that works on a live system.

We'll be able to test that after the PR for the release builds is finalized. The behavior of the SystemExtension with developer mode enabled and not is quite different unfortunately.

public func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) {
completionHandler?(SystemExtensionError.NeedsUserApproval)

// TODO: Inform the user to approve the system extension in System Preferences > Security & Privacy.

@jamilbk jamilbk Dec 4, 2024

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not strictly required - the user is shown a macOS alert when the sysex is installed, so this might not be necessary.

We may need to handle the edge case where the user dismisses it, then tries to sign in anyway, so this TODO might not be in the relevant place here. Will circle back to it when I get the release / distributable build PR open.

Comment on lines +242 to +246
if let error = error {
Log.app.error("\(#function): Installing system extension failed! \(error.localizedDescription)")
} else {
self.startTunnel(options: options)
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this just returns from the closure and not the outer function.

@jamilbk

jamilbk commented Dec 4, 2024

Copy link
Copy Markdown
Member Author

It would be nice to split some of the unrelated stuff out into other PRs, just to make sure we have a minimal commit on main for the change.

What are the follow tasks from this?

  • We need to update firezone.dev/changelog to offer the new downloads.
  • We'll have to start making draft releases on GitHub.
  • Makefile docs for releasing will need to be updated.
  • Update KB on how to downgrade the client might be useful (as far as I understand, you can install an old version but need to remove the current one first?)

Updated #7071 with a tasklist for tracking the work here.

@jamilbk
jamilbk added this pull request to the merge queue Dec 4, 2024
Merged via the queue into main with commit bd3f912 Dec 4, 2024
@jamilbk
jamilbk deleted the feat/standalone-macos-app branch December 4, 2024 05:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants