On Apple platforms, `UserDefaults` provides a convenient way to store
and fetch simple plist-compatible data for your app. Unbeknownst to the
author at the time of original implementation was the fact this these
keys are already designed for managed configurations to "mask" any
user-configured equivalents.
This means we no longer need to juggle two dicts in UserDefaults, and we
can instead check which keys are forced via a simple method call.
Additionally, the implementation was simplified in the following ways:
- The host app is the "source of truth" for app configuration now. The
tunnel service receives `setConfiguration` which applies the current
configuration, and saves it in order to start up again without the GUI
connected. The obvious caveat here is that if the GUI isn't running,
configuration such as `internetResourceEnabled` applied by the
administrator won't take effect. This is considered an edge case for the
time being since no customers have asked for this. Additionally, admins
can be advised to ensure the Firezone GUI is running on the system at
all times to prevent this.
- Settings and ConfigurationManager are now able to be removed - these
became redundant after consolidating configuration to the containing
app.
In #9169 we applied MDM configuration from MDM upon _any_ change to
UserDefaults. This is unnecessary.
Instead, we can compare new vs old and only apply the new dict if
there's changes.
In this PR we also log the old and new dicts for debugging reasons.
Adds a new settings/configuration item `startOnLogin` which simply adds
a "Login Item" which starts Firezone after signing into the Mac.
This feature is macOS 13 and above only because otherwise we will need
to bundle a helper application to register as a service to start the
app. Given our very small footprint of macOS 12 users, and how
unsupported it is, this is ok.
When it comes time to implement MDM for this feature, note that Apple
provides a means to enforce login items via the
[`ServiceManagementLoginItems`
payload](https://developer.apple.com/documentation/devicemanagement/servicemanagementmanagedloginitems)
which is outside the scope of `com.apple.configuration.managed`. This
enforces the login item in System Settings so that the user is unable to
disable it.
We also add functionality here, but bear in mind that even if we disable
the Toggle switch in our Settings page, the user could still disable the
item in system settings unless it is being set through MDM via the
service management key above.
Another thing to note is that this setting is applied on the GUI side of
the tunnel only, because it is inherently tied to the process it is
running as. We are not able to (nor does it make sense to) enable the
login item for the tunnel service. This should be fine.
Tested to ensure enabling/disabling appropriately adds and removes the
login item (and re-adds it if I manually remove it from system
settings).
Related: #8916
Related: #2306
---------
Signed-off-by: Jamil <jamilbk@users.noreply.github.com>
When the MDM installs a configuration payload to
`dev.firezone.firezone.network-extension`, the tunnel service will now
be notified of a change to its `managedDict`, applying the configuration
and updating `packetTunnelProvider`'s local copy so that it'll be
returned on the next configuration fetch from the UI.
Related: #4505
For App Store installed macOS clients, it doesn't make sense to run an
update checker, because the system is managing the updates, and will
notify the user if there's an update available for Firezone (the user
has configured the system to manage app updates).
Related: #7664
Related: #4505
On macOS, after upgrading the client, the new system extension fails to
respond to IPC commands until it receives a `startTunnel` call. After
that, subsequent IPC calls will succeed across relaunches of the app.
To fix this, we introduce a dummy `startTunnel` call on macOS that
attempts to bring life into the System extension whenever we receive
`nil` configuration.
We also tidy up a few other things to make this easier to follow.
Fixes#9156Fixes#8476
---------
Signed-off-by: Jamil <jamilbk@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
The settings fields are getting tedious to manage individually, so a
helper class `Settings` is added which abstracts all of the view-related
logic applicable to user-defined settings.
When settings are saved, they are first applied to the `store`'s
existing Configuration, and then that configuration is saved via a new
consolidated IPC call `setConfiguration`.
`actorName` has been moved to a GUI-only cached store since it does not
need to live on `Configuration` any longer.
This greatly simplifies both the view logic and the IPC interface.
Notably, this does not handle the edge case where the configuration is
updated while the Settings window is open. That is saved for a later
time.
If the user tries to start the tunnel from system settings without
launching the GUI after upgrading to >= 1.4.15, the new configuration
will be empty, and we'll fail to set the accountSlug, preventing the
tunnel from starting.
To fix this, we add a simple convenience function that returns the
legacy configuration, and we use this configuration to start the tunnel
in case the GUI hasn't started.
Once the GUI starts, the legacy configuration is migrated and deleted,
so this is more of an edge case. Still, given the hundreds/thousands of
Apple device installations we have, someone is bound to hit it, and it
would be better to spend a few minutes saving potentially man-hours of
troubleshooting later.
On macOS, the token is saved in the system keychain so that the `root`
user is able to manage it. `secd`, the daemon that responds to Keychain
requests, is very strict about which binaries can access Keychain items
created by other binaries.
In development, the Firezone system extension runs from an unprivileged
directory, and isn't release-signed, which means it is not able to
manage the Keychain token for the release binary, and vice-versa.
To fix this, we isolate the Keychain items from each other with
different labels for `debug` and `release`, where the latter is
unchanged.
This is only an issue on debug, so a Changelog entry is not created.
Fixes#8917Fixes#8642
Exposes a configuration toggle to connect on start, allowing it to be
overridden by MDM. Currently we assume this to be true.
Will need to refactor the settings soon to a dedicated
`ObservableObject` in the ViewModel to make these validations and field
checks less verbose.
related: #4505
Now that configuration is persisted in a more reasonable fashion, we can
expose a new `General` section to the Settings, allowing the user to
configure an account slug.
This field will automatically be populated upon the first sign in, so
that subsequent sign-ins will take the user directly to the account sign
in page.
Fixes#5119
Related #8919
---------
Signed-off-by: Jamil <jamilbk@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
In the UI, we need to use Strings to bind to the text inputs.
In the configuration dictionaries, we need to use Strings to save the
URLs.
It makes no sense to convert these to URLs in between. Instead, we can
validate upon save and then use them as Strings throughout.
All the MDM configuration to shadow the `internetResourceEnabled` state.
Related: #4505
---------
Signed-off-by: Jamil <jamilbk@users.noreply.github.com>
If `authURL`, `apiURL`, or `logFilter` are set in the managed
configuration, we disable each of these fields respectively from user
editing.
If all of them are overridden, we disable the `Apply` and `Reset to
Defaults` buttons.
Related #4505
One simple way we can tell the GUI app which configuration fields have
been overridden by MDM is to specify an `overriddenKeys` string array.
If provided, this will disable the relevant configuration from being set
/ editable in the GUI app and communicate to the user as such.
Related: #4505
---------
Signed-off-by: Jamil <jamilbk@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Similar to how we fetch new resources, we add a Configuration poller
that fetches new configuration every 1s. If the configuration is
unchanged, we respond to the caller with a cached copy to avoid needing
to serialize the data over IPC.
Related: #4505
We are currently storing app configuration across three places:
- UserDefaults (favorite resources)
- VPN configuration (Settings)
- Disk (firezone id)
These can be consolidated to UserDefaults, which is the standard way to
store app configuration like this.
UserDefaults is the umbrella persistence store for regular app
configuration (`plist` files which are just XML dictionaries),
iCloud-synced app configuration across a user's devices, and managed app
configuration (MDM). They provide a cached, thread-safe, and
interprocess-supported mechanism for handling app configuration. We can
also subscribe to changes on this app configuration to react to changes.
Unfortunately, the System Extension ruins some of our fun because it
runs as root, and is confined to a different group container, meaning we
cannot share configuration directly between GUI and tunnel procs.
To address this, we use the tunnel process to store all vital
configuration and introduce IPC calls to set and fetch these.
Commit-by-commit review recommended, but things got a little crazy
towards the end when I realized that we can't share a single
UserDefaults between both procs.
The current `rust/` directory is a bit of a wild-west in terms of how
the crates are organised. Most of them are simply at the top-level when
in reality, they are all `connlib`-related. The Apple and Android FFI
crates - which are entrypoints in the Rust code are defined several
layers deep.
To improve the situation, we move around and rename several crates. The
end result is that all top-level crates / directories are:
- Either entrypoints into the Rust code, i.e. applications such as
Gateway, Relay or a Client
- Or crates shared across all those entrypoints, such as `telemetry` or
`logging`
When developing system extensions, Apple's
[documentation](https://developer.apple.com/documentation/DriverKit/debugging-and-testing-system-extensions)
instructs developers to disable SIP and turn on system extension
developer mode to disable certain runtime checks that allow the
extension to run.
It turns out this is completely unnecessary - any properly set up Xcode
toolchain can build a functioning macOS debug client.
When developing the macOS app, we always build the exact same version
and build code for each build. ~~This _may_ be one reason why we
constantly have to deactivate the extension before the new one will
launch.~~ Edit: Just tested, and I can verify that this does fix the
issue on dev builds, so no more having to uninstall the sysex between
builds.
Even if that's not the reason, this is a cleaner approach than building
it in our prod-only scripts.
---------
Signed-off-by: Jamil <jamilbk@users.noreply.github.com>
When updating the provisioning profiles (i.e. when changing anything the
Apple Developer Portal), we needed to manually update these build
scripts to point to the new UUIDs.
This can be made simpler to automatically pull it out of the profiles in
CI.
Somewhere between Xcode 16.0 and Xcode 16.3, the API for the libresolv
functions we call changed slightly, and we can now pass the return value
of `__res_9_state()` directly to the `res_9_ninit`, `res_9_ndestroy` and
`res_9_getservers` functions.
This isn't plugged into anything yet on the Swift side but lays the
foundation for changing the log-level at runtime without having to sign
the user out.
If another VPN has been activated on the system while Firezone is
active, Apple OSes will deactivate our configuration, and never
reactivate it.
We knew this already, and always activated the configuration when
starting during the sign in flow, but failed to also do this when
autoStarting on launch.
This PR updates ensures that during autoStart, we re-enable the
configuration as well.
Fixes#8813
Within the event-loop, we already react to the channel being closed
which happens when the `Sender` within the `Session` gets dropped. As
such, there is no need to send an explicit `Stop` command, dropping the
`Session` is equivalent.
As it turns out, `swift-bridge` already calls `Drop` for us when the
last pointer is set to `nil`:
280a9dd999/swift/apple/FirezoneNetworkExtension/Connlib/Generated/connlib-client-apple/connlib-client-apple.swift (L24-L28)
Thus, we can also remove the explicit `disconnect` call to
`WrappedSession` entirely.
This is a regression introduced in c9f085c102. The `status` at this
point is still `nil` because we have not yet fully subscribed to VPN
status change updates from the system.
That actually shouldn't prevent us from trying to start the tunnel
anyway. If the `token` is missing from the Keychain, the tunnel process
will no-op. So we simply try to start a session on launch always.
Fixes#8456
In order to have the system expand search domains for us, we need to set
a very peculiar combination of configuration options in the
`NEDNSSettings` of the VPN configuration:
- We need to include our search domains in the list of `matchDomains`
- We need to set `matchDomainsNoSearch = false`
- We need to set the `searchDomains` field
Technically, we don't even need to set `searchDomains` by itself.
Reading the docs in more detail for the `matchDomainsNoSearch` flag
explains why:
> A Boolean that specifies if the domains in the matchDomains list
should not be appended to the resolver’s list of search domains.
The double-negative here is confusing but essentially, what this says
is:
> If false, append the list of match domains to the resolver's search
domains.
That is exactly what we want. We want a search domain of e.g.
`example.com` to append to the list of search domains for the primary
resolver of non-scoped DNS queries.
I tested without setting `searchDomains` and it does still work: The
system will still expand the domain for us und send us a FQDN query of
e.g. `foo.example.com`. However, I figured not setting `searchDomains`
at all is quite confusing so I left it in there.
Related: #8410 (Fixes it for MacOS)
---------
Signed-off-by: Thomas Eizinger <thomas@eizinger.io>
Co-authored-by: thomas <firezone@firezones-MacBook-Air.fritz.box>
Co-authored-by: Jamil <jamilbk@users.noreply.github.com>