diff --git a/api/src/main/kotlin/app/hakurei/planterette/api/HakureiConfig.kt b/api/src/main/kotlin/app/hakurei/planterette/api/HakureiConfig.kt new file mode 100644 index 0000000..167ef72 --- /dev/null +++ b/api/src/main/kotlin/app/hakurei/planterette/api/HakureiConfig.kt @@ -0,0 +1,122 @@ +package app.hakurei.planterette.api + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class HakureiConfig( + val id: String, + + val path: String? = null, + val args: Array, + + val enablements: Byte, + + @SerialName("session_bus") val sessionBus: DbusConfig?, + @SerialName("system_bus") val systemBus: DbusConfig?, + @SerialName("direct_wayland") val directWayland: Boolean?, + + val username: String? = null, + val shell: String? = null, + val data: String, + val dir: String, + @SerialName("extra_perms") val extraPerms: Array?, + + val identity: Int, + val groups: Array, + + val container: ContainerConfig +) { + @Serializable + data class DbusConfig( + val see: Array? = null, + val talk: Array, + val own: Array?, + val call: Map?, + val broadcast: Map?, + val log: Boolean, + val filter: Boolean + ) + @Serializable + data class ExtraPermConfig( + val ensure: Boolean? = null, + val path: String, + @SerialName("r") val read: Boolean? = null, + @SerialName("w") val write: Boolean? = null, + @SerialName("x") val execute: Boolean? = null + ) + @Serializable + data class ContainerConfig( + val hostname: String? = null, + + @SerialName("seccomp_flags") val seccompFlags: Int, + @SerialName("seccomp_presets") val seccompPresets: Int, + @SerialName("seccomp_compat") val seccompCompat: Boolean? = null, + + val devel: Boolean? = null, + val userns: Boolean? = null, + val net: Boolean? = null, + val tty: Boolean? = null, + val multiarch: Boolean? = null, + + val env: Map, + @SerialName("map_real_uid") val mapRealUid: Boolean, + + val device: Boolean? = null, + val filesystem: Array, + @SerialName("symlink") val link: Array, + + val etc: String? = null, + @SerialName("auto_etc") val autoEtc: Boolean, + val cover: Array + ) + @Serializable + data class FilesystemConfig( + val dst: String? = null, + val src: String, + val write: Boolean? = null, + val device: Boolean? = null, + @SerialName("require") val must: Boolean? = null + ) + @Serializable + enum class Enablement(val value: Int) { + Wayland(1 shl 0), + X11(1 shl 1), + DBus(1 shl 2), + Pulse(1 shl 3), + + EM(0b10000); + + companion object { + fun enablements(vararg enablements: Enablement): Byte { + return enablements.sumOf(Enablement::value).toByte() + } + } + } + @Serializable + enum class SeccompFilterPreset(val value: Int) { + Ext(1 shl 0), + DenyNS(1 shl 1), + DenyTTY(1 shl 2), + DenyDevel(1 shl 3), + Linux32(1 shl 4); + + companion object { + fun filterPresets(vararg filterPresets: SeccompFilterPreset): Int { + return filterPresets.sumOf(SeccompFilterPreset::value) + } + } + } + @Serializable + enum class HakureiExportFlag(val value: Int) { + Multiarch(1 shl 0), + CAN(1 shl 1), + Bluetooth(1 shl 2); + + companion object { + fun exportFlags(vararg exportFlags: HakureiExportFlag): Int { + return exportFlags.sumOf(HakureiExportFlag::value) + } + } + } +} diff --git a/api/src/test/kotlin/app/hakurei/planterette/api/HakureiConfigTest.kt b/api/src/test/kotlin/app/hakurei/planterette/api/HakureiConfigTest.kt new file mode 100644 index 0000000..b09bd4d --- /dev/null +++ b/api/src/test/kotlin/app/hakurei/planterette/api/HakureiConfigTest.kt @@ -0,0 +1,97 @@ +package app.hakurei.planterette.api + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.Json +import kotlinx.serialization.serializer +import org.junit.jupiter.api.Test +import java.io.FileInputStream +import kotlin.test.asserter + +class HakureiConfigTest { + @OptIn(ExperimentalSerializationApi::class) + val format = Json { + explicitNulls = false + } + val templateFile: String = javaClass.classLoader.getResource("template.json")!!.readText() + + @Test + fun testHakureiConfigSerialization() { + val testConfig: HakureiConfig = HakureiConfig( + id = "org.chromium.Chromium", + path = "/run/current-system/sw/bin/chromium", + args = arrayOf( + "chromium", + "--ignore-gpu-blocklist", + "--disable-smooth-scrolling", + "--enable-features=UseOzonePlatform", + "--ozone-platform=wayland" + ), + enablements = HakureiConfig.Enablement.enablements(HakureiConfig.Enablement.Wayland, HakureiConfig.Enablement.DBus, + HakureiConfig.Enablement.Pulse), + sessionBus = HakureiConfig.DbusConfig( + see = null, + talk = arrayOf("org.freedesktop.Notifications", "org.freedesktop.FileManager1", "org.freedesktop.ScreenSaver", + "org.freedesktop.secrets", "org.kde.kwalletd5", "org.kde.kwalletd6", "org.gnome.SessionManager"), + own = arrayOf("org.chromium.Chromium.*", "org.mpris.MediaPlayer2.org.chromium.Chromium.*", + "org.mpris.MediaPlayer2.chromium.*"), + call = mapOf("org.freedesktop.portal.*" to "*"), + broadcast = mapOf("org.freedesktop.portal.*" to "@/org/freedesktop/portal/*"), + log = false, + filter = true + ), + systemBus = HakureiConfig.DbusConfig( + see = null, + talk = arrayOf("org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"), + own = null, + call = null, + broadcast = null, + log = false, + filter = true + ), + directWayland = false, + + username = "chronos", + shell = "/run/current-system/sw/bin/zsh", + data = "/var/lib/hakurei/u0/org.chromium.Chromium", + dir = "/data/data/org.chromium.Chromium", + extraPerms = arrayOf( + HakureiConfig.ExtraPermConfig(path = "/var/lib/hakurei/u0", ensure = true, execute = true), + HakureiConfig.ExtraPermConfig(path = "/var/lib/hakurei/u0/org.chromium.Chromium", read = true, write = true, execute = true) + ), + identity = 9, + groups = arrayOf("video", "dialout", "plugdev"), + container = HakureiConfig.ContainerConfig( + hostname = "localhost", + devel = true, + userns = true, + net = true, + device = true, + seccompFlags = HakureiConfig.HakureiExportFlag.Multiarch.value, + seccompPresets = HakureiConfig.SeccompFilterPreset.Ext.value, + tty = true, + multiarch = true, + mapRealUid = true, + env = mapOf( + "GOOGLE_API_KEY" to "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY", + "GOOGLE_DEFAULT_CLIENT_ID" to "77185425430.apps.googleusercontent.com", + "GOOGLE_DEFAULT_CLIENT_SECRET" to "OTJgUOQcT7lO7GsGZq2G4IlT", + ), + filesystem = arrayOf( + HakureiConfig.FilesystemConfig(src = "/nix/store"), + HakureiConfig.FilesystemConfig(src = "/run/current-system"), + HakureiConfig.FilesystemConfig(src = "/run/opengl-driver"), + HakureiConfig.FilesystemConfig(src = "/var/db/nix-channels"), + HakureiConfig.FilesystemConfig(src = "/var/lib/hakurei/u0/org.chromium.Chromium", dst = "/data/data/org.chromium.Chromium", write = true, must = true), + HakureiConfig.FilesystemConfig(src = "/dev/dri", device = true) + ), + link = arrayOf("/run/user/65534", "/run/user/150"), + etc = "/etc", + autoEtc = true, + cover = arrayOf("/var/run/nscd") + ) + ) + val json = format.encodeToString(serializer(), testConfig) + println("can't compare to template generated by go right now, so printing here. add \"prettyPrint = true\" to format to add whitespace") + println(json) + } +} \ No newline at end of file diff --git a/api/src/test/resources/template.json b/api/src/test/resources/template.json new file mode 100644 index 0000000..c33ced9 --- /dev/null +++ b/api/src/test/resources/template.json @@ -0,0 +1,123 @@ +{ + "id": "org.chromium.Chromium", + "path": "/run/current-system/sw/bin/chromium", + "args": [ + "chromium", + "--ignore-gpu-blocklist", + "--disable-smooth-scrolling", + "--enable-features=UseOzonePlatform", + "--ozone-platform=wayland" + ], + "enablements": 13, + "session_bus": { + "see": null, + "talk": [ + "org.freedesktop.Notifications", + "org.freedesktop.FileManager1", + "org.freedesktop.ScreenSaver", + "org.freedesktop.secrets", + "org.kde.kwalletd5", + "org.kde.kwalletd6", + "org.gnome.SessionManager" + ], + "own": [ + "org.chromium.Chromium.*", + "org.mpris.MediaPlayer2.org.chromium.Chromium.*", + "org.mpris.MediaPlayer2.chromium.*" + ], + "call": { + "org.freedesktop.portal.*": "*" + }, + "broadcast": { + "org.freedesktop.portal.*": "@/org/freedesktop/portal/*" + }, + "filter": true + }, + "system_bus": { + "see": null, + "talk": [ + "org.bluez", + "org.freedesktop.Avahi", + "org.freedesktop.UPower" + ], + "own": null, + "call": null, + "broadcast": null, + "filter": true + }, + "username": "chronos", + "shell": "/run/current-system/sw/bin/zsh", + "data": "/var/lib/hakurei/u0/org.chromium.Chromium", + "dir": "/data/data/org.chromium.Chromium", + "extra_perms": [ + { + "ensure": true, + "path": "/var/lib/hakurei/u0", + "x": true + }, + { + "path": "/var/lib/hakurei/u0/org.chromium.Chromium", + "r": true, + "w": true, + "x": true + } + ], + "identity": 9, + "groups": [ + "video", + "dialout", + "plugdev" + ], + "container": { + "hostname": "localhost", + "seccomp_flags": 1, + "seccomp_presets": 1, + "devel": true, + "userns": true, + "net": true, + "tty": true, + "multiarch": true, + "env": { + "GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY", + "GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com", + "GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT" + }, + "map_real_uid": true, + "device": true, + "filesystem": [ + { + "src": "/nix/store" + }, + { + "src": "/run/current-system" + }, + { + "src": "/run/opengl-driver" + }, + { + "src": "/var/db/nix-channels" + }, + { + "dst": "/data/data/org.chromium.Chromium", + "src": "/var/lib/hakurei/u0/org.chromium.Chromium", + "write": true, + "require": true + }, + { + "src": "/dev/dri", + "dev": true + } + ], + "symlink": [ + [ + "/run/user/65534", + "/run/user/150" + ] + ], + "etc": "/etc", + "auto_etc": true, + "cover": [ + "/var/run/nscd" + ] + } +} \ No newline at end of file