graygnuorg / pound

Light-weight reverse proxy, load balancer and HTTPS front-end for Web servers.
GNU General Public License v3.0
43 stars 13 forks source link

Pound

Pound is a reverse proxy, load balancer and HTTPS front-end for Web servers. It was developed to enable distributing the load among several Web-servers and to allow for a convenient SSL wrapper for those Web servers that do not offer it natively. Pound is distributed under the GNU General Public License, Version 3, or (at your option) any later version.

The original version of pound was written by Robert Segall at Apsis GmbH. In 2018, Sergey Poznyakoff added support for OpenSSL 1.x to the then current version of the program (2.8). This version of pound, hosted on github was further modified by Rick O'Sullivan and Frank Schmirler, who added WebSocket support.

On April 2020, Apsis started development of pound 3.0 - essentially an attempt to rewrite pound from scratch, introducing dependencies on some third-party software.

On 2022-09-19, Robert announced that he stops further development and maintenance of pound. Following that, Sergey decided to continue development of the program starting from his fork.

What Pound Is

  1. a reverse-proxy: it passes requests from client browsers to one or more backend servers.
  2. a load balancer: it distributes requests from client browsers among several backend servers, while keeping session information.
  3. an SSL wrapper: it decrypts HTTPS requests from client browsers and passes them as plain HTTP to the backend servers.
  4. an HTTP/HTTPS sanitizer: it verifies requests for correctness and accepts only well-formed ones.
  5. a fail-over server: should a backend server fail, pound will take note of the fact and stop passing requests to it until it recovers.
  6. a request redirector: requests may be distributed among servers according to the requested URL.

Pound is a very small program, easily audited for security problems. It can run as setuid/setgid and/or in a chroot jail. Pound does not access the hard-disk at all (except for reading certificate files on start, if required) and should thus pose no security threat to any machine.

What Pound Is Not

  1. Pound is not a Web server: it serves no content itself, it only passes requests and responses back and forth between clients and actual web servers (backends).
  2. Pound is not a Web accelerator: no caching is done -- every request is passed to a backend server "as is".

Notice On Project Versioning

I took over pound development at its 2.x branch. The branch 3.x, which emerged for a short time before the original project was abandoned, I consider to be a failed experiment. To ensure consistent versioning and avoid confusion, my versioning of pound starts with 4.0.

Documentation

Documentation in texinfo and manpage formats is available in the distribution. A copy of the documentation is available online.

Build requirements

To build, pound needs OpenSSL version 1.1.x or 3.0.x.

As of current release, pound still supports OpenSSL 1.0, but this support will soon be discontinued.

If you compile it on a Debian-based system, you need to install the libssl-dev package prior to building pound.

Compilation

If you cloned pound from the repository, you will need the following tools in order to build it:

First, run

 ./bootstrap

This will prepare the necessary infrastructure files (Makefile.in's etc.)

If you are building pound from a tarball, the above step is not needed, since all the necessary files are already included in it.

To prepare pound for compilation, run ./configure. Its command line options will decide where on the filesystem the binary will be installed, where it will look for its configuration file, etc. When run without options, the binary will be installed at /usr/local/sbin and it will look for its configuration in file /usr/local/etc/pound.cfg.

If you run it as:

 ./configure --prefix=/usr --sysconfdir=/etc

then the binary will be installed at /usr/sbin/pound and it will read its configuration from /etc/pound.cfg.

For a detailed discussion of --prefix, --sysconfdir, and other generic configure options, refer to Autoconf documentation.

Apart from the generic ones, there are also several pound-specific configuration options:

When configuration is finished, run

 make

When building from a git clone, the first run of this command can take considerable time, if you are compiling with OpenSSL 1.0. That's because it involves generating DH parameters.

Testing

Testing a reverse proxy in general, and pound in particular, is not a trivial task. Testsuite in pound was implemented quite recently and is still somewhat experimental. Notwithstanding that, it has already helped to discover several important bugs that lurked in the code.

To test pound you will need Perl version 5.26.3 or later, and the IO::FDPass module. To install the latter on a reasonably recent debian-based system, run

 apt-get install libio-fdpass-perl

On other systems you may need to install it directly from cpan by running

 cpan -i IO::FDPass

Testing HTTPS requires additionally Perl modules IO::Socket::SSL and Net::SSLeay. If these are not installed, HTTPS tests will be skipped. To install these on a debian-based system, run:

 apt-get install libio-socket-ssl-perl libnet-ssleay-perl

Additionally, to test DNS-based dynamic backends functionality, the Net::DNS::Nameserver module is needed. On debian-based systems it is installed with the following command:

 apt-get install libnet-dns-perl

You will need version 1.47 of libnet-dns-perl, which corresponds to version 1990 of Net::DNS::Nameserver.

To run tests, type

 make check

from the top-level source directory. On success, you will see something like that (ellipsis indicating output omitted for brevity):

## -------------------------- ##
## pound 4.8 test suite.      ##
## -------------------------- ##
  1: Configuration file syntax                       ok
  2: Basic request processing                        ok
  3: xHTTP                                           ok
  4: CheckURL                                        ok
  5: Custom Error Response                           ok
  6: MaxRequest                                      ok
  7: RewriteLocation                                 ok

Listener request modification

  8: Basic set directives                            ok

  ...

## ------------- ##
## Test results. ##
## ------------- ##

All 46 tests were successful.

If a test results in something other than ok, it leaves detailed diagnostics in directory tests/testsuite.dir/NN, where NN is the ordinal number of the test. If you encounter such failed tests, please tar the contents of tests/testsuite.dir and send the resulting tarball over to gray@gnu.org for investigation. See also section Bug Reporting below.

Installation

If both building and testing succeeded, it's time to install pound. To do so, run the following command as root:

 make install

Configuration

Pound looks for its configuration file in a location defined at compile time, normally /etc/pound.cfg, or /usr/local/etc/pound.cfg. The configuration file syntax is discussed in detail in the manual. Here we will describe some example configurations.

Any pound configuration must contain at least two parts: a ListenHTTP (or ListenHTTPS) section, that declares a frontend, i.e. the end of the proxy that is responsible for connection with the outside world, and Service section with one or more Backend sections within, which declares where the incoming requests should go. The Service section can be global or it can be located within the ListenHTTP block. Global Service sections can be shared between two or more ListenHTTP sections. Multiple Service sections can be supplied, in which case the Service to use when handling a particular HTTP request will be selected using the supplied criteria, such as source IP address, URL, request header or the like.

Simplest configuration

The following configuration instructs pound to listen for incoming HTTP requests on 192.0.2.1:80 and pass them to single backend on 10.10.0.1:8080.

ListenHTTP
    Address 192.0.2.1
    Port 80
    Service
        Backend
            Address 10.10.0.1
            Port 8080
        End
    End
End

Notice, that the two statements Address, and Port are in general mandatory both in ListenHTTP and in Backend. There are two exceptions, however: if Address is a file name of a UNIX socket file, or if an already opened socket is passed to pound via the SocketFrom statement. These two cases are discussed below.

Argument to the Address statement can be an IPv4 or IPv6 address, a hostname, that will be resolved at program startup, or a full pathname of a UNIX socket file.

HTTPS frontend

This example shows how to configure HTTPS frontend and redirect all plain HTTP requests to it. It assumes the domain name of the site is www.example.org and its IP address is 192.0.2.1.

# Declare HTTP frontend
ListenHTTP
    Address 192.0.2.1
    Port 80
    Service
        # Redirect all requests to HTTPS.  The redirection
        # target has no path component, which means that the
        # path (and query parameters, if any) from the request
        # will be preserved.
        Redirect 301 https://www.example.org
    End
End

# Declare HTTPS frontend.
ListenHTTPS
    Address 192.0.2.1
    Port 443
    # Certificate file must contain the certificate, optional
    # certificate chain and the signature, in that order.
    Cert "/etc/ssl/priv/example.pem"
    # List of certificate authority certificates.
    CAlist /etc/ssl/acme/lets-encrypt-root.pem"
    # Disable obsolete protocols (SSLv2, SSLv3 and TLSv1).
    Disable TLSv1
    Service
        Backend
            Address 10.10.0.1
            Port 8080
        End
    End
End

Virtual Hosts

To implement virtual hosts, one needs to instruct pound to route requests to different services depending on the values of their Host: headers. To do so, use the Host statement in the Service section.

The argument to Host specifies the host name. When an incoming request arrives, it is compared with this value. The Service section will be used only if the value of the Host: header from the request matched the argument to the Host statement. By default, exact case-insensitive comparison is used.

Let's assume that you have internal server 192.168.0.10 that is supposed to serve the needs of virtual host www.server0.com and 192.168.0.11 that serves www.server1.com. You want pound to listen on address 192.0.2.1. The configuration file would look like this:

ListenHTTP
    Address 192.0.2.1
    Port    80

    Service
        Host "www.server0.com"
        Backend
            Address 192.168.0.10
            Port    80
        End
    End

    Service
        Host "www.server1.com"
        Backend
            Address 192.168.0.11
            Port    80
        End
    End
End

The same can be done using ListenHTTPS.

If you want to use the same service for both the hostname and the hostname prefixed with www., you can either use the Match statement, or a regular expression.

A Match statement groups several conditions using boolean shortcut evaluation. In the following example, boolean or is used to group two Host statements:

    Service
        Match OR
            Host "server0.com"
            Host "www.server0.com"
        End
        Backend
            Address 192.168.0.10
            Port    80
        End
    End

When this service is considered, the value of the Host: header from the incoming request is matched against each host listed in the Match OR statement. If any value compares equal, the match succeeds and the service is selected for processing the request.

By default, the Host directive uses exact case-insensitive string match. This can be altered by supplying one or more options to it. In the example below, we use regular expression matching to achieve the same result as in the configuration above:

    Service
        Host -re "^(www\\.)?server0\\.com$"
        Backend
            Address 192.168.0.10
            Port    80
        End
    End

Notice double-slashes: a slash is an escape character and must be escaped if intended to be used literally.

Dynamic backends

Dynamic backends are created and updated on the fly based on the information from DNS, such as A (AAAA) or SRV records. The following dynamic backend types are defined:

An example of a dynamic backend definition:

    Service
        Backend
            Address "_proxy._tcp.example.org"
            Resolve srv
            Family any
        End
    End

Sessions

Pound is able to keep track of sessions between a client browser and a backend server. Unfortunately, HTTP is defined as a stateless protocol, which complicates matters: many schemes have been invented to allow keeping track of sessions, and none of them works perfectly. What's worse, sessions are critical in order to allow web-based applications to function correctly - it is vital that once a session is established all subsequent requests from the same browser be directed to the same backend server.

Six possible ways of detecting a session have been implemented in pound (hopefully the most useful ones): by client address, by Basic authentication (user id/password), by URL parameter, by cookie, by HTTP parameter and by header value.

Session tracking is declared using the Session block in Service section. Only one Session can be used per Service. The type of session tracking is declared with the Type statement.

Please note the following restrictions on session tracking:

A note on cookie injection: some applications have no session-tracking mechanism at all but would still like to have the client always directed to the same backend time after time. Some reverse proxies use a mechanism called cookie injection in order to achieve this: a cookie is added to backend responses and tracked by the reverse proxy.

Pound was designed to be as transparent as possible, therefore this mechanism is not supported. If you really need this sort of persistent mapping use the client address session mechanism (Type IP), which achieves the same result without changing the contents in any way.

Logging

If pound operates in daemon mode (the default), all diagnostics goes to the syslog facility daemon. Pound switches to syslog right before it disconnects from the controlling terminal. Until then, it sends its messages to the standard error.

By default only error and informative messages are logged. The amount of information logged is controlled by the LogLevel configuration statement. Possible settings are:

The LogLevel statement can be global (effective for all listeners), as well as per-listener.

Socket Passing

Pound can obtain socket to listen on from another program via a UNIX socket. This mode of operation is requested by the following statement in ListenHTTP section:

  SocketFrom "/path/to/socket"

When this statement is present, neither Address nor Port may be used in this listener. Pound will connect to the named socket and obtain the socket descriptor from it. Then it will start listening for incoming requests on that socket.

This can be used both in ListenHTTP and ListenHTTPS sections.

Currently it is used in pound testsuite.

Request Modification

Normally, pound passes all incoming requests to backends verbatim. Several request modification directives are provided, that allow you to add or remove headers from the request. The following two groups of headers are added by default. Each of them can be turned off using the HeaderOption directive.

  1. The forwarded headers:
  1. Second group contains ssl headers that are added only if the client connected using HTTPS. The X-SSL-Cipher header is always present if this header group is enabled. The rest of headers below is added only if the client certificate was supplied:

The HeaderOption directive can be used (either globally or in listener block) to disable any or both of these groups, e.g.:

HeaderOption no-ssl forwarded

Any number of headers can be added or removed using the HeaderAdd and HeaderRemove directives in the listener section. The order in which these directives are applied is:

  1. Headers controlled by the HeaderOption directive are added.
  2. Headers requested by HeaderRemove directives are removed.
  3. Headers from HeaderAdd directives are added.

ACME

Pound offers built-in support for ACME (a.k.a. LetsEncrypt) HTTP-01 challenge type. Thus, it can be used with any certificate controller to obtain SSL certificates on the fly.

Assuming your certificate controller is configured to store challenges in directory /var/lib/pound/acme, all you need to do is add the ACME statement to the ListenHTTP block, for example:

ListenHTTP
    ACME "/var/lib/pound/acme"
    .
    .
    .
End

Now, each request whose URL ends in /.well-known/acme-challenge/NAME will be served by directly by pound: it will send the content of the file /var/lib/pound/acme/NAME as a reply.

Using RootJail

The RootJail configuration directive instructs pound to chroot to the given directory at startup. Normally, its use should be quite straightforward:

RootJail "/var/pound"

Pound tries to open all files and devices it needs before chrooting. There might be cases, however, when it is not enough and you would need to copy certain system files to the chroot directory.

Bug-reporting

If you think you found a bug in pound or in its documentation, please send a mail to Sergey Poznyakoff gray@gnu.org (or gray+pound@gnu.org.ua), or use the github issue tracker.

When reporting failed tests, please make an archive of the tests/testsuite.dir subdirectory and attach it to your report.