Merge branch 'dev'

This commit is contained in:
Ara Sadoyan
2026-06-22 13:24:42 +02:00
9 changed files with 132 additions and 40 deletions

43
Cargo.lock generated
View File

@@ -127,7 +127,7 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]] [[package]]
name = "aralez" name = "aralez"
version = "0.92.10" version = "0.92.11"
dependencies = [ dependencies = [
"ahash", "ahash",
"arc-swap", "arc-swap",
@@ -139,6 +139,7 @@ dependencies = [
"futures", "futures",
"instant-acme", "instant-acme",
"jsonwebtoken", "jsonwebtoken",
"libc",
"log", "log",
"log4rs", "log4rs",
"mimalloc", "mimalloc",
@@ -164,7 +165,7 @@ dependencies = [
"subtle", "subtle",
"tokio", "tokio",
"tonic", "tonic",
"tower-http", "tower-http 0.7.0",
"urlencoding", "urlencoding",
"x509-parser", "x509-parser",
] ]
@@ -1712,9 +1713,9 @@ dependencies = [
[[package]] [[package]]
name = "inotify" name = "inotify"
version = "0.11.1" version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" checksum = "533e68a5842e734946fe159fb03fc9bbbb254f590dd0d8ad321ae5ff7beca2c1"
dependencies = [ dependencies = [
"bitflags 2.11.1", "bitflags 2.11.1",
"inotify-sys", "inotify-sys",
@@ -1871,9 +1872,9 @@ dependencies = [
[[package]] [[package]]
name = "kqueue" name = "kqueue"
version = "1.1.1" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5"
dependencies = [ dependencies = [
"kqueue-sys", "kqueue-sys",
"libc", "libc",
@@ -1881,9 +1882,9 @@ dependencies = [
[[package]] [[package]]
name = "kqueue-sys" name = "kqueue-sys"
version = "1.1.0" version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7b65860415f949f23fa882e669f2dbd4a0f0eeb1acdd56790b30494afd7da2f" checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087"
dependencies = [ dependencies = [
"bitflags 2.11.1", "bitflags 2.11.1",
"libc", "libc",
@@ -2211,9 +2212,9 @@ dependencies = [
[[package]] [[package]]
name = "noyalib" name = "noyalib"
version = "0.0.7" version = "0.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaecb583890b00deddd413f1c9522cc3537e893cd3845dec578ed6c85de653f7" checksum = "14057395c16a4230575c6f86bfa074db87e8458626d3e56b20c2454334a7e50c"
dependencies = [ dependencies = [
"indexmap 2.14.0", "indexmap 2.14.0",
"itoa", "itoa",
@@ -3190,7 +3191,7 @@ dependencies = [
"tokio-rustls", "tokio-rustls",
"tokio-util", "tokio-util",
"tower", "tower",
"tower-http", "tower-http 0.6.11",
"tower-service", "tower-service",
"url", "url",
"wasm-bindgen", "wasm-bindgen",
@@ -4085,6 +4086,24 @@ name = "tower-http"
version = "0.6.11" version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [
"bitflags 2.11.1",
"bytes",
"futures-util",
"http",
"http-body",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
"url",
]
[[package]]
name = "tower-http"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b11f75e912b0c2be01b63d8cf8057b8c3f97cf34abb3d431a3a4c8675498e233"
dependencies = [ dependencies = [
"bitflags 2.11.1", "bitflags 2.11.1",
"bytes", "bytes",
@@ -4101,10 +4120,8 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
"tokio-util", "tokio-util",
"tower",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
"url",
] ]
[[package]] [[package]]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "aralez" name = "aralez"
version = "0.92.10" version = "0.92.11"
edition = "2021" edition = "2021"
license = "Apache-2.0" license = "Apache-2.0"
description = "Reverse proxy built on top of Cloudflare's Pingora" description = "Reverse proxy built on top of Cloudflare's Pingora"
@@ -16,20 +16,20 @@ strip = true
[dependencies] [dependencies]
tokio = { version = "1.52.3", features = ["full"] } tokio = { version = "1.52.3", features = ["full"] }
pingora = { version = "0.8.0", features = ["lb", "openssl"] } # openssl, rustls, boringssl pingora = { version = "0.8.1", features = ["lb", "openssl"] } # openssl, rustls, boringssl
serde = { version = "1.0.228", features = ["derive"] } pingora-core = "0.8.1"
pingora-proxy = "0.8.1"
pingora-http = "0.8.1"
pingora-limits = "0.8.1"
dashmap = "7.0.0-rc2" dashmap = "7.0.0-rc2"
pingora-core = "0.8.0"
pingora-proxy = "0.8.0"
pingora-http = "0.8.0"
pingora-limits = "0.8.0"
async-trait = "0.1.89" async-trait = "0.1.89"
log = "0.4.30" log = "0.4.30"
futures = "0.3.32" futures = "0.3.32"
notify = "9.0.0-rc.4" notify = "9.0.0-rc.4"
axum = { version = "0.8.9" } axum = { version = "0.8.9" }
reqwest = { version = "0.13.4", features = ["json", "stream", "blocking"] } reqwest = { version = "0.13.4", features = ["json", "stream", "blocking"] }
noyalib = { version = "0.0.7", features = ["compat-serde-yaml"] } serde = { version = "1.0.228", features = ["derive"] }
noyalib = { version = "0.0.8", features = ["compat-serde-yaml"] }
rand = "0.10.1" rand = "0.10.1"
base64 = "0.22.1" base64 = "0.22.1"
jsonwebtoken = { version = "10.4.0", default-features = false, features = ["use_pem", "rust_crypto"] } jsonwebtoken = { version = "10.4.0", default-features = false, features = ["use_pem", "rust_crypto"] }
@@ -41,7 +41,7 @@ arc-swap = "1.9.1"
prometheus = "0.14.0" prometheus = "0.14.0"
x509-parser = "0.18.1" x509-parser = "0.18.1"
rustls-pemfile = "2.2.0" rustls-pemfile = "2.2.0"
tower-http = { version = "0.6.11", features = ["fs"] } tower-http = { version = "0.7.0", features = ["fs"] }
privdrop = "0.5.6" privdrop = "0.5.6"
serde_json = "1.0.150" serde_json = "1.0.150"
subtle = "2.6.1" subtle = "2.6.1"
@@ -53,3 +53,4 @@ log4rs = "1.4.0"
mimalloc = { version = "0.1.52", default-features = false } mimalloc = { version = "0.1.52", default-features = false }
signal-hook = "0.4.4" signal-hook = "0.4.4"
sd-notify = "0.5.0" sd-notify = "0.5.0"
libc = "0.2.186"

View File

@@ -74,6 +74,7 @@ Built on Rust, on top of **Cloudflares Pingora engine**, **Aralez** delivers
| **proxy_address_tls** | 0.0.0.0:6194 | Aralez HTTPS bind address (Optional) | | **proxy_address_tls** | 0.0.0.0:6194 | Aralez HTTPS bind address (Optional) |
| **proxy_configs** | /etc/aralez/ | Direcotry containing configuration files, must be writeable by user `aralez` | | **proxy_configs** | /etc/aralez/ | Direcotry containing configuration files, must be writeable by user `aralez` |
| **upstreams_conf** | /etc/aralez/upstreams.yaml | Location of the upstreams file | | **upstreams_conf** | /etc/aralez/upstreams.yaml | Location of the upstreams file |
| **access_log** | access | Configure access logging. Values: `access, error` |
| **log_level** | info | Log level: `info`, `warn`, `error`, `debug`, `trace`, `off` | | **log_level** | info | Log level: `info`, `warn`, `error`, `debug`, `trace`, `off` |
| **log_file** | /full/path/to/aralez.log | Optional, the location of log file. If thi entry does not exist logs will be emitted to stdout. | | **log_file** | /full/path/to/aralez.log | Optional, the location of log file. If thi entry does not exist logs will be emitted to stdout. |
| **hc_method** | HEAD | Healthcheck method: HEAD, GET, POST (UPPERCASE) | | **hc_method** | HEAD | Healthcheck method: HEAD, GET, POST (UPPERCASE) |

View File

@@ -112,6 +112,7 @@ pub struct AppConfig {
pub hc_method: String, pub hc_method: String,
pub upstreams_conf: String, pub upstreams_conf: String,
pub log_level: String, pub log_level: String,
pub access_log: Option<String>,
pub pid_file: Option<String>, pub pid_file: Option<String>,
pub master_key: Option<String>, pub master_key: Option<String>,
pub config_address: String, pub config_address: String,

View File

@@ -10,9 +10,12 @@ use sha2::{Digest, Sha256};
use std::any::type_name; use std::any::type_name;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fmt::Write; use std::fmt::Write;
use std::fs::OpenOptions;
use std::io::Write as IoWrite;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::net::TcpListener; use std::net::TcpListener;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::os::unix::fs::OpenOptionsExt;
use std::str::FromStr; use std::str::FromStr;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc::{channel, Sender}; use std::sync::mpsc::{channel, Sender};
@@ -380,3 +383,14 @@ pub fn prepend(prefix: &str, val: &Option<Arc<str>>, uri: &str, port: &str) -> O
buf buf
}) })
} }
pub fn write_pid_file(path: &str) -> std::io::Result<()> {
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.custom_flags(libc::O_NOFOLLOW) // refuse to follow symlinks
.open(path)?;
file.write_all(process::id().to_string().as_bytes())?;
Ok(())
}

View File

@@ -1,6 +1,7 @@
pub mod acme; pub mod acme;
pub mod bgservice; pub mod bgservice;
pub mod gethosts; pub mod gethosts;
pub mod logging;
pub mod proxyhttp; pub mod proxyhttp;
pub mod start; pub mod start;
pub mod webserver; pub mod webserver;

57
src/web/logging.rs Normal file
View File

@@ -0,0 +1,57 @@
use log::info;
use pingora_proxy::Session;
use std::net::{IpAddr, Ipv4Addr};
use std::sync::OnceLock;
pub static ACCESS_LOG: OnceLock<LogLevel> = OnceLock::new();
pub fn init_access_log(level_str: &str) {
let level = LogLevel::from_str(level_str);
let _ = ACCESS_LOG.set(level);
}
pub enum LogLevel {
Access,
Error,
None,
}
impl LogLevel {
pub fn from_str(s: &str) -> Self {
match s {
"all" => LogLevel::Access,
"error" => LogLevel::Error,
_ => LogLevel::None,
}
}
}
pub fn access_log(response_code: u16, summary: &str, session: &Session) {
let level = ACCESS_LOG.get().unwrap_or(&LogLevel::None);
let should_log = match level {
LogLevel::Access => true,
LogLevel::None => false,
LogLevel::Error => !(100..=399).contains(&response_code),
};
if !should_log {
return;
}
let ip = session
.client_addr()
.and_then(|addr| addr.as_inet())
.map(|addr| addr.ip())
.unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST));
let user_agent = session.req_header().headers.get("user-agent").and_then(|v| v.to_str().ok()).unwrap_or("-");
info!(
"{}, response code: {response_code}, client: {}, version: {:?}, useragent: {}",
summary,
ip,
session.req_header().version,
user_agent,
);
}

View File

@@ -3,10 +3,11 @@ use crate::utils::lazylock::{LOCALHOST, RATE_LIMITER, REQUESTS_4XX, REVERSE_STOR
use crate::utils::metrics::*; use crate::utils::metrics::*;
use crate::utils::structs::{AppConfig, Extraparams, Headers, InnerMap, UpstreamsDashMap, UpstreamsIdMap}; use crate::utils::structs::{AppConfig, Extraparams, Headers, InnerMap, UpstreamsDashMap, UpstreamsIdMap};
use crate::web::gethosts::{GetHost, GetHostsReturHeaders}; use crate::web::gethosts::{GetHost, GetHostsReturHeaders};
use crate::web::logging::access_log;
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use async_trait::async_trait; use async_trait::async_trait;
use axum::body::Bytes; use axum::body::Bytes;
use log::{debug, error, warn}; use log::error;
use pingora::http::{RequestHeader, ResponseHeader, StatusCode}; use pingora::http::{RequestHeader, ResponseHeader, StatusCode};
use pingora::prelude::*; use pingora::prelude::*;
use pingora::ErrorSource::Upstream; use pingora::ErrorSource::Upstream;
@@ -20,10 +21,6 @@ use std::sync::Arc;
use tokio::time::Instant; use tokio::time::Instant;
thread_local! {static IP_BUFFER: RefCell<String> = RefCell::new(String::with_capacity(50));} thread_local! {static IP_BUFFER: RefCell<String> = RefCell::new(String::with_capacity(50));}
// static REVERSE_STORE: LazyLock<DashMap<String, String>> = LazyLock::new(DashMap::new);
// pub static RATE_LIMITER: LazyLock<Rate> = LazyLock::new(|| Rate::new(Duration::from_secs(1)));
// pub static REQUESTS_4XX: LazyLock<Cache<IpAddr, u32>> = LazyLock::new(|| Cache::builder().time_to_live(Duration::from_secs(1)).build());
// pub static LOCALHOST: LazyLock<Arc<str>> = LazyLock::new(|| Arc::from("localhost"));
#[derive(Clone)] #[derive(Clone)]
pub struct LB { pub struct LB {
@@ -86,7 +83,6 @@ impl ProxyHttp for LB {
if let Some(auth) = _ctx.extraparams.authentication.as_ref().or(innermap.authorization.as_ref()) { if let Some(auth) = _ctx.extraparams.authentication.as_ref().or(innermap.authorization.as_ref()) {
if !authenticate(&auth, session).await { if !authenticate(&auth, session).await {
let _ = session.respond_error(401).await; let _ = session.respond_error(401).await;
warn!("Forbidden: {:?}, {}", session.client_addr(), session.req_header().uri.path());
return Ok(true); return Ok(true);
} }
} }
@@ -99,9 +95,9 @@ impl ProxyHttp for LB {
let header = ResponseHeader::build(429, None)?; let header = ResponseHeader::build(429, None)?;
session.set_keepalive(None); session.set_keepalive(None);
session.write_response_header(Box::new(header), true).await?; session.write_response_header(Box::new(header), true).await?;
if let (Some(oi), Some(oa)) = (&_ctx.hostname, rate_key) { // if let (Some(oi), Some(oa)) = (&_ctx.hostname, rate_key) {
warn!("Limit 4XX: {}-rps exceed on {} from {} path {}", rate, oi, oa, session.req_header().uri.path()); // warn!("Limit 4XX: {}-rps exceed on {} from {} path {}", rate, oi, oa, session.req_header().uri.path());
} // }
return Ok(true); return Ok(true);
} }
} }
@@ -113,9 +109,9 @@ impl ProxyHttp for LB {
let header = ResponseHeader::build(429, None)?; let header = ResponseHeader::build(429, None)?;
session.set_keepalive(None); session.set_keepalive(None);
session.write_response_header(Box::new(header), true).await?; session.write_response_header(Box::new(header), true).await?;
if let (Some(oi), Some(oa)) = (&_ctx.hostname, rate_key) { // if let (Some(oi), Some(oa)) = (&_ctx.hostname, rate_key) {
warn!("Limit: {}-rps exceed on {} from {}", rate, oi, oa); // warn!("Limit: {}-rps exceed on {} from {}", rate, oi, oa);
} // }
return Ok(true); return Ok(true);
} }
} }
@@ -281,14 +277,12 @@ impl ProxyHttp for LB {
REVERSE_STORE.insert(hh.clone(), bid.clone()); REVERSE_STORE.insert(hh.clone(), bid.clone());
hh hh
}; };
// let _ = _upstream_response.insert_header("set-cookie", format!("backend_id={}; Path=/; Max-Age=600; HttpOnly; SameSite=Lax", tt));
let mut buf = String::with_capacity(80); let mut buf = String::with_capacity(80);
buf.push_str("backend_id="); buf.push_str("backend_id=");
buf.push_str(&tt); buf.push_str(&tt);
buf.push_str("; Path=/; Max-Age="); buf.push_str("; Path=/; Max-Age=");
buf.push_str(&val.to_string()); buf.push_str(&val.to_string());
buf.push_str("; HttpOnly; SameSite=Lax"); buf.push_str("; HttpOnly; SameSite=Lax");
// buf.push_str("; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax");
let _ = _upstream_response.insert_header("set-cookie", buf.as_str()); let _ = _upstream_response.insert_header("set-cookie", buf.as_str());
} }
} }
@@ -303,7 +297,6 @@ impl ProxyHttp for LB {
async fn logging(&self, session: &mut Session, _e: Option<&pingora::Error>, ctx: &mut Self::CTX) { async fn logging(&self, session: &mut Session, _e: Option<&pingora::Error>, ctx: &mut Self::CTX) {
let response_code = session.response_written().map_or(0, |resp| resp.status.as_u16()); let response_code = session.response_written().map_or(0, |resp| resp.status.as_u16());
debug!("{}, response code: {response_code}", self.request_summary(session, ctx));
let m = &MetricTypes { let m = &MetricTypes {
method: session.req_header().method.clone(), method: session.req_header().method.clone(),
code: session.response_written().map(|resp| resp.status), code: session.response_written().map(|resp| resp.status),
@@ -314,13 +307,14 @@ impl ProxyHttp for LB {
calc_metrics(m); calc_metrics(m);
ACTIVE_SESSIONS.dec(); ACTIVE_SESSIONS.dec();
if let Some(_) = ctx.x4xx_limit.or(ctx.extraparams.x4xx_limit) { if let Some(_) = ctx.x4xx_limit.or(ctx.extraparams.x4xx_limit) {
if 400 <= response_code && response_code <= 499 { if (400..=499).contains(&response_code) {
if let Some(ip) = session.client_addr().and_then(|a| a.as_inet()).map(|i| i.ip()) { if let Some(ip) = session.client_addr().and_then(|a| a.as_inet()).map(|i| i.ip()) {
let current = REQUESTS_4XX.get(&ip).unwrap_or(0); let current = REQUESTS_4XX.get(&ip).unwrap_or(0);
REQUESTS_4XX.insert(ip, current + 1); REQUESTS_4XX.insert(ip, current + 1);
} }
} }
} }
access_log(response_code, &self.request_summary(session, ctx), session);
} }
} }

View File

@@ -4,6 +4,7 @@ use crate::tls::load;
use crate::tls::load::CertificateConfig; use crate::tls::load::CertificateConfig;
use crate::utils::structs::Extraparams; use crate::utils::structs::Extraparams;
use crate::utils::tools::*; use crate::utils::tools::*;
use crate::web::logging::init_access_log;
use crate::web::proxyhttp::LB; use crate::web::proxyhttp::LB;
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use dashmap::DashMap; use dashmap::DashMap;
@@ -23,7 +24,7 @@ use signal_hook::{
use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use std::{fs, process, thread}; use std::{fs, thread};
pub fn run() { pub fn run() {
// default_provider().install_default().expect("Failed to install rustls crypto provider"); // default_provider().install_default().expect("Failed to install rustls crypto provider");
@@ -59,6 +60,8 @@ pub fn run() {
server_headers: sh_config, server_headers: sh_config,
extraparams: ec_config, extraparams: ec_config,
}; };
let al = cfg.access_log.clone().unwrap_or("none".to_string());
init_access_log(al.as_str());
let grade = cfg.proxy_tls_grade.clone().unwrap_or("medium".to_string()); let grade = cfg.proxy_tls_grade.clone().unwrap_or("medium".to_string());
info!("TLS grade set to: [ {} ]", grade); info!("TLS grade set to: [ {} ]", grade);
@@ -146,8 +149,11 @@ pub fn run() {
drop_priv(user, group, cfg.proxy_address_http.clone(), cfg.proxy_address_tls.clone()); drop_priv(user, group, cfg.proxy_address_http.clone(), cfg.proxy_address_tls.clone());
} }
let _ = sd_notify::notify(&[NotifyState::Ready]); let _ = sd_notify::notify(&[NotifyState::Ready]);
let _ = fs::write(cfg.pid_file.clone().unwrap_or("/tmp/aralez.pid".to_string()), process::id().to_string());
let pf = cfg.pid_file.clone().unwrap_or("/tmp/aralez.pid".to_string());
if let Err(e) = write_pid_file(pf.as_str()) {
panic!("Failed to write PID file: {} : {}", pf, e);
}
let mut signals = Signals::new(&[SIGINT, SIGTERM, SIGQUIT]).unwrap(); let mut signals = Signals::new(&[SIGINT, SIGTERM, SIGQUIT]).unwrap();
for sig in signals.forever() { for sig in signals.forever() {
match sig { match sig {