Give Codex Connector its own Idunn health identity
This commit is contained in:
parent
366225eeae
commit
155fac6d22
8 changed files with 872 additions and 13 deletions
|
|
@ -190,6 +190,8 @@ pub fn serve(config_path: &Path) -> Result<(), CodexDaemonError> {
|
|||
config.callers.len(),
|
||||
readiness.auth_mode
|
||||
);
|
||||
#[cfg(target_os = "linux")]
|
||||
start_idunn_health_publisher()?;
|
||||
|
||||
for accepted in listener.incoming() {
|
||||
let stream = match accepted {
|
||||
|
|
@ -224,6 +226,52 @@ pub fn serve(config_path: &Path) -> Result<(), CodexDaemonError> {
|
|||
Err(CodexDaemonError::ListenerClosed)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn start_idunn_health_publisher() -> Result<(), CodexDaemonError> {
|
||||
let Some(endpoint) = std::env::var_os("CODEX_CONNECTOR_IDUNN_RUDP") else {
|
||||
return Ok(());
|
||||
};
|
||||
let endpoint = endpoint
|
||||
.to_string_lossy()
|
||||
.parse::<SocketAddr>()
|
||||
.map_err(|_| CodexDaemonError::InvalidConfig("Idunn health endpoint"))?;
|
||||
let daemon_id = required_environment("CODEX_CONNECTOR_IDUNN_DAEMON")?;
|
||||
let runtime_id = required_environment("CODEX_CONNECTOR_RUNTIME_ID")?;
|
||||
let contract = std::env::var("CODEX_CONNECTOR_IDUNN_HEALTH_CONTRACT")
|
||||
.unwrap_or_else(|_| crate::CODEX_CONNECTOR_IDUNN_HEALTH_CONTRACT.to_string());
|
||||
let identity = PathBuf::from(required_environment(
|
||||
"CODEX_CONNECTOR_IDUNN_HEALTH_IDENTITY",
|
||||
)?);
|
||||
let mut publisher =
|
||||
crate::ProviderHealthPublisher::open(endpoint, daemon_id, runtime_id, contract, &identity)
|
||||
.map_err(CodexDaemonError::Health)?;
|
||||
publisher
|
||||
.publish("active", "credential-isolated-transport-ready")
|
||||
.map_err(CodexDaemonError::Health)?;
|
||||
thread::Builder::new()
|
||||
.name("codex-connector-health".to_string())
|
||||
.spawn(move || {
|
||||
loop {
|
||||
thread::sleep(Duration::from_secs(10));
|
||||
if let Err(error) =
|
||||
publisher.publish("active", "credential-isolated-transport-ready")
|
||||
{
|
||||
eprintln!("codex-connector health publication failed: {error}");
|
||||
}
|
||||
}
|
||||
})
|
||||
.map_err(CodexDaemonError::HealthThread)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn required_environment(name: &'static str) -> Result<String, CodexDaemonError> {
|
||||
std::env::var(name)
|
||||
.ok()
|
||||
.filter(|value| !value.trim().is_empty() && value == value.trim())
|
||||
.ok_or(CodexDaemonError::InvalidConfig(name))
|
||||
}
|
||||
|
||||
fn load_admissions(
|
||||
config: &CodexDaemonConfig,
|
||||
) -> Result<Vec<CodexCallerAdmission>, CodexDaemonError> {
|
||||
|
|
@ -349,6 +397,12 @@ pub enum CodexDaemonError {
|
|||
Listen(#[source] std::io::Error),
|
||||
#[error("connector request thread failed")]
|
||||
Thread(#[source] std::io::Error),
|
||||
#[cfg(target_os = "linux")]
|
||||
#[error("connector health publication could not start")]
|
||||
Health(#[source] anyhow::Error),
|
||||
#[cfg(target_os = "linux")]
|
||||
#[error("connector health thread could not start")]
|
||||
HealthThread(#[source] std::io::Error),
|
||||
#[error("connector connection failed")]
|
||||
Connection(#[source] std::io::Error),
|
||||
#[error("connector frame exceeded its bound")]
|
||||
|
|
|
|||
449
src/idunn_health.rs
Normal file
449
src/idunn_health.rs
Normal file
|
|
@ -0,0 +1,449 @@
|
|||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::net::{SocketAddr, UdpSocket};
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::Path;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use cultcache_rs::{CacheBackingStore, CultCacheEnvelope, SingleFileMessagePackBackingStore};
|
||||
use cultnet_rs::{
|
||||
CultNetMessage, CultNetRawDocumentRecord, CultNetRawPayloadEncoding,
|
||||
CultNetRudpSocketTransportConnection, CultNetRudpSocketTransportOptions, CultNetWireContract,
|
||||
encode_cultnet_message_to_vec,
|
||||
};
|
||||
use ed25519_dalek::{Signer, SigningKey};
|
||||
use rand_core::{OsRng, RngCore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
pub const CODEX_CONNECTOR_IDUNN_HEALTH_CONTRACT: &str =
|
||||
"codex-connector.cultnet-rudp-service-health";
|
||||
const HEALTH_SCHEMA: &str = "idunn.signed_daemon_health.v1";
|
||||
const IDENTITY_SCHEMA: &str = "gamecult.provider_health_identity.private.v1";
|
||||
const IDENTITY_TYPE: &str = "gamecult.provider_health_identity.private.v1";
|
||||
const IDENTITY_KEY: &str = "gamecult-provider-health-identity";
|
||||
const ID_DOMAIN: &[u8] = b"gamecult.provider-health.identity.v1\0";
|
||||
const SIGNATURE_DOMAIN: &[u8] = b"gamecult.provider-health.signature.v1\0";
|
||||
const SIGNATURE_PURPOSE: &[u8] = b"idunn.signed_daemon_health.v1";
|
||||
const PROTECTOR_CONTEXT: &str = "gamecult-provider-health-identity-v1";
|
||||
const RUDP_PROTOCOL_ID: &str = "cultnet.transport.rudp.v0";
|
||||
const RUDP_CONNECTION_ID: u32 = 0x1d0d_0004;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
struct ProviderHealthIdentity {
|
||||
schema_version: String,
|
||||
identity_id: String,
|
||||
#[serde(with = "serde_bytes")]
|
||||
public_key: Vec<u8>,
|
||||
#[serde(with = "serde_bytes")]
|
||||
protected_private_seed: Vec<u8>,
|
||||
protector_kind: String,
|
||||
protector_binding: String,
|
||||
protector_version: String,
|
||||
assurance: String,
|
||||
created_at: String,
|
||||
#[serde(with = "serde_bytes")]
|
||||
enrollment_nonce: Vec<u8>,
|
||||
}
|
||||
|
||||
struct ProviderHealthSigner {
|
||||
entry: ProviderHealthIdentity,
|
||||
key: SigningKey,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct SignedDaemonHealth {
|
||||
schema_version: String,
|
||||
daemon_id: String,
|
||||
health_contract: String,
|
||||
source_runtime_id: String,
|
||||
state: String,
|
||||
detail: String,
|
||||
signer_identity_id: String,
|
||||
publisher_incarnation_id: String,
|
||||
publisher_sequence: u64,
|
||||
observed_at_unix_millis: u64,
|
||||
release_id: Option<String>,
|
||||
release_witness_sha256: Option<String>,
|
||||
source_commit: Option<String>,
|
||||
deployment_id: Option<String>,
|
||||
signature_algorithm: String,
|
||||
#[serde(with = "serde_bytes")]
|
||||
signature: Vec<u8>,
|
||||
private_state_exposed: bool,
|
||||
}
|
||||
|
||||
impl SignedDaemonHealth {
|
||||
fn validate(&self) -> Result<()> {
|
||||
for (value, label) in [
|
||||
(&self.daemon_id, "daemon id"),
|
||||
(&self.health_contract, "health contract"),
|
||||
(&self.source_runtime_id, "runtime id"),
|
||||
(&self.signer_identity_id, "signer identity"),
|
||||
(&self.publisher_incarnation_id, "publisher incarnation"),
|
||||
] {
|
||||
require_id(value, label)?;
|
||||
}
|
||||
if self.schema_version != HEALTH_SCHEMA
|
||||
|| !matches!(
|
||||
self.state.as_str(),
|
||||
"active" | "warming" | "degraded" | "failed"
|
||||
)
|
||||
|| self.detail.len() > 512
|
||||
|| self.detail.chars().any(char::is_control)
|
||||
|| self.publisher_sequence == 0
|
||||
|| self.observed_at_unix_millis == 0
|
||||
|| self.release_id.is_some()
|
||||
|| self.release_witness_sha256.is_some()
|
||||
|| self.source_commit.is_some()
|
||||
|| self.deployment_id.is_some()
|
||||
|| self.signature_algorithm != "ed25519"
|
||||
|| self.signature.len() != 64
|
||||
|| self.private_state_exposed
|
||||
{
|
||||
bail!("signed daemon health shape is invalid");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProviderHealthPublisher {
|
||||
endpoint: SocketAddr,
|
||||
daemon_id: String,
|
||||
runtime_id: String,
|
||||
contract: String,
|
||||
signer: ProviderHealthSigner,
|
||||
incarnation: String,
|
||||
sequence: u64,
|
||||
}
|
||||
|
||||
impl ProviderHealthPublisher {
|
||||
pub fn open(
|
||||
endpoint: SocketAddr,
|
||||
daemon_id: impl Into<String>,
|
||||
runtime_id: impl Into<String>,
|
||||
contract: impl Into<String>,
|
||||
identity_store: &Path,
|
||||
) -> Result<Self> {
|
||||
let daemon_id = daemon_id.into();
|
||||
let runtime_id = runtime_id.into();
|
||||
let contract = contract.into();
|
||||
require_id(&daemon_id, "daemon id")?;
|
||||
require_id(&runtime_id, "runtime id")?;
|
||||
require_id(&contract, "health contract")?;
|
||||
Ok(Self {
|
||||
endpoint,
|
||||
daemon_id,
|
||||
runtime_id,
|
||||
contract,
|
||||
signer: open_identity(identity_store)?,
|
||||
incarnation: uuid::Uuid::new_v4().to_string(),
|
||||
sequence: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn publish(&mut self, state: &str, detail: &str) -> Result<()> {
|
||||
self.sequence = self
|
||||
.sequence
|
||||
.checked_add(1)
|
||||
.ok_or_else(|| anyhow!("health publisher sequence overflow"))?;
|
||||
let mut record = SignedDaemonHealth {
|
||||
schema_version: HEALTH_SCHEMA.into(),
|
||||
daemon_id: self.daemon_id.clone(),
|
||||
health_contract: self.contract.clone(),
|
||||
source_runtime_id: self.runtime_id.clone(),
|
||||
state: state.into(),
|
||||
detail: detail.into(),
|
||||
signer_identity_id: self.signer.entry.identity_id.clone(),
|
||||
publisher_incarnation_id: self.incarnation.clone(),
|
||||
publisher_sequence: self.sequence,
|
||||
observed_at_unix_millis: chrono::Utc::now().timestamp_millis().try_into()?,
|
||||
release_id: None,
|
||||
release_witness_sha256: None,
|
||||
source_commit: None,
|
||||
deployment_id: None,
|
||||
signature_algorithm: "ed25519".into(),
|
||||
signature: Vec::new(),
|
||||
private_state_exposed: false,
|
||||
};
|
||||
let unsigned = unsigned_record(&record)?;
|
||||
record.signature = self
|
||||
.signer
|
||||
.key
|
||||
.sign(&signing_message(&unsigned))
|
||||
.to_bytes()
|
||||
.to_vec();
|
||||
record.validate()?;
|
||||
publish(self.endpoint, &self.runtime_id, &record)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enroll_provider_health_identity(path: &Path) -> Result<String> {
|
||||
if path.exists() {
|
||||
bail!("provider health identity already exists");
|
||||
}
|
||||
let parent = path
|
||||
.parent()
|
||||
.ok_or_else(|| anyhow!("provider health identity path has no parent"))?;
|
||||
std::fs::create_dir_all(parent)?;
|
||||
std::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o700))?;
|
||||
let mut seed = [0_u8; 32];
|
||||
OsRng.fill_bytes(&mut seed);
|
||||
let key = SigningKey::from_bytes(&seed);
|
||||
let public_key = key.verifying_key().to_bytes();
|
||||
let mut nonce = [0_u8; 32];
|
||||
OsRng.fill_bytes(&mut nonce);
|
||||
let binding = machine_binding()?;
|
||||
let entry = ProviderHealthIdentity {
|
||||
schema_version: IDENTITY_SCHEMA.into(),
|
||||
identity_id: identity_id(&public_key),
|
||||
public_key: public_key.to_vec(),
|
||||
protected_private_seed: mask_seed(&seed, &binding),
|
||||
protector_kind: "linux_file_mode_machine_id_binding".into(),
|
||||
protector_binding: binding,
|
||||
protector_version: "v1".into(),
|
||||
assurance: "os_installation_file_bound_cloneable_baseline".into(),
|
||||
created_at: chrono::Utc::now().to_rfc3339(),
|
||||
enrollment_nonce: nonce.to_vec(),
|
||||
};
|
||||
validate_identity(&entry)?;
|
||||
let envelope = CultCacheEnvelope {
|
||||
key: IDENTITY_KEY.into(),
|
||||
r#type: IDENTITY_TYPE.into(),
|
||||
payload: rmp_serde::to_vec(&entry)?,
|
||||
stored_at: entry.created_at.clone(),
|
||||
schema_id: Some(IDENTITY_SCHEMA.into()),
|
||||
};
|
||||
let bytes = rmp_serde::to_vec(&vec![envelope])?;
|
||||
let mut file = OpenOptions::new().create_new(true).write(true).open(path)?;
|
||||
if let Err(error) = file.write_all(&bytes).and_then(|()| file.sync_all()) {
|
||||
drop(file);
|
||||
let _ = std::fs::remove_file(path);
|
||||
return Err(error.into());
|
||||
}
|
||||
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))?;
|
||||
provider_health_public_key_hex(path)
|
||||
}
|
||||
|
||||
pub fn provider_health_public_key_hex(path: &Path) -> Result<String> {
|
||||
Ok(hex(&open_identity(path)?.entry.public_key))
|
||||
}
|
||||
|
||||
fn open_identity(path: &Path) -> Result<ProviderHealthSigner> {
|
||||
let entries = SingleFileMessagePackBackingStore::new(path).pull_all()?;
|
||||
let [envelope] = entries.as_slice() else {
|
||||
bail!("provider health identity store must contain exactly one record");
|
||||
};
|
||||
if envelope.key != IDENTITY_KEY
|
||||
|| envelope.r#type != IDENTITY_TYPE
|
||||
|| envelope.schema_id.as_deref() != Some(IDENTITY_SCHEMA)
|
||||
{
|
||||
bail!("provider health identity store has the wrong type");
|
||||
}
|
||||
let entry: ProviderHealthIdentity = rmp_serde::from_slice(&envelope.payload)?;
|
||||
validate_identity(&entry)?;
|
||||
let binding = machine_binding()?;
|
||||
if entry.protector_binding != binding {
|
||||
bail!("provider health identity belongs to another machine");
|
||||
}
|
||||
let seed: [u8; 32] = mask_seed(&entry.protected_private_seed, &binding)
|
||||
.try_into()
|
||||
.map_err(|_| anyhow!("provider health identity seed has invalid length"))?;
|
||||
let key = SigningKey::from_bytes(&seed);
|
||||
if key.verifying_key().to_bytes().as_slice() != entry.public_key.as_slice() {
|
||||
bail!("provider health identity seed does not match its public key");
|
||||
}
|
||||
Ok(ProviderHealthSigner { entry, key })
|
||||
}
|
||||
|
||||
fn validate_identity(entry: &ProviderHealthIdentity) -> Result<()> {
|
||||
if entry.schema_version != IDENTITY_SCHEMA
|
||||
|| entry.public_key.len() != 32
|
||||
|| entry.protected_private_seed.len() != 32
|
||||
|| entry.enrollment_nonce.len() != 32
|
||||
|| entry.protector_kind != "linux_file_mode_machine_id_binding"
|
||||
|| entry.protector_version != "v1"
|
||||
|| entry.identity_id != identity_id(&entry.public_key)
|
||||
{
|
||||
bail!("provider health identity is invalid");
|
||||
}
|
||||
chrono::DateTime::parse_from_rfc3339(&entry.created_at)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn machine_binding() -> Result<String> {
|
||||
let raw = std::fs::read_to_string("/etc/machine-id")
|
||||
.or_else(|_| std::fs::read_to_string("/var/lib/dbus/machine-id"))?;
|
||||
let id = raw.trim();
|
||||
if id.is_empty() {
|
||||
bail!("Linux machine-id is empty");
|
||||
}
|
||||
Ok(format!(
|
||||
"{PROTECTOR_CONTEXT}:machine-id-sha256:{}",
|
||||
hex(&Sha256::digest(id.as_bytes()))
|
||||
))
|
||||
}
|
||||
|
||||
fn mask_seed(seed: &[u8], binding: &str) -> Vec<u8> {
|
||||
let mask = Sha256::digest(
|
||||
[
|
||||
b"gamecult-linux-service-seed-v1\0".as_slice(),
|
||||
PROTECTOR_CONTEXT.as_bytes(),
|
||||
binding.as_bytes(),
|
||||
]
|
||||
.concat(),
|
||||
);
|
||||
seed.iter()
|
||||
.zip(mask)
|
||||
.map(|(left, right)| left ^ right)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn identity_id(public_key: &[u8]) -> String {
|
||||
hex(&Sha256::digest([ID_DOMAIN, public_key].concat()))
|
||||
}
|
||||
|
||||
fn unsigned_record(record: &SignedDaemonHealth) -> Result<Vec<u8>> {
|
||||
let mut unsigned = record.clone();
|
||||
unsigned.signature.clear();
|
||||
uuid::Uuid::parse_str(&unsigned.publisher_incarnation_id)?;
|
||||
Ok(rmp_serde::to_vec(&unsigned)?)
|
||||
}
|
||||
|
||||
fn signing_message(payload: &[u8]) -> Vec<u8> {
|
||||
let mut out =
|
||||
Vec::with_capacity(SIGNATURE_DOMAIN.len() + SIGNATURE_PURPOSE.len() + payload.len() + 16);
|
||||
out.extend_from_slice(SIGNATURE_DOMAIN);
|
||||
out.extend_from_slice(&(SIGNATURE_PURPOSE.len() as u64).to_be_bytes());
|
||||
out.extend_from_slice(SIGNATURE_PURPOSE);
|
||||
out.extend_from_slice(&(payload.len() as u64).to_be_bytes());
|
||||
out.extend_from_slice(payload);
|
||||
out
|
||||
}
|
||||
|
||||
fn publish(endpoint: SocketAddr, runtime_id: &str, record: &SignedDaemonHealth) -> Result<()> {
|
||||
let payload = rmp_serde::to_vec(record)?;
|
||||
if rmp_serde::from_slice::<SignedDaemonHealth>(&payload)? != *record {
|
||||
bail!("signed health MessagePack did not round trip");
|
||||
}
|
||||
let message = CultNetMessage::DocumentPutRaw {
|
||||
message_id: format!(
|
||||
"codex-connector-health:{}:{}:{}",
|
||||
record.daemon_id, record.publisher_incarnation_id, record.publisher_sequence
|
||||
),
|
||||
document: CultNetRawDocumentRecord {
|
||||
schema_id: HEALTH_SCHEMA.into(),
|
||||
record_key: record.daemon_id.clone(),
|
||||
stored_at: chrono::DateTime::from_timestamp_millis(
|
||||
record.observed_at_unix_millis.try_into()?,
|
||||
)
|
||||
.context("health observation time is invalid")?
|
||||
.to_rfc3339(),
|
||||
payload_encoding: CultNetRawPayloadEncoding::Messagepack,
|
||||
payload,
|
||||
source_runtime_id: Some(runtime_id.into()),
|
||||
source_agent_id: Some(record.signer_identity_id.clone()),
|
||||
source_role: Some("daemon-health-publisher".into()),
|
||||
tags: Some(vec![RUDP_PROTOCOL_ID.into()]),
|
||||
},
|
||||
};
|
||||
let socket = UdpSocket::bind(if endpoint.is_ipv4() {
|
||||
"0.0.0.0:0"
|
||||
} else {
|
||||
"[::]:0"
|
||||
})?;
|
||||
socket.set_read_timeout(Some(Duration::from_millis(100)))?;
|
||||
let mut transport = CultNetRudpSocketTransportConnection::new(
|
||||
CultNetRudpSocketTransportOptions::client(runtime_id, socket, endpoint, RUDP_CONNECTION_ID),
|
||||
)?;
|
||||
transport.connect(Vec::new())?;
|
||||
let deadline = Instant::now() + Duration::from_millis(500);
|
||||
while !transport.connected() {
|
||||
let _ = transport.receive_once()?;
|
||||
transport.poll_resends()?;
|
||||
if Instant::now() >= deadline {
|
||||
bail!("timed out connecting provider health to {endpoint}");
|
||||
}
|
||||
}
|
||||
transport.send(
|
||||
"schema",
|
||||
encode_cultnet_message_to_vec(&message, CultNetWireContract::CultNetSchemaV0)?,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn require_id(value: &str, label: &str) -> Result<()> {
|
||||
if value.is_empty()
|
||||
|| value != value.trim()
|
||||
|| value.len() > 256
|
||||
|| value.chars().any(char::is_control)
|
||||
{
|
||||
bail!("{label} is invalid");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn hex(bytes: &[u8]) -> String {
|
||||
const DIGITS: &[u8; 16] = b"0123456789abcdef";
|
||||
let mut output = String::with_capacity(bytes.len() * 2);
|
||||
for byte in bytes {
|
||||
output.push(DIGITS[(byte >> 4) as usize] as char);
|
||||
output.push(DIGITS[(byte & 15) as usize] as char);
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
|
||||
|
||||
#[test]
|
||||
fn identity_round_trips_and_signed_health_is_private_free() {
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
"codex-connector-health-{}-{}",
|
||||
std::process::id(),
|
||||
uuid::Uuid::new_v4()
|
||||
));
|
||||
std::fs::create_dir_all(&root).unwrap();
|
||||
let path = root.join("identity.cc");
|
||||
let public_key = enroll_provider_health_identity(&path).unwrap();
|
||||
assert_eq!(provider_health_public_key_hex(&path).unwrap(), public_key);
|
||||
let signer = open_identity(&path).unwrap();
|
||||
let mut record = SignedDaemonHealth {
|
||||
schema_version: HEALTH_SCHEMA.into(),
|
||||
daemon_id: "yggdrasil-codex-connector".into(),
|
||||
health_contract: CODEX_CONNECTOR_IDUNN_HEALTH_CONTRACT.into(),
|
||||
source_runtime_id: "codex-connector-yggdrasil".into(),
|
||||
state: "active".into(),
|
||||
detail: "credential-isolated-transport-ready".into(),
|
||||
signer_identity_id: signer.entry.identity_id.clone(),
|
||||
publisher_incarnation_id: uuid::Uuid::new_v4().to_string(),
|
||||
publisher_sequence: 1,
|
||||
observed_at_unix_millis: 1_787_315_696_789,
|
||||
release_id: None,
|
||||
release_witness_sha256: None,
|
||||
source_commit: None,
|
||||
deployment_id: None,
|
||||
signature_algorithm: "ed25519".into(),
|
||||
signature: Vec::new(),
|
||||
private_state_exposed: false,
|
||||
};
|
||||
let unsigned = unsigned_record(&record).unwrap();
|
||||
record.signature = signer
|
||||
.key
|
||||
.sign(&signing_message(&unsigned))
|
||||
.to_bytes()
|
||||
.to_vec();
|
||||
record.validate().unwrap();
|
||||
let public: [u8; 32] = signer.entry.public_key.try_into().unwrap();
|
||||
let signature = Signature::from_slice(&record.signature).unwrap();
|
||||
VerifyingKey::from_bytes(&public)
|
||||
.unwrap()
|
||||
.verify(&signing_message(&unsigned), &signature)
|
||||
.unwrap();
|
||||
assert!(!record.private_state_exposed);
|
||||
std::fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,8 @@ use zeroize::{Zeroize, Zeroizing};
|
|||
|
||||
#[cfg(feature = "daemon")]
|
||||
mod daemon;
|
||||
#[cfg(all(feature = "daemon", target_os = "linux"))]
|
||||
mod idunn_health;
|
||||
#[cfg(feature = "daemon")]
|
||||
mod provider_backend;
|
||||
|
||||
|
|
@ -35,6 +37,11 @@ pub use daemon::load_daemon_config;
|
|||
pub use daemon::serve;
|
||||
#[cfg(feature = "daemon")]
|
||||
pub use daemon::write_daemon_config;
|
||||
#[cfg(all(feature = "daemon", target_os = "linux"))]
|
||||
pub use idunn_health::{
|
||||
CODEX_CONNECTOR_IDUNN_HEALTH_CONTRACT, ProviderHealthPublisher,
|
||||
enroll_provider_health_identity, provider_health_public_key_hex,
|
||||
};
|
||||
#[cfg(feature = "daemon")]
|
||||
pub use provider_backend::CodexAppServerConfig;
|
||||
#[cfg(feature = "daemon")]
|
||||
|
|
|
|||
25
src/main.rs
25
src/main.rs
|
|
@ -3,10 +3,33 @@ use std::path::PathBuf;
|
|||
|
||||
use codex_connector::{CodexCallerConfig, CodexDaemonConfig};
|
||||
|
||||
const USAGE: &str = "usage:\n codex-connector --config PATH.cc\n codex-connector --initialize-single-caller-config PATH.cc BIND CODEX_EXECUTABLE CODEX_SHA256 CODEX_HOME REPLAY_STORE CALLER_RUNTIME_ID CONNECTION_KEY_FILE CONNECTION_KEY_EPOCH ALLOWED_MODEL MAX_CONCURRENT_REQUESTS MAX_PAYLOAD_BYTES MAX_OUTPUT_TOKENS";
|
||||
const USAGE: &str = "usage:\n codex-connector --config PATH.cc\n codex-connector --initialize-single-caller-config PATH.cc BIND CODEX_EXECUTABLE CODEX_SHA256 CODEX_HOME REPLAY_STORE CALLER_RUNTIME_ID CONNECTION_KEY_FILE CONNECTION_KEY_EPOCH ALLOWED_MODEL MAX_CONCURRENT_REQUESTS MAX_PAYLOAD_BYTES MAX_OUTPUT_TOKENS\n codex-connector --enroll-provider-health-identity PATH.cc\n codex-connector --provider-health-public-key PATH.cc";
|
||||
|
||||
fn main() {
|
||||
let args = std::env::args_os().skip(1).collect::<Vec<_>>();
|
||||
#[cfg(target_os = "linux")]
|
||||
if let [flag, path] = args.as_slice() {
|
||||
if flag == "--enroll-provider-health-identity" {
|
||||
match codex_connector::enroll_provider_health_identity(&PathBuf::from(path)) {
|
||||
Ok(public_key) => println!("{public_key}"),
|
||||
Err(error) => {
|
||||
eprintln!("codex-connector health identity enrollment failed: {error}");
|
||||
std::process::exit(2);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if flag == "--provider-health-public-key" {
|
||||
match codex_connector::provider_health_public_key_hex(&PathBuf::from(path)) {
|
||||
Ok(public_key) => println!("{public_key}"),
|
||||
Err(error) => {
|
||||
eprintln!("codex-connector health identity read failed: {error}");
|
||||
std::process::exit(2);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if args
|
||||
.first()
|
||||
.is_some_and(|value| value == "--initialize-single-caller-config")
|
||||
|
|
|
|||
Loading…
Reference in a new issue