htyproc: dynamic sudo token via loginWithCert → sudo, drop PROC_SUDOER_TOKEN
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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>) {
|
||||
|
||||
Reference in New Issue
Block a user