From 04fd66c55f7e29b10ebab1b64890adf7356cfb2e Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Fri, 5 Sep 2025 09:01:37 +0100 Subject: [PATCH] fix: force client logging on when any mdm is configured Signed-off-by: Michael Nahkies --- android/src/main/java/com/tailscale/ipn/App.kt | 13 +++++++++++-- .../main/java/com/tailscale/ipn/mdm/MDMSettings.kt | 14 ++++++++++++-- .../ipn/mdm/MDMSettingsChangedReceiver.kt | 9 +++++++++ .../java/com/tailscale/ipn/ui/view/SettingsView.kt | 7 ++++++- android/src/main/res/values/strings.xml | 1 + 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/tailscale/ipn/App.kt b/android/src/main/java/com/tailscale/ipn/App.kt index 1329019..3326de3 100644 --- a/android/src/main/java/com/tailscale/ipn/App.kt +++ b/android/src/main/java/com/tailscale/ipn/App.kt @@ -146,6 +146,10 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner { } private fun initializeApp() { + // Read MDM settings as early as possible, before starting the go backend. + val rm = getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager + MDMSettings.update(this, rm, true) + // Check if a directory URI has already been stored. val storedUri = getStoredDirectoryUri() if (storedUri != null && storedUri.toString().startsWith("content://")) { @@ -158,8 +162,6 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner { NetworkChangeCallback.monitorDnsChanges(connectivityManager, dns) initViewModels() applicationScope.launch { - val rm = getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager - MDMSettings.update(get(), rm) Notifier.state.collect { _ -> combine(Notifier.state, MDMSettings.forceEnabled.flow, Notifier.prefs, Notifier.netmap) { state, @@ -545,6 +547,13 @@ open class UninitializedApp : Application() { } fun getIsClientLoggingEnabled(): Boolean { + + // Force client logging to be enabled, when the device is managed by MDM + // Later this could become a dedicated MDMSetting / restriction. + if (MDMSettings.isMDMConfigured) { + return true + } + return getUnencryptedPrefs().getBoolean(IS_CLIENT_LOGGING_ENABLED_KEY, true) } diff --git a/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt b/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt index 34b341f..5aa5b04 100644 --- a/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt +++ b/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt @@ -18,6 +18,11 @@ object MDMSettings { // to the backend. class NoSuchKeyException : Exception("no such key") + // We default this to true, so that stricter behavior is used during initialization, + // prior to receiving MDM restrictions. + var isMDMConfigured = true + private set + val forceEnabled = BooleanMDMSetting("ForceEnabled", "Force Enabled Connection Toggle") // Handled on the backed @@ -117,10 +122,15 @@ object MDMSettings { val allSettingsByKey by lazy { allSettings.associateBy { it.key } } - fun update(app: App, restrictionsManager: RestrictionsManager?) { + fun update(app: App, restrictionsManager: RestrictionsManager?, skipNotify: Boolean = false) { val bundle = restrictionsManager?.applicationRestrictions val preferences = lazy { app.getEncryptedPrefs() } allSettings.forEach { it.setFrom(bundle, preferences) } - app.notifyPolicyChanged() + + isMDMConfigured = bundle?.isEmpty == true + + if (!skipNotify) { + app.notifyPolicyChanged() + } } } diff --git a/android/src/main/java/com/tailscale/ipn/mdm/MDMSettingsChangedReceiver.kt b/android/src/main/java/com/tailscale/ipn/mdm/MDMSettingsChangedReceiver.kt index d54129d..7647308 100644 --- a/android/src/main/java/com/tailscale/ipn/mdm/MDMSettingsChangedReceiver.kt +++ b/android/src/main/java/com/tailscale/ipn/mdm/MDMSettingsChangedReceiver.kt @@ -16,7 +16,16 @@ class MDMSettingsChangedReceiver : BroadcastReceiver() { TSLog.d("syspolicy", "MDM settings changed") val restrictionsManager = context?.getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager + + val previouslyIsMDMEnabled = MDMSettings.isMDMConfigured + MDMSettings.update(App.get(), restrictionsManager) + + if (MDMSettings.isMDMConfigured && !previouslyIsMDMEnabled) { + // async MDM settings updated from disabled -> enabled. restart to ensure + // correctly applied (particularly forcing client logs on). + // TODO: actually restart + } } } } diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/SettingsView.kt b/android/src/main/java/com/tailscale/ipn/ui/view/SettingsView.kt index 75ccc4d..6e86e87 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/SettingsView.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/SettingsView.kt @@ -111,8 +111,13 @@ fun SettingsView( Lists.ItemDivider() Setting.Switch( R.string.client_remote_logging_enabled, - subtitle = stringResource(R.string.client_remote_logging_enabled_subtitle), + subtitle = + stringResource( + if (MDMSettings.isMDMConfigured) + R.string.client_remote_logging_enabled_subtitle_mdm + else R.string.client_remote_logging_enabled_subtitle), isOn = isClientRemoteLoggingEnabled, + enabled = !MDMSettings.isMDMConfigured, onToggle = { viewModel.toggleIsClientRemoteLoggingEnabled() }) if (!AndroidTVUtil.isAndroidTV()) { diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml index c681ade..4f94eef 100644 --- a/android/src/main/res/values/strings.xml +++ b/android/src/main/res/values/strings.xml @@ -348,6 +348,7 @@ Subnet routing Remote client logging Whether debug logs are uploaded to Tailscale support. When disabled no support or network flow logs.\nChanges require restarting the app to take effect. + Client logging is always enabled for devices under remote management. Specifies a device name to be used instead of the automatic default. Hostname Failed to save