Give Codex Connector its own Idunn health identity

This commit is contained in:
Meta Vens 2026-08-24 21:04:54 +02:00
commit 155fac6d22
8 changed files with 872 additions and 13 deletions

322
Cargo.lock generated
View file

@ -58,12 +58,24 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
[[package]]
name = "base64ct"
version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]]
name = "block-buffer"
version = "0.10.4"
@ -130,18 +142,30 @@ name = "codex-connector"
version = "0.1.0"
dependencies = [
"aes-gcm",
"base64",
"cultcache-rs",
"rand_core",
"anyhow",
"base64 0.23.1",
"chrono",
"cultcache-rs 0.1.0 (git+https://github.com/GameCult/cultcache-rs.git?rev=fdbf3bf0a4b3f6b617c224d7439feb8e69d4474c)",
"cultnet-rs",
"ed25519-dalek",
"rand_core 0.6.4",
"rmp-serde",
"serde",
"serde_bytes",
"serde_json",
"sha2",
"thiserror",
"ureq",
"uuid",
"zeroize",
]
[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@ -164,7 +188,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"rand_core",
"rand_core 0.6.4",
"typenum",
]
@ -177,6 +201,23 @@ dependencies = [
"cipher",
]
[[package]]
name = "cultcache-rs"
version = "0.1.0"
source = "git+https://github.com/GameCult/CultLib.git?rev=75c180782aeba7cfd22d6412877397708a4ed28f#75c180782aeba7cfd22d6412877397708a4ed28f"
dependencies = [
"anyhow",
"chrono",
"cultcache-rs-derive 0.1.0 (git+https://github.com/GameCult/CultLib.git?rev=75c180782aeba7cfd22d6412877397708a4ed28f)",
"fs2",
"rmp-serde",
"serde",
"serde_bytes",
"serde_json",
"uuid",
"windows-sys 0.59.0",
]
[[package]]
name = "cultcache-rs"
version = "0.1.0"
@ -184,7 +225,7 @@ source = "git+https://github.com/GameCult/cultcache-rs.git?rev=fdbf3bf0a4b3f6b61
dependencies = [
"anyhow",
"chrono",
"cultcache-rs-derive",
"cultcache-rs-derive 0.1.0 (git+https://github.com/GameCult/cultcache-rs.git?rev=fdbf3bf0a4b3f6b617c224d7439feb8e69d4474c)",
"fs2",
"redb",
"rmp-serde",
@ -195,6 +236,16 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "cultcache-rs-derive"
version = "0.1.0"
source = "git+https://github.com/GameCult/CultLib.git?rev=75c180782aeba7cfd22d6412877397708a4ed28f#75c180782aeba7cfd22d6412877397708a4ed28f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "cultcache-rs-derive"
version = "0.1.0"
@ -205,6 +256,65 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "cultnet-rs"
version = "0.1.0"
source = "git+https://github.com/GameCult/CultLib.git?rev=75c180782aeba7cfd22d6412877397708a4ed28f#75c180782aeba7cfd22d6412877397708a4ed28f"
dependencies = [
"aes-gcm",
"anyhow",
"base64 0.22.1",
"chrono",
"cultcache-rs 0.1.0 (git+https://github.com/GameCult/CultLib.git?rev=75c180782aeba7cfd22d6412877397708a4ed28f)",
"hmac",
"rand",
"rmp-serde",
"rmpv",
"serde",
"serde_bytes",
"serde_json",
"sha2",
"socket2",
"uuid",
]
[[package]]
name = "curve25519-dalek"
version = "4.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
dependencies = [
"cfg-if",
"cpufeatures",
"curve25519-dalek-derive",
"digest",
"fiat-crypto",
"rustc_version",
"subtle",
"zeroize",
]
[[package]]
name = "curve25519-dalek-derive"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "der"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"const-oid",
"zeroize",
]
[[package]]
name = "digest"
version = "0.10.7"
@ -213,8 +323,39 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
name = "ed25519"
version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
"pkcs8",
"signature",
]
[[package]]
name = "ed25519-dalek"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
dependencies = [
"curve25519-dalek",
"ed25519",
"serde",
"sha2",
"subtle",
"zeroize",
]
[[package]]
name = "fiat-crypto"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "find-msvc-tools"
version = "0.1.11"
@ -276,6 +417,18 @@ dependencies = [
"wasi",
]
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi 5.3.0",
"wasip2",
]
[[package]]
name = "getrandom"
version = "0.4.3"
@ -284,7 +437,7 @@ checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"r-efi 6.0.0",
]
[[package]]
@ -297,6 +450,15 @@ dependencies = [
"polyval",
]
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]]
name = "http"
version = "1.5.0"
@ -414,6 +576,16 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"spki",
]
[[package]]
name = "polyval"
version = "0.6.2"
@ -426,6 +598,15 @@ dependencies = [
"universal-hash",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.107"
@ -444,12 +625,38 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
dependencies = [
"rand_chacha",
"rand_core 0.9.5",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.5",
]
[[package]]
name = "rand_core"
version = "0.6.4"
@ -459,6 +666,15 @@ dependencies = [
"getrandom 0.2.17",
]
[[package]]
name = "rand_core"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom 0.3.4",
]
[[package]]
name = "redb"
version = "4.2.0"
@ -501,6 +717,26 @@ dependencies = [
"serde",
]
[[package]]
name = "rmpv"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a4e1d4b9b938a26d2996af33229f0ca0956c652c1375067f0b45291c1df8417"
dependencies = [
"rmp",
"serde",
"serde_bytes",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "rustls"
version = "0.23.43"
@ -542,6 +778,12 @@ version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "semver"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "serde"
version = "1.0.229"
@ -612,12 +854,41 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "signature"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "socket2"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "spki"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der",
]
[[package]]
name = "subtle"
version = "2.6.1"
@ -700,7 +971,7 @@ version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d"
dependencies = [
"base64",
"base64 0.23.1",
"log",
"percent-encoding",
"rustls",
@ -716,7 +987,7 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613"
dependencies = [
"base64",
"base64 0.23.1",
"http",
"httparse",
"log",
@ -751,6 +1022,15 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.4+wasi-0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.127"
@ -968,6 +1248,32 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "zerocopy"
version = "0.8.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "zeroize"
version = "1.9.0"

View file

@ -18,17 +18,32 @@ required-features = ["daemon"]
[features]
default = []
daemon = ["dep:cultcache-rs", "dep:ureq"]
daemon = [
"dep:anyhow",
"dep:chrono",
"dep:cultcache-rs",
"dep:cultnet-rs",
"dep:ed25519-dalek",
"dep:serde_bytes",
"dep:ureq",
"dep:uuid",
]
[dependencies]
aes-gcm = "0.10"
anyhow = { version = "1", optional = true }
base64 = "0.23"
chrono = { version = "0.4", features = ["serde"], optional = true }
cultcache-rs = { git = "https://github.com/GameCult/cultcache-rs.git", rev = "fdbf3bf0a4b3f6b617c224d7439feb8e69d4474c", optional = true }
cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "75c180782aeba7cfd22d6412877397708a4ed28f", optional = true }
ed25519-dalek = { version = "2.2", optional = true }
rand_core = { version = "0.6", features = ["getrandom"] }
rmp-serde = "1"
serde = { version = "1", features = ["derive"] }
serde_bytes = { version = "0.11", optional = true }
serde_json = "1"
sha2 = "0.10"
thiserror = "2"
ureq = { version = "3.4", default-features = false, features = ["rustls"], optional = true }
uuid = { version = "1", features = ["v4"], optional = true }
zeroize = "1"

View file

@ -55,9 +55,10 @@ cargo check --bin codex-connector --features daemon
The same production binary can initialize a typed single-caller `.cc`
configuration for first deployment, then serve only from that admitted state.
Redacted CultMesh/Odin publication and the independent Idunn target remain
open. The old daemon remains the sole live credential writer until that
deployment transaction lands.
The daemon publishes private-free signed health to Idunn from its own runtime
identity. Its source revision also owns the exact Codex Linux package and
binary hashes in `deployment/codex-linux-x64.manifest`, so changing the provider
toolchain necessarily changes the connector release selected by Idunn.
## Contract invariant

View file

@ -0,0 +1,4 @@
schema_version=gamecult.codex_connector.codex_toolchain.v1
package_url=https://registry.npmjs.org/@openai/codex/-/codex-0.150.0-alpha.7-linux-x64.tgz
package_sha256=8aca3112aef60127d4ffaf4c6f08e524fa7315279a1d08e7aa9c5e7f1a6943e0
binary_sha256=aca268dc02dcef8b1ea9f528a10173d5071ae76d659a03cce6458bbebe228bee

View file

@ -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
View 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();
}
}

View file

@ -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")]

View file

@ -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")