Split record modules without changing wire behavior
This commit is contained in:
parent
ccf23ccac1
commit
6301eeba3e
8 changed files with 5674 additions and 5591 deletions
File diff suppressed because it is too large
Load diff
201
crates/lazaret-core/src/records/configuration.rs
Normal file
201
crates/lazaret-core/src/records/configuration.rs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
use super::{BTreeMap, Deserialize, HostPath, Serialize};
|
||||
|
||||
pub const DOMAIN_STORE_SCHEMA_ID: &str = "lazaret-domain-store-v2";
|
||||
pub const LEGACY_DOMAIN_STORE_SCHEMA_ID: &str = "lazaret-domain-store-v1";
|
||||
pub const STORAGE_CATALOG_SCHEMA_ID: &str = "lazaret-storage-catalog-v1";
|
||||
pub const DOMAIN_STORE_SCHEMA_VERSION: u32 = 2;
|
||||
pub const LEGACY_DOMAIN_STORE_SCHEMA_VERSION: u32 = 1;
|
||||
pub const STORAGE_CATALOG_SCHEMA_VERSION: u32 = 1;
|
||||
pub const TRUSTED_HOST_CONFIGURATION_SCHEMA_ID: &str = "lazaret-trusted-host-config-v1";
|
||||
|
||||
#[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,
|
||||
#[serde(default)]
|
||||
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
|
||||
}
|
||||
|
||||
#[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>,
|
||||
}
|
||||
632
crates/lazaret-core/src/records/credentials.rs
Normal file
632
crates/lazaret-core/src/records/credentials.rs
Normal file
|
|
@ -0,0 +1,632 @@
|
|||
use super::{
|
||||
CapabilityId, CertificateDigest, ClientId, ConfigurationFingerprint, CoordinatorId,
|
||||
Deserialize, DomainId, GenerationId, ProtocolCredentialId, RequestDigest, RequestId, SandboxId,
|
||||
Serialize, SessionId, StreamDigest, TrustManifestDigest, fmt,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum ProtocolVersion {
|
||||
V1,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum RecordInvariantError {
|
||||
CredentialBundleRoles,
|
||||
ParticipantSnapshotState,
|
||||
SessionTerminalSnapshotState,
|
||||
}
|
||||
|
||||
impl fmt::Display for RecordInvariantError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::CredentialBundleRoles => {
|
||||
formatter.write_str("credential bundle roles are invalid")
|
||||
}
|
||||
Self::ParticipantSnapshotState => {
|
||||
formatter.write_str("participant ownership snapshot is invalid for its role/state")
|
||||
}
|
||||
Self::SessionTerminalSnapshotState => {
|
||||
formatter.write_str("Session Terminal evidence is invalid for its state")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RecordInvariantError {}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum RecordTransitionError {
|
||||
RecordIdentityChanged,
|
||||
StateRegression,
|
||||
EvidenceChanged,
|
||||
EvidenceLost,
|
||||
ProcessStageSkipped,
|
||||
PriorAcquisitionSnapshotRequired,
|
||||
AbsenceProofRequired,
|
||||
InvalidCleanTarget,
|
||||
}
|
||||
|
||||
impl fmt::Display for RecordTransitionError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::RecordIdentityChanged => formatter.write_str("record identity changed"),
|
||||
Self::StateRegression => formatter.write_str("lifecycle transition is invalid"),
|
||||
Self::EvidenceChanged => formatter.write_str("recorded evidence changed"),
|
||||
Self::EvidenceLost => formatter.write_str("recorded evidence was cleared"),
|
||||
Self::ProcessStageSkipped => {
|
||||
formatter.write_str("process ownership did not follow the staged barrier")
|
||||
}
|
||||
Self::PriorAcquisitionSnapshotRequired => {
|
||||
formatter.write_str("active state requires a prior acquisition snapshot")
|
||||
}
|
||||
Self::AbsenceProofRequired => formatter.write_str("cleanup requires absence proof"),
|
||||
Self::InvalidCleanTarget => formatter.write_str("Clean target is not fully cleared"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RecordTransitionError {}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct GenerationIdentity {
|
||||
pub agent_profile: String,
|
||||
pub domain_id: DomainId,
|
||||
pub generation_id: GenerationId,
|
||||
pub configuration_fingerprint: ConfigurationFingerprint,
|
||||
pub coordinator_id: CoordinatorId,
|
||||
pub sandbox_id: SandboxId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct TrustManifest {
|
||||
pub protocol_version: ProtocolVersion,
|
||||
pub generation_identity: GenerationIdentity,
|
||||
pub tls_ca_sha256: CertificateDigest,
|
||||
pub protocol_issuer_public_key: Vec<u8>,
|
||||
pub issuer_signature: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum TlsRole {
|
||||
SessionCoordinator,
|
||||
ClientCli,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum TlsSubjectId {
|
||||
SessionCoordinator(CoordinatorId),
|
||||
ClientCli(ClientId),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||
pub struct TlsCredential {
|
||||
pub generation_identity: GenerationIdentity,
|
||||
role: TlsRole,
|
||||
subject_id: TlsSubjectId,
|
||||
pub certificate_der: Vec<u8>,
|
||||
pub certificate_sha256: CertificateDigest,
|
||||
}
|
||||
|
||||
impl TlsCredential {
|
||||
#[must_use]
|
||||
pub fn session_coordinator(
|
||||
generation_identity: GenerationIdentity,
|
||||
subject_id: CoordinatorId,
|
||||
certificate_der: Vec<u8>,
|
||||
certificate_sha256: CertificateDigest,
|
||||
) -> Self {
|
||||
Self {
|
||||
generation_identity,
|
||||
role: TlsRole::SessionCoordinator,
|
||||
subject_id: TlsSubjectId::SessionCoordinator(subject_id),
|
||||
certificate_der,
|
||||
certificate_sha256,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn client_cli(
|
||||
generation_identity: GenerationIdentity,
|
||||
subject_id: ClientId,
|
||||
certificate_der: Vec<u8>,
|
||||
certificate_sha256: CertificateDigest,
|
||||
) -> Self {
|
||||
Self {
|
||||
generation_identity,
|
||||
role: TlsRole::ClientCli,
|
||||
subject_id: TlsSubjectId::ClientCli(subject_id),
|
||||
certificate_der,
|
||||
certificate_sha256,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn role(&self) -> TlsRole {
|
||||
self.role
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn subject_id(&self) -> TlsSubjectId {
|
||||
self.subject_id
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for TlsCredential {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct Fields {
|
||||
generation_identity: GenerationIdentity,
|
||||
role: TlsRole,
|
||||
subject_id: TlsSubjectId,
|
||||
certificate_der: Vec<u8>,
|
||||
certificate_sha256: CertificateDigest,
|
||||
}
|
||||
|
||||
let fields = Fields::deserialize(deserializer)?;
|
||||
match (fields.role, fields.subject_id) {
|
||||
(TlsRole::SessionCoordinator, TlsSubjectId::SessionCoordinator(subject_id)) => {
|
||||
Ok(Self::session_coordinator(
|
||||
fields.generation_identity,
|
||||
subject_id,
|
||||
fields.certificate_der,
|
||||
fields.certificate_sha256,
|
||||
))
|
||||
}
|
||||
(TlsRole::ClientCli, TlsSubjectId::ClientCli(subject_id)) => Ok(Self::client_cli(
|
||||
fields.generation_identity,
|
||||
subject_id,
|
||||
fields.certificate_der,
|
||||
fields.certificate_sha256,
|
||||
)),
|
||||
_ => Err(serde::de::Error::custom(
|
||||
"TLS role does not match its subject identity",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct ProtocolPublicationIdentity {
|
||||
pub protocol_version: ProtocolVersion,
|
||||
pub generation_identity: GenerationIdentity,
|
||||
pub tls_ca_certificate_der: Vec<u8>,
|
||||
pub trust_manifest_sha256: TrustManifestDigest,
|
||||
pub coordinator_tls_certificate_sha256: CertificateDigest,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum ProtocolCredentialRole {
|
||||
SessionCoordinator,
|
||||
AgentSandbox,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum ProtocolSubjectId {
|
||||
SessionCoordinator(CoordinatorId),
|
||||
AgentSandbox(SandboxId),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||
pub struct ProtocolCredential {
|
||||
pub protocol_version: ProtocolVersion,
|
||||
pub credential_id: ProtocolCredentialId,
|
||||
pub generation_identity: GenerationIdentity,
|
||||
role: ProtocolCredentialRole,
|
||||
subject_id: ProtocolSubjectId,
|
||||
pub public_key: Vec<u8>,
|
||||
pub issuer_signature: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ProtocolCredential {
|
||||
#[must_use]
|
||||
pub fn session_coordinator(
|
||||
credential_id: ProtocolCredentialId,
|
||||
generation_identity: GenerationIdentity,
|
||||
subject_id: CoordinatorId,
|
||||
public_key: Vec<u8>,
|
||||
issuer_signature: Vec<u8>,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol_version: ProtocolVersion::V1,
|
||||
credential_id,
|
||||
generation_identity,
|
||||
role: ProtocolCredentialRole::SessionCoordinator,
|
||||
subject_id: ProtocolSubjectId::SessionCoordinator(subject_id),
|
||||
public_key,
|
||||
issuer_signature,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn agent_sandbox(
|
||||
credential_id: ProtocolCredentialId,
|
||||
generation_identity: GenerationIdentity,
|
||||
subject_id: SandboxId,
|
||||
public_key: Vec<u8>,
|
||||
issuer_signature: Vec<u8>,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol_version: ProtocolVersion::V1,
|
||||
credential_id,
|
||||
generation_identity,
|
||||
role: ProtocolCredentialRole::AgentSandbox,
|
||||
subject_id: ProtocolSubjectId::AgentSandbox(subject_id),
|
||||
public_key,
|
||||
issuer_signature,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn role(&self) -> ProtocolCredentialRole {
|
||||
self.role
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn subject_id(&self) -> ProtocolSubjectId {
|
||||
self.subject_id
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ProtocolCredential {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct Fields {
|
||||
protocol_version: ProtocolVersion,
|
||||
credential_id: ProtocolCredentialId,
|
||||
generation_identity: GenerationIdentity,
|
||||
role: ProtocolCredentialRole,
|
||||
subject_id: ProtocolSubjectId,
|
||||
public_key: Vec<u8>,
|
||||
issuer_signature: Vec<u8>,
|
||||
}
|
||||
|
||||
let fields = Fields::deserialize(deserializer)?;
|
||||
match fields.protocol_version {
|
||||
ProtocolVersion::V1 => {}
|
||||
}
|
||||
match (fields.role, fields.subject_id) {
|
||||
(
|
||||
ProtocolCredentialRole::SessionCoordinator,
|
||||
ProtocolSubjectId::SessionCoordinator(subject_id),
|
||||
) => Ok(Self::session_coordinator(
|
||||
fields.credential_id,
|
||||
fields.generation_identity,
|
||||
subject_id,
|
||||
fields.public_key,
|
||||
fields.issuer_signature,
|
||||
)),
|
||||
(ProtocolCredentialRole::AgentSandbox, ProtocolSubjectId::AgentSandbox(subject_id)) => {
|
||||
Ok(Self::agent_sandbox(
|
||||
fields.credential_id,
|
||||
fields.generation_identity,
|
||||
subject_id,
|
||||
fields.public_key,
|
||||
fields.issuer_signature,
|
||||
))
|
||||
}
|
||||
_ => Err(serde::de::Error::custom(
|
||||
"protocol credential role does not match its subject identity",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||
pub struct SandboxCredentialBundle {
|
||||
pub protocol_version: ProtocolVersion,
|
||||
pub generation_identity: GenerationIdentity,
|
||||
pub trust_manifest: TrustManifest,
|
||||
coordinator_protocol_credential: ProtocolCredential,
|
||||
sandbox_protocol_credential: ProtocolCredential,
|
||||
pub sandbox_private_key: Vec<u8>,
|
||||
}
|
||||
|
||||
impl SandboxCredentialBundle {
|
||||
/// Constructs a bundle only when its two credentials have their required roles.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error when either credential has the wrong role.
|
||||
pub fn new(
|
||||
generation_identity: GenerationIdentity,
|
||||
trust_manifest: TrustManifest,
|
||||
coordinator_protocol_credential: ProtocolCredential,
|
||||
sandbox_protocol_credential: ProtocolCredential,
|
||||
sandbox_private_key: Vec<u8>,
|
||||
) -> Result<Self, RecordInvariantError> {
|
||||
if coordinator_protocol_credential.role() != ProtocolCredentialRole::SessionCoordinator
|
||||
|| sandbox_protocol_credential.role() != ProtocolCredentialRole::AgentSandbox
|
||||
{
|
||||
return Err(RecordInvariantError::CredentialBundleRoles);
|
||||
}
|
||||
Ok(Self {
|
||||
protocol_version: ProtocolVersion::V1,
|
||||
generation_identity,
|
||||
trust_manifest,
|
||||
coordinator_protocol_credential,
|
||||
sandbox_protocol_credential,
|
||||
sandbox_private_key,
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn coordinator_protocol_credential(&self) -> &ProtocolCredential {
|
||||
&self.coordinator_protocol_credential
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn sandbox_protocol_credential(&self) -> &ProtocolCredential {
|
||||
&self.sandbox_protocol_credential
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SandboxCredentialBundle {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct Fields {
|
||||
protocol_version: ProtocolVersion,
|
||||
generation_identity: GenerationIdentity,
|
||||
trust_manifest: TrustManifest,
|
||||
coordinator_protocol_credential: ProtocolCredential,
|
||||
sandbox_protocol_credential: ProtocolCredential,
|
||||
sandbox_private_key: Vec<u8>,
|
||||
}
|
||||
|
||||
let fields = Fields::deserialize(deserializer)?;
|
||||
match fields.protocol_version {
|
||||
ProtocolVersion::V1 => {}
|
||||
}
|
||||
Self::new(
|
||||
fields.generation_identity,
|
||||
fields.trust_manifest,
|
||||
fields.coordinator_protocol_credential,
|
||||
fields.sandbox_protocol_credential,
|
||||
fields.sandbox_private_key,
|
||||
)
|
||||
.map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct SessionBinding {
|
||||
pub generation_identity: GenerationIdentity,
|
||||
pub cli_id: ClientId,
|
||||
pub session_id: SessionId,
|
||||
pub session_anchor_public_key: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct SessionRequestCapability {
|
||||
pub protocol_version: ProtocolVersion,
|
||||
pub capability_id: CapabilityId,
|
||||
pub session_binding: SessionBinding,
|
||||
pub coordinator_credential_id: ProtocolCredentialId,
|
||||
pub sandbox_credential_id: ProtocolCredentialId,
|
||||
pub coordinator_signature: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct SignedProtocolRecord {
|
||||
pub payload_bytes: Vec<u8>,
|
||||
pub signature: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||
pub struct MailboxRequest {
|
||||
protocol_version: ProtocolVersion,
|
||||
session_binding: SessionBinding,
|
||||
request_id: RequestId,
|
||||
session_capability: SessionRequestCapability,
|
||||
sandbox_credential_id: ProtocolCredentialId,
|
||||
command_argv: Vec<Vec<u8>>,
|
||||
justification: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum MailboxRequestError {
|
||||
EmptyCommand,
|
||||
InvalidArgument,
|
||||
NonAbsoluteExecutable,
|
||||
}
|
||||
|
||||
impl fmt::Display for MailboxRequestError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::EmptyCommand => formatter.write_str("command argument vector is empty"),
|
||||
Self::InvalidArgument => formatter.write_str("command argument is not NUL-free UTF-8"),
|
||||
Self::NonAbsoluteExecutable => {
|
||||
formatter.write_str("command executable is not an absolute path")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for MailboxRequestError {}
|
||||
|
||||
impl MailboxRequest {
|
||||
/// # Errors
|
||||
/// The command must contain an absolute executable followed by NUL-free UTF-8 arguments.
|
||||
pub fn new(
|
||||
session_binding: SessionBinding,
|
||||
request_id: RequestId,
|
||||
session_capability: SessionRequestCapability,
|
||||
sandbox_credential_id: ProtocolCredentialId,
|
||||
command_argv: Vec<Vec<u8>>,
|
||||
justification: String,
|
||||
) -> Result<Self, MailboxRequestError> {
|
||||
let Some(executable) = command_argv.first() else {
|
||||
return Err(MailboxRequestError::EmptyCommand);
|
||||
};
|
||||
if command_argv
|
||||
.iter()
|
||||
.any(|argument| argument.contains(&0) || std::str::from_utf8(argument).is_err())
|
||||
{
|
||||
return Err(MailboxRequestError::InvalidArgument);
|
||||
}
|
||||
if !executable.starts_with(b"/") {
|
||||
return Err(MailboxRequestError::NonAbsoluteExecutable);
|
||||
}
|
||||
Ok(Self {
|
||||
protocol_version: ProtocolVersion::V1,
|
||||
session_binding,
|
||||
request_id,
|
||||
session_capability,
|
||||
sandbox_credential_id,
|
||||
command_argv,
|
||||
justification,
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn protocol_version(&self) -> ProtocolVersion {
|
||||
self.protocol_version
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn session_binding(&self) -> &SessionBinding {
|
||||
&self.session_binding
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn request_id(&self) -> RequestId {
|
||||
self.request_id
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn session_capability(&self) -> &SessionRequestCapability {
|
||||
&self.session_capability
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn sandbox_credential_id(&self) -> ProtocolCredentialId {
|
||||
self.sandbox_credential_id
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn command_argv(&self) -> &[Vec<u8>] {
|
||||
&self.command_argv
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn justification(&self) -> &str {
|
||||
&self.justification
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for MailboxRequest {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct Fields {
|
||||
protocol_version: ProtocolVersion,
|
||||
session_binding: SessionBinding,
|
||||
request_id: RequestId,
|
||||
session_capability: SessionRequestCapability,
|
||||
sandbox_credential_id: ProtocolCredentialId,
|
||||
command_argv: Vec<Vec<u8>>,
|
||||
justification: String,
|
||||
}
|
||||
|
||||
let fields = Fields::deserialize(deserializer)?;
|
||||
if fields.protocol_version != ProtocolVersion::V1 {
|
||||
return Err(serde::de::Error::custom("unknown mailbox request version"));
|
||||
}
|
||||
Self::new(
|
||||
fields.session_binding,
|
||||
fields.request_id,
|
||||
fields.session_capability,
|
||||
fields.sandbox_credential_id,
|
||||
fields.command_argv,
|
||||
fields.justification,
|
||||
)
|
||||
.map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct MailboxCancellation {
|
||||
pub protocol_version: ProtocolVersion,
|
||||
pub session_binding: SessionBinding,
|
||||
pub request_id: RequestId,
|
||||
pub session_capability: SessionRequestCapability,
|
||||
pub sandbox_credential_id: ProtocolCredentialId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct MailboxResultAck {
|
||||
pub protocol_version: ProtocolVersion,
|
||||
pub session_binding: SessionBinding,
|
||||
pub request_id: RequestId,
|
||||
pub request_sha256: RequestDigest,
|
||||
pub session_capability: SessionRequestCapability,
|
||||
pub sandbox_credential_id: ProtocolCredentialId,
|
||||
pub highest_contiguous_sequence: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum DeveloperDecisionKind {
|
||||
Approve,
|
||||
Deny,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct DeveloperDecision {
|
||||
pub protocol_version: ProtocolVersion,
|
||||
pub session_binding: SessionBinding,
|
||||
pub request_id: RequestId,
|
||||
pub request_sha256: RequestDigest,
|
||||
pub decision: DeveloperDecisionKind,
|
||||
pub approved_command_argv: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum EscalationOutcome {
|
||||
Completed,
|
||||
Denied,
|
||||
TimedOut,
|
||||
Cancelled,
|
||||
Failed,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct MailboxResult {
|
||||
pub protocol_version: ProtocolVersion,
|
||||
pub session_binding: SessionBinding,
|
||||
pub request_id: RequestId,
|
||||
pub request_sha256: RequestDigest,
|
||||
pub coordinator_credential_id: ProtocolCredentialId,
|
||||
pub outcome: EscalationOutcome,
|
||||
pub exit_code: Option<i32>,
|
||||
pub chunk_count: u64,
|
||||
pub stdout_sha256: StreamDigest,
|
||||
pub stderr_sha256: StreamDigest,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum ResultStream {
|
||||
Stdout,
|
||||
Stderr,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct MailboxResultChunk {
|
||||
pub protocol_version: ProtocolVersion,
|
||||
pub session_binding: SessionBinding,
|
||||
pub request_id: RequestId,
|
||||
pub request_sha256: RequestDigest,
|
||||
pub coordinator_credential_id: ProtocolCredentialId,
|
||||
pub sequence: u64,
|
||||
pub stream: ResultStream,
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
1535
crates/lazaret-core/src/records/lifecycle.rs
Normal file
1535
crates/lazaret-core/src/records/lifecycle.rs
Normal file
File diff suppressed because it is too large
Load diff
373
crates/lazaret-core/src/records/network.rs
Normal file
373
crates/lazaret-core/src/records/network.rs
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
use super::{
|
||||
ConfigurationFingerprint, DerivedPolicyDigest, Deserialize, DomainId, GenerationId,
|
||||
NamespaceIdentity, NetworkSidecarId, PidfsProcessIdentityV1, ReadinessFingerprint, Serialize,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum AddressFamily {
|
||||
Ipv4,
|
||||
Ipv6,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum AddressFamilyDispositionValue {
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct AddressFamilyDisposition {
|
||||
pub family: AddressFamily,
|
||||
pub disposition: AddressFamilyDispositionValue,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub enum IpAddress {
|
||||
V4([u8; 4]),
|
||||
V6([u8; 16]),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct IpNetwork {
|
||||
pub address: IpAddress,
|
||||
pub prefix_length: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum PolicyAction {
|
||||
Allow,
|
||||
Deny,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct DefaultAction {
|
||||
pub family: AddressFamily,
|
||||
pub action: PolicyAction,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct PrefixRule {
|
||||
pub family: AddressFamily,
|
||||
pub network: IpNetwork,
|
||||
pub action: PolicyAction,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct Resolver {
|
||||
pub family: AddressFamily,
|
||||
pub address: IpAddress,
|
||||
}
|
||||
|
||||
pub const DERIVED_NETWORK_POLICY_SCHEMA_ID: &str = "lazaret-derived-network-policy-v1";
|
||||
pub const NETWORK_READINESS_ATTESTATION_SCHEMA_ID: &str = "lazaret-network-attestation-v1";
|
||||
pub const NETWORK_READINESS_IDENTITY_SCHEMA_ID: &str = "lazaret-network-readiness-identity-v1";
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct DerivedNetworkPolicyFields {
|
||||
pub address_families: Vec<AddressFamilyDisposition>,
|
||||
pub default_actions: Vec<DefaultAction>,
|
||||
pub prefix_rules: Vec<PrefixRule>,
|
||||
pub resolvers: Vec<Resolver>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||
pub struct DerivedNetworkPolicy {
|
||||
schema_id: String,
|
||||
pub address_families: Vec<AddressFamilyDisposition>,
|
||||
pub default_actions: Vec<DefaultAction>,
|
||||
pub prefix_rules: Vec<PrefixRule>,
|
||||
pub resolvers: Vec<Resolver>,
|
||||
}
|
||||
|
||||
impl DerivedNetworkPolicy {
|
||||
#[must_use]
|
||||
pub fn v1(fields: DerivedNetworkPolicyFields) -> Self {
|
||||
Self {
|
||||
schema_id: DERIVED_NETWORK_POLICY_SCHEMA_ID.into(),
|
||||
address_families: fields.address_families,
|
||||
default_actions: fields.default_actions,
|
||||
prefix_rules: fields.prefix_rules,
|
||||
resolvers: fields.resolvers,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn schema_id(&self) -> &str {
|
||||
&self.schema_id
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for DerivedNetworkPolicy {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct Fields {
|
||||
schema_id: String,
|
||||
address_families: Vec<AddressFamilyDisposition>,
|
||||
default_actions: Vec<DefaultAction>,
|
||||
prefix_rules: Vec<PrefixRule>,
|
||||
resolvers: Vec<Resolver>,
|
||||
}
|
||||
|
||||
let fields = Fields::deserialize(deserializer)?;
|
||||
if fields.schema_id != DERIVED_NETWORK_POLICY_SCHEMA_ID {
|
||||
return Err(serde::de::Error::custom(
|
||||
"unknown derived-network-policy schema identity",
|
||||
));
|
||||
}
|
||||
Ok(Self::v1(DerivedNetworkPolicyFields {
|
||||
address_families: fields.address_families,
|
||||
default_actions: fields.default_actions,
|
||||
prefix_rules: fields.prefix_rules,
|
||||
resolvers: fields.resolvers,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum PolicyDelivery {
|
||||
SealedMemfd,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct NetworkObservation {
|
||||
pub policy_delivery: PolicyDelivery,
|
||||
pub helper_ready: bool,
|
||||
pub observed_routes: Vec<IpNetwork>,
|
||||
pub routes_attested: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct NetworkReadinessAttestationFields {
|
||||
pub domain_id: DomainId,
|
||||
pub generation_id: GenerationId,
|
||||
pub configuration_fingerprint: ConfigurationFingerprint,
|
||||
pub network_sidecar_id: NetworkSidecarId,
|
||||
pub derived_policy_digest: DerivedPolicyDigest,
|
||||
pub enforcement_namespace: NamespaceIdentity,
|
||||
pub runtime_supervisor: PidfsProcessIdentityV1,
|
||||
pub connectivity_helper: PidfsProcessIdentityV1,
|
||||
pub observation: NetworkObservation,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||
pub struct NetworkReadinessAttestation {
|
||||
schema_id: String,
|
||||
pub domain_id: DomainId,
|
||||
pub generation_id: GenerationId,
|
||||
pub configuration_fingerprint: ConfigurationFingerprint,
|
||||
pub network_sidecar_id: NetworkSidecarId,
|
||||
pub derived_policy_digest: DerivedPolicyDigest,
|
||||
pub enforcement_namespace: NamespaceIdentity,
|
||||
pub runtime_supervisor: PidfsProcessIdentityV1,
|
||||
pub connectivity_helper: PidfsProcessIdentityV1,
|
||||
pub observation: NetworkObservation,
|
||||
}
|
||||
|
||||
impl NetworkReadinessAttestation {
|
||||
#[must_use]
|
||||
pub fn v1(fields: NetworkReadinessAttestationFields) -> Self {
|
||||
Self {
|
||||
schema_id: NETWORK_READINESS_ATTESTATION_SCHEMA_ID.into(),
|
||||
domain_id: fields.domain_id,
|
||||
generation_id: fields.generation_id,
|
||||
configuration_fingerprint: fields.configuration_fingerprint,
|
||||
network_sidecar_id: fields.network_sidecar_id,
|
||||
derived_policy_digest: fields.derived_policy_digest,
|
||||
enforcement_namespace: fields.enforcement_namespace,
|
||||
runtime_supervisor: fields.runtime_supervisor,
|
||||
connectivity_helper: fields.connectivity_helper,
|
||||
observation: fields.observation,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn schema_id(&self) -> &str {
|
||||
&self.schema_id
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for NetworkReadinessAttestation {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct Fields {
|
||||
schema_id: String,
|
||||
domain_id: DomainId,
|
||||
generation_id: GenerationId,
|
||||
configuration_fingerprint: ConfigurationFingerprint,
|
||||
network_sidecar_id: NetworkSidecarId,
|
||||
derived_policy_digest: DerivedPolicyDigest,
|
||||
enforcement_namespace: NamespaceIdentity,
|
||||
runtime_supervisor: PidfsProcessIdentityV1,
|
||||
connectivity_helper: PidfsProcessIdentityV1,
|
||||
observation: NetworkObservation,
|
||||
}
|
||||
|
||||
let fields = Fields::deserialize(deserializer)?;
|
||||
if fields.schema_id != NETWORK_READINESS_ATTESTATION_SCHEMA_ID {
|
||||
return Err(serde::de::Error::custom(
|
||||
"unknown network-readiness-attestation schema identity",
|
||||
));
|
||||
}
|
||||
Ok(Self::v1(NetworkReadinessAttestationFields {
|
||||
domain_id: fields.domain_id,
|
||||
generation_id: fields.generation_id,
|
||||
configuration_fingerprint: fields.configuration_fingerprint,
|
||||
network_sidecar_id: fields.network_sidecar_id,
|
||||
derived_policy_digest: fields.derived_policy_digest,
|
||||
enforcement_namespace: fields.enforcement_namespace,
|
||||
runtime_supervisor: fields.runtime_supervisor,
|
||||
connectivity_helper: fields.connectivity_helper,
|
||||
observation: fields.observation,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct NetworkReadinessIdentityFields {
|
||||
pub domain_id: DomainId,
|
||||
pub generation_id: GenerationId,
|
||||
pub configuration_fingerprint: ConfigurationFingerprint,
|
||||
pub network_sidecar_id: NetworkSidecarId,
|
||||
pub derived_policy_digest: DerivedPolicyDigest,
|
||||
pub enforcement_namespace: NamespaceIdentity,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||
pub struct NetworkReadinessIdentity {
|
||||
schema_id: String,
|
||||
pub domain_id: DomainId,
|
||||
pub generation_id: GenerationId,
|
||||
pub configuration_fingerprint: ConfigurationFingerprint,
|
||||
pub network_sidecar_id: NetworkSidecarId,
|
||||
pub derived_policy_digest: DerivedPolicyDigest,
|
||||
pub enforcement_namespace: NamespaceIdentity,
|
||||
}
|
||||
|
||||
impl NetworkReadinessIdentity {
|
||||
#[must_use]
|
||||
pub fn v1(fields: NetworkReadinessIdentityFields) -> Self {
|
||||
Self {
|
||||
schema_id: NETWORK_READINESS_IDENTITY_SCHEMA_ID.into(),
|
||||
domain_id: fields.domain_id,
|
||||
generation_id: fields.generation_id,
|
||||
configuration_fingerprint: fields.configuration_fingerprint,
|
||||
network_sidecar_id: fields.network_sidecar_id,
|
||||
derived_policy_digest: fields.derived_policy_digest,
|
||||
enforcement_namespace: fields.enforcement_namespace,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn schema_id(&self) -> &str {
|
||||
&self.schema_id
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for NetworkReadinessIdentity {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct Fields {
|
||||
schema_id: String,
|
||||
domain_id: DomainId,
|
||||
generation_id: GenerationId,
|
||||
configuration_fingerprint: ConfigurationFingerprint,
|
||||
network_sidecar_id: NetworkSidecarId,
|
||||
derived_policy_digest: DerivedPolicyDigest,
|
||||
enforcement_namespace: NamespaceIdentity,
|
||||
}
|
||||
|
||||
let fields = Fields::deserialize(deserializer)?;
|
||||
if fields.schema_id != NETWORK_READINESS_IDENTITY_SCHEMA_ID {
|
||||
return Err(serde::de::Error::custom(
|
||||
"unknown network-readiness-identity schema identity",
|
||||
));
|
||||
}
|
||||
Ok(Self::v1(NetworkReadinessIdentityFields {
|
||||
domain_id: fields.domain_id,
|
||||
generation_id: fields.generation_id,
|
||||
configuration_fingerprint: fields.configuration_fingerprint,
|
||||
network_sidecar_id: fields.network_sidecar_id,
|
||||
derived_policy_digest: fields.derived_policy_digest,
|
||||
enforcement_namespace: fields.enforcement_namespace,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct DeniedDestinationObservation {
|
||||
pub destination: IpAddress,
|
||||
pub unreachable: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct PermittedDestinationObservation {
|
||||
pub destination: IpAddress,
|
||||
pub reachable: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct DnsObservation {
|
||||
pub resolver: IpAddress,
|
||||
pub query_name: String,
|
||||
pub resolved_addresses: Vec<IpAddress>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct NetworkReachabilityProbe {
|
||||
pub domain_id: DomainId,
|
||||
pub generation_id: GenerationId,
|
||||
pub enforcement_namespace: NamespaceIdentity,
|
||||
pub denied_destinations: Vec<DeniedDestinationObservation>,
|
||||
pub permitted_destinations: Vec<PermittedDestinationObservation>,
|
||||
pub dns_observations: Vec<DnsObservation>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct NetworkReadinessEvidence {
|
||||
pub domain_id: DomainId,
|
||||
pub generation_id: GenerationId,
|
||||
pub network_sidecar_id: NetworkSidecarId,
|
||||
pub readiness_fingerprint: ReadinessFingerprint,
|
||||
pub attestation: NetworkReadinessAttestation,
|
||||
pub readiness_identity: NetworkReadinessIdentity,
|
||||
pub reachability_probe: NetworkReachabilityProbe,
|
||||
}
|
||||
|
||||
impl NetworkReadinessEvidence {
|
||||
pub(super) fn matches(
|
||||
&self,
|
||||
generation_id: GenerationId,
|
||||
derived_policy_digest: DerivedPolicyDigest,
|
||||
readiness_fingerprint: ReadinessFingerprint,
|
||||
namespace: NamespaceIdentity,
|
||||
runtime_supervisor: &PidfsProcessIdentityV1,
|
||||
) -> bool {
|
||||
self.generation_id == generation_id
|
||||
&& self.readiness_fingerprint == readiness_fingerprint
|
||||
&& self.domain_id == self.attestation.domain_id
|
||||
&& self.domain_id == self.readiness_identity.domain_id
|
||||
&& self.domain_id == self.reachability_probe.domain_id
|
||||
&& self.generation_id == self.attestation.generation_id
|
||||
&& self.generation_id == self.readiness_identity.generation_id
|
||||
&& self.generation_id == self.reachability_probe.generation_id
|
||||
&& self.network_sidecar_id == self.attestation.network_sidecar_id
|
||||
&& self.network_sidecar_id == self.readiness_identity.network_sidecar_id
|
||||
&& self.attestation.configuration_fingerprint
|
||||
== self.readiness_identity.configuration_fingerprint
|
||||
&& self.attestation.derived_policy_digest == derived_policy_digest
|
||||
&& self.readiness_identity.derived_policy_digest == derived_policy_digest
|
||||
&& self.attestation.enforcement_namespace == namespace
|
||||
&& self.readiness_identity.enforcement_namespace == namespace
|
||||
&& self.reachability_probe.enforcement_namespace == namespace
|
||||
&& self.attestation.runtime_supervisor == *runtime_supervisor
|
||||
}
|
||||
}
|
||||
138
crates/lazaret-core/src/records/registry.rs
Normal file
138
crates/lazaret-core/src/records/registry.rs
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
use super::{
|
||||
AddressFamily, AddressFamilyDisposition, ArtifactLease, ArtifactState,
|
||||
ConfigurationFingerprintRecordV1, DefaultAction, DeniedDestinationObservation,
|
||||
DerivedNetworkPolicy, DeveloperDecision, DeveloperDecisionKind, DnsObservation, Domain,
|
||||
DomainStore, EnvironmentReference, EscalationOutcome, Generation, GenerationIdentity,
|
||||
GitRoutingOwnership, GuestProcessIdentity, HelperOwnership, HostProcessIdentity,
|
||||
HostProcessOwnershipSlot, LegacyUnverifiedProcessIdentityV1, Mailbox, MailboxCancellation,
|
||||
MailboxRequest, MailboxResult, MailboxResultAck, MailboxResultChunk, MountIdentity,
|
||||
NamespaceIdentity, NetworkObservation, NetworkOwnership, NetworkReachabilityProbe,
|
||||
NetworkReadinessAttestation, NetworkReadinessEvidence, NetworkReadinessIdentity, Participant,
|
||||
ParticipantOwnership, ParticipantState, PermittedDestinationObservation,
|
||||
PersistedHostProcessOwnership, PidfsProcessAuthorityV1, PidfsProcessIdentityV1, PolicyAction,
|
||||
PolicyDelivery, PrefixRule, PrepareOperation, PreparedArtifact, PreparedLowerOwnership,
|
||||
ProcessOwnershipCommitAck, ProcessOwnershipMessageVersion, ProtocolCredential,
|
||||
ProtocolCredentialRole, ProtocolPublicationIdentity, PtyIdentity, PublicationOwnership,
|
||||
ResolvedAutoApprovalRule, ResolvedVolume, Resolver, ResultStream, SandboxCredentialBundle,
|
||||
SandboxOwnership, Session, SessionBinding, SessionClose, SessionExit, SessionExitOutcome,
|
||||
SessionRequestCapability, SessionTerminal, SessionTerminalEvent, SessionTerminalMessage,
|
||||
SessionTerminalState, SignedProtocolRecord, StagedProcessOwnership, StateAnchorOwnership,
|
||||
StorageCatalog, StoreMetadata, TerminalInput, TerminalOutput, TerminalResize, TerminalSignal,
|
||||
TerminalSignalKind, TlsCredential, TlsRole, TrustManifest, TrustedHostConfigurationV1,
|
||||
};
|
||||
|
||||
pub trait SchemaType {
|
||||
const NAME: &'static str;
|
||||
}
|
||||
|
||||
macro_rules! schema_types {
|
||||
($($name:literal => $record:ty),+; $($declared:literal),* $(,)?) => {
|
||||
$(
|
||||
impl SchemaType for $record {
|
||||
const NAME: &'static str = $name;
|
||||
}
|
||||
)+
|
||||
|
||||
pub const IMPLEMENTED_SERIALIZED_SCHEMA_TYPE_NAMES: &[&str] = &[$($name),+];
|
||||
pub const DECLARED_SERIALIZED_SCHEMA_TYPE_NAMES: &[&str] = &[
|
||||
$($name),+,
|
||||
$($declared),*
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
schema_types!(
|
||||
"StoreMetadata" => StoreMetadata,
|
||||
"Domain" => Domain,
|
||||
"Generation" => Generation,
|
||||
"Participant" => Participant,
|
||||
"ParticipantState" => ParticipantState,
|
||||
"ParticipantOwnership" => ParticipantOwnership,
|
||||
"Session" => Session,
|
||||
"SessionTerminal" => SessionTerminal,
|
||||
"SessionTerminalState" => SessionTerminalState,
|
||||
"Mailbox" => Mailbox,
|
||||
"PtyIdentity" => PtyIdentity,
|
||||
"PidfsProcessAuthorityV1" => PidfsProcessAuthorityV1,
|
||||
"PidfsProcessIdentityV1" => PidfsProcessIdentityV1,
|
||||
"LegacyUnverifiedProcessIdentityV1" => LegacyUnverifiedProcessIdentityV1,
|
||||
"HostProcessIdentity" => HostProcessIdentity,
|
||||
"PersistedHostProcessOwnership" => PersistedHostProcessOwnership,
|
||||
"HostProcessOwnershipSlot" => HostProcessOwnershipSlot,
|
||||
"ProcessOwnershipMessageVersion" => ProcessOwnershipMessageVersion,
|
||||
"StagedProcessOwnership" => StagedProcessOwnership,
|
||||
"ProcessOwnershipCommitAck" => ProcessOwnershipCommitAck,
|
||||
"GuestProcessIdentity" => GuestProcessIdentity,
|
||||
"SessionTerminalEvent" => SessionTerminalEvent,
|
||||
"TerminalInput" => TerminalInput,
|
||||
"TerminalResize" => TerminalResize,
|
||||
"TerminalSignal" => TerminalSignal,
|
||||
"TerminalSignalKind" => TerminalSignalKind,
|
||||
"SessionClose" => SessionClose,
|
||||
"SessionTerminalMessage" => SessionTerminalMessage,
|
||||
"TerminalOutput" => TerminalOutput,
|
||||
"SessionExit" => SessionExit,
|
||||
"SessionExitOutcome" => SessionExitOutcome,
|
||||
"GenerationIdentity" => GenerationIdentity,
|
||||
"TrustManifest" => TrustManifest,
|
||||
"TlsCredential" => TlsCredential,
|
||||
"ProtocolPublicationIdentity" => ProtocolPublicationIdentity,
|
||||
"SandboxCredentialBundle" => SandboxCredentialBundle,
|
||||
"TlsRole" => TlsRole,
|
||||
"ProtocolCredential" => ProtocolCredential,
|
||||
"ProtocolCredentialRole" => ProtocolCredentialRole,
|
||||
"SessionBinding" => SessionBinding,
|
||||
"SessionRequestCapability" => SessionRequestCapability,
|
||||
"SignedProtocolRecord" => SignedProtocolRecord,
|
||||
"MailboxRequest" => MailboxRequest,
|
||||
"MailboxCancellation" => MailboxCancellation,
|
||||
"MailboxResultAck" => MailboxResultAck,
|
||||
"DeveloperDecision" => DeveloperDecision,
|
||||
"DeveloperDecisionKind" => DeveloperDecisionKind,
|
||||
"MailboxResult" => MailboxResult,
|
||||
"MailboxResultChunk" => MailboxResultChunk,
|
||||
"ResultStream" => ResultStream,
|
||||
"EscalationOutcome" => EscalationOutcome,
|
||||
"NamespaceIdentity" => NamespaceIdentity,
|
||||
"MountIdentity" => MountIdentity,
|
||||
"PreparedLowerOwnership" => PreparedLowerOwnership,
|
||||
"StateAnchorOwnership" => StateAnchorOwnership,
|
||||
"GitRoutingOwnership" => GitRoutingOwnership,
|
||||
"NetworkOwnership" => NetworkOwnership,
|
||||
"HelperOwnership" => HelperOwnership,
|
||||
"SandboxOwnership" => SandboxOwnership,
|
||||
"PublicationOwnership" => PublicationOwnership,
|
||||
"PreparedArtifact" => PreparedArtifact,
|
||||
"ArtifactState" => ArtifactState,
|
||||
"ArtifactLease" => ArtifactLease,
|
||||
"PrepareOperation" => PrepareOperation,
|
||||
"AddressFamilyDisposition" => AddressFamilyDisposition,
|
||||
"DefaultAction" => DefaultAction,
|
||||
"PrefixRule" => PrefixRule,
|
||||
"Resolver" => Resolver,
|
||||
"AddressFamily" => AddressFamily,
|
||||
"PolicyAction" => PolicyAction,
|
||||
"DerivedNetworkPolicy" => DerivedNetworkPolicy,
|
||||
"NetworkReadinessEvidence" => NetworkReadinessEvidence,
|
||||
"NetworkReadinessAttestation" => NetworkReadinessAttestation,
|
||||
"NetworkObservation" => NetworkObservation,
|
||||
"PolicyDelivery" => PolicyDelivery,
|
||||
"NetworkReadinessIdentity" => NetworkReadinessIdentity,
|
||||
"NetworkReachabilityProbe" => NetworkReachabilityProbe,
|
||||
"DeniedDestinationObservation" => DeniedDestinationObservation,
|
||||
"PermittedDestinationObservation" => PermittedDestinationObservation,
|
||||
"DnsObservation" => DnsObservation,
|
||||
"TrustedHostConfigurationV1" => TrustedHostConfigurationV1,
|
||||
"ConfigurationFingerprintRecordV1" => ConfigurationFingerprintRecordV1,
|
||||
"EnvironmentReference" => EnvironmentReference,
|
||||
"ResolvedVolume" => ResolvedVolume,
|
||||
"ResolvedAutoApprovalRule" => ResolvedAutoApprovalRule,
|
||||
"DomainStore" => DomainStore,
|
||||
"StorageCatalog" => StorageCatalog;
|
||||
);
|
||||
|
||||
pub const DECLARED_RUNTIME_SCHEMA_TYPE_NAMES: &[&str] = &[
|
||||
"ResolvedDomainConfiguration",
|
||||
"SandboxLayoutV1",
|
||||
"ConfinedPathPlan",
|
||||
];
|
||||
49
crates/lazaret-core/src/records/storage.rs
Normal file
49
crates/lazaret-core/src/records/storage.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use super::{
|
||||
Deserialize, DeviceId, DomainId, GenerationId, HostPath, IdMapDigest, InodeId, PolicyId,
|
||||
PrepareOperationId, PreparedArtifactId, SelectedManifestDigest, Serialize, SourceIdentity,
|
||||
StorageCatalogMetadata,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum ArtifactState {
|
||||
Available,
|
||||
Reclaiming,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct PreparedArtifact {
|
||||
pub prepared_artifact_id: PreparedArtifactId,
|
||||
pub source_identity: SourceIdentity,
|
||||
pub selected_manifest_digest: SelectedManifestDigest,
|
||||
pub policy_id: PolicyId,
|
||||
pub id_map_digest: IdMapDigest,
|
||||
pub path: HostPath,
|
||||
pub device: DeviceId,
|
||||
pub inode: InodeId,
|
||||
pub state: ArtifactState,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct ArtifactLease {
|
||||
pub prepared_artifact_id: PreparedArtifactId,
|
||||
pub domain_id: DomainId,
|
||||
pub generation_id: GenerationId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct PrepareOperation {
|
||||
pub operation_id: PrepareOperationId,
|
||||
pub expected_artifact_id: PreparedArtifactId,
|
||||
pub source_identity: SourceIdentity,
|
||||
pub stage_path: HostPath,
|
||||
pub device: DeviceId,
|
||||
pub inode: InodeId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct StorageCatalog {
|
||||
pub metadata: StorageCatalogMetadata,
|
||||
pub prepared_artifacts: Vec<PreparedArtifact>,
|
||||
pub artifact_leases: Vec<ArtifactLease>,
|
||||
pub prepare_operations: Vec<PrepareOperation>,
|
||||
}
|
||||
138
crates/lazaret-core/src/records/terminal.rs
Normal file
138
crates/lazaret-core/src/records/terminal.rs
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
use super::{Deserialize, NonZeroU16, Serialize, SessionId};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct TerminalInput {
|
||||
pub session_id: SessionId,
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct TerminalResize {
|
||||
pub session_id: SessionId,
|
||||
pub rows: NonZeroU16,
|
||||
pub columns: NonZeroU16,
|
||||
pub pixel_width: u16,
|
||||
pub pixel_height: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum TerminalSignalKind {
|
||||
Interrupt,
|
||||
Quit,
|
||||
Suspend,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct TerminalSignal {
|
||||
pub session_id: SessionId,
|
||||
pub signal: TerminalSignalKind,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct SessionClose {
|
||||
pub session_id: SessionId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum SessionTerminalEvent {
|
||||
Input(TerminalInput),
|
||||
Resize(TerminalResize),
|
||||
Signal(TerminalSignal),
|
||||
Close(SessionClose),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct TerminalOutput {
|
||||
pub session_id: SessionId,
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum SessionExitOutcome {
|
||||
Exited,
|
||||
Terminated,
|
||||
RuntimeLost,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
|
||||
pub struct SessionExit {
|
||||
session_id: SessionId,
|
||||
outcome: SessionExitOutcome,
|
||||
exit_code: Option<i32>,
|
||||
}
|
||||
|
||||
impl SessionExit {
|
||||
#[must_use]
|
||||
pub const fn exited(session_id: SessionId, exit_code: i32) -> Self {
|
||||
Self {
|
||||
session_id,
|
||||
outcome: SessionExitOutcome::Exited,
|
||||
exit_code: Some(exit_code),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn terminated(session_id: SessionId) -> Self {
|
||||
Self {
|
||||
session_id,
|
||||
outcome: SessionExitOutcome::Terminated,
|
||||
exit_code: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn runtime_lost(session_id: SessionId) -> Self {
|
||||
Self {
|
||||
session_id,
|
||||
outcome: SessionExitOutcome::RuntimeLost,
|
||||
exit_code: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn session_id(self) -> SessionId {
|
||||
self.session_id
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn outcome(self) -> SessionExitOutcome {
|
||||
self.outcome
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn exit_code(self) -> Option<i32> {
|
||||
self.exit_code
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SessionExit {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct Fields {
|
||||
session_id: SessionId,
|
||||
outcome: SessionExitOutcome,
|
||||
exit_code: Option<i32>,
|
||||
}
|
||||
|
||||
let fields = Fields::deserialize(deserializer)?;
|
||||
match (fields.outcome, fields.exit_code) {
|
||||
(SessionExitOutcome::Exited, Some(exit_code)) => {
|
||||
Ok(Self::exited(fields.session_id, exit_code))
|
||||
}
|
||||
(SessionExitOutcome::Terminated, None) => Ok(Self::terminated(fields.session_id)),
|
||||
(SessionExitOutcome::RuntimeLost, None) => Ok(Self::runtime_lost(fields.session_id)),
|
||||
_ => Err(serde::de::Error::custom(
|
||||
"exit_code must be present exactly for Exited",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum SessionTerminalMessage {
|
||||
Output(TerminalOutput),
|
||||
Exit(SessionExit),
|
||||
}
|
||||
Loading…
Reference in a new issue