use std::sync::Arc;
use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;
use koi_compose::cores::Cores;
pub struct TrustPlaneConfig {
pub mtls_port: u16,
pub acme_port: u16,
pub no_acme: bool,
pub dns_zone: String,
pub announce_http_port: Option<u16>,
}
pub fn spawn(
cores: &Cores,
cfg: TrustPlaneConfig,
cancel: CancellationToken,
tasks: &mut Vec<JoinHandle<()>>,
) {
let Some(certmesh) = cores.certmesh.clone() else {
return;
};
let dns = cores.dns.clone();
let mdns = cores.mdns.clone();
tasks.push(tokio::spawn(async move {
let mut posture_rx = certmesh.watch_posture();
let mut live: Option<Live> = None;
loop {
let secure = posture_rx.borrow_and_update().signed;
match (secure, live.is_some()) {
(true, false) => {
if let Some(started) =
start_listeners(&certmesh, &dns, &mdns, &cfg, &cancel).await
{
tracing::info!("trust-plane presence started (CA available)");
live = Some(started);
}
}
(false, true) => {
stop_listeners(live.take(), &mdns).await;
tracing::info!("trust-plane presence stopped (CA unavailable)");
}
_ => {}
}
tokio::select! {
_ = cancel.cancelled() => {
stop_listeners(live.take(), &mdns).await;
break;
}
changed = posture_rx.changed() => {
if changed.is_err() {
stop_listeners(live.take(), &mdns).await;
break;
}
}
}
}
}));
}
struct Live {
cancel: CancellationToken,
handles: Vec<JoinHandle<()>>,
announce_id: Option<String>,
}
async fn stop_listeners(live: Option<Live>, mdns: &Option<Arc<koi_mdns::MdnsCore>>) {
if let Some(live) = live {
if let (Some(id), Some(mdns)) = (live.announce_id.as_deref(), mdns) {
if let Err(e) = mdns.unregister(id) {
tracing::debug!(error = %e, "failed to withdraw _certmesh._tcp announce");
}
}
live.cancel.cancel();
for h in live.handles {
let _ = h.await;
}
}
}
async fn start_listeners(
certmesh: &Arc<koi_certmesh::CertmeshCore>,
dns: &Option<Arc<koi_dns::DnsRuntime>>,
mdns: &Option<Arc<koi_mdns::MdnsCore>>,
cfg: &TrustPlaneConfig,
parent_cancel: &CancellationToken,
) -> Option<Live> {
let enrollment = match certmesh.self_enroll().await {
Ok(e) => e,
Err(e) => {
tracing::info!(reason = %e, "trust-plane: CA not ready for self-enroll yet");
return None;
}
};
let token = parent_cancel.child_token();
let mut handles = Vec::new();
{
let cm = certmesh.clone();
let port = cfg.mtls_port;
let token = token.clone();
let enr = enrollment.clone();
handles.push(tokio::spawn(async move {
if let Err(e) = crate::adapters::mtls::start(
port,
cm,
&enr.cert_pem,
&enr.key_pem,
&enr.ca_cert_pem,
token,
)
.await
{
tracing::error!(error = %e, "mTLS adapter failed");
}
}));
}
if !cfg.no_acme {
if let Some(dns) = dns {
let base_url = format!("https://{}:{}", local_fqdn(), cfg.acme_port);
let dns_solver: Arc<dyn koi_common::integration::AcmeDnsSolver> =
koi_compose::bridges::AcmeDnsBridge::new(dns.clone());
let acme_state = certmesh.acme_state(koi_certmesh::acme::AcmeStateConfig {
base_url,
zone: cfg.dns_zone.clone(),
dns: dns_solver,
});
let port = cfg.acme_port;
let token = token.clone();
let enr = enrollment.clone();
handles.push(tokio::spawn(async move {
if let Err(e) = crate::adapters::acme::start(
port,
acme_state,
&enr.cert_pem,
&enr.key_pem,
token,
)
.await
{
tracing::error!(error = %e, "ACME adapter failed");
}
}));
} else {
tracing::info!(
"ACME adapter: skipped (DNS capability disabled; dns-01 needs the DNS core)"
);
}
}
let announce_id = match (mdns, cfg.announce_http_port) {
(Some(mdns), Some(http_port)) => {
crate::infra::register_certmesh_record(certmesh, mdns, http_port).await
}
_ => None,
};
Some(Live {
cancel: token,
handles,
announce_id,
})
}
fn local_fqdn() -> String {
hostname::get()
.ok()
.and_then(|os| os.into_string().ok())
.filter(|h| !h.is_empty())
.unwrap_or_else(|| "localhost".to_string())
}