diff --git a/htyproc/src/config.rs b/htyproc/src/config.rs index 4cc5640..90f2056 100644 --- a/htyproc/src/config.rs +++ b/htyproc/src/config.rs @@ -53,6 +53,14 @@ pub fn proc_port() -> u16 { .unwrap_or(3004) } +pub fn priv_key() -> anyhow::Result { + env::var("PRIV_KEY").map_err(|_| anyhow::anyhow!("PRIV_KEY not set")) +} + +pub fn pub_key() -> anyhow::Result { + env::var("PUB_KEY").map_err(|_| anyhow::anyhow!("PUB_KEY not set")) +} + pub fn http_timeout() -> Duration { Duration::from_secs(120) } diff --git a/htyproc/src/processor.rs b/htyproc/src/processor.rs index 498cfe1..9db1fe2 100644 --- a/htyproc/src/processor.rs +++ b/htyproc/src/processor.rs @@ -1,13 +1,22 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; +use htycommons::cert::encrypt_text_with_private_key; use htyts_models::{ReqTask, TaskStatus, TaskType}; +use serde::Deserialize; +use serde_json::json; use crate::clients::{merge_row_status, ngx_combine_image, ngx_convert_audio, TsClient}; use crate::config; use crate::redis_task::RedisTask; use crate::tasks; +#[derive(Deserialize)] +struct HtyResp { + r: bool, + d: Option, +} + pub static PROCESSING: AtomicUsize = AtomicUsize::new(0); /// Mirrors Java `ProcessorHelper.Status`. @@ -59,11 +68,11 @@ impl ProcessorRuntime { let http = reqwest::Client::builder() .timeout(config::http_timeout()) .build()?; - Ok(Arc::new(Self { + let rt = Arc::new(Self { machine_status: Mutex::new(ProcMachineStatus::Pending), ts, redis, - sudo_token: tokio::sync::Mutex::new(std::env::var("PROC_SUDOER_TOKEN").ok()), + sudo_token: tokio::sync::Mutex::new(None), ts_domain, ngx_url, http, @@ -71,16 +80,79 @@ impl ProcessorRuntime { htyws_url, htyuc_url, ts_url, - })) + }); + // eagerly fetch token at startup so processor_loop can start immediately + if let Err(e) = rt.refresh_sudo_token().await { + tracing::warn!("initial sudo token refresh failed (will retry in loop): {e:?}"); + } + Ok(rt) } pub fn proc_status(&self) -> ProcMachineStatus { *self.machine_status.lock().expect("proc status mutex poisoned") } + /// Returns the cached sudoer token, or tries to refresh on the fly. pub async fn sudo_token(&self) -> Option { + let cached = self.sudo_token.lock().await.clone(); + if cached.is_some() { + return cached; + } + // token missing — try to obtain one + if let Err(e) = self.refresh_sudo_token().await { + tracing::warn!("sudo_token refresh on demand failed: {e:?}"); + } self.sudo_token.lock().await.clone() } + + /// Obtain a fresh sudoer token from UC via loginWithCert → sudo. + /// + /// Mirrors Java `HtyucUtil.refreshRootTokenWithLoginWithCertAndThenSudo`. + async fn refresh_sudo_token(&self) -> anyhow::Result<()> { + // 1. Read keys + let priv_key = config::priv_key()?; + let pub_key = config::pub_key()?; + + // 2. Sign pub_key with priv_key using Ed25519 + let encrypted = encrypt_text_with_private_key(priv_key, pub_key.clone())?; + + // 3. POST loginWithCert → returns JWT + let cert_url = format!("{}/api/v1/uc/login_with_cert", self.htyuc_url.trim_end_matches('/')); + let login_resp = self + .http + .post(&cert_url) + .header("HtyHost", &self.ts_domain) + .json(&json!({"encrypted_data": encrypted})) + .send() + .await?; + let login_body: HtyResp = login_resp.json().await?; + let login_jwt = login_body + .d + .and_then(|v| v.as_str().map(String::from)) + .ok_or_else(|| anyhow::anyhow!("loginWithCert: r=false or missing d"))?; + + tracing::debug!("loginWithCert succeeded, got JWT"); + + // 4. POST sudo → returns sudoer token (NO "Bearer " prefix) + let sudo_url = format!("{}/api/v1/uc/sudo", self.htyuc_url.trim_end_matches('/')); + let sudo_resp = self + .http + .post(&sudo_url) + .header("Authorization", &login_jwt) + .send() + .await?; + let sudo_body: HtyResp = sudo_resp.json().await?; + let sudoer_token = sudo_body + .d + .and_then(|v| v.as_str().map(String::from)) + .ok_or_else(|| anyhow::anyhow!("sudo: r=false or missing d"))?; + + tracing::info!("obtained new sudoer token"); + + // 5. Cache in-memory + *self.sudo_token.lock().await = Some(sudoer_token); + Ok(()) + } } pub async fn processor_loop(rt: Arc) {