2026-07-15 02:09:18 +09:00
|
|
|
use super::{BTreeMap, Deserialize, HostPath, Serialize};
|
|
|
|
|
|
2026-07-15 14:01:46 +09:00
|
|
|
pub const DOMAIN_STORE_SCHEMA_ID: &str = "lazaret-domain-store-v1";
|
2026-07-15 02:09:18 +09:00
|
|
|
pub const STORAGE_CATALOG_SCHEMA_ID: &str = "lazaret-storage-catalog-v1";
|
2026-07-15 14:01:46 +09:00
|
|
|
pub const DOMAIN_STORE_SCHEMA_VERSION: u32 = 1;
|
2026-07-15 02:09:18 +09:00
|
|
|
pub const STORAGE_CATALOG_SCHEMA_VERSION: u32 = 1;
|
|
|
|
|
pub const TRUSTED_HOST_CONFIGURATION_SCHEMA_ID: &str = "lazaret-trusted-host-config-v1";
|
2026-07-22 03:23:38 +09:00
|
|
|
pub const DEFAULT_PERSISTENT_DIRS: [&str; 5] = ["/root", "/usr", "/var", "/etc", "/opt"];
|
2026-07-15 02:09:18 +09:00
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
|
pub struct StorageConfigurationV1 {
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub data_root: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
|
pub struct NetworkConfigurationV1 {
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub default_allow: Vec<String>,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub default_deny: Vec<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
|
|
|
|
#[serde(rename_all = "kebab-case")]
|
|
|
|
|
pub enum VolumeKind {
|
|
|
|
|
File,
|
|
|
|
|
Directory,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
|
|
|
|
#[serde(rename_all = "kebab-case")]
|
|
|
|
|
pub enum VolumeAccess {
|
|
|
|
|
ReadOnly,
|
|
|
|
|
#[default]
|
|
|
|
|
ReadWrite,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
|
pub struct VolumeConfigurationV1 {
|
|
|
|
|
pub host: String,
|
|
|
|
|
pub container: String,
|
|
|
|
|
pub kind: VolumeKind,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub access: VolumeAccess,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub create: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
|
pub struct AutoApprovalRuleConfigurationV1 {
|
|
|
|
|
pub executable: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub args: Vec<String>,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub allow_extra_args: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
|
pub struct EscalationConfigurationV1 {
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub enabled: bool,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub auto_approve: Vec<AutoApprovalRuleConfigurationV1>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
|
pub struct ProfileConfigurationV1 {
|
|
|
|
|
pub image: String,
|
|
|
|
|
#[serde(default = "default_isolate_gitignored")]
|
|
|
|
|
pub isolate_gitignored: bool,
|
2026-07-22 03:23:38 +09:00
|
|
|
#[serde(default = "default_persistent_dirs")]
|
2026-07-15 02:09:18 +09:00
|
|
|
pub persistent_dirs: Vec<String>,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub allow_egress: Vec<String>,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub deny_egress: Vec<String>,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub env: BTreeMap<String, String>,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub volumes: Vec<VolumeConfigurationV1>,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub escalation: EscalationConfigurationV1,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fn default_isolate_gitignored() -> bool {
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-22 03:23:38 +09:00
|
|
|
fn default_persistent_dirs() -> Vec<String> {
|
|
|
|
|
DEFAULT_PERSISTENT_DIRS.map(str::to_owned).into()
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-15 02:09:18 +09:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
|
|
|
|
pub struct TrustedHostConfigurationV1 {
|
|
|
|
|
#[serde(rename = "schema")]
|
|
|
|
|
schema_id: String,
|
|
|
|
|
storage: StorageConfigurationV1,
|
|
|
|
|
network: NetworkConfigurationV1,
|
|
|
|
|
profiles: BTreeMap<String, ProfileConfigurationV1>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TrustedHostConfigurationV1 {
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub fn schema_id(&self) -> &str {
|
|
|
|
|
&self.schema_id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub const fn storage(&self) -> &StorageConfigurationV1 {
|
|
|
|
|
&self.storage
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub const fn network(&self) -> &NetworkConfigurationV1 {
|
|
|
|
|
&self.network
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub const fn profiles(&self) -> &BTreeMap<String, ProfileConfigurationV1> {
|
|
|
|
|
&self.profiles
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'de> Deserialize<'de> for TrustedHostConfigurationV1 {
|
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
|
|
where
|
|
|
|
|
D: serde::Deserializer<'de>,
|
|
|
|
|
{
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
|
struct Fields {
|
|
|
|
|
#[serde(rename = "schema")]
|
|
|
|
|
schema_id: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
storage: StorageConfigurationV1,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
network: NetworkConfigurationV1,
|
|
|
|
|
profiles: BTreeMap<String, ProfileConfigurationV1>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let fields = Fields::deserialize(deserializer)?;
|
|
|
|
|
if fields.schema_id != TRUSTED_HOST_CONFIGURATION_SCHEMA_ID {
|
|
|
|
|
return Err(serde::de::Error::custom(
|
|
|
|
|
"unknown trusted-host configuration schema",
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
if fields.profiles.is_empty() {
|
|
|
|
|
return Err(serde::de::Error::custom(
|
|
|
|
|
"trusted-host configuration has no profiles",
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
Ok(Self {
|
|
|
|
|
schema_id: fields.schema_id,
|
|
|
|
|
storage: fields.storage,
|
|
|
|
|
network: fields.network,
|
|
|
|
|
profiles: fields.profiles,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
|
|
|
|
pub struct EnvironmentReference {
|
|
|
|
|
pub sandbox_name: String,
|
|
|
|
|
pub host_name: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
|
|
|
|
pub struct ResolvedVolume {
|
|
|
|
|
pub canonical_host_source: HostPath,
|
|
|
|
|
pub container_path: String,
|
|
|
|
|
pub kind: VolumeKind,
|
|
|
|
|
pub access: VolumeAccess,
|
|
|
|
|
pub create: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
|
|
|
|
pub struct ResolvedAutoApprovalRule {
|
|
|
|
|
pub canonical_executable: HostPath,
|
|
|
|
|
pub args: Vec<String>,
|
|
|
|
|
pub allow_extra_args: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
|
|
|
|
pub struct ConfigurationFingerprintRecordV1 {
|
|
|
|
|
pub selected_profile: String,
|
|
|
|
|
pub storage_root: HostPath,
|
|
|
|
|
pub runtime_root: HostPath,
|
|
|
|
|
pub image: String,
|
|
|
|
|
pub isolate_gitignored: bool,
|
|
|
|
|
pub persistent_dirs: Vec<String>,
|
|
|
|
|
pub environment_references: Vec<EnvironmentReference>,
|
|
|
|
|
pub volumes: Vec<ResolvedVolume>,
|
|
|
|
|
pub effective_allow_prefixes: Vec<([u8; 4], u8)>,
|
|
|
|
|
pub effective_deny_prefixes: Vec<([u8; 4], u8)>,
|
|
|
|
|
pub escalation_enabled: bool,
|
|
|
|
|
pub auto_approval_rules: Vec<ResolvedAutoApprovalRule>,
|
|
|
|
|
}
|