mirror of
https://github.com/sadoyan/aralez.git
synced 2026-04-30 14:58:38 +08:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bee307793c | ||
|
|
6e83775127 | ||
|
|
baded40e6e | ||
|
|
c0a419f6f7 | ||
|
|
8aff2fa875 | ||
|
|
9b4ee26a2b | ||
|
|
f135106a44 | ||
|
|
389c12119a | ||
|
|
93a8661281 | ||
|
|
0505ce2849 | ||
|
|
72ed870538 | ||
|
|
68140d0cf0 | ||
|
|
7b9b206c13 | ||
|
|
4706b281bc | ||
|
|
1f8efc6af7 | ||
|
|
9f595b2709 | ||
|
|
ed44516015 | ||
|
|
17da7862e3 | ||
|
|
24d00da855 | ||
|
|
c9422759aa |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,6 +1,6 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
github: sadoyan
|
||||||
patreon: # Replace with a single Patreon username
|
patreon: # Replace with a single Patreon username
|
||||||
open_collective: # Replace with a single Open Collective username
|
open_collective: # Replace with a single Open Collective username
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
|
|||||||
1568
Cargo.lock
generated
1568
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
29
Cargo.toml
29
Cargo.toml
@@ -11,7 +11,7 @@ panic = "abort"
|
|||||||
strip = true
|
strip = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.49.0", features = ["full"] }
|
tokio = { version = "1.52.1", features = ["full"] }
|
||||||
pingora = { version = "0.8.0", features = ["lb", "openssl"] } # openssl, rustls, boringssl
|
pingora = { version = "0.8.0", features = ["lb", "openssl"] } # openssl, rustls, boringssl
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
dashmap = "7.0.0-rc2"
|
dashmap = "7.0.0-rc2"
|
||||||
@@ -20,34 +20,29 @@ 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"
|
||||||
env_logger = "0.11.9"
|
env_logger = "0.11.10"
|
||||||
log = "0.4.29"
|
log = "0.4.29"
|
||||||
futures = "0.3.32"
|
futures = "0.3.32"
|
||||||
notify = "9.0.0-rc.2"
|
notify = "9.0.0-rc.3"
|
||||||
axum = { version = "0.8.8" }
|
axum = { version = "0.8.9" }
|
||||||
#axum-server = { version = "0.8.0" }
|
reqwest = { version = "0.13.2", features = ["json", "stream", "blocking"] }
|
||||||
reqwest = { version = "0.13.2", features = ["json", "stream"] }
|
serde_yml = "0.0.12"
|
||||||
serde_yaml = "0.9.34-deprecated"
|
rand = "0.10.1"
|
||||||
rand = "0.10.0"
|
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
#jsonwebtoken = { version = "10.3.0", features = ["aws_lc_rs"] }
|
|
||||||
#jsonwebtoken = { version = "10.3.0", default-features = false, features = ["use_pem"] }
|
|
||||||
jsonwebtoken = { version = "10.3.0", default-features = false, features = ["use_pem", "rust_crypto"] }
|
jsonwebtoken = { version = "10.3.0", default-features = false, features = ["use_pem", "rust_crypto"] }
|
||||||
tonic = "0.14.5"
|
tonic = "0.14.5"
|
||||||
sha2 = { version = "0.11.0-rc.5", default-features = false }
|
sha2 = { version = "0.11.0-rc.5", default-features = false }
|
||||||
base16ct = { version = "1.0.0", features = ["alloc"] }
|
base16ct = { version = "1.0.0", features = ["alloc"] }
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
arc-swap = "1.8.2"
|
arc-swap = "1.9.1"
|
||||||
mimalloc = { version = "0.1.48", default-features = false }
|
mimalloc = { version = "0.1.50", default-features = false }
|
||||||
prometheus = "0.14.0"
|
prometheus = "0.14.0"
|
||||||
lazy_static = "1.5.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.8", features = ["fs"] }
|
tower-http = { version = "0.6.8", features = ["fs"] }
|
||||||
once_cell = "1.21.3"
|
|
||||||
privdrop = "0.5.6"
|
privdrop = "0.5.6"
|
||||||
ctrlc = "3.5.2"
|
ctrlc = "3.5.2"
|
||||||
port_check = "0.3.0"
|
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
http = "1.4.0"
|
subtle = "2.6.1"
|
||||||
itoa = "1.0.14"
|
moka = { version = "0.12.1", features = ["sync"] }
|
||||||
|
ahash = "0.8.12"
|
||||||
|
|||||||
@@ -123,9 +123,11 @@ Make the binary executable `chmod 755 ./aralez-VERSION` and run.
|
|||||||
File names:
|
File names:
|
||||||
|
|
||||||
| File Name | Description |
|
| File Name | Description |
|
||||||
|---------------------------|--------------------------------------------------------------------------|
|
|---------------------------------|--------------------------------------------------------------------------|
|
||||||
| `aralez-x86_64-musl.gz` | Static Linux x86_64 binary, without any system dependency |
|
| `aralez-x86_64-musl.gz` | Static Linux x86_64 binary, without any system dependency |
|
||||||
| `aralez-x86_64-glibc.gz` | Dynamic Linux x86_64 binary, with minimal system dependencies |
|
| `aralez-x86_64-glibc.gz` | Dynamic Linux x86_64 binary, with minimal system dependencies |
|
||||||
|
| `aralez-x86_64-compat-musl.gz` | Static Linux x86_64 binary, compatible with old pre Haswell CPUs |
|
||||||
|
| `aralez-x86_64-compat-glibc.gz` | Dynamic Linux x86_64 binary, compatible with old pre Haswell CPUs |
|
||||||
| `aralez-aarch64-musl.gz` | Static Linux ARM64 binary, without any system dependency |
|
| `aralez-aarch64-musl.gz` | Static Linux ARM64 binary, without any system dependency |
|
||||||
| `aralez-aarch64-glibc.gz` | Dynamic Linux ARM64 binary, with minimal system dependencies |
|
| `aralez-aarch64-glibc.gz` | Dynamic Linux ARM64 binary, with minimal system dependencies |
|
||||||
| `sadoyan/aralez` | Docker image on Debian 13 slim (https://hub.docker.com/r/sadoyan/aralez) |
|
| `sadoyan/aralez` | Docker image on Debian 13 slim (https://hub.docker.com/r/sadoyan/aralez) |
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ kubernetes:
|
|||||||
path: "/"
|
path: "/"
|
||||||
upstream: "webapi-service"
|
upstream: "webapi-service"
|
||||||
- hostname: "webapi-service"
|
- hostname: "webapi-service"
|
||||||
upstream: "vt-console-service"
|
upstream: "console-service"
|
||||||
path: "/one"
|
path: "/one"
|
||||||
client_headers:
|
client_headers:
|
||||||
- "X-Some-Thing:Yaaaaaaaaaaaaaaa"
|
- "X-Some-Thing:Yaaaaaaaaaaaaaaa"
|
||||||
@@ -51,7 +51,7 @@ kubernetes:
|
|||||||
rate_limit: 100
|
rate_limit: 100
|
||||||
to_https: false
|
to_https: false
|
||||||
- hostname: "webapi-service"
|
- hostname: "webapi-service"
|
||||||
upstream: "vt-rambulik-service"
|
upstream: "rambul-service"
|
||||||
path: "/two"
|
path: "/two"
|
||||||
- hostname: "websocket-service"
|
- hostname: "websocket-service"
|
||||||
upstream: "websocket-service"
|
upstream: "websocket-service"
|
||||||
@@ -72,6 +72,9 @@ upstreams:
|
|||||||
- "127.0.0.4:8000"
|
- "127.0.0.4:8000"
|
||||||
- "127.0.0.5:8000"
|
- "127.0.0.5: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:
|
server_headers:
|
||||||
- "X-Forwarded-Proto:https"
|
- "X-Forwarded-Proto:https"
|
||||||
@@ -107,9 +110,10 @@ upstreams:
|
|||||||
healthcheck: false
|
healthcheck: false
|
||||||
servers:
|
servers:
|
||||||
- "127.0.0.1:8001"
|
- "127.0.0.1:8001"
|
||||||
localpost:
|
rdr.mydomain.com:
|
||||||
paths:
|
paths:
|
||||||
"/":
|
"/":
|
||||||
to_https: false
|
redirect_to: "https://som.other.domain:6194"
|
||||||
|
healthcheck: false
|
||||||
servers:
|
servers:
|
||||||
- "127.0.0.1:9000"
|
- "127.0.0.1:8080"
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
|
mod tls;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod web;
|
mod web;
|
||||||
|
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||||
|
// pub static A: CountingAllocator = CountingAllocator;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
web::start::run();
|
web::start::run();
|
||||||
|
|||||||
2
src/tls.rs
Normal file
2
src/tls.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod grades;
|
||||||
|
pub mod load;
|
||||||
75
src/tls/grades.rs
Normal file
75
src/tls/grades.rs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
use log::{info, warn};
|
||||||
|
use pingora::tls::ssl::{select_next_proto, AlpnError, SslRef, SslVersion};
|
||||||
|
use pingora_core::listeners::tls::TlsSettings;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CipherSuite {
|
||||||
|
pub high: &'static str,
|
||||||
|
pub medium: &'static str,
|
||||||
|
pub legacy: &'static str,
|
||||||
|
}
|
||||||
|
const CIPHERS: CipherSuite = CipherSuite {
|
||||||
|
high: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305",
|
||||||
|
medium: "ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:AES128-GCM-SHA256",
|
||||||
|
legacy: "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH",
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TlsGrade {
|
||||||
|
HIGH,
|
||||||
|
MEDIUM,
|
||||||
|
LEGACY,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TlsGrade {
|
||||||
|
pub fn from_str(s: &str) -> Option<Self> {
|
||||||
|
match s.to_ascii_lowercase().as_str() {
|
||||||
|
"high" => Some(TlsGrade::HIGH),
|
||||||
|
"medium" => Some(TlsGrade::MEDIUM),
|
||||||
|
"unsafe" => Some(TlsGrade::LEGACY),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn prefer_h2<'a>(_ssl: &mut SslRef, alpn_in: &'a [u8]) -> Result<&'a [u8], AlpnError> {
|
||||||
|
match select_next_proto("\x02h2\x08http/1.1".as_bytes(), alpn_in) {
|
||||||
|
Some(p) => Ok(p),
|
||||||
|
_ => Err(AlpnError::NOACK),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_tsl_grade(tls_settings: &mut TlsSettings, grade: &str) {
|
||||||
|
let config_grade = TlsGrade::from_str(grade);
|
||||||
|
match config_grade {
|
||||||
|
Some(TlsGrade::HIGH) => {
|
||||||
|
let _ = tls_settings.set_min_proto_version(Some(SslVersion::TLS1_2));
|
||||||
|
// let _ = tls_settings.set_max_proto_version(Some(SslVersion::TLS1_3));
|
||||||
|
let _ = tls_settings.set_cipher_list(CIPHERS.high);
|
||||||
|
// let _ = tls_settings.set_ciphersuites(CIPHERS.high);
|
||||||
|
let _ = tls_settings.set_cipher_list(CIPHERS.high);
|
||||||
|
info!("TLS grade: {:?}, => HIGH", tls_settings.options());
|
||||||
|
}
|
||||||
|
Some(TlsGrade::MEDIUM) => {
|
||||||
|
let _ = tls_settings.set_min_proto_version(Some(SslVersion::TLS1));
|
||||||
|
let _ = tls_settings.set_cipher_list(CIPHERS.medium);
|
||||||
|
// let _ = tls_settings.set_ciphersuites(CIPHERS.medium);
|
||||||
|
let _ = tls_settings.set_cipher_list(CIPHERS.medium);
|
||||||
|
info!("TLS grade: {:?}, => MEDIUM", tls_settings.options());
|
||||||
|
}
|
||||||
|
Some(TlsGrade::LEGACY) => {
|
||||||
|
let _ = tls_settings.set_min_proto_version(Some(SslVersion::SSL3));
|
||||||
|
let _ = tls_settings.set_cipher_list(CIPHERS.legacy);
|
||||||
|
// let _ = tls_settings.set_ciphersuites(CIPHERS.legacy);
|
||||||
|
let _ = tls_settings.set_cipher_list(CIPHERS.legacy);
|
||||||
|
warn!("TLS grade: {:?}, => UNSAFE", tls_settings.options());
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Defaults to MEDIUM
|
||||||
|
let _ = tls_settings.set_min_proto_version(Some(SslVersion::TLS1));
|
||||||
|
let _ = tls_settings.set_cipher_list(CIPHERS.medium);
|
||||||
|
// let _ = tls_settings.set_ciphersuites(CIPHERS.medium);
|
||||||
|
let _ = tls_settings.set_cipher_list(CIPHERS.medium);
|
||||||
|
warn!("TLS grade is not detected defaulting top MEDIUM");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
|
use crate::tls::grades;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use log::{error, info, warn};
|
use log::error;
|
||||||
use pingora::tls::ssl::{select_next_proto, AlpnError, NameType, SniError, SslAlert, SslContext, SslFiletype, SslMethod, SslRef, SslVersion};
|
use pingora::tls::ssl::{NameType, SniError, SslAlert, SslContext, SslFiletype, SslMethod, SslRef};
|
||||||
use pingora_core::listeners::tls::TlsSettings;
|
|
||||||
use rustls_pemfile::{read_one, Item};
|
use rustls_pemfile::{read_one, Item};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@@ -10,7 +10,6 @@ use std::io::BufReader;
|
|||||||
use x509_parser::extensions::GeneralName;
|
use x509_parser::extensions::GeneralName;
|
||||||
use x509_parser::nom::Err as NomErr;
|
use x509_parser::nom::Err as NomErr;
|
||||||
use x509_parser::prelude::*;
|
use x509_parser::prelude::*;
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Debug)]
|
#[derive(Clone, Deserialize, Debug)]
|
||||||
pub struct CertificateConfig {
|
pub struct CertificateConfig {
|
||||||
pub cert_path: String,
|
pub cert_path: String,
|
||||||
@@ -180,79 +179,7 @@ fn create_ssl_context(cert_path: &str, key_path: &str) -> Result<SslContext, Box
|
|||||||
let mut ctx = SslContext::builder(SslMethod::tls())?;
|
let mut ctx = SslContext::builder(SslMethod::tls())?;
|
||||||
ctx.set_certificate_chain_file(cert_path)?;
|
ctx.set_certificate_chain_file(cert_path)?;
|
||||||
ctx.set_private_key_file(key_path, SslFiletype::PEM)?;
|
ctx.set_private_key_file(key_path, SslFiletype::PEM)?;
|
||||||
ctx.set_alpn_select_callback(prefer_h2);
|
ctx.set_alpn_select_callback(grades::prefer_h2);
|
||||||
let built = ctx.build();
|
let built = ctx.build();
|
||||||
Ok(built)
|
Ok(built)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct CipherSuite {
|
|
||||||
pub high: &'static str,
|
|
||||||
pub medium: &'static str,
|
|
||||||
pub legacy: &'static str,
|
|
||||||
}
|
|
||||||
const CIPHERS: CipherSuite = CipherSuite {
|
|
||||||
high: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305",
|
|
||||||
medium: "ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:AES128-GCM-SHA256",
|
|
||||||
legacy: "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH",
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum TlsGrade {
|
|
||||||
HIGH,
|
|
||||||
MEDIUM,
|
|
||||||
LEGACY,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TlsGrade {
|
|
||||||
pub fn from_str(s: &str) -> Option<Self> {
|
|
||||||
match s.to_ascii_lowercase().as_str() {
|
|
||||||
"high" => Some(TlsGrade::HIGH),
|
|
||||||
"medium" => Some(TlsGrade::MEDIUM),
|
|
||||||
"unsafe" => Some(TlsGrade::LEGACY),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn prefer_h2<'a>(_ssl: &mut SslRef, alpn_in: &'a [u8]) -> Result<&'a [u8], AlpnError> {
|
|
||||||
match select_next_proto("\x02h2\x08http/1.1".as_bytes(), alpn_in) {
|
|
||||||
Some(p) => Ok(p),
|
|
||||||
_ => Err(AlpnError::NOACK),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_tsl_grade(tls_settings: &mut TlsSettings, grade: &str) {
|
|
||||||
let config_grade = TlsGrade::from_str(grade);
|
|
||||||
match config_grade {
|
|
||||||
Some(TlsGrade::HIGH) => {
|
|
||||||
let _ = tls_settings.set_min_proto_version(Some(SslVersion::TLS1_2));
|
|
||||||
// let _ = tls_settings.set_max_proto_version(Some(SslVersion::TLS1_3));
|
|
||||||
let _ = tls_settings.set_cipher_list(CIPHERS.high);
|
|
||||||
// let _ = tls_settings.set_ciphersuites(CIPHERS.high);
|
|
||||||
let _ = tls_settings.set_cipher_list(CIPHERS.high);
|
|
||||||
info!("TLS grade: {:?}, => HIGH", tls_settings.options());
|
|
||||||
}
|
|
||||||
Some(TlsGrade::MEDIUM) => {
|
|
||||||
let _ = tls_settings.set_min_proto_version(Some(SslVersion::TLS1));
|
|
||||||
let _ = tls_settings.set_cipher_list(CIPHERS.medium);
|
|
||||||
// let _ = tls_settings.set_ciphersuites(CIPHERS.medium);
|
|
||||||
let _ = tls_settings.set_cipher_list(CIPHERS.medium);
|
|
||||||
info!("TLS grade: {:?}, => MEDIUM", tls_settings.options());
|
|
||||||
}
|
|
||||||
Some(TlsGrade::LEGACY) => {
|
|
||||||
let _ = tls_settings.set_min_proto_version(Some(SslVersion::SSL3));
|
|
||||||
let _ = tls_settings.set_cipher_list(CIPHERS.legacy);
|
|
||||||
// let _ = tls_settings.set_ciphersuites(CIPHERS.legacy);
|
|
||||||
let _ = tls_settings.set_cipher_list(CIPHERS.legacy);
|
|
||||||
warn!("TLS grade: {:?}, => UNSAFE", tls_settings.options());
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// Defaults to MEDIUM
|
|
||||||
let _ = tls_settings.set_min_proto_version(Some(SslVersion::TLS1));
|
|
||||||
let _ = tls_settings.set_cipher_list(CIPHERS.medium);
|
|
||||||
// let _ = tls_settings.set_ciphersuites(CIPHERS.medium);
|
|
||||||
let _ = tls_settings.set_cipher_list(CIPHERS.medium);
|
|
||||||
warn!("TLS grade is not detected defaulting top MEDIUM");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,6 +11,5 @@ pub mod metrics;
|
|||||||
pub mod parceyaml;
|
pub mod parceyaml;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod structs;
|
pub mod structs;
|
||||||
pub mod tls;
|
|
||||||
pub mod tools;
|
pub mod tools;
|
||||||
// pub mod watchksecret;
|
// pub mod watchksecret;
|
||||||
|
|||||||
@@ -1,42 +1,187 @@
|
|||||||
use crate::utils::jwt::check_jwt;
|
use crate::utils::jwt::check_jwt;
|
||||||
|
// use reqwest::Client;
|
||||||
|
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 pingora_proxy::Session;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, LazyLock};
|
||||||
|
use subtle::ConstantTimeEq;
|
||||||
use urlencoding::decode;
|
use urlencoding::decode;
|
||||||
|
|
||||||
|
// use pingora::http::{RequestHeader, ResponseHeader, StatusCode};
|
||||||
|
use pingora::http::RequestHeader;
|
||||||
|
// --------------------------------- //
|
||||||
|
use pingora_core::connectors::http::Connector;
|
||||||
|
use pingora_core::upstreams::peer::HttpPeer;
|
||||||
|
use pingora_http::ResponseHeader;
|
||||||
|
// --------------------------------- //
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
trait AuthValidator {
|
trait AuthValidator {
|
||||||
fn validate(&self, session: &Session) -> bool;
|
async fn validate(&self, session: &mut Session) -> bool;
|
||||||
}
|
}
|
||||||
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<'a>(&'a str);
|
||||||
|
struct ForwardAuth<'a>(&'a str);
|
||||||
|
|
||||||
|
pub static AUTH_CONNECTOR: LazyLock<Connector> = LazyLock::new(|| Connector::new(None));
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl AuthValidator for ForwardAuth<'_> {
|
||||||
|
async fn validate(&self, session: &mut Session) -> bool {
|
||||||
|
let method = match session.req_header().method.as_str() {
|
||||||
|
"HEAD" => "HEAD",
|
||||||
|
_ => "GET",
|
||||||
|
};
|
||||||
|
|
||||||
|
let auth_url = self.0;
|
||||||
|
|
||||||
|
let (plain, tls) = if let Some(p) = auth_url.strip_prefix("http://") {
|
||||||
|
(p, false)
|
||||||
|
} else if let Some(p) = auth_url.strip_prefix("https://") {
|
||||||
|
(p, true)
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let (addr, uri) = if let Some(pos) = plain.find('/') {
|
||||||
|
(&plain[..pos], &plain[pos..])
|
||||||
|
} else {
|
||||||
|
(plain, "/")
|
||||||
|
};
|
||||||
|
|
||||||
|
let hp = match split_host_port(addr, tls) {
|
||||||
|
Some(hp) => hp,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let peer = HttpPeer::new((hp.0, hp.1), tls, hp.0.to_string());
|
||||||
|
|
||||||
|
let (mut http_session, _) = match AUTH_CONNECTOR.get_http_session(&peer).await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("ForwardAuth: connect failed: {}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut auth_req = match RequestHeader::build(method, uri.as_bytes(), None) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("ForwardAuth: failed to build request: {}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// auth_req.headers = session.req_header().headers.clone();
|
||||||
|
auth_req.insert_header("Host", addr).ok();
|
||||||
|
auth_req.insert_header("X-Forwarded-Uri", uri).ok();
|
||||||
|
auth_req.insert_header("X-Forwarded-Method", session.req_header().method.as_str()).ok();
|
||||||
|
if let Some(auth) = session.req_header().headers.get("authorization") {
|
||||||
|
auth_req.insert_header("Authorization", auth.clone()).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cookie) = session.req_header().headers.get("cookie") {
|
||||||
|
auth_req.insert_header("Cookie", cookie.clone()).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
if tls {
|
||||||
|
auth_req.insert_header("X-Forwarded-Proto", "https").ok();
|
||||||
|
} else {
|
||||||
|
auth_req.insert_header("X-Forwarded-Proto", "http").ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = http_session.write_request_header(Box::new(auth_req)).await {
|
||||||
|
log::warn!("ForwardAuth: write failed: {}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = match http_session.read_response_header().await {
|
||||||
|
Ok(_) => http_session.response_header().map(|r| r.status.as_u16()).unwrap_or(500),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("ForwardAuth: read failed: {}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let auth_headers_to_forward: Vec<(String, String)> = if let Some(resp_header) = http_session.response_header() {
|
||||||
|
resp_header
|
||||||
|
.headers
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(name, value)| {
|
||||||
|
let name_str = name.as_str();
|
||||||
|
if name_str.starts_with("x-") || name_str.starts_with("remote-") || name_str.starts_with("locat") {
|
||||||
|
value.to_str().ok().map(|v| (name_str.to_string(), v.to_string()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
AUTH_CONNECTOR.release_http_session(http_session, &peer, None).await;
|
||||||
|
|
||||||
|
if (200..300).contains(&status) {
|
||||||
|
for (name, value) in auth_headers_to_forward {
|
||||||
|
session.req_header_mut().insert_header(name, value).ok();
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else if status == 302 || status == 301 {
|
||||||
|
let resp = ResponseHeader::build(StatusCode::MOVED_PERMANENTLY, None);
|
||||||
|
match resp {
|
||||||
|
Ok(mut r) => {
|
||||||
|
for (name, value) in auth_headers_to_forward {
|
||||||
|
r.insert_header(name, value).ok();
|
||||||
|
}
|
||||||
|
let _ = r.insert_header("Content-Length", "0");
|
||||||
|
let _ = session.write_response_header(Box::new(r), true).await;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(_) => return false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl AuthValidator for BasicAuth<'_> {
|
impl AuthValidator for BasicAuth<'_> {
|
||||||
fn validate(&self, session: &Session) -> bool {
|
async fn validate(&self, session: &mut Session) -> bool {
|
||||||
if let Some(header) = session.get_header("authorization") {
|
if let Some(header) = session.get_header("authorization") {
|
||||||
if let Some((_, val)) = header.to_str().ok().unwrap().split_once(' ') {
|
if let Some(h) = header.to_str().ok() {
|
||||||
let decoded = STANDARD.decode(val).ok().unwrap();
|
if let Some((_, val)) = h.split_once(' ') {
|
||||||
let decoded_str = String::from_utf8(decoded).ok().unwrap();
|
if let Some(decoded) = STANDARD.decode(val).ok() {
|
||||||
return decoded_str == self.0;
|
if decoded.as_slice().ct_eq(self.0.as_bytes()).into() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl AuthValidator for ApiKeyAuth<'_> {
|
impl AuthValidator for ApiKeyAuth<'_> {
|
||||||
fn validate(&self, session: &Session) -> bool {
|
async fn validate(&self, session: &mut Session) -> bool {
|
||||||
if let Some(header) = session.get_header("x-api-key") {
|
if let Some(header) = session.get_header("x-api-key") {
|
||||||
return header.to_str().ok().unwrap() == self.0;
|
if let Some(h) = header.to_str().ok() {
|
||||||
|
return h.as_bytes().ct_eq(self.0.as_bytes()).into();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl AuthValidator for JwtAuth<'_> {
|
impl AuthValidator for JwtAuth<'_> {
|
||||||
fn validate(&self, session: &Session) -> bool {
|
async fn validate(&self, session: &mut Session) -> bool {
|
||||||
let jwtsecret = self.0;
|
let jwtsecret = self.0;
|
||||||
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);
|
||||||
@@ -53,33 +198,21 @@ impl AuthValidator for JwtAuth<'_> {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn validate(auth: &dyn AuthValidator, session: &Session) -> bool {
|
|
||||||
auth.validate(session)
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub fn authenticate(c: &[Arc<str>], session: &Session) -> bool {
|
pub async fn authenticate(auth_type: &Arc<str>, credentials: &Arc<str>, session: &mut Session) -> bool {
|
||||||
pub fn authenticate(auth_type: &Arc<str>, credentials: &Arc<str>, session: &Session) -> bool {
|
match &**auth_type {
|
||||||
match &*auth_type.clone() {
|
"basic" => BasicAuth(credentials).validate(session).await,
|
||||||
"basic" => {
|
"apikey" => ApiKeyAuth(credentials).validate(session).await,
|
||||||
let auth = BasicAuth(&*credentials.clone());
|
"jwt" => JwtAuth(credentials).validate(session).await,
|
||||||
validate(&auth, session)
|
"forward" => ForwardAuth(credentials).validate(session).await,
|
||||||
}
|
|
||||||
"apikey" => {
|
|
||||||
let auth = ApiKeyAuth(&*credentials.clone());
|
|
||||||
validate(&auth, session)
|
|
||||||
}
|
|
||||||
"jwt" => {
|
|
||||||
let auth = JwtAuth(&*credentials.clone());
|
|
||||||
validate(&auth, session)
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
println!("Unsupported authentication mechanism : {}", auth_type);
|
log::warn!("Unsupported authentication mechanism : {}", auth_type);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_query_param(session: &Session, key: &str) -> Option<String> {
|
pub fn get_query_param(session: &mut Session, key: &str) -> Option<String> {
|
||||||
let query = session.req_header().uri.query()?;
|
let query = session.req_header().uri.query()?;
|
||||||
|
|
||||||
let params: HashMap<_, _> = query
|
let params: HashMap<_, _> = query
|
||||||
@@ -91,6 +224,24 @@ pub fn get_query_param(session: &Session, key: &str) -> Option<String> {
|
|||||||
Some((k, v))
|
Some((k, v))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
params.get(key).and_then(|v| decode(v).ok()).map(|s| s.to_string())
|
||||||
params.get(key).map(|v| decode(v).ok()).flatten().map(|s| s.to_string())
|
}
|
||||||
|
|
||||||
|
fn split_host_port(addr: &str, tls: bool) -> Option<(&str, u16, bool, &str)> {
|
||||||
|
match addr.split_once(':') {
|
||||||
|
Some((h, p)) => match p.parse::<u16>() {
|
||||||
|
Ok(port) => return Some((h, port, tls, h)),
|
||||||
|
Err(_) => {
|
||||||
|
log::warn!("ForwardAuth: invalid port in {}", addr);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
if tls {
|
||||||
|
return Some((addr, 443u16, tls, addr));
|
||||||
|
} else {
|
||||||
|
return Some((addr, 80u16, tls, addr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ async fn build_upstreams(fullist: &UpstreamsDashMap, method: &str, client: &Clie
|
|||||||
to_https: upstream.to_https,
|
to_https: upstream.to_https,
|
||||||
rate_limit: upstream.rate_limit,
|
rate_limit: upstream.rate_limit,
|
||||||
healthcheck: upstream.healthcheck,
|
healthcheck: upstream.healthcheck,
|
||||||
|
redirect_to: upstream.redirect_to.clone(),
|
||||||
authorization: upstream.authorization.clone(),
|
authorization: upstream.authorization.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -118,18 +119,11 @@ async fn http_request(url: &str, method: &str, payload: &str, client: &Client) -
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn ping_grpc(addr: &str) -> bool {
|
pub async fn ping_grpc(addr: &str) -> bool {
|
||||||
let endpoint_result = Endpoint::from_shared(addr.to_owned());
|
let endpoint = match Endpoint::from_shared(addr.to_owned()) {
|
||||||
|
Ok(e) => e.timeout(Duration::from_secs(2)),
|
||||||
if let Ok(endpoint) = endpoint_result {
|
Err(_) => return false,
|
||||||
let endpoint = endpoint.timeout(Duration::from_secs(2));
|
};
|
||||||
|
tokio::time::timeout(Duration::from_secs(3), endpoint.connect()).await.ok().and_then(Result::ok).is_some()
|
||||||
match tokio::time::timeout(Duration::from_secs(3), endpoint.connect()).await {
|
|
||||||
Ok(Ok(_channel)) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn detect_tls(ip: &str, port: &u16, client: &Client) -> (bool, Option<Version>) {
|
async fn detect_tls(ip: &str, port: &u16, client: &Client) -> (bool, Option<Version>) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::utils::kuberconsul::{match_path, ConsulService, KubeEndpoints};
|
use crate::utils::kuberconsul::{match_path, ConsulService, KubeEndpoints};
|
||||||
use crate::utils::structs::{InnerMap, ServiceMapping};
|
use crate::utils::structs::{GlobalServiceMapping, InnerMap};
|
||||||
use axum::http::{HeaderMap, HeaderValue};
|
use axum::http::{HeaderMap, HeaderValue};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
@@ -7,7 +7,7 @@ use std::sync::atomic::AtomicUsize;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub async fn for_consul(url: String, token: Option<String>, conf: &ServiceMapping) -> Option<DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>> {
|
pub async fn for_consul(url: String, token: Option<String>, conf: &GlobalServiceMapping) -> Option<DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>> {
|
||||||
let client = Client::builder().timeout(Duration::from_secs(2)).danger_accept_invalid_certs(true).build().ok()?;
|
let client = Client::builder().timeout(Duration::from_secs(2)).danger_accept_invalid_certs(true).build().ok()?;
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
if let Some(token) = token {
|
if let Some(token) = token {
|
||||||
@@ -27,6 +27,7 @@ pub async fn for_consul(url: String, token: Option<String>, conf: &ServiceMappin
|
|||||||
// let prt = subsets.tagged_addresses.get("lan_ipv4").unwrap().port.clone();
|
// let prt = subsets.tagged_addresses.get("lan_ipv4").unwrap().port.clone();
|
||||||
let addr = subsets.tagged_addresses.get("lan_ipv4").unwrap().address.clone();
|
let addr = subsets.tagged_addresses.get("lan_ipv4").unwrap().address.clone();
|
||||||
let prt = subsets.tagged_addresses.get("lan_ipv4").unwrap().port.clone();
|
let prt = subsets.tagged_addresses.get("lan_ipv4").unwrap().port.clone();
|
||||||
|
// let redirect_link = conf.redirect_to.as_ref().map(|www| Arc::from(www.as_str()));
|
||||||
let to_add = Arc::from(InnerMap {
|
let to_add = Arc::from(InnerMap {
|
||||||
address: Arc::from(&*addr),
|
address: Arc::from(&*addr),
|
||||||
port: prt,
|
port: prt,
|
||||||
@@ -34,6 +35,7 @@ pub async fn for_consul(url: String, token: Option<String>, conf: &ServiceMappin
|
|||||||
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,
|
||||||
|
redirect_to: None,
|
||||||
healthcheck: None,
|
healthcheck: None,
|
||||||
authorization: None,
|
authorization: None,
|
||||||
});
|
});
|
||||||
@@ -43,7 +45,7 @@ pub async fn for_consul(url: String, token: Option<String>, conf: &ServiceMappin
|
|||||||
Some(upstreams)
|
Some(upstreams)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn for_kuber(url: &str, token: &str, conf: &ServiceMapping) -> Option<DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>> {
|
pub async fn for_kuber(url: &str, token: &str, conf: &GlobalServiceMapping) -> Option<DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>> {
|
||||||
let to = Duration::from_secs(10);
|
let to = Duration::from_secs(10);
|
||||||
let client = Client::builder().timeout(Duration::from_secs(10)).danger_accept_invalid_certs(true).build().ok()?;
|
let client = Client::builder().timeout(Duration::from_secs(10)).danger_accept_invalid_certs(true).build().ok()?;
|
||||||
let resp = client.get(url).timeout(to).bearer_auth(token).send().await.ok()?;
|
let resp = client.get(url).timeout(to).bearer_auth(token).send().await.ok()?;
|
||||||
@@ -61,6 +63,7 @@ pub async fn for_kuber(url: &str, token: &str, conf: &ServiceMapping) -> Option<
|
|||||||
let mut inner_vec = Vec::new();
|
let mut inner_vec = Vec::new();
|
||||||
for addr in addresses {
|
for addr in addresses {
|
||||||
for port in &ports {
|
for port in &ports {
|
||||||
|
// let redirect_link = conf.redirect_to.as_ref().map(|www| Arc::from(www.as_str()));
|
||||||
let to_add = Arc::from(InnerMap {
|
let to_add = Arc::from(InnerMap {
|
||||||
address: Arc::from(addr.ip.clone()),
|
address: Arc::from(addr.ip.clone()),
|
||||||
port: port.port.clone(),
|
port: port.port.clone(),
|
||||||
@@ -69,6 +72,7 @@ pub async fn for_kuber(url: &str, token: &str, conf: &ServiceMapping) -> Option<
|
|||||||
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,
|
||||||
healthcheck: None,
|
healthcheck: None,
|
||||||
|
redirect_to: None,
|
||||||
authorization: None,
|
authorization: None,
|
||||||
});
|
});
|
||||||
inner_vec.push(to_add);
|
inner_vec.push(to_add);
|
||||||
|
|||||||
@@ -1,16 +1,87 @@
|
|||||||
|
use ahash::AHasher;
|
||||||
|
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
|
||||||
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
|
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
|
||||||
|
use moka::sync::Cache;
|
||||||
|
use moka::Expiry;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
use std::time::{Duration, Instant, SystemTime};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub(crate) struct Claims {
|
pub struct Claims {
|
||||||
pub(crate) user: String,
|
pub master_key: String,
|
||||||
pub(crate) exp: u64,
|
pub owner: String,
|
||||||
|
pub exp: u64,
|
||||||
|
pub random: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Expired {
|
||||||
|
exp: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
static JWT_VALIDATION: LazyLock<Validation> = LazyLock::new(|| Validation::new(Algorithm::HS256));
|
||||||
|
|
||||||
|
static JWT_CACHE: LazyLock<Cache<u64, u64>> = LazyLock::new(|| Cache::builder().max_capacity(100_000).expire_after(JwtExpiry).build());
|
||||||
|
struct JwtExpiry;
|
||||||
|
impl Expiry<u64, u64> for JwtExpiry {
|
||||||
|
fn expire_after_create(&self, _key: &u64, value: &u64, _current_time: Instant) -> Option<Duration> {
|
||||||
|
let now = SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs();
|
||||||
|
if *value > now {
|
||||||
|
Some(Duration::from_secs(value - now))
|
||||||
|
} else {
|
||||||
|
Some(Duration::ZERO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_jwt(token: &str, secret: &str) -> bool {
|
||||||
|
let key = hash_token(token, secret);
|
||||||
|
let now = SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs();
|
||||||
|
if let Some(exp) = JWT_CACHE.get(&key) {
|
||||||
|
if exp < now {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
match is_expired(token, now) {
|
||||||
|
Ok(true) => return false,
|
||||||
|
Ok(false) => {}
|
||||||
|
Err(_) => return false,
|
||||||
|
}
|
||||||
|
|
||||||
|
match decode::<Claims>(token, &DecodingKey::from_secret(secret.as_ref()), &JWT_VALIDATION) {
|
||||||
|
Ok(data) => {
|
||||||
|
let now = SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs();
|
||||||
|
if data.claims.exp > now {
|
||||||
|
JWT_CACHE.insert(key, data.claims.exp);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn check_jwt(input: &str, secret: &str) -> bool {
|
|
||||||
let validation = Validation::new(Algorithm::HS256);
|
|
||||||
let token_data = decode::<Claims>(&input, &DecodingKey::from_secret(secret.as_ref()), &validation);
|
|
||||||
match token_data {
|
|
||||||
Ok(_) => true,
|
|
||||||
Err(_) => false,
|
Err(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_expired(token: &str, now: u64) -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
|
let parts: Vec<&str> = token.split('.').collect();
|
||||||
|
if parts.len() != 3 {
|
||||||
|
return Err("Invalid JWT format".into());
|
||||||
|
}
|
||||||
|
let decoded = URL_SAFE_NO_PAD.decode(parts[1])?;
|
||||||
|
let claims: Expired = serde_json::from_slice(&decoded)?;
|
||||||
|
if let Some(exp) = claims.exp {
|
||||||
|
Ok(exp < now)
|
||||||
|
} else {
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_token(token: &str, secret: &str) -> u64 {
|
||||||
|
let mut hasher = AHasher::default();
|
||||||
|
token.hash(&mut hasher);
|
||||||
|
secret.hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::utils::httpclient;
|
use crate::utils::httpclient;
|
||||||
use crate::utils::parceyaml::build_headers;
|
use crate::utils::parceyaml::build_headers;
|
||||||
use crate::utils::structs::{Configuration, InnerMap, ServiceMapping, UpstreamsDashMap};
|
use crate::utils::structs::{Configuration, GlobalServiceMapping, InnerMap, UpstreamsDashMap};
|
||||||
use crate::utils::tools::{clone_dashmap_into, compare_dashmaps, print_upstreams};
|
use crate::utils::tools::{clone_dashmap_into, compare_dashmaps, print_upstreams};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
@@ -52,7 +52,7 @@ pub struct ConsulTaggedAddress {
|
|||||||
#[serde(rename = "Port")]
|
#[serde(rename = "Port")]
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
}
|
}
|
||||||
pub fn list_to_upstreams(lt: Option<DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>>, upstreams: &UpstreamsDashMap, i: &ServiceMapping) {
|
pub fn list_to_upstreams(lt: Option<DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>>, upstreams: &UpstreamsDashMap, i: &GlobalServiceMapping) {
|
||||||
if let Some(list) = lt {
|
if let Some(list) = lt {
|
||||||
match upstreams.get(&*i.hostname.clone()) {
|
match upstreams.get(&*i.hostname.clone()) {
|
||||||
Some(upstr) => {
|
Some(upstr) => {
|
||||||
@@ -67,7 +67,7 @@ pub fn list_to_upstreams(lt: Option<DashMap<Arc<str>, (Vec<Arc<InnerMap>>, Atomi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn match_path(conf: &ServiceMapping, upstreams: &DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>, values: Vec<Arc<InnerMap>>) {
|
pub fn match_path(conf: &GlobalServiceMapping, upstreams: &DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>, values: Vec<Arc<InnerMap>>) {
|
||||||
match conf.path {
|
match conf.path {
|
||||||
Some(ref p) => {
|
Some(ref p) => {
|
||||||
upstreams.insert(Arc::from(p.clone()), (values, AtomicUsize::new(0)));
|
upstreams.insert(Arc::from(p.clone()), (values, AtomicUsize::new(0)));
|
||||||
@@ -115,7 +115,7 @@ impl ServiceDiscovery for KubernetesDiscovery {
|
|||||||
if let Some(kuber) = config.kubernetes.clone() {
|
if let Some(kuber) = config.kubernetes.clone() {
|
||||||
if let Some(svc) = kuber.services {
|
if let Some(svc) = kuber.services {
|
||||||
for service in svc {
|
for service in svc {
|
||||||
let header_list: DashMap<Arc<str>, Vec<(Arc<str>, Arc<str>)>> = DashMap::new();
|
let header_list: DashMap<Arc<str>, Vec<(String, Arc<str>)>> = DashMap::new();
|
||||||
let mut hl = Vec::new();
|
let mut hl = Vec::new();
|
||||||
build_headers(&service.client_headers, config.as_ref(), &mut hl);
|
build_headers(&service.client_headers, config.as_ref(), &mut hl);
|
||||||
if !hl.is_empty() {
|
if !hl.is_empty() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use http::method::Method;
|
use pingora_http::Method;
|
||||||
use http::StatusCode;
|
use pingora_http::StatusCode;
|
||||||
use pingora_http::Version;
|
use pingora_http::Version;
|
||||||
use prometheus::{register_histogram, register_int_counter, register_int_counter_vec, Histogram, IntCounter, IntCounterVec};
|
use prometheus::{register_histogram, register_int_counter, register_int_counter_vec, Histogram, IntCounter, IntCounterVec};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -12,46 +12,40 @@ pub struct MetricTypes {
|
|||||||
pub latency: Duration,
|
pub latency: Duration,
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
}
|
}
|
||||||
lazy_static::lazy_static! {
|
|
||||||
pub static ref REQUEST_COUNT: IntCounter = register_int_counter!(
|
use std::sync::LazyLock;
|
||||||
"aralez_requests_total",
|
|
||||||
"Total number of requests handled by Aralez"
|
pub static REQUEST_COUNT: LazyLock<IntCounter> = LazyLock::new(|| register_int_counter!("aralez_requests_total", "Total number of requests handled by Aralez").unwrap());
|
||||||
).unwrap();
|
|
||||||
pub static ref RESPONSE_CODES: IntCounterVec = register_int_counter_vec!(
|
pub static RESPONSE_CODES: LazyLock<IntCounterVec> =
|
||||||
"aralez_responses_total",
|
LazyLock::new(|| register_int_counter_vec!("aralez_responses_total", "Responses grouped by status code", &["status"]).unwrap());
|
||||||
"Responses grouped by status code",
|
|
||||||
&["status"]
|
pub static REQUEST_LATENCY: LazyLock<Histogram> = LazyLock::new(|| {
|
||||||
).unwrap();
|
register_histogram!(
|
||||||
pub static ref REQUEST_LATENCY: Histogram = register_histogram!(
|
|
||||||
"aralez_request_latency_seconds",
|
"aralez_request_latency_seconds",
|
||||||
"Request latency in seconds",
|
"Request latency in seconds",
|
||||||
vec![0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0]
|
vec![0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0]
|
||||||
).unwrap();
|
)
|
||||||
pub static ref RESPONSE_LATENCY: Histogram = register_histogram!(
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
pub static RESPONSE_LATENCY: LazyLock<Histogram> = LazyLock::new(|| {
|
||||||
|
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.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.0, 5.0]
|
||||||
).unwrap();
|
)
|
||||||
pub static ref REQUESTS_BY_METHOD: IntCounterVec = register_int_counter_vec!(
|
.unwrap()
|
||||||
"aralez_requests_by_method_total",
|
});
|
||||||
"Number of requests by HTTP method",
|
|
||||||
&["method"]
|
pub static REQUESTS_BY_METHOD: LazyLock<IntCounterVec> =
|
||||||
).unwrap();
|
LazyLock::new(|| register_int_counter_vec!("aralez_requests_by_method_total", "Number of requests by HTTP method", &["method"]).unwrap());
|
||||||
pub static ref REQUESTS_BY_UPSTREAM: IntCounterVec = register_int_counter_vec!(
|
|
||||||
"aralez_requests_by_upstream",
|
pub static REQUESTS_BY_UPSTREAM: LazyLock<IntCounterVec> =
|
||||||
"Number of requests by UPSTREAM server",
|
LazyLock::new(|| register_int_counter_vec!("aralez_requests_by_upstream", "Number of requests by UPSTREAM server", &["upstream"]).unwrap());
|
||||||
&["upstream"]
|
|
||||||
).unwrap();
|
pub static REQUESTS_BY_VERSION: LazyLock<IntCounterVec> =
|
||||||
pub static ref REQUESTS_BY_VERSION: IntCounterVec = register_int_counter_vec!(
|
LazyLock::new(|| register_int_counter_vec!("aralez_requests_by_version_total", "Number of requests by HTTP versions", &["version"]).unwrap());
|
||||||
"aralez_requests_by_version_total",
|
|
||||||
"Number of requests by HTTP versions",
|
|
||||||
&["version"]
|
|
||||||
).unwrap();
|
|
||||||
pub static ref ERROR_COUNT: IntCounter = register_int_counter!(
|
|
||||||
"aralez_errors_total",
|
|
||||||
"Total number of errors"
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn calc_metrics(metric_types: &MetricTypes) {
|
pub fn calc_metrics(metric_types: &MetricTypes) {
|
||||||
REQUEST_COUNT.inc();
|
REQUEST_COUNT.inc();
|
||||||
@@ -66,7 +60,7 @@ pub fn calc_metrics(metric_types: &MetricTypes) {
|
|||||||
_ => "Unknown",
|
_ => "Unknown",
|
||||||
};
|
};
|
||||||
REQUESTS_BY_VERSION.with_label_values(&[&version_str]).inc();
|
REQUESTS_BY_VERSION.with_label_values(&[&version_str]).inc();
|
||||||
RESPONSE_CODES.with_label_values(&[metric_types.code.unwrap_or(http::StatusCode::GONE).as_str()]).inc();
|
RESPONSE_CODES.with_label_values(&[metric_types.code.unwrap_or(StatusCode::GONE).as_str()]).inc();
|
||||||
REQUESTS_BY_METHOD.with_label_values(&[&metric_types.method]).inc();
|
REQUESTS_BY_METHOD.with_label_values(&[&metric_types.method]).inc();
|
||||||
REQUESTS_BY_UPSTREAM.with_label_values(&[metric_types.upstream.as_ref()]).inc();
|
REQUESTS_BY_UPSTREAM.with_label_values(&[metric_types.upstream.as_ref()]).inc();
|
||||||
RESPONSE_LATENCY.observe(metric_types.latency.as_secs_f64());
|
RESPONSE_LATENCY.observe(metric_types.latency.as_secs_f64());
|
||||||
|
|||||||
@@ -5,14 +5,39 @@ use crate::utils::tools::{clone_dashmap, clone_dashmap_into, print_upstreams};
|
|||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
|
|
||||||
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 yaml_data = match kind {
|
let yaml_data = match kind {
|
||||||
"filepath" => match fs::read_to_string(d) {
|
"filepath" => match fs::read_to_string(d) {
|
||||||
Ok(data) => {
|
Ok(data) => {
|
||||||
|
let mut confdir = Path::new(d).parent().unwrap().to_path_buf();
|
||||||
|
confdir.push("conf.d");
|
||||||
|
if let Ok(entries) = fs::read_dir(&confdir) {
|
||||||
|
let mut paths: Vec<_> = entries
|
||||||
|
.flatten()
|
||||||
|
.map(|e| e.path())
|
||||||
|
.filter(|p| p.extension().and_then(|e| e.to_str()) == Some("yaml"))
|
||||||
|
.collect();
|
||||||
|
paths.sort();
|
||||||
|
|
||||||
|
for path in paths {
|
||||||
|
let content = fs::read_to_string(&path);
|
||||||
|
match content {
|
||||||
|
Ok(content) => {
|
||||||
|
conf_files.push(content);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Reading: {}: {:?}", path.display(), e)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
info!("Reading upstreams from {}", d);
|
info!("Reading upstreams from {}", d);
|
||||||
data
|
data
|
||||||
}
|
}
|
||||||
@@ -32,18 +57,28 @@ pub async fn load_configuration(d: &str, kind: &str) -> (Option<Configuration>,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let parsed: Config = match serde_yaml::from_str(&yaml_data) {
|
let mut parsed: Config = match serde_yml::from_str(&yaml_data) {
|
||||||
Ok(cfg) => {
|
Ok(cfg) => cfg,
|
||||||
// println!("{:#?}", cfg);
|
|
||||||
cfg
|
|
||||||
}
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to parse upstreams file: {}", e);
|
error!("Failed to parse upstreams file: {}", e);
|
||||||
return (None, e.to_string());
|
return (None, e.to_string());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut toreturn = Configuration::default();
|
|
||||||
|
|
||||||
|
if let Some(ref mut upstreams) = parsed.upstreams {
|
||||||
|
for uconf in conf_files {
|
||||||
|
let p: HashMap<String, HostConfig> = match serde_yml::from_str(&uconf) {
|
||||||
|
Ok(ucfg) => ucfg,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to parse upstreams file: {}", e);
|
||||||
|
return (None, e.to_string());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
upstreams.extend(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut toreturn = Configuration::default();
|
||||||
populate_headers_and_auth(&mut toreturn, &parsed).await;
|
populate_headers_and_auth(&mut toreturn, &parsed).await;
|
||||||
toreturn.typecfg = parsed.provider.clone();
|
toreturn.typecfg = parsed.provider.clone();
|
||||||
|
|
||||||
@@ -68,27 +103,27 @@ pub async fn load_configuration(d: &str, kind: &str) -> (Option<Configuration>,
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn populate_headers_and_auth(config: &mut Configuration, parsed: &Config) {
|
async fn populate_headers_and_auth(config: &mut Configuration, parsed: &Config) {
|
||||||
let mut ch: Vec<(Arc<str>, Arc<str>)> = Vec::new();
|
let mut ch: Vec<(String, Arc<str>)> = Vec::new();
|
||||||
if let Some(headers) = &parsed.client_headers {
|
if let Some(headers) = &parsed.client_headers {
|
||||||
for header in headers {
|
for header in headers {
|
||||||
if let Some((key, val)) = header.split_once(':') {
|
if let Some((key, val)) = header.split_once(':') {
|
||||||
ch.push((Arc::from(key), Arc::from(val)));
|
ch.push((key.to_string(), Arc::from(val)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let global_headers: DashMap<Arc<str>, Vec<(Arc<str>, 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);
|
||||||
|
|
||||||
let mut sh: Vec<(Arc<str>, Arc<str>)> = Vec::new();
|
let mut sh: Vec<(String, Arc<str>)> = Vec::new();
|
||||||
if let Some(headers) = &parsed.server_headers {
|
if let Some(headers) = &parsed.server_headers {
|
||||||
for header in headers {
|
for header in headers {
|
||||||
if let Some((key, val)) = header.split_once(':') {
|
if let Some((key, val)) = header.split_once(':') {
|
||||||
sh.push((Arc::from(key.trim()), Arc::from(val.trim())));
|
sh.push((key.to_string(), Arc::from(val.trim())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let server_global_headers: DashMap<Arc<str>, Vec<(Arc<str>, Arc<str>)>> = DashMap::new();
|
let server_global_headers: DashMap<Arc<str>, Vec<(String, Arc<str>)>> = DashMap::new();
|
||||||
server_global_headers.insert(Arc::from("/"), sh);
|
server_global_headers.insert(Arc::from("/"), sh);
|
||||||
config.server_headers.insert(Arc::from("GLOBAL_SERVER_HEADERS"), server_global_headers);
|
config.server_headers.insert(Arc::from("GLOBAL_SERVER_HEADERS"), server_global_headers);
|
||||||
config.extraparams.to_https = parsed.to_https;
|
config.extraparams.to_https = parsed.to_https;
|
||||||
@@ -104,7 +139,7 @@ async fn populate_headers_and_auth(config: &mut Configuration, parsed: &Config)
|
|||||||
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()),
|
||||||
};
|
};
|
||||||
config.extraparams.authentication = Some(y);
|
config.extraparams.authentication = Some(Arc::from(y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,8 +154,8 @@ async fn populate_file_upstreams(config: &mut Configuration, parsed: &Config) {
|
|||||||
if let Some(rate) = &path_config.rate_limit {
|
if let Some(rate) = &path_config.rate_limit {
|
||||||
info!("Applied Rate Limit for {} : {} request per second", hostname, rate);
|
info!("Applied Rate Limit for {} : {} request per second", hostname, rate);
|
||||||
}
|
}
|
||||||
let mut hl: Vec<(Arc<str>, Arc<str>)> = Vec::new();
|
let mut hl: Vec<(String, Arc<str>)> = Vec::new();
|
||||||
let mut sl: Vec<(Arc<str>, Arc<str>)> = Vec::new();
|
let mut sl: Vec<(String, Arc<str>)> = Vec::new();
|
||||||
build_headers(&path_config.client_headers, config, &mut hl);
|
build_headers(&path_config.client_headers, config, &mut hl);
|
||||||
build_headers(&path_config.server_headers, config, &mut sl);
|
build_headers(&path_config.server_headers, config, &mut sl);
|
||||||
client_header_list.insert(Arc::from(path.as_str()), hl);
|
client_header_list.insert(Arc::from(path.as_str()), hl);
|
||||||
@@ -135,17 +170,18 @@ async fn populate_file_upstreams(config: &mut Configuration, parsed: &Config) {
|
|||||||
};
|
};
|
||||||
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()));
|
||||||
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>() {
|
||||||
server_list.push(Arc::from(InnerMap {
|
server_list.push(Arc::from(InnerMap {
|
||||||
address: Arc::from(ip),
|
address: Arc::from(ip),
|
||||||
port,
|
port,
|
||||||
is_ssl: true,
|
is_ssl: false,
|
||||||
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,
|
||||||
healthcheck: path_config.healthcheck,
|
healthcheck: path_config.healthcheck,
|
||||||
|
redirect_to: redirect_link,
|
||||||
authorization: path_auth,
|
authorization: path_auth,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -173,8 +209,8 @@ async fn populate_file_upstreams(config: &mut Configuration, parsed: &Config) {
|
|||||||
pub fn parce_main_config(path: &str) -> AppConfig {
|
pub fn parce_main_config(path: &str) -> AppConfig {
|
||||||
let data = fs::read_to_string(path).unwrap();
|
let data = fs::read_to_string(path).unwrap();
|
||||||
let reply = DashMap::new();
|
let reply = DashMap::new();
|
||||||
let cfg: HashMap<String, String> = serde_yaml::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_yaml::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");
|
||||||
log_builder(&cfo);
|
log_builder(&cfo);
|
||||||
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 {
|
||||||
@@ -185,13 +221,24 @@ pub fn parce_main_config(path: &str) -> AppConfig {
|
|||||||
cfo.local_server = Option::from((ip.to_string(), port));
|
cfo.local_server = Option::from((ip.to_string(), port));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// if let Some(tlsport_cfg) = cfo.proxy_address_tls.clone() {
|
||||||
|
// if let Some((_, port_str)) = tlsport_cfg.split_once(':') {
|
||||||
|
// if let Ok(port) = port_str.parse::<u16>() {
|
||||||
|
// cfo.proxy_port_tls = Some(port);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
if let Some(tlsport_cfg) = cfo.proxy_address_tls.clone() {
|
if let Some(tlsport_cfg) = cfo.proxy_address_tls.clone() {
|
||||||
if let Some((_, port_str)) = tlsport_cfg.split_once(':') {
|
if let Some((_, port_str)) = tlsport_cfg.split_once(':') {
|
||||||
if let Ok(port) = port_str.parse::<u16>() {
|
cfo.proxy_port_tls = Some(port_str.to_string());
|
||||||
cfo.proxy_port_tls = Some(port);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some((_, port_str)) = cfo.proxy_address_http.split_once(':') {
|
||||||
|
cfo.proxy_port = Some(port_str.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
cfo.proxy_tls_grade = parce_tls_grades(cfo.proxy_tls_grade.clone());
|
cfo.proxy_tls_grade = parce_tls_grades(cfo.proxy_tls_grade.clone());
|
||||||
cfo
|
cfo
|
||||||
}
|
}
|
||||||
@@ -218,7 +265,7 @@ fn parce_tls_grades(what: Option<String>) -> Option<String> {
|
|||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
warn!("TLS grade not set, defaulting to: medium");
|
warn!("TLS grade not set, defaulting to: medium");
|
||||||
Some("b".to_string())
|
Some("medium".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -242,11 +289,11 @@ fn log_builder(conf: &AppConfig) {
|
|||||||
env_logger::builder().init();
|
env_logger::builder().init();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_headers(path_config: &Option<Vec<String>>, _config: &Configuration, hl: &mut Vec<(Arc<str>, 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 {
|
||||||
if let Some((key, val)) = header.split_once(':') {
|
if let Some((key, val)) = header.split_once(':') {
|
||||||
hl.push((Arc::from(key.trim()), Arc::from(val.trim())));
|
hl.push((key.trim().to_string(), Arc::from(val.trim())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
use once_cell::sync::Lazy;
|
use std::sync::{LazyLock, RwLock};
|
||||||
use std::sync::RwLock;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SharedState {
|
pub struct SharedState {
|
||||||
pub first_run: bool,
|
pub first_run: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static GLOBAL_STATE: Lazy<RwLock<SharedState>> = Lazy::new(|| RwLock::new(SharedState { first_run: true }));
|
pub static GLOBAL_STATE: LazyLock<RwLock<SharedState>> = LazyLock::new(|| RwLock::new(SharedState { first_run: true }));
|
||||||
|
|
||||||
pub fn mark_not_first_run() {
|
pub fn mark_not_first_run() {
|
||||||
let mut state = GLOBAL_STATE.write().unwrap();
|
let mut state = GLOBAL_STATE.write().unwrap();
|
||||||
|
|||||||
@@ -7,18 +7,20 @@ use std::sync::Arc;
|
|||||||
pub type UpstreamsDashMap = DashMap<Arc<str>, DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>>;
|
pub type UpstreamsDashMap = DashMap<Arc<str>, DashMap<Arc<str>, (Vec<Arc<InnerMap>>, AtomicUsize)>>;
|
||||||
|
|
||||||
pub type UpstreamsIdMap = DashMap<String, Arc<InnerMap>>;
|
pub type UpstreamsIdMap = DashMap<String, Arc<InnerMap>>;
|
||||||
pub type Headers = DashMap<Arc<str>, DashMap<Arc<str>, Vec<(Arc<str>, Arc<str>)>>>;
|
pub type Headers = DashMap<Arc<str>, DashMap<Arc<str>, Vec<(String, Arc<str>)>>>;
|
||||||
|
// pub type UpstreamsSerDde = Option<HashMap<String, HostConfig>>;
|
||||||
|
// pub type UpstreamsSerDe = HashMap<String, HostConfig>;
|
||||||
|
|
||||||
#[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: bool,
|
||||||
pub authentication: Option<InnerAuth>,
|
pub authentication: Option<Arc<InnerAuth>>,
|
||||||
pub rate_limit: Option<isize>,
|
pub rate_limit: Option<isize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||||
pub struct ServiceMapping {
|
pub struct GlobalServiceMapping {
|
||||||
pub upstream: String,
|
pub upstream: String,
|
||||||
pub hostname: String,
|
pub hostname: String,
|
||||||
pub path: Option<String>,
|
pub path: Option<String>,
|
||||||
@@ -32,14 +34,14 @@ pub struct ServiceMapping {
|
|||||||
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
|
||||||
pub struct Kubernetes {
|
pub struct Kubernetes {
|
||||||
pub servers: Option<Vec<String>>,
|
pub servers: Option<Vec<String>>,
|
||||||
pub services: Option<Vec<ServiceMapping>>,
|
pub services: Option<Vec<GlobalServiceMapping>>,
|
||||||
pub tokenpath: Option<String>,
|
pub tokenpath: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
|
||||||
pub struct Consul {
|
pub struct Consul {
|
||||||
pub servers: Option<Vec<String>>,
|
pub servers: Option<Vec<String>>,
|
||||||
pub services: Option<Vec<ServiceMapping>>,
|
pub services: Option<Vec<GlobalServiceMapping>>,
|
||||||
pub token: Option<String>,
|
pub token: Option<String>,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
@@ -74,7 +76,7 @@ pub struct HostConfig {
|
|||||||
pub struct Auth {
|
pub struct Auth {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub auth_type: String,
|
pub auth_type: String,
|
||||||
#[serde(rename = "creds")]
|
#[serde(rename = "data")]
|
||||||
pub auth_cred: String,
|
pub auth_cred: String,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
@@ -86,6 +88,7 @@ pub struct PathConfig {
|
|||||||
pub server_headers: Option<Vec<String>>,
|
pub server_headers: Option<Vec<String>>,
|
||||||
pub rate_limit: Option<isize>,
|
pub rate_limit: Option<isize>,
|
||||||
pub healthcheck: Option<bool>,
|
pub healthcheck: Option<bool>,
|
||||||
|
pub redirect_to: Option<String>,
|
||||||
pub authorization: Option<Auth>,
|
pub authorization: Option<Auth>,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
@@ -113,7 +116,8 @@ pub struct AppConfig {
|
|||||||
pub config_tls_certificate: Option<String>,
|
pub config_tls_certificate: Option<String>,
|
||||||
pub config_tls_key_file: Option<String>,
|
pub config_tls_key_file: Option<String>,
|
||||||
pub proxy_address_tls: Option<String>,
|
pub proxy_address_tls: Option<String>,
|
||||||
pub proxy_port_tls: Option<u16>,
|
pub proxy_port_tls: Option<String>,
|
||||||
|
pub proxy_port: Option<String>,
|
||||||
pub local_server: Option<(String, u16)>,
|
pub local_server: Option<(String, u16)>,
|
||||||
pub proxy_certificates: Option<String>,
|
pub proxy_certificates: Option<String>,
|
||||||
pub proxy_tls_grade: Option<String>,
|
pub proxy_tls_grade: Option<String>,
|
||||||
@@ -138,6 +142,7 @@ pub struct InnerMap {
|
|||||||
pub to_https: bool,
|
pub to_https: bool,
|
||||||
pub rate_limit: Option<isize>,
|
pub rate_limit: Option<isize>,
|
||||||
pub healthcheck: Option<bool>,
|
pub healthcheck: Option<bool>,
|
||||||
|
pub redirect_to: Option<Arc<str>>,
|
||||||
pub authorization: Option<Arc<InnerAuth>>,
|
pub authorization: Option<Arc<InnerAuth>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +157,7 @@ impl InnerMap {
|
|||||||
to_https: Default::default(),
|
to_https: Default::default(),
|
||||||
rate_limit: Default::default(),
|
rate_limit: Default::default(),
|
||||||
healthcheck: Default::default(),
|
healthcheck: Default::default(),
|
||||||
|
redirect_to: Default::default(),
|
||||||
authorization: Default::default(),
|
authorization: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
|
use crate::tls::load;
|
||||||
|
use crate::tls::load::CertificateConfig;
|
||||||
use crate::utils::structs::{InnerMap, InnerMapForJson, UpstreamSnapshotForJson, UpstreamsDashMap, UpstreamsIdMap};
|
use crate::utils::structs::{InnerMap, InnerMapForJson, UpstreamSnapshotForJson, UpstreamsDashMap, UpstreamsIdMap};
|
||||||
use crate::utils::tls;
|
|
||||||
use crate::utils::tls::CertificateConfig;
|
|
||||||
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};
|
||||||
use port_check::is_port_reachable;
|
|
||||||
use privdrop::PrivDrop;
|
use privdrop::PrivDrop;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
@@ -12,6 +11,7 @@ use std::any::type_name;
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
use std::net::TcpListener;
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
@@ -146,7 +146,7 @@ pub fn compare_dashmaps(map1: &UpstreamsDashMap, map2: &UpstreamsDashMap) -> boo
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn merge_headers(target: &DashMap<Arc<str>, Vec<(Arc<str>, Arc<str>)>>, source: &DashMap<Arc<str>, Vec<(Arc<str>, Arc<str>)>>) {
|
pub fn merge_headers(target: &DashMap<Arc<str>, Vec<(String, Arc<str>)>>, source: &DashMap<Arc<str>, Vec<(String, Arc<str>)>>) {
|
||||||
for entry in source.iter() {
|
for entry in source.iter() {
|
||||||
let global_key = entry.key().clone();
|
let global_key = entry.key().clone();
|
||||||
let global_values = entry.value().clone();
|
let global_values = entry.value().clone();
|
||||||
@@ -166,8 +166,22 @@ pub fn clone_idmap_into(original: &UpstreamsDashMap, cloned: &UpstreamsIdMap) {
|
|||||||
let new_vec = vec.clone();
|
let new_vec = vec.clone();
|
||||||
for x in vec.iter() {
|
for x in vec.iter() {
|
||||||
let mut id = String::new();
|
let mut id = String::new();
|
||||||
write!(&mut id, "{}:{}:{}", x.address, x.port, x.is_ssl).unwrap();
|
write!(
|
||||||
|
&mut id,
|
||||||
|
"{}:{}:{}:{}:{}:{}:{}:{:?}",
|
||||||
|
outer_entry.key(),
|
||||||
|
x.address,
|
||||||
|
x.port,
|
||||||
|
x.is_http2,
|
||||||
|
x.to_https,
|
||||||
|
x.rate_limit.unwrap_or_default(),
|
||||||
|
x.healthcheck.unwrap_or_default(),
|
||||||
|
x.authorization
|
||||||
|
)
|
||||||
|
.unwrap_or(());
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
|
// address: "127.0.0.3", port: 8000, is_ssl: false, is_http2: false, to_https: false, rate_limit: Some(200), healthcheck: None, authorization: None } }
|
||||||
|
|
||||||
hasher.update(id.clone().into_bytes());
|
hasher.update(id.clone().into_bytes());
|
||||||
let hash = hasher.finalize();
|
let hash = hasher.finalize();
|
||||||
let hex_hash = base16ct::lower::encode_string(&hash);
|
let hex_hash = base16ct::lower::encode_string(&hash);
|
||||||
@@ -180,9 +194,9 @@ pub fn clone_idmap_into(original: &UpstreamsDashMap, cloned: &UpstreamsIdMap) {
|
|||||||
to_https: false,
|
to_https: false,
|
||||||
rate_limit: None,
|
rate_limit: None,
|
||||||
healthcheck: None,
|
healthcheck: None,
|
||||||
|
redirect_to: None,
|
||||||
authorization: None,
|
authorization: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
cloned.insert(id, Arc::from(to_add));
|
cloned.insert(id, Arc::from(to_add));
|
||||||
cloned.insert(hh, Arc::from(x.to_owned()));
|
cloned.insert(hh, Arc::from(x.to_owned()));
|
||||||
// println!("CLONNED :===========> {:?}", cloned);
|
// println!("CLONNED :===========> {:?}", cloned);
|
||||||
@@ -190,11 +204,12 @@ pub fn clone_idmap_into(original: &UpstreamsDashMap, cloned: &UpstreamsIdMap) {
|
|||||||
new_inner_map.insert(path.clone(), new_vec);
|
new_inner_map.insert(path.clone(), new_vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
info!("Upstreams are fully populated. Ready to server requests");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn listdir(dir: String) -> Vec<tls::CertificateConfig> {
|
pub fn listdir(dir: String) -> Vec<load::CertificateConfig> {
|
||||||
let mut f = HashMap::new();
|
let mut f = HashMap::new();
|
||||||
let mut certificate_configs: Vec<tls::CertificateConfig> = vec![];
|
let mut certificate_configs: Vec<load::CertificateConfig> = vec![];
|
||||||
let paths = fs::read_dir(dir).unwrap();
|
let paths = fs::read_dir(dir).unwrap();
|
||||||
for path in paths {
|
for path in paths {
|
||||||
let path_str = path.unwrap().path().to_str().unwrap().to_owned();
|
let path_str = path.unwrap().path().to_str().unwrap().to_owned();
|
||||||
@@ -212,13 +227,13 @@ pub fn listdir(dir: String) -> Vec<tls::CertificateConfig> {
|
|||||||
certificate_configs.push(y);
|
certificate_configs.push(y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (_, v) in f.iter() {
|
// for (_, v) in f.iter() {
|
||||||
let y = CertificateConfig {
|
// let y = CertificateConfig {
|
||||||
cert_path: v[0].clone(),
|
// cert_path: v[0].clone(),
|
||||||
key_path: v[1].clone(),
|
// key_path: v[1].clone(),
|
||||||
};
|
// };
|
||||||
certificate_configs.push(y);
|
// certificate_configs.push(y);
|
||||||
}
|
// }
|
||||||
certificate_configs
|
certificate_configs
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,14 +268,14 @@ pub fn drop_priv(user: String, group: String, http_addr: String, tls_addr: Optio
|
|||||||
thread::sleep(time::Duration::from_millis(10));
|
thread::sleep(time::Duration::from_millis(10));
|
||||||
loop {
|
loop {
|
||||||
thread::sleep(time::Duration::from_millis(10));
|
thread::sleep(time::Duration::from_millis(10));
|
||||||
if is_port_reachable(http_addr.clone()) {
|
if port_is_available(http_addr.clone()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(tls_addr) = tls_addr {
|
if let Some(tls_addr) = tls_addr {
|
||||||
loop {
|
loop {
|
||||||
thread::sleep(time::Duration::from_millis(10));
|
thread::sleep(time::Duration::from_millis(10));
|
||||||
if is_port_reachable(tls_addr.clone()) {
|
if port_is_available(tls_addr.clone()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,6 +287,13 @@ pub fn drop_priv(user: String, group: String, http_addr: String, tls_addr: Optio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn port_is_available(addr: String) -> bool {
|
||||||
|
match TcpListener::bind(addr) {
|
||||||
|
Ok(_) => false,
|
||||||
|
Err(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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()).unwrap();
|
||||||
match port < 1024 {
|
match port < 1024 {
|
||||||
@@ -368,3 +390,16 @@ pub fn upstreams_liveness_json(configured: &UpstreamsDashMap, current: &Upstream
|
|||||||
}
|
}
|
||||||
Value::Object(result)
|
Value::Object(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn prepend(prefix: &str, val: &Option<Arc<str>>, uri: &str, port: &str) -> Option<String> {
|
||||||
|
val.as_ref().map(|s| {
|
||||||
|
let mut buf = String::with_capacity(32);
|
||||||
|
buf.push_str(prefix);
|
||||||
|
buf.push_str(s);
|
||||||
|
buf.push_str(":");
|
||||||
|
buf.push_str(port);
|
||||||
|
buf.push_str(uri);
|
||||||
|
buf
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GetHostsReturHeaders {
|
pub struct GetHostsReturHeaders {
|
||||||
pub client_headers: Option<Vec<(Arc<str>, Arc<str>)>>,
|
pub client_headers: Option<Vec<(String, Arc<str>)>>,
|
||||||
pub server_headers: Option<Vec<(Arc<str>, Arc<str>)>>,
|
pub server_headers: Option<Vec<(String, Arc<str>)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ 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 dashmap::DashMap;
|
||||||
// use x509_parser::asn1_rs::ToDer;
|
|
||||||
use itoa::Buffer;
|
|
||||||
use log::{debug, error, warn};
|
use log::{debug, error, warn};
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
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;
|
||||||
@@ -18,17 +15,19 @@ use pingora_core::prelude::HttpPeer;
|
|||||||
// use pingora_core::protocols::TcpKeepalive;
|
// use pingora_core::protocols::TcpKeepalive;
|
||||||
use pingora_limits::rate::Rate;
|
use pingora_limits::rate::Rate;
|
||||||
use pingora_proxy::{ProxyHttp, Session};
|
use pingora_proxy::{ProxyHttp, Session};
|
||||||
|
// use prometheus::{register_int_counter, IntCounter};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
// use std::collections::BTreeMap;
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, LazyLock};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
|
|
||||||
static RATE_LIMITER: Lazy<Rate> = Lazy::new(|| Rate::new(Duration::from_secs(1)));
|
// static RATE_LIMITER: Lazy<Rate> = Lazy::new(|| Rate::new(Duration::from_secs(1)));
|
||||||
static REVERSE_STORE: Lazy<DashMap<String, String>> = Lazy::new(|| DashMap::new());
|
// static REVERSE_STORE: Lazy<DashMap<String, String>> = Lazy::new(|| DashMap::new());
|
||||||
|
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)));
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LB {
|
pub struct LB {
|
||||||
@@ -43,14 +42,13 @@ pub struct LB {
|
|||||||
|
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
backend_id: Option<String>,
|
backend_id: Option<String>,
|
||||||
to_https: bool,
|
|
||||||
sticky_sessions: bool,
|
sticky_sessions: bool,
|
||||||
redirect_to: Option<String>,
|
// redirect_to: Option<String>,
|
||||||
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<Arc<Vec<(Arc<str>, Arc<str>)>>>,
|
client_headers: Option<Vec<(String, Arc<str>)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -59,9 +57,8 @@ impl ProxyHttp for LB {
|
|||||||
fn new_ctx(&self) -> Self::CTX {
|
fn new_ctx(&self) -> Self::CTX {
|
||||||
Context {
|
Context {
|
||||||
backend_id: None,
|
backend_id: None,
|
||||||
to_https: false,
|
|
||||||
sticky_sessions: false,
|
sticky_sessions: false,
|
||||||
redirect_to: None,
|
// redirect_to: None,
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
hostname: None,
|
hostname: None,
|
||||||
upstream_peer: None,
|
upstream_peer: None,
|
||||||
@@ -70,19 +67,9 @@ impl ProxyHttp for LB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
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> {
|
||||||
// let ep = _ctx.extraparams.as_ref();
|
|
||||||
if let Some(auth) = &_ctx.extraparams.authentication {
|
|
||||||
let authenticated = authenticate(&auth.auth_type, &auth.auth_cred, &session);
|
|
||||||
if !authenticated {
|
|
||||||
let _ = session.respond_error(401).await;
|
|
||||||
warn!("Forbidden: {:?}, {}", session.client_addr(), session.req_header().uri.path());
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 _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() {
|
||||||
@@ -98,38 +85,66 @@ impl ProxyHttp for LB {
|
|||||||
None => return Ok(false),
|
None => return Ok(false),
|
||||||
Some(host) => {
|
Some(host) => {
|
||||||
let optioninnermap = self.get_host(host, session.req_header().uri.path(), backend_id);
|
let optioninnermap = self.get_host(host, session.req_header().uri.path(), backend_id);
|
||||||
|
|
||||||
match optioninnermap {
|
match optioninnermap {
|
||||||
None => return Ok(false),
|
None => return Ok(false),
|
||||||
Some(ref innermap) => {
|
Some(ref innermap) => {
|
||||||
// Inner auth works only if global is disabled.
|
if let Some(auth) = _ctx.extraparams.authentication.as_ref().or(innermap.authorization.as_ref()) {
|
||||||
if let Some(auth) = &innermap.authorization {
|
if !authenticate(&auth.auth_type, &auth.auth_cred, session).await {
|
||||||
if _ctx.extraparams.authentication.is_none() {
|
|
||||||
let authenticated = authenticate(&auth.auth_type, &auth.auth_cred, &session);
|
|
||||||
if !authenticated {
|
|
||||||
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.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);
|
||||||
if curr_window_requests > rate {
|
if curr_window_requests > rate {
|
||||||
let mut buf = Buffer::new();
|
let header = ResponseHeader::build(429, None)?;
|
||||||
let rate_str = buf.format(rate);
|
|
||||||
let mut header = ResponseHeader::build(429, None)?;
|
|
||||||
header.insert_header("X-Rate-Limit-Limit", rate_str)?;
|
|
||||||
header.insert_header("X-Rate-Limit-Remaining", "0")?;
|
|
||||||
header.insert_header("X-Rate-Limit-Reset", "1")?;
|
|
||||||
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);
|
debug!("Rate limited: {:?}, {}", rate_key, rate);
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(redirect_to) = &innermap.redirect_to {
|
||||||
|
let uri = session.req_header().uri.path();
|
||||||
|
let capacity = redirect_to.len() + uri.len();
|
||||||
|
let mut s = String::with_capacity(capacity);
|
||||||
|
s.push_str(redirect_to);
|
||||||
|
s.push_str(uri);
|
||||||
|
let mut resp = ResponseHeader::build(StatusCode::MOVED_PERMANENTLY, None)?;
|
||||||
|
resp.insert_header("Location", s)?;
|
||||||
|
resp.insert_header("Content-Length", "0")?;
|
||||||
|
session.write_response_header(Box::new(resp), true).await?;
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if _ctx.extraparams.to_https.unwrap_or(false) || innermap.to_https {
|
||||||
|
if let Some(stream) = session.stream() {
|
||||||
|
if stream.get_ssl().is_none() {
|
||||||
|
if let Some(host) = _ctx.hostname.as_ref() {
|
||||||
|
let port = self.config.proxy_port_tls.as_deref().unwrap_or("443");
|
||||||
|
let uri = session.req_header().uri.path();
|
||||||
|
let capacity = host.len() + uri.len() + 8;
|
||||||
|
let mut s = String::with_capacity(capacity);
|
||||||
|
s.push_str("https://");
|
||||||
|
s.push_str(host);
|
||||||
|
if port != "443" {
|
||||||
|
s.push_str(":");
|
||||||
|
s.push_str(&port);
|
||||||
|
}
|
||||||
|
s.push_str(uri);
|
||||||
|
let mut resp = ResponseHeader::build(StatusCode::MOVED_PERMANENTLY, None)?;
|
||||||
|
resp.insert_header("Location", s)?;
|
||||||
|
resp.insert_header("Content-Length", "0")?;
|
||||||
|
session.write_response_header(Box::new(resp), true).await?;
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ctx.upstream_peer = optioninnermap;
|
_ctx.upstream_peer = optioninnermap;
|
||||||
@@ -150,38 +165,36 @@ impl ProxyHttp for LB {
|
|||||||
peer.options.verify_cert = false;
|
peer.options.verify_cert = false;
|
||||||
peer.options.verify_hostname = false;
|
peer.options.verify_hostname = false;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
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),
|
||||||
|
});
|
||||||
|
|
||||||
// Experimental optionsv
|
peer.options.idle_timeout = Some(Duration::from_secs(300));
|
||||||
// The following TCP optimizations were tested but caused performance degrade under heavy load:
|
peer.options.tcp_recv_buf = Some(128 * 1024);
|
||||||
// peer.options.tcp_keepalive = Some(TcpKeepalive {
|
End of experimental options
|
||||||
// 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 ctx.extraparams.to_https.unwrap_or(false) || innermap.to_https {
|
|
||||||
if let Some(stream) = session.stream() {
|
|
||||||
if stream.get_ssl().is_none() {
|
|
||||||
if let Some(host) = ctx.hostname.as_ref() {
|
|
||||||
let uri = session.req_header().uri.path_and_query().map_or("/", |pq| pq.as_str());
|
|
||||||
let port = self.config.proxy_port_tls.unwrap_or(443);
|
|
||||||
ctx.to_https = true;
|
|
||||||
let mut s = String::with_capacity(64);
|
|
||||||
write!(&mut s, "https://{}:{}{}", host, port, uri).unwrap_or_default();
|
|
||||||
ctx.redirect_to = Some(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.extraparams.sticky_sessions {
|
if ctx.extraparams.sticky_sessions {
|
||||||
let mut s = String::with_capacity(64);
|
let mut s = String::with_capacity(64);
|
||||||
write!(&mut s, "{}:{}:{}", innermap.address, innermap.port, innermap.is_ssl).unwrap();
|
write!(
|
||||||
|
&mut s,
|
||||||
|
"{}:{}:{}:{}:{}:{}:{}:{:?}",
|
||||||
|
hostname,
|
||||||
|
innermap.address,
|
||||||
|
innermap.port,
|
||||||
|
innermap.is_http2,
|
||||||
|
innermap.to_https,
|
||||||
|
innermap.rate_limit.unwrap_or_default(),
|
||||||
|
innermap.healthcheck.unwrap_or_default(),
|
||||||
|
innermap.authorization
|
||||||
|
)
|
||||||
|
.unwrap_or(());
|
||||||
ctx.backend_id = Some(s);
|
ctx.backend_id = Some(s);
|
||||||
ctx.sticky_sessions = true;
|
ctx.sticky_sessions = true;
|
||||||
}
|
}
|
||||||
@@ -216,18 +229,19 @@ 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(hostname) = ctx.hostname.as_deref() {
|
// if let Some(hostname) = ctx.hostname.as_deref() {
|
||||||
upstream_request.insert_header("Host", hostname)?;
|
// upstream_request.insert_header("Host", hostname)?;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if let Some(client_ip) = session.client_addr() {
|
if let Some(client_ip) = session.client_addr() {
|
||||||
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, "{}", client_ip).unwrap_or(());
|
||||||
upstream_request.append_header("x-forward-for", buf.as_str()).unwrap_or(false);
|
upstream_request.append_header("X-Forwarded-For", buf.as_str()).unwrap_or(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let hostname = ctx.hostname.as_deref().unwrap_or("localhost");
|
let hostname = ctx.hostname.as_deref().unwrap_or("localhost");
|
||||||
let path = session.req_header().uri.path();
|
let path = session.req_header().uri.path();
|
||||||
let GetHostsReturHeaders { server_headers, client_headers } = match self.get_header(hostname, path) {
|
let GetHostsReturHeaders { server_headers, client_headers } = match self.get_header(hostname, path) {
|
||||||
@@ -237,46 +251,43 @@ impl ProxyHttp for LB {
|
|||||||
|
|
||||||
if let Some(sh) = server_headers {
|
if let Some(sh) = server_headers {
|
||||||
for (k, v) in sh {
|
for (k, v) in sh {
|
||||||
upstream_request.insert_header(k.to_string(), v.as_ref())?;
|
upstream_request.insert_header(k, v.as_ref())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(ch) = client_headers {
|
if let Some(ch) = client_headers {
|
||||||
ctx.client_headers = Some(Arc::new(ch));
|
ctx.client_headers = Some(ch);
|
||||||
}
|
}
|
||||||
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 ctx.sticky_sessions {
|
||||||
if let Some(bid) = ctx.backend_id.clone() {
|
if let Some(bid) = &ctx.backend_id {
|
||||||
if REVERSE_STORE.get(&*bid).is_none() {
|
let tt = if let Some(existing) = REVERSE_STORE.get(bid) {
|
||||||
|
existing.value().clone()
|
||||||
|
} else {
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(bid.clone().into_bytes());
|
hasher.update(bid.as_bytes());
|
||||||
let hash = hasher.finalize();
|
let hash = hasher.finalize();
|
||||||
let hex_hash = base16ct::lower::encode_string(&hash);
|
let hex_hash = base16ct::lower::encode_string(&hash);
|
||||||
let hh = hex_hash[0..50].to_string();
|
let hh = hex_hash[0..50].to_string();
|
||||||
REVERSE_STORE.insert(bid.clone(), hh.clone());
|
REVERSE_STORE.insert(bid.clone(), hh.clone());
|
||||||
REVERSE_STORE.insert(hh.clone(), bid.clone());
|
REVERSE_STORE.insert(hh.clone(), bid.clone());
|
||||||
}
|
hh
|
||||||
if let Some(tt) = REVERSE_STORE.get(&*bid) {
|
};
|
||||||
let _ = _upstream_response.insert_header("set-cookie", format!("backend_id={}; Path=/; Max-Age=600; HttpOnly; SameSite=Lax", tt.value()));
|
// let _ = _upstream_response.insert_header("set-cookie", format!("backend_id={}; Path=/; Max-Age=600; HttpOnly; SameSite=Lax", tt));
|
||||||
}
|
let mut buf = String::with_capacity(80);
|
||||||
|
buf.push_str("backend_id=");
|
||||||
|
buf.push_str(&tt);
|
||||||
|
buf.push_str("; Path=/; Max-Age=600; HttpOnly; SameSite=Lax");
|
||||||
|
let _ = _upstream_response.insert_header("set-cookie", buf.as_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.to_https {
|
|
||||||
let mut redirect_response = ResponseHeader::build(StatusCode::MOVED_PERMANENTLY, None)?;
|
|
||||||
redirect_response.insert_header("Location", ctx.redirect_to.clone().unwrap_or(String::from("/")))?;
|
|
||||||
redirect_response.insert_header("Content-Length", "0")?;
|
|
||||||
session.write_response_header(Box::new(redirect_response), false).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ALLOCATIONS !
|
|
||||||
if let Some(client_headers) = &ctx.client_headers {
|
if let Some(client_headers) = &ctx.client_headers {
|
||||||
for (k, v) in client_headers.iter() {
|
for (k, v) in client_headers.iter() {
|
||||||
_upstream_response.append_header(k.to_string(), v.as_ref())?;
|
_upstream_response.append_header(k.clone(), v.as_ref())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// END ALLOCATIONS !
|
|
||||||
|
|
||||||
// session.set_keepalive(Some(300));
|
// session.set_keepalive(Some(300));
|
||||||
// println!("session.get_keepalive: {:?}", session.get_keepalive());
|
// println!("session.get_keepalive: {:?}", session.get_keepalive());
|
||||||
@@ -291,7 +302,8 @@ impl ProxyHttp for LB {
|
|||||||
code: session.response_written().map(|resp| resp.status),
|
code: session.response_written().map(|resp| resp.status),
|
||||||
latency: ctx.start_time.elapsed(),
|
latency: ctx.start_time.elapsed(),
|
||||||
version: session.req_header().version,
|
version: session.req_header().version,
|
||||||
upstream: ctx.hostname.clone().unwrap_or(Arc::from("localhost")),
|
// upstream: ctx.hostname.clone().unwrap_or(Arc::from("localhost")),
|
||||||
|
upstream: ctx.hostname.take().unwrap_or_else(|| Arc::from("localhost")),
|
||||||
};
|
};
|
||||||
calc_metrics(m);
|
calc_metrics(m);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
// use rustls::crypto::ring::default_provider;
|
// use rustls::crypto::ring::default_provider;
|
||||||
|
use crate::tls::grades;
|
||||||
|
use crate::tls::load;
|
||||||
|
use crate::tls::load::CertificateConfig;
|
||||||
use crate::utils::structs::Extraparams;
|
use crate::utils::structs::Extraparams;
|
||||||
use crate::utils::tls;
|
|
||||||
use crate::utils::tls::CertificateConfig;
|
|
||||||
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;
|
||||||
@@ -68,27 +69,27 @@ pub fn run() {
|
|||||||
watch_folder(certs_path, tx).unwrap();
|
watch_folder(certs_path, tx).unwrap();
|
||||||
});
|
});
|
||||||
let certificate_configs = rx.recv().unwrap();
|
let certificate_configs = rx.recv().unwrap();
|
||||||
let first_set = tls::Certificates::new(&certificate_configs, grade.as_str()).unwrap_or_else(|| panic!("Unable to load initial certificate info"));
|
let first_set = load::Certificates::new(&certificate_configs, grade.as_str()).unwrap_or_else(|| panic!("Unable to load initial certificate info"));
|
||||||
let certificates = Arc::new(ArcSwap::from_pointee(first_set));
|
let certificates = Arc::new(ArcSwap::from_pointee(first_set));
|
||||||
let certs_for_callback = certificates.clone();
|
let certs_for_callback = certificates.clone();
|
||||||
|
|
||||||
let certs_for_watcher = certificates.clone();
|
let certs_for_watcher = certificates.clone();
|
||||||
let new_certs = tls::Certificates::new(&certificate_configs, grade.as_str());
|
let new_certs = load::Certificates::new(&certificate_configs, grade.as_str());
|
||||||
certs_for_watcher.store(Arc::new(new_certs.unwrap()));
|
certs_for_watcher.store(Arc::new(new_certs.unwrap()));
|
||||||
|
|
||||||
let mut tls_settings =
|
let mut tls_settings =
|
||||||
TlsSettings::intermediate(&certs_for_callback.load().default_cert_path, &certs_for_callback.load().default_key_path).expect("unable to load or parse cert/key");
|
TlsSettings::intermediate(&certs_for_callback.load().default_cert_path, &certs_for_callback.load().default_key_path).expect("unable to load or parse cert/key");
|
||||||
|
|
||||||
tls::set_tsl_grade(&mut tls_settings, grade.as_str());
|
grades::set_tsl_grade(&mut tls_settings, grade.as_str());
|
||||||
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(tls::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, None, tls_settings);
|
||||||
|
|
||||||
let certs_for_watcher = certificates.clone();
|
let certs_for_watcher = certificates.clone();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
while let Ok(new_configs) = rx.recv() {
|
while let Ok(new_configs) = rx.recv() {
|
||||||
let new_certs = tls::Certificates::new(&new_configs, grade.as_str());
|
let new_certs = load::Certificates::new(&new_configs, grade.as_str());
|
||||||
match new_certs {
|
match new_certs {
|
||||||
Some(new_certs) => {
|
Some(new_certs) => {
|
||||||
certs_for_watcher.store(Arc::new(new_certs));
|
certs_for_watcher.store(Arc::new(new_certs));
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
use crate::utils::discovery::APIUpstreamProvider;
|
use crate::utils::discovery::APIUpstreamProvider;
|
||||||
|
// use std::net::SocketAddr;
|
||||||
|
use crate::utils::jwt::Claims;
|
||||||
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 axum::body::Body;
|
use axum::body::Body;
|
||||||
use axum::extract::{Query, State};
|
use axum::extract::{Query, State};
|
||||||
use axum::http::{Response, StatusCode};
|
use axum::http::{header::HeaderMap, Response, StatusCode};
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use axum::routing::{get, post};
|
use axum::routing::{get, post};
|
||||||
use axum::{Json, Router};
|
use axum::{Json, Router};
|
||||||
@@ -13,21 +15,14 @@ use futures::SinkExt;
|
|||||||
use jsonwebtoken::{encode, EncodingKey, Header};
|
use jsonwebtoken::{encode, EncodingKey, Header};
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use prometheus::{gather, Encoder, TextEncoder};
|
use prometheus::{gather, Encoder, TextEncoder};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Serialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
// use std::net::SocketAddr;
|
|
||||||
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 tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct InputKey {
|
|
||||||
master_key: String,
|
|
||||||
owner: String,
|
|
||||||
valid: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
struct OutToken {
|
struct OutToken {
|
||||||
token: String,
|
token: String,
|
||||||
@@ -88,24 +83,19 @@ pub async fn run_server(config: &APIUpstreamProvider, mut to_return: Sender<Conf
|
|||||||
axum::serve(listener, app).await.unwrap();
|
axum::serve(listener, app).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn conf(State(st): State<AppState>, Query(params): Query<HashMap<String, String>>, content: String) -> impl IntoResponse {
|
async fn conf(State(st): State<AppState>, Query(params): Query<HashMap<String, String>>, headers: HeaderMap, 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) = params.get("key") {
|
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.to_owned() == st.master_key {
|
if s.as_bytes().ct_eq(st.master_key.as_bytes()).into() {
|
||||||
let strcontent = content.as_str();
|
let strcontent = content.as_str();
|
||||||
let parsed = serde_yaml::from_str::<Config>(strcontent);
|
let parsed = serde_yml::from_str::<Config>(strcontent);
|
||||||
match parsed {
|
match parsed {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
if let Some(s) = params.get("key") {
|
|
||||||
if s.to_owned() == st.master_key {
|
|
||||||
let _ = tokio::spawn(async move { apply_config(content.as_str(), st).await });
|
let _ = tokio::spawn(async move { apply_config(content.as_str(), st).await });
|
||||||
return 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();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Access Denied !\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();
|
return Response::builder().status(StatusCode::BAD_GATEWAY).body(Body::from(format!("Failed: {}\n", err))).unwrap();
|
||||||
@@ -123,15 +113,21 @@ async fn apply_config(content: &str, mut st: AppState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn jwt_gen(State(state): State<AppState>, Json(payload): Json<InputKey>) -> (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 payload.master_key == state.master_key {
|
||||||
let now = SystemTime::now() + Duration::from_secs(payload.valid * 60);
|
let now = SystemTime::now() + Duration::from_secs(payload.exp * 60);
|
||||||
let a = now.duration_since(UNIX_EPOCH).unwrap().as_secs();
|
let expire = now.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
|
||||||
let claim = crate::utils::jwt::Claims { user: payload.owner, exp: a };
|
|
||||||
|
let claim = Claims {
|
||||||
|
master_key: String::new(),
|
||||||
|
owner: payload.owner,
|
||||||
|
exp: expire,
|
||||||
|
random: payload.random,
|
||||||
|
};
|
||||||
match encode(&Header::default(), &claim, &EncodingKey::from_secret(payload.master_key.as_ref())) {
|
match encode(&Header::default(), &claim, &EncodingKey::from_secret(payload.master_key.as_ref())) {
|
||||||
Ok(t) => {
|
Ok(t) => {
|
||||||
let tok = OutToken { token: t };
|
let tok = OutToken { token: t };
|
||||||
info!("Generating token: {:?}", tok);
|
info!("Generating token: {:?}", tok.token);
|
||||||
(StatusCode::CREATED, Json(tok))
|
(StatusCode::CREATED, Json(tok))
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user