diff --git a/common/src/config.rs b/common/src/config.rs index 0950609ef..206173000 100644 --- a/common/src/config.rs +++ b/common/src/config.rs @@ -11,7 +11,7 @@ pub struct Config { pub storage: StorageConfig, pub monorepo: MonoConfig, pub pack: PackConfig, - pub relay: RelayConfig, + pub ztm: ZTMConfig, } impl Config { @@ -137,16 +137,18 @@ impl Default for PackConfig { } #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct RelayConfig { +pub struct ZTMConfig { pub ca: String, pub hub: String, + pub agent: String, } -impl Default for RelayConfig { +impl Default for ZTMConfig { fn default() -> Self { Self { ca: String::from("127.0.0.1:9999"), hub: String::from("127.0.0.1:8888"), + agent: String::from("127.0.0.1:7777"), } } } diff --git a/gateway/src/relay_server.rs b/gateway/src/relay_server.rs index 6f49043d0..075b9c19f 100644 --- a/gateway/src/relay_server.rs +++ b/gateway/src/relay_server.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use axum::body::Body; use axum::extract::{Query, State}; -use axum::http::{Response, StatusCode, Uri}; +use axum::http::{Request, Response, StatusCode, Uri}; use axum::routing::get; use axum::Router; use clap::Args; @@ -74,8 +74,8 @@ pub async fn app(config: Config, host: String, port: u16) -> Router { ) .route( "/*path", - get(get_method_router), // .post(post_method_router) - // .put(put_method_router), + get(get_method_router).post(post_method_router), + // .put(put_method_router), ) .layer(ServiceBuilder::new().layer(CorsLayer::new().allow_origin(Any))) .layer(TraceLayer::new_for_http()) @@ -88,11 +88,26 @@ async fn get_method_router( Query(params): Query, uri: Uri, ) -> Result, (StatusCode, String)> { - let relay_config = state.context.config.relay.clone(); + let ztm_config = state.context.config.ztm.clone(); if Regex::new(r"/hello$").unwrap().is_match(uri.path()) { return gemini::http::handler::hello_gemini(params).await; } else if Regex::new(r"/certificate$").unwrap().is_match(uri.path()) { - return gemini::ztm::handler::get_ztm_certificate(relay_config, params).await; + return gemini::http::handler::certificate(ztm_config, params).await; + } + Err(( + StatusCode::NOT_FOUND, + String::from("Operation not supported\n"), + )) +} + +async fn post_method_router( + state: State, + uri: Uri, + req: Request, +) -> Result, (StatusCode, String)> { + let ztm_config = state.context.config.ztm.clone(); + if Regex::new(r"/relay_init$").unwrap().is_match(uri.path()) { + return gemini::http::handler::init(ztm_config, req, state.0.port).await; } Err(( StatusCode::NOT_FOUND, diff --git a/gemini/src/http/handler.rs b/gemini/src/http/handler.rs index 7f15c0c05..774224f75 100644 --- a/gemini/src/http/handler.rs +++ b/gemini/src/http/handler.rs @@ -1,9 +1,15 @@ use axum::{ - body::Body, - http::{Response, StatusCode}, + body::{to_bytes, Body}, + http::{Request, Response, StatusCode}, }; +use common::config::ZTMConfig; -use crate::RelayGetParams; +use crate::{ + ztm::{ + ZTMUserPermit, {connect_ztm_hub, create_ztm_certificate, create_ztm_service}, + }, + RelayGetParams, +}; pub async fn hello_gemini(_params: RelayGetParams) -> Result, (StatusCode, String)> { Ok(Response::builder() @@ -11,5 +17,72 @@ pub async fn hello_gemini(_params: RelayGetParams) -> Result, (St .unwrap()) } +pub async fn certificate( + config: ZTMConfig, + params: RelayGetParams, +) -> Result, (StatusCode, String)> { + if params.name.is_none() { + return Err((StatusCode::BAD_REQUEST, "not enough paras".to_string())); + } + let name = params.name.unwrap(); + let permit = match create_ztm_certificate(config, name.clone()).await { + Ok(p) => p, + Err(e) => { + return Err((StatusCode::INTERNAL_SERVER_ERROR, e)); + } + }; + + let permit_json = serde_json::to_string(&permit).unwrap(); + tracing::info!("new permit [{name}]: {permit_json}"); + + Ok(Response::builder() + .header("Content-Type", "application/json") + .body(Body::from(permit_json)) + .unwrap()) +} + +pub async fn init( + config: ZTMConfig, + req: Request, + relay_port: u16, +) -> Result, (StatusCode, String)> { + // transfer body to json + let body_bytes = match to_bytes(req.into_body(), usize::MAX).await { + Ok(b) => b, + Err(e) => { + return Err((StatusCode::BAD_REQUEST, e.to_string())); + } + }; + + let permit: ZTMUserPermit = match serde_json::from_slice(&body_bytes) { + Ok(p) => p, + Err(e) => { + return Err((StatusCode::BAD_REQUEST, e.to_string())); + } + }; + + // 1. connect to ZTM hub (join a mesh) + let mesh = match connect_ztm_hub(config.clone(), permit).await { + Ok(m) => m, + Err(e) => { + return Err((StatusCode::INTERNAL_SERVER_ERROR, e)); + } + }; + + // 2. create a ZTM service + let response_text = + match create_ztm_service(config, mesh.agent.id, "relay".to_string(), relay_port).await { + Ok(m) => m, + Err(e) => { + return Err((StatusCode::INTERNAL_SERVER_ERROR, e)); + } + }; + + Ok(Response::builder() + .header("Content-Type", "application/json") + .body(Body::from(response_text)) + .unwrap()) +} + #[cfg(test)] mod tests {} diff --git a/gemini/src/ztm/handler.rs b/gemini/src/ztm/handler.rs deleted file mode 100644 index c9dc23f11..000000000 --- a/gemini/src/ztm/handler.rs +++ /dev/null @@ -1,91 +0,0 @@ -use axum::{ - body::Body, - http::{Response, StatusCode}, -}; -use common::config::RelayConfig; -use reqwest::Client; - -use crate::RelayGetParams; - -use super::{Agent, ZTMUserPermit}; - -pub async fn get_ztm_certificate( - config: RelayConfig, - params: RelayGetParams, -) -> Result, (StatusCode, String)> { - if params.name.is_none() { - return Err((StatusCode::BAD_REQUEST, "not enough paras".to_string())); - } - let name = params.name.unwrap(); - let ca_address = config.ca; - let hub_address = config.hub; - - //1. GET {ca}/api/certificates/ca -> ca certificate - let url = format!("http://{ca_address}/api/certificates/ca"); - let request_result = reqwest::get(url).await; - let ca_certificate = match handle_ztm_response(request_result).await { - Ok(s) => s, - Err(s) => { - return Err((StatusCode::INTERNAL_SERVER_ERROR, s)); - } - }; - - //2. POST {ca}/api/certificates/{username} -> user private key - let url = format!("http://{ca_address}/api/certificates/{name}"); - let client = Client::new(); - let request_result = client.post(url).send().await; - let user_key = match handle_ztm_response(request_result).await { - Ok(s) => s, - Err(s) => { - return Err((StatusCode::INTERNAL_SERVER_ERROR, s)); - } - }; - - //3. GET {ca}/api/certificates/{username} -> user certificate - let url = format!("http://{ca_address}/api/certificates/{name}"); - let request_result = reqwest::get(url).await; - let user_certificate = match handle_ztm_response(request_result).await { - Ok(s) => s, - Err(s) => { - return Err((StatusCode::INTERNAL_SERVER_ERROR, s)); - } - }; - - // Combine those into a json permit - let agent = Agent { - certificate: user_certificate.clone(), - private_key: user_key.clone(), - }; - - let permit = ZTMUserPermit { - ca: ca_certificate.clone(), - agent, - bootstraps: vec![hub_address], - }; - - let permit_json = serde_json::to_string(&permit).unwrap(); - tracing::info!("new permit [{name}]: {permit_json}"); - - Ok(Response::builder() - .header("Content-Type", "application/json") - .body(Body::from(permit_json)) - .unwrap()) -} - -pub async fn handle_ztm_response( - request_result: Result, -) -> Result { - match request_result { - Ok(res) => { - if res.status().is_success() { - Ok(res.text().await.unwrap()) - } else { - Err(res.text().await.unwrap()) - } - } - Err(e) => Err(e.to_string()), - } -} - -#[cfg(test)] -mod tests {} diff --git a/gemini/src/ztm/mod.rs b/gemini/src/ztm/mod.rs index cb290b900..b95a7b858 100644 --- a/gemini/src/ztm/mod.rs +++ b/gemini/src/ztm/mod.rs @@ -1,16 +1,209 @@ +use common::config::ZTMConfig; +use reqwest::{header::CONTENT_TYPE, Client}; use serde::{Deserialize, Serialize}; -pub mod handler; - #[derive(Deserialize, Serialize, Debug)] pub struct ZTMUserPermit { + pub ca: String, + pub agent: CertAgent, + pub bootstraps: Vec, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct CertAgent { + pub name: String, + pub certificate: String, + #[serde(rename = "privateKey")] + pub private_key: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct ZTMMesh { + pub name: String, pub ca: String, pub agent: Agent, pub bootstraps: Vec, + pub connected: bool, + pub errors: Vec, } #[derive(Deserialize, Serialize, Debug)] pub struct Agent { + pub id: String, + pub name: String, + pub username: String, pub certificate: String, - pub private_key: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct ZTMServiceReq { + pub host: String, + pub port: u16, +} + +/// create ztm certificate +/// ztm.ca and ztm.hub in config.toml are required +/// # Arguments +/// * `config` - ZTMConfig +/// * `name` - String +/// +/// # Returns +/// * ZTMUserPermit +/// +/// ZTMUserPermit include ca_certificate,user_certificate,user_key +/// ``` +pub async fn create_ztm_certificate( + config: ZTMConfig, + name: String, +) -> Result { + let ca_address = config.ca; + let hub_address = config.hub; + + //1. GET {ca}/api/certificates/ca -> ca certificate + let url = format!("{ca_address}/api/certificates/ca"); + let request_result = reqwest::get(url).await; + let ca_certificate = match handle_ztm_response(request_result).await { + Ok(s) => s, + Err(s) => { + return Err(s); + } + }; + + //2. POST {ca}/api/certificates/{username} -> user private key + let url = format!("{ca_address}/api/certificates/{name}"); + let client = Client::new(); + let request_result = client.post(url).send().await; + let user_key = match handle_ztm_response(request_result).await { + Ok(s) => s, + Err(s) => { + return Err(s); + } + }; + + //3. GET {ca}/api/certificates/{username} -> user certificate + let url = format!("{ca_address}/api/certificates/{name}"); + let request_result = reqwest::get(url).await; + let user_certificate = match handle_ztm_response(request_result).await { + Ok(s) => s, + Err(s) => { + return Err(s); + } + }; + + // Combine those into a json permit + let agent = CertAgent { + name: name.clone(), + certificate: user_certificate.clone(), + private_key: user_key.clone(), + }; + + let hub_address = hub_address.replace("http://", ""); + let permit = ZTMUserPermit { + ca: ca_certificate.clone(), + agent, + bootstraps: vec![hub_address], + }; + + let permit_json = serde_json::to_string(&permit).unwrap(); + tracing::info!("new permit [{name}]: {permit_json}"); + + Ok(permit) +} + +/// connect to hub (join a mesh) +/// ztm.agent in config.toml is required +/// # Arguments +/// * `config` - ZTMConfig +/// * `permit` - ZTMUserPermit +/// +/// # Returns +/// * ZTMMesh +/// +/// ``` +pub async fn connect_ztm_hub(config: ZTMConfig, permit: ZTMUserPermit) -> Result { + // POST {agent}/api/meshes/${meshName} + let permit_string = serde_json::to_string(&permit).unwrap(); + let agent_address = config.agent; + let url = format!("{agent_address}/api/meshes/relay_mesh"); + let client = Client::new(); + let request_result = client + .post(url) + .header(CONTENT_TYPE, "application/json") + .body(permit_string) + .send() + .await; + let response_text = match handle_ztm_response(request_result).await { + Ok(s) => s, + Err(s) => { + return Err(s); + } + }; + + let mesh: ZTMMesh = match serde_json::from_slice(response_text.as_bytes()) { + Ok(p) => p, + Err(e) => { + return Err(e.to_string()); + } + }; + Ok(mesh) +} + +/// create ZTM Service +/// ztm.agent in config.toml is required +/// # Arguments +/// * `config` - ZTMConfig +/// * `ep_id` - String +/// * `service_name` - String +/// * `port` - u16 +/// +/// # Returns +/// * String +/// +/// ``` +pub async fn create_ztm_service( + config: ZTMConfig, + ep_id: String, + service_name: String, + port: u16, +) -> Result { + // create a ZTM service + // POST {agent}/api/meshes/${mesh.name}/endpoints/${ep.id}/services/${svcName} + let agent_address = config.agent; + let url = format!( + "{agent_address}/api/meshes/relay_mesh/endpoints/{ep_id}/services/tcp/{service_name}" + ); + let client = Client::new(); + let req = ZTMServiceReq { + host: "127.0.0.1".to_string(), + port, + }; + let req_string = serde_json::to_string(&req).unwrap(); + let request_result = client + .post(url) + .header(CONTENT_TYPE, "application/json") + .body(req_string) + .send() + .await; + let response_text = match handle_ztm_response(request_result).await { + Ok(s) => s, + Err(s) => { + return Err(s); + } + }; + Ok(response_text) +} + +pub async fn handle_ztm_response( + request_result: Result, +) -> Result { + match request_result { + Ok(res) => { + if res.status().is_success() { + Ok(res.text().await.unwrap()) + } else { + Err(res.text().await.unwrap()) + } + } + Err(e) => Err(e.to_string()), + } } diff --git a/mega/config.toml b/mega/config.toml index 35bd74be8..d13aa606f 100644 --- a/mega/config.toml +++ b/mega/config.toml @@ -69,6 +69,7 @@ clean_cache_after_decode = true channel_message_size = 1_000_000 -[relay] -ca = "127.0.0.1:9999" -hub = "127.0.0.1:8888" \ No newline at end of file +[ztm] +ca = "http://127.0.0.1:9999" +hub = "http://127.0.0.1:8888" +agent = "http://127.0.0.1:7777" \ No newline at end of file