diff --git a/Cargo.lock b/Cargo.lock index ae275cc..bc0c89f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,7 +127,7 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "aralez" -version = "0.92.10" +version = "0.92.11" dependencies = [ "ahash", "arc-swap", @@ -165,7 +165,7 @@ dependencies = [ "subtle", "tokio", "tonic", - "tower-http", + "tower-http 0.7.0", "urlencoding", "x509-parser", ] @@ -1713,9 +1713,9 @@ dependencies = [ [[package]] name = "inotify" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" +checksum = "533e68a5842e734946fe159fb03fc9bbbb254f590dd0d8ad321ae5ff7beca2c1" dependencies = [ "bitflags 2.11.1", "inotify-sys", @@ -1872,9 +1872,9 @@ dependencies = [ [[package]] name = "kqueue" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" dependencies = [ "kqueue-sys", "libc", @@ -1882,9 +1882,9 @@ dependencies = [ [[package]] name = "kqueue-sys" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7b65860415f949f23fa882e669f2dbd4a0f0eeb1acdd56790b30494afd7da2f" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" dependencies = [ "bitflags 2.11.1", "libc", @@ -2212,9 +2212,9 @@ dependencies = [ [[package]] name = "noyalib" -version = "0.0.7" +version = "0.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaecb583890b00deddd413f1c9522cc3537e893cd3845dec578ed6c85de653f7" +checksum = "14057395c16a4230575c6f86bfa074db87e8458626d3e56b20c2454334a7e50c" dependencies = [ "indexmap 2.14.0", "itoa", @@ -3191,7 +3191,7 @@ dependencies = [ "tokio-rustls", "tokio-util", "tower", - "tower-http", + "tower-http 0.6.11", "tower-service", "url", "wasm-bindgen", @@ -4086,6 +4086,24 @@ name = "tower-http" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ "bitflags 2.11.1", "bytes", @@ -4102,10 +4120,8 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-util", - "tower", "tower-layer", "tower-service", - "url", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5f23063..33cf5e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aralez" -version = "0.92.10" +version = "0.92.11" edition = "2021" license = "Apache-2.0" description = "Reverse proxy built on top of Cloudflare's Pingora" @@ -16,20 +16,20 @@ strip = true [dependencies] tokio = { version = "1.52.3", features = ["full"] } -pingora = { version = "0.8.0", features = ["lb", "openssl"] } # openssl, rustls, boringssl -serde = { version = "1.0.228", features = ["derive"] } +pingora = { version = "0.8.1", features = ["lb", "openssl"] } # openssl, rustls, boringssl +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" -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" log = "0.4.30" futures = "0.3.32" notify = "9.0.0-rc.4" axum = { version = "0.8.9" } 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" base64 = "0.22.1" 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" x509-parser = "0.18.1" 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" serde_json = "1.0.150" subtle = "2.6.1" diff --git a/README.md b/README.md index e3664c8..7ab5172 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ Built on Rust, on top of **Cloudflare’s Pingora engine**, **Aralez** delivers | **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` | | **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_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) | diff --git a/src/utils/structs.rs b/src/utils/structs.rs index d183686..390140b 100644 --- a/src/utils/structs.rs +++ b/src/utils/structs.rs @@ -112,6 +112,7 @@ pub struct AppConfig { pub hc_method: String, pub upstreams_conf: String, pub log_level: String, + pub access_log: Option, pub pid_file: Option, pub master_key: Option, pub config_address: String, diff --git a/src/web.rs b/src/web.rs index 846b5ee..b7ba18d 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1,6 +1,7 @@ pub mod acme; pub mod bgservice; pub mod gethosts; +pub mod logging; pub mod proxyhttp; pub mod start; pub mod webserver; diff --git a/src/web/logging.rs b/src/web/logging.rs new file mode 100644 index 0000000..894cddf --- /dev/null +++ b/src/web/logging.rs @@ -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 = 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, + ); +} diff --git a/src/web/proxyhttp.rs b/src/web/proxyhttp.rs index 93d4c42..dffa1e1 100644 --- a/src/web/proxyhttp.rs +++ b/src/web/proxyhttp.rs @@ -3,10 +3,11 @@ use crate::utils::lazylock::{LOCALHOST, RATE_LIMITER, REQUESTS_4XX, REVERSE_STOR use crate::utils::metrics::*; use crate::utils::structs::{AppConfig, Extraparams, Headers, InnerMap, UpstreamsDashMap, UpstreamsIdMap}; use crate::web::gethosts::{GetHost, GetHostsReturHeaders}; +use crate::web::logging::access_log; use arc_swap::ArcSwap; use async_trait::async_trait; use axum::body::Bytes; -use log::{debug, error, warn}; +use log::error; use pingora::http::{RequestHeader, ResponseHeader, StatusCode}; use pingora::prelude::*; use pingora::ErrorSource::Upstream; @@ -20,10 +21,6 @@ use std::sync::Arc; use tokio::time::Instant; thread_local! {static IP_BUFFER: RefCell = RefCell::new(String::with_capacity(50));} -// static REVERSE_STORE: LazyLock> = LazyLock::new(DashMap::new); -// pub static RATE_LIMITER: LazyLock = LazyLock::new(|| Rate::new(Duration::from_secs(1))); -// pub static REQUESTS_4XX: LazyLock> = LazyLock::new(|| Cache::builder().time_to_live(Duration::from_secs(1)).build()); -// pub static LOCALHOST: LazyLock> = LazyLock::new(|| Arc::from("localhost")); #[derive(Clone)] 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 !authenticate(&auth, session).await { let _ = session.respond_error(401).await; - warn!("Forbidden: {:?}, {}", session.client_addr(), session.req_header().uri.path()); return Ok(true); } } @@ -99,9 +95,9 @@ impl ProxyHttp for LB { let header = ResponseHeader::build(429, None)?; session.set_keepalive(None); session.write_response_header(Box::new(header), true).await?; - 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()); - } + // 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()); + // } return Ok(true); } } @@ -113,9 +109,9 @@ impl ProxyHttp for LB { let header = ResponseHeader::build(429, None)?; session.set_keepalive(None); session.write_response_header(Box::new(header), true).await?; - if let (Some(oi), Some(oa)) = (&_ctx.hostname, rate_key) { - warn!("Limit: {}-rps exceed on {} from {}", rate, oi, oa); - } + // if let (Some(oi), Some(oa)) = (&_ctx.hostname, rate_key) { + // warn!("Limit: {}-rps exceed on {} from {}", rate, oi, oa); + // } return Ok(true); } } @@ -281,14 +277,12 @@ impl ProxyHttp for LB { REVERSE_STORE.insert(hh.clone(), bid.clone()); 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); buf.push_str("backend_id="); buf.push_str(&tt); buf.push_str("; Path=/; Max-Age="); buf.push_str(&val.to_string()); 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()); } } @@ -303,7 +297,6 @@ impl ProxyHttp for LB { 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()); - debug!("{}, response code: {response_code}", self.request_summary(session, ctx)); let m = &MetricTypes { method: session.req_header().method.clone(), code: session.response_written().map(|resp| resp.status), @@ -314,13 +307,14 @@ impl ProxyHttp for LB { calc_metrics(m); ACTIVE_SESSIONS.dec(); 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()) { let current = REQUESTS_4XX.get(&ip).unwrap_or(0); REQUESTS_4XX.insert(ip, current + 1); } } } + access_log(response_code, &self.request_summary(session, ctx), session); } } diff --git a/src/web/start.rs b/src/web/start.rs index b21e82c..ebf5f9c 100644 --- a/src/web/start.rs +++ b/src/web/start.rs @@ -4,6 +4,7 @@ use crate::tls::load; use crate::tls::load::CertificateConfig; use crate::utils::structs::Extraparams; use crate::utils::tools::*; +use crate::web::logging::init_access_log; use crate::web::proxyhttp::LB; use arc_swap::ArcSwap; use dashmap::DashMap; @@ -59,6 +60,8 @@ pub fn run() { server_headers: sh_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()); info!("TLS grade set to: [ {} ]", grade);