
{# This file configures haproxy to redirect requests from ports to specific urls.
 # It provides TLS support for server and optionnaly for client.
 #
 # All parameters are given through the `parameter_dict` variable, see the
 # list entries :
 #
 #     parameter_dict = {
 #       #  Path of the PID file. HAProxy will write its own PID to this file
 #       #  Sending USR2 signal to this pid will cause haproxy to reload
 #       #  its configuration.
 #       "pidfile": "<file_path>",
 #
 #       #  AF_UNIX socket for logs. Syslog must be listening on this socket.
 #       "log-socket": "<file_path>",
 #
 #       #  AF_UNIX socket for statistics and control.
 #       #  Haproxy will listen on this socket.
 #       "stats-socket": "<file_path>",
 #
 #       #  IPv4 to listen on
 #       #  All backends from `backend-dict` will listen on this IP.
 #       "ipv4": "0.0.0.0",
 #
 #       #  IPv6 to listen on
 #       #  All backends from `backend-dict` will listen on this IP.
 #       "ipv6": "::1",
 #
 #       #  Certificate and key in PEM format. All ports will serve TLS using
 #       #  this certificate.
 #       "cert": "<file_path>",
 #
 #       #  CA to verify client certificates in PEM format.
 #       #  If set, client certificates will be verified with these CAs.
 #       #  If not set, client certificates are not verified.
 #       "ca-cert": "<file_path>",
 #
 #       #  An optional CRL in PEM format (the file can contain multiple CRL)
 #       #  This is required if ca-cert is passed.
 #       "crl": "<file_path>",
 #
 #       #  Path to use for HTTP health check on backends from `backend-dict`.
 #       "server-check-path": "/",
 #
 #       #  The mapping of backends, keyed by family name
 #       "backend-dict": {
 #          "family-secure": {
 #            ( 8000, # port int
 #              'https', # proto str
 #               True, # ssl_required bool
 #               None,  # timeout (in seconds) int | None
 #               [  # backends
 #                  '10.0.0.10:8001', # netloc str
 #                   1, # max_connection_count int
 #                   False, # is_web_dav bool
 #               ],
 #            ),
 #          },
 #          "family-default": {
 #            ( 8002, # port int
 #              'https', # proto str
 #               False, # ssl_required bool
 #               None,  # timeout (in seconds) int | None
 #               [  # backends
 #                  '10.0.0.10:8003', # netloc str
 #                   1, # max_connection_count int
 #                   False, # is_web_dav bool
 #               ],
 #            ),
 #          },
 #
 #       # The mapping of zope paths.
 #       # This is a Zope specific feature.
 #       # `enable_authentication` has same meaning as for `backend-list`.
 #       "zope-virtualhost-monster-backend-dict": {
 #          # {(ip, port): ( enable_authentication, {frontend_path: ( internal_url ) }, ) }
 #          ('[::1]', 8004): (
 #            True, {
 #              'zope-1': 'http://10.0.0.10:8001',
 #              'zope-2': 'http://10.0.0.10:8002',
 #            },
 #          ),
 #        },
 #     }
 #
 #  This sample of `parameter_dict` will make haproxy listening to :
 #  From to `backend-list`:
 #  For "family-secure":
 #   - 0.0.0.0:8000 redirecting internaly to http://10.0.0.10:8001 and
 #   - [::1]:8000 redirecting internaly to http://10.0.0.10:8001
 #  only accepting requests from clients providing a verified TLS certificate
 #  emitted by a CA from `ca-cert` and not revoked in `crl`.
 #  For "family-default":
 #   - 0.0.0.0:8002 redirecting internaly to http://10.0.0.10:8003
 #   - [::1]:8002 redirecting internaly to http://10.0.0.10:8003
 #  accepting requests from any client.
 #
 #  For both families, X-Forwarded-For header will be stripped unless
 #  client presents a certificate that can be verified with `ca-cert` and `crl`.
 #
 # From zope-virtualhost-monster-backend-dict`:
 #   - [::1]:8004 with some path based rewrite-rules redirecting to:
 #     * http://10.0.0.10/8001 when path matches /zope-1(.*)
 #     * http://10.0.0.10/8002 when path matches /zope-2(.*)
 #   with some VirtualHostMonster rewrite rules so zope writes URLs with
 #  [::1]:8004 as server name.
 #  For more details, refer to
 #  https://docs.zope.org/zope2/zope2book/VirtualHosting.html#using-virtualhostroot-and-virtualhostbase-together
-#}

{% set server_check_path = parameter_dict['server-check-path'] -%}
global
  maxconn 4096
  master-worker
  pidfile {{ parameter_dict['pidfile'] }}

  # SSL configuration was generated with mozilla SSL Configuration Generator
  # generated 2020-10-28, Mozilla Guideline v5.6, HAProxy 2.1, OpenSSL 1.1.1g, modern configuration
  # https://ssl-config.mozilla.org/#server=haproxy&version=2.1&config=modern&openssl=1.1.1g&guideline=5.6
  ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
  ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tlsv12 no-tls-tickets
  ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
  ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tlsv12 no-tls-tickets

  stats socket {{ parameter_dict['stats-socket'] }} level admin


defaults
  mode http
  retries 1
  option redispatch
  maxconn 2000
  balance roundrobin

  stats uri /haproxy
  stats realm Global\ statistics

  timeout connect 10s
  timeout queue 60s
  timeout client 305s

  option http-server-close

  # compress some content types
  compression algo gzip
  compression type application/font-woff application/font-woff2 application/hal+json application/javascript application/json application/rss+xml application/wasm application/x-font-opentype application/x-font-ttf application/x-javascript application/xml image/svg+xml text/cache-manifest text/css text/html text/javascript text/plain text/xml

  log {{ parameter_dict['log-socket'] }} local0 info

{% set bind_ssl_crt = 'ssl crt ' ~ parameter_dict['cert'] ~  ' alpn h2,http/1.1' %}
{% set family_path_routing_dict = parameter_dict['family-path-routing-dict'] %}
{% set path_routing_list = parameter_dict['path-routing-list'] %}

{% for name, (port, _, certificate_authentication, timeout, backend_list) in sorted(six.iteritems(parameter_dict['backend-dict'])) -%}
listen family_{{ name }}
{%-  if parameter_dict.get('ca-cert') -%}
{%-    set ssl_auth = ' ca-file ' ~ parameter_dict['ca-cert'] ~ ' verify' ~ ( ' required' if certificate_authentication else ' optional' ) ~ ' crl-file ' ~ parameter_dict['crl'] %}
{%-  else %}
{%-    set ssl_auth = '' %}
{%-  endif %}
  bind {{ parameter_dict['ipv4'] }}:{{ port }} {{ bind_ssl_crt }} {{ ssl_auth }}
  bind {{ parameter_dict['ipv6'] }}:{{ port }} {{ bind_ssl_crt }} {{ ssl_auth }}
  cookie SERVERID rewrite
  http-request set-header X-Balancer-Current-Cookie SERVERID

{% if timeout %}
  {#
    Apply a slightly longer timeout than the zope timeout so that clients can see the
    TimeoutReachedError from zope, that is a bit more informative than the 504 error
    page from haproxy.
  #}
  timeout server {{ timeout + 3 }}s
{%-  endif %}

  # remove X-Forwarded-For unless client presented a verified certificate
  acl client_cert_verified ssl_c_used ssl_c_verify 0
  http-request del-header X-Forwarded-For unless client_cert_verified
  # set Remote-User if client presented a verified certificate
  http-request del-header Remote-User
  http-request set-header Remote-User %{+Q}[ssl_c_s_dn(cn)] if client_cert_verified

  # logs
  capture request header Referer len 512
  capture request header User-Agent len 512
  log-format "%{+Q}o %{-Q}ci - - [%trg] %r %ST %B %{+Q}[capture.req.hdr(0)] %{+Q}[capture.req.hdr(1)] %Ta"

{%   for outer_prefix, inner_prefix in family_path_routing_dict.get(name, []) + path_routing_list %}
  {%   set outer_prefix = outer_prefix.strip('/') -%}
  http-request replace-path ^(/+VirtualHostBase/+[^/]+/+[^/]+)/+VirtualHostRoot/+{% if outer_prefix %}{{ outer_prefix }}($|/.*){% else %}(.*){% endif %} \1/{{ inner_prefix.strip('/') }}/VirtualHostRoot/{% if outer_prefix %}_vh_{{ outer_prefix.replace('/', '/_vh_') }}{% endif %}\2
{%  endfor %}

{%   set has_webdav = [] -%}
{%   for address, connection_count, webdav in backend_list -%}
{%     if webdav %}{% do has_webdav.append(None) %}{% endif -%}
{%     set server_name = name ~ '-' ~ loop.index0 %}
  server {{ server_name }} {{ address }} cookie {{ server_name }} check inter 3s rise 1 fall 2 maxqueue 5 maxconn {{ connection_count }}
{%-  endfor -%}
{%-  if not has_webdav and server_check_path %}
  option httpchk GET {{ server_check_path }}
{%-   endif %}

{% endfor %}


{% for (ip, port), (_, backend_dict) in sorted(six.iteritems(parameter_dict['zope-virtualhost-monster-backend-dict'])) -%}
{%   set group_name = 'testrunner_' ~ loop.index0 %}
frontend frontend_{{ group_name }}
  bind {{ ip }}:{{ port }} {{ bind_ssl_crt }}
  timeout client 8h

  # logs
  capture request header Referer len 512
  capture request header User-Agent len 512
  log-format "%{+Q}o %{-Q}ci - - [%trg] %r %ST %B %{+Q}[capture.req.hdr(0)] %{+Q}[capture.req.hdr(1)] %Tt"

{%   for name in sorted(backend_dict.keys()) %}
  use_backend backend_{{ group_name }}_{{ name }} if { path -m beg /{{ name }} }
{%-   endfor %}

{%   for name, url in sorted(backend_dict.items()) %}
backend backend_{{ group_name }}_{{ name }}
  http-request replace-path ^/{{ name }}(.*) /VirtualHostBase/https/{{ ip }}:{{ port }}/VirtualHostRoot/_vh_{{ name }}\1
  timeout server 8h
  server {{ name }} {{ urllib_parse.urlparse(url).netloc }}
{%-  endfor %}
{% endfor %}
