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) .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 { pub fn http_timeout() -> Duration {
Duration::from_secs(120) Duration::from_secs(120)
} }
+75 -3
View File
@@ -1,13 +1,22 @@
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use htycommons::cert::encrypt_text_with_private_key;
use htyts_models::{ReqTask, TaskStatus, TaskType}; 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::clients::{merge_row_status, ngx_combine_image, ngx_convert_audio, TsClient};
use crate::config; use crate::config;
use crate::redis_task::RedisTask; use crate::redis_task::RedisTask;
use crate::tasks; use crate::tasks;
#[derive(Deserialize)]
struct HtyResp {
r: bool,
d: Option<serde_json::Value>,
}
pub static PROCESSING: AtomicUsize = AtomicUsize::new(0); pub static PROCESSING: AtomicUsize = AtomicUsize::new(0);
/// Mirrors Java `ProcessorHelper.Status`. /// Mirrors Java `ProcessorHelper.Status`.
@@ -59,11 +68,11 @@ impl ProcessorRuntime {
let http = reqwest::Client::builder() let http = reqwest::Client::builder()
.timeout(config::http_timeout()) .timeout(config::http_timeout())
.build()?; .build()?;
Ok(Arc::new(Self { let rt = Arc::new(Self {
machine_status: Mutex::new(ProcMachineStatus::Pending), machine_status: Mutex::new(ProcMachineStatus::Pending),
ts, ts,
redis, redis,
sudo_token: tokio::sync::Mutex::new(std::env::var("PROC_SUDOER_TOKEN").ok()), sudo_token: tokio::sync::Mutex::new(None),
ts_domain, ts_domain,
ngx_url, ngx_url,
http, http,
@@ -71,16 +80,79 @@ impl ProcessorRuntime {
htyws_url, htyws_url,
htyuc_url, htyuc_url,
ts_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 { pub fn proc_status(&self) -> ProcMachineStatus {
*self.machine_status.lock().expect("proc status mutex poisoned") *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> { 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() 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>) { pub async fn processor_loop(rt: Arc<ProcessorRuntime>) {