You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							266 lines
						
					
					
						
							8.1 KiB
						
					
					
				
			
		
		
	
	
							266 lines
						
					
					
						
							8.1 KiB
						
					
					
				use std::fmt::Debug; | 
						|
use std::net::SocketAddr; | 
						|
use std::{collections::HashMap, time::Duration}; | 
						|
 | 
						|
use anyhow::{anyhow, Context, Result}; | 
						|
use reqwest::{header, header::HeaderMap, Certificate, Client, Identity, Method, Url}; | 
						|
use serde::de::DeserializeOwned; | 
						|
use serde_json::{from_str, Value}; | 
						|
 | 
						|
use crate::{ | 
						|
    config::{get_self_id, MASTER_PORT}, | 
						|
    conv::opt_to_string, | 
						|
    messaging::{self, AsMsg}, | 
						|
    models::*, | 
						|
    types::Id, | 
						|
    UError, UResult, | 
						|
}; | 
						|
 | 
						|
const AGENT_IDENTITY: &[u8] = include_bytes!("../../../certs/alice.p12"); | 
						|
const ROOT_CA_CERT: &[u8] = include_bytes!("../../../certs/ca.crt"); | 
						|
 | 
						|
pub mod api_types { | 
						|
    use super::*; | 
						|
 | 
						|
    pub type GetPersonalJobs = Vec<AssignedJobById>; | 
						|
    pub type Report = (); | 
						|
    pub type GetJob = Job; | 
						|
    pub type GetBriefJob = Job; | 
						|
    pub type GetJobs = Vec<JobMeta>; | 
						|
    pub type GetAgents = Vec<Agent>; | 
						|
    pub type UpdateAgent = (); | 
						|
    pub type UpdateJob = (); | 
						|
    pub type UpdateResult = (); | 
						|
    pub type UpdatePayload = (); | 
						|
    pub type UploadJobs = (); | 
						|
    pub type UploadPayloads = (); | 
						|
    pub type Del = (); | 
						|
    pub type SetJobs = (); | 
						|
    pub type GetAgentJobs = Vec<AssignedJob>; | 
						|
    pub type Ping = (); | 
						|
    pub type GetPayloads = Vec<Payload>; | 
						|
    pub type GetPayload = Payload; | 
						|
} | 
						|
 | 
						|
#[derive(Clone, Debug)] | 
						|
pub struct HttpClient { | 
						|
    base_url: Url, | 
						|
    client: Client, | 
						|
} | 
						|
 | 
						|
impl HttpClient { | 
						|
    pub async fn new(server: &str, password: Option<String>) -> UResult<Self> { | 
						|
        let identity = Identity::from_pkcs12_der(AGENT_IDENTITY, "").unwrap(); | 
						|
        let mut default_headers = | 
						|
            HashMap::from([(header::USER_AGENT, get_self_id().hyphenated().to_string())]); | 
						|
 | 
						|
        if let Some(pwd) = password { | 
						|
            default_headers.insert(header::AUTHORIZATION, format!("Bearer {pwd}")); | 
						|
        } | 
						|
 | 
						|
        let client = Client::builder() | 
						|
            .identity(identity) | 
						|
            .default_headers(HeaderMap::try_from(&default_headers).unwrap()) | 
						|
            .add_root_certificate(Certificate::from_pem(ROOT_CA_CERT).unwrap()) | 
						|
            .timeout(Duration::from_secs(20)); | 
						|
 | 
						|
        async fn resolve(domain_name: &str) -> Result<SocketAddr> { | 
						|
            let dns_response = Client::new() | 
						|
                .request( | 
						|
                    Method::GET, | 
						|
                    format!("https://1.1.1.1/dns-query?name={domain_name}&type=A"), | 
						|
                ) | 
						|
                .header(header::ACCEPT, "application/dns-json") | 
						|
                .send() | 
						|
                .await? | 
						|
                .text() | 
						|
                .await?; | 
						|
 | 
						|
            let ip = from_str::<Value>(&dns_response)? | 
						|
                .get("Answer") | 
						|
                .and_then(|a| a.get(0)) | 
						|
                .and_then(|a| a.get("data")) | 
						|
                .map(|ip| ip.to_owned()) | 
						|
                .ok_or_else(|| anyhow!("can't extract dns answer"))?; | 
						|
 | 
						|
            let raw_addr = format!("{}:{MASTER_PORT}", ip.as_str().unwrap()); | 
						|
            Ok(raw_addr.parse().unwrap()) | 
						|
        } | 
						|
 | 
						|
        let client = match resolve(server).await { | 
						|
            Ok(addr) => client.resolve(server, addr), | 
						|
            Err(e) => { | 
						|
                warn!("DNS error: {e}"); | 
						|
                client | 
						|
            } | 
						|
        } | 
						|
        .build() | 
						|
        .unwrap(); | 
						|
 | 
						|
        Ok(Self { | 
						|
            client, | 
						|
            base_url: Url::parse(&format!("https://{}:{}", server, MASTER_PORT)).unwrap(), | 
						|
        }) | 
						|
    } | 
						|
 | 
						|
    async fn req<R: AsMsg + DeserializeOwned>(&self, url: impl AsRef<str>) -> Result<R> { | 
						|
        self.req_with_payload(url, &()).await | 
						|
    } | 
						|
 | 
						|
    async fn req_with_payload<P: AsMsg, R: AsMsg + DeserializeOwned>( | 
						|
        &self, | 
						|
        url: impl AsRef<str>, | 
						|
        payload: &P, | 
						|
    ) -> Result<R> { | 
						|
        let url = url.as_ref(); | 
						|
        let request = self | 
						|
            .client | 
						|
            .post(self.base_url.join(url).unwrap()) | 
						|
            .json(payload); | 
						|
 | 
						|
        let response = request | 
						|
            .send() | 
						|
            .await | 
						|
            .context("error while sending request")?; | 
						|
 | 
						|
        let is_success = match response.error_for_status_ref() { | 
						|
            Ok(_) => Ok(()), | 
						|
            Err(e) => Err(UError::from(e)), | 
						|
        }; | 
						|
 | 
						|
        let resp = response.text().await.context("resp")?; | 
						|
 | 
						|
        let result = match is_success { | 
						|
            Ok(_) => { | 
						|
                from_str::<R>(&resp).map_err(|e| UError::DeserializeError(e.to_string(), resp)) | 
						|
            } | 
						|
            Err(UError::NetError(err, url, _)) => Err(UError::NetError(err, url, resp)), | 
						|
            _ => unreachable!(), | 
						|
        } | 
						|
        .map_err(From::from); | 
						|
 | 
						|
        debug!("url = {url}, response = {result:?}"); | 
						|
 | 
						|
        result | 
						|
    } | 
						|
 | 
						|
    /// get jobs for agent | 
						|
    pub async fn get_personal_jobs(&self, agent_id: Id) -> Result<api_types::GetPersonalJobs> { | 
						|
        self.req(format!("get_personal_jobs/{}", agent_id)).await | 
						|
    } | 
						|
 | 
						|
    /// send something to server | 
						|
    pub async fn report( | 
						|
        &self, | 
						|
        payload: impl IntoIterator<Item = messaging::Reportable>, | 
						|
    ) -> Result<api_types::Report> { | 
						|
        self.req_with_payload("report", &payload.into_iter().collect::<Vec<_>>()) | 
						|
            .await | 
						|
    } | 
						|
 | 
						|
    /// download payload | 
						|
    pub async fn _dl(&self, file: &str) -> Result<Vec<u8>> { | 
						|
        self.req(format!("dl/{file}")).await | 
						|
    } | 
						|
 | 
						|
    /// get exact job | 
						|
    pub async fn get_job(&self, job: Id, brief: Brief) -> Result<api_types::GetJob> { | 
						|
        self.req(format!("get_job/{job}?brief={brief}")).await | 
						|
    } | 
						|
 | 
						|
    pub async fn get_full_job(&self, job: Id) -> Result<api_types::GetJob> { | 
						|
        self.get_job(job, Brief::No).await | 
						|
    } | 
						|
 | 
						|
    pub async fn get_brief_job(&self, job: Id) -> Result<api_types::GetJob> { | 
						|
        self.get_job(job, Brief::Yes).await | 
						|
    } | 
						|
 | 
						|
    /// get all available jobs | 
						|
    pub async fn get_jobs(&self) -> Result<api_types::GetJobs> { | 
						|
        self.req("get_jobs").await | 
						|
    } | 
						|
} | 
						|
 | 
						|
//##########// Admin area //##########// | 
						|
#[cfg(feature = "panel")] | 
						|
impl HttpClient { | 
						|
    /// agent listing | 
						|
    pub async fn get_agents(&self, agent: Option<Id>) -> Result<api_types::GetAgents> { | 
						|
        self.req(format!("get_agents/{}", opt_to_string(agent))) | 
						|
            .await | 
						|
    } | 
						|
 | 
						|
    /// update agent | 
						|
    pub async fn update_agent(&self, agent: &Agent) -> Result<api_types::UpdateAgent> { | 
						|
        self.req_with_payload("update_agent", agent).await | 
						|
    } | 
						|
 | 
						|
    /// update job | 
						|
    pub async fn update_job(&self, job: &JobMeta) -> Result<api_types::UpdateJob> { | 
						|
        self.req_with_payload("update_job", job).await | 
						|
    } | 
						|
 | 
						|
    /// update result | 
						|
    pub async fn update_result(&self, result: &AssignedJob) -> Result<api_types::UpdateResult> { | 
						|
        self.req_with_payload("update_result", result).await | 
						|
    } | 
						|
 | 
						|
    pub async fn update_payload(&self, payload: &Payload) -> Result<api_types::UpdatePayload> { | 
						|
        self.req_with_payload("update_payload", payload).await | 
						|
    } | 
						|
 | 
						|
    /// create and upload job | 
						|
    pub async fn upload_jobs( | 
						|
        &self, | 
						|
        jobs: impl IntoIterator<Item = &Job>, | 
						|
    ) -> Result<api_types::UploadJobs> { | 
						|
        self.req_with_payload("upload_jobs", &jobs.into_iter().collect::<Vec<_>>()) | 
						|
            .await | 
						|
    } | 
						|
 | 
						|
    pub async fn upload_payload(&self, payload: &RawPayload) -> Result<api_types::UploadPayloads> { | 
						|
        self.req_with_payload("upload_payload", payload).await | 
						|
    } | 
						|
 | 
						|
    /// delete something | 
						|
    pub async fn del(&self, item: Id) -> Result<api_types::Del> { | 
						|
        self.req(format!("del/{item}")).await | 
						|
    } | 
						|
 | 
						|
    /// set jobs for any agent | 
						|
    pub async fn assign_jobs( | 
						|
        &self, | 
						|
        assigned: impl IntoIterator<Item = &AssignedJobById>, | 
						|
    ) -> Result<api_types::SetJobs> { | 
						|
        self.req_with_payload( | 
						|
            format!("assign_jobs"), | 
						|
            &assigned.into_iter().collect::<Vec<_>>(), | 
						|
        ) | 
						|
        .await | 
						|
    } | 
						|
 | 
						|
    /// get jobs for any agent by job_id, agent_id or result_id | 
						|
    pub async fn get_assigned_jobs(&self, id: Option<Id>) -> Result<api_types::GetAgentJobs> { | 
						|
        self.req(format!("get_assigned_jobs/{}", opt_to_string(id))) | 
						|
            .await | 
						|
    } | 
						|
 | 
						|
    pub async fn get_payloads(&self) -> Result<api_types::GetPayloads> { | 
						|
        self.req("get_payloads").await | 
						|
    } | 
						|
 | 
						|
    pub async fn get_payload( | 
						|
        &self, | 
						|
        payload: impl AsRef<str>, | 
						|
        brief: Brief, | 
						|
    ) -> Result<api_types::GetPayload> { | 
						|
        let payload = payload.as_ref(); | 
						|
        self.req(format!("get_payload/{payload}?brief={brief}")) | 
						|
            .await | 
						|
    } | 
						|
 | 
						|
    pub async fn ping(&self) -> Result<api_types::Ping> { | 
						|
        self.req("ping").await | 
						|
    } | 
						|
}
 | 
						|
 |