From 28aaebc23ac5a85d35a3b775304f0b5eb081ce3f Mon Sep 17 00:00:00 2001 From: Luiz Fonseca Date: Fri, 17 May 2024 01:18:06 +0200 Subject: [PATCH] feat(config): enable letsencrypt options via config file --- .gitignore | 2 +- Cargo.toml | 6 +- README.md | 9 +-- examples/config.example.toml | 37 ----------- examples/config.example.yaml | 28 -------- examples/proksi.toml | 123 +++++++++++++++++++++++++++++++++++ examples/proksi.yaml | 120 ++++++++++++++++++++++++++++++++++ src/config/mod.rs | 40 ++++++++++++ src/main.rs | 1 - 9 files changed, 292 insertions(+), 74 deletions(-) delete mode 100644 examples/config.example.toml delete mode 100644 examples/config.example.yaml create mode 100644 examples/proksi.toml create mode 100644 examples/proksi.yaml diff --git a/.gitignore b/.gitignore index 156f8bd..5268696 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ .DS_Store /data /dist -proksi.yaml +/proksi.yaml .zed/ .vscode/ /tmp diff --git a/Cargo.toml b/Cargo.toml index 954d12a..44d9e0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,10 @@ incremental = true [profile.release] opt-level = 2 -strip = "symbols" # Automatically strip symbols from the binary. -lto = true # Enable link-time optimization. +strip = "symbols" # Automatically strip symbols from the binary. +lto = true # Enable link-time optimization. debug = false -codegen-units = 4 +codegen-units = 12 [dependencies] anyhow = "1.0.83" diff --git a/README.md b/README.md index b0d00ee..e13e6b5 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Proksi is a simple, lightweight, and easy-to-use proxy server that automatically - [Configuration](#configuration) - [YAML/TOML Configuration](#yamltoml-configuration) - [Environment variables](#environment-variables) - - [Examples](#examples) + - [Configuration Examples](#examples) - [Performance \& Benchmarks](#performance--benchmarks) - [Why build another proxy...?](#why-build-another-proxy) @@ -296,7 +296,7 @@ export PROKSI_ROUTES='[{host="example.com", upstreams=[{ip="10.0.1.24", port=300 In the future you might be able to use `PROKSI_ROUTES__0__HOST` to set the host of the first route (or any other), but this is not yet implemented. -## Examples +## Configuration Examples See [the examples folder](./examples) to learn about how to use Proksi. @@ -308,8 +308,9 @@ An sample run from the `wrk` benchmark on the simple `/ping` endpoint shows the ```bash # Apple M1 Pro, 16GB -# Memory Usage: 15MB, CPU Usage: 13%, 12 threads -# Ran at 2024-05-16T23:47 +# Memory Usage: 15MB, CPU Usage: 13%, 4 worker threads +# at 2024-05-16T23:47 +# Wrk > 50 connections, 4 threads, 30s duration wrk -c 50 -t 4 -d 30s http://127.0.0.1/ping Running 30s test @ http://127.0.0.1/ping diff --git a/examples/config.example.toml b/examples/config.example.toml deleted file mode 100644 index 28ac31f..0000000 --- a/examples/config.example.toml +++ /dev/null @@ -1,37 +0,0 @@ -service_name = "proksi" - -[logging] -level = "INFO" -access_logs_enabled = true -error_logs_enabled = false - -[paths] -tls_certificates = "/etc/proksi/certificates" -tls_challenges = "/etc/proksi/challenges" -tls_order = "/etc/proksi/orders" -tls_account_credentials = "/etc/proksi/account-credentials" - -[[routes]] -host = "example.com" -path_prefix = "/api" - -[[routes.headers.add]] -name = "X-Forwarded-For" -value = "" - -[[routes.headers.add]] -name = "X-Api-Version" -value = "1.0" - -[[routes.headers.remove]] -name = "Server" - -[[routes.upstreams]] -ip = "10.1.2.24/24" -port = 3_000 -network = "public" - -[[routes.upstreams]] -ip = "10.1.2.23/24" -port = 3_000 -network = "shared" diff --git a/examples/config.example.yaml b/examples/config.example.yaml deleted file mode 100644 index adb6e15..0000000 --- a/examples/config.example.yaml +++ /dev/null @@ -1,28 +0,0 @@ -service_name: "proksi" -logging: - level: "INFO" - access_logs_enabled: true - error_logs_enabled: false -paths: - tls_certificates: "/etc/proksi/certificates" - tls_challenges: "/etc/proksi/challenges" - tls_order: "/etc/proksi/orders" - tls_account_credentials: "/etc/proksi/account-credentials" -routes: - - host: "example.com" - path_prefix: "/api" - headers: - add: - - name: "X-Forwarded-For" - value: "" - - name: "X-Api-Version" - value: "1.0" - remove: - - name: "Server" - upstreams: - - ip: "10.1.2.24/24" - port: 3000 - network: "public" - - ip: "10.1.2.23/24" - port: 3000 - network: "shared" diff --git a/examples/proksi.toml b/examples/proksi.toml new file mode 100644 index 0000000..c18d27b --- /dev/null +++ b/examples/proksi.toml @@ -0,0 +1,123 @@ +# Description: Example configuration file for Proksi +# +# Proksi is a reverse proxy server that can be used to route incoming requests to different upstream servers based on the request's host, path, headers, and other attributes. +# +# This configuration file specifies the following settings: +# +# +# ------------------------------------------------------------------ +# The name of the service is "proksi". +# This will show in logs and it's mostly used for log filtering if needed +service_name = "proksi" + +# Number of threads that the HTTPS service will use to handle incoming requests. +# This can be adjusted based on the number of CPU cores available on the server. +# The default value is 1. +# +# Note: Increasing the number of threads can improve the performance of the server, but it can also increase the memory usage. +# +# Note 2: This only affect the HTTPS service, the HTTP service +# (and other background services) is single threaded. +worker_threads = 4 + +# The configuration for the Let's Encrypt integration. +[letsencrypt] +# Whether the Let's Encrypt integration is enabled +# (the background service will run and issue certificates for your routes). +enabled = true + +# The email address to use for Let's Encrypt notifications and account registration. +# Important: Make sure to replace this with your own email address. +# any "@example.com" is invalid and will not work. +email = "your-email@example.com" + +# The staging flag is used to test the Let's Encrypt integration without hitting the rate limits. +# When set to , the integration will use the Let's Encrypt staging environment. +# -- +# When set to , the integration will use the Let's Encrypt production environment +# and certificates will be publicly trusted for 90 days. +staging = true + +# The logging configuration for the server. +[logging] +# The log level for the server (can be "DEBUG", "INFO", "WARN", "ERROR"). +level = "INFO" + +# Whether access logs are enabled. +# When set to , the server will log information about incoming requests. +# This information includes the request method, path, status code, response time and more. +access_logs_enabled = true + +# Whether error logs are enabled. +error_logs_enabled = false + +# The paths for the TLS certificates, challenges, orders, and account credentials. +# You can override any, these are the current defaults. +[paths] +# The path where the TLS certificates will be stored in JSON format +tls_certificates = "/etc/proksi/certificates" + +# The path where the TLS challenges will be stored in JSON format +tls_challenges = "/etc/proksi/challenges" + +# The path where the TLS orders will be stored in JSON format +tls_order = "/etc/proksi/orders" + +# The path where the TLS account credentials will be stored in JSON format +# This is used to register an account with Let's Encrypt and it's reused as +# long as the folder is not deleted. +tls_account_credentials = "/etc/proksi/account-credentials" + +# The list of routes that the server will use to route incoming requests +# to different upstream servers. +# Each route is an item in the list and it has the following attributes: +[[routes]] + +# The host attribute specifies the hostname that the route will match. +# This is normally the domain, subdomain that you want to route to a particular server/ip. +# This can be a domain name or an IP address. For IP address, no certificate will be issued. +# The host attribute is required. +host = "example.com" + +# The path_prefix attribute specifies the path prefix that the route will match. +path_prefix = "/api" + +# The headers attribute specifies the headers that will +# be added or removed at the *end* of the response +# -- +# In the near future you will be able to modify the headers +# of the request send to the upstream server +# (Adds) the given headers to the dowstream (client) response +# +[[routes.headers.add]] +name = "X-Forwarded-For" +value = "" + +[[routes.headers.add]] +name = "X-Api-Version" +value = "1.0" + +# Removes the given headers from the dowstream (client) response +[[routes.headers.remove]] +name = "Server" + +# The upstreams attribute specifies the list of upstream servers that the route will use. +# These are load balanced and the server will try to connect to the first one in the list. +# If the connection fails, it will try the next one. +# -- +# Health checks run in the background to ensure you have a healthy connection always. +[[routes.upstreams]] + +# The IP address of the upstream server +# (can be any IP address, as long as Proksi can access it). +ip = "10.1.2.24/24" +# The port of the upstream server (can be any port). +port = 3_000 +# The network attribute specifies the network that the upstream server is part of. +# This is mostly important for Docker containers, but it can be used for other purposes. +network = "public" + +[[routes.upstreams]] +ip = "10.1.2.23/24" +port = 3_000 +network = "shared" diff --git a/examples/proksi.yaml b/examples/proksi.yaml new file mode 100644 index 0000000..bd6acb3 --- /dev/null +++ b/examples/proksi.yaml @@ -0,0 +1,120 @@ +# Description: Example configuration file for Proksi +# +# Proksi is a reverse proxy server that can be used to route incoming requests to different upstream servers based on the request's host, path, headers, and other attributes. +# +# This configuration file specifies the following settings: +# +# +# ------------------------------------------------------------------ +# The name of the service is "proksi". +# This will show in logs and it's mostly used for log filtering if needed +service_name: "proksi" + +# Number of threads that the HTTPS service will use to handle incoming requests. +# This can be adjusted based on the number of CPU cores available on the server. +# The default value is 1. +# +# Note: Increasing the number of threads can improve the performance of the server, but it can also increase the memory usage. +# +# Note 2: This only affect the HTTPS service, the HTTP service +# (and other background services) is single threaded. +worker_threads: 4 + +# The configuration for the Let's Encrypt integration. +letsencrypt: + + # Whether the Let's Encrypt integration is enabled + # (the background service will run and issue certificates for your routes). + enabled: true + + # The email address to use for Let's Encrypt notifications and account registration. + # Important: Make sure to replace this with your own email address. + # any "@example.com" is invalid and will not work. + email: "your-email@example.com" + + # The staging flag is used to test the Let's Encrypt integration without hitting the rate limits. + # When set to , the integration will use the Let's Encrypt staging environment. + # -- + # When set to , the integration will use the Let's Encrypt production environment + # and certificates will be publicly trusted for 90 days. + staging: true + +# The logging configuration for the server. +logging: + # The log level for the server (can be "DEBUG", "INFO", "WARN", "ERROR"). + level: "INFO" + + # Whether access logs are enabled. + # When set to , the server will log information about incoming requests. + # This information includes the request method, path, status code, response time and more. + access_logs_enabled: true + + # Whether error logs are enabled. + error_logs_enabled: false + +# The paths for the TLS certificates, challenges, orders, and account credentials. +# You can override any, these are the current defaults. +paths: + + # The path where the TLS certificates will be stored in JSON format + tls_certificates: "/etc/proksi/certificates" + + # The path where the TLS challenges will be stored in JSON format + tls_challenges: "/etc/proksi/challenges" + + # The path where the TLS orders will be stored in JSON format + tls_order: "/etc/proksi/orders" + + # The path where the TLS account credentials will be stored in JSON format + # This is used to register an account with Let's Encrypt and it's reused as + # long as the folder is not deleted. + tls_account_credentials: "/etc/proksi/account-credentials" + + +# The list of routes that the server will use to route incoming requests +# to different upstream servers. +# Each route is an item in the list and it has the following attributes: +routes: + + # The host attribute specifies the hostname that the route will match. + # This is normally the domain, subdomain that you want to route to a particular server/ip. + # This can be a domain name or an IP address. For IP address, no certificate will be issued. + # The host attribute is required. + - host: "example.com" + + # The path_prefix attribute specifies the path prefix that the route will match. + path_prefix: "/api" + + # The headers attribute specifies the headers that will + # be added or removed at the end of the response + # -- + # In the near future you will be able to modify the headers + # of the request send to the upstream server + headers: + # Adds the given headers to the dowstream (client) response + add: + - name: "X-Forwarded-For" + value: "" + - name: "X-Api-Version" + value: "1.0" + # Removes the given headers from the dowstream (client) response + remove: + - name: "Server" + # The upstreams attribute specifies the list of upstream servers that the route will use. + # These are load balanced and the server will try to connect to the first one in the list. + # If the connection fails, it will try the next one. + # -- + # Health checks run in the background to ensure you have a healthy connection always. + upstreams: + # The IP address of the upstream server + # (can be any IP address, as long as Proksi can access it). + - ip: "10.1.2.24/24" + # The port of the upstream server (can be any port). + port: 3000 + + # The network attribute specifies the network that the upstream server is part of. + # This is mostly important for Docker containers, but it can be used for other purposes. + network: "public" + - ip: "10.1.2.23/24" + port: 3000 + network: "shared" diff --git a/src/config/mod.rs b/src/config/mod.rs index e2ae201..9715764 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -6,6 +6,27 @@ use figment::{ use serde::{Deserialize, Deserializer, Serialize}; use tracing::level_filters::LevelFilter; +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ConfigLetsEncrypt { + /// The email to use for the let's encrypt account + pub email: String, + /// Whether to enable the background service that renews the certificates (default: true) + pub enabled: Option, + + /// Use the staging let's encrypt server (default: true) + pub staging: Option, +} + +impl Default for ConfigLetsEncrypt { + fn default() -> Self { + Self { + email: "contact@example.com".to_string(), + enabled: Some(true), + staging: Some(true), + } + } +} + #[derive(Debug, Serialize, Deserialize)] pub struct ConfigPath { // TLS @@ -142,6 +163,10 @@ pub struct ConfigLogging { /// level: "INFO" /// access_logs_enabled: true /// error_logs_enabled: false +/// letsencrypt: +/// enabled: true +/// email: "youremail@example.com" +/// production: true /// paths: /// config_file: "/etc/proksi/config.toml" /// tls_certificates: "/etc/proksi/certificates" @@ -196,6 +221,9 @@ pub(crate) struct Config { #[command(flatten)] pub logging: ConfigLogging, + #[clap(skip)] + pub letsencrypt: ConfigLetsEncrypt, + /// Configuration for paths (TLS, config file, etc.) #[clap(skip)] pub paths: ConfigPath, @@ -214,6 +242,7 @@ impl Default for Config { config_path: "/etc/proksi/config".to_string(), service_name: "proksi".to_string(), worker_threads: Some(1), + letsencrypt: ConfigLetsEncrypt::default(), routes: vec![], logging: ConfigLogging { level: LogLevel::Info, @@ -349,6 +378,8 @@ mod tests { )?; jail.set_env("PROKSI_SERVICE_NAME", "new_name"); jail.set_env("PROKSI_LOGGING__LEVEL", "warn"); + jail.set_env("PROKSI_LETSENCRYPT__STAGING", "false"); + jail.set_env("PROKSI_LETSENCRYPT__EMAIL", "my-real-email@domain.com"); jail.set_env( "PROKSI_ROUTES", r#"[{ @@ -362,6 +393,10 @@ mod tests { let proxy_config = config.unwrap(); assert_eq!(proxy_config.service_name, "new_name"); assert_eq!(proxy_config.logging.level, LogLevel::Warn); + + assert_eq!(proxy_config.letsencrypt.staging, Some(false)); + assert_eq!(proxy_config.letsencrypt.email, "my-real-email@domain.com"); + assert_eq!(proxy_config.routes[0].host, "changed.example.com"); assert_eq!(proxy_config.routes[0].upstreams[0].ip, "10.0.1.2/24"); @@ -409,6 +444,7 @@ mod tests { let proxy_config = config.unwrap(); let logging = proxy_config.logging; let paths = proxy_config.paths; + let letsencrypt = proxy_config.letsencrypt; assert_eq!(proxy_config.service_name, "proksi"); assert_eq!(logging.level, LogLevel::Info); @@ -416,6 +452,10 @@ mod tests { assert_eq!(logging.error_logs_enabled, false); assert_eq!(proxy_config.routes.len(), 1); + assert_eq!(letsencrypt.email, "contact@example.com"); + assert_eq!(letsencrypt.enabled, Some(true)); + assert_eq!(letsencrypt.staging, Some(true)); + assert_eq!(paths.tls_account_credentials, "/etc/proksi/tls/account"); assert_eq!(paths.tls_certificates, "/etc/proksi/tls/certificates"); diff --git a/src/main.rs b/src/main.rs index f16abdc..c7b9a6b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,7 +30,6 @@ fn main() -> Result<(), anyhow::Error> { // Loads configuration from command-line, YAML or TOML sources let proxy_config = load_proxy_config("/etc/proksi/configs")?; - // let file_appender = tracing_appender::rolling::hourly("./tmp", "proksi.log"); let (log_sender, log_receiver) = mpsc::unbounded_channel::>(); let proxy_logger = ProxyLogger::new(log_sender);