pull/695/merge
Michael Nahkies 2 months ago committed by GitHub
commit 6ad2576f93
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -148,6 +148,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://")) {
@ -160,8 +164,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,
@ -294,6 +296,10 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
return packageManager.hasSystemFeature("android.hardware.type.pc")
}
override fun isClientLoggingEnabled(): Boolean {
return getIsClientLoggingEnabled()
}
override fun getInterfacesAsString(): String {
val interfaces: ArrayList<NetworkInterface> =
java.util.Collections.list(NetworkInterface.getNetworkInterfaces())
@ -419,6 +425,7 @@ open class UninitializedApp : Application() {
// the VPN (i.e. we're logged in and machine is authorized).
private const val ABLE_TO_START_VPN_KEY = "ableToStartVPN"
private const val DISALLOWED_APPS_KEY = "disallowedApps"
private const val IS_CLIENT_LOGGING_ENABLED_KEY = "isClientLoggingEnabled"
// File for shared preferences that are not encrypted.
private const val UNENCRYPTED_PREFERENCES = "unencrypted"
private lateinit var appInstance: UninitializedApp
@ -583,6 +590,21 @@ open class UninitializedApp : Application() {
return builder.build()
}
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)
}
fun updateIsClientLoggingEnabled(value: Boolean) {
getUnencryptedPrefs().edit().putBoolean(IS_CLIENT_LOGGING_ENABLED_KEY, value).apply()
}
fun updateUserDisallowedPackageNames(packageNames: List<String>) {
if (packageNames.any { it.isEmpty() }) {
TSLog.e(TAG, "updateUserDisallowedPackageNames called with empty packageName(s)")

@ -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()
}
}
}

@ -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
}
}
}
}

@ -58,6 +58,7 @@ fun SettingsView(
val isVPNPrepared by appViewModel.vpnPrepared.collectAsState()
val showTailnetLock by MDMSettings.manageTailnetLock.flow.collectAsState()
val useTailscaleSubnets by MDMSettings.useTailscaleSubnets.flow.collectAsState()
val isClientRemoteLoggingEnabled by viewModel.isClientRemoteLoggingEnabled.collectAsState()
Scaffold(
topBar = {
@ -106,6 +107,19 @@ fun SettingsView(
Lists.ItemDivider()
Setting.Text(R.string.subnet_routing, onClick = settingsNav.onNavigateToSubnetRouting)
}
Lists.ItemDivider()
Setting.Switch(
R.string.client_remote_logging_enabled,
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()) {
Lists.ItemDivider()
Setting.Text(R.string.permissions, onClick = settingsNav.onNavigateToPermissions)
@ -175,6 +189,7 @@ object Setting {
fun Switch(
titleRes: Int = 0,
title: String? = null,
subtitle: String? = null,
isOn: Boolean,
enabled: Boolean = true,
onToggle: (Boolean) -> Unit = {}
@ -187,6 +202,15 @@ object Setting {
style = MaterialTheme.typography.bodyMedium,
)
},
supportingContent =
subtitle?.let {
{
Text(
it,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant)
}
},
trailingContent = {
TintedSwitch(checked = isOn, onCheckedChange = onToggle, enabled = enabled)
})

@ -4,6 +4,7 @@
package com.tailscale.ipn.ui.viewModel
import androidx.lifecycle.viewModelScope
import com.tailscale.ipn.App
import com.tailscale.ipn.ui.localapi.Client
import com.tailscale.ipn.ui.notifier.Notifier
import com.tailscale.ipn.ui.util.LoadingIndicator
@ -34,8 +35,11 @@ class SettingsViewModel : IpnViewModel() {
val tailNetLockEnabled: StateFlow<Boolean?> = MutableStateFlow(null)
// True if tailscaleDNS is enabled. nil if not yet known.
val corpDNSEnabled: StateFlow<Boolean?> = MutableStateFlow(null)
val isClientRemoteLoggingEnabled: StateFlow<Boolean> = MutableStateFlow(true)
init {
isClientRemoteLoggingEnabled.set(App.get().isClientLoggingEnabled())
viewModelScope.launch {
Notifier.netmap.collect { netmap -> isAdmin.set(netmap?.SelfNode?.isAdmin ?: false) }
}
@ -52,4 +56,9 @@ class SettingsViewModel : IpnViewModel() {
}
}
}
fun toggleIsClientRemoteLoggingEnabled() {
isClientRemoteLoggingEnabled.set(!isClientRemoteLoggingEnabled.value)
App.get().updateIsClientLoggingEnabled(isClientRemoteLoggingEnabled.value)
}
}

@ -346,6 +346,9 @@
<string name="run_as_subnet_router">Run as subnet router</string>
<string name="use_tailscale_subnets_subtitle">Route traffic according to your network\'s rules. Some networks require this to access IP addresses that don\'t start with 100.x.y.z.</string>
<string name="subnet_routing">Subnet routing</string>
<string name="client_remote_logging_enabled">Remote client logging</string>
<string name="client_remote_logging_enabled_subtitle">Whether debug logs are uploaded to Tailscale support. When disabled no support or network flow logs.\nChanges require restarting the app to take effect.</string>
<string name="client_remote_logging_enabled_subtitle_mdm">Client logging is always enabled for devices under remote management.</string>
<string name="specifies_a_device_name_to_be_used_instead_of_the_automatic_default">Specifies a device name to be used instead of the automatic default.</string>
<string name="hostname">Hostname</string>
<string name="failed_to_save">Failed to save</string>

@ -288,7 +288,7 @@ func (a *App) newBackend(dataDir string, appCtx AppContext, store *stateStore,
log.Printf("netmon.New: %w", err)
}
b.netMon = netMon
b.setupLogs(dataDir, logID, logf, sys.HealthTracker())
b.setupLogs(dataDir, logID, logf, sys.HealthTracker(), a.isClientLoggingEnabled())
dialer := new(tsdial.Dialer)
vf := &VPNFacade{
SetBoth: b.setCfg,

@ -48,6 +48,9 @@ type AppContext interface {
// IsChromeOS reports whether we're on a ChromeOS device.
IsChromeOS() (bool, error)
// IsClientLoggingEnabled reports whether the user has enabled remote client logging.
IsClientLoggingEnabled() (bool, error)
// GetInterfacesAsString gets a string representation of all network
// interfaces.
GetInterfacesAsString() (string, error)

@ -104,8 +104,16 @@ func (a *App) isChromeOS() bool {
return isChromeOS
}
func (a *App) isClientLoggingEnabled() bool {
isClientLoggingEnabled, err := a.appCtx.IsClientLoggingEnabled()
if err != nil {
panic(err)
}
return isClientLoggingEnabled
}
// SetupLogs sets up remote logging.
func (b *backend) setupLogs(logDir string, logID logid.PrivateID, logf logger.Logf, health *health.Tracker) {
func (b *backend) setupLogs(logDir string, logID logid.PrivateID, logf logger.Logf, health *health.Tracker, enableUpload bool) {
if b.netMon == nil {
panic("netMon must be created prior to SetupLogs")
}
@ -135,6 +143,11 @@ func (b *backend) setupLogs(logDir string, logID logid.PrivateID, logf logger.Lo
b.logger = logtail.NewLogger(logcfg, logf)
if !enableUpload {
log.Printf("disabling remote log upload")
logtail.Disable()
}
log.SetFlags(0)
log.SetOutput(b.logger)

Loading…
Cancel
Save