55 Commits

Author SHA1 Message Date
Ara Sadoyan
162c5060c9 cleanup, minor fix 2026-06-10 11:46:30 +02:00
Ara Sadoyan
132cf45dfe cleanup, minor fix 2026-06-10 11:29:35 +02:00
Ara Sadoyan
77dcafbb4e tcp_keepalive options for kernel timer 2026-06-09 17:53:04 +02:00
Ara Sadoyan
649bd979f7 socker address #41 2026-06-07 12:07:06 +02:00
Ara Sadoyan
bd315106b9 Merge pull request #41 from Taqman-probe/fix/TcpListener-bind
Fix TcpListener binding and port availability check
2026-06-07 11:57:06 +02:00
Ara Sadoyan
3ba2ed33ae cargo.toml 2026-06-07 11:38:50 +02:00
Taqman-probe
c09efab9fd prevent panic when parsing invalid address without port 2026-06-07 00:47:43 +09:00
Taqman-probe
735d605f6d prevent panic when parsing invalid address without port 2026-06-06 22:32:32 +09:00
Ara Sadoyan
6773d0f502 Cargo 2026-06-04 19:12:50 +02:00
Ara Sadoyan
a8cb727da5 Cargo Licence 2026-06-04 18:39:02 +02:00
Ara Sadoyan
f81194aee7 Cargo Licence 2026-06-04 18:38:47 +02:00
Ara Sadoyan
0f09a2e02b Roll back to MiMalloc 2026-06-04 18:14:22 +02:00
Ara Sadoyan
27aca0a3a5 Roll back to MiMalloc 2026-06-04 17:03:19 +02:00
Ara Sadoyan
3c99ed0c44 Fixed #40 2026-06-03 16:37:56 +02:00
Ara Sadoyan
4a6e1d817b OS signals , PID file 2026-06-02 19:56:08 +02:00
Ara Sadoyan
f6a2df44d0 README 2026-06-02 12:20:18 +02:00
Ara Sadoyan
e8d976f5f1 A minor issue with file loader 2026-06-01 15:35:49 +02:00
Ara Sadoyan
a1341e90f7 Merge branch 'Taqman-probe-fix/mismatch-file-watch' into dev 2026-06-01 14:39:44 +02:00
Ara Sadoyan
ddcacc509c Merge branch 'fix/mismatch-file-watch' of github.com:Taqman-probe/aralez into Taqman-probe-fix/mismatch-file-watch 2026-06-01 14:39:06 +02:00
Ara Sadoyan
2b385cc630 gitignore 2026-06-01 14:29:50 +02:00
Ara Sadoyan
1b5223986d README, signals 2026-06-01 14:25:36 +02:00
Ara Sadoyan
ac2bd8113d README update 2026-06-01 11:48:54 +02:00
Ara Sadoyan
4d9a2ecfe3 Honoring OS signals 2026-05-30 18:53:36 +02:00
Taqman-probe
aa70d818b2 Fix config file change detection logic in file watcher 2026-05-30 00:34:35 +09:00
Ara Sadoyan
15d356f087 Removed Dockerfile, put content to README 2026-05-27 19:23:32 +02:00
Ara Sadoyan
b17962e6d5 Merge pull request #31 from Taqman-probe/fix/rate-limit-setting-message
fix: Fix global rate limit and 4xx limit fallback in upstream config log
2026-05-27 16:28:33 +02:00
Ara Sadoyan
ea779182f2 Include request path in rate limit and 4xx limit warning logs
#32
2026-05-27 16:15:55 +02:00
Ara Sadoyan
9339a142a6 Merge pull request #33 from Taqman-probe/fix/skip-tls-detection-when-hc-disabled
Skip TLS detection when healthcheck: false
2026-05-27 15:09:12 +02:00
Ara Sadoyan
cfc9bd319d Merge branch 'dev' 2026-05-27 14:58:56 +02:00
Ara Sadoyan
a5ced59e5c Merge branch 'main' of github.com:sadoyan/aralez 2026-05-27 14:58:33 +02:00
Ara Sadoyan
c3df7bc131 gitignore 2026-05-27 14:57:34 +02:00
Ara Sadoyan
0248d73836 Merge pull request #24 from gzsombor/forwarded-for-header
X-Forwarded-For should only contain the IP address
2026-05-27 14:55:51 +02:00
Ara Sadoyan
96a5aef7d0 warning log on retry parce 2026-05-27 14:54:21 +02:00
Ara Sadoyan
5c3b72b7a3 parceyaml 2026-05-27 14:26:05 +02:00
Ara Sadoyan
3cf0fc493f Merge pull request #34 from Taqman-probe/fix/failed-hot-reload-config
Add retry mechanism for configuration parsing failures
2026-05-27 14:18:28 +02:00
Ara Sadoyan
207ee481fb update 2026-05-27 14:12:10 +02:00
Ara Sadoyan
1c2f7327aa gitignore 2026-05-27 14:03:55 +02:00
Ara Sadoyan
310a554a25 gitignore 2026-05-27 14:03:22 +02:00
Ara Sadoyan
69a5167346 Merge pull request #35 from sadoyan/dev
Dev
2026-05-27 13:56:10 +02:00
Ara Sadoyan
4734ccab2f Minor fixed #26 & #28 2026-05-27 13:40:22 +02:00
Taqman-probe
61d65f6e4e Failed hot reloading of config file 2026-05-27 18:36:00 +09:00
Taqman-probe
20ac39067d Skip TLS detection when healthcheck: false 2026-05-27 16:44:17 +09:00
Taqman-probe
7afa76de8f fix: Fix global rate limit fallback in upstream config log 2026-05-27 14:22:59 +09:00
Ara Sadoyan
e29161965f Added more monitoring metrics 2026-05-26 19:34:10 +02:00
Zsombor Gegesy
9216710dda X-Forwarded-For should only contain the IP address
https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For
2026-05-23 07:11:55 +02:00
Ara Sadoyan
faf840d102 Merge pull request #23 from sadoyan/dev
New features, 4xx counter
2026-05-22 16:57:07 +02:00
Ara Sadoyan
d74883e16e New features, 4xx counter 2026-05-22 16:56:33 +02:00
Ara Sadoyan
d95bbfcd1a Merge pull request #22 from sadoyan/dev
- Changed sticky session value from bool to u32, corresponding to `Max-Age=` of cookie
- Added counter and rate limiter for 4xx requests 
- Changed config for JWT authorization. The `data`  property is removed.
2026-05-22 16:52:27 +02:00
Ara Sadoyan
d301f7225f New features, 4xx counter 2026-05-22 16:47:40 +02:00
Ara Sadoyan
df02e523e4 cleanups 2026-05-21 18:34:46 +02:00
Ara Sadoyan
2f5def5c3c Changed sticky session from bool to Option<u64> 2026-05-20 21:09:23 +02:00
Ara Sadoyan
1727a2b5e7 deleted some comments 2026-05-20 17:17:04 +02:00
Ara Sadoyan
4bbedee27b JWT auth read and caches KEY from system env. 2026-05-19 15:26:05 +02:00
Ara Sadoyan
37ef118861 Dockerfile #21 2026-05-19 11:48:12 +02:00
Ara Sadoyan
00062b00da Removed authentication from API server, JWT master key as environment variable 2026-05-18 20:38:30 +02:00
26 changed files with 783 additions and 518 deletions

7
.gitignore vendored
View File

@@ -7,10 +7,15 @@
*.sh *.sh
/docs/ /docs/
/docs /docs
/etc
/etc/
etc
.etc/
assets/
assets
/target/ /target/
*.iml *.iml
.idea/ .idea/
.etc/
*.ipr *.ipr
*.iws *.iws
/out/ /out/

125
Cargo.lock generated
View File

@@ -127,7 +127,7 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]] [[package]]
name = "aralez" name = "aralez"
version = "0.9.2" version = "0.92.10"
dependencies = [ dependencies = [
"ahash", "ahash",
"arc-swap", "arc-swap",
@@ -135,7 +135,6 @@ dependencies = [
"axum", "axum",
"base16ct 1.0.0", "base16ct 1.0.0",
"base64", "base64",
"ctrlc",
"dashmap", "dashmap",
"futures", "futures",
"instant-acme", "instant-acme",
@@ -156,10 +155,12 @@ dependencies = [
"rcgen", "rcgen",
"reqwest", "reqwest",
"rustls-pemfile", "rustls-pemfile",
"sd-notify",
"serde", "serde",
"serde_json", "serde_json",
"serde_yml", "serde_yml",
"sha2 0.11.0", "sha2 0.11.0",
"signal-hook",
"subtle", "subtle",
"tokio", "tokio",
"tonic", "tonic",
@@ -359,6 +360,15 @@ version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]]
name = "bit-vec"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@@ -398,15 +408,6 @@ dependencies = [
"hybrid-array", "hybrid-array",
] ]
[[package]]
name = "block2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
dependencies = [
"objc2",
]
[[package]] [[package]]
name = "brotli" name = "brotli"
version = "3.5.0" version = "3.5.0"
@@ -719,17 +720,6 @@ dependencies = [
"hybrid-array", "hybrid-array",
] ]
[[package]]
name = "ctrlc"
version = "3.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162"
dependencies = [
"dispatch2",
"nix 0.31.2",
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "curve25519-dalek" name = "curve25519-dalek"
version = "4.1.3" version = "4.1.3"
@@ -956,18 +946,6 @@ dependencies = [
"crypto-common 0.2.1", "crypto-common 0.2.1",
] ]
[[package]]
name = "dispatch2"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [
"bitflags 2.11.1",
"block2",
"libc",
"objc2",
]
[[package]] [[package]]
name = "displaydoc" name = "displaydoc"
version = "0.2.5" version = "0.2.5"
@@ -1940,9 +1918,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]] [[package]]
name = "libmimalloc-sys" name = "libmimalloc-sys"
version = "0.1.47" version = "0.1.49"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d1eacfa31c33ec25e873c136ba5669f00f9866d0688bea7be4d3f7e43067df6" checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9"
dependencies = [ dependencies = [
"cc", "cc",
] ]
@@ -1995,9 +1973,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.29" version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
dependencies = [ dependencies = [
"serde_core", "serde_core",
] ]
@@ -2075,9 +2053,9 @@ dependencies = [
[[package]] [[package]]
name = "mimalloc" name = "mimalloc"
version = "0.1.50" version = "0.1.52"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3627c4272df786b9260cabaa46aec1d59c93ede723d4c3ef646c503816b0640" checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862"
dependencies = [ dependencies = [
"libmimalloc-sys", "libmimalloc-sys",
] ]
@@ -2202,18 +2180,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "nix"
version = "0.31.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3"
dependencies = [
"bitflags 2.11.1",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]] [[package]]
name = "nom" name = "nom"
version = "7.1.3" version = "7.1.3"
@@ -2315,15 +2281,6 @@ dependencies = [
"libm", "libm",
] ]
[[package]]
name = "objc2"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
dependencies = [
"objc2-encode",
]
[[package]] [[package]]
name = "objc2-core-foundation" name = "objc2-core-foundation"
version = "0.3.2" version = "0.3.2"
@@ -2343,12 +2300,6 @@ dependencies = [
"objc2-core-foundation", "objc2-core-foundation",
] ]
[[package]]
name = "objc2-encode"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]] [[package]]
name = "object" name = "object"
version = "0.37.3" version = "0.37.3"
@@ -3147,9 +3098,9 @@ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
[[package]] [[package]]
name = "rcgen" name = "rcgen"
version = "0.14.7" version = "0.14.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055"
dependencies = [ dependencies = [
"aws-lc-rs", "aws-lc-rs",
"pem", "pem",
@@ -3200,9 +3151,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.13.3" version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes",
@@ -3465,6 +3416,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sd-notify"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e4ef7359e694bfaf1dd27a30f9d760b54c00dfae9f19bd0c05a39bc9128fe76"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "sec1" name = "sec1"
version = "0.7.3" version = "0.7.3"
@@ -3550,9 +3510,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.149" version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@@ -3651,6 +3611,16 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.8" version = "1.4.8"
@@ -4111,9 +4081,9 @@ dependencies = [
[[package]] [[package]]
name = "tower-http" name = "tower-http"
version = "0.6.10" version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [ dependencies = [
"bitflags 2.11.1", "bitflags 2.11.1",
"bytes", "bytes",
@@ -4891,10 +4861,11 @@ checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3"
[[package]] [[package]]
name = "yasna" name = "yasna"
version = "0.5.2" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282"
dependencies = [ dependencies = [
"bit-vec",
"time", "time",
] ]

View File

@@ -1,7 +1,11 @@
[package] [package]
name = "aralez" name = "aralez"
version = "0.9.2" version = "0.92.10"
edition = "2021" edition = "2021"
license = "Apache-2.0"
description = "Reverse proxy built on top of Cloudflare's Pingora"
exclude = ["etc/*"]
repository = "https://github.com/sadoyan/aralez"
[profile.release] [profile.release]
opt-level = 3 opt-level = 3
@@ -20,11 +24,11 @@ pingora-proxy = "0.8.0"
pingora-http = "0.8.0" pingora-http = "0.8.0"
pingora-limits = "0.8.0" pingora-limits = "0.8.0"
async-trait = "0.1.89" async-trait = "0.1.89"
log = "0.4.29" log = "0.4.30"
futures = "0.3.32" futures = "0.3.32"
notify = "9.0.0-rc.4" notify = "9.0.0-rc.4"
axum = { version = "0.8.9" } axum = { version = "0.8.9" }
reqwest = { version = "0.13.3", features = ["json", "stream", "blocking"] } reqwest = { version = "0.13.4", features = ["json", "stream", "blocking"] }
serde_yml = "0.0.12" serde_yml = "0.0.12"
rand = "0.10.1" rand = "0.10.1"
base64 = "0.22.1" base64 = "0.22.1"
@@ -34,17 +38,18 @@ 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"
arc-swap = "1.9.1" arc-swap = "1.9.1"
mimalloc = { version = "0.1.50", default-features = false }
prometheus = "0.14.0" prometheus = "0.14.0"
x509-parser = "0.18.1" x509-parser = "0.18.1"
rustls-pemfile = "2.2.0" rustls-pemfile = "2.2.0"
tower-http = { version = "0.6.10", features = ["fs"] } tower-http = { version = "0.6.11", features = ["fs"] }
privdrop = "0.5.6" privdrop = "0.5.6"
ctrlc = "3.5.2" serde_json = "1.0.150"
serde_json = "1.0.149"
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"
instant-acme = "0.8.5" instant-acme = "0.8.5"
rcgen = "0.14.7" rcgen = "0.14.8"
log4rs = "1.4.0" log4rs = "1.4.0"
mimalloc = { version = "0.1.52", default-features = false }
signal-hook = "0.4.4"
sd-notify = "0.5.0"

106
README.md
View File

@@ -9,13 +9,23 @@
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 lightweight ingress-style proxy. featuring Consul and Kubernetes integration for dynamic pod discovery and health-checked routing, acting as a 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 **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) ---
## Links
- [**Documentation**](https://aralez.rs) : The manual you should read
- [**Downloads**](https://github.com/sadoyan/aralez/releases) : Binary downloads
- [**Issues**](https://github.com/sadoyan/aralez/issues) : Issues and requests
- [**Crates**](https://crates.io/crates/aralez) : The Rust crate registry
- [**DockerHUB**](https://hub.docker.com/r/sadoyan/aralez) : DockerHUB official repository
- [**GitHUB Packages**](https://github.com/sadoyan/aralez/pkgs/container/aralez) : GitHUB ghcr.io images
--- ---
@@ -51,30 +61,26 @@ Built on Rust, on top of **Cloudflares Pingora engine**, **Aralez** delivers
### `main.yaml` ### `main.yaml`
| Key | Example Value | Description | | Key | Example Value | Description |
|----------------------------------|--------------------------|----------------------------------------------------------------------------------------------------| |----------------------------------|----------------------------|-------------------------------------------------------------------------------------------------|
| **threads** | 12 | Number of running daemon threads. Optional, defaults to 1 | | **threads** | 12 | Number of running daemon threads. Optional, defaults to 1 |
| **runuser** | aralez | Optional, Username for running aralez after dropping root privileges, requires to launch as root | | **runuser** | aralez | Optional. Username for running aralez after dropping root privileges (requires launch as root) |
| **rungroup** | aralez | Optional,Group for running aralez after dropping root privileges, requires to launch as root | | **rungroup** | aralez | Optional. Group for running aralez after dropping root privileges (requires launch as root) |
| **daemon** | false | Run in background (boolean) | | **daemon** | false | Run in background (boolean) |
| **upstream_keepalive_pool_size** | 500 | Pool size for upstream keepalive connections | | **upstream_keepalive_pool_size** | 500 | Pool size for upstream keepalive connections |
| **pid_file** | /tmp/aralez.pid | Path to PID file | | **pid_file** | /tmp/aralez.pid | Path to PID file |
| **error_log** | /tmp/aralez_err.log | Path to error log file |
| **upgrade_sock** | /tmp/aralez.sock | Path to live upgrade socket file |
| **config_address** | 0.0.0.0:3000 | HTTP API address for pushing upstreams.yaml from remote location | | **config_address** | 0.0.0.0:3000 | HTTP API address for pushing upstreams.yaml from remote location |
| **proxy_tls_grade** | (high, medium, unsafe) | Grade of TLS ciphers, for easy configuration. High matches Qualys SSL Labs A+ (defaults to medium) | | **proxy_tls_grade** | high, medium, unsafe | Grade of TLS ciphers. `high` matches Qualys SSL Labs A+ (defaults to `medium`) |
| **config_tls_key_file** | etc/key.pem | Private Key file path. Mandatory if proxy_address_tls is set, else optional |
| **proxy_address_http** | 0.0.0.0:6193 | Aralez HTTP bind address | | **proxy_address_http** | 0.0.0.0:6193 | Aralez HTTP bind address |
| **proxy_address_tls** | 0.0.0.0:6194 | Aralez HTTPS bind address (Optional) | | **proxy_address_tls** | 0.0.0.0:6194 | Aralez HTTPS bind address (Optional) |
| **proxy_configs** | etc/ | The top directory of config files | | **proxy_configs** | /etc/aralez/ | Direcotry containing configuration files, must be writeable by user `aralez` |
| **upstreams_conf** | etc/upstreams.yaml | The location of upstreams file | | **upstreams_conf** | /etc/aralez/upstreams.yaml | Location of the upstreams file |
| **log_level** | info | Log level , possible values : info, warn, error, debug, trace, off | | **log_level** | info | Log level: `info`, `warn`, `error`, `debug`, `trace`, `off` |
| **log_file** | /full/path/to/aralez.log | Optional, the location of log file. If thi entry does not exist logs will be emitted to stdout. | | **log_file** | /full/path/to/aralez.log | Optional, the location of log file. If thi entry does not exist logs will be emitted to stdout. |
| **hc_method** | HEAD | Healthcheck method (HEAD, GET, POST are supported) UPPERCASE | | **hc_method** | HEAD | Healthcheck method: HEAD, GET, POST (UPPERCASE) |
| **hc_interval** | 2 | Interval for health checks in seconds | | **hc_interval** | 2 | Interval for health checks in seconds |
| **master_key** | Random long string | Master key for working with API server and JWT Secret generation | | **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. Can set as upstream for public access | | **config_api_enabled** | true | Enable/disable remote config push capability |
| **config_api_enabled** | true | Boolean to enable/disable remote config push capability |
--- ---
@@ -110,11 +116,28 @@ For getting the best performance on newer hardware use `aralez-x86_64-*.gz`.
**Via docker** **Via docker**
```shell ```shell
docker run -d \ docker run -d -v /path/to/config:/etc/aralez:rw -p 80:80 -p 443:443 sadoyan/aralez
-v /local/path/to/config:/etc/aralez:ro \ docker run -d -v /path/to/config:/etc/aralez:rw -p 80:80 -p 443:443 sadoyan/aralez:compat
-p 80:80 \ docker run -d -v /path/to/config:/etc/aralez:rw -p 80:80 -p 443:443 ghcr.io/sadoyan/aralez:latest
-p 443:443 \ docker run -d -v /path/to/config:/etc/aralez:rw -p 80:80 -p 443:443 ghcr.io/sadoyan/aralez:compat
sadoyan/aralez ```
**Dockerfile :**
```dockerfile
FROM debian:trixie-slim
RUN apt-get update && apt-get install -y ca-certificates curl net-tools iputils-ping
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
COPY aralez /usr/local/bin/aralez
RUN chmod +x /usr/local/bin/aralez
RUN mkdir -p /etc/aralez/certs/upstreams
WORKDIR /etc/aralez
ENTRYPOINT ["/usr/local/bin/aralez", "-c", "/etc/aralez/main.yaml"]
``` ```
## Running the Proxy ## Running the Proxy
@@ -125,7 +148,7 @@ docker run -d \
## Systemd integration ## Systemd integration
Assuming Arales in installed in `/opt/aralez` folder Assuming Aralez in installed in `/opt/aralez` folder
```bash ```bash
cat > /etc/systemd/system/aralez.service <<EOF cat > /etc/systemd/system/aralez.service <<EOF
@@ -164,9 +187,10 @@ systemctl restart aralez.service.
```yaml ```yaml
provider: "file" provider: "file"
sticky_sessions: false sticky_sessions: 8600
to_https: false to_https: false
rate_limit: 10 rate_limit: 20
x4xx_limit: 20
server_headers: server_headers:
- "X-Forwarded-Proto:https" - "X-Forwarded-Proto:https"
- "X-Forwarded-Port:443" - "X-Forwarded-Port:443"
@@ -177,7 +201,8 @@ client_headers:
myhost.mydomain.com: myhost.mydomain.com:
paths: paths:
"/": "/":
rate_limit: 20 rate_limit: 10
x4xx_limit: 10
to_https: false to_https: false
server_headers: server_headers:
- "X-Something-Else:Foobar" - "X-Something-Else:Foobar"
@@ -211,15 +236,20 @@ DEFAULT:
**This means:** **This means:**
- Sticky sessions are disabled globally. This setting applies to all upstreams. If enabled all requests will be 301 redirected to HTTPS. - Sticky sessions are enabled globally. This setting applies to all upstreams. If enabled the value will be set for `Max-Age=` cookie.
- HTTP to HTTPS redirect disabled globally, but can be overridden by `to_https` setting per upstream. - HTTP to HTTPS redirect disabled globally, but can be overridden by `to_https` setting per upstream.
- All upstreams will receive custom headers : `X-Forwarded-Proto:https` and `X-Forwarded-Port:443` - All upstreams will receive custom headers : `X-Forwarded-Proto:https` and `X-Forwarded-Port:443`
- Additionally, myhost.mydomain.com with path `/` will receive custom headers : `X-Another-Header:Hohohohoho` and `X-Something-Else:Foobar` - Additionally, myhost.mydomain.com with path `/` will receive custom headers : `X-Another-Header:Hohohohoho` and `X-Something-Else:Foobar`
- Requests to each hosted domains will be limited to 10 requests per second per virtualhost. - Requests with response 4xx to each hosted domains will be limited to 20 requests per second per virtualhost.
- Requests limits are calculated per requester ip plus requested virtualhost.
- If the requester exceeds the limit it will receive `429 Too Many Requests` error.
- Optional. Rate limiter will be disabled if the parameter is entirely removed from config.
- Requests to each hosted domains will be limited to 20 requests per second per virtualhost.
- Requests limits are calculated per requester ip plus requested virtualhost. - Requests limits are calculated per requester ip plus requested virtualhost.
- If the requester exceeds the limit it will receive `429 Too Many Requests` error. - If the requester exceeds the limit it will receive `429 Too Many Requests` error.
- Optional. Rate limiter will be disabled if the parameter is entirely removed from config. - Optional. Rate limiter will be disabled if the parameter is entirely removed from config.
- Requests to `myhost.mydomain.com/` will be limited to 20 requests per second. - Requests to `myhost.mydomain.com/` will be limited to 20 requests per second.
- Requests with 4xx responses to `myhost.mydomain.com/` will be limited to 10 requests per second.
- 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.
- `myhost.mydomain.com/foo` will require authentication with JWT token, signed by `266463d1-210a-4787-9a81-4aacb37a8723`. - `myhost.mydomain.com/foo` will require authentication with JWT token, signed by `266463d1-210a-4787-9a81-4aacb37a8723`.
@@ -231,10 +261,8 @@ DEFAULT:
- Global headers (CORS for this case) will be injected to all upstreams. - Global headers (CORS for this case) will be injected to all upstreams.
- Additional headers will be injected into the request for `myhost.mydomain.com`. - Additional headers will be injected into the request for `myhost.mydomain.com`.
- You can choose any path, deep nested paths are supported, the best match chosen. - You can choose any path, deep nested paths are supported, the best match chosen.
- All requests to servers will require JWT token authentication (You can comment out the authorization to disable it),
- Firs parameter specifies the mechanism of authorisation `jwt`
- Second is the secret key for validating `jwt` tokens
- `DEFAULT` catch up everything else and proxy to `127.0.0.1:3000` - `DEFAULT` catch up everything else and proxy to `127.0.0.1:3000`
- This is a special upstream and in order to do the catch-up jub it must be **DEFAULT** all capitals
--- ---
@@ -250,11 +278,20 @@ DEFAULT:
To enable TLS for the proxy server. To enable TLS for the proxy server.
- Set `proxy_address_tls` in `main.yaml` - Set `proxy_address_tls` in `main.yaml`
- Provide at least on `tls_certificate/tls_key_file` pair. - Provide at least one `tls_certificate/tls_key_file` pair.
- First pair is required to create the TLS listener. - First pair is required to create the TLS listener.
- This pair can be anything, even self-signed with dummy domain. - This pair can be anything, even self-signed with dummy domain.
- After getting normal certificate it can be deleted - After getting normal certificate it can be deleted
```shell
mkdir -p /etc/aralez/certificates
chown -R aralez:aralez /etc/aralez
cd /etc/aralez/certificates
openssl req -x509 -newkey rsa:4096 \
-keyout dummy.key -out dummy.crt -sha256 -days 3650 -nodes \
-subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=CommonNameOrHostname"
```
--- ---
## Remote Config API ## Remote Config API
@@ -530,10 +567,3 @@ The results show requests per second performed by Load balancer. You can see 3 b
1. Requests via http1.1 to plain text endpoint. 1. Requests via http1.1 to plain text endpoint.
2. Requests to via http2 to SSL endpoint. 2. Requests to via http2 to SSL endpoint.
3. Mixed workload with plain http1.1 and htt2 SSL. 3. Mixed workload with plain http1.1 and htt2 SSL.
## Links
- [**Documentation**](https://aralez.rs) : The manual you should read
- [**Downloads**](https://github.com/sadoyan/aralez/releases) : Binary downloads
- [**Issues**](https://github.com/sadoyan/aralez/issues) : Issues and requests

View File

@@ -1,22 +1,23 @@
# Main configuration file, applied on startup # Main configuration file, applied on startup
threads: 12 # Number of daemon threads default setting threads: 12 # Number of daemon threads default setting
runuser: aralez # Username for running aralez after dropping root privileges, requires program to start as root #runuser: pastor # Username for running aralez after dropping root privileges, requires program to start as root
rungroup: aralez # Group for running aralez after dropping root privileges, requires program to start as root #rungroup: pastor # Group for running aralez after dropping root privileges, requires program to start as root
daemon: false # Run in background #daemon: false # Run in background
upstream_keepalive_pool_size: 500 # Pool size for upstream keepalive connections upstream_keepalive_pool_size: 500 # Pool size for upstream keepalive connections
pid_file: /tmp/aralez.pid # Path to PID file #pid_file: /tmp/aralez.pid # Path to PID file
error_log: /tmp/aralez_err.log # Path to error log #error_log: /tmp/aralez_err.log # Path to error log
upgrade_sock: /tmp/aralez.sock # Path to socket file upgrade_sock: /tmp/aralez.sock # Path to socket file
config_api_enabled: true # Boolean to enable/disable remote config push capability. config_api_enabled: true # Boolean to enable/disable remote config push capability.
config_address: 0.0.0.0:3000 # HTTP API address for pushing upstreams.yaml from remote location config_address: 0.0.0.0+3000 # HTTP API address for pushing upstreams.yaml from remote location
proxy_address_http: 0.0.0.0:6193 # Proxy HTTP bind address proxy_address_http: 0.0.0.0:6193 # Proxy HTTP bind address
proxy_address_tls: 0.0.0.0:6194 # Optional, Proxy TLS bind address proxy_address_tls: 0.0.0.0:6194 # Optional, Proxy TLS bind address
proxy_configs: /opt/aralez/etc # Mandatory if proxy_address_tls set, should contain a certificate and key files strictly in a format {NAME}.crt, {NAME}.key. proxy_configs: /opt/Rust/Projects/asyncweb/etc # Mandatory if proxy_address_tls set, should contain a certificate and key files strictly in a format {NAME}.crt, {NAME}.key.
proxy_tls_grade: a+ # Grade of TLS suite for proxy (a+, a, b, c, unsafe), matching grades of Qualys SSL Labs proxy_tls_grade: high # Grade of TLS suite for proxy (high, medium, unsafe), matching grades of Qualys SSL Labs
upstreams_conf: /opt/aralez/etc/upstreams.yaml # the location of upstreams file upstreams_conf: /opt/Rust/Projects/asyncweb/etc/upstreams.yaml # the location of upstreams file
file_server_folder: /opt/storage # Optional, local folder to serve file_server_folder: /tmp/gazan # Optional, local folder to serve
file_server_address: 127.0.0.1:3002 # Optional, Local address for file server. Can set as upstream for public access. file_server_address: 127.0.0.1:3002 # Optional, Local address for file server. Can set as upstream for public access.
log_level: info # info, warn, error, debug, trace, off log_level: info # info, warn, error, debug, trace, off
#log_file: /tmp/aralez.log # Optional, the location of log file. If this entry does not exist logs will be emitted to stdout.
hc_method: HEAD # Healthcheck method (HEAD, GET, POST are supported) UPPERCASE hc_method: HEAD # Healthcheck method (HEAD, GET, POST are supported) UPPERCASE
hc_interval: 2 #Interval for health checks in seconds hc_interval: 2 #Interval for health checks in seconds
master_key: 910517d9-f9a1-48de-8826-dbadacbd84af-cb6f830e-ab16-47ec-9d8f-0090de732774 # Mater key for working with API server and JWT Secret #master_key: 910517d9-f9a1-48de-8826-dbadacbd84af-cb6f830e-ab16-47ec-9d8f-0090de732774 # Mater key for working with API server and JWT Secret

View File

@@ -1,48 +1,47 @@
# The file under watch and hot reload, changes are applied immediately, no need to restart or reload. # The file under watch and hot reload, changes are applied immediately, no need to restart or reload.
provider: "file" # "file" "consul" "kubernetes" provider: "file" # "file" "consul" "kubernetes"
sticky_sessions: false sticky_sessions: 172000
to_https: false to_https: false
rate_limit: 100 rate_limit: 500000
server_headers: x4xx_limit: 100000
- "X-Forwarded-Proto:https" #server_headers:
- "X-Forwarded-Port:443" # - "Y-Global-Something: Yes this is something"
client_headers: #client_headers:
- "Access-Control-Allow-Origin:*" # - "Access-Control-Allow-Origin:*"
- "Access-Control-Allow-Methods:POST, GET, OPTIONS" # - "Access-Control-Allow-Methods:POST, GET, OPTIONS"
- "Access-Control-Max-Age:86400" # - "Access-Control-Max-Age:86400"
# - "Strict-Transport-Security:max-age=31536000; includeSubDomains; preload"
#authorization: #authorization:
# type: "jwt"
# creds: "910517d9-f9a1-48de-8826-dbadacbd84af-cb6f830e-ab16-47ec-9d8f-0090de732774"
# type: "basic" # type: "basic"
# creds: "username:Pa$$w0rd" # data: "root:toor"
# type: "jwt"
# data: "910517d9-f9a1-48de-8826-dbadacbd84af-cb6f830e-ab16-47ec-9d8f-0090de732774"
# type: "apikey" # type: "apikey"
# creds: "5ecbf799-1343-4e94-a9b5-e278af5cd313-56b45249-1839-4008-a450-a60dc76d2bae" # data: "5ecbf799-1343-4e94-a9b5-e278af5cd313-56b45249-1839-4008-a450-a60dc76d2bae"
consul: consul:
servers: servers:
- "http://192.168.1.199:8500" - "http://consul1:8500"
- "http://192.168.1.200:8500"
- "http://192.168.1.201:8500"
services: # hostname: The hostname to access the proxy server, upstream : The real service name in Consul database. services: # hostname: The hostname to access the proxy server, upstream : The real service name in Consul database.
- hostname: "webapi-service" - hostname: "nconsul"
upstream: "webapi-service-health" upstream: "nginx-consul-NginX-health"
path: "/one" path: "/one"
client_headers: client_headers:
- "X-Some-Thing:Yaaaaaaaaaaaaaaa" - "X-Some-Thing:Yaaaaaaaaaaaaaaa"
- "X-Proxy-From:Aralez" - "X-Proxy-From:Aralez"
rate_limit: 1 rate_limit: 1
to_https: false to_https: false
- hostname: "webapi-service" - hostname: "nconsul"
upstream: "webapi-service-health" upstream: "nginx-consul-NginX-health"
path: "/" path: "/"
token: "8e2db809-845b-45e1-8b47-2c8356a09da0-a4370955-18c2-4d6e-a8f8-ffcc0b47be81" # Consul server access token, If Consul auth is enabled token: "8e2db809-845b-45e1-8b47-2c8356a09da0-a4370955-18c2-4d6e-a8f8-ffcc0b47be81" # Consul server access token, If Consul auth is enabled
kubernetes: kubernetes:
servers: servers:
- "192.168.1.55:443" #For testing only, overrides with KUBERNETES_SERVICE_HOST : KUBERNETES_SERVICE_PORT_HTTPS env variables. - "172.16.0.11:5443" # Gets KUBERNETES_SERVICE_HOST : KUBERNETES_SERVICE_PORT_HTTPS env variables.
services: services:
- hostname: "webapi-service" - hostname: "api-service-v2"
upstream: "api-service-v2"
path: "/" path: "/"
upstream: "webapi-service" - hostname: "api-service-v2"
- hostname: "webapi-service"
upstream: "console-service" upstream: "console-service"
path: "/one" path: "/one"
client_headers: client_headers:
@@ -50,70 +49,146 @@ kubernetes:
- "X-Proxy-From:Aralez" - "X-Proxy-From:Aralez"
rate_limit: 100 rate_limit: 100
to_https: false to_https: false
- hostname: "webapi-service" - hostname: "api-service-v2"
upstream: "rambul-service" upstream: "feed-fanout-service"
path: "/two" path: "/two"
- hostname: "websocket-service" - hostname: "websocket-service"
upstream: "websocket-service" upstream: "websocket-service"
path: "/" path: "/"
tokenpath: "/path/to/kubetoken.txt" #If not set, will default to /var/run/secrets/kubernetes.io/serviceaccount/token tokenpath: "/opt/Rust/Projects/asyncweb/etc/kubetoken.txt" # Defaults to /var/run/secrets/kubernetes.io/serviceaccount/token
upstreams: upstreams:
myip.mydomain.com: myip.netangels.net:
paths: paths:
"/": "/":
rate_limit: 200 # rate_limit: 50
to_https: false # x4xx_limit: 100
# to_https: false
# authorization:
# type: "basic"
# data: "root:toor"
server_headers:
- "Y-Proxy-Server-Some:Yaaaaaaaaaaaaaaa"
- "Y-Proxy-Server-From:Aralez"
- "Y-Proxy-Server-Vers:Aralez v0.89"
client_headers: client_headers:
- "X-Proxy-From:Aralez" - "X-Proxy-From:Aralezzzzzzzzzzz"
- "X-Hopar-From:Hopaaaaaaaaaaaar"
- "X-Proxy-Some:X-Proxy-Somebody"
servers: servers:
- "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"
- "127.0.0.5:8000" - "127.0.0.5:8000"
- "192.168.1.1:8000"
"/ping": "/ping":
authorization: # Will be ignored if global authentication is enabled.
type: "basic"
creds: "admin:admin"
to_https: false to_https: false
server_headers:
- "X-Forwarded-Proto:https"
- "X-Forwarded-Port:443"
client_headers: client_headers:
- "X-Some-Thing:Yaaaaaaaaaaaaaaa" - "X-Some-Thing:Yaaaaaaaaaaaaaaa"
- "X-Proxy-From:Aralez" - "X-Proxy-From:Aralez"
servers: servers:
- "127.0.0.1:8000" - "127.0.0.1:8000"
- "127.0.0.2:8000" - "127.0.0.2:8000"
"/draw": "/pong":
servers:
- "192.168.1.1:8000"
polo.mydomain.com:
paths:
"/":
to_https: false to_https: false
client_headers: client_headers:
- "X-Some-Thing:Yaaaaaaaaaaaaaaa" - "X-Some-Thing:Yaaaaaaaaaaaaaaa"
- "X-Proxy-From:Aralez"
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" "/secret":
- "127.0.0.3:8000" authorization:
- "127.0.0.4:8000" type: "forward"
apt.mydomain.com: data: "http://192.168.1.1:8899/admin/login"
#data: "https://netangels.net/admin/login"
servers:
- "192.168.1.10:8000"
netangels.net:
paths: paths:
"/": "/":
redirect_to: "https://www.netangels.net:6194"
servers: servers:
- "192.168.1.10:443" - "192.168.1.1:80"
www.netangels.net:
paths:
"/":
to_https: true
servers:
- "192.168.1.1:80"
apt.netangels.net:
paths:
"/":
server_headers:
- "Y-Global-Something: Yes this is something"
client_headers:
- "Access-Control-Allow-Methods:POST, GET, OPTIONS"
rate_limit: 60
x4xx_limit: 30
#authorization:
# type: "jwt"
# data: "SOMETHING"
servers:
- "127.0.0.1:8000"
- "127.0.0.2:8000"
"/.well-known/acme-challenge": "/.well-known/acme-challenge":
healthcheck: false healthcheck: false
servers: servers:
- "127.0.0.1:8001" - "127.0.0.1:8001"
rdr.mydomain.com: "/400":
paths: rate_limit: 4
"/": x4xx_limit: 2
redirect_to: "https://som.other.domain:6194" servers:
- "192.168.1.1:8899"
"/500":
healthcheck: false healthcheck: false
servers: servers:
- "127.0.0.1:8080" - "192.168.1.1:8899"
# grafanalocal:
# paths:
# "/":
# healthcheck: false
# servers:
# - "95.211.203.222:443"
# "/.well-known/acme-challenge":
# healthcheck: false
# servers:
# - "127.0.0.1:8001"
localpost:
paths:
"/":
to_https: true
servers:
- "127.0.0.1:9000"
# 192.168.177.2:
# paths:
# "/":
# servers:
# - "127.0.0.1:8000"
ara.matyan.org:
paths:
"/":
servers:
- "127.0.0.1:8000"
"/.well-known/acme-challenge":
healthcheck: false
servers:
- "127.0.0.1:3000"
aro.matyan.org:
paths:
"/":
servers:
- "127.0.0.1:8000"
"/.well-known/acme-challenge":
healthcheck: false
servers:
- "127.0.0.1:3000"
DEFAUwLT:
paths:
"/":
healthcheck: false
servers:
- "127.0.0.1:3000"
"/.well-known/acme-challenge":
healthcheck: false
servers:
- "127.0.0.1:3000"

View File

@@ -7,5 +7,10 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
// pub static A: CountingAllocator = CountingAllocator; // pub static A: CountingAllocator = CountingAllocator;
fn main() { fn main() {
if std::env::args().any(|a| a == "--version" || a == "-v") {
println!("aralez {}", env!("CARGO_PKG_VERSION"));
std::process::exit(0);
}
web::start::run(); web::start::run();
} }

View File

@@ -6,6 +6,7 @@ pub mod healthcheck;
pub mod httpclient; pub mod httpclient;
pub mod jwt; pub mod jwt;
pub mod kuberconsul; pub mod kuberconsul;
pub mod lazylock;
pub mod metrics; pub mod metrics;
pub mod parceyaml; pub mod parceyaml;
pub mod state; pub mod state;

View File

@@ -1,21 +1,17 @@
use crate::utils::jwt::check_jwt; use crate::utils::jwt::{check_jwt, JWT_TOKEN};
// use reqwest::Client; use crate::utils::structs::InnerAuth;
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;
use pingora_proxy::Session;
use std::collections::HashMap;
use std::sync::{Arc, LazyLock};
use subtle::ConstantTimeEq;
use urlencoding::decode;
// use pingora::http::{RequestHeader, ResponseHeader, StatusCode};
use pingora::http::RequestHeader; use pingora::http::RequestHeader;
// --------------------------------- //
use pingora_core::connectors::http::Connector; use pingora_core::connectors::http::Connector;
use pingora_core::upstreams::peer::HttpPeer; use pingora_core::upstreams::peer::HttpPeer;
use pingora_http::ResponseHeader; use pingora_http::ResponseHeader;
// --------------------------------- // use pingora_proxy::Session;
use std::collections::HashMap;
use std::sync::LazyLock;
use subtle::ConstantTimeEq;
use urlencoding::decode;
#[async_trait::async_trait] #[async_trait::async_trait]
trait AuthValidator { trait AuthValidator {
@@ -23,7 +19,7 @@ trait AuthValidator {
} }
struct BasicAuth<'a>(&'a str); struct BasicAuth<'a>(&'a str);
struct ApiKeyAuth<'a>(&'a str); struct ApiKeyAuth<'a>(&'a str);
struct JwtAuth<'a>(&'a str); struct JwtAuth();
struct ForwardAuth<'a>(&'a str); struct ForwardAuth<'a>(&'a str);
pub static AUTH_CONNECTOR: LazyLock<Connector> = LazyLock::new(|| Connector::new(None)); pub static AUTH_CONNECTOR: LazyLock<Connector> = LazyLock::new(|| Connector::new(None));
@@ -180,17 +176,18 @@ impl AuthValidator for ApiKeyAuth<'_> {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl AuthValidator for JwtAuth<'_> { impl AuthValidator for JwtAuth {
async fn validate(&self, session: &mut Session) -> bool { async fn validate(&self, session: &mut Session) -> bool {
let jwtsecret = self.0; if let Some(jwtsecret) = JWT_TOKEN.clone() {
if let Some(tok) = get_query_param(session, "araleztoken") { if let Some(tok) = get_query_param(session, "araleztoken") {
return check_jwt(tok.as_str(), jwtsecret); return check_jwt(tok.as_str(), jwtsecret.as_ref());
} }
if let Some(auth_header) = session.get_header("authorization") { if let Some(auth_header) = session.get_header("authorization") {
if let Ok(header_str) = auth_header.to_str() { if let Ok(header_str) = auth_header.to_str() {
if let Some((scheme, token)) = header_str.split_once(' ') { if let Some((scheme, token)) = header_str.split_once(' ') {
if scheme.eq_ignore_ascii_case("bearer") { if scheme.eq_ignore_ascii_case("bearer") {
return check_jwt(token, jwtsecret); return check_jwt(token, jwtsecret.as_ref());
}
} }
} }
} }
@@ -199,14 +196,14 @@ impl AuthValidator for JwtAuth<'_> {
} }
} }
pub async fn authenticate(auth_type: &Arc<str>, credentials: &Arc<str>, session: &mut Session) -> bool { pub async fn authenticate(auth: &InnerAuth, session: &mut Session) -> bool {
match &**auth_type { match &*auth.auth_type {
"basic" => BasicAuth(credentials).validate(session).await, "basic" => BasicAuth(&*auth.auth_cred).validate(session).await,
"apikey" => ApiKeyAuth(credentials).validate(session).await, "apikey" => ApiKeyAuth(&*auth.auth_cred).validate(session).await,
"jwt" => JwtAuth(credentials).validate(session).await, "jwt" => JwtAuth().validate(session).await,
"forward" => ForwardAuth(credentials).validate(session).await, "forward" => ForwardAuth(&*auth.auth_cred).validate(session).await,
_ => { _ => {
log::warn!("Unsupported authentication mechanism : {}", auth_type); log::warn!("Unsupported authentication mechanism : {}", &*auth.auth_type);
false false
} }
} }

View File

@@ -9,13 +9,10 @@ use std::sync::Arc;
pub struct APIUpstreamProvider { pub struct APIUpstreamProvider {
pub config_api_enabled: bool, pub config_api_enabled: bool,
pub address: String, pub address: String,
pub masterkey: String, pub masterkey: Option<String>,
pub certs_dir: String, pub certs_dir: String,
pub config_dir: String, pub config_dir: String,
pub upstreams_file: String, pub upstreams_file: String,
// pub tls_address: Option<String>,
// pub tls_certificate: Option<String>,
// pub tls_key_file: Option<String>,
pub file_server_address: Option<String>, pub file_server_address: Option<String>,
pub file_server_folder: Option<String>, pub file_server_folder: Option<String>,
pub current_upstreams: Arc<UpstreamsDashMap>, pub current_upstreams: Arc<UpstreamsDashMap>,

View File

@@ -37,9 +37,8 @@ pub async fn start(fp: String, mut toreturn: Sender<Configuration>) {
match event { match event {
Ok(e) => match e.kind { Ok(e) => match e.kind {
EventKind::Modify(ModifyKind::Data(_)) | EventKind::Create(..) | EventKind::Remove(..) => { EventKind::Modify(ModifyKind::Data(_)) | EventKind::Create(..) | EventKind::Remove(..) => {
if e.paths[0].to_str().unwrap().ends_with("yaml") && start.elapsed() > Duration::from_secs(2) { if start.elapsed() > Duration::from_secs(2) {
start = Instant::now(); start = Instant::now();
// info!("Config File changed :=> {:?}", e);
let snd = load_configuration(file_path, "filepath").await.0; let snd = load_configuration(file_path, "filepath").await.0;
if let Some(snd) = snd { if let Some(snd) = snd {
toreturn.send(snd).await.unwrap(); toreturn.send(snd).await.unwrap();

View File

@@ -1,3 +1,4 @@
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;
@@ -20,6 +21,7 @@ pub async fn hc2(upslist: Arc<UpstreamsDashMap>, fullist: Arc<UpstreamsDashMap>,
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);
REVERSE_STORE.clear();
} }
} }
} }
@@ -53,15 +55,14 @@ async fn build_upstreams(fullist: &UpstreamsDashMap, method: &str, client: &Clie
let mut innervec = Vec::new(); let mut innervec = Vec::new();
for upstream in path_entry.value().0.iter() { for upstream in path_entry.value().0.iter() {
let tls = detect_tls(upstream.address.as_ref(), &upstream.port, client).await; let tls = if upstream.healthcheck.unwrap_or(true) {
let is_h2 = matches!(tls.1, Some(Version::HTTP_2)); detect_tls(upstream.address.as_ref(), &upstream.port, client).await
let link = if tls.0 {
format!("https://{}:{}{}", upstream.address, upstream.port, path)
} else { } else {
format!("http://{}:{}{}", upstream.address, upstream.port, path) (false, None)
}; };
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,
@@ -69,12 +70,19 @@ 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,
x4xx_limit: upstream.x4xx_limit,
healthcheck: upstream.healthcheck, healthcheck: upstream.healthcheck,
redirect_to: upstream.redirect_to.clone(), redirect_to: upstream.redirect_to.clone(),
authorization: upstream.authorization.clone(), authorization: upstream.authorization.clone(),
}; };
if scheme.healthcheck.unwrap_or(true) { if scheme.healthcheck.unwrap_or(true) {
let link = if tls.0 {
format!("https://{}:{}{}", upstream.address, upstream.port, path)
} else {
format!("http://{}:{}{}", upstream.address, upstream.port, path)
};
let resp = http_request(&link, method, "", client).await; let resp = http_request(&link, method, "", client).await;
if resp.0 { if resp.0 {
if resp.1 { if resp.1 {
@@ -133,10 +141,7 @@ async fn detect_tls(ip: &str, port: &u16, client: &Client) -> (bool, Option<Vers
} }
let http_url = format!("http://{}:{}", ip, port); let http_url = format!("http://{}:{}", ip, port);
match client.get(&http_url).send().await { match client.get(&http_url).send().await {
Ok(response) => { Ok(response) => (false, Some(response.version())),
// println!("{} => {:?} (HTTP)", http_url, response.version());
(false, Some(response.version()))
}
Err(_) => { Err(_) => {
if ping_grpc(&http_url).await { if ping_grpc(&http_url).await {
(false, Some(Version::HTTP_2)) (false, Some(Version::HTTP_2))

View File

@@ -32,6 +32,7 @@ pub async fn for_consul(url: String, token: Option<String>, conf: &GlobalService
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,
redirect_to: None, redirect_to: None,
healthcheck: None, healthcheck: None,
authorization: None, authorization: None,
@@ -68,6 +69,7 @@ pub async fn for_kuber(url: &str, token: &str, conf: &GlobalServiceMapping) -> O
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,
healthcheck: None, healthcheck: None,
redirect_to: None, redirect_to: None,
authorization: None, authorization: None,

View File

@@ -4,8 +4,9 @@ use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use moka::sync::Cache; use moka::sync::Cache;
use moka::Expiry; use moka::Expiry;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::env;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::sync::LazyLock; use std::sync::{Arc, LazyLock};
use std::time::{Duration, Instant, SystemTime}; use std::time::{Duration, Instant, SystemTime};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@@ -23,6 +24,11 @@ struct Expired {
static JWT_VALIDATION: LazyLock<Validation> = LazyLock::new(|| Validation::new(Algorithm::HS256)); static JWT_VALIDATION: LazyLock<Validation> = LazyLock::new(|| Validation::new(Algorithm::HS256));
pub static JWT_TOKEN: LazyLock<Option<Arc<str>>> = LazyLock::new(|| match env::var("JWT_KEY") {
Ok(key) if !key.is_empty() => Some(Arc::from(key.as_str())),
_ => None,
});
static JWT_CACHE: LazyLock<Cache<u64, u64>> = LazyLock::new(|| Cache::builder().max_capacity(100_000).expire_after(JwtExpiry).build()); static JWT_CACHE: LazyLock<Cache<u64, u64>> = LazyLock::new(|| Cache::builder().max_capacity(100_000).expire_after(JwtExpiry).build());
struct JwtExpiry; struct JwtExpiry;
impl Expiry<u64, u64> for JwtExpiry { impl Expiry<u64, u64> for JwtExpiry {

View File

@@ -222,7 +222,7 @@ async fn clone_compare(upstreams: &UpstreamsDashMap, prev_upstreams: &UpstreamsD
}; };
clone_dashmap_into(upstreams, prev_upstreams); clone_dashmap_into(upstreams, prev_upstreams);
clone_dashmap_into(upstreams, &tosend.upstreams); clone_dashmap_into(upstreams, &tosend.upstreams);
print_upstreams(&tosend.upstreams); print_upstreams(&tosend.upstreams, &tosend.extraparams);
return Some(tosend); return Some(tosend);
}; };
None None

11
src/utils/lazylock.rs Normal file
View File

@@ -0,0 +1,11 @@
use dashmap::DashMap;
use moka::sync::Cache;
use pingora_limits::rate::Rate;
use std::net::IpAddr;
use std::sync::{Arc, LazyLock};
use std::time::Duration;
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 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"));

View File

@@ -14,18 +14,28 @@ pub struct MetricTypes {
pub version: Version, pub version: Version,
} }
pub static OPEN_FILES: LazyLock<IntGauge> = LazyLock::new(|| register_int_gauge!("aralez_open_files", "Number of open file descriptors").unwrap());
pub static MEMORY_USAGE: LazyLock<IntGauge> = LazyLock::new(|| register_int_gauge!("aralez_memory_bytes", "Total memory allocated in bytes").unwrap());
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 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());
// pub static RESPONSE_LATENCY: LazyLock<Histogram> = LazyLock::new(|| {
// register_histogram!(
// "aralez_response_latency_seconds",
// "Response latency in seconds",
// vec![0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.0, 5.0]
// )
// .unwrap()
// });
pub static RESPONSE_LATENCY: LazyLock<Histogram> = LazyLock::new(|| { pub static RESPONSE_LATENCY: LazyLock<Histogram> = LazyLock::new(|| {
register_histogram!( register_histogram!(
"aralez_response_latency_seconds", "aralez_response_latency_seconds",
"Response latency in seconds", "Response latency in seconds",
vec![0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.0, 5.0] vec![0.001, 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 1.0, 2.0, 5.0]
) )
.unwrap() .unwrap()
}); });
@@ -54,3 +64,20 @@ pub fn calc_metrics(metric_types: &MetricTypes) {
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());
} }
pub(crate) fn get_memory_usage() -> usize {
std::fs::read_to_string("/proc/self/status")
.ok()
.and_then(|s| {
s.lines()
.find(|l| l.starts_with("VmRSS:"))
.and_then(|l| l.split_whitespace().nth(1))
.and_then(|v| v.parse::<usize>().ok())
})
.unwrap_or(0)
* 1024
}
pub fn get_open_files() -> usize {
std::fs::read_dir("/proc/self/fd").map(|dir| dir.count()).unwrap_or(0)
}

View File

@@ -1,4 +1,5 @@
use crate::utils::healthcheck; use crate::utils::healthcheck;
use crate::utils::lazylock::REVERSE_STORE;
use crate::utils::state::{is_first_run, mark_not_first_run}; use crate::utils::state::{is_first_run, mark_not_first_run};
use crate::utils::structs::*; use crate::utils::structs::*;
use crate::utils::tools::{clone_dashmap, clone_dashmap_into, print_upstreams}; use crate::utils::tools::{clone_dashmap, clone_dashmap_into, print_upstreams};
@@ -11,18 +12,49 @@ use log4rs::{
encode::pattern::PatternEncoder, encode::pattern::PatternEncoder,
}; };
use std::collections::HashMap; use std::collections::HashMap;
use std::fs;
use std::path::Path; use std::path::Path;
use std::sync::atomic::AtomicUsize; use std::sync::atomic::AtomicUsize;
use std::sync::{Arc, LazyLock}; use std::sync::{Arc, LazyLock};
use std::{env, fs};
pub static DOMAINS: LazyLock<DashMap<String, bool>> = LazyLock::new(DashMap::new); pub static DOMAINS: LazyLock<DashMap<String, bool>> = LazyLock::new(DashMap::new);
pub async fn load_configuration(d: &str, kind: &str) -> (Option<Configuration>, String) { pub async fn load_configuration(d: &str, kind: &str) -> (Option<Configuration>, String) {
let mut conf_files = Vec::new(); let mut conf_files = Vec::new();
let yaml_data = match kind { let yaml_data = match kind {
"filepath" => match fs::read_to_string(d) { "filepath" => {
Ok(data) => { let mut data = String::new();
let mut last_error = None;
for _ in 0..5 {
match fs::read_to_string(d) {
Ok(content) => {
if !content.trim().is_empty() {
data = content;
break;
}
}
Err(e) => {
error!("Config read failed, retrying...");
last_error = Some(e);
}
}
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
if data.is_empty() {
let err_msg = match last_error {
Some(e) => {
error!("Reading: {}: {:?}", d, e);
e.to_string()
}
None => {
error!("Reading: {}: File is empty after retries", d);
"File is empty".to_string()
}
};
warn!("Running with empty upstreams list, update it via API");
return (None, err_msg);
}
let mut confdir = Path::new(d).parent().unwrap().to_path_buf(); let mut confdir = Path::new(d).parent().unwrap().to_path_buf();
let mut autocfg = Path::new(d).parent().unwrap().to_path_buf(); let mut autocfg = Path::new(d).parent().unwrap().to_path_buf();
@@ -66,12 +98,6 @@ pub async fn load_configuration(d: &str, kind: &str) -> (Option<Configuration>,
info!("Reading upstreams from {}", d); info!("Reading upstreams from {}", d);
data data
} }
Err(e) => {
error!("Reading: {}: {:?}", d, e);
warn!("Running with empty upstreams list, update it via API");
return (None, e.to_string());
}
},
"content" => { "content" => {
info!("Reading upstreams from API post body"); info!("Reading upstreams from API post body");
d.to_string() d.to_string()
@@ -136,6 +162,7 @@ async fn populate_headers_and_auth(config: &mut Configuration, parsed: &Config)
} }
} }
} }
let global_headers: DashMap<Arc<str>, Vec<(String, Arc<str>)>> = DashMap::new(); let global_headers: DashMap<Arc<str>, Vec<(String, Arc<str>)>> = DashMap::new();
global_headers.insert(Arc::from("/"), ch); global_headers.insert(Arc::from("/"), ch);
config.client_headers.insert(Arc::from("GLOBAL_CLIENT_HEADERS"), global_headers); config.client_headers.insert(Arc::from("GLOBAL_CLIENT_HEADERS"), global_headers);
@@ -154,6 +181,7 @@ async fn populate_headers_and_auth(config: &mut Configuration, parsed: &Config)
config.extraparams.to_https = parsed.to_https; config.extraparams.to_https = parsed.to_https;
config.extraparams.sticky_sessions = parsed.sticky_sessions; config.extraparams.sticky_sessions = parsed.sticky_sessions;
config.extraparams.rate_limit = parsed.rate_limit; config.extraparams.rate_limit = parsed.rate_limit;
config.extraparams.x4xx_limit = parsed.x4xx_limit;
if let Some(rate) = &parsed.rate_limit { if let Some(rate) = &parsed.rate_limit {
info!("Applied Global Rate Limit : {} request per second", rate); info!("Applied Global Rate Limit : {} request per second", rate);
@@ -162,7 +190,7 @@ async fn populate_headers_and_auth(config: &mut Configuration, parsed: &Config)
if let Some(pa) = &parsed.authorization { if let Some(pa) = &parsed.authorization {
let y: InnerAuth = InnerAuth { let y: InnerAuth = InnerAuth {
auth_type: Arc::from(pa.auth_type.clone()), auth_type: Arc::from(pa.auth_type.clone()),
auth_cred: Arc::from(pa.auth_cred.clone()), auth_cred: Arc::from(pa.auth_cred.clone().unwrap_or_default()),
}; };
config.extraparams.authentication = Some(Arc::from(y)); config.extraparams.authentication = Some(Arc::from(y));
} }
@@ -191,10 +219,11 @@ async fn populate_file_upstreams(config: &mut Configuration, parsed: &Config) {
if let Some(pa) = &path_config.authorization { if let Some(pa) = &path_config.authorization {
let y: InnerAuth = InnerAuth { let y: InnerAuth = InnerAuth {
auth_type: Arc::from(pa.auth_type.clone()), auth_type: Arc::from(pa.auth_type.clone()),
auth_cred: Arc::from(pa.auth_cred.clone()), auth_cred: Arc::from(pa.auth_cred.clone().unwrap_or_default()),
}; };
path_auth = Some(Arc::from(y)); path_auth = Some(Arc::from(y));
} }
let redirect_link = path_config.redirect_to.as_ref().map(|www| Arc::from(www.as_str())); let redirect_link = path_config.redirect_to.as_ref().map(|www| Arc::from(www.as_str()));
if let Some((ip, port_str)) = server.split_once(':') { if let Some((ip, port_str)) = server.split_once(':') {
if let Ok(port) = port_str.parse::<u16>() { if let Ok(port) = port_str.parse::<u16>() {
@@ -205,6 +234,7 @@ async fn populate_file_upstreams(config: &mut Configuration, parsed: &Config) {
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: path_config.rate_limit, rate_limit: path_config.rate_limit,
x4xx_limit: path_config.x4xx_limit,
healthcheck: path_config.healthcheck, healthcheck: path_config.healthcheck,
redirect_to: redirect_link, redirect_to: redirect_link,
authorization: path_auth, authorization: path_auth,
@@ -228,7 +258,8 @@ async fn populate_file_upstreams(config: &mut Configuration, parsed: &Config) {
clone_dashmap_into(&r, &config.upstreams); clone_dashmap_into(&r, &config.upstreams);
} }
info!("Upstream Config:"); info!("Upstream Config:");
print_upstreams(&config.upstreams); REVERSE_STORE.clear();
print_upstreams(&config.upstreams, &config.extraparams);
} }
} }
pub fn parce_main_config(path: &str) -> AppConfig { pub fn parce_main_config(path: &str) -> AppConfig {
@@ -236,6 +267,11 @@ pub fn parce_main_config(path: &str) -> AppConfig {
let reply = DashMap::new(); let reply = DashMap::new();
let cfg: HashMap<String, String> = serde_yml::from_str(&data).expect("Failed to parse main config file"); let cfg: HashMap<String, String> = serde_yml::from_str(&data).expect("Failed to parse main config file");
let mut cfo: AppConfig = serde_yml::from_str(&data).expect("Failed to parse main config file"); let mut cfo: AppConfig = serde_yml::from_str(&data).expect("Failed to parse main config file");
if let Ok(jwt_key) = env::var("JWT_KEY") {
cfo.master_key = Some(jwt_key);
};
log_builder(&cfo, &cfo.log_file); log_builder(&cfo, &cfo.log_file);
cfo.hc_method = cfo.hc_method.to_uppercase(); cfo.hc_method = cfo.hc_method.to_uppercase();
for (k, v) in cfg { for (k, v) in cfg {
@@ -287,27 +323,6 @@ fn parce_tls_grades(what: Option<String>) -> Option<String> {
} }
} }
/*
fn log_builder1(conf: &AppConfig) {
let log_level = conf.log_level.clone();
unsafe {
match log_level.as_str() {
"info" => env::set_var("RUST_LOG", "info"),
"error" => env::set_var("RUST_LOG", "error"),
"warn" => env::set_var("RUST_LOG", "warn"),
"debug" => env::set_var("RUST_LOG", "debug"),
"trace" => env::set_var("RUST_LOG", "trace"),
"off" => env::set_var("RUST_LOG", "off"),
_ => {
println!("Error reading log level, defaulting to: INFO");
env::set_var("RUST_LOG", "info")
}
}
}
env_logger::builder().init();
}
*/
pub fn build_headers(path_config: &Option<Vec<String>>, _config: &Configuration, hl: &mut Vec<(String, Arc<str>)>) { pub fn build_headers(path_config: &Option<Vec<String>>, _config: &Configuration, hl: &mut Vec<(String, Arc<str>)>) {
if let Some(headers) = &path_config { if let Some(headers) = &path_config {
for header in headers { for header in headers {
@@ -331,7 +346,8 @@ fn log_builder(conf: &AppConfig, location: &Option<String>) {
LevelFilter::Info LevelFilter::Info
} }
}; };
let pattern = "{d(%Y-%m-%d %H:%M:%S)} {l} {t} - {m}{n}"; // let pattern = "{d(%Y-%m-%d %H:%M:%S)} {l} {t} - {m}{n}";
let pattern = "{d(%Y-%m-%d %H:%M:%S)} {l} {t} - {m}\n";
if let Some(location) = location { if let Some(location) = location {
let file = FileAppender::builder().encoder(Box::new(PatternEncoder::new(pattern))).build(location).unwrap(); let file = FileAppender::builder().encoder(Box::new(PatternEncoder::new(pattern))).build(location).unwrap();
let config = Log4rsConfig::builder() let config = Log4rsConfig::builder()

View File

@@ -14,9 +14,10 @@ pub type Headers = DashMap<Arc<str>, DashMap<Arc<str>, Vec<(String, Arc<str>)>>>
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct Extraparams { pub struct Extraparams {
pub to_https: Option<bool>, pub to_https: Option<bool>,
pub sticky_sessions: bool, pub sticky_sessions: Option<u64>,
pub authentication: Option<Arc<InnerAuth>>, pub authentication: Option<Arc<InnerAuth>>,
pub rate_limit: Option<isize>, pub rate_limit: Option<isize>,
pub x4xx_limit: Option<u32>,
} }
#[derive(Debug, Default, Clone, Serialize, Deserialize)] #[derive(Debug, Default, Clone, Serialize, Deserialize)]
@@ -25,8 +26,9 @@ pub struct GlobalServiceMapping {
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<bool>, pub sticky_sessions: Option<u64>,
pub rate_limit: Option<isize>, pub rate_limit: Option<isize>,
pub x4xx_limit: Option<u32>,
pub client_headers: Option<Vec<String>>, pub client_headers: Option<Vec<String>>,
pub server_headers: Option<Vec<String>>, pub server_headers: Option<Vec<String>>,
} }
@@ -48,7 +50,7 @@ pub struct Consul {
pub struct Config { pub struct Config {
pub provider: String, pub provider: String,
pub to_https: Option<bool>, pub to_https: Option<bool>,
pub sticky_sessions: bool, pub sticky_sessions: Option<u64>,
#[serde(default)] #[serde(default)]
pub upstreams: Option<HashMap<String, HostConfig>>, pub upstreams: Option<HashMap<String, HostConfig>>,
#[serde(default)] #[serde(default)]
@@ -65,28 +67,30 @@ pub struct Config {
pub kubernetes: Option<Kubernetes>, pub kubernetes: Option<Kubernetes>,
#[serde(default)] #[serde(default)]
pub rate_limit: Option<isize>, pub rate_limit: Option<isize>,
pub x4xx_limit: Option<u32>,
} }
#[derive(Debug, Default, Serialize, Deserialize)] #[derive(Debug, Default, Serialize, Deserialize)]
pub struct HostConfig { pub struct HostConfig {
pub paths: HashMap<String, PathConfig>, pub paths: HashMap<String, PathConfig>,
pub rate_limit: Option<isize>, pub rate_limit: Option<isize>,
pub x4xx_limit: Option<u32>,
} }
#[derive(Debug, Default, Serialize, Deserialize, Clone)] #[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct Auth { pub struct Auth {
#[serde(rename = "type")] #[serde(rename = "type")]
pub auth_type: String, pub auth_type: String,
#[serde(rename = "data")] #[serde(rename = "data")]
pub auth_cred: String, pub auth_cred: Option<String>,
} }
#[derive(Debug, Default, Serialize, Deserialize)] #[derive(Debug, Default, Serialize, Deserialize)]
pub struct PathConfig { pub struct PathConfig {
pub servers: Vec<String>, pub servers: Vec<String>,
pub to_https: Option<bool>, pub to_https: Option<bool>,
pub sticky_sessions: Option<bool>,
pub client_headers: Option<Vec<String>>, pub client_headers: Option<Vec<String>>,
pub server_headers: Option<Vec<String>>, pub server_headers: Option<Vec<String>>,
pub rate_limit: Option<isize>, pub rate_limit: Option<isize>,
pub x4xx_limit: Option<u32>,
pub healthcheck: Option<bool>, pub healthcheck: Option<bool>,
pub redirect_to: Option<String>, pub redirect_to: Option<String>,
pub authorization: Option<Auth>, pub authorization: Option<Auth>,
@@ -108,7 +112,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 master_key: String, pub pid_file: Option<String>,
pub master_key: Option<String>,
pub config_address: String, pub config_address: String,
pub proxy_address_http: String, pub proxy_address_http: String,
pub config_api_enabled: bool, pub config_api_enabled: bool,
@@ -126,6 +131,9 @@ pub struct AppConfig {
pub runuser: Option<String>, pub runuser: Option<String>,
pub rungroup: Option<String>, pub rungroup: Option<String>,
pub log_file: Option<String>, pub log_file: Option<String>,
pub tcp_keepalive_idle: Option<u64>,
pub tcp_keepalive_interval: Option<u64>,
pub tcp_keepalive_count: Option<usize>,
} }
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
@@ -142,6 +150,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 x4xx_limit: Option<u32>,
pub healthcheck: Option<bool>, pub healthcheck: Option<bool>,
pub redirect_to: Option<Arc<str>>, pub redirect_to: Option<Arc<str>>,
pub authorization: Option<Arc<InnerAuth>>, pub authorization: Option<Arc<InnerAuth>>,
@@ -157,6 +166,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(),
x4xx_limit: Default::default(),
healthcheck: Default::default(), healthcheck: Default::default(),
redirect_to: Default::default(), redirect_to: Default::default(),
authorization: Default::default(), authorization: Default::default(),
@@ -172,6 +182,7 @@ pub struct InnerMapForJson {
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 x4xx_limit: Option<u32>,
pub healthcheck: Option<bool>, pub healthcheck: Option<bool>,
} }
#[derive(Debug, Default, Serialize, Deserialize)] #[derive(Debug, Default, Serialize, Deserialize)]

View File

@@ -1,6 +1,6 @@
use crate::tls::load; use crate::tls::load;
use crate::tls::load::CertificateConfig; use crate::tls::load::CertificateConfig;
use crate::utils::structs::{InnerMap, InnerMapForJson, UpstreamSnapshotForJson, UpstreamsDashMap, UpstreamsIdMap}; use crate::utils::structs::{Extraparams, InnerMap, InnerMapForJson, UpstreamSnapshotForJson, UpstreamsDashMap, UpstreamsIdMap};
use dashmap::DashMap; use dashmap::DashMap;
use log::{error, info}; use log::{error, info};
use notify::{event::ModifyKind, Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; use notify::{event::ModifyKind, Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
@@ -20,7 +20,7 @@ use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use std::{fs, process, thread, time}; use std::{fs, process, thread, time};
pub fn print_upstreams(upstreams: &UpstreamsDashMap) { pub fn print_upstreams(upstreams: &UpstreamsDashMap, extraparams: &Extraparams) {
let mut out = String::new(); let mut out = String::new();
for host_entry in upstreams.iter() { for host_entry in upstreams.iter() {
writeln!(out, "Hostname: {}", host_entry.key()).unwrap(); writeln!(out, "Hostname: {}", host_entry.key()).unwrap();
@@ -29,13 +29,14 @@ pub fn print_upstreams(upstreams: &UpstreamsDashMap) {
for f in path_entry.value().0.clone() { for f in path_entry.value().0.clone() {
writeln!( writeln!(
out, out,
" IP: {}, Port: {}, SSL: {}, H2: {}, To HTTPS: {}, Rate Limit: {}", " IP: {}, Port: {}, SSL: {}, H2: {}, To HTTPS: {}, Rate Limit: {}, 4xx Limit: {}",
f.address, f.address,
f.port, f.port,
f.is_ssl, f.is_ssl,
f.is_http2, f.is_http2,
f.to_https, f.to_https,
f.rate_limit.unwrap_or(0) f.rate_limit.unwrap_or(extraparams.rate_limit.unwrap_or(0)),
f.x4xx_limit.unwrap_or(extraparams.x4xx_limit.unwrap_or(0))
) )
.unwrap(); .unwrap();
} }
@@ -152,12 +153,13 @@ pub fn clone_idmap_into(original: &UpstreamsDashMap, cloned: &UpstreamsIdMap) {
let mut id = String::new(); let mut id = String::new();
write!( write!(
&mut id, &mut id,
"{}:{}:{}:{}:{}:{}:{}:{:?}", "{}:{}:{}:{}:{}:{}:{}:{}:{:?}",
outer_entry.key(), outer_entry.key(),
x.address, x.address,
x.port, x.port,
x.is_http2, x.is_http2,
x.to_https, x.to_https,
x.x4xx_limit.unwrap_or_default(),
x.rate_limit.unwrap_or_default(), x.rate_limit.unwrap_or_default(),
x.healthcheck.unwrap_or_default(), x.healthcheck.unwrap_or_default(),
x.authorization x.authorization
@@ -177,6 +179,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,
x4xx_limit: None,
healthcheck: None, healthcheck: None,
redirect_to: None, redirect_to: None,
authorization: None, authorization: None,
@@ -271,7 +274,7 @@ pub fn drop_priv(user: String, group: String, http_addr: String, tls_addr: Optio
} }
pub fn check_priv(addr: &str) { pub fn check_priv(addr: &str) {
let port = SocketAddr::from_str(addr).map(|sa| sa.port()).unwrap(); let port = SocketAddr::from_str(addr).map(|sa| sa.port()).expect("Failed to parse address port ");
if port < 1024 { if port < 1024 {
let meta = std::fs::metadata("/proc/self").map(|m| m.uid()).unwrap(); let meta = std::fs::metadata("/proc/self").map(|m| m.uid()).unwrap();
if meta != 0 { if meta != 0 {
@@ -303,6 +306,7 @@ pub fn upstreams_to_json(upstreams: &UpstreamsDashMap) -> serde_json::Result<Str
is_http2: a.is_http2, is_http2: a.is_http2,
to_https: a.to_https, to_https: a.to_https,
rate_limit: a.rate_limit, rate_limit: a.rate_limit,
x4xx_limit: a.x4xx_limit,
healthcheck: a.healthcheck, healthcheck: a.healthcheck,
}) })
.collect(), .collect(),

View File

@@ -1,3 +1,4 @@
pub mod acme;
pub mod bgservice; pub mod bgservice;
pub mod gethosts; pub mod gethosts;
pub mod proxyhttp; pub mod proxyhttp;

64
src/web/acme.rs Normal file
View File

@@ -0,0 +1,64 @@
use crate::tls::acme::order::CHALLENGES;
use crate::tls::acme::{account, order};
use axum::body::Body;
use axum::extract::State;
use axum::http::{Response, StatusCode};
use axum::response::IntoResponse;
#[allow(clippy::needless_return)]
pub async fn acme_create(State(state): State<crate::web::webserver::AppState>) -> impl IntoResponse {
match account::load_or_create(state.cert_creds.as_str()).await {
Ok(txt) => {
return Response::builder()
.status(StatusCode::OK)
.header("Content-Type", "text/plain")
.body(Body::from(txt))
.unwrap()
}
Err(e) => {
return Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::from(format!("Failed to create account: {}", e)))
.unwrap()
}
};
}
#[allow(clippy::needless_return)]
pub async fn acme_order(State(state): State<crate::web::webserver::AppState>, axum::extract::Path(domain): axum::extract::Path<String>) -> impl IntoResponse {
let domain_clean = domain.trim_matches('/');
match order::order(domain_clean, state.cert_creds.as_str(), state.certs_dir).await {
Ok(txt) => {
return Response::builder()
.status(StatusCode::OK)
.header("Content-Type", "text/plain")
.body(Body::from(txt))
.unwrap()
}
Err(e) => {
return Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::from(format!("Failed to order a certificate: {}", e)))
.unwrap()
}
};
}
pub async fn http01_challenge(axum::extract::Path(token): axum::extract::Path<String>) -> impl IntoResponse {
if let Ok(challenges) = CHALLENGES.read() {
// for k in challenges.iter() {
// println!(" ==> {} : {}", k.0, k.1);
// }
if let Some(key_authorization) = challenges.get(&token) {
return Response::builder()
.status(StatusCode::OK)
.header("Content-Type", "text/plain")
.body(Body::from(key_authorization.clone()))
.unwrap();
}
}
Response::builder()
.status(StatusCode::NOT_FOUND)
.header("Content-Type", "text/plain")
.body(Body::from("Not found"))
.unwrap()
}

View File

@@ -60,19 +60,13 @@ impl BackgroundService for LB {
masterkey: self.config.master_key.clone(), masterkey: self.config.master_key.clone(),
config_api_enabled: self.config.config_api_enabled, config_api_enabled: self.config.config_api_enabled,
upstreams_file: self.config.upstreams_conf.clone(), upstreams_file: self.config.upstreams_conf.clone(),
// certs_dir: self.config.proxy_certificates.clone().unwrap_or_else(|| "/tmp".to_string()),
config_dir: confdir.clone(), config_dir: confdir.clone(),
certs_dir: certdir.clone(), certs_dir: certdir.clone(),
// tls_address: self.config.config_tls_address.clone(),
// tls_certificate: self.config.config_tls_certificate.clone(),
// tls_key_file: self.config.config_tls_key_file.clone(),
file_server_address: self.config.file_server_address.clone(), file_server_address: self.config.file_server_address.clone(),
file_server_folder: self.config.file_server_folder.clone(), file_server_folder: self.config.file_server_folder.clone(),
current_upstreams: self.ump_upst.clone(), current_upstreams: self.ump_upst.clone(),
full_upstreams: self.ump_full.clone(), full_upstreams: self.ump_full.clone(),
}; };
// let crtdir = api_load.certs_dir.clone();
// let tx_api = tx.clone();
drop(tokio::spawn(async move { api_load.start(tx_api).await })); drop(tokio::spawn(async move { api_load.start(tx_api).await }));
let uu = self.ump_upst.clone(); let uu = self.ump_upst.clone();
@@ -93,12 +87,14 @@ impl BackgroundService for LB {
if let Some(ss) = val { if let Some(ss) = val {
clone_dashmap_into(&ss.upstreams, &self.ump_full); clone_dashmap_into(&ss.upstreams, &self.ump_full);
clone_dashmap_into(&ss.upstreams, &self.ump_upst); clone_dashmap_into(&ss.upstreams, &self.ump_upst);
clone_idmap_into(&ss.upstreams, &self.ump_byid);
let current = self.extraparams.load_full(); let current = self.extraparams.load_full();
let mut new = (*current).clone(); let mut new = (*current).clone();
new.to_https = ss.extraparams.to_https; new.to_https = ss.extraparams.to_https;
new.sticky_sessions = ss.extraparams.sticky_sessions; new.sticky_sessions = ss.extraparams.sticky_sessions;
new.authentication = ss.extraparams.authentication.clone(); new.authentication = ss.extraparams.authentication.clone();
new.rate_limit = ss.extraparams.rate_limit; new.rate_limit = ss.extraparams.rate_limit;
new.x4xx_limit = ss.extraparams.x4xx_limit;
self.extraparams.store(Arc::new(new)); self.extraparams.store(Arc::new(new));
self.client_headers.clear(); self.client_headers.clear();
self.server_headers.clear(); self.server_headers.clear();

View File

@@ -1,30 +1,29 @@
use crate::utils::auth::authenticate; use crate::utils::auth::authenticate;
use crate::utils::lazylock::{LOCALHOST, 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};
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 dashmap::DashMap;
use log::{debug, error, warn}; use log::{debug, error, warn};
use pingora::http::{RequestHeader, ResponseHeader, StatusCode}; use pingora::http::{RequestHeader, ResponseHeader, StatusCode};
use pingora::prelude::*; use pingora::prelude::*;
use pingora::ErrorSource::Upstream; use pingora::ErrorSource::Upstream;
use pingora_core::listeners::ALPN; use pingora_core::listeners::ALPN;
use pingora_core::prelude::HttpPeer; use pingora_core::prelude::HttpPeer;
use pingora_limits::rate::Rate;
use pingora_proxy::{ProxyHttp, Session}; use pingora_proxy::{ProxyHttp, Session};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt::Write; use std::fmt::Write;
use std::sync::{Arc, LazyLock}; use std::sync::Arc;
use std::time::Duration;
use tokio::time::Instant; use tokio::time::Instant;
static REVERSE_STORE: LazyLock<DashMap<String, String>> = LazyLock::new(DashMap::new);
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));}
pub static RATE_LIMITER: LazyLock<Rate> = LazyLock::new(|| Rate::new(Duration::from_secs(1))); // static REVERSE_STORE: LazyLock<DashMap<String, String>> = LazyLock::new(DashMap::new);
pub static LOCALHOST: LazyLock<Arc<str>> = LazyLock::new(|| Arc::from("localhost")); // pub static RATE_LIMITER: LazyLock<Rate> = LazyLock::new(|| Rate::new(Duration::from_secs(1)));
// pub static REQUESTS_4XX: LazyLock<Cache<IpAddr, u32>> = LazyLock::new(|| Cache::builder().time_to_live(Duration::from_secs(1)).build());
// pub static LOCALHOST: LazyLock<Arc<str>> = LazyLock::new(|| Arc::from("localhost"));
#[derive(Clone)] #[derive(Clone)]
pub struct LB { pub struct LB {
@@ -39,12 +38,12 @@ pub struct LB {
pub struct Context { pub struct Context {
backend_id: Option<String>, backend_id: Option<String>,
sticky_sessions: bool,
start_time: Instant, start_time: Instant,
hostname: Option<Arc<str>>, hostname: Option<Arc<str>>,
upstream_peer: Option<Arc<InnerMap>>, upstream_peer: Option<Arc<InnerMap>>,
extraparams: arc_swap::Guard<Arc<Extraparams>>, extraparams: arc_swap::Guard<Arc<Extraparams>>,
client_headers: Option<Vec<(String, Arc<str>)>>, client_headers: Option<Vec<(String, Arc<str>)>>,
x4xx_limit: Option<u32>,
} }
#[async_trait] #[async_trait]
@@ -53,12 +52,12 @@ impl ProxyHttp for LB {
fn new_ctx(&self) -> Self::CTX { fn new_ctx(&self) -> Self::CTX {
Context { Context {
backend_id: None, backend_id: None,
sticky_sessions: false,
start_time: Instant::now(), start_time: Instant::now(),
hostname: None, hostname: None,
upstream_peer: None, upstream_peer: None,
extraparams: self.extraparams.load(), extraparams: self.extraparams.load(),
client_headers: None, client_headers: None,
x4xx_limit: None,
} }
} }
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> {
@@ -66,7 +65,7 @@ impl ProxyHttp for LB {
let hostname = return_header_host_from_upstream(session, &self.ump_upst); let hostname = return_header_host_from_upstream(session, &self.ump_upst);
_ctx.hostname = hostname; _ctx.hostname = hostname;
let mut backend_id = None; let mut backend_id = None;
if _ctx.extraparams.sticky_sessions { if let Some(_) = _ctx.extraparams.sticky_sessions {
if let Some(cookies) = session.req_header().headers.get("cookie") { if let Some(cookies) = session.req_header().headers.get("cookie") {
if let Ok(cookie_str) = cookies.to_str() { if let Ok(cookie_str) = cookies.to_str() {
if let Some(pos) = cookie_str.find("backend_id=") { if let Some(pos) = cookie_str.find("backend_id=") {
@@ -85,13 +84,28 @@ impl ProxyHttp for LB {
None => return Ok(false), None => return Ok(false),
Some(ref innermap) => { Some(ref innermap) => {
if let Some(auth) = _ctx.extraparams.authentication.as_ref().or(innermap.authorization.as_ref()) { if let Some(auth) = _ctx.extraparams.authentication.as_ref().or(innermap.authorization.as_ref()) {
if !authenticate(&auth.auth_type, &auth.auth_cred, session).await { if !authenticate(&auth, session).await {
let _ = session.respond_error(401).await; let _ = session.respond_error(401).await;
warn!("Forbidden: {:?}, {}", session.client_addr(), session.req_header().uri.path()); warn!("Forbidden: {:?}, {}", session.client_addr(), session.req_header().uri.path());
return Ok(true); return Ok(true);
} }
} }
if let Some(rate) = innermap.x4xx_limit.or(_ctx.extraparams.x4xx_limit) {
_ctx.x4xx_limit = innermap.x4xx_limit;
let rate_key = session.client_addr().and_then(|addr| addr.as_inet()).map(|inet| inet.ip());
if let Some(rk) = rate_key {
let count = REQUESTS_4XX.get(&rk).unwrap_or(0);
if count > rate {
let header = ResponseHeader::build(429, None)?;
session.set_keepalive(None);
session.write_response_header(Box::new(header), true).await?;
if let (Some(oi), Some(oa)) = (&_ctx.hostname, rate_key) {
warn!("Limit 4XX: {}-rps exceed on {} from {} path {}", rate, oi, oa, session.req_header().uri.path());
}
return Ok(true);
}
}
}
if let Some(rate) = innermap.rate_limit.or(_ctx.extraparams.rate_limit) { if let Some(rate) = innermap.rate_limit.or(_ctx.extraparams.rate_limit) {
let rate_key = session.client_addr().and_then(|addr| addr.as_inet()).map(|inet| inet.ip()); let rate_key = session.client_addr().and_then(|addr| addr.as_inet()).map(|inet| inet.ip());
let curr_window_requests = RATE_LIMITER.observe(&rate_key, 1); let curr_window_requests = RATE_LIMITER.observe(&rate_key, 1);
@@ -99,7 +113,9 @@ impl ProxyHttp for LB {
let header = ResponseHeader::build(429, None)?; let header = ResponseHeader::build(429, None)?;
session.set_keepalive(None); session.set_keepalive(None);
session.write_response_header(Box::new(header), true).await?; session.write_response_header(Box::new(header), true).await?;
debug!("Rate limited: {:?}, {}", rate_key, rate); if let (Some(oi), Some(oa)) = (&_ctx.hostname, rate_key) {
warn!("Limit: {}-rps exceed on {} from {}", rate, oi, oa);
}
return Ok(true); return Ok(true);
} }
} }
@@ -161,23 +177,37 @@ impl ProxyHttp for LB {
peer.options.verify_cert = false; peer.options.verify_cert = false;
peer.options.verify_hostname = false; peer.options.verify_hostname = false;
} }
if ctx.extraparams.sticky_sessions { /*
Experimental optionsv
The following TCP optimizations were tested but caused performance degrade under heavy load:
peer.options.tcp_keepalive = Some(TcpKeepalive {
idle: Duration::from_secs(60),
interval: Duration::from_secs(10),
count: 5,
user_timeout: Duration::from_secs(30),
});
peer.options.idle_timeout = Some(Duration::from_secs(300));
peer.options.tcp_recv_buf = Some(128 * 1024);
End of experimental options
*/
if let Some(_) = ctx.extraparams.sticky_sessions {
let mut s = String::with_capacity(64); let mut s = String::with_capacity(64);
write!( write!(
&mut s, &mut s,
"{}:{}:{}:{}:{}:{}:{}:{:?}", "{}:{}:{}:{}:{}:{}:{}:{}:{:?}",
hostname, hostname,
innermap.address, innermap.address,
innermap.port, innermap.port,
innermap.is_http2, innermap.is_http2,
innermap.to_https, innermap.to_https,
innermap.x4xx_limit.unwrap_or_default(),
innermap.rate_limit.unwrap_or_default(), innermap.rate_limit.unwrap_or_default(),
innermap.healthcheck.unwrap_or_default(), innermap.healthcheck.unwrap_or_default(),
innermap.authorization innermap.authorization
) )
.unwrap_or(()); .unwrap_or(());
ctx.backend_id = Some(s); ctx.backend_id = Some(s);
ctx.sticky_sessions = true;
} }
Ok(peer) Ok(peer)
} }
@@ -210,11 +240,11 @@ 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<()> {
if let Some(client_ip) = session.client_addr() { if let Some(ip) = session.client_addr().and_then(|a| a.as_inet()).map(|i| i.ip()) {
IP_BUFFER.with(|buffer| { IP_BUFFER.with(|buffer| {
let mut buf = buffer.borrow_mut(); let mut buf = buffer.borrow_mut();
buf.clear(); buf.clear();
write!(buf, "{}", client_ip).unwrap_or(()); write!(buf, "{}", ip).unwrap_or(());
upstream_request.append_header("X-Forwarded-For", buf.as_str()).unwrap_or(false); upstream_request.append_header("X-Forwarded-For", buf.as_str()).unwrap_or(false);
}); });
} }
@@ -237,7 +267,7 @@ impl ProxyHttp for LB {
Ok(()) Ok(())
} }
async fn response_filter(&self, _session: &mut Session, _upstream_response: &mut ResponseHeader, ctx: &mut Self::CTX) -> Result<()> { async fn response_filter(&self, _session: &mut Session, _upstream_response: &mut ResponseHeader, ctx: &mut Self::CTX) -> Result<()> {
if ctx.sticky_sessions { if let Some(val) = ctx.extraparams.sticky_sessions {
if let Some(bid) = &ctx.backend_id { if let Some(bid) = &ctx.backend_id {
let tt = if let Some(existing) = REVERSE_STORE.get(bid) { let tt = if let Some(existing) = REVERSE_STORE.get(bid) {
existing.value().clone() existing.value().clone()
@@ -255,7 +285,10 @@ impl ProxyHttp for LB {
let mut buf = String::with_capacity(80); let mut buf = String::with_capacity(80);
buf.push_str("backend_id="); buf.push_str("backend_id=");
buf.push_str(&tt); buf.push_str(&tt);
buf.push_str("; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax"); buf.push_str("; Path=/; Max-Age=");
buf.push_str(&val.to_string());
buf.push_str("; HttpOnly; SameSite=Lax");
// buf.push_str("; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax");
let _ = _upstream_response.insert_header("set-cookie", buf.as_str()); let _ = _upstream_response.insert_header("set-cookie", buf.as_str());
} }
} }
@@ -280,6 +313,14 @@ impl ProxyHttp for LB {
}; };
calc_metrics(m); calc_metrics(m);
ACTIVE_SESSIONS.dec(); ACTIVE_SESSIONS.dec();
if let Some(_) = ctx.x4xx_limit.or(ctx.extraparams.x4xx_limit) {
if 400 <= response_code && response_code <= 499 {
if let Some(ip) = session.client_addr().and_then(|a| a.as_inet()).map(|i| i.ip()) {
let current = REQUESTS_4XX.get(&ip).unwrap_or(0);
REQUESTS_4XX.insert(ip, current + 1);
}
}
}
} }
} }

View File

@@ -6,16 +6,25 @@ use crate::utils::structs::Extraparams;
use crate::utils::tools::*; use crate::utils::tools::*;
use crate::web::proxyhttp::LB; use crate::web::proxyhttp::LB;
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use ctrlc;
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_core::listeners::tls::TlsSettings; use pingora_core::listeners::tls::TlsSettings;
use pingora_core::listeners::TcpSocketOptions;
use pingora_core::prelude::{background_service, Opt}; use pingora_core::prelude::{background_service, Opt};
use pingora_core::protocols::TcpKeepalive;
use pingora_core::server::Server; use pingora_core::server::Server;
use privdrop::reexports::libc::SIGQUIT;
use sd_notify::NotifyState;
use signal_hook::{
consts::{SIGINT, SIGTERM},
iterator::Signals,
};
use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::Arc; use std::sync::Arc;
use std::{fs, thread}; use std::time::Duration;
use std::{fs, process, thread};
pub fn run() { pub fn run() {
// default_provider().install_default().expect("Failed to install rustls crypto provider"); // default_provider().install_default().expect("Failed to install rustls crypto provider");
let parameters = Opt::parse_args(); let parameters = Opt::parse_args();
@@ -33,9 +42,10 @@ pub fn run() {
let ec_config = Arc::new(ArcSwap::from_pointee(Extraparams { let ec_config = Arc::new(ArcSwap::from_pointee(Extraparams {
to_https: None, to_https: None,
sticky_sessions: false, sticky_sessions: None,
authentication: None, authentication: None,
rate_limit: None, rate_limit: None,
x4xx_limit: None,
})); }));
let cfg = Arc::new(maincfg); let cfg = Arc::new(maincfg);
@@ -54,12 +64,34 @@ pub fn run() {
info!("TLS grade set to: [ {} ]", grade); info!("TLS grade set to: [ {} ]", grade);
let bg_srvc = background_service("bgsrvc", lb.clone()); let bg_srvc = background_service("bgsrvc", lb.clone());
let mut proxy = pingora_proxy::http_proxy_service(&server.configuration, lb.clone());
let bind_address_http = cfg.proxy_address_http.clone(); let bind_address_http = cfg.proxy_address_http.clone();
let bind_address_tls = cfg.proxy_address_tls.clone(); let bind_address_tls = cfg.proxy_address_tls.clone();
let mut proxy = pingora_proxy::http_proxy_service(&server.configuration, lb.clone());
check_priv(bind_address_http.as_str()); check_priv(bind_address_http.as_str());
// let mut tcp_options: Option<TcpSocketOptions> = Some(TcpSocketOptions::default());
// let mut tcp_options = TcpSocketOptions::default();
let mut tcp_options: Option<TcpSocketOptions> = None;
if let Some(idle) = cfg.tcp_keepalive_idle {
let mut to = TcpSocketOptions::default();
to.tcp_keepalive = Some(TcpKeepalive {
idle: Duration::from_secs(idle),
interval: Duration::from_secs(cfg.tcp_keepalive_interval.unwrap_or(10)),
user_timeout: Default::default(),
count: cfg.tcp_keepalive_count.unwrap_or(5usize),
});
tcp_options = Some(to);
info!(
"Applying kernel tcp_keepalive parameters: idle {}, interval {}, count {}",
idle,
cfg.tcp_keepalive_interval.unwrap_or(60),
cfg.tcp_keepalive_count.unwrap_or(5),
);
}
if let Some(bind_address_tls) = bind_address_tls { if let Some(bind_address_tls) = bind_address_tls {
check_priv(bind_address_tls.as_str()); check_priv(bind_address_tls.as_str());
let (tx, rx): (Sender<Vec<CertificateConfig>>, Receiver<Vec<CertificateConfig>>) = channel(); let (tx, rx): (Sender<Vec<CertificateConfig>>, Receiver<Vec<CertificateConfig>>) = channel();
@@ -87,7 +119,7 @@ pub fn run() {
tls_settings.set_servername_callback(move |ssl_ref: &mut SslRef, ssl_alert: &mut SslAlert| certs_for_callback.load().server_name_callback(ssl_ref, ssl_alert)); tls_settings.set_servername_callback(move |ssl_ref: &mut SslRef, ssl_alert: &mut SslAlert| certs_for_callback.load().server_name_callback(ssl_ref, ssl_alert));
tls_settings.set_alpn_select_callback(grades::prefer_h2); tls_settings.set_alpn_select_callback(grades::prefer_h2);
proxy.add_tls_with_settings(&bind_address_tls, None, tls_settings); proxy.add_tls_with_settings(&bind_address_tls, tcp_options.clone(), tls_settings);
let certs_for_watcher = certificates.clone(); let certs_for_watcher = certificates.clone();
thread::spawn(move || { thread::spawn(move || {
@@ -99,19 +131,34 @@ pub fn run() {
} }
}); });
} }
info!("Running HTTP listener on :{}", bind_address_http.as_str()); info!("Running HTTP listener on :{}", bind_address_http);
proxy.add_tcp(bind_address_http.as_str()); if let Some(tc) = tcp_options {
proxy.add_tcp_with_settings(&bind_address_http, tc);
} else {
proxy.add_tcp(&bind_address_http)
}
server.add_service(proxy); server.add_service(proxy);
server.add_service(bg_srvc); server.add_service(bg_srvc);
thread::spawn(move || server.run_forever()); thread::spawn(move || server.run_forever());
if let (Some(user), Some(group)) = (cfg.rungroup.clone(), cfg.runuser.clone()) { if let (Some(user), Some(group)) = (cfg.rungroup.clone(), cfg.runuser.clone()) {
drop_priv(user, group, cfg.proxy_address_http.clone(), cfg.proxy_address_tls.clone()); drop_priv(user, group, cfg.proxy_address_http.clone(), cfg.proxy_address_tls.clone());
} }
let _ = sd_notify::notify(&[NotifyState::Ready]);
let _ = fs::write(cfg.pid_file.clone().unwrap_or("/tmp/aralez.pid".to_string()), process::id().to_string());
let (tx, rx) = channel(); let mut signals = Signals::new(&[SIGINT, SIGTERM, SIGQUIT]).unwrap();
ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel.")).expect("Error setting Ctrl-C handler"); for sig in signals.forever() {
rx.recv().expect("Could not receive from channel."); match sig {
info!("Signal received ! Exiting..."); SIGINT => info!("SIGINT received! Exiting..."),
SIGTERM => info!("SIGTERM received! Exiting..."),
SIGQUIT => {
thread::sleep(Duration::from_secs(300));
info!("SIGQUIT received! Exiting...")
}
_ => unreachable!(),
}
break;
}
} }

View File

@@ -1,14 +1,12 @@
// use std::net::SocketAddr;
use crate::tls::acme::order::CHALLENGES;
// use axum_server::tls_openssl::OpenSSLConfig;
use crate::tls::acme::{account, order};
use crate::utils::discovery::APIUpstreamProvider; use crate::utils::discovery::APIUpstreamProvider;
use crate::utils::jwt::Claims; use crate::utils::jwt::Claims;
use crate::utils::metrics::{get_memory_usage, get_open_files, MEMORY_USAGE, OPEN_FILES};
use crate::utils::structs::{Config, Configuration, UpstreamsDashMap}; use crate::utils::structs::{Config, Configuration, UpstreamsDashMap};
use crate::utils::tools::{upstreams_liveness_json, upstreams_to_json}; use crate::utils::tools::{upstreams_liveness_json, upstreams_to_json};
use crate::web::acme::{acme_create, acme_order, http01_challenge};
use axum::body::Body; use axum::body::Body;
use axum::extract::{Query, State}; use axum::extract::{Query, State};
use axum::http::{header::HeaderMap, Response, StatusCode}; use axum::http::{Response, StatusCode};
use axum::response::IntoResponse; use axum::response::IntoResponse;
use axum::routing::{any, get, post}; use axum::routing::{any, get, post};
use axum::{Json, Router}; use axum::{Json, Router};
@@ -18,11 +16,14 @@ use jsonwebtoken::{encode, EncodingKey, Header};
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use prometheus::{gather, Encoder, TextEncoder}; use prometheus::{gather, Encoder, TextEncoder};
use serde::Serialize; use serde::Serialize;
use signal_hook::{consts::SIGQUIT, iterator::Signals};
use std::collections::HashMap; use std::collections::HashMap;
use std::net::SocketAddr;
use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{Duration, SystemTime, UNIX_EPOCH};
use subtle::ConstantTimeEq;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tokio::sync::mpsc;
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
@@ -31,10 +32,10 @@ struct OutToken {
} }
#[derive(Clone)] #[derive(Clone)]
struct AppState { pub(crate) struct AppState {
master_key: String, master_key: Option<String>,
cert_creds: String, pub(crate) cert_creds: String,
certs_dir: String, pub(crate) certs_dir: String,
upstreams_file: String, upstreams_file: String,
config_sender: Sender<Configuration>, config_sender: Sender<Configuration>,
config_api_enabled: bool, config_api_enabled: bool,
@@ -57,11 +58,6 @@ pub async fn run_server(config: &APIUpstreamProvider, mut to_return: Sender<Conf
}; };
let app = Router::new() let app = Router::new()
// .route("/{*wildcard}", get(senderror)) // .route("/{*wildcard}", get(senderror))
// .route("/{*wildcard}", post(senderror))
// .route("/{*wildcard}", put(senderror))
// .route("/{*wildcard}", head(senderror))
// .route("/{*wildcard}", delete(senderror))
// .nest_service("/static", static_files)
.route("/jwt", post(jwt_gen)) .route("/jwt", post(jwt_gen))
.route("/acme_create", any(acme_create)) .route("/acme_create", any(acme_create))
.route("/acme_order/{*domain}", any(acme_order)) .route("/acme_order/{*domain}", any(acme_order))
@@ -71,37 +67,40 @@ pub async fn run_server(config: &APIUpstreamProvider, mut to_return: Sender<Conf
.route("/status", get(status)) .route("/status", get(status))
.with_state(app_state); .with_state(app_state);
// if let Some(value) = &config.tls_address { let mut static_handle: Option<tokio::task::JoinHandle<()>> = None;
// let cf = OpenSSLConfig::from_pem_file(config.tls_certificate.clone().unwrap(), config.tls_key_file.clone().unwrap()).unwrap();
// let addr: SocketAddr = value.parse().expect("Unable to parse socket address");
// let tls_app = app.clone();
// tokio::spawn(async move {
// if let Err(e) = axum_server::bind_openssl(addr, cf).serve(tls_app.into_make_service()).await {
// eprintln!("TLS server failed: {}", e);
// }
// });
// info!("Starting the TLS API server on: {}", value);
// }
if let (Some(address), Some(folder)) = (&config.file_server_address, &config.file_server_folder) { if let (Some(address), Some(folder)) = (&config.file_server_address, &config.file_server_folder) {
let static_listen = port_is_available("File Server", &address).await;
let static_files = ServeDir::new(folder); let static_files = ServeDir::new(folder);
let static_serve: Router = Router::new().fallback_service(static_files); let static_serve: Router = Router::new().fallback_service(static_files);
let static_listen = TcpListener::bind(address).await.unwrap(); // drop(tokio::spawn(async move { axum::serve(static_listen, static_serve).await.unwrap() }));
drop(tokio::spawn(async move { axum::serve(static_listen, static_serve).await.unwrap() })); static_handle = Some(tokio::spawn(async move { axum::serve(static_listen, static_serve).await.unwrap() }))
} }
let listener = TcpListener::bind(config.address.clone()).await.unwrap(); let listener = port_is_available("Config API", &config.address).await;
info!("Starting the API server on: {}", config.address); info!("Starting the API server on: {}", config.address);
axum::serve(listener, app).await.unwrap(); let api_server = tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });
let (tx, mut rx) = mpsc::channel(1);
std::thread::spawn(move || {
let mut signals = Signals::new(&[SIGQUIT]).unwrap();
for sig in signals.forever() {
tx.blocking_send(sig).unwrap();
break;
}
});
rx.recv().await;
api_server.abort();
if let Some(handle) = static_handle {
handle.abort();
}
info!("Exiting...");
} }
async fn conf(State(st): State<AppState>, Query(params): Query<HashMap<String, String>>, headers: HeaderMap, content: String) -> impl IntoResponse { async fn conf(State(st): State<AppState>, Query(params): Query<HashMap<String, String>>, content: String) -> impl IntoResponse {
if !st.config_api_enabled { if !st.config_api_enabled {
return Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Config API is disabled !\n")).unwrap(); return Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Config API is disabled !\n")).unwrap();
} }
// if let Some(s) = headers.get("x-api-key").and_then(|v| v.to_str().ok()).or(params.get("key").map(|s| s.as_str())) {
if key_authorization(&headers, &params, &st.master_key) {
let strcontent = content.as_str(); let strcontent = content.as_str();
let parsed = serde_yml::from_str::<Config>(strcontent); let parsed = serde_yml::from_str::<Config>(strcontent);
match parsed { match parsed {
@@ -111,16 +110,13 @@ async fn conf(State(st): State<AppState>, Query(params): Query<HashMap<String, S
} else { } else {
drop(tokio::spawn(async move { apply_config(content.as_str(), st, false).await })); drop(tokio::spawn(async move { apply_config(content.as_str(), st, false).await }));
} }
// apply_config(content.as_str(), st).await; Response::builder().status(StatusCode::OK).body(Body::from("Accepted! Applying in background\n")).unwrap()
return Response::builder().status(StatusCode::OK).body(Body::from("Accepted! Applying in background\n")).unwrap();
} }
Err(err) => { Err(err) => {
error!("Failed to parse upstreams file: {}", err); error!("Failed to parse upstreams file: {}", err);
return Response::builder().status(StatusCode::BAD_GATEWAY).body(Body::from(format!("Failed: {}\n", err))).unwrap(); Response::builder().status(StatusCode::BAD_GATEWAY).body(Body::from(format!("Failed: {}\n", err))).unwrap()
} }
} }
}
Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Access Denied !\n")).unwrap()
} }
async fn apply_config(content: &str, mut st: AppState, save: bool) { async fn apply_config(content: &str, mut st: AppState, save: bool) {
@@ -137,7 +133,8 @@ async fn apply_config(content: &str, mut st: AppState, save: bool) {
} }
async fn jwt_gen(State(state): State<AppState>, Json(payload): Json<Claims>) -> (StatusCode, Json<OutToken>) { async fn jwt_gen(State(state): State<AppState>, Json(payload): Json<Claims>) -> (StatusCode, Json<OutToken>) {
if payload.master_key == state.master_key { if let Some(master_key) = &state.master_key {
if &payload.master_key == master_key {
let now = SystemTime::now() + Duration::from_secs(payload.exp * 60); let now = SystemTime::now() + Duration::from_secs(payload.exp * 60);
let expire = now.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(); let expire = now.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
@@ -166,9 +163,19 @@ async fn jwt_gen(State(state): State<AppState>, Json(payload): Json<Claims>) ->
warn!("Unauthorised JWT generate request: {:?}", tok); warn!("Unauthorised JWT generate request: {:?}", tok);
(StatusCode::FORBIDDEN, Json(tok)) (StatusCode::FORBIDDEN, Json(tok))
} }
} else {
let tok = OutToken {
token: "ERROR Getting JWT_KEY environment variable".to_string(),
};
error!("ERROR Getting JWT_KEY environment variable");
(StatusCode::INTERNAL_SERVER_ERROR, Json(tok))
}
} }
async fn metrics() -> impl IntoResponse { async fn metrics() -> impl IntoResponse {
MEMORY_USAGE.set(get_memory_usage() as i64);
OPEN_FILES.set(get_open_files() as i64);
let metric_families = gather(); let metric_families = gather();
let encoder = TextEncoder::new(); let encoder = TextEncoder::new();
let mut buffer = Vec::new(); let mut buffer = Vec::new();
@@ -220,85 +227,26 @@ async fn status(State(st): State<AppState>, Query(params): Query<HashMap<String,
.unwrap() .unwrap()
} }
#[allow(clippy::needless_return)] pub async fn port_is_available(name: &str, address: &str) -> TcpListener {
async fn acme_create(State(state): State<AppState>, Query(params): Query<HashMap<String, String>>, headers: HeaderMap) -> impl IntoResponse { let addr = SocketAddr::from_str(address)
if !key_authorization(&headers, &params, &state.master_key) { .unwrap_or_else(|e| panic!("{}: Invalid address format: {:?}", name, e));
return Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Access Denied !\n")).unwrap(); let t = Duration::from_secs(2);
}
match account::load_or_create(state.cert_creds.as_str()).await { //if addr.ip() == IpAddr::V4(Ipv4Addr::UNSPECIFIED) {
Ok(txt) => { // addr.set_ip(IpAddr::V4(Ipv4Addr::LOCALHOST));
return Response::builder() //}
.status(StatusCode::OK) let p = addr.port();
.header("Content-Type", "text/plain") loop {
.body(Body::from(txt)) match TcpListener::bind(addr).await {
.unwrap() Ok(listener) => {
return listener;
} }
Err(e) => { Err(_) => {
return Response::builder() warn!("{} port is not available: {} will try again in {:?}", name, p, t);
.status(StatusCode::INTERNAL_SERVER_ERROR) tokio::time::sleep(t).await;
.body(Body::from(format!("Failed to create account: {}", e)))
.unwrap()
}
};
}
#[allow(clippy::needless_return)]
async fn acme_order(
State(state): State<AppState>,
axum::extract::Path(domain): axum::extract::Path<String>,
Query(params): Query<HashMap<String, String>>,
headers: HeaderMap,
) -> impl IntoResponse {
if !key_authorization(&headers, &params, &state.master_key) {
return Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Access Denied !\n")).unwrap();
}
let domain_clean = domain.trim_matches('/');
match order::order(domain_clean, state.cert_creds.as_str(), state.certs_dir).await {
Ok(txt) => {
return Response::builder()
.status(StatusCode::OK)
.header("Content-Type", "text/plain")
.body(Body::from(txt))
.unwrap()
}
Err(e) => {
return Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::from(format!("Failed to order a certificate: {}", e)))
.unwrap()
}
};
}
pub async fn http01_challenge(axum::extract::Path(token): axum::extract::Path<String>) -> impl IntoResponse {
if let Ok(challenges) = CHALLENGES.read() {
// for k in challenges.iter() {
// println!(" ==> {} : {}", k.0, k.1);
// }
if let Some(key_authorization) = challenges.get(&token) {
return Response::builder()
.status(StatusCode::OK)
.header("Content-Type", "text/plain")
.body(Body::from(key_authorization.clone()))
.unwrap();
} }
} }
Response::builder()
.status(StatusCode::NOT_FOUND)
.header("Content-Type", "text/plain")
.body(Body::from("Not found"))
.unwrap()
}
fn key_authorization(headers: &HeaderMap, params: &HashMap<String, String>, masterkey: &str) -> bool {
if let Some(s) = headers.get("x-api-key").and_then(|v| v.to_str().ok()).or(params.get("key").map(|s| s.as_str())) {
if s.as_bytes().ct_eq(masterkey.as_bytes()).into() {
return true;
} }
}
false
} }
// -- ⚝ by Dave -- in NeoVim ⚝ -- // -- ⚝ by Dave -- in NeoVim ⚝ --