8 Commits

Author SHA1 Message Date
Ara Sadoyan
dd069b8532 minor fix 2025-09-17 16:51:57 +02:00
Ara Sadoyan
c78245e695 disable HC for upstream. 2025-09-16 12:54:23 +02:00
Ara Sadoyan
66b1a1c399 upstreams pathconfig fix 2025-09-15 15:22:21 +02:00
Ara Sadoyan
bba6dd8514 minor cleanup 2025-09-09 14:51:37 +02:00
Ara Sadoyan
79485ac69d minor cleanup 2025-09-04 18:16:09 +02:00
Ara Sadoyan
61c5625016 A coffee :-) 2025-09-02 14:57:47 +02:00
Ara Sadoyan
57bdc71acd A coffee :-) 2025-09-02 14:56:36 +02:00
Ara Sadoyan
9e09b829a6 README update 2025-09-01 17:02:57 +02:00
10 changed files with 82 additions and 43 deletions

View File

@@ -1,12 +1,18 @@
![Aralez](https://netangels.net/utils/aralez-white.jpg) ![Aralez](https://netangels.net/utils/aralez-white.jpg)
# Aralez (Արալեզ), Reverse proxy and service mesh built on top of Cloudflare's Pingora ---
# Aralez (Արալեզ),
### **Reverse proxy and service mesh built on top of Cloudflare's Pingora**
What Aralez means ? What Aralez means ?
**Aralez = Արալեզ** <ins>.Named after the legendary Armenian guardian spirit, winged dog-like creature, that descend upon fallen heroes to lick their wounds and resurrect them.</ins>. **Aralez = Արալեզ** <ins>.Named after the legendary Armenian guardian spirit, winged dog-like creature, that descend upon fallen heroes to lick their wounds and resurrect them.</ins>.
Built on Rust, on top of **Cloudflares Pingora engine**, **Aralez** delivers world-class performance, security and scalability — right out of the box. Built on Rust, on top of **Cloudflares Pingora engine**, **Aralez** delivers world-class performance, security and scalability — right out of the box.
[![Buy Me A Coffee](https://img.shields.io/badge/☕-Buy%20me%20a%20coffee-orange)](https://www.buymeacoffee.com/sadoyan)
--- ---
## 🔧 Key Features ## 🔧 Key Features
@@ -112,12 +118,23 @@ Make the binary executable `chmod 755 ./aralez-VERSION` and run.
File names: File names:
| File Name | Description | | File Name | Description |
|---------------------------|---------------------------------------------------------------| |---------------------------|--------------------------------------------------------------------------|
| `aralez-x86_64-musl.gz` | Static Linux x86_64 binary, without any system dependency | | `aralez-x86_64-musl.gz` | Static Linux x86_64 binary, without any system dependency |
| `aralez-x86_64-glibc.gz` | Dynamic Linux x86_64 binary, with minimal system dependencies | | `aralez-x86_64-glibc.gz` | Dynamic Linux x86_64 binary, with minimal system dependencies |
| `aralez-aarch64-musl.gz` | Static Linux ARM64 binary, without any system dependency | | `aralez-aarch64-musl.gz` | Static Linux ARM64 binary, without any system dependency |
| `aralez-aarch64-glibc.gz` | Dynamic Linux ARM64 binary, with minimal system dependencies | | `aralez-aarch64-glibc.gz` | Dynamic Linux ARM64 binary, with minimal system dependencies |
| `sadoyan/aralez` | Docker image on Debian 13 slim (https://hub.docker.com/r/sadoyan/aralez) |
**Via docker**
```shell
docker run -d \
-v /local/path/to/config:/etc/aralez:ro \
-p 80:80 \
-p 443:443 \
sadoyan/aralez
```
## 💡 Note ## 💡 Note
@@ -195,6 +212,10 @@ myhost.mydomain.com:
servers: servers:
- "127.0.0.4:8443" - "127.0.0.4:8443"
- "127.0.0.5:8443" - "127.0.0.5:8443"
"/.well-known/acme-challenge":
healthcheck: false
servers:
- "127.0.0.1:8001"
``` ```
**This means:** **This means:**
@@ -209,6 +230,7 @@ myhost.mydomain.com:
- Requests to `myhost.mydomain.com/` will be proxied to `127.0.0.1` and `127.0.0.2`. - Requests to `myhost.mydomain.com/` will be proxied to `127.0.0.1` and `127.0.0.2`.
- Plain HTTP to `myhost.mydomain.com/foo` will get 301 redirect to configured TLS port of Aralez. - Plain HTTP to `myhost.mydomain.com/foo` will get 301 redirect to configured TLS port of Aralez.
- Requests to `myhost.mydomain.com/foo` will be proxied to `127.0.0.4` and `127.0.0.5`. - Requests to `myhost.mydomain.com/foo` will be proxied to `127.0.0.4` and `127.0.0.5`.
- Requests to `myhost.mydomain.com/.well-known/acme-challenge` will be proxied to `127.0.0.1:8001`, but healthcheks are disabled.
- SSL/TLS for upstreams is detected automatically, no need to set any config parameter. - SSL/TLS for upstreams is detected automatically, no need to set any config parameter.
- Assuming the `127.0.0.5:8443` is SSL protected. The inner traffic will use TLS. - Assuming the `127.0.0.5:8443` is SSL protected. The inner traffic will use TLS.
- Self-signed certificates are silently accepted. - Self-signed certificates are silently accepted.

View File

@@ -65,9 +65,11 @@ upstreams:
headers: headers:
- "X-Some-Thing:Yaaaaaaaaaaaaaaa" - "X-Some-Thing:Yaaaaaaaaaaaaaaa"
servers: servers:
- "192.168.1.1:8000"
- "192.168.1.10:8000"
- "127.0.0.1:8000" - "127.0.0.1:8000"
- "127.0.0.2:8000" - "127.0.0.2:8000"
- "127.0.0.3:8000" - "127.0.0.3:8000"
- "127.0.0.4:8000" - "127.0.0.4:8000"
"/.well-known/acme-challenge":
healthcheck: false
servers:
- "127.0.0.1:8001"

View File

@@ -128,6 +128,7 @@ async fn get_by_http(url: String, token: Option<String>) -> Option<DashMap<Strin
is_http2: false, is_http2: false,
to_https: false, to_https: false,
rate_limit: None, rate_limit: None,
healthcheck: None,
}; };
values.push(to_add); values.push(to_add);
} }

View File

@@ -62,17 +62,32 @@ async fn build_upstreams(fullist: &UpstreamsDashMap, method: &str, client: &Clie
is_http2: is_h2, is_http2: is_h2,
to_https: upstream.to_https, to_https: upstream.to_https,
rate_limit: upstream.rate_limit, rate_limit: upstream.rate_limit,
healthcheck: upstream.healthcheck,
}; };
let resp = http_request(&link, method, "", &client).await; if scheme.healthcheck.unwrap_or(true) {
if resp.0 { let resp = http_request(&link, method, "", &client).await;
if resp.1 { if resp.0 {
scheme.is_http2 = is_h2; // could be adjusted further if resp.1 {
scheme.is_http2 = is_h2; // could be adjusted further
}
innervec.push(scheme);
} else {
warn!("Dead Upstream : {}", link);
} }
innervec.push(scheme);
} else { } else {
warn!("Dead Upstream : {}", link); innervec.push(scheme);
} }
// let resp = http_request(&link, method, "", &client).await;
// if resp.0 {
// if resp.1 {
// scheme.is_http2 = is_h2; // could be adjusted further
// }
// innervec.push(scheme);
// } else {
// warn!("Dead Upstream : {}", link);
// }
} }
inner.insert(path.clone(), (innervec, AtomicUsize::new(0))); inner.insert(path.clone(), (innervec, AtomicUsize::new(0)));
} }

View File

@@ -14,8 +14,8 @@ use std::time::Duration;
use tokio::fs::File; use tokio::fs::File;
use tokio::io::AsyncReadExt; use tokio::io::AsyncReadExt;
// static KUBERNETES_SERVICE_HOST: &str = "140.238.122.18:6443"; // static KUBERNETES_SERVICE_HOST: &str = "IP_ADDRESS";
// static TOKEN: &str = "eyJhbGciOiJSUzI1NiIsImtpZCI6InJXMzl2VFlHTDM4V0tBbWxNQnRhbnRVcDlvcS10MjRDVHNwc2p3d1ZXeDAifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzg4MjY3OTgxLCJpYXQiOjE3NTY3MzE5ODEsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiMDM4ZWVlODAtNGViZi00MjU5LWI0OTctYmYwNDgxMmYyNWJhIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJzdGFnaW5nIiwibm9kZSI6eyJuYW1lIjoiMTcyLjE2LjAuMTU1IiwidWlkIjoiOGY2ZTk2ZjYtNDdjNS00YjZhLTkwMmUtYmZhZTUwNzcxMWFjIn0sInBvZCI6eyJuYW1lIjoiYXJhbGV6LTY3Njg5OGY5ZDUtaHpienEiLCJ1aWQiOiI2NjAyZDFjNy00ZWM2LTRiZDEtYTc3NS00NzI5OGYyMTc0N2QifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImFyYWxlei1zYSIsInVpZCI6IjVjYmYxZTU2LTJjY2YtNDFlMS05OGU2LTc3NmY1ZWY1NGRkOCJ9LCJ3YXJuYWZ0ZXIiOjE3NTY3MzU1ODh9LCJuYmYiOjE3NTY3MzE5ODEsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpzdGFnaW5nOmFyYWxlei1zYSJ9.fnlQoOMEztWx6aE4JNPCMVDVc4s8ygs1tvuU_4FoNTWnTipFivzV3duHAg5sHFnLexYEbeBzWqr1betk__ATfy5RB5UaDdg0kk2AdVjYhvUfW4FcIqPNzXBUd74BOUG8vhN6bhZTQC8Fh0eCLCn9XXwVGIO94LKi9LmLbFiDAhpQDGwbXYnI8kV4nNGRE3kf0fsb6SyHs_8bOGc-t2U6OPdAFqBsk4JliaqiXhJKsoc8JCfUkcxYkT7GqIZxFYpvgisbOdZL7_iVyLU1BiSPMHb0jFa4O60aZ8EzCR7Mio0F5A8eZjSCf_f90ecUCGFuW3eCTCDd02RutXeSyPqxhQ"; // static TOKEN: &str = "TOKEN";
#[derive(Debug, serde::Deserialize)] #[derive(Debug, serde::Deserialize)]
struct Endpoints { struct Endpoints {
@@ -114,6 +114,7 @@ pub async fn get_by_http(url: &str, token: &str) -> Option<DashMap<String, (Vec<
is_http2: false, is_http2: false,
to_https: false, to_https: false,
rate_limit: None, rate_limit: None,
healthcheck: None,
}; };
inner_vec.push(to_add); inner_vec.push(to_add);
} }

View File

@@ -128,8 +128,8 @@ async fn populate_file_upstreams(config: &mut Configuration, parsed: &Config) {
is_ssl: true, is_ssl: true,
is_http2: false, is_http2: false,
to_https: path_config.to_https.unwrap_or(false), to_https: path_config.to_https.unwrap_or(false),
// rate_limit: rate,
rate_limit: path_config.rate_limit, rate_limit: path_config.rate_limit,
healthcheck: path_config.healthcheck,
}); });
} }
} }

View File

@@ -67,6 +67,7 @@ pub struct PathConfig {
pub to_https: Option<bool>, pub to_https: Option<bool>,
pub headers: Option<Vec<String>>, pub headers: Option<Vec<String>>,
pub rate_limit: Option<isize>, pub rate_limit: Option<isize>,
pub healthcheck: Option<bool>,
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Configuration { pub struct Configuration {
@@ -108,6 +109,7 @@ pub struct InnerMap {
pub is_http2: bool, pub is_http2: bool,
pub to_https: bool, pub to_https: bool,
pub rate_limit: Option<isize>, pub rate_limit: Option<isize>,
pub healthcheck: Option<bool>,
} }
#[allow(dead_code)] #[allow(dead_code)]
@@ -120,6 +122,7 @@ impl InnerMap {
is_http2: Default::default(), is_http2: Default::default(),
to_https: Default::default(), to_https: Default::default(),
rate_limit: Default::default(), rate_limit: Default::default(),
healthcheck: Default::default(),
} }
} }
} }

View File

@@ -193,9 +193,7 @@ pub struct CipherSuite {
} }
const CIPHERS: CipherSuite = CipherSuite { const CIPHERS: CipherSuite = CipherSuite {
high: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305", high: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305",
// aa: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256",
medium: "ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:AES128-GCM-SHA256", medium: "ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:AES128-GCM-SHA256",
// cc: "AES128-SHA:DES-CBC3-SHA",
legacy: "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH", legacy: "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH",
}; };

View File

@@ -155,6 +155,7 @@ pub fn clone_idmap_into(original: &UpstreamsDashMap, cloned: &UpstreamsIdMap) {
is_http2: false, is_http2: false,
to_https: false, to_https: false,
rate_limit: None, rate_limit: None,
healthcheck: None,
}; };
cloned.insert(id, to_add); cloned.insert(id, to_add);
cloned.insert(hh, x.to_owned()); cloned.insert(hh, x.to_owned());

View File

@@ -5,7 +5,7 @@ use crate::web::gethosts::GetHost;
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, warn}; use log::{debug, error, warn};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use pingora::http::{RequestHeader, ResponseHeader, StatusCode}; use pingora::http::{RequestHeader, ResponseHeader, StatusCode};
use pingora::prelude::*; use pingora::prelude::*;
@@ -67,7 +67,7 @@ impl ProxyHttp for LB {
}; };
let hostname = return_header_host(&session); let hostname = return_header_host(&session);
_ctx.hostname = hostname.clone(); _ctx.hostname = hostname;
let mut backend_id = None; let mut backend_id = None;
@@ -85,10 +85,11 @@ impl ProxyHttp for LB {
} }
} }
match hostname { match _ctx.hostname.as_ref() {
None => return Ok(false), None => return Ok(false),
Some(host) => { Some(host) => {
let optioninnermap = self.get_host(host.as_str(), host.as_str(), backend_id); // let optioninnermap = self.get_host(host.as_str(), host.as_str(), backend_id);
let optioninnermap = self.get_host(host.as_str(), session.req_header().uri.path(), backend_id);
match optioninnermap { match optioninnermap {
None => return Ok(false), None => return Ok(false),
Some(ref innermap) => { Some(ref innermap) => {
@@ -150,7 +151,9 @@ impl ProxyHttp for LB {
Ok(peer) Ok(peer)
} }
None => { None => {
session.respond_error_with_body(502, Bytes::from("502 Bad Gateway\n")).await.expect("Failed to send error"); if let Err(e) = session.respond_error_with_body(502, Bytes::from("502 Bad Gateway\n")).await {
error!("Failed to send error response: {:?}", e);
}
Err(Box::new(Error { Err(Box::new(Error {
etype: HTTPStatus(502), etype: HTTPStatus(502),
esource: Upstream, esource: Upstream,
@@ -162,7 +165,10 @@ impl ProxyHttp for LB {
} }
} }
None => { None => {
session.respond_error_with_body(502, Bytes::from("502 Bad Gateway\n")).await.expect("Failed to send error"); // session.respond_error_with_body(502, Bytes::from("502 Bad Gateway\n")).await.expect("Failed to send error");
if let Err(e) = session.respond_error_with_body(502, Bytes::from("502 Bad Gateway\n")).await {
error!("Failed to send error response: {:?}", e);
}
Err(Box::new(Error { Err(Box::new(Error {
etype: HTTPStatus(502), etype: HTTPStatus(502),
esource: Upstream, esource: Upstream,
@@ -174,22 +180,12 @@ impl ProxyHttp for LB {
} }
} }
async fn upstream_request_filter(&self, session: &mut Session, _upstream_request: &mut RequestHeader, _ctx: &mut Self::CTX) -> Result<()> { async fn upstream_request_filter(&self, _session: &mut Session, upstream_request: &mut RequestHeader, ctx: &mut Self::CTX) -> Result<()> {
match session.client_addr() { if let Some(hostname) = ctx.hostname.as_ref() {
Some(ip) => { upstream_request.insert_header("Host", hostname)?;
let inet = ip.as_inet(); }
match inet { if let Some(peer) = ctx.upstream_peer.as_ref() {
Some(addr) => { upstream_request.insert_header("X-Forwarded-For", peer.address.as_str())?;
_upstream_request
.insert_header("X-Forwarded-For", addr.to_string().split(':').collect::<Vec<&str>>()[0])
.unwrap();
}
None => warn!("Malformed Client IP: {:?}", inet),
}
}
None => {
warn!("Cannot detect client IP");
}
} }
Ok(()) Ok(())
} }