reproxy

repository·master·Indexed 23 days ago

https://github.com/umputun/reproxy

A lightweight, high-performance edge HTTP(s) reverse proxy featuring dynamic service discovery via Docker and Consul, automatic SSL termination via Let's Encrypt, and flexible proxy rules using exact matches or regex. It supports multiple providers (Static, File, Docker, and Consul Catalog), traffic compression, IP-based access control, per-route basic authentication, and live health checks with load-balancing.

Tokens
14.5K
Snippets
22
Records
69
Agent score
79%

What's inside reproxy

  1. What is Reproxy?

    master

    Reproxy is a simple edge HTTP(s) server and reverse proxy. It uses one or more providers (such as Docker, Static, File, or Consul Catalog) to supply information about requested servers, requested URLs, destination URLs, and health check URLs.

    Key features include:

    • Automatic SSL termination via Let's Encrypt or support for user-provided certificates.
    • Flexible proxy rules using exact matches or regex.
    • Support for multiple (virtual) hosts.
    • Dynamic discovery via Docker or Consul Catalog.
    • Traffic compression, IP-based access control, and per-route basic authentication.
    • Live health checks with fail-over and load-balancing.
    • A management server providing route information and Prometheus metrics.
    • Plugin support via RPC.
  2. Configure system-wide and per-user throttling

    master

    Reproxy supports rate limiting (requests per second) at the system level and per user via --throttle.user.

    Throttling Logic:

    • 0 (default) means unlimited.
    • Unmatched routes: All unmatched routes are treated as a single group with a limit of rate * 3. If --throttle.user=10, unmatched routes allow up to 30 r/s.
    • Matched routes: The limiter is maintained per destination (route). If two different routes are matched, each gets its own limit of 10 r/s.

    Per-route Overrides: Individual routes can override global settings using provider-specific fields. A value of 0 or an omitted field inherits the global setting; any positive value overrides it.

    ProviderSyntax for timeout and throttle
    File (YAML)timeout: 5m, throttle: 2
    Static (CSV)6th and 7th positional fields: *,^/path/(.*),http://dest/$1,,,5m,2
    Dockerreproxy.timeout=5m, reproxy.throttle=2 (or reproxy.<n>.timeout)
    Consulreproxy.timeout=5m, reproxy.throttle=2

    Important Limitation: Per-route timeout does not override the global --timeout.resp-header (default 5s). This transport-level timeout applies before the upstream sends response headers. To support slow-responding endpoints, you must increase --timeout.resp-header globally.

  3. Security Note: Trusting Remote Lookup Headers

    master

    When using the --remote-lookup-headers flag, be aware of a critical security assumption: headers like X-Real-IP and X-Forwarded-For are client-supplied and can be easily spoofed.

    Requirement: You must only enable this flag if reproxy is situated behind a trusted proxy that is configured to overwrite these headers. If enabled on an exposed interface, attackers can spoof their IP address to bypass IP-based access controls (allowlists).

  4. Extend Reproxy with Plugins

    master

    Reproxy can be extended via external processes/containers implementing a Go net/rpc server. Plugins are added to the middleware chain and receive the request (URL, headers, route info) and respond with headers and a status code.

    Header Management:

    • HeadersIn: Incoming headers sent to the proxied URL.
    • HeadersOut: Outgoing headers sent back to the client.
    • Overriding: To replace all headers instead of mixing them, a plugin must set the OverrideHeadersIn or OverrideHeadersOut fields.

    Error Handling: Any status code $\ge 400$ returned by a plugin is treated as an error and terminates the flow immediately with a proxy error.

    Development: The plugin package provides lib.Plugin for registration/dispatching and lib.Request/lib.Response for data structures. Handlers must satisfy the signature: func(req lib.Request, res *lib.HandlerResponse) (err error).

  5. How proxy rules and URL matching work

    master

    Reproxy matches incoming requests based on the host and the requested URL.

    Host Matching

    Servers can be defined as an FQDN (e.g., s.example.com), a catch-all (*), or a regex. Exact matches take priority over regex matches. For example, if you have rules for example.com and example\.(com|org), a request to example.com/url will match the exact example.com rule.

    URL and Destination Matching

    • Regex Groups: You can use regex in the requested URL and capture groups in the destination URL. For example, a rule with requested URL ^/api/(.*) and destination http://d.example.com:8080/$1 will map http://s.example.com/api/something?foo=bar to http://d.example.com:8080/something?foo=bar.
    • Implicit Expansion: For convenience, requests with a trailing / or without regex groups are automatically expanded to /(.*) and /$1. For example, /api/ $\rightarrow$ http://127.0.0.1/service is treated as ^/api/(.*) $\rightarrow$ http://127.0.0.1/service/$1.
    • Host Substitution: You can substitute the matched host name in the destination URL using ${host} or $host. For example, /files/${host} will be replaced with the actual matched host name.
  6. Understand per-route timeout behavior and limitations

    master

    Per-route timeout settings allow you to control the connection write deadline for specific matched routes.

    Key Behaviors:

    • Cancellation: When a per-route timeout is reached, the downstream context is cancelled. In a standard httputil.ReverseProxy setup, this typically results in an HTTP 502 response to the client.
    • Precedence: A positive per-route timeout overrides the global connection write deadline. A value of 0 means the route inherits the global timeout.
    • Limitation (Response Headers): Per-route timeout does not override the transport-level --timeout.resp-header (which defaults to 5s). If an upstream is extremely slow to send the first byte of headers, the request will fail at the global resp-header boundary regardless of the per-route timeout setting. To accommodate very slow header responses, you must increase the global --timeout.resp-header value.
  7. How per-route Timeout and Throttle work

    master

    Reproxy implements per-route constraints using a middleware chain. When a request matches a route, the routeTimeoutHandler is executed immediately after the matchHandler. This ensures that any subsequent middleware (such as authentication or throttling) operates within the specific deadline defined for that route.

    Key Behaviors:

    • Inheritance: If a route does not define a timeout or throttle, it defaults to the global settings.
    • Throttling Granularity: The per-route limiter preserves the [ip, dst] key shape, meaning throttling is applied per-user (per IP) rather than as a single bucket for the entire route.
    • Timeout Implementation: The system uses http.ResponseController (available in Go 1.20+) to override connection deadlines for specific routes.
  8. Understand per-route throttling

    master

    Throttling can be applied to specific routes to limit request rates independently of the global rate limit.

    Key Behaviors:

    • Isolation: Throttling is applied per route. A request to a throttled route does not consume the quota of a different route, even if they share the same destination.
    • User/IP Scoping: Throttling respects the existing key shape (typically [IP, Destination]), meaning each unique user/IP gets their own budget for that specific route.
    • Precedence: A positive per-route throttle value overrides the global rate limit. A value of 0 means the route inherits the global setting.
    • Error Code: When a route's throttle limit is exceeded, the proxy returns an HTTP 429 (Too Many Requests).
  9. Configure HTTP Redirects

    master

    By default, Reproxy proxies requests to the destination. To perform a redirect instead, prefix the destination URL with a status code code:

    • @301 or @perm: Permanent redirect
    • @302, @temp, or @tmp: Temporary redirect

    Example: Setting a destination to @301 https://example.com/something will cause Reproxy to return a 301 redirect to the client.

  10. Run Reproxy as a non-root user

    master

    While the default container runs as root to access the Docker socket, it is recommended to use a non-privileged user if the Docker provider is not required.

    A user with UID 1001 (groups 1001 and 999) is pre-created in the image.

    Docker Compose Example:

    services:
      reproxy:
        user: 1001
        image: umputun/reproxy:latest

    Note: If using the Docker provider with a non-root user, you must ensure that user has permissions to access the host's Docker socket.

  11. Use the Docker provider for automatic service discovery

    master

    The Docker provider automatically discovers running containers.

    Automatic Mode (--docker.auto)

    When enabled, all containers with exposed ports are considered destinations. You can restrict this using:

    • --docker.exclude=container_name
    • --docker.network=network_name
    • Setting the label reproxy.enabled=false on specific containers.

    By default, requests are routed via http://<url>/<container_name>/(.*). You can change the common prefix for all container routes using --docker.prefix.

    Manual/Label-based Configuration

    If not using --docker.auto, containers must have reproxy.* labels to be discovered. Supported labels include:

    • reproxy.server: Hostname to match.
    • reproxy.route: Source route.
    • reproxy.dest: Destination path (appended to container's IP:port).
    • reproxy.port: Destination port.
    • reproxy.ping: Ping path.
    • reproxy.remote: Access restriction (comma-separated IPs/subnets).
    • reproxy.auth: Basic auth (user:bcrypt_hash).
    • reproxy.assets: Mapping as web-root:location (e.g., /web:/var/www).
    • reproxy.keep-host: Keep host header (yes, true, 1) or replace (no, false, 0).
    • reproxy.forward-health-checks: Forward /ping and /health to backend.
    • reproxy.timeout: Go duration.
    • reproxy.throttle: Req/sec limit per user.
    • reproxy.enabled: Enable/disable container.

    Multi-route Support

    You can define multiple distinct routes on a single container using indexed labels: reproxy.1.server, reproxy.1.port, etc. (indices 0-9).

  12. Use a static provider within Docker Compose

    master
    You can use reproxy as a web-serving container within a Docker Compose setup by using a static provider. This allows you to route API requests to specific containers directly by name, bypassing the need for full Docker discovery. This is useful when you want to manually define routing rules for specific services in your Compose file.