# # https://www.haproxy.com/blog/haproxy-on-docker-swarm-load-balancing-and-dns-service-discovery/ # global log fd@2 local2 {{ docker_swarm_haproxy_loglevel }} chroot /var/lib/haproxy pidfile /var/run/haproxy.pid maxconn {{ haproxy_maxconns }} user haproxy group haproxy stats socket /var/lib/haproxy/stats expose-fd listeners master-worker ca-base /etc/ssl/certs crt-base /etc/ssl/private # https://ssl-config.mozilla.org/#server=haproxy&version=2.2&config=intermediate&openssl=1.1.1d&guideline=5.6 tune.ssl.default-dh-param 2048 ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 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-tls-tickets ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 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-tls-tickets resolvers docker nameserver dns1 127.0.0.11:53 resolve_retries 3 timeout resolve 1s timeout retry 1s hold other 10s hold refused 10s hold nx 10s hold timeout 10s hold valid 10s hold obsolete 10s defaults timeout connect 10s timeout client 30s timeout server 30s log global monitor-uri /_haproxy_health_check timeout http-keep-alive {{ haproxy_global_keepalive_timeout }} timeout connect {{ haproxy_connect_timeout }} timeout client {{ haproxy_client_timeout }} timeout server {{ haproxy_server_timeout }} timeout check {{ haproxy_check_timeout }} timeout http-request 10s # slowloris protection default-server inter 3s fall 2 rise 2 slowstart 60s # Needed to preserve the stick tables peers mypeers peer local_haproxy 127.0.0.1:1024 listen stats bind *:{{ haproxy_admin_port }} ssl crt {{ haproxy_cert_dir }} alpn h2,http/1.1 mode http option httplog stats enable stats uri / stats realm HAProxy\ Statistics stats auth admin:{{ haproxy_admin_pwd }} stats refresh 15s stats show-legends stats show-node listen local_stats bind 127.0.0.1:8881 mode http option httplog stats enable stats uri / stats realm HAProxy\ Statistics frontend http bind *:{{ https_port }} ssl crt {{ haproxy_cert_dir }} alpn h2,http/1.1 {% if docker_swarm_haproxy_installation_type == 'global' %}accept-proxy{% endif %} bind *:{{ haproxy_default_port }} {% if docker_swarm_haproxy_installation_type == 'global' %}accept-proxy{% endif %} mode http option http-keep-alive option forwardfor http-request add-header X-Forwarded-Proto https # HSTS (63072000 seconds) http-response set-header Strict-Transport-Security max-age=63072000 {% if docker_swarm_cluster_portainer_install %} acl portainer_srv hdr_dom(host) -i {{ docker_swarm_portainer_hostname }} {% endif %} {% for srv in docker_swarm_haproxy_additional_services %} acl {{ srv.acl_name }} {{ srv.acl_rule }} {% if srv.allowed_networks is defined %} acl {{ srv.acl_name }}_nets src {% for net in srv.allowed_networks %} {{ net }}{% endfor %} http-request deny if {{ srv.acl_name }} !{{ srv.acl_name }}_nets {% endif %} {% endfor %} redirect scheme https code 301 if !{ ssl_fc } {% if docker_swarm_cluster_portainer_install %} use_backend portainer_bck if portainer_srv {% endif %} {% for srv in docker_swarm_haproxy_additional_services %} use_backend {{ srv.acl_name }}_bck if {{ srv.acl_name }} {% endfor %} {% if docker_swarm_expose_api_via_haproxy %} frontend docker_ft bind :{{ docker_swarm_haproxy_swarm_port }} ssl crt {{ haproxy_cert_dir }} alpn h2,http/1.1 {% if docker_swarm_haproxy_installation_type == 'global' %}accept-proxy{% endif %} mode http acl swarm_api hdr_dom(host) -i {{ docker_swarm_expose_api_hostname }} # acl swarm_api_allowed_nets src {% for net in docker_swarm_api_networks_acl %} {{ net }}{% endfor %} # http-request deny if swarm_api !swarm_api_allowed_nets # http-request deny unless METH_GET || { env(POST) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/((stop)|(restart)|(kill)) } { env(ALLOW_RESTARTS) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/auth } { env(AUTH) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/build } { env(BUILD) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/commit } { env(COMMIT) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/configs } { env(CONFIGS) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers } { env(CONTAINERS) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/distribution } { env(DISTRIBUTION) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/events } { env(EVENTS) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/exec } { env(EXEC) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/images } { env(IMAGES) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/info } { env(INFO) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/networks } { env(NETWORKS) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/nodes } { env(NODES) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/_ping } { env(PING) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/plugins } { env(PLUGINS) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/post } { env(POST) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/secrets } { env(SECRETS) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/services } { env(SERVICES) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/session } { env(SESSION) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/swarm } { env(SWARM) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/system } { env(SYSTEM) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/tasks } { env(TASKS) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/version } { env(VERSION) -m bool } # http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/volumes } { env(VOLUMES) -m bool } # http-request deny default_backend swarm_api_bck {% endif %} # # Backends # {% if docker_swarm_expose_api_via_haproxy %} # swarm API backend swarm_api_bck mode http {% if docker_swarm_api_check_availability %} option httpchk http-check send meth HEAD uri /version ver HTTP/1.1 hdr Host localhost http-check expect rstatus (2|3)[0-9][0-9] {% endif %} server {{ docker_swarm_api_backend }} {% endif %} {% if docker_swarm_cluster_portainer_install %} backend portainer_bck mode http option httpchk http-check send meth HEAD uri / ver HTTP/1.1 hdr Host localhost http-check expect rstatus (2|3)[0-9][0-9] balance roundrobin server-template portainer- 1 portainer_portainer:{{ docker_swarm_portainer_http_port }} check resolvers docker init-addr libc,none {% endif %} {% for srv in docker_swarm_haproxy_additional_services %} backend {{ srv.acl_name }}_bck mode http option httpchk balance {{ srv.balance_type | default('roundrobin') }} {% if srv.http_check_enabled is defined and srv.http_check_enabled %} http-check send {{ srv.http_check }} http-check expect {{ srv.http_check_expect }} {% endif %} {% if srv.stick_sessions %} {% if srv.stick_on_cookie %} cookie {{ srv.stick_cookie }} {% else %} stick on src stick-table {{ srv.stick_table }} {% endif %} {% endif %} server-template {{ srv.service_name }}- {{ srv.service_replica_num }} {{ srv.stack_name }}_{{ srv.service_name }}:{{ srv.service_port }} {{ srv.backend_options | default('') }} check resolvers docker init-addr libc,none {% endfor %}