## {{ salt['pillar.get']('salt_managed', default='Salt Managed') }} {%- from "haproxy/map.jinja" import haproxy,certs,fqdn with context %} {%- set ns = namespace(default_backend='notdefined') %} {%- for name, values in haproxy.config.vhosts.items() %}{% if values.default_backend|default(false) %}{% set ns.default_backend = name %}{% endif %}{% endfor %} {%- macro internal() -%} acl internal src -f {{ haproxy.config.dir }}/maps/access http-response return status 403 content-type text/html string "

403 forbidden

" if !internal {%- endmacro -%} {%- macro head() -%} http-request return status 200 if METH_HEAD {%- endmacro -%} {%- macro statusresponses() -%} http-response return status 403 content-type text/html string "

403 forbidden

" if { status 403 } http-response return status 404 content-type text/html string "

404 not found

" if { status 404 } {%- endmacro -%} {%- macro httpcheckrules(layer="layer7",inter="1s",fall=5,rise=5) -%}check observe {{ layer }} inter {{ inter }} fall {{ fall }} rise {{ rise }}{%- endmacro -%} {%- macro httpsslrules(h2=False) -%}ssl verify none{{ " alpn h2,http/1.1" if h2 }}{%- endmacro -%} {%- macro httpendpoints(servers=[], check=True, disabled=False) -%} {%- if servers -%} {%- for server in servers %} {% if server.name in haproxy.servers.keys() -%} {%- set addr = haproxy.servers[server.name][0] -%} {%- else -%} {%- set addr = server.name -%} {%- endif -%} server {{ server.name }} {{ addr }}:{{ server.port }}{{ " " + httpcheckrules(inter=server.inter|default("1s"), fall=server.fall|default(5), rise=server.rise|default(5)) if check }}{{ " " + httpsslrules(server.h2|default(False)) if server.ssl|default(False) }}{{ " disabled" if server.disabled|default(False) }}{{ " send-proxy" if server.proxy|default(False) }}{{ " on-marked-down shutdown-sessions on-marked-up shutdown-backup-sessions" if server.killsessions|default(False) }} {%- endfor %} {%- endif -%} {%- endmacro -%} {%- macro tcpendpoints(servers=[], check=True) -%} {%- if servers -%} {%- for server in servers %} {% if server.name in haproxy.servers.keys() -%} {%- set addr = haproxy.servers[server.name][0] -%} {%- else -%} {%- set addr = server.name -%} {%- endif -%} server {{ server.name }} {{ addr }}:{{ server.port }}{{ " check" if check }}{{ " backup" if server.backup|default(False) }} port {{ server.port }}{{ " on-marked-down shutdown-sessions on-marked-up shutdown-backup-sessions" if server.killsessions|default(False) }} {%- endfor %} {%- endif -%} {%- endmacro -%} {%- macro cache() -%} http-request cache-use static if { path_end {{ haproxy.config.cache.file_types|join(" ") }} } http-response cache-store static {%- endmacro -%} {%- macro compression() -%} compression algo gzip compression type {{ haproxy.config.compression_mime_types|join(' ') }} {%- endmacro -%} {%- macro admin() -%} # Stats Backend backend admin from {{ haproxy.config.namespace }} mode http stats enable stats admin if TRUE stats refresh 10s stats show-modules stats show-legends stats uri / {%- endmacro -%} {%- macro api() -%} # Runtime API stats socket {{ haproxy.config.api.tcpsocket }} level admin stats socket {{ haproxy.config.api.filesocket }} mode 666 level admin {%- endmacro %} # Global config global master-worker server-state-file /var/run/haproxy.state mworker-max-reloads 2 maxconn 1000 lua-prepend-path {{ haproxy.config.dir }}/mods/?.so cpath lua-prepend-path {{ haproxy.config.dir }}/scripts/?.lua {%- for script in haproxy.config.scripts %} {%- if not script.lib and script.enabled|default(true) %} lua-load {{ haproxy.config.dir }}/{{ script.name }} {% if "args" in script.keys() %}{{ script.args|join(" ") }}{% endif %} {%- endif %} {%- endfor %} {%- if haproxy.config.api.enable %} {{ api() }} {%- endif %} crt-base {{ haproxy.config.acme_fullchains_dir }} ssl-dh-param-file {{ haproxy.config.acme_dh_dir }}/dh.pem ssl-default-bind-ciphers {{ haproxy.config.ssl_ciphers|join(":") }} ssl-default-bind-options {{ haproxy.config.ssl_options|join(" ") }} ssl-default-server-ciphers {{ haproxy.config.ssl_ciphers|join(":") }} ssl-default-server-options {{ haproxy.config.ssl_options|join(" ") }} tune.lua.maxmem {{ haproxy.config.lua_max_mem }} expose-experimental-directives .if feature(QUIC) limited-quic .endif # Defaults values defaults {{ haproxy.config.namespace }} {%- for key, value in haproxy.config.defaults.items() %} {{ key }} {{ value }} {%- endfor %} {% if haproxy.config.peers.hosts -%} peers paulbsd bind *:{{ haproxy.config.peers.port }} ssl crt {{ haproxy.config.acme_fullchains_dir }} default-server ssl verify none server {{ salt['grains.get']('fqdn') }} {%- for host in haproxy.config.peers.hosts %} server {{ host[0] }} {{ host[1] }}:{{ haproxy.config.peers.port }} {%- endfor %} {%- endif %} # Cache cache static total-max-size {{ haproxy.config.cache.total|default(64) }} max-object-size {{ (haproxy.config.cache.size|default(8))*1024*1024 }} max-age {{ haproxy.config.cache.age|default(3600) }} # Per IP rates stick table backend per_ip_rates from {{ haproxy.config.namespace }} stick-table type string size {{ haproxy.config.ddos.size|default("1m") }} expire {{ haproxy.config.ddos.timeperiod|default("10s") }} store http_req_rate({{ haproxy.config.ddos.timeperiod|default("10s")}}) {{ "peers paulbsd" if haproxy.config.peers.hosts }} # Default HTTP frontend frontend fe_http from {{ haproxy.config.namespace }} bind *:{{ haproxy.config.http_port }},:::{{ haproxy.config.http_port }} v4v6 name http mode http {% for name, service in haproxy.config.spoe.items() %} filter spoe engine {{ name }} config {{ haproxy.config.dir }}/spoe.cfg {%- endfor %} ## ACLs acl http ssl_fc,not acl self_host req.hdr(Host) {{ fqdn }} acl path_root path / acl path_host path_dir /host acl path_date path_dir /date acl path_srchash path_dir /srchash ## Basic rules http-request set-var(txn.srchash) src,crc32,mod(100) http-request set-var(txn.httpdate) date,http_date() http-request return status 200 content-type text/html lf-string "%H\n" if self_host path_host http-request return status 200 content-type text/html lf-string "%[var(txn.httpdate)]\n" if self_host path_date http-request return status 200 content-type text/html lf-string "%[var(txn.srchash)]\n" if self_host path_srchash http-request redirect scheme https if http # Default HTTPS frontend frontend fe_https from {{ haproxy.config.namespace }} .if feature(QUIC) bind quic4@*:{{ haproxy.config.https_port }},quic6@:::{{ haproxy.config.https_port }} v4v6 ssl crt {{ haproxy.config.acme_fullchains_dir }}{% if haproxy.config.http2 %} alpn h3,h2,http/1.1{% endif %} name https .endif bind *:{{ haproxy.config.https_port }},:::{{ haproxy.config.https_port }} v4v6 ssl crt {{ haproxy.config.acme_fullchains_dir }}{% if haproxy.config.http2 %} alpn h2,http/1.1{% endif %} name https mode http option httplog option http-buffer-request {% for name, service in haproxy.config.spoe.items() %} filter spoe engine {{ name }} config {{ haproxy.config.dir }}/spoe.cfg {%- endfor %} ## ACLs acl internal src -f {{ haproxy.config.dir }}/maps/access acl domains req.hdr(Host),map_dom({{ haproxy.config.dir }}/maps/domains) -m found acl ua req.hdr(User-Agent),map_beg(/etc/haproxy/maps/ua) -m found acl security_txt path /.well-known/security.txt acl robots_txt path /robots.txt acl max_req_rate sc_http_req_rate(0) gt {{ haproxy.config.ddos.maxrequests|default(200) }} acl self_host req.hdr(Host) {{ fqdn }} acl path_root path / acl path_host path_dir /host acl path_date path_dir /date acl path_srchash path /srchash acl path_location path /location ## Basic rules http-request set-var(txn.random) rand,mul(5) http-request set-var(txn.httpdate) date,http_date() http-request set-var(txn.srchash) src,crc32,mod(100) http-request set-var(req.src) src http-request set-var(req.host) req.hdr(Host) http-request set-var(req.accesshash) str(),concat(,req.src,),concat(-,req.host,) http-request track-sc0 var(req.accesshash) table per_ip_rates http-request capture req.hdr(User-Agent) len 200 http-request capture req.hdr(Content-Type) len 50 http-request capture sc_http_req_rate(0) len 4 ## Silent drop all external requests with no host header http-request silent-drop if !domains !internal ## DDoS http-request deny deny_status 429 if max_req_rate !internal {%- if haproxy.config.geoip.enabled %} # GeoIP http-request set-var(txn.country) lua.country(req.src) http-request return status 200 content-type "text/html; charset=utf-8" lf-string "

ip: %[var(req.src)]

country: %[lua.country(req.src)]

city: %[lua.city(req.src)]

" if self_host path_location acl allowed_country var(txn.country),map_str(/etc/haproxy/maps/countries,OK) OK http-response deny deny_status 451 content-type text/plain string "Denied" if !allowed_country !internal {%- endif %} ## Returns http-request return status 200 content-type text/plain string "User-agent: *\r\nAllow: /" if robots_txt http-request return status 200 content-type text/plain string "Contact: mailto:{{ haproxy.config.syscontact }}" if security_txt http-request return status 200 content-type text/html lf-string "%H\n" if self_host path_host http-request return status 200 content-type text/html lf-string "%[var(txn.httpdate)]\n" if self_host path_date http-request return status 200 content-type text/html lf-string "%[var(txn.srchash)]\n" if self_host path_srchash ## Headers http-request set-header X-Proxy-Id "%H" http-request set-header X-Proto https if { ssl_fc } http-response set-header Date "%[var(txn.httpdate)]" http-response set-header Server "{{ haproxy.config.servername }}" http-response set-header X-Random "%[var(txn.random)]" .if feature(QUIC) http-response set-header Alt-Svc "h3=\":443\"; ma=3600" .endif http-request redirect location %[req.hdr(Host),map_dom({{ haproxy.config.dir }}/maps/redirects)] code 301 if { req.hdr(Host),map_dom({{ haproxy.config.dir }}/maps/redirects) -m found } http-request deny deny_status 404 unless domains || ua {%- if haproxy.config.admin %} use_backend admin if self_host internal {%- endif %} use_backend %[req.hdr(Host),lower,map({{ haproxy.config.dir }}/maps/vhosts)] if domains use_backend %[req.hdr(User-Agent),map_beg({{ haproxy.config.dir }}/maps/ua)] if ua default_backend {{ ns.default_backend }} monitor-uri /dead_or_alive log-format "%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r" # HTTP Backends {%- for name, values in haproxy.config.vhosts.items() %} {%- if not values.redirect|default(False) %} backend {{ name }} from {{ haproxy.config.namespace }} balance {{ values.balance|default(haproxy.config.balance) }} mode http option forwardfor {%- if values.check|default(haproxy.config.check) %} option httpchk {%- for step in values.check_steps|default([]) %} http-check {{ step }} {%- endfor %} {%- endif %} {%- if values.overrides|default([]) %} {%- for override in values.overrides %} {{ override }} {%- endfor %} {%- endif %} {%- if values.head|default(False) %} {{ head() }} {%- endif %} {%- if values.compression|default(True) %} {{ compression() }} {%- endif %} {%- if values.usecache|default(True) %} {{ cache() }} {%- endif %} {%- if values.internal|default(False) %} {{ internal() }} {%- endif %} {{- httpendpoints(servers=values.servers|default([]), check=values.check|default(haproxy.config.check)) }} {%- endif %} {% endfor %} # TCP services {%- for name, values in haproxy.config.services.items() %} listen {{ name }} from {{ haproxy.config.namespace }} bind *:{{ values.port }},:::{{ values.port }} v4v6 name {{ name }} mode tcp option tcplog option tcpka {%- if values.type == "postgres" %} option pgsql-check user repmgr {%- endif %} {%- if values.type == "spoe" %} timeout connect 1s timeout server 1s option spop-check {%- endif %} default-server inter 1s fall 5 {{- tcpendpoints(servers=values.servers|default([]), check=values.check|default(haproxy.config.check)) }} {% endfor %} # SPOE Agents {%- for name, values in haproxy.config.spoe.items() %} backend {{ name }} from {{ haproxy.config.namespace }} mode tcp timeout connect 5s timeout server 3m option spop-check {{- tcpendpoints(servers=values.servers, check=values.check|default(haproxy.config.check)) }} {% endfor %} {%- if haproxy.config.admin %} {{ admin() if haproxy.config.admin }} {%- endif %}