Compare commits

...
17 Commits
Author SHA1 Message Date
Ara Sadoyan 0006d27d02 hcl examples 2026-09-01 12:19:09 +02:00
Ara Sadoyan d2d4100cba Finalized Consul integration,
Cached headers bug fix
2026-08-31 19:18:10 +02:00
Ara Sadoyan f575007044 Rewrite of Consul integration. 2026-08-30 18:38:31 +02:00
Ara Sadoyan bb43df0e4d Full rewrite of Kubernetes integration. 2026-08-25 19:32:42 +02:00
Ara Sadoyan 29c3d6a413 Full rewrite of Kubernetes integration. 2026-08-25 19:31:05 +02:00
Ara Sadoyan c21374f2a2 Full rewrite of Kubernetes integration. 2026-08-25 19:29:53 +02:00
Ara Sadoyan 51eec524ca Cargo 2026-08-10 15:40:50 +02:00
Ara Sadoyan 97ac839001 Removed reqwest dependency, moved all http client reqvetsts to native pingora. 2026-08-06 19:38:03 +02:00
Ara Sadoyan b63d822577 Rewrote and optimized healtchecks 2026-08-05 18:51:07 +02:00
Ara Sadoyan 600cb602e3 Readme update 2026-07-28 14:45:46 +02:00
Ara Sadoyan 29d1f1db78 Solved conflict on H2 upstream and Websocket, better gRPC ping 2026-07-24 12:28:44 +02:00
Ara Sadoyan 3ae54185ad Solved conflict on H2 upstream and Websocket, better gRPC ping 2026-07-24 12:27:12 +02:00
Ara Sadoyan 430f867792 Merge branch 'main' of github.com:sadoyan/aralez 2026-07-20 13:47:54 +02:00
Ara Sadoyan f6c092adc4 version fix 2026-07-20 13:47:05 +02:00
Ara Sadoyan 19ad8fdbf4 yaml update 2026-07-20 13:40:18 +02:00
Ara Sadoyan b55ff2d392 minor fix on memory response cache 2026-07-20 13:37:10 +02:00
Ara Sadoyan b73ccc527f Implementing in memory response cache 2026-07-20 12:25:27 +02:00
35 changed files with 2137 additions and 951 deletions
Generated
+695 -532
View File
File diff suppressed because it is too large Load Diff
+19 -10
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "aralez" name = "aralez"
version = "0.92.14" version = "0.93.2"
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"
@@ -15,24 +15,23 @@ panic = "abort"
strip = true strip = true
[dependencies] [dependencies]
tokio = { version = "1.52.3", features = ["full"] } tokio = { version = "1.53.1", features = ["full"] }
pingora = { version = "0.8.1", features = ["lb", "openssl"] } # openssl, rustls, boringssl pingora = { version = "0.8.1", features = ["lb", "openssl"] } # openssl, rustls, boringssl
pingora-core = "0.8.1" pingora-core = "0.8.1"
pingora-proxy = "0.8.1" pingora-proxy = "0.8.1"
pingora-http = "0.8.1" pingora-http = "0.8.1"
pingora-limits = "0.8.1" pingora-limits = "0.8.1"
pingora-cache = "0.8.1"
dashmap = "7.0.0-rc2" dashmap = "7.0.0-rc2"
async-trait = "0.1.89" async-trait = "0.1.91"
log = "0.4.33" log = "0.4.33"
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"] } serde = { version = "1.0.229", features = ["derive"] }
serde = { version = "1.0.228", features = ["derive"] } noyalib = { version = "0.0.18", features = ["compat-serde-yaml"] }
noyalib = { version = "0.0.15", features = ["compat-serde-yaml"] }
rand = "0.10.2" rand = "0.10.2"
base64 = "0.22.1" base64 = "0.23.1"
jsonwebtoken = { version = "10.4.0", default-features = false, features = ["use_pem", "rust_crypto"] } jsonwebtoken = { version = "11.0.0", default-features = false, features = ["use_pem", "rust_crypto"] }
tonic = "0.14.6"
sha2 = { version = "0.11.0-rc.5", default-features = false } sha2 = { version = "0.11.0-rc.5", default-features = false }
base16ct = { version = "1.0.0", features = ["alloc"] } base16ct = { version = "1.0.0", features = ["alloc"] }
urlencoding = "2.1.3" urlencoding = "2.1.3"
@@ -42,7 +41,7 @@ x509-parser = "0.18.1"
rustls-pemfile = "2.2.0" rustls-pemfile = "2.2.0"
tower-http = { version = "0.7.0", 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.151"
subtle = "2.6.1" subtle = "2.6.1"
moka = { version = "0.12.15", features = ["sync"] } moka = { version = "0.12.15", features = ["sync"] }
ahash = "0.8.12" ahash = "0.8.12"
@@ -53,3 +52,13 @@ 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" libc = "0.2.186"
tokio-native-tls = "0.3.1"
hyper = "1.11.0"
hyper-util = "0.1.20"
http-body-util = "0.1.4"
bytes = "1.12.1"
rustls = { version = "0.23", default-features = false, features = ["ring"] }
kube = { version = "4.2.0", features = ["runtime", "client", "derive"] }
k8s-openapi = { version = "0.28.0", features = ["v1_32"] }
futures = "0.3"
+12 -13
View File
@@ -6,13 +6,12 @@
### **Reverse proxy built on top of Cloudflare's Pingora** ### **Reverse proxy built on top of Cloudflare's Pingora**
Aralez is a high-performance Rust reverse proxy with zero-configuration automatic protocol handling, TLS, and upstream management, Aralez is a high-performance Rust reverse proxy with zero-configuration automatic protocol handling, TLS, and upstream management, featuring Consul and Kubernetes integration for dynamic pod discovery and health-checked routing, acting as a
featuring Consul and Kubernetes integration for dynamic pod discovery and health-checked routing, acting as a lightweight ingress-style proxy. lightweight ingress-style proxy.
--- ---
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 **Cloudflare’s Pingora engine**, **Aralez** delivers world-class performance, security and scalability — right out of the box. Built on Rust, on top of **Cloudflare’s Pingora engine**, **Aralez** delivers world-class performance, security and scalability — right out of the box.
@@ -36,6 +35,7 @@ Built on Rust, on top of **Cloudflare’s Pingora engine**, **Aralez** delivers
- **Let’s Encrypt Certificates** — Ordering and renewal of SSL/TLS certificates via the HTTP-01 challenge. - **Let’s Encrypt Certificates** — Ordering and renewal of SSL/TLS certificates via the HTTP-01 challenge.
- **Upstreams TLS detection** — Aralez will automatically detect if upstreams uses secure connection. - **Upstreams TLS detection** — Aralez will automatically detect if upstreams uses secure connection.
- **Built in rate limiter** — Globar or route limit requests to upstreams. - **Built in rate limiter** — Globar or route limit requests to upstreams.
- **Caching Proxy** — In memory cache with configurable TTL, without external dependencies.
- **Authentication** — Supports Basic Auth, API tokens, and JWT verification. - **Authentication** — Supports Basic Auth, API tokens, and JWT verification.
- **Basic Auth** - **Basic Auth**
- **API Key** via `x-api-key` header - **API Key** via `x-api-key` header
@@ -82,13 +82,14 @@ Built on Rust, on top of **Cloudflare’s Pingora engine**, **Aralez** delivers
| **file_server_folder** | /some/local/folder | Optional. Local folder to serve | | **file_server_folder** | /some/local/folder | Optional. Local folder to serve |
| **file_server_address** | 127.0.0.1:3002 | Optional. Local address for file server | | **file_server_address** | 127.0.0.1:3002 | Optional. Local address for file server |
| **config_api_enabled** | true | Enable/disable remote config push capability | | **config_api_enabled** | true | Enable/disable remote config push capability |
| **cache_size_mb** | 50 | Optional. Enable internal cache with size in megabytes of value. Cache is entirely disabled when this line is commented out. |
| **cache_ttl** | 10 | Optional. Set Default TTL in seconds for cached items, works if cache control headers are not set via upstream. Defaults to 60 if cache_size_mb is set. |
--- ---
## Installation ## Installation
Download the prebuilt binary for your architecture from releases section of [GitHub](https://github.com/sadoyan/aralez/releases) repo Download the prebuilt binary for your architecture from releases section of [GitHub](https://github.com/sadoyan/aralez/releases) repo Make the binary executable `chmod 755 ./aralez-VERSION` and run.
Make the binary executable `chmod 755 ./aralez-VERSION` and run.
File names: File names:
@@ -106,8 +107,7 @@ File names:
**glibc** builds are in general faster, but have few, basic, Glibc dependencies: **glibc** builds are in general faster, but have few, basic, Glibc dependencies:
**musl** builds are 100% portable, static compiled binaries and have zero system dependencies. **musl** builds are 100% portable, static compiled binaries and have zero system dependencies. In general musl builds have a little less performance.
In general musl builds have a little less performance.
The most intensive tests shows 107k-110k requests per second on **Glibc** binaries against 97k-100k **Musl** ones. The most intensive tests shows 107k-110k requests per second on **Glibc** binaries against 97k-100k **Musl** ones.
@@ -297,8 +297,7 @@ openssl req -x509 -newkey rsa:4096 \
## Remote Config API ## Remote Config API
Push new `upstreams.yaml` over HTTP to `config_address` (`:3000` by default). Useful for CI/CD automation or remote config updates. Push new `upstreams.yaml` over HTTP to `config_address` (`:3000` by default). Useful for CI/CD automation or remote config updates. URL parameter. `key=MASTERKEY` is required. `MASTERKEY` is the value of `master_key` in the `main.yaml`
URL parameter. `key=MASTERKEY` is required. `MASTERKEY` is the value of `master_key` in the `main.yaml`
```bash ```bash
curl -XPOST --data-binary @./etc/upstreams.txt 127.0.0.1:3000/conf?key=${MASTERKEY} curl -XPOST --data-binary @./etc/upstreams.txt 127.0.0.1:3000/conf?key=${MASTERKEY}
@@ -441,7 +440,7 @@ curl -u username:password -H 'Host: myip.mydomain.com' http://127.0.0.1:6193/
## Results reflect synthetic performance under optimal conditions. ## Results reflect synthetic performance under optimal conditions.
- CPU : Intel(R) Xeon(R) CPU E3-1270 v6 @ 3.80GHz - CPU : Intel (R) Xeon (R) CPU E3-1270 v6 @ 3.80GHz
- 300 : simultaneous connections - 300 : simultaneous connections
- Duration : 10 Minutes - Duration : 10 Minutes
- Binary : aralez-x86_64-glibc - Binary : aralez-x86_64-glibc
@@ -497,7 +496,7 @@ Error distribution:
![Aralez](https://netangels.net/utils/glibc10.png) ![Aralez](https://netangels.net/utils/glibc10.png)
- CPU : Intel(R) Xeon(R) CPU E3-1270 v6 @ 3.80GHz - CPU : Intel (R) Xeon (R) CPU E3-1270 v6 @ 3.80GHz
- 300 : simultaneous connections - 300 : simultaneous connections
- Duration : 10 Minutes - Duration : 10 Minutes
- Binary : aralez-x86_64-musl - Binary : aralez-x86_64-musl
@@ -555,7 +554,7 @@ Error distribution:
## Aralez, Nginx, Traefik performance benchmark ## Aralez, Nginx, Traefik performance benchmark
This benchmark is done on 4 servers. With CPU Intel(R) Xeon(R) E-2174G CPU @ 3.80GHz, 64 GB RAM. This benchmark is done on 4 servers. With CPU Intel (R) Xeon (R) E-2174G CPU @ 3.80GHz, 64 GB RAM.
1. Sever runs Aralez, Traefik, Nginx on different ports. Tuned as much as I could . 1. Sever runs Aralez, Traefik, Nginx on different ports. Tuned as much as I could .
2. 3x Upstreams servers, running Nginx. Replying with dummy json hardcoded in config file for max performance. 2. 3x Upstreams servers, running Nginx. Replying with dummy json hardcoded in config file for max performance.
+66
View File
@@ -0,0 +1,66 @@
job "apache" {
meta {
uuid = uuidv4()
}
datacenters = ["eu-ams-1"]
type = "service"
constraint {
attribute = node.unique.name
operator = "regexp"
value = "consul[0-9]"
}
update {
max_parallel = 1
min_healthy_time = "10s"
healthy_deadline = "3m"
progress_deadline = "10m"
auto_revert = false
canary = 0
}
migrate {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
}
group "Apache" {
network {
port "apache" { to = 80 }
}
constraint {
operator = "distinct_hosts"
value = "true"
}
count = 3
restart {
attempts = 2
interval = "30m"
delay = "15s"
mode = "fail"
}
task "health" {
service {
port = "apache"
tags = [
"aralez.service=yes",
"aralez.host=apache.example.com",
"aralez.path=/",
"aralez.redirect=http://checkip.amazonaws.com"
]
check {
type = "http"
port = "apache"
path = "/"
interval = "5s"
timeout = "2s"
}
}
driver = "docker"
config {
image = "httpd:latest"
force_pull = true
ports = ["apache"]
}
}
}
}
+70
View File
@@ -0,0 +1,70 @@
job "echo" {
meta {
uuid = uuidv4()
}
datacenters = ["eu-ams-1"]
type = "service"
constraint {
attribute = node.unique.name
operator = "regexp"
value = "consul[0-9]"
}
update {
max_parallel = 1
min_healthy_time = "10s"
healthy_deadline = "3m"
progress_deadline = "10m"
auto_revert = false
canary = 0
}
migrate {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
}
group "Echo" {
network {
port "echo" { to = 80 }
}
constraint {
operator = "distinct_hosts"
value = "true"
}
count = 3
restart {
attempts = 2
interval = "30m"
delay = "15s"
mode = "fail"
}
task "server" {
service {
port = "echo"
tags = [
"aralez.service=yes",
"aralez.host=echo.example.com",
"aralez.path=/",
"aralez.rate=20",
"aralez.4xx_rate=10",
"aralez.to_https=true",
"aralez.client_header=X-Client-Header:Some simple header",
"aralez.server_header=X-Server-Header:Some simple header",
]
check {
type = "http"
port = "echo"
path = "/"
interval = "5s"
timeout = "2s"
}
}
driver = "docker"
config {
image = "ealen/echo-server:latest"
force_pull = true
ports = ["echo"]
}
}
}
}
+72
View File
@@ -0,0 +1,72 @@
job "nginx" {
meta {
uuid = uuidv4()
}
datacenters = ["eu-ams-1"]
type = "service"
constraint {
attribute = node.unique.name
operator = "regexp"
value = "consul[0-9]"
}
update {
max_parallel = 1
min_healthy_time = "10s"
healthy_deadline = "3m"
progress_deadline = "10m"
auto_revert = false
canary = 0
}
migrate {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
}
group "NginX" {
network {
port "nginx" { to = 80 }
}
constraint {
operator = "distinct_hosts"
value = "true"
}
count = 3
restart {
attempts = 2
interval = "30m"
delay = "15s"
mode = "fail"
}
task "server" {
service {
port = "nginx"
tags = [
"aralez.service=yes",
"aralez.host=nginx.example.com",
"aralez.path=/",
"aralez.rate=1",
"aralez.4xx_rate=10",
"aralez.to_https=true",
"aralez.client_header=X-Some:Some Random Header",
"aralez.client_header=X-Emos:Redaeh Modnar Emos",
"aralez.server_header=X-From:Aralez & Consul",
"aralez.server_header=X-Mrof: Lusnoc & Zelara",
]
check {
type = "http"
port = "nginx"
path = "/"
interval = "5s"
timeout = "2s"
}
}
driver = "docker"
config {
image = "nginx:latest"
force_pull = true
ports = ["nginx"]
}
}
}
}
@@ -0,0 +1,9 @@
apiVersion: v1
kind: Secret
metadata:
name: aralez-tls
type: kubernetes.io/tls
data:
tls.crt: <base64-cert>
tls.key: <base64-key>
@@ -0,0 +1,43 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: aralez
spec:
replicas: 1
selector:
matchLabels:
app: aralez
template:
metadata:
labels:
app: aralez
spec:
serviceAccountName: aralez-sa
containers:
- name: aralez
image: sadoyan/aralez:latest
ports:
- containerPort: 80
- containerPort: 443
volumeMounts:
- name: main-config
mountPath: /etc/aralez/main.yaml
subPath: main.yaml
readOnly: true
- name: upstreams-config
mountPath: /etc/aralez/upstreams.yml
subPath: upstreams.yml
readOnly: true
- name: tls-certs
mountPath: /etc/aralez/certificates
readOnly: true
volumes:
- name: main-config
configMap:
name: aralez-main-config
- name: upstreams-config
configMap:
name: aralez-upstreams-config
- name: tls-certs
secret:
secretName: aralez-tls
+16
View File
@@ -0,0 +1,16 @@
threads: 12
daemon: false
upstream_keepalive_pool_size: 500
pid_file: /tmp/aralez.pid
error_log: /tmp/aralez_err.log
upgrade_sock: /tmp/aralez.sock
config_api_enabled: false
config_address: 127.0.0.1:3000
proxy_address_http: 0.0.0.0:80
proxy_address_tls: 0.0.0.0:443
proxy_configs: /etc/aralez
proxy_tls_grade: high
upstreams_conf: /etc/aralez/upstreams.yml
log_level: info
hc_method: HEAD
hc_interval: 2
@@ -0,0 +1,14 @@
# The file under watch and hot reload, changes are applied immediately, no need to restart or reload.
provider: "kubernetes" # "file" "consul" "kubernetes"
sticky_sessions: 172000
to_https: false
rate_limit: 500000
x4xx_limit: 100000
server_headers:
- "X-Forwarded-Proto:https"
- "X-Forwarded-Port:443"
client_headers:
- "X-Global-Client:Yooooooo"
kubernetes:
servers:
- "127.0.0.1:6443" # Gets KUBERNETES_SERVICE_HOST : KUBERNETES_SERVICE_PORT_HTTPS env variables.
@@ -0,0 +1,21 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: svc-nginx
labels:
app: svc-nginx
spec:
selector:
matchLabels:
app: svc-nginx
replicas: 3
template:
metadata:
labels:
app: svc-nginx
spec:
containers:
- name: svc-nginx
image: nginx:latest
ports:
- containerPort: 80
+24
View File
@@ -0,0 +1,24 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wss-service-v2
namespace: default
annotations:
# These custom annotations are parsed by Aralez
aralez.rs/rate_limit: "50"
aralez.rs/x4xx_limit: "10"
aralez.rs/client_headers: '["X-Some-Client:Some Custom Header", "X-Example-Client:An Example Header"]'
aralez.rs/server_headers: '["X-Some-Server:Some Custom Header", "X-Example-Server:An Example Header"]'
spec:
ingressClassName: aralez # Matches the IngressClass metadata
rules:
- host: wss-service-v2.blabla.com
http:
paths:
- path: /ws
pathType: Prefix
backend:
service:
name: svc-nginx
port:
number: 80
+11
View File
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: svc-nginx
spec:
selector:
app: svc-nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
+2
View File
@@ -22,4 +22,6 @@ hc_interval: 2 #Interval for health checks in seconds
tcp_keepalive_idle: 60 # Seconds of inactivity before the kernel starts sending keepalive probes to a downstream client tcp_keepalive_idle: 60 # Seconds of inactivity before the kernel starts sending keepalive probes to a downstream client
tcp_keepalive_interval: 10 # Seconds between individual keepalive probes if the client does not respond tcp_keepalive_interval: 10 # Seconds between individual keepalive probes if the client does not respond
tcp_keepalive_count: 5 # Number of unanswered probes before the kernel declares the connection dead and closes it tcp_keepalive_count: 5 # Number of unanswered probes before the kernel declares the connection dead and closes it
cache_size_mb: 50 # Optional. Enable internal cache with size in megabytes of value. Cache is entirely disabled when this line is commented out
cache_ttl: 120 # Optional. Set Default TTL in seconds for cached items, works if cache control headers are not set via upstream. Defaults to 60 if cache_size_mb is set
+1 -4
View File
@@ -1,17 +1,14 @@
use crate::tls::acme::account::get_account; use crate::tls::acme::account::get_account;
use crate::utils::lazylock::CHALLENGES;
use crate::utils::parceyaml::DOMAINS; use crate::utils::parceyaml::DOMAINS;
use instant_acme::{ChallengeType, Identifier, NewOrder, RetryPolicy}; use instant_acme::{ChallengeType, Identifier, NewOrder, RetryPolicy};
use log::{error, info}; use log::{error, info};
use pingora::prelude::sleep; use pingora::prelude::sleep;
use rcgen::{CertificateParams, DistinguishedName, KeyPair}; use rcgen::{CertificateParams, DistinguishedName, KeyPair};
use std::collections::HashMap;
use std::fs; use std::fs;
use std::sync::{LazyLock, RwLock};
use std::time::Duration; use std::time::Duration;
use x509_parser::prelude::*; use x509_parser::prelude::*;
pub static CHALLENGES: LazyLock<RwLock<HashMap<String, String>>> = LazyLock::new(|| RwLock::new(HashMap::new()));
pub async fn refresh_order(certs_dir: String, autoconf_dir: String) { pub async fn refresh_order(certs_dir: String, autoconf_dir: String) {
let credsfile = autoconf_dir + "/acme_credentials.json"; let credsfile = autoconf_dir + "/acme_credentials.json";
loop { loop {
+5 -1
View File
@@ -1,15 +1,19 @@
pub mod auth; pub mod auth;
pub mod consul;
pub mod discovery; pub mod discovery;
mod filewatch; mod filewatch;
pub mod fordebug; pub mod fordebug;
pub mod grpc;
pub mod hcclient;
pub mod healthcheck; pub mod healthcheck;
pub mod httpclient; pub mod httpclient;
pub mod jwt; pub mod jwt;
pub mod kuberconsul; pub mod kuberconsul;
pub mod kubernetes;
pub mod kubewatcher;
pub mod lazylock; pub mod lazylock;
pub mod metrics; pub mod metrics;
pub mod parceyaml; pub mod parceyaml;
pub mod state; pub mod state;
pub mod structs; pub mod structs;
pub mod tools; pub mod tools;
// pub mod watchksecret;
+20 -19
View File
@@ -1,5 +1,6 @@
use crate::utils::jwt::{check_jwt, JWT_TOKEN}; use crate::utils::jwt::{check_jwt, JWT_TOKEN};
use crate::utils::structs::InnerAuth; use crate::utils::structs::InnerAuth;
pub(crate) use crate::utils::tools::split_host_port;
use axum::http::StatusCode; use axum::http::StatusCode;
use base64::engine::general_purpose::STANDARD; use base64::engine::general_purpose::STANDARD;
use base64::Engine; use base64::Engine;
@@ -224,22 +225,22 @@ pub fn get_query_param(session: &mut Session, key: &str) -> Option<String> {
params.get(key).and_then(|v| decode(v).ok()).map(|s| s.to_string()) params.get(key).and_then(|v| decode(v).ok()).map(|s| s.to_string())
} }
#[allow(clippy::needless_return)] // #[allow(clippy::needless_return)]
fn split_host_port(addr: &str, tls: bool) -> Option<(&str, u16, bool, &str)> { // pub fn split_host_port(addr: &str, tls: bool) -> Option<(&str, u16, bool, &str)> {
match addr.split_once(':') { // match addr.split_once(':') {
Some((h, p)) => match p.parse::<u16>() { // Some((h, p)) => match p.parse::<u16>() {
Ok(port) => return Some((h, port, tls, h)), // Ok(port) => return Some((h, port, tls, h)),
Err(_) => { // Err(_) => {
log::warn!("ForwardAuth: invalid port in {}", addr); // log::warn!("ForwardAuth: invalid port in {}", addr);
return None; // return None;
} // }
}, // },
None => { // None => {
if tls { // if tls {
return Some((addr, 443u16, tls, addr)); // return Some((addr, 443u16, tls, addr));
} else { // } else {
return Some((addr, 80u16, tls, addr)); // return Some((addr, 80u16, tls, addr));
} // }
} // }
}; // };
} // }
+91
View File
@@ -0,0 +1,91 @@
use crate::utils::httpclient;
use crate::utils::kuberconsul::{list_to_upstreams, ServiceDiscovery};
use crate::utils::parceyaml::build_headers;
use crate::utils::structs::{Configuration, GlobalServiceMapping, UpstreamsDashMap};
use async_trait::async_trait;
use dashmap::DashMap;
use pingora::prelude::sleep;
use rand::RngExt;
use serde::Deserialize;
use std::env;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::mpsc::Sender;
#[derive(Debug, Deserialize)]
pub struct ConsulService {
#[serde(rename = "Address")]
pub address: String,
#[serde(rename = "ServicePort")]
pub port: u16,
}
pub struct ConsulDiscovery;
#[async_trait]
impl ServiceDiscovery for ConsulDiscovery {
async fn fetch_upstreams(&self, config: Arc<Configuration>, toreturn: Sender<Configuration>) {
loop {
let upstreams = UpstreamsDashMap::new();
if let Some(consul) = config.consul.clone() {
let servers = consul.servers.unwrap_or_else(|| {
vec![format!(
"{}:{}",
env::var("CONSUL_SERVICE_HOST").unwrap_or_else(|_| "0.0.0.0".to_string()),
env::var("CONSUL_SERVICE_PORT").unwrap_or_else(|_| "0".to_string())
)]
});
let end = servers.len().saturating_sub(1);
let num = if end > 0 { rand::rng().random_range(0..end) } else { 0 };
let consul_data = servers.get(num).unwrap().to_string();
let ss = format!("{}/v1/catalog/service/", consul_data);
let dc = format!("{}/v1/catalog/services?filter=%22aralez.service=yes%22+in+ServiceTags", consul_data);
if let Some(infolist) = httpclient::for_consul_list(dc.as_str(), consul.token.clone()).await {
for (k, v) in infolist {
let client_header_map: DashMap<Arc<str>, Vec<(String, Arc<str>)>> = DashMap::new();
let mut client_header_list = Vec::new();
build_headers(&v.client_headers, config.as_ref(), &mut client_header_list);
let sever_header_map: DashMap<Arc<str>, Vec<(String, Arc<str>)>> = DashMap::new();
let mut server_header_list = Vec::new();
build_headers(&v.server_headers, config.as_ref(), &mut server_header_list);
if !client_header_list.is_empty() {
let path_key = v.path.clone();
client_header_map.insert(Arc::from(path_key), client_header_list);
config.client_headers.insert(Arc::from(v.host.clone()), client_header_map);
}
if !server_header_list.is_empty() {
let path_key = v.path.clone();
sever_header_map.insert(Arc::from(path_key), server_header_list);
config.server_headers.insert(Arc::from(v.host.clone()), sever_header_map);
}
let pref = format!("{}{}", ss, k);
let gsm = &GlobalServiceMapping {
upstream: k.clone(),
hostname: v.host.clone(),
path: Some(v.path),
to_https: v.to_https,
redirect_to: v.redirect,
authorization: v.auth,
rate_limit: v.rate,
x4xx_limit: v.xrate,
client_headers: v.client_headers,
server_headers: v.server_headers,
};
let list = httpclient::for_consul(pref.as_str(), consul.token.clone(), gsm).await;
list_to_upstreams(list, &upstreams, gsm);
}
}
}
if let Some(lt) = crate::utils::kuberconsul::clone_compare(&upstreams, &config).await {
let _ = toreturn.send(lt).await;
}
sleep(Duration::from_secs(5)).await;
}
}
}
+3 -1
View File
@@ -1,5 +1,7 @@
use crate::utils::consul::ConsulDiscovery;
use crate::utils::filewatch; use crate::utils::filewatch;
use crate::utils::kuberconsul::{ConsulDiscovery, KubernetesDiscovery, ServiceDiscovery}; use crate::utils::kuberconsul::ServiceDiscovery;
use crate::utils::kubernetes::KubernetesDiscovery;
use crate::utils::structs::{Configuration, UpstreamsDashMap}; use crate::utils::structs::{Configuration, UpstreamsDashMap};
use crate::web::webserver; use crate::web::webserver;
use async_trait::async_trait; use async_trait::async_trait;
+23
View File
@@ -0,0 +1,23 @@
use bytes::Bytes;
use http_body_util::Full;
use hyper::Request;
use hyper_util::client::legacy::connect::HttpConnector;
use hyper_util::client::legacy::Client;
use hyper_util::rt::TokioExecutor;
pub async fn ping_grpc(addr: &str) -> bool {
let connector = HttpConnector::new();
let client = Client::builder(TokioExecutor::new()).http2_only(true).build(connector);
let uri = format!("{}{}", addr, "/aralez.Probe/Ping");
let body = Full::new(Bytes::from(vec![0, 0, 0, 0, 0]));
let request = Request::post(uri).header("content-type", "application/grpc").header("te", "trailers").body(body).unwrap();
match client.request(request).await {
Ok(resp) => resp
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.map(|v| v.starts_with("application/grpc"))
.unwrap_or(false),
Err(_) => false,
}
}
+95
View File
@@ -0,0 +1,95 @@
use crate::utils::grpc::ping_grpc;
use bytes::Bytes;
use pingora_core::connectors::http::Connector;
use pingora_core::listeners::ALPN;
use pingora_core::prelude::HttpPeer;
use pingora_http::RequestHeader;
use std::sync::LazyLock;
use std::time::Duration;
pub static HC_CONNECTOR: LazyLock<Connector> = LazyLock::new(|| Connector::new(None));
pub async fn httpclient(method: &str, tls: bool, host: &str, path: &str, address: &str, port: u16, url: String) -> (bool, bool) {
let method = match method {
"HEAD" => "HEAD",
"GET" => "GET",
"POST" => "POST",
_ => "GET",
};
let mut peer = HttpPeer::new((address, port), tls, host.to_string());
if tls {
peer.options.verify_cert = false;
peer.options.verify_hostname = false;
}
let mut is_h2 = false;
peer.options.total_connection_timeout = Option::from(Duration::from_secs(5));
peer.options.alpn = ALPN::H2H1;
let (mut http_session, _) = match HC_CONNECTOR.get_http_session(&peer).await {
Ok(s) => s,
Err(e) => {
if let Some(msg) = e.context {
log::warn!("{}, type: {}", msg.as_str(), e.etype.as_str());
} else {
log::warn!("Fail to connect to addr: {}, tls {}, host {}", address, tls, host);
}
return (false, false);
}
};
match http_session.as_http2() {
Some(_) => {
peer.options.alpn = ALPN::H2;
is_h2 = true;
}
None => {
if ping_grpc(url.as_str()).await {
return (true, true);
}
}
}
let mut req = match RequestHeader::build(method, path.as_bytes(), None) {
Ok(r) => r,
Err(e) => {
log::warn!("Failed to build request: {}", e);
return (false, false);
}
};
req.insert_header("Host", host).ok();
if let Err(e) = http_session.write_request_header(Box::new(req)).await {
log::warn!("Write header failed: {}", e);
return (false, false);
}
let status = if is_h2 {
if let Err(e) = http_session.write_request_body(Bytes::new(), true).await {
log::warn!("Write body failed: {}", e);
return (false, false);
}
match http_session.read_response_header().await {
Ok(_) => http_session.response_header().map(|r| r.status.as_u16()).unwrap_or(500),
Err(e) => {
if ping_grpc(url.as_str()).await {
return (true, true);
}
log::warn!("Health Check H2 read failed: {} - {}", host, e);
return (false, false);
}
}
} else {
match http_session.read_response_header().await {
Ok(_) => http_session.response_header().map(|r| r.status.as_u16()).unwrap_or(500),
Err(e) => {
log::warn!("Health Check H1 read failed: {} - {}", e, host);
return (false, false);
}
}
};
HC_CONNECTOR.release_http_session(http_session, &peer, None).await;
((200..500).contains(&status), is_h2)
}
+28 -78
View File
@@ -1,23 +1,21 @@
use crate::utils::hcclient::httpclient;
use crate::utils::lazylock::REVERSE_STORE; use crate::utils::lazylock::REVERSE_STORE;
use crate::utils::structs::{InnerMap, UpstreamsDashMap, UpstreamsIdMap}; use crate::utils::structs::{InnerMap, UpstreamsDashMap, UpstreamsIdMap};
use crate::utils::tools::*; use crate::utils::tools::*;
use dashmap::DashMap; use dashmap::DashMap;
use log::{error, warn};
use reqwest::{Client, Version};
use std::sync::atomic::AtomicUsize; use std::sync::atomic::AtomicUsize;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tokio::time::interval; use tokio::net::TcpStream;
use tonic::transport::Endpoint; use tokio::time::{interval, timeout};
use tokio_native_tls::native_tls::TlsConnector;
pub async fn hc2(upslist: Arc<UpstreamsDashMap>, fullist: Arc<UpstreamsDashMap>, idlist: Arc<UpstreamsIdMap>, params: (&str, u64)) { pub async fn hc2(upslist: Arc<UpstreamsDashMap>, fullist: Arc<UpstreamsDashMap>, idlist: Arc<UpstreamsIdMap>, params: (&str, u64)) {
let mut period = interval(Duration::from_secs(params.1)); let mut period = interval(Duration::from_secs(params.1));
let client = Client::builder().timeout(Duration::from_secs(params.1)).danger_accept_invalid_certs(true).build().unwrap();
loop { loop {
tokio::select! { tokio::select! {
_ = period.tick() => { _ = period.tick() => {
// populate_upstreams(&upslist, &fullist, &idlist, params, &client).await; let totest = build_upstreams(&fullist, params.0).await;
let totest = build_upstreams(&fullist, params.0, &client).await;
if !compare_dashmaps(&totest, &upslist) { if !compare_dashmaps(&totest, &upslist) {
clone_dashmap_into(&totest, &upslist); clone_dashmap_into(&totest, &upslist);
clone_idmap_into(&totest, &idlist); clone_idmap_into(&totest, &idlist);
@@ -28,22 +26,11 @@ pub async fn hc2(upslist: Arc<UpstreamsDashMap>, fullist: Arc<UpstreamsDashMap>,
} }
} }
/*
pub async fn populate_upstreams(upslist: &Arc<UpstreamsDashMap>, fullist: &Arc<UpstreamsDashMap>, idlist: &Arc<UpstreamsIdMap>, params: (&str, u64), client: &Client) {
let totest = build_upstreams(fullist, params.0, client).await;
if !compare_dashmaps(&totest, upslist) {
clone_dashmap_into(&totest, upslist);
clone_idmap_into(&totest, idlist);
}
}
*/
pub async fn initiate_upstreams(fullist: UpstreamsDashMap) -> UpstreamsDashMap { pub async fn initiate_upstreams(fullist: UpstreamsDashMap) -> UpstreamsDashMap {
let client = Client::builder().timeout(Duration::from_secs(2)).danger_accept_invalid_certs(true).build().unwrap(); build_upstreams(&fullist, "HEAD").await
build_upstreams(&fullist, "HEAD", &client).await
} }
async fn build_upstreams(fullist: &UpstreamsDashMap, method: &str, client: &Client) -> UpstreamsDashMap { async fn build_upstreams(fullist: &UpstreamsDashMap, method: &str) -> UpstreamsDashMap {
let totest: UpstreamsDashMap = DashMap::new(); let totest: UpstreamsDashMap = DashMap::new();
let fclone = clone_dashmap(fullist); let fclone = clone_dashmap(fullist);
for val in fclone.iter() { for val in fclone.iter() {
@@ -56,18 +43,15 @@ async fn build_upstreams(fullist: &UpstreamsDashMap, method: &str, client: &Clie
for upstream in path_entry.value().0.iter() { for upstream in path_entry.value().0.iter() {
let tls = if upstream.healthcheck.unwrap_or(true) { let tls = if upstream.healthcheck.unwrap_or(true) {
detect_tls(upstream.address.as_ref(), &upstream.port, client).await detect_tls(upstream.address.as_ref(), &upstream.port, host).await
} else { } else {
(false, None) false
}; };
let is_h2 = matches!(tls.1, Some(Version::HTTP_2));
let mut scheme = InnerMap { let mut scheme = InnerMap {
address: upstream.address.clone(), address: upstream.address.clone(),
port: upstream.port, port: upstream.port,
is_ssl: tls.0, is_ssl: tls,
is_http2: is_h2, is_http2: false,
to_https: upstream.to_https, to_https: upstream.to_https,
rate_limit: upstream.rate_limit, rate_limit: upstream.rate_limit,
x4xx_limit: upstream.x4xx_limit, x4xx_limit: upstream.x4xx_limit,
@@ -77,20 +61,17 @@ async fn build_upstreams(fullist: &UpstreamsDashMap, method: &str, client: &Clie
}; };
if scheme.healthcheck.unwrap_or(true) { if scheme.healthcheck.unwrap_or(true) {
let link = if tls.0 { let link = if tls {
format!("https://{}:{}{}", upstream.address, upstream.port, path) format!("https://{}:{}{}", upstream.address, upstream.port, path)
} else { } else {
format!("http://{}:{}{}", upstream.address, upstream.port, path) format!("http://{}:{}{}", upstream.address, upstream.port, path)
}; };
let resp = httpclient(method, tls, host, path, upstream.address.as_ref(), upstream.port, link).await;
let resp = http_request(&link, method, "", client).await;
if resp.0 { if resp.0 {
if resp.1 { if resp.1 {
scheme.is_http2 = is_h2; // could be adjusted further scheme.is_http2 = resp.1;
} }
innervec.push(Arc::from(scheme)); innervec.push(Arc::from(scheme));
} else {
warn!("Dead Upstream : {}", link);
} }
} else { } else {
innervec.push(Arc::from(scheme)); innervec.push(Arc::from(scheme));
@@ -103,51 +84,20 @@ async fn build_upstreams(fullist: &UpstreamsDashMap, method: &str, client: &Clie
totest totest
} }
async fn http_request(url: &str, method: &str, payload: &str, client: &Client) -> (bool, bool) { async fn detect_tls(ip: &str, port: &u16, sni: &str) -> bool {
if !["POST", "GET", "HEAD"].contains(&method) { let addr = format!("{ip}:{port}");
error!("Method {} not supported. Only GET|POST|HEAD are supported ", method); let timeout_duration = Duration::from_secs(2); // THINK !!!
return (false, false); let stream = match timeout(timeout_duration, TcpStream::connect(&addr)).await {
} Ok(Ok(s)) => s,
async fn send_request(client: &Client, method: &str, url: &str, payload: &str) -> Option<reqwest::Response> { Ok(Err(_)) => return false,
match method {
"POST" => client.post(url).body(payload.to_owned()).send().await.ok(),
"GET" => client.get(url).send().await.ok(),
"HEAD" => client.head(url).send().await.ok(),
_ => None,
}
}
match send_request(client, method, url, payload).await {
Some(response) => {
let status = response.status().as_u16();
((99..499).contains(&status), false)
}
None => (ping_grpc(url).await, true),
}
}
pub async fn ping_grpc(addr: &str) -> bool {
let endpoint = match Endpoint::from_shared(addr.to_owned()) {
Ok(e) => e.timeout(Duration::from_secs(2)),
Err(_) => return false, Err(_) => return false,
}; };
tokio::time::timeout(Duration::from_secs(3), endpoint.connect()).await.ok().and_then(Result::ok).is_some() let connector = tokio_native_tls::TlsConnector::from(
} TlsConnector::builder()
.danger_accept_invalid_certs(true)
async fn detect_tls(ip: &str, port: &u16, client: &Client) -> (bool, Option<Version>) { .danger_accept_invalid_hostnames(true)
let https_url = format!("https://{}:{}", ip, port); .build()
if let Ok(response) = client.get(&https_url).send().await { .unwrap(),
return (true, Some(response.version())); );
} connector.connect(sni, stream).await.is_ok()
let http_url = format!("http://{}:{}", ip, port);
match client.get(&http_url).send().await {
Ok(response) => (false, Some(response.version())),
Err(_) => {
if ping_grpc(&http_url).await {
(false, Some(Version::HTTP_2))
} else {
(false, None)
}
}
}
} }
+184 -34
View File
@@ -1,69 +1,119 @@
use crate::utils::kuberconsul::{match_path, ConsulService, KubeEndpoints}; use crate::utils::consul::ConsulService;
use crate::utils::kuberconsul::match_path;
use crate::utils::kubernetes::KubeEndpoints;
use crate::utils::structs::{GlobalServiceMapping, InnerMap}; use crate::utils::structs::{GlobalServiceMapping, InnerMap};
use axum::http::{HeaderMap, HeaderValue}; use ahash::HashMap;
use dashmap::DashMap; use dashmap::DashMap;
use reqwest::Client; use pingora_core::connectors::http::Connector;
use pingora_core::listeners::ALPN;
use pingora_core::prelude::HttpPeer;
use pingora_http::RequestHeader;
use serde::{Deserialize, Serialize};
use std::sync::atomic::AtomicUsize; use std::sync::atomic::AtomicUsize;
use std::sync::Arc; use std::sync::{Arc, LazyLock};
use std::time::Duration; use std::time::Duration;
pub async fn for_consul(url: String, token: Option<String>, conf: &GlobalServiceMapping) -> Option<DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>> { pub static CONNECTOR: LazyLock<Connector> = LazyLock::new(|| Connector::new(None));
let client = Client::builder().timeout(Duration::from_secs(2)).danger_accept_invalid_certs(true).build().ok()?;
let mut headers = HeaderMap::new(); #[derive(Debug, Default, Serialize, Deserialize, Clone)]
if let Some(token) = token { pub struct ConsulServicesInternal {
headers.insert("X-Consul-Token", HeaderValue::from_str(&token).unwrap()); #[serde(rename = "aralez.host")]
pub host: String,
#[serde(rename = "aralez.path")]
pub path: String,
#[serde(rename = "aralez.auth")]
pub auth: Option<String>,
#[serde(rename = "aralez.redirect")]
pub redirect: Option<String>,
#[serde(rename = "aralez.rate")]
pub rate: Option<isize>,
#[serde(rename = "aralez.4xx_rate")]
pub xrate: Option<u32>,
#[serde(rename = "aralez.client_headers")]
pub client_headers: Option<Vec<String>>,
#[serde(rename = "aralez.server_headers")]
pub server_headers: Option<Vec<String>>,
#[serde(rename = "aralez.to_https")]
pub to_https: Option<bool>,
}
pub type ConsulServices = HashMap<String, ConsulServicesInternal>;
pub async fn for_consul_list(url: &str, token: Option<String>) -> Option<ConsulServices> {
if let Some(data) = getfromapi(url, token, "consul").await {
let yo = parse_services(data);
if let Ok(y) = yo {
return Some(y);
} }
let to = Duration::from_secs(1);
let resp = client.get(url).timeout(to).send().await.ok()?;
if !resp.status().is_success() {
eprintln!("Consul API returned status: {}", resp.status());
return None; return None;
} }
None
}
fn parse_services(json: Vec<u8>) -> Result<ConsulServices, serde_json::Error> {
let raw: HashMap<String, Vec<String>> = serde_json::from_slice(&json)?;
Ok(raw
.into_iter()
.map(|(service, tags)| {
let mut internal = ConsulServicesInternal::default();
for tag in tags {
if let Some((key, value)) = tag.split_once('=') {
match key {
"aralez.host" => internal.host = value.to_string(),
"aralez.path" => internal.path = value.to_string(),
"aralez.rate" => internal.rate = value.parse::<isize>().ok(),
"aralez.4xx_rate" => internal.xrate = value.parse::<u32>().ok(),
"aralez.to_https" => internal.to_https = value.parse::<bool>().ok(),
"aralez.auth" => internal.auth = Option::from(value.to_string()),
"aralez.redirect" => internal.redirect = Option::from(value.to_string()),
"aralez.client_header" => internal.client_headers.get_or_insert_default().push(value.to_string()),
"aralez.server_header" => internal.server_headers.get_or_insert_default().push(value.to_string()),
_ => {}
}
}
}
(service, internal)
})
.collect())
}
pub async fn for_consul(url: &str, token: Option<String>, conf: &GlobalServiceMapping) -> Option<DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>> {
if let Some(data) = getfromapi(url, token, "consul").await {
let endpoints: Vec<ConsulService> = serde_json::from_slice(&data).ok()?;
let mut inner_vec = Vec::new(); let mut inner_vec = Vec::new();
let upstreams: DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)> = DashMap::new(); let upstreams: DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)> = DashMap::new();
let endpoints: Vec<ConsulService> = resp.json().await.ok()?;
for subsets in endpoints { for subsets in endpoints {
let addr = subsets.tagged_addresses.get("lan_ipv4").unwrap().address.clone();
let prt = subsets.tagged_addresses.get("lan_ipv4").unwrap().port;
let to_add = Arc::from(InnerMap { let to_add = Arc::from(InnerMap {
address: Arc::from(&*addr), address: Arc::from(&*subsets.address),
port: prt, port: subsets.port,
is_ssl: false, is_ssl: false,
is_http2: false, is_http2: false,
to_https: conf.to_https.unwrap_or(false), to_https: conf.to_https.unwrap_or(false),
rate_limit: conf.rate_limit, rate_limit: conf.rate_limit,
x4xx_limit: conf.x4xx_limit, x4xx_limit: conf.x4xx_limit,
redirect_to: None, redirect_to: conf.redirect_to.clone().map(Arc::<str>::from),
healthcheck: None, healthcheck: None,
authorization: None, authorization: None,
}); });
inner_vec.push(to_add); inner_vec.push(to_add);
} }
match_path(conf, &upstreams, inner_vec); match_path(conf, &upstreams, inner_vec);
Some(upstreams) return Some(upstreams);
};
None
} }
pub async fn for_kuber(url: &str, token: &str, conf: &GlobalServiceMapping) -> Option<DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>> { pub async fn for_kuber(url: &str, token: &str, conf: &GlobalServiceMapping) -> Option<DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>> {
let to = Duration::from_secs(10); if let Some(data) = getfromapi(url, Some(token.to_string()), "kubernetes").await {
let client = Client::builder().timeout(Duration::from_secs(10)).danger_accept_invalid_certs(true).build().ok()?; let endpoints: KubeEndpoints = serde_json::from_slice(&data).ok()?;
let resp = client.get(url).timeout(to).bearer_auth(token).send().await.ok()?;
if !resp.status().is_success() {
eprintln!("Kubernetes API returned status: {}", resp.status());
return None;
}
let endpoints: KubeEndpoints = resp.json().await.ok()?;
let upstreams: DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)> = DashMap::new(); let upstreams: DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)> = DashMap::new();
let mut inner_vec = Vec::new();
if let Some(subsets) = endpoints.subsets { if let Some(subsets) = endpoints.subsets {
for subset in subsets { for subset in subsets {
if let (Some(addresses), Some(ports)) = (subset.addresses, subset.ports) { if let (Some(addresses), Some(ports)) = (subset.addresses, subset.ports) {
let mut inner_vec = Vec::new();
for addr in addresses { for addr in addresses {
for port in &ports { for port in &ports {
// let redirect_link = conf.redirect_to.as_ref().map(|www| Arc::from(www.as_str()));
let to_add = Arc::from(InnerMap { let to_add = Arc::from(InnerMap {
address: Arc::from(addr.ip.clone()), address: Arc::from(addr.ip.as_str()),
port: port.port, port: port.port,
is_ssl: false, is_ssl: false,
is_http2: false, is_http2: false,
@@ -77,9 +127,109 @@ pub async fn for_kuber(url: &str, token: &str, conf: &GlobalServiceMapping) -> O
inner_vec.push(to_add); inner_vec.push(to_add);
} }
} }
match_path(conf, &upstreams, inner_vec.clone());
} }
} }
} }
Some(upstreams) if !inner_vec.is_empty() {
match_path(conf, &upstreams, inner_vec);
return Some(upstreams);
}
}
None
} }
pub async fn getfromapi(url: &str, token: Option<String>, provider: &str) -> Option<Vec<u8>> {
let (host, port, path, is_tls) = parse_url(&url).ok()?;
let mut peer = HttpPeer::new((host, port), is_tls, host.to_string());
peer.options.total_connection_timeout = Some(Duration::from_secs(5));
peer.options.read_timeout = Some(Duration::from_secs(5));
if is_tls {
peer.options.verify_cert = false;
peer.options.verify_hostname = false;
peer.options.alpn = ALPN::H2H1;
}
let mut http_session = CONNECTOR.get_http_session(&peer).await.ok()?;
let mut req = RequestHeader::build("GET", path.as_bytes(), None).ok()?;
let host_header = if (is_tls && port == 443) || (!is_tls && port == 80) {
host.to_string()
} else {
format!("{}:{}", host, port)
};
req.insert_header("Host", host_header).ok()?;
req.insert_header("Accept", "application/json").ok()?;
match provider {
"consul" => {
if let Some(token) = token {
req.insert_header("X-Consul-Token", token).ok()?;
}
}
"kubernetes" => {
if let Some(token) = token {
req.insert_header("Authorization", format!("Bearer {}", token)).ok()?;
}
}
_ => {}
}
if http_session.0.write_request_header(Box::new(req)).await.is_err() {
CONNECTOR.release_http_session(http_session.0, &peer, None).await;
return None;
}
let status = match http_session.0.read_response_header().await {
Ok(_) => http_session.0.response_header().map(|r| r.status.as_u16()).unwrap_or(500),
Err(e) => {
log::warn!("API call failed to read response header for {}: {}", url, e);
500
}
};
let mut body_bytes = Vec::new();
if status == 200 {
while let Ok(Some(chunk)) = http_session.0.read_response_body().await {
body_bytes.extend_from_slice(&chunk);
}
}
CONNECTOR.release_http_session(http_session.0, &peer, None).await;
if status == 200 && !body_bytes.is_empty() {
Some(body_bytes)
} else {
None
}
}
fn parse_url(url: &str) -> Result<(&str, u16, &str, bool), &'static str> {
let is_https = url.starts_with("https://");
let default_port = if is_https { 443 } else { 80 };
let no_scheme = url.split_once("://").map_or(url, |(_, rest)| rest);
let (authority, uri) = no_scheme.find('/').map_or((no_scheme, "/"), |i| (&no_scheme[..i], &no_scheme[i..]));
let (host, port) = match authority.split_once(':') {
Some((h, p)) => {
let port_num = p.parse::<u16>().map_err(|_| "Invalid port number")?;
(h, port_num)
}
None => (authority, default_port),
};
if host.is_empty() {
return Err("Empty host in URL");
}
Ok((host, port, uri, is_https))
}
// fn base64_decode(encoded: &str) -> Option<String> {
// let decoded_bytes = general_purpose::STANDARD.decode(encoded).unwrap_or_default();
// let decoded: Result<HashMap<String, String>, serde_json::Error> = serde_json::from_slice(&decoded_bytes);
// println!("Decoded: {:?}", decoded);
// None
// }
+14 -179
View File
@@ -1,216 +1,50 @@
use crate::utils::httpclient;
use crate::utils::parceyaml::build_headers;
use crate::utils::structs::{Configuration, GlobalServiceMapping, InnerMap, UpstreamsDashMap}; use crate::utils::structs::{Configuration, GlobalServiceMapping, InnerMap, UpstreamsDashMap};
use crate::utils::tools::{clone_dashmap_into, compare_dashmaps, print_upstreams}; use crate::utils::tools::{clone_dashmap_into, compare_dashmaps, print_upstreams};
use async_trait::async_trait; use async_trait::async_trait;
use dashmap::DashMap; use dashmap::DashMap;
use pingora::prelude::sleep;
use rand::RngExt;
use serde::Deserialize;
use std::collections::HashMap;
use std::env;
use std::fs;
use std::path::Path;
use std::sync::atomic::AtomicUsize; use std::sync::atomic::AtomicUsize;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration;
use tokio::fs::File; use tokio::fs::File;
use tokio::io::AsyncReadExt; use tokio::io::AsyncReadExt;
use tokio::sync::mpsc::Sender; use tokio::sync::mpsc::Sender;
#[derive(Debug, serde::Deserialize)]
pub struct KubeEndpoints {
pub subsets: Option<Vec<KubeSubset>>,
}
#[derive(Debug, serde::Deserialize)]
pub struct KubeSubset {
pub addresses: Option<Vec<KubeAddress>>,
pub ports: Option<Vec<KubePort>>,
}
#[derive(Debug, serde::Deserialize)]
pub struct KubeAddress {
pub ip: String,
}
#[derive(Debug, serde::Deserialize)]
pub struct KubePort {
pub port: u16,
}
#[derive(Debug, Deserialize)]
pub struct ConsulService {
#[serde(rename = "ServiceTaggedAddresses")]
pub tagged_addresses: HashMap<String, ConsulTaggedAddress>,
}
#[derive(Debug, Deserialize)]
pub struct ConsulTaggedAddress {
#[serde(rename = "Address")]
pub address: String,
#[serde(rename = "Port")]
pub port: u16,
}
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn list_to_upstreams(lt: Option<DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>>, upstreams: &UpstreamsDashMap, i: &GlobalServiceMapping) { pub fn list_to_upstreams(lt: Option<DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>>, upstreams: &UpstreamsDashMap, i: &GlobalServiceMapping) {
if let Some(list) = lt { if let Some(list) = lt {
match upstreams.get(&*i.hostname.clone()) { let key: Arc<str> = Arc::from(i.hostname.as_str());
match upstreams.get(&key) {
Some(upstr) => { Some(upstr) => {
for (k, v) in list { for (k, v) in list {
upstr.value().insert(k.to_owned(), v); upstr.value().insert(k, v);
} }
} }
None => { None => {
upstreams.insert(Arc::from(i.hostname.clone()), list); upstreams.insert(key, list);
} }
}; };
} }
} }
pub fn match_path(conf: &GlobalServiceMapping, upstreams: &DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>, values: Vec<Arc<InnerMap>>) { pub fn match_path(conf: &GlobalServiceMapping, upstreams: &DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>, values: Vec<Arc<InnerMap>>) {
match conf.path { let path = conf.path.as_deref().unwrap_or("/");
Some(ref p) => { upstreams.insert(Arc::from(path), (values, AtomicUsize::new(0)));
upstreams.insert(Arc::from(p.clone()), (values, AtomicUsize::new(0)));
}
None => {
upstreams.insert(Arc::from("/"), (values, AtomicUsize::new(0)));
}
}
} }
async fn read_token(path: &str) -> String { pub async fn read_token(path: &str) -> String {
let mut file = File::open(path).await.unwrap(); let mut file = File::open(path).await.unwrap();
let mut contents = String::new(); let mut contents = String::new();
file.read_to_string(&mut contents).await.unwrap(); file.read_to_string(&mut contents).await.unwrap();
contents.trim().to_string() contents.trim().to_string()
} }
#[async_trait] #[async_trait]
pub trait ServiceDiscovery { pub trait ServiceDiscovery {
async fn fetch_upstreams(&self, config: Arc<Configuration>, toreturn: Sender<Configuration>); async fn fetch_upstreams(&self, config: Arc<Configuration>, toreturn: Sender<Configuration>);
} }
pub struct KubernetesDiscovery; pub async fn clone_compare(upstreams: &UpstreamsDashMap, config: &Arc<Configuration>) -> Option<Configuration> {
pub struct ConsulDiscovery; if !compare_dashmaps(upstreams, &config.upstreams) {
let tosend = Configuration {
#[async_trait]
impl ServiceDiscovery for KubernetesDiscovery {
async fn fetch_upstreams(&self, config: Arc<Configuration>, toreturn: Sender<Configuration>) {
let prev_upstreams = UpstreamsDashMap::new();
if let Some(kuber) = config.kubernetes.clone() {
let servers = kuber.servers.unwrap_or(vec![format!(
"{}:{}",
env::var("KUBERNETES_SERVICE_HOST").unwrap_or("0.0.0.0".to_string()),
env::var("KUBERNETES_SERVICE_PORT_HTTPS").unwrap_or("0".to_string())
)]);
let end = servers.len().saturating_sub(1);
let num = if end > 0 { rand::rng().random_range(0..end) } else { 0 };
let server = servers.get(num).unwrap().to_string();
let path = kuber.tokenpath.unwrap_or("/var/run/secrets/kubernetes.io/serviceaccount/token".to_string());
let namespace = get_current_namespace().unwrap_or_else(|| "default".to_string());
let token = read_token(path.as_str()).await;
loop {
let upstreams = UpstreamsDashMap::new();
if let Some(kuber) = config.kubernetes.clone() {
if let Some(svc) = kuber.services {
for service in svc {
let header_list: DashMap<Arc<str>, Vec<(String, Arc<str>)>> = DashMap::new();
let mut hl = Vec::new();
build_headers(&service.client_headers, config.as_ref(), &mut hl);
if !hl.is_empty() {
match service.path.clone() {
Some(path) => {
header_list.insert(Arc::from(path.as_str()), hl);
}
None => {
header_list.insert(Arc::from("/"), hl);
}
}
// header_list.insert(Arc::from(path.as_str()), hl);
// header_list.insert(Arc::from(i.path).unwrap_or(Arc::from("/")).as_str(), hl);
config.client_headers.insert(Arc::from(service.hostname.clone()), header_list);
}
let url = format!("https://{}/api/v1/namespaces/{}/endpoints/{}", server, namespace, service.hostname);
// let url = format!("https://{}/api/v1/namespaces/{}/endpoints?labelSelector=app", server, namespace);
let list = httpclient::for_kuber(&url, &token, &service).await;
// println!("{:?}", list);
list_to_upstreams(list, &upstreams, &service);
}
}
if let Some(lt) = clone_compare(&upstreams, &prev_upstreams, &config).await {
toreturn.send(lt).await.unwrap();
}
}
sleep(Duration::from_secs(5)).await;
}
}
}
}
fn get_current_namespace() -> Option<String> {
let ns_path = "/var/run/secrets/kubernetes.io/serviceaccount/namespace";
if Path::new(ns_path).exists() {
if let Ok(contents) = fs::read_to_string(ns_path) {
return Some(contents.trim().to_string());
}
}
std::env::var("POD_NAMESPACE").ok()
}
#[async_trait]
impl ServiceDiscovery for ConsulDiscovery {
async fn fetch_upstreams(&self, config: Arc<Configuration>, toreturn: Sender<Configuration>) {
let prev_upstreams = UpstreamsDashMap::new();
loop {
let upstreams = UpstreamsDashMap::new();
if let Some(consul) = config.consul.clone() {
let servers = consul.servers.unwrap_or(vec![format!(
"{}:{}",
env::var("CONSUL_SERVICE_HOST").unwrap_or("0.0.0.0".to_string()),
env::var("CONSUL_SERVICE_PORT").unwrap_or("0".to_string())
)]);
let end = servers.len().saturating_sub(1);
let num = if end > 0 { rand::rng().random_range(0..end) } else { 0 };
let consul_data = servers.get(num).unwrap().to_string();
let ss = consul_data + "/v1/catalog/service/";
if let Some(svc) = consul.services {
for i in svc {
let header_list = DashMap::new();
let mut hl = Vec::new();
build_headers(&i.client_headers, config.as_ref(), &mut hl);
if !hl.is_empty() {
match i.path.clone() {
Some(path) => {
header_list.insert(Arc::from(path.as_str()), hl);
}
None => {
header_list.insert(Arc::from("/"), hl);
}
}
// header_list.insert(i.path.clone().unwrap_or("/".to_string()), hl);
config.client_headers.insert(Arc::from(i.hostname.clone()), header_list);
}
let pref = ss.clone() + &i.upstream;
let list = httpclient::for_consul(pref, consul.token.clone(), &i).await;
list_to_upstreams(list, &upstreams, &i);
}
}
}
if let Some(lt) = clone_compare(&upstreams, &prev_upstreams, &config).await {
toreturn.send(lt).await.unwrap();
}
sleep(Duration::from_secs(5)).await;
}
}
}
async fn clone_compare(upstreams: &UpstreamsDashMap, prev_upstreams: &UpstreamsDashMap, config: &Arc<Configuration>) -> Option<Configuration> {
if !compare_dashmaps(upstreams, prev_upstreams) {
let tosend: Configuration = Configuration {
upstreams: Default::default(), upstreams: Default::default(),
client_headers: config.client_headers.clone(), client_headers: config.client_headers.clone(),
server_headers: config.server_headers.clone(), server_headers: config.server_headers.clone(),
@@ -219,10 +53,11 @@ async fn clone_compare(upstreams: &UpstreamsDashMap, prev_upstreams: &UpstreamsD
typecfg: config.typecfg.clone(), typecfg: config.typecfg.clone(),
extraparams: config.extraparams.clone(), extraparams: config.extraparams.clone(),
}; };
clone_dashmap_into(upstreams, prev_upstreams); clone_dashmap_into(upstreams, &config.upstreams);
clone_dashmap_into(upstreams, &tosend.upstreams); clone_dashmap_into(upstreams, &tosend.upstreams);
print_upstreams(&tosend.upstreams, &tosend.extraparams); print_upstreams(&tosend.upstreams, &tosend.extraparams);
return Some(tosend); return Some(tosend);
}; }
None None
} }
+157
View File
@@ -0,0 +1,157 @@
use crate::utils::httpclient;
use crate::utils::kubewatcher::{start_ingress_watcher_with_config, AralezRouteUpdate};
use crate::utils::parceyaml::build_headers;
use crate::utils::structs::{Configuration, GlobalServiceMapping, UpstreamsDashMap};
use async_trait::async_trait;
use dashmap::DashMap;
use log::error;
use rand::RngExt;
use std::env;
use std::fs;
use std::path::Path;
use std::sync::Arc;
use crate::utils::kuberconsul::{clone_compare, list_to_upstreams, ServiceDiscovery};
use rustls::crypto::ring::default_provider;
use rustls::crypto::CryptoProvider;
use tokio::sync::mpsc::Sender;
#[derive(Debug, serde::Deserialize)]
pub struct KubeEndpoints {
pub subsets: Option<Vec<KubeSubset>>,
}
#[derive(Debug, serde::Deserialize)]
pub struct KubeSubset {
pub addresses: Option<Vec<KubeAddress>>,
pub ports: Option<Vec<KubePort>>,
}
#[derive(Debug, serde::Deserialize)]
pub struct KubeAddress {
pub ip: String,
}
#[derive(Debug, serde::Deserialize)]
pub struct KubePort {
pub port: u16,
}
pub struct KubernetesDiscovery;
#[async_trait]
impl ServiceDiscovery for KubernetesDiscovery {
async fn fetch_upstreams(&self, config: Arc<Configuration>, toreturn: Sender<Configuration>) {
if let Some(kuber) = config.kubernetes.clone() {
let servers = kuber.servers.unwrap_or_else(|| {
vec![format!(
"{}:{}",
env::var("KUBERNETES_SERVICE_HOST").unwrap_or_else(|_| "0.0.0.0".to_string()),
env::var("KUBERNETES_SERVICE_PORT_HTTPS").unwrap_or_else(|_| "0".to_string())
)]
});
let end = servers.len().saturating_sub(1);
let num = if end > 0 { rand::rng().random_range(0..end) } else { 0 };
let server = servers.get(num).unwrap().to_string();
let path = kuber.tokenpath.unwrap_or_else(|| "/var/run/secrets/kubernetes.io/serviceaccount/token".to_string());
let namespace = get_current_namespace().unwrap_or_else(|| "default".to_string());
let token = crate::utils::kuberconsul::read_token(path.as_str()).await;
if CryptoProvider::get_default().is_none() {
default_provider().install_default().expect("Failed to install rustls crypto provider");
}
let (tx, mut rx) = tokio::sync::mpsc::channel::<AralezRouteUpdate>(100);
let srv = server.clone();
let tok = token.clone();
tokio::spawn(async move {
if let Err(e) = start_ingress_watcher_with_config(srv.as_str(), tok.as_str(), tx, "aralez").await {
error!("Fatal watcher error: {:?}", e);
}
});
let upstreams = UpstreamsDashMap::new();
while let Some(update) = rx.recv().await {
let host_key: Arc<str> = Arc::from(update.host.as_str());
if update.is_deleted {
upstreams.remove(&host_key);
config.client_headers.remove(&host_key);
} else {
let service = GlobalServiceMapping {
upstream: update.service_name.clone(),
hostname: update.host.clone(),
path: Some(update.path.clone()),
to_https: None,
redirect_to: None,
authorization: None,
// sticky_sessions: update.sticky_sessions,
rate_limit: update.rate_limit,
x4xx_limit: update.x4xx_limit,
client_headers: None,
server_headers: None,
};
let cheader_list: DashMap<Arc<str>, Vec<(String, Arc<str>)>> = DashMap::new();
let sheader_list: DashMap<Arc<str>, Vec<(String, Arc<str>)>> = DashMap::new();
let mut client_headers_list = Vec::new();
let mut server_headers_list = Vec::new();
let mut chl: Option<Vec<String>> = None;
let mut shl: Option<Vec<String>> = None;
if let Some(hdr) = service.client_headers.as_ref() {
chl.get_or_insert_with(Vec::new).extend(hdr.iter().cloned());
}
if let Some(hdr) = service.server_headers.as_ref() {
shl.get_or_insert_with(Vec::new).extend(hdr.iter().cloned());
}
if let Some(ch) = update.client_headers {
chl.get_or_insert_with(Vec::new).extend(ch);
}
if let Some(hdr) = update.server_headers.as_ref() {
shl.get_or_insert_with(Vec::new).extend(hdr.iter().cloned());
}
build_headers(&chl, config.as_ref(), &mut client_headers_list);
if !client_headers_list.is_empty() {
let path_key = service.path.as_deref().unwrap_or("/");
cheader_list.insert(Arc::from(path_key), client_headers_list);
config.client_headers.insert(host_key.clone(), cheader_list);
}
build_headers(&shl, config.as_ref(), &mut server_headers_list);
if !server_headers_list.is_empty() {
let path_key = service.path.as_deref().unwrap_or("/");
sheader_list.insert(Arc::from(path_key), server_headers_list);
config.server_headers.insert(host_key.clone(), sheader_list);
}
let url = format!("https://{}/api/v1/namespaces/{}/endpoints/{}", server, namespace, update.service_name);
let list = httpclient::for_kuber(&url, &token, &service).await;
if list.is_none() {
error!("Endpoint URL: {} returned empty/invalid response", url);
}
list_to_upstreams(list, &upstreams, &service);
}
if let Some(lt) = clone_compare(&upstreams, &config).await {
let _ = toreturn.send(lt).await;
}
}
}
}
}
fn get_current_namespace() -> Option<String> {
let ns_path = "/var/run/secrets/kubernetes.io/serviceaccount/namespace";
if Path::new(ns_path).exists() {
if let Ok(contents) = fs::read_to_string(ns_path) {
return Some(contents.trim().to_string());
}
}
env::var("POD_NAMESPACE").ok()
}
+271
View File
@@ -0,0 +1,271 @@
use futures::StreamExt;
use hyper::Uri;
use k8s_openapi::api::core::v1::{Node, Service};
use k8s_openapi::api::networking::v1::Ingress;
use kube::api::{Patch, PatchParams};
use kube::{
api::Api,
client::Client,
config::{AuthInfo, Config},
runtime::watcher::{self, Event},
};
use log::{error, info};
use serde_json::json;
use std::time::Duration;
use tokio::sync::mpsc;
pub struct AralezRouteUpdate {
pub host: String,
pub path: String,
pub service_name: String,
pub is_deleted: bool,
pub rate_limit: Option<isize>,
pub x4xx_limit: Option<u32>,
pub sticky_sessions: Option<u64>,
pub client_headers: Option<Vec<String>>,
pub server_headers: Option<Vec<String>>,
}
#[derive(Debug, Clone)]
struct AralezAnnotation {
rate_limit: Option<isize>,
x4xx_limit: Option<u32>,
sticky_sessions: Option<u64>,
pub client_headers: Option<Vec<String>>,
pub server_headers: Option<Vec<String>>,
}
pub async fn start_ingress_watcher_with_config(
api_server: &str,
token: &str,
tx: mpsc::Sender<AralezRouteUpdate>,
target_class: &'static str,
) -> Result<(), Box<dyn std::error::Error>> {
let client = create_custom_k8s_client(api_server, token, true).await?;
let ingresses: Api<Ingress> = Api::all(client.clone());
let watcher_config = watcher::Config::default().any_semantic();
let mut stream = watcher::watcher(ingresses, watcher_config).boxed();
while let Some(event) = stream.next().await {
match event {
Ok(Event::Apply(ing)) => {
process_ingress_change(&client, &ing, target_class, false, &tx).await;
}
Ok(Event::Delete(ing)) => {
process_ingress_change(&client, &ing, target_class, true, &tx).await;
}
Ok(Event::InitApply(ing)) => {
process_ingress_change(&client, &ing, target_class, false, &tx).await;
}
Ok(Event::InitDone) => {
info!("Ingress watcher initial sync complete");
}
Err(err) => {
error!("Kubernetes Ingress watch error: {:?}", err);
}
_ => {}
}
}
Ok(())
}
async fn process_ingress_change(client: &Client, ing: &Ingress, target_class: &str, is_deleted: bool, tx: &mpsc::Sender<AralezRouteUpdate>) {
let spec = match &ing.spec {
Some(s) => s,
None => return,
};
let class_name = spec.ingress_class_name.as_deref().unwrap_or_default();
if class_name != target_class {
return;
}
if !is_deleted {
if let (Some(name), Some(ns)) = (ing.metadata.name.as_deref(), ing.metadata.namespace.as_deref()) {
let svc_name = std::env::var("ARALEZ_SERVICE_NAME").unwrap_or_else(|_| "aralez-service".to_string());
let external_ip = match discover_controller_ip(&client, ns, &svc_name).await {
Some(ip) => ip,
None => discover_node_ip(&client)
.await
.unwrap_or_else(|| std::env::var("ARALEZ_EXTERNAL_IP").unwrap_or_else(|_| "127.0.0.1".to_string())),
};
let client_clone = client.clone();
let name_clone = name.to_string();
let ns_clone = ns.to_string();
let status = ing.status.clone();
tokio::spawn(async move {
patch_ingress_status_address(&client_clone, &ns_clone, &name_clone, status.as_ref(), &external_ip).await;
});
}
}
if let Some(rules) = &spec.rules {
for rule in rules {
let host = match &rule.host {
Some(h) => h.clone(),
None => "*".to_string(),
};
if let Some(http) = &rule.http {
for path_entry in &http.paths {
let path = path_entry.path.clone().unwrap_or_else(|| "/".to_string());
if let Some(service) = &path_entry.backend.service {
let service_name = service.name.clone();
let mut update = AralezRouteUpdate {
host: host.clone(),
path,
service_name,
is_deleted,
rate_limit: None,
x4xx_limit: None,
sticky_sessions: None,
client_headers: None,
server_headers: None,
};
if let Some(annot) = parse_ingress_config(ing) {
update.rate_limit = annot.rate_limit;
update.x4xx_limit = annot.x4xx_limit;
update.sticky_sessions = annot.sticky_sessions;
update.client_headers = annot.client_headers;
update.server_headers = annot.server_headers;
}
if let Err(e) = tx.send(update).await {
error!("Failed to send route update to channel: {:?}", e);
}
}
}
}
}
}
}
pub async fn create_custom_k8s_client(api_server_url: &str, bearer_token: &str, insecure_tls: bool) -> Result<Client, Box<dyn std::error::Error>> {
let full_url = if !api_server_url.starts_with("http://") && !api_server_url.starts_with("https://") {
format!("https://{}", api_server_url)
} else {
api_server_url.to_string()
};
let uri: Uri = full_url.parse()?;
let mut config = Config::new(uri);
config.auth_info = AuthInfo {
token: Some(bearer_token.trim().to_string().into()),
..Default::default()
};
config.connect_timeout = Some(Duration::from_secs(10));
config.read_timeout = None;
if insecure_tls {
config.accept_invalid_certs = true;
}
let client = Client::try_from(config)?;
Ok(client)
}
fn parse_ingress_config(ing: &Ingress) -> Option<AralezAnnotation> {
if let Some(annotations) = &ing.metadata.annotations {
let mut clh: Option<Vec<String>> = None;
let mut srh: Option<Vec<String>> = None;
if let Some(ch) = annotations.get("aralez.rs/client_headers") {
let data: Vec<String> = serde_json::from_str(ch).unwrap_or_default();
clh = Some(data);
}
if let Some(sh) = annotations.get("aralez.rs/server_headers") {
let data: Vec<String> = serde_json::from_str(sh).unwrap_or_default();
srh = Some(data);
};
let annots = AralezAnnotation {
rate_limit: annotations.get("aralez.rs/rate_limit").and_then(|v| v.parse().ok()),
x4xx_limit: annotations.get("aralez.rs/x4xx_limit").and_then(|v| v.parse().ok()),
sticky_sessions: annotations.get("aralez.rs/sticky_sessions").and_then(|v| v.parse().ok()),
client_headers: clh,
server_headers: srh,
};
return Some(annots);
}
None
}
pub async fn patch_ingress_status_address(
client: &Client,
namespace: &str,
name: &str,
current_status: Option<&k8s_openapi::api::networking::v1::IngressStatus>,
external_ip: &str,
) {
if let Some(status) = current_status {
if let Some(lb) = &status.load_balancer {
if let Some(ing_list) = &lb.ingress {
if ing_list.iter().any(|i| i.ip.as_deref() == Some(external_ip)) {
return;
}
}
}
}
let ingresses: Api<Ingress> = Api::namespaced(client.clone(), namespace);
let patch = json!({
"apiVersion": "networking.k8s.io/v1",
"kind": "Ingress",
"status": {
"loadBalancer": {
"ingress": [
{
"ip": external_ip
}
]
}
}
});
let params = PatchParams::apply("aralez-ingress-controller").force();
match ingresses.patch_status(name, &params, &Patch::Apply(patch)).await {
Ok(_) => info!("Patched Ingress {}/{} ADDRESS to {}", namespace, name, external_ip),
Err(e) => error!("Failed to patch status for Ingress {}/{}: {:?}", namespace, name, e),
}
}
pub async fn discover_node_ip(client: &Client) -> Option<String> {
let nodes: Api<Node> = Api::all(client.clone());
if let Ok(node_list) = nodes.list(&Default::default()).await {
for node in node_list {
if let Some(status) = node.status {
if let Some(addresses) = status.addresses {
for addr in &addresses {
if addr.type_ == "ExternalIP" {
return Some(addr.address.clone());
}
}
for addr in &addresses {
if addr.type_ == "InternalIP" {
return Some(addr.address.clone());
}
}
}
}
}
}
None
}
pub async fn discover_controller_ip(client: &Client, namespace: &str, service_name: &str) -> Option<String> {
let services: Api<Service> = Api::namespaced(client.clone(), namespace);
if let Ok(svc) = services.get(service_name).await {
if let Some(status) = svc.status {
if let Some(lb) = status.load_balancer {
if let Some(ingress_list) = lb.ingress {
for ing in ingress_list {
if let Some(ip) = ing.ip {
return Some(ip);
}
if let Some(hostname) = ing.hostname {
return Some(hostname);
}
}
}
}
}
}
None
}
+10
View File
@@ -1,11 +1,21 @@
use dashmap::DashMap; use dashmap::DashMap;
use moka::sync::Cache; use moka::sync::Cache;
use pingora_cache::eviction::simple_lru::Manager;
use pingora_cache::lock::CacheLock;
use pingora_cache::MemCache;
use pingora_limits::rate::Rate; use pingora_limits::rate::Rate;
use std::collections::HashMap;
use std::net::IpAddr; use std::net::IpAddr;
use std::sync::{Arc, LazyLock}; use std::sync::{Arc, LazyLock};
use std::sync::{OnceLock, RwLock};
use std::time::Duration; use std::time::Duration;
pub static REVERSE_STORE: LazyLock<DashMap<String, String>> = LazyLock::new(DashMap::new); pub 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 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 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")); pub static LOCALHOST: LazyLock<Arc<str>> = LazyLock::new(|| Arc::from("localhost"));
pub static MEM_CACHE: LazyLock<MemCache> = LazyLock::new(MemCache::new);
pub static CACHE_LOCK: LazyLock<CacheLock> = LazyLock::new(|| CacheLock::new(Duration::from_secs(30)));
pub static EVICTION: OnceLock<Manager> = OnceLock::new();
pub static CACHE_TTL: OnceLock<u64> = OnceLock::new();
pub static CHALLENGES: LazyLock<RwLock<HashMap<String, String>>> = LazyLock::new(|| RwLock::new(HashMap::new()));
+14
View File
@@ -1,3 +1,5 @@
use crate::utils::lazylock::EVICTION;
use pingora_cache::eviction::EvictionManager;
use pingora_http::Method; use pingora_http::Method;
use pingora_http::StatusCode; use pingora_http::StatusCode;
use pingora_http::Version; use pingora_http::Version;
@@ -20,6 +22,11 @@ pub static MEMORY_USAGE: LazyLock<IntGauge> = LazyLock::new(|| register_int_gaug
pub static ACTIVE_SESSIONS: LazyLock<IntGauge> = LazyLock::new(|| register_int_gauge!("aralez_active_sessions", "Current number of active sessions").unwrap()); pub static ACTIVE_SESSIONS: LazyLock<IntGauge> = LazyLock::new(|| register_int_gauge!("aralez_active_sessions", "Current number of active sessions").unwrap());
pub static REQUEST_COUNT: LazyLock<IntCounter> = LazyLock::new(|| register_int_counter!("aralez_requests_total", "Total number of requests handled by Aralez").unwrap()); pub static REQUEST_COUNT: LazyLock<IntCounter> = LazyLock::new(|| register_int_counter!("aralez_requests_total", "Total number of requests handled by Aralez").unwrap());
pub static CACHE_SIZE_BYTES: LazyLock<IntGauge> = LazyLock::new(|| register_int_gauge!("aralez_cache_size_bytes", "Current cache size in bytes").unwrap());
pub static CACHE_ITEMS: LazyLock<IntGauge> = LazyLock::new(|| register_int_gauge!("aralez_cache_items", "Current number of cached objects").unwrap());
pub static CACHE_EVICTED_BYTES: LazyLock<IntGauge> = LazyLock::new(|| register_int_gauge!("aralez_cache_evicted_bytes_total", "Total bytes evicted from cache").unwrap());
pub static CACHE_EVICTED_ITEMS: LazyLock<IntGauge> = LazyLock::new(|| register_int_gauge!("aralez_cache_evicted_items_total", "Total cache items evicted").unwrap());
pub static RESPONSE_CODES: LazyLock<IntCounterVec> = pub static RESPONSE_CODES: LazyLock<IntCounterVec> =
LazyLock::new(|| register_int_counter_vec!("aralez_responses_total", "Responses grouped by status code", &["status"]).unwrap()); LazyLock::new(|| register_int_counter_vec!("aralez_responses_total", "Responses grouped by status code", &["status"]).unwrap());
@@ -55,6 +62,13 @@ pub fn calc_metrics(metric_types: &MetricTypes) {
REQUESTS_BY_METHOD.with_label_values(&[metric_types.method.as_str()]).inc(); REQUESTS_BY_METHOD.with_label_values(&[metric_types.method.as_str()]).inc();
REQUESTS_BY_UPSTREAM.with_label_values(&[metric_types.upstream.as_ref()]).inc(); REQUESTS_BY_UPSTREAM.with_label_values(&[metric_types.upstream.as_ref()]).inc();
RESPONSE_LATENCY.observe(metric_types.latency.as_secs_f64()); RESPONSE_LATENCY.observe(metric_types.latency.as_secs_f64());
if let Some(eviction) = EVICTION.get() {
CACHE_SIZE_BYTES.set(eviction.total_size() as i64);
CACHE_ITEMS.set(eviction.total_items() as i64);
CACHE_EVICTED_BYTES.set(eviction.evicted_size() as i64);
CACHE_EVICTED_ITEMS.set(eviction.evicted_items() as i64);
}
} }
pub(crate) fn get_memory_usage() -> usize { pub(crate) fn get_memory_usage() -> usize {
-11
View File
@@ -16,14 +16,3 @@ pub fn is_first_run() -> bool {
let state = GLOBAL_STATE.read().unwrap(); let state = GLOBAL_STATE.read().unwrap();
state.first_run state.first_run
} }
/*
impl SharedState {
pub fn mark_first_run(&mut self) {
self.first_run = false;
}
pub fn is_first_run(&self) -> bool {
self.first_run
}
}
*/
+4 -3
View File
@@ -20,11 +20,12 @@ pub struct Extraparams {
#[derive(Debug, Default, Clone, Serialize, Deserialize)] #[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct GlobalServiceMapping { pub struct GlobalServiceMapping {
pub redirect_to: Option<String>,
pub authorization: Option<String>,
pub upstream: String, pub upstream: String,
pub hostname: String, pub hostname: String,
pub path: Option<String>, pub path: Option<String>,
pub to_https: Option<bool>, pub to_https: Option<bool>,
pub sticky_sessions: Option<u64>,
pub rate_limit: Option<isize>, pub rate_limit: Option<isize>,
pub x4xx_limit: Option<u32>, pub x4xx_limit: Option<u32>,
pub client_headers: Option<Vec<String>>, pub client_headers: Option<Vec<String>>,
@@ -34,14 +35,12 @@ pub struct GlobalServiceMapping {
#[derive(Clone, Default, Debug, Serialize, Deserialize)] #[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct Kubernetes { pub struct Kubernetes {
pub servers: Option<Vec<String>>, pub servers: Option<Vec<String>>,
pub services: Option<Vec<GlobalServiceMapping>>,
pub tokenpath: Option<String>, pub tokenpath: Option<String>,
} }
#[derive(Clone, Default, Debug, Serialize, Deserialize)] #[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct Consul { pub struct Consul {
pub servers: Option<Vec<String>>, pub servers: Option<Vec<String>>,
pub services: Option<Vec<GlobalServiceMapping>>,
pub token: Option<String>, pub token: Option<String>,
} }
#[derive(Debug, Default, Serialize, Deserialize)] #[derive(Debug, Default, Serialize, Deserialize)]
@@ -110,6 +109,8 @@ 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 cache_size_mb: Option<usize>,
pub cache_ttl: Option<u64>,
pub access_log: Option<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>,
+20
View File
@@ -377,3 +377,23 @@ pub fn write_pid_file(path: &str) -> std::io::Result<()> {
file.write_all(process::id().to_string().as_bytes())?; file.write_all(process::id().to_string().as_bytes())?;
Ok(()) Ok(())
} }
#[allow(clippy::needless_return)]
pub fn split_host_port(addr: &str, tls: bool) -> Option<(&str, u16, bool, &str)> {
match addr.split_once(':') {
Some((h, p)) => match p.parse::<u16>() {
Ok(port) => return Some((h, port, tls, h)),
Err(_) => {
log::warn!("ForwardAuth: invalid port in {}", addr);
return None;
}
},
None => {
if tls {
return Some((addr, 443u16, tls, addr));
} else {
return Some((addr, 80u16, tls, addr));
}
}
};
}
+1 -5
View File
@@ -1,5 +1,5 @@
use crate::tls::acme::order::CHALLENGES;
use crate::tls::acme::{account, order}; use crate::tls::acme::{account, order};
use crate::utils::lazylock::CHALLENGES;
use axum::body::Body; use axum::body::Body;
use axum::extract::State; use axum::extract::State;
use axum::http::{Response, StatusCode}; use axum::http::{Response, StatusCode};
@@ -44,10 +44,6 @@ pub async fn acme_order(State(state): State<crate::web::webserver::AppState>, ax
} }
pub async fn http01_challenge(axum::extract::Path(token): axum::extract::Path<String>) -> impl IntoResponse { pub async fn http01_challenge(axum::extract::Path(token): axum::extract::Path<String>) -> impl IntoResponse {
if let Ok(challenges) = CHALLENGES.read() { if let Ok(challenges) = CHALLENGES.read() {
// for k in challenges.iter() {
// println!(" ==> {} : {}", k.0, k.1);
// }
if let Some(key_authorization) = challenges.get(&token) { if let Some(key_authorization) = challenges.get(&token) {
return Response::builder() return Response::builder()
.status(StatusCode::OK) .status(StatusCode::OK)
+16 -3
View File
@@ -10,6 +10,7 @@ use log4rs::{
config::{Appender, Config as Log4rsConfig, Root}, config::{Appender, Config as Log4rsConfig, Root},
encode::pattern::PatternEncoder, encode::pattern::PatternEncoder,
}; };
use pingora_cache::CachePhase;
use pingora_http::Version; use pingora_http::Version;
use pingora_proxy::Session; use pingora_proxy::Session;
use std::net::{IpAddr, Ipv4Addr}; use std::net::{IpAddr, Ipv4Addr};
@@ -23,6 +24,7 @@ pub struct LogMessage {
pub client_ip: IpAddr, pub client_ip: IpAddr,
pub version: Version, pub version: Version,
pub user_agent: String, pub user_agent: String,
pub cache_status: CachePhase,
} }
static LOG_SENDER: OnceLock<mpsc::Sender<LogMessage>> = OnceLock::new(); static LOG_SENDER: OnceLock<mpsc::Sender<LogMessage>> = OnceLock::new();
static ACCESS_LOG: OnceLock<LogLevel> = OnceLock::new(); static ACCESS_LOG: OnceLock<LogLevel> = OnceLock::new();
@@ -126,13 +128,19 @@ pub fn access_log(response_code: u16, summary: &str, session: &Session) {
.unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)); .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("-"); let user_agent = session.req_header().headers.get("user-agent").and_then(|v| v.to_str().ok()).unwrap_or("-");
// let cache_status = match session.cache.phase() {
// CachePhase::Hit => "Hit",
// CachePhase::Miss => "Miss",
// CachePhase::Expired => "Expired",
// _ => "Disabled",
// };
let log = LogMessage { let log = LogMessage {
response_code, response_code,
summary: summary.to_owned(), summary: summary.to_owned(),
client_ip: ip, client_ip: ip,
version: session.req_header().version, version: session.req_header().version,
user_agent: user_agent.to_owned(), user_agent: user_agent.to_owned(),
cache_status: session.cache.phase(),
}; };
if let Some(sender) = LOG_SENDER.get() { if let Some(sender) = LOG_SENDER.get() {
@@ -156,8 +164,13 @@ pub fn init_logging(enabled: Option<String>) {
pub fn log_receiver(mut receiver: mpsc::Receiver<LogMessage>) { pub fn log_receiver(mut receiver: mpsc::Receiver<LogMessage>) {
while let Some(msg) = receiver.blocking_recv() { while let Some(msg) = receiver.blocking_recv() {
info!( info!(
"{}, {}, client: {}, version: {:?}, useragent: {}", "{}, {}, {}, client: {}, version: {:?}, useragent: {}",
msg.response_code, msg.summary, msg.client_ip, msg.version, msg.user_agent, msg.response_code,
msg.cache_status.as_str(),
msg.summary,
msg.client_ip,
msg.version,
msg.user_agent,
); );
} }
} }
+46 -8
View File
@@ -1,5 +1,5 @@
use crate::utils::auth::authenticate; use crate::utils::auth::authenticate;
use crate::utils::lazylock::{LOCALHOST, RATE_LIMITER, REQUESTS_4XX, REVERSE_STORE}; use crate::utils::lazylock::{CACHE_LOCK, CACHE_TTL, EVICTION, LOCALHOST, MEM_CACHE, RATE_LIMITER, REQUESTS_4XX, REVERSE_STORE};
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};
@@ -8,9 +8,11 @@ use arc_swap::ArcSwap;
use async_trait::async_trait; use async_trait::async_trait;
use axum::body::Bytes; use axum::body::Bytes;
use log::error; use log::error;
use pingora::http::{RequestHeader, ResponseHeader, StatusCode}; use pingora::http::{Method, RequestHeader, ResponseHeader, StatusCode};
use pingora::prelude::*; use pingora::prelude::*;
use pingora::ErrorSource::Upstream; use pingora::ErrorSource::Upstream;
use pingora_cache::cache_control::CacheControl;
use pingora_cache::{CacheKey, CacheMetaDefaults, RespCacheable};
use pingora_core::listeners::ALPN; use pingora_core::listeners::ALPN;
use pingora_core::prelude::HttpPeer; use pingora_core::prelude::HttpPeer;
use pingora_proxy::{ProxyHttp, Session}; use pingora_proxy::{ProxyHttp, Session};
@@ -18,10 +20,10 @@ use sha2::{Digest, Sha256};
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt::Write; use std::fmt::Write;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration;
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));}
#[derive(Clone)] #[derive(Clone)]
pub struct LB { pub struct LB {
pub ump_upst: Arc<UpstreamsDashMap>, pub ump_upst: Arc<UpstreamsDashMap>,
@@ -31,6 +33,7 @@ pub struct LB {
pub server_headers: Arc<Headers>, pub server_headers: Arc<Headers>,
pub config: Arc<AppConfig>, pub config: Arc<AppConfig>,
pub extraparams: Arc<ArcSwap<Extraparams>>, pub extraparams: Arc<ArcSwap<Extraparams>>,
pub cache_enabled: bool,
} }
pub struct Context { pub struct Context {
@@ -57,6 +60,34 @@ impl ProxyHttp for LB {
x4xx_limit: None, x4xx_limit: None,
} }
} }
fn cache_key_callback(&self, session: &Session, _ctx: &mut Self::CTX) -> Result<CacheKey> {
Ok(CacheKey::new(_ctx.hostname.clone().unwrap_or_default().as_ref(), session.req_header().uri.to_string(), ""))
}
fn request_cache_filter(&self, session: &mut Session, _ctx: &mut Self::CTX) -> Result<()> {
if self.cache_enabled && session.req_header().method == Method::GET {
if let Some(eviction) = EVICTION.get() {
session.cache.enable(&*MEM_CACHE, Some(eviction), None, Some(&*CACHE_LOCK), None);
}
}
Ok(())
}
fn response_cache_filter(&self, _session: &Session, resp: &ResponseHeader, _ctx: &mut Self::CTX) -> Result<RespCacheable> {
let cc = CacheControl::from_resp_headers(resp);
let defaults = CacheMetaDefaults::new(
|status| {
if status == StatusCode::OK {
Some(Duration::from_secs(*CACHE_TTL.get().unwrap_or(&60)))
} else {
None
}
},
0,
0,
);
Ok(pingora_cache::filters::resp_cacheable(cc.as_ref(), resp.clone(), false, &defaults))
}
async fn request_filter(&self, session: &mut Session, _ctx: &mut Self::CTX) -> Result<bool> { async fn request_filter(&self, session: &mut Session, _ctx: &mut Self::CTX) -> Result<bool> {
ACTIVE_SESSIONS.inc(); ACTIVE_SESSIONS.inc();
let hostname = return_header_host_from_upstream(session, &self.ump_upst); let hostname = return_header_host_from_upstream(session, &self.ump_upst);
@@ -159,8 +190,7 @@ impl ProxyHttp for LB {
Some(hostname) => match ctx.upstream_peer.as_ref() { Some(hostname) => match ctx.upstream_peer.as_ref() {
Some(innermap) => { Some(innermap) => {
let mut peer = Box::new(HttpPeer::new((&*innermap.address, innermap.port), innermap.is_ssl, hostname.to_string())); let mut peer = Box::new(HttpPeer::new((&*innermap.address, innermap.port), innermap.is_ssl, hostname.to_string()));
if innermap.is_http2 && !session.is_upgrade_req() {
if innermap.is_http2 {
peer.options.alpn = ALPN::H2; peer.options.alpn = ALPN::H2;
} }
if innermap.is_ssl { if innermap.is_ssl {
@@ -280,15 +310,16 @@ impl ProxyHttp for LB {
let _ = _upstream_response.append_header("set-cookie", buf.as_str()); let _ = _upstream_response.append_header("set-cookie", buf.as_str());
} }
} }
Ok(())
}
async fn upstream_response_filter(&self, _session: &mut Session, upstream_response: &mut ResponseHeader, ctx: &mut Self::CTX) -> Result<()> {
if let Some(client_headers) = &ctx.client_headers { if let Some(client_headers) = &ctx.client_headers {
for (k, v) in client_headers.iter() { for (k, v) in client_headers.iter() {
_upstream_response.append_header(k.clone(), v.as_ref())?; upstream_response.append_header(k.clone(), v.as_ref())?;
} }
} }
Ok(()) Ok(())
} }
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());
let m = &MetricTypes { let m = &MetricTypes {
@@ -308,6 +339,13 @@ impl ProxyHttp for LB {
} }
} }
} }
// match session.cache.phase() {
// CachePhase::Hit => info!("Cache Status: HIT (Served from Memory)"),
// CachePhase::Miss => info!("Cache Status: MISS (Fetched from Upstream)"),
// CachePhase::Expired => info!("Cache Status: EXPIRED (Revalidating)"),
// _ => {}
// }
access_log(response_code, &self.request_summary(session, ctx), session); access_log(response_code, &self.request_summary(session, ctx), session);
} }
} }
+10
View File
@@ -2,6 +2,7 @@
use crate::tls::grades; use crate::tls::grades;
use crate::tls::load; use crate::tls::load;
use crate::tls::load::CertificateConfig; use crate::tls::load::CertificateConfig;
use crate::utils::lazylock::{CACHE_TTL, EVICTION};
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::logging::init_access_log;
@@ -10,6 +11,7 @@ use arc_swap::ArcSwap;
use dashmap::DashMap; use dashmap::DashMap;
use log::info; use log::info;
use pingora::tls::ssl::{SslAlert, SslRef}; use pingora::tls::ssl::{SslAlert, SslRef};
use pingora_cache::eviction::simple_lru::Manager;
use pingora_core::listeners::tls::TlsSettings; use pingora_core::listeners::tls::TlsSettings;
use pingora_core::listeners::TcpSocketOptions; use pingora_core::listeners::TcpSocketOptions;
use pingora_core::prelude::{background_service, Opt}; use pingora_core::prelude::{background_service, Opt};
@@ -32,6 +34,13 @@ pub fn run() {
let file = parameters.conf.clone().unwrap(); let file = parameters.conf.clone().unwrap();
let maincfg = crate::utils::parceyaml::parce_main_config(file.as_str()); let maincfg = crate::utils::parceyaml::parce_main_config(file.as_str());
let cache_enabled = maincfg.cache_size_mb.is_some();
CACHE_TTL.set(maincfg.cache_ttl.unwrap_or(60)).unwrap();
if let Some(size) = maincfg.cache_size_mb {
let cache_size = size * 1024 * 1024;
drop(EVICTION.set(Manager::new(cache_size)));
}
let mut server = Server::new(parameters).unwrap(); let mut server = Server::new(parameters).unwrap();
server.bootstrap(); server.bootstrap();
@@ -59,6 +68,7 @@ pub fn run() {
client_headers: ch_config, client_headers: ch_config,
server_headers: sh_config, server_headers: sh_config,
extraparams: ec_config, extraparams: ec_config,
cache_enabled: cache_enabled,
}; };
let al = cfg.access_log.clone().unwrap_or("none".to_string()); let al = cfg.access_log.clone().unwrap_or("none".to_string());
init_access_log(al.as_str()); init_access_log(al.as_str());