Skip to content

Commit

Permalink
feat: encrypt the bot database at rest
Browse files Browse the repository at this point in the history
  • Loading branch information
DarkAtra committed Nov 15, 2024
1 parent 344f1cc commit 3bdb741
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ package de.darkatra.vrising.discord.migration
import org.dizitart.no2.Nitrite
import org.dizitart.no2.collection.Document
import org.dizitart.no2.collection.NitriteId
import org.dizitart.no2.common.Constants
import org.dizitart.no2.common.meta.Attributes
import org.dizitart.no2.store.NitriteMap

fun Nitrite.getNitriteMap(name: String): NitriteMap<NitriteId, Document> {
return store.openMap(name, NitriteId::class.java, Document::class.java)
}

fun Nitrite.listAllCollectionNames(): List<String> {
return store.openMap<String, Attributes>(Constants.META_MAP_NAME, String::class.java, Attributes::class.java).keys()
.filter { key -> !key.startsWith("\$nitrite") }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,28 @@ package de.darkatra.vrising.discord.persistence

import de.darkatra.vrising.discord.BotProperties
import de.darkatra.vrising.discord.migration.SchemaEntityConverter
import de.darkatra.vrising.discord.migration.getNitriteMap
import de.darkatra.vrising.discord.migration.listAllCollectionNames
import de.darkatra.vrising.discord.persistence.model.converter.ErrorEntityConverter
import de.darkatra.vrising.discord.persistence.model.converter.PlayerActivityFeedEntityConverter
import de.darkatra.vrising.discord.persistence.model.converter.PvpKillFeedEntityConverter
import de.darkatra.vrising.discord.persistence.model.converter.ServerEntityConverter
import de.darkatra.vrising.discord.persistence.model.converter.StatusMonitorEntityConverter
import org.dizitart.no2.Nitrite
import org.dizitart.no2.NitriteBuilder
import org.dizitart.no2.exceptions.NitriteIOException
import org.dizitart.no2.mvstore.MVStoreModule
import org.dizitart.no2.store.StoreModule
import org.slf4j.LoggerFactory
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.absolutePathString
import kotlin.io.path.copyTo
import kotlin.io.path.deleteIfExists
import kotlin.io.path.exists

@Configuration
@EnableConfigurationProperties(BotProperties::class)
Expand All @@ -22,12 +33,63 @@ class DatabaseConfiguration(

companion object {

private val logger by lazy { LoggerFactory.getLogger(DatabaseConfiguration::class.java) }

fun buildNitriteDatabase(databaseFile: Path, username: String? = null, password: String? = null): Nitrite {

val storeModule = MVStoreModule.withConfig()
.filePath(databaseFile.toAbsolutePath().toFile())
.compress(true)
.build()
// version 2.12.0 introduced database encryption at rest. the following code attempts to perform the migration if necessary
return try {
// try to open the database with encryption
getNitriteBuilder(getStoreModule(databaseFile, password)).openOrCreate(username, password)
} catch (e: NitriteIOException) {

// if the automated migration was aborted while writing the files to disc, restore the backup
val unencryptedDatabaseBackupFile = Path.of(System.getProperty("java.io.tmpdir")).resolve("v-rising-bot.db.unencrypted")
if (unencryptedDatabaseBackupFile.exists()) {
logger.info("Found an unencrypted backup of the database at: ${unencryptedDatabaseBackupFile.absolutePathString()}")
unencryptedDatabaseBackupFile.copyTo(databaseFile, overwrite = true)
logger.info("Successfully restored the backup. Will re-attempt the migration.")
}

logger.info("Attempting to encrypt the bot database with the provided database password.")

// retry opening the database without encryption if we encounter an error
val unencryptedDatabase = try {
getNitriteBuilder(getStoreModule(databaseFile, null)).openOrCreate(username, password)
} catch (e2: NitriteIOException) {
// if we also can't open the database without encryption, throw the initial error as it probably isn't related to encryption
throw e
}

unencryptedDatabaseBackupFile.deleteIfExists()

// create an encrypted copy of the existing database
val tempDatabaseFile = Files.createTempFile("v-rising-bot", ".db")

val encryptedDatabase = getNitriteBuilder(getStoreModule(tempDatabaseFile, password)).openOrCreate(username, password)
for (collectionName in unencryptedDatabase.listAllCollectionNames()) {

val oldCollection = unencryptedDatabase.getNitriteMap(collectionName)
val newCollection = encryptedDatabase.getNitriteMap(collectionName)

oldCollection.values().forEach { document -> newCollection.put(document.id, document) }
}
unencryptedDatabase.close()
encryptedDatabase.close()

databaseFile.copyTo(unencryptedDatabaseBackupFile)
tempDatabaseFile.copyTo(databaseFile, overwrite = true)

unencryptedDatabaseBackupFile.deleteIfExists()
tempDatabaseFile.deleteIfExists()

getNitriteBuilder(getStoreModule(databaseFile, password)).openOrCreate(username, password).also {
logger.info("Successfully encrypted the database.")
}
}
}

private fun getNitriteBuilder(storeModule: StoreModule): NitriteBuilder {

return Nitrite.builder()
.loadModule(storeModule)
Expand All @@ -38,7 +100,15 @@ class DatabaseConfiguration(
.registerEntityConverter(PvpKillFeedEntityConverter())
.registerEntityConverter(ServerEntityConverter())
.registerEntityConverter(StatusMonitorEntityConverter())
.openOrCreate(username, password)
}

private fun getStoreModule(databaseFile: Path, password: String?): MVStoreModule {

return MVStoreModule.withConfig()
.filePath(databaseFile.toAbsolutePath().toFile())
.encryptionKey(password?.let(String::toCharArray))
.compress(true)
.build()
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ logging:
level:
root: info
com.ibasco.agql: warn
# nitrite is logging some of the exceptions before throwing - disable all nitrite logs since we already log all exceptions
nitrite: off
nitrite-mvstore: off
pattern:
console: "%clr(%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}){faint} %clr(%5p) %clr(${PID:-}){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m %mdc%n%wEx"

Expand Down

0 comments on commit 3bdb741

Please sign in to comment.