Public | Automated Build

Last pushed: a year ago
Short Description
Provides a [HAProxy](http://www.haproxy.org/) image.
Full Description


Ergomentum HAProxy

Provides a HAProxy image based on the Ergomentum CentOS image.

This is the gatekeeper for a docker service orchestration. The HAProxy acts a router which dispatches incoming requests
to the corresponding services.

Responsibilities

  1. Logging: Log incoming requests
  2. Encryption: Redirect non TLS connections to TLS one.
  3. Virtual TLS hosting: Inspecting the hostname provided via SNI extension
    and route the request to the corresponding docker service. This allows end to end encryption.
  4. Sorry Backend: Provide as default backend where all clients without SNI support will be routed to. This can be used
    to explain why the client can't receive the required resource.
  5. Act as Transparent proxy: Only in transparent proxy
    mode each docker service will see the real client IP. This might be important for logging issues, authorization or to
    defeat SPAM.

Preconditions

Example

+----------------+
|     Client     |
+----------------+
| 192.168.99.1   |
+----------------+
        |
+----------------+
|  Docker Host   |
+----------------+
| 192.168.99.100 |---\
+----------------+   NAT
|   172.17.0.1   |---/
+----------------+
        |
+----------------+
|     HAProxy    |
+----------------+
|   172.17.0.2   |---\
+----------------+   NAT
|   172.18.0.2   |---/
+----------------+
        |
        +---------------------+
        |                     |
+----------------+   +----------------+
|  tls-default   |   |  example.com   |
+----------------+   +----------------+
|   172.18.0.2   |   |   172.18.0.3   |
+----------------+   +----------------+

Volumes

<dl>
<dt><code>/etc/haproxy/haproxy.cfg</code></dt>
<dd>The HAProxy configuration, see <a href="#usage">Usage</a>.</dd>
</dl>

Environment Variables

None.

Exposed Ports

<dl>
<dt><code>80</code></dt>
<dd>The HTTP default port.</dd>
<dt><code>443</code></dt>
<dd>The HTTPS default port.</dd>
</dl>

Configuration

General requirements

The container expects a HAProxy configuration file mounted to /etc/haproxy/haproxy.cfg.
this must contain:

<pre>
global
# Uses the patched feature!
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#log">log</a> /dev/stdout local0
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#pidfile">pidfile</a> /var/run/haproxy.pid

defaults
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#mode">mode</a> tcp
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#log%20global">log global</a>
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#log-format">log-format</a> %ci:%cp\ ->\ %fi:%fp\ ->\ %bi:%bp\ ->\ %si:%sp\ (%f->%b->%s)
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#timeout%20connect">timeout connect</a> 10s
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#timeout%20client">timeout client</a> 1m
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#timeout%20server">timeout server</a> 1m
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#source">source</a> 0.0.0.0 usesrc clientip
</pre>

Rewrite HTTP to HTTPS requests

Add the following to the file /etc/haproxy/haproxy.cfg:

<pre>
frontend http-to-https-rewrite
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#bind">bind</a> *:80
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#mode">mode</a> http
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#redirect%20scheme">redirect scheme</a> https code 301 if !{ <a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#7.3.4-ssl_fc">ssl_fc</a> }
</pre>

HTTPS frontend

Add the following to the file /etc/haproxy/haproxy.cfg:

<pre>
frontend https-in
# Bind to HTTPS default port:
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#bind">bind</a> *:443

# Use tcp content accepts to detects ssl client and server hello:
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#tcp-request%20inspect-delay">tcp-request inspect-delay</a> 5s

<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#tcp-request%20content">tcp-request content</a> accept if { <a href ="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#req.ssl_hello_type">req.ssl_hello_type</a> 1 }

# Map to a default backend if no more appropriate found:
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#default_backend">default_backend</a> tls-default

# Map to a backend depending from the hostname. Add as much mappings as needed:
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#use_backend">use_backend</a> <var>example.com</var> if { <a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#req.ssl_sni">req.ssl_sni</a> -i <var>example.com</var> }
</pre>

Define the default backend

Add the following to the file /etc/haproxy/haproxy.cfg:

<pre>
backend tls-default
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#stick-table">stick-table</a> type binary <a href="https://tools.ietf.org/html/rfc5246#section-7.4.1.2" title="Maximum TLS session ID length is 32 bytes">len 32</a> size 30k expire 30m
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#acl">acl</a> clienthello <a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#7.3.5-req.ssl_hello_type">req.ssl_hello_type</a> <a href="https://tools.ietf.org/html/rfc5246#section-7.4" title="TLS Client Hello = 0x1">1</a>
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#acl">acl</a> serverhello <a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#7.3.5-res.ssl_hello_type">res.ssl_hello_type</a> <a href="https://tools.ietf.org/html/rfc5246#section-7.4" title="TLS Server Hello = 0x2">2</a>

# Use tcp content accepts to detects ssl client and server hello:
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4.2-tcp-request%20inspect-delay">tcp-request inspect-delay</a> 5s

<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#tcp-request%20content">tcp-request content</a> accept if clienthello

# No timeout on response inspect delay by default.

<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4.2-tcp-response%20content">tcp-response content</a> accept if serverhello

# Extract a binary block. The block length is defined at offset 43 with length 1. The block offset is at 43 + 1.
# Offset start with 0. All values are decimals. This is to extract the TLS session ID from the TLS handshake record.
#
# <a href="https://tools.ietf.org/html/rfc5246#section-7.4">TLS handshake record</a>:
# Offset Length Content
# 0 1 Record type
# 1 2 TLS version
# 3 2 Record length
# 5 1 Handshake type
# 6 3 Record length
# 9 2 TLS Version
# 11 4 Unix timestamp
# 15 28 Random bytes
# 43 1 Session ID length
# 44 <Session ID length> Session ID
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#stick%20on">stick on</a> <a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#req.payload_lv">req.payload_lv(43,1)</a> if clienthello

# Learn on response if server hello:
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4.2-stick%20store-response">stick store-response</a> <a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#7.3.5-payload_lv">payload_lv(43,1)</a> if serverhello

# Map to a backend with name "sorry" and hostname "sorry":
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#server">server</a> sorry sorry:443
</pre>

Define a backend

Add the following to the file /etc/haproxy/haproxy.cfg:

<pre>
backend <var>example.com</var>
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#stick-table">stick-table</a> type binary <a href="https://tools.ietf.org/html/rfc5246#section-7.4.1.2" title="Maximum TLS session ID length is 32 bytes">len 32</a> size 30k expire 30m
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#acl">acl</a> clienthello <a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#7.3.5-req.ssl_hello_type">req.ssl_hello_type</a> <a href="https://tools.ietf.org/html/rfc5246#section-7.4" title="TLS Client Hello = 0x1">1</a>
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#acl">acl</a> serverhello <a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#7.3.5-res.ssl_hello_type">res.ssl_hello_type</a> <a href="https://tools.ietf.org/html/rfc5246#section-7.4" title="TLS Server Hello = 0x2">2</a>

# Use tcp content accepts to detects ssl client and server hello:
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4.2-tcp-request%20inspect-delay">tcp-request inspect-delay</a> 5s

# http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#tcp-request%20content
tcp-request content accept if clienthello

# No timeout on response inspect delay by default.

<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4.2-tcp-response%20content">tcp-response content</a> accept if serverhello

# Extract a binary block. The block length is defined at offset 43 with length 1. The block offset is at 43 + 1.
# Offset start with 0. All values are decimals. This is to extract the TLS session ID from the TLS handshake record.
#
# <a href="https://tools.ietf.org/html/rfc5246#section-7.4">TLS handshake record</a>:
# Offset Length Content
# 0 1 Record type
# 1 2 TLS version
# 3 2 Record length
# 5 1 Handshake type
# 6 3 Record length
# 9 2 TLS Version
# 11 4 Unix timestamp
# 15 28 Random bytes
# 43 1 Session ID length
# 44 <Session ID length> Session ID
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#stick%20on">stick on</a> <a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#req.payload_lv">req.payload_lv(43,1)</a> if clienthello

# Learn on response if server hello:
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4.2-stick%20store-response">stick store-response</a> <a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#7.3.5-payload_lv">payload_lv(43,1)</a> if serverhello

# Map to a backend with name "example.com" and hostname "myhttpd":
<a href="http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#server">server</a> example.com myhttpd:443
</pre>

Usage

Is is not necessary to use this image as base image. Simply create a container from it.

Run the example setup

docker network \
  create \
  --driver bridge \
  backend-network

docker \
  create \
  --name dispatcher \
  -v "$PWD/haproxy.cfg:/etc/haproxy/haproxy.cfg" \
  -p '80:80' \
  -p '443:443' \
  --cap-add=NET_ADMIN \
  ergomentum/haproxy
docker network \
  connect \
  backend-network \
  dispatcher
docker \
  run \
  -d \
  --name example.com \
  --net=backend-network \
  --cap-add=NET_ADMIN \
  ergomentum/httpd
docker \
  run \
  -d \
  --name tls-default \
  --net=backend-network \
  --cap-add=NET_ADMIN \
  ergomentum/httpd

# Must be started after all configured backends to resolve hostnames:
docker \
  start \
  dispatcher

# Must be run after the dispatcher is started to resolve the IP of the dispatcher:
docker \
  exec \
  -ti \
  example.com \
  bash -c 'yum -y install iproute && ip route replace default via $(getent hosts dispatcher | cut -d " " -f 1)'
docker \
  exec \
  -ti \
  tls-default \
  bash -c 'yum -y install iproute && ip route replace default via $(getent hosts dispatcher | cut -d " " -f 1)'

The iptables and the ip command require the NET_ADMIN capability.

Contributing

To contribute a feature or a bugfix please open a pull request on
GitHub.

See CONTRIBUTING for details.

License

See the LICENSE file for license
rights and limitations (Apache License, Version 2.0).

References

Docker Pull Command
Owner
ergomentum
Source Repository