htyproc: dynamic sudo token via loginWithCert → sudo, drop PROC_SUDOER_TOKEN

This commit is contained in:
2026-04-26 23:15:08 +08:00
parent eee06c31ca
commit eca81f0134
2 changed files with 83 additions and 3 deletions
+8
View File
@@ -53,6 +53,14 @@ pub fn proc_port() -> u16 {
.unwrap_or(3004)
}
pub fn priv_key() -> anyhow::Result<String> {
env::var("PRIV_KEY").map_err(|_| anyhow::anyhow!("PRIV_KEY not set"))
}
pub fn pub_key() -> anyhow::Result<String> {
env::var("PUB_KEY").map_err(|_| anyhow::anyhow!("PUB_KEY not set"))
}
pub fn http_timeout() -> Duration {
Duration::from_secs(120)
}
+75 -3
View File
@@ -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<serde_json::Value>,
}
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<String> {
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<ProcessorRuntime>) {