# 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](https://aur.archlinux.org/packages/gnix) - [Matrix Room](https://matrix.to/#/#gnix:metamuffin.org) for questions, announcements and general discussion around the software. ## Installation On arch use `gnix` from the AUR; Otherwise install `rustup`, then run `cargo install --git https://codeberg.org/metamuffin/gnix --tag v2.x.x`. ## 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: ```yaml http: bind: "[::]:80" https: bind: "[::]:443" cert_path: "/etc/letsencrypt/live" # Scans directory and selects a valid certificate based on hostname handler: !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": !files root: "/srv/http" index: true # requests for `panel.mydomain.com` will be logged, authentificated and passed on to `otherserver`. "panel.mydomain.com": !access_log file: "/var/log/gnix/panel" next: !cookie_auth users: "/etc/panel_creds" expire: 86400 fail: !file { path: "/usr/share/gnix/login.html" } next: !proxy { backend: "otherserver:80" } ``` ## 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 corresponding `fullchain.pem` and `privkey.pem` files. The correct certificate is selected automatically by subject (`CN`). Pointing this directly at `/etc/letsencrypt/live` is 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: false - `disable_h2` Stops advertising HTTP/2 support. Default: false - `disable_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` number of maximum incoming (downstream) connections over TCP transport. excess connections are rejected. Default: 512 - `max_incoming_connections_h3` same but for HTTP/3 where connections are cheaper due to reuse of a single UDP socket. Default: 4096 - `max_outgoing_connections` number of maximum outgoing (upstream) connections. excess connections are rejected. Default: 256 - **section `handler`** - A module to handle all requests. Usually an instance of `hosts` or `paths`. - `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. - `watch_config`: boolean if to watch the configuration file for changes and apply them accordingly. Default: true (Note: This will watch the entire parent directory of the config since most editors first move the file. Currently any change will trigger a reload. TODO) ### 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 `host` header. Special keys are `:fallback` which handles hosts that are not found in the map and `:none` to handle requests without a host header. - Takes a map from hostname (string) to handler (module) - **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-ip` header is added to the request. Connection upgrades are handled by direct forwarding of network traffic. - `backend`: IP socket address or absolute path to unix socket of the backend server. (string) - `set_real_ip`: Sets the `X-Real-IP` header. (boolean) - `set_forwarded_for`: Sets the `X-Forwarded-For`, `X-Forwarded-Host`, `X-Forwaded-Proto`, `X-Forwarded-Scheme` and `X-Forwarded-Port` headers. (boolean) - **module `files`** - Provides a simple built-in fileserver. The server handles `accept-ranges`. The `content-type` header is inferred from the file extension and falls back to `application/octet-stream`. If a directory is requested `index.html` will be served or else when indexing is enabled, `index.banner.html` will 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** - **module `file`** - Replies with static content. - `path`: file path to the content. _This will be loaded into memory at startup!_ (string) - `content`: inline declaration of the content. (string) - `type`: type of content reflected in `content-type` header. (string) - **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_login` with form fields `password` and `username` (optional, default: "user") in `x-www-form-urlencoded` format. 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_username` containing the username and `gnix_auth` containing an opaque authentification token. The `gnix_username` cookie is authentificated by gnix and can therefore be used by applications. Alternatively a login may be implemented by returning the `gnix-auth-success` header with a username as the value from the `fail` handler, which is handled like a sucessful login for that user. If `gnix-auth-no-redirect` is set the response of the `fail` handler 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 the `gnix-auth-success` header, see above. (module) - **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 `: Checks if the URI path starts with some prefix - `!path_is `: Checks if the URI path is exactly what you specified - `!has_header `: 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_true` Handler with matched requests (module) - `case_false` Handler 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 to `next`. (object string:string) - `response`: A map headers added to responsed from `next`. (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) - `args`: List of arguments. (list of string) - `env`: Environment variables. (map from string to 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}.rate` Maximum transmission rate in bytes per second. (number) - `{request,response}.rate_buffer` How much transmission time can be accumulated by not reading for some time in milliseconds. (number) - `{request,response}.rate_buffer_filled` If the rate buffer is filled up initially. (boolean) - `{request,response}.size` Maximum 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. - **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 keys `v4` and `v6` which 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 and `Retry-After` header set. Later thresholds are not checked. - `!exec `: Invokes a script like CGI would but expects no output. - `next`: Inner handler. (module) - **module `debug`** - Replies with information about the request to debug. Includes source address, HTTP version, method, URI and headers. #### 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](./COPYING)