- Rust 99.3%
- HTML 0.7%
| src | extract http transport code from main.rs to files | |
| .gitignore | ||
| Cargo.lock | experimental websocket to tcp proxy | |
| Cargo.toml | experimental websocket to tcp proxy | |
| comparison.md | proxy parse from string | |
| COPYING | ||
| readme.md | experimental websocket to tcp proxy | |
| rust-toolchain | ||
gnix
a simple stupid reverse proxy
Features
- HTTP/1.1, HTTP/2 and HTTP/3 support by default
- Simple and predictable configuration (see below)
- Somewhat fast (about 150k req/s on a rpi5 for simple routes)
- Composable modules
- Handles connection upgrades correctly by default (websocket, etc.)
- TLS support with automatic certificate selection
- Configuration hot-reloading
- Client authentification (http basic auth, form + cookie)
Links
- AUR Package
- Matrix Room for questions, announcements and general discussion around the software.
Installation
On Arch Linux use gnix from the AUR. Otherwise install rustup, then run
cargo install --git https://codeberg.org/metamuffin/gnix --tag v2.4.1.
Quick Start
Run the binary with the a path to the configuration as the first argument. The configuration file is written in YAML and could look like this:
http:bind:"[::]:80"https:bind:"[::]:443"cert_path:"/etc/letsencrypt/live"# Scans directory and selects a valid certificate based on hostnamehandler:!hosts# !hosts routes requests for different hostnames.# requests for `example.org` are forwarded to 127.0.0.1:8000."example.org": !proxy { backend: "127.0.0.1:8000" }# requests for `mydomain.com` will access files from /srv/http."mydomain.com": !filesroot:"/srv/http"index:true# requests for `panel.mydomain.com` will be logged, authentificated and passed on to `otherserver`."panel.mydomain.com": !access_logfile:"/var/log/gnix/panel"next:!cookie_authusers:"/etc/panel_creds"expire:86400fail:!file { path: "/usr/share/gnix/login.html" }next:!proxy { backend: "otherserver:80" }Command-line Option Reference
Usage: gnix [OPTIONS] <CONFIG>
Arguments:
<CONFIG> Path to main configuration file in YAML format
Options:
-u, --user <USER>
Switch to specified user after loading configuration and certificates
-s, --control-socket <CONTROL_SOCKET>
Create/Use a control socket to reload configuration with
-w, --watch
Watch the configuration file and reload on changes. Can be used with --reload
-r, --reload
Reload configuration of another running instance. Requires --control-socket option
-h, --help
Print help
--reload and --user are only supported on UNIX platforms.
Configuration Reference
The configuration uses YAML formatting. When the configuration file is changed, it will automatically be loaded and applied if valid. Configuration errors are reported in stderr and are only fatal at startup.
-
section
http- Optional section. Omit to disable unencrypted http.
bind: Addresses to accept http requests on (string or list of strings).
-
section
https- Optional section. Omit to disable https.
bind: Addresses to accept https requests on (string or list of strings).cert_path: Path to a directory structure that certificates are loaded from. The hierachy should contain directories containing correspondingfullchain.pemandprivkey.pemfiles. The correct certificate is selected automatically by subject (CN). Pointing this directly at/etc/letsencrypt/liveis possible. (string or list of strings)cert_fallback: Path to a single directory containing a key-cert pair that is used when no other certificate seems appropriate. This is useful for testing locally with a self-signed subjectless certificate. (optional string)disable_h1: Stops advertising HTTP/1.1 support but still continues to support it. Default: falsedisable_h2: Stops advertising HTTP/2 support. Default: falsedisable_h3: Disables HTTP/3 support. Default: false
-
section
limits- Note: Make sure you do not exceed the maximum file descriptor limit on your platform.
max_incoming_connections: Maximum number of incoming (downstream) connections over TCP transport. Excess connections are rejected. Default: 512max_incoming_connections_h3: Maximum number of connections via HTTP/3 where connections are cheaper due to reuse of a single UDP socket. Default: 4096max_requests_per_connnection: Maximum number of parallel streams/requests per HTTP/3 connection. Default: 16max_outgoing_connections: Maximum number of maximum outgoing (upstream) connections. Excess connections are rejected. Default: 256
-
section
handler- A module to handle all requests. Usually an instance of
hostsorpaths.
- A module to handle all requests. Usually an instance of
-
source_ip_from_header: Uses the X-Real-IP header for source ip address, requests without it will be rejected. Useful when gnix is behing another proxy. -
private_key: An array of 32 random 8-bit numbers used for cryptographic purposes such as encrypting cookies. If not specified these are randomly choosen at startup which results in all cookie auth session to be invalid after a restart. -
alpn: Maps from ALPN protocols for proxying (map from string to endpoint addr)
Modules
Modules handle requests. Some of them have arguments which are modules themselves; in that case the request is passed on.
-
module
hosts- Hands over the requests to different modules depending on the
hostheader. Special keys are:fallbackwhich handles hosts that are not found in the map and:noneto handle requests without a host header. - Takes a map from hostname (string) to handler (module)
- Hands over the requests to different modules depending on the
-
module
paths- Routes requests by matching the path against regexes. The first matching regex determines the module to handle this request. If the regex has an unnamed capture group, its value will become the downstream request URI.
- Takes a map from regex (string) to handler (module) OR a sequence of such maps. The priority is assigned first by position in the sequence then alphabetically in the map.
-
module
proxy- Forwards the request as-is to some other server. the
x-real-ipheader is added to the request. Connection upgrades are handled by direct forwarding of network traffic. Can either be specified as string directly if only backend field is required. backend: IP socket address or absolute path to unix socket of the backend server. (string, endpoint address)set_real_ip: Sets theX-Real-IPheader. (boolean)set_forwarded_for: Sets theX-Forwarded-For,X-Forwarded-Host,X-Forwaded-Proto,X-Forwarded-SchemeandX-Forwarded-Portheaders. (boolean)
- Forwards the request as-is to some other server. the
-
module
files- Provides a simple built-in fileserver. The server handles
accept-ranges. Thecontent-typeheader is inferred from the file extension and falls back toapplication/octet-stream. If a directory is requestedindex.htmlwill be served or else when indexing is enabled,index.banner.htmlwill be prepended to the response. root: root directory to be served (string)index: enables directory indexing (boolean)localize: sends a localized version if possible. for localized content the ISO 639-1 language code is injected in front of the first file name extension (boolean) semi-experimental
- Provides a simple built-in fileserver. The server handles
-
module
file- Basic HTTP response with custom body.
path: File path to the content.preload: Load file into memory at startup. (boolean)content: inline declaration of the content. (string)type: type of content reflected incontent-typeheader. (string)status: returned HTTP status code (int)
-
module
access_log- Logs requests to a file.
file: file path to log (string)reject_on_fail: rejects requests if log could not be written (boolean)flush: flushes log on every request (boolean)next: module for further handling of the request (module)
-
module
error- Rejects every request with a custom error message.
- Takes an error message (string)
-
module
http_basic_auth- Filters requests via HTTP Basic Authentification. Unauthorized clients will be challenged.
realm: describes what the user is logging into (most modern browsers dont show this anymore -_-)users: list of valid logins (credentials)next: a module to handle this request on successfully authentificated. (module)
-
module
cookie_auth- Authentificates a client based on cookies. The cookies are set on login by a
POST request to
/_gnix_loginwith form fieldspasswordandusername(optional, default: "user") inx-www-form-urlencodedformat. In any case the users submitting this request will be directed back to the page they come from. For successful logins two cookies are set:gnix_usernamecontaining the username andgnix_authcontaining an opaque authentification token. Thegnix_usernamecookie is authentificated by gnix and can therefore be used by applications. Alternatively a login may be implemented by returning thegnix-auth-successheader with a username as the value from thefailhandler, which is handled like a sucessful login for that user. Ifgnix-auth-no-redirectis set the response of thefailhandler is passed instead of a redirect being sent. This method can be useful for implementing custom login logic like OTP login or a CAPTCHA. users: list of valid logins (credentials)expire: seconds before logins expire; not setting this option keeps the login valid forever on the server but cleared after the session on the client (optional number)secure: makes the cookies accessable from secure contexts exclusively i.e. HTTPS (boolean)next: a module to handle this request on successfully authentification. (module)fail: a module to handle the request when a user is not authorized. This could show an HTML form prompting the user to log in. An implementation of such a form is provided with the distribution of this software, usually in/usr/share/gnix/login.html. It can return thegnix-auth-successheader, see above. (module)
- Authentificates a client based on cookies. The cookies are set on login by a
POST request to
-
module
switch- Decides between two possible routes based on a condition.
condition:!is_websocket_upgrade: Checks if a websocket was requested.!is_{get,post,put,patch,options}: Checks if this is a GET/... request!path_starts_with <prefix>: Checks if the URI path starts with some prefix!path_is <path>: Checks if the URI path is exactly what you specified!has_header <name>: Checks if the request includes a certain header.!http_version [n]: Checks for HTTP version n where HTTP/0.9 is considered 1 aswell.!any [conditions]: Checks if any of a set of conditions are satisfied.!all [conditions]: Checks if all conditions are satisfied.
case_trueHandler with matched requests (module)case_falseHandler for all other requests (module)
-
module
headers- Appends multiple headers to the response.
request: A map of all header name-value pairs added before the request is passed tonext. (object string:string)response: A map headers added to responsed fromnext. (object string:string)next: The handler to add the headers to. (module)
-
module
redirect- Responds with a permanent redirect.
- Takes the location to redirect to. (string)
-
module
cgi- Runs a CGI script on the request.
bin: Path to the CGI binary (string)user: User that the script is executed as. Requires to run gnix as root. (optional string) (unix only)args: List of arguments. (list of string)env: Environment variables. (map from string to string)pwd: Working directory path. (optional string)
-
module
cache- Caches requests. This is experimental! Don't use this.
- Not documented yet
-
module
loadbalance- Balances load across multiple modules (presumably proxied backends). The current implementation selects the least busy handler for each request and does not scale well to many modules. This is experimental! Don't use this.
- Takes a set of handlers. (sequence of module)
-
module
fallback- Runs a series of handlers until one returns a success, informational or redirect response and the returns that. The last handler's response will unconditionally be returned. Note that to run multiple handlers the request body will first be completely received to memory before any handler is run. There is currently no size limit for request bodies, so be careful using this. TODO request size limit
- Takes a sequence of handlers.
-
module
limits- Limits size and transmission rate of request and response bodies. The limit is enforced on an internal data frame level and does therefore not exactly reach the specified limits, but never exceeds them.
{request,response}.rateMaximum transmission rate in bytes per second. (number){request,response}.rate_bufferHow much transmission time can be accumulated by not reading for some time in milliseconds. (number){request,response}.rate_buffer_filledIf the rate buffer is filled up initially. (boolean){request,response}.sizeMaximum total body size. The body is cut off before the frame that exceeds this limit. Therefore the body is up to one frame size smaller than allowed.next: Inner handler. (module)
-
module
ratelimit- Limits the rate at which requests can be processed. For this every identity (see below) has a request counter. The counter is reset after a fixed time delay.
reference_duration: Duration in seconds after which request the counter are reset.identity: Requests are counted per identity. Default is source address.!global: Use a central counter!source_address: Count per source ip address!source_address_trunc: Same but truncate them before. Requires keysv4andv6which control how many trailing bits are discarded respectively.!path: Count per path (excluding query)!path_query: Count per path (including query)
max_identities: Always rejects requests if there are already more than this many identites tracked.thresholds: A list of[threshold, mode]pairs that are checked and conditionally executed in order.!too_many_requests: Responds with a empty request with 429 status code andRetry-Afterheader set. Later thresholds are not checked.!exec <path>: Invokes a script like CGI would but expects no output.
next: Inner handler. (module)
-
module
semaphore- Limits the number of requests processed in parallel
permits: Permit count (number)next: Inner handler. (module)
-
module
debug- Replies with information about the request to debug. Includes source address, HTTP version, method, URI and headers.
-
module
upgrade_insecure- Redirects requests from HTTP to HTTPS when
Upgrade-Insecure-Requestsis present in the request. The redirect URI's authority will be theHostheader of the request. Do not use behind another proxy because requests will always be HTTP and result in an endless redirect loop. next: Inner handler. (module)
- Redirects requests from HTTP to HTTPS when
Credentials config format
Login credentials for cookie_auth and http_basic_auth are supplied as either
an object mapping usernames to PHC strings or a file path pointing to a file
that contains that map in YALM format. Currently only argon2id is supported.
Additional Notes
Internally gnix processes requests as they would be sent in HTTP/1.1. HTTP/2 and HTTP/3 is translated on arrival.
Paths matching /_gnix* might be used internally in gnix for purposes like
OpenID callback or login action endpoints. I hope your application doesn't rely
on using them for itself.
License
AGPL-3.0-only; see COPYING