Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,10 @@ run-frontend:
run-backend:
cd backend && npm i && node index.js
run-tests:
cd test/cypress && npm i && npx cypress run
cd test/cypress && npm i && npx cypress run

run-spa-frontend:
cd spa/frontend && npm i && npm run dev

run-spa-backend:
cd spa/backend && npm i && node index.js
1 change: 1 addition & 0 deletions forward-proxy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dotenv = "0.15.0"
futures = "0.3.31"
pingora-router = { path = "../pingora-router" }
once_cell = "1.21.3"
utils = { path = "../utils" }

[patch.crates-io]
sfv = { git = "https://github.com/undef1nd/sfv.git", tag = "v0.9.4" }
9 changes: 8 additions & 1 deletion forward-proxy/src/handler/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@ impl ForwardHeaderKeys {
}

const LAYER8_URL: &str = "http://127.0.0.1:5001";
const RP_URL: &str = "http://127.0.0.1:6193";
const RP_URL: &str = "http://127.0.0.1:6194";

pub static RP_INIT_ENCRYPTED_TUNNEL_PATH: Lazy<String> = Lazy::new(|| format!("{}/init-tunnel", RP_URL));
pub static RP_PROXY_PATH: Lazy<String> = Lazy::new(|| format!("{}/proxy", RP_URL));
pub static LAYER8_GET_CERTIFICATE_PATH: Lazy<String> = Lazy::new(|| format!("{}/sp-pub-key?backend_url=", LAYER8_URL));

pub const NTOR_SERVER_ID: &str = "ReverseProxyServer";
pub const NTOR_STATIC_PUBLIC_KEY: [u8; 32] = [
131, 210, 36, 101, 39, 191, 61, 165, 29, 112, 94, 149, 120, 202, 189, 170,
151, 62, 247, 71, 208, 255, 144, 173, 52, 223, 239, 221, 153, 225, 40, 10
];
28 changes: 28 additions & 0 deletions forward-proxy/src/handler/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use serde::Serialize;
use chrono::Utc;
use jsonwebtoken::{encode, EncodingKey, Header};

// Get SECRET_KEY from environment variable
pub fn get_secret_key() -> String {
std::env::var("JWT_SECRET_KEY").expect("JWT_SECRET_KEY must be set")
}

#[derive(Serialize)]
struct Claims {
exp: usize,
}

pub fn generate_standard_token(secret_key: &str) -> pingora::Result<String, Box<dyn std::error::Error>> {
let now = Utc::now();
let claims = Claims {
exp: (now + chrono::Duration::days(1)).timestamp() as usize,
};

let token = encode(
&Header::new(jsonwebtoken::Algorithm::HS256),
&claims,
&EncodingKey::from_secret(secret_key.as_bytes()),
)?;

Ok(token)
}
64 changes: 44 additions & 20 deletions forward-proxy/src/handler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ use pingora::http::StatusCode;
use reqwest::{Client, Response};
use pingora_router::ctx::{Layer8Context, Layer8ContextTrait};
use pingora_router::handler::{APIHandlerResponse, DefaultHandlerTrait, RequestBodyTrait};
use crate::handler::types::response::{ErrorResponse, FpHealthcheckError, FpHealthcheckSuccess, InitEncryptedTunnelResponse, ProxyResponse};
use crate::handler::types::response::{ErrorResponse, FpHealthcheckError, FpHealthcheckSuccess, InitTunnelResponseFromRP, InitTunnelResponseToINT, ProxyResponse};
use pingora_router::handler::ResponseBodyTrait;
use reqwest::header::HeaderMap;
use crate::handler::consts::ForwardHeaderKeys::{FpHeaderRequestKey, FpHeaderResponseKey};
use crate::handler::consts::{LAYER8_GET_CERTIFICATE_PATH, RP_INIT_ENCRYPTED_TUNNEL_PATH, RP_PROXY_PATH};
use crate::handler::types::request::{InitEncryptedTunnelRequest, ProxyRequest};
use crate::handler::consts::{LAYER8_GET_CERTIFICATE_PATH, NTOR_SERVER_ID, NTOR_STATIC_PUBLIC_KEY, RP_INIT_ENCRYPTED_TUNNEL_PATH, RP_PROXY_PATH};
use crate::handler::types::request::{InitTunnelRequest, ProxyRequest};
use utils;

pub mod types;
mod utils;
mod helpers;
mod consts;

pub struct ForwardHandler {
Expand All @@ -20,17 +21,20 @@ pub struct ForwardHandler {

impl DefaultHandlerTrait for ForwardHandler {}

type NTorPublicKey = Vec<u8>;
struct NTorServerCertificate {
server_id: String,
public_key: Vec<u8>,
}

impl ForwardHandler {
async fn get_public_key(
&self,
backend_url: String,
ctx: &mut Layer8Context
) -> Result<NTorPublicKey, APIHandlerResponse>
) -> Result<NTorServerCertificate, APIHandlerResponse>
{
let secret_key = utils::get_secret_key();
let token = utils::generate_standard_token(&secret_key).unwrap();
let secret_key = helpers::get_secret_key();
let token = self::helpers::generate_standard_token(&secret_key).unwrap();
let client = Client::new();

return match client
Expand All @@ -54,7 +58,10 @@ impl ForwardHandler {
})
} else {
// todo extract public key from response
Ok(vec![])
Ok(NTorServerCertificate {
server_id: "".to_string(),
public_key: vec![],
})
}
}
Err(e) => {
Expand Down Expand Up @@ -101,7 +108,7 @@ impl ForwardHandler {
) -> HeaderMap {
// copy all origin header to new request
let origin_headers = ctx.get_request_header().clone();
let mut reqwest_header = utils::to_reqwest_header(origin_headers);
let mut reqwest_header = ::utils::to_reqwest_header(origin_headers);

// add forward proxy header `fp_request_header`
reqwest_header.insert(
Expand All @@ -120,6 +127,7 @@ impl ForwardHandler {
ctx: &mut Layer8Context,
headers: HeaderMap,
body: Vec<u8>,
ntor_server_certificate: NTorServerCertificate
) -> APIHandlerResponse
{
let body_string = utils::bytes_to_string(&body);
Expand All @@ -141,22 +149,29 @@ impl ForwardHandler {
info!("{log_meta} response headers from RP: {:?}", headers);
info!("{log_meta} response body from RP: {}", utils::bytes_to_string(&rp_response_body.to_vec()));

// validate reverse proxy response format, is it necessary?
return match utils::bytes_to_json::<InitEncryptedTunnelResponse>(rp_response_body.to_vec()) {
return match utils::bytes_to_json::<InitTunnelResponseFromRP>(rp_response_body.to_vec()) {
Err(e) => {
error!("Error parsing RP response: {:?}", e);
APIHandlerResponse {
status: StatusCode::INTERNAL_SERVER_ERROR,
body: None,
}
}
_ => {
Ok(res_from_rp) => {
// forward ReverseProxy's headers
ForwardHandler::create_response_headers(headers, ctx, FpHeaderResponseKey.placeholder_value());

let res_to_int = InitTunnelResponseToINT {
ephemeral_public_key: res_from_rp.public_key,
t_b_hash: res_from_rp.t_b_hash,
session_id: res_from_rp.session_id,
static_public_key: ntor_server_certificate.public_key,
server_id: ntor_server_certificate.server_id,
};

APIHandlerResponse {
status: StatusCode::OK,
body: Some(rp_response_body.to_vec()), // forward reverse proxy's response
body: Some(res_to_int.to_bytes()),
}
}
};
Expand Down Expand Up @@ -212,7 +227,7 @@ impl ForwardHandler {
pub async fn handle_init_encrypted_tunnel(&self, ctx: &mut Layer8Context) -> APIHandlerResponse {
// validate request body
let received_body = match ForwardHandler::parse_request_body::<
InitEncryptedTunnelRequest,
InitTunnelRequest,
ErrorResponse
>(&ctx.get_request_body())
{
Expand All @@ -236,9 +251,13 @@ impl ForwardHandler {
None => "".to_string()
};

//todo handle result_public_key
let _result_public_key = self.get_public_key(backend_url.to_string(), ctx).await;
//todo handle result_certificate
let _result_certificate = self.get_public_key(backend_url.to_string(), ctx).await;
// println!("public_key: {:?}", result_public_key);
let ntor_server_certificate = NTorServerCertificate { // hardcoded for now
server_id: NTOR_SERVER_ID.to_string(),
public_key: NTOR_STATIC_PUBLIC_KEY.to_vec(),
};

// copy origin headers and add ForwardProxy header
let new_headers = ForwardHandler::create_forward_request_headers(
Expand All @@ -248,7 +267,12 @@ impl ForwardHandler {
);

// forward origin request body
ForwardHandler::init_tunnel_forward_to_rp(ctx, new_headers, received_body).await
ForwardHandler::init_tunnel_forward_to_rp(
ctx,
new_headers,
received_body,
ntor_server_certificate
).await
}

/// forward manipulated `proxy` request to ReverseProxy and handle success response
Expand All @@ -258,7 +282,7 @@ impl ForwardHandler {
body: Vec<u8>,
) -> APIHandlerResponse
{
let body_string = utils::bytes_to_string(&body);
let body_string = ::utils::bytes_to_string(&body);
let log_meta = format!("[FORWARD {}]", RP_PROXY_PATH.as_str());
debug!("{log_meta} request headers to RP: {:?}", headers);
debug!("{log_meta} request body to RP: {}", body_string);
Expand All @@ -279,7 +303,7 @@ impl ForwardHandler {
debug!("{log_meta} response body from RP: {}", utils::bytes_to_string(&rp_response_bytes.to_vec()));

// validate reverse proxy's response body format, is it necessary?
match utils::bytes_to_json::<ProxyResponse>(rp_response_bytes.to_vec()) {
match ::utils::bytes_to_json::<ProxyResponse>(rp_response_bytes.to_vec()) {
Err(err) => {
error!("Reverse Proxy's response mismatch: {:}", err);
return APIHandlerResponse {
Expand Down
6 changes: 3 additions & 3 deletions forward-proxy/src/handler/types/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ use serde::{Deserialize, Serialize};
use pingora_router::handler::RequestBodyTrait;

#[derive(Serialize, Deserialize, Debug)]
pub struct InitEncryptedTunnelRequest {
pub int_request_body: String,
pub struct InitTunnelRequest {
pub public_key: Vec<u8>,
}

impl RequestBodyTrait for InitEncryptedTunnelRequest {}
impl RequestBodyTrait for InitTunnelRequest {}

#[derive(Serialize, Deserialize, Debug)]
pub struct ProxyRequest {
Expand Down
19 changes: 16 additions & 3 deletions forward-proxy/src/handler/types/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,24 @@ impl ResponseBodyTrait for ErrorResponse {
}

#[derive(Serialize, Deserialize, Debug)]
pub struct InitEncryptedTunnelResponse { // this struct should match ReverseProxy's Response
pub rp_response_body: String,
pub struct InitTunnelResponseFromRP { // this struct should match ReverseProxy's Response
pub public_key: Vec<u8>,
pub t_b_hash: Vec<u8>,
pub session_id: String,
}

impl ResponseBodyTrait for InitTunnelResponseFromRP {}

#[derive(Serialize, Deserialize, Debug)]
pub struct InitTunnelResponseToINT { // this struct should match Interceptor's expected Response
pub ephemeral_public_key: Vec<u8>,
pub t_b_hash: Vec<u8>,
pub session_id: String,
pub static_public_key: Vec<u8>,
pub server_id: String
}

impl ResponseBodyTrait for InitEncryptedTunnelResponse {}
impl ResponseBodyTrait for InitTunnelResponseToINT {}

#[derive(Serialize, Deserialize, Debug)]
pub struct ProxyResponse {
Expand Down
6 changes: 6 additions & 0 deletions reverse-proxy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ tokio = "1.44.2"
pingora-router = { path = "../pingora-router" }
pingora = { version = "0.4.0", features = ["lb"] }
futures = "0.3.31"
ntor = { git = "https://github.com/globe-and-citizen/ntor.git", tag = "0.1.0"}
#ntor = { path = "../../../ntor" }
config = "0.15.11"
toml = "0.8.23"
uuid = { version = "1.16.0", features = ["v4"] }
utils = { path = "../utils" }

[patch.crates-io]
sfv = { git = "https://github.com/undef1nd/sfv.git", tag = "v0.9.4" }
16 changes: 16 additions & 0 deletions reverse-proxy/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[upstream]
host="localhost"
port=0

[server]
local_address="127.0.0.1:6194"
public_address="0.0.0.0:6193"

[log]
level="DEBUG"
path="console"

[handler]
#jwt_secret="this is 32-byte wgp's jwt secret"
ntor_server_id="ReverseProxyServer"
ntor_static_secret="this is 32-byte nTorStaticSecret"
66 changes: 66 additions & 0 deletions reverse-proxy/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use std::fs;
use serde::Deserialize;
use toml;

#[derive(Debug, Deserialize)]
pub struct Config {
pub upstream: UpstreamConfig,
pub log: LogConfig,
pub server: ServerConfig,
pub handler: HandlerConfig
}

impl Config {
/// panic if unable to validate.
/// assuming after this validation, all configs are valid
pub fn validate(&self) {
// todo
}
}

impl Config {
pub fn from_file(path: &str) -> Self {
let content = fs::read_to_string(path).expect("Failed to read configuration file");
toml::from_str(&content).expect("Failed to parse configuration file")
}
}

#[derive(Debug, Deserialize)]
pub(super) struct UpstreamConfig {
pub host: String,
pub port: u16,
}

#[derive(Debug, Deserialize)]
pub(super) struct LogConfig {
pub path: String,
pub level: String,
}

impl LogConfig {
pub fn to_level_filter(&self) -> log::LevelFilter {
match self.level.to_uppercase().as_str() {
"INFO" => log::LevelFilter::Info,
"DEBUG" => log::LevelFilter::Debug,
"WARNING" => log::LevelFilter::Warn,
"ERROR" => log::LevelFilter::Error,
"TRACE" => log::LevelFilter::Trace,
"OFF" => log::LevelFilter::Off,
_ => log::max_level()
}
}
}

#[derive(Debug, Deserialize)]
pub(super) struct ServerConfig {
pub local_address: String,
pub public_address: String
}

#[derive(Debug, Deserialize)]
pub(super) struct HandlerConfig {
pub ntor_server_id: String,
pub ntor_static_secret: String,
}


Loading
Loading