mirror of
https://github.com/bitwarden/android.git
synced 2026-04-10 16:46:01 -05:00
Using the environment url hosts from ServerConfigRepository to redact logs
This commit is contained in:
@@ -10,7 +10,7 @@ import com.bitwarden.core.data.util.toFormattedPattern
|
||||
import com.bitwarden.data.datasource.disk.model.FlightRecorderDataSet
|
||||
import com.bitwarden.data.manager.file.FileManager
|
||||
import com.bitwarden.data.repository.ServerConfigRepository
|
||||
import com.bitwarden.network.util.redactSelfHostedHostnames
|
||||
import com.bitwarden.network.util.redactHostnamesInMessage
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.BufferedWriter
|
||||
@@ -18,6 +18,7 @@ import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.net.URI
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
@@ -35,6 +36,19 @@ internal class FlightRecorderWriterImpl(
|
||||
private val buildInfoManager: BuildInfoManager,
|
||||
private val serverConfigRepository: ServerConfigRepository,
|
||||
) : FlightRecorderWriter {
|
||||
private val configuredHosts: Set<String>
|
||||
get() {
|
||||
val environment = serverConfigRepository.serverConfigStateFlow.value
|
||||
?.serverData?.environment ?: return emptySet()
|
||||
return listOfNotNull(
|
||||
environment.vaultUrl,
|
||||
environment.apiUrl,
|
||||
environment.identityUrl,
|
||||
environment.notificationsUrl,
|
||||
environment.ssoUrl,
|
||||
).mapNotNull { runCatching { URI(it).host }.getOrNull() }.toSet()
|
||||
}
|
||||
|
||||
override suspend fun deleteLog(data: FlightRecorderDataSet.FlightRecorderData) {
|
||||
fileManager.delete(File(File(fileManager.logsDirectory), data.fileName))
|
||||
}
|
||||
@@ -99,6 +113,7 @@ internal class FlightRecorderWriterImpl(
|
||||
val formattedTime = clock
|
||||
.instant()
|
||||
.toFormattedPattern(pattern = LOG_TIME_PATTERN, clock = clock)
|
||||
val hosts = configuredHosts
|
||||
withContext(context = dispatcherManager.io) {
|
||||
runCatching {
|
||||
BufferedWriter(FileWriter(logFile, true)).use { bw ->
|
||||
@@ -110,10 +125,10 @@ internal class FlightRecorderWriterImpl(
|
||||
bw.append(it)
|
||||
}
|
||||
bw.append(" – ")
|
||||
bw.append(message.redactSelfHostedHostnames())
|
||||
bw.append(message.redactHostnamesInMessage(hosts))
|
||||
throwable?.let {
|
||||
bw.append(" – ")
|
||||
bw.append(it.getStackTraceString().redactSelfHostedHostnames())
|
||||
bw.append(it.getStackTraceString().redactHostnamesInMessage(hosts))
|
||||
}
|
||||
bw.newLine()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.bitwarden.network.core
|
||||
|
||||
import com.bitwarden.network.model.NetworkResult
|
||||
import com.bitwarden.network.util.UNKNOWN_HOST_REGEX
|
||||
import okhttp3.Request
|
||||
import okio.IOException
|
||||
import okio.Timeout
|
||||
@@ -17,6 +16,8 @@ import java.lang.reflect.Type
|
||||
*/
|
||||
private const val NO_CONTENT_RESPONSE_CODE: Int = 204
|
||||
|
||||
private val UNKNOWN_HOST_REGEX = Regex("""Unable to resolve host "([^"]+)"""")
|
||||
|
||||
/**
|
||||
* A [Call] for wrapping a network request into a [NetworkResult].
|
||||
*/
|
||||
|
||||
@@ -1,38 +1,10 @@
|
||||
package com.bitwarden.network.util
|
||||
|
||||
internal val UNKNOWN_HOST_REGEX = Regex("""Unable to resolve host "([^"]+)"""")
|
||||
/**
|
||||
* List of official Bitwarden cloud hostnames that are safe to log.
|
||||
*/
|
||||
private val BITWARDEN_HOSTS = listOf("bitwarden.com", "bitwarden.eu", "bitwarden.pw")
|
||||
|
||||
private val URL_HOST_REGEX = Regex("""https?://([^/?#\s"]+)""")
|
||||
|
||||
// Matches hostnames as single-argument method calls, e.g. getCookies(vault.example.com)
|
||||
private val METHOD_CALL_HOST_REGEX =
|
||||
Regex("""\b\w+\(([a-zA-Z0-9][a-zA-Z0-9.\-]*\.[a-zA-Z]{2,})\)""")
|
||||
|
||||
/**
|
||||
* Extracts hostnames from URLs, UnknownHostException messages, and method-call log patterns
|
||||
* present in this string, then redacts any self-hosted (non-Bitwarden) hostnames with
|
||||
* [REDACTED_SELF_HOST].
|
||||
*
|
||||
* Recognized patterns:
|
||||
* - Full URLs: `https://hostname/path`
|
||||
* - UnknownHostException: `Unable to resolve host "hostname"`
|
||||
* - Method-call logs: `methodName(hostname)`
|
||||
*/
|
||||
internal fun String.redactSelfHostedHostnames(): String {
|
||||
val urlHosts = URL_HOST_REGEX.findAll(this).map { it.groupValues[1] }
|
||||
val exceptionHosts = UNKNOWN_HOST_REGEX.findAll(this).map { it.groupValues[1] }
|
||||
val methodCallHosts = METHOD_CALL_HOST_REGEX.findAll(this).map { it.groupValues[1] }
|
||||
val extractedHosts = (urlHosts + exceptionHosts + methodCallHosts)
|
||||
.map { it.substringBefore(':') } // strip port if present
|
||||
.filter { host -> BITWARDEN_HOSTS.none { host.endsWith(it) } }
|
||||
.toSet()
|
||||
return this.redactHostnamesInMessage(extractedHosts)
|
||||
}
|
||||
|
||||
/**
|
||||
* Redacts hostnames in a log message by replacing bare hostnames with [REDACTED_SELF_HOST].
|
||||
*
|
||||
@@ -42,7 +14,7 @@ internal fun String.redactSelfHostedHostnames(): String {
|
||||
* @param configuredHosts Set of hostnames to redact
|
||||
* @return Message with hostnames redacted as [REDACTED_SELF_HOST]
|
||||
*/
|
||||
internal fun String.redactHostnamesInMessage(configuredHosts: Set<String>): String =
|
||||
fun String.redactHostnamesInMessage(configuredHosts: Set<String>): String =
|
||||
configuredHosts.fold(this) { result, hostname ->
|
||||
val escapedHostname = Regex.escape(hostname)
|
||||
val bareHostnamePattern = Regex("""\b$escapedHostname\b""")
|
||||
|
||||
@@ -143,49 +143,4 @@ class HostnameRedactionUtilTest {
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redactSelfHostedHostnames redacts hostname in getCookies method-call log`() {
|
||||
val message = "getCookies(vault.example.com): resolved=vault.example.com, count=0"
|
||||
|
||||
val result = message.redactSelfHostedHostnames()
|
||||
|
||||
assertEquals(
|
||||
"getCookies([REDACTED_SELF_HOST]): resolved=[REDACTED_SELF_HOST], count=0",
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redactSelfHostedHostnames redacts hostname in needsBootstrap method-call log`() {
|
||||
val message = "needsBootstrap(vault.example.com): false (cookieDomain=null)"
|
||||
|
||||
val result = message.redactSelfHostedHostnames()
|
||||
|
||||
assertEquals(
|
||||
"needsBootstrap([REDACTED_SELF_HOST]): false (cookieDomain=null)",
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redactSelfHostedHostnames redacts hostname in resolveHostname method-call log`() {
|
||||
val message = "resolveHostname(vault.example.com): no stored config found, using original"
|
||||
|
||||
val result = message.redactSelfHostedHostnames()
|
||||
|
||||
assertEquals(
|
||||
"resolveHostname([REDACTED_SELF_HOST]): no stored config found, using original",
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redactSelfHostedHostnames preserves Bitwarden domain in method-call log`() {
|
||||
val message = "getCookies(api.bitwarden.com): resolved=api.bitwarden.com, count=3"
|
||||
|
||||
val result = message.redactSelfHostedHostnames()
|
||||
|
||||
assertEquals(message, result)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user