Skip to content

Commit

Permalink
added option for SSL fingerprint
Browse files Browse the repository at this point in the history
  • Loading branch information
ansibleguy committed May 4, 2024
1 parent 6457963 commit f9a920b
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 25 deletions.
36 changes: 24 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<img src="https://www.haproxy.com/assets/legal/web-logo.png" alt="HAProxy Logo" width="300"/>
</a>

# Ansible Role - HAProxy Community
# Ansible Role - HAProxy Community (with ACME, GeoIP and some WAF-Features)

Role to deploy HAProxy (*Focus on the Community Version*)

Expand Down Expand Up @@ -41,6 +41,18 @@ ansible-galaxy install -r requirements.yml

----

### Roadmap

* Security
* Basic bot flagging
* Basic rate limit (GET/HEAD and POST/PUT/DELETE separated)
* Generic client fingerprint
* 'Interface' for Dict to Map-File translation/creation
* Option to easily Download & Integrate IPLists (*like Tor Exit nodes*)
* Easy way to override the default error-files

----

## Functionality

* **Package installation**
Expand Down Expand Up @@ -73,6 +85,10 @@ ansible-galaxy install -r requirements.yml
* [GeoIP Lookups](https://github.com/superstes/haproxy-geoip)
* Blocking of well-known Script-Bots
* Blocking TRACE & CONNECT methods
* SSL Fingerprinting ([JA3](https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967/?ref=waf.ninja))

* Backend
* Sticky sessions (*use same backend )

----

Expand Down Expand Up @@ -103,6 +119,11 @@ ansible-galaxy install -r requirements.yml
* **Info:** If you are using [Graylog Server](https://graylog.org/products/source-available/) to gather and analyze your logs - make sure to split your HAProxy logs into fields using pipeline rules. Example: [HAProxy Community - Graylog Pipeline Rule](https://gist.github.com/superstes/a2f6c5d855857e1f10dcb51255fe08c6#haproxy-split)


* **Info:** If you enable `fingerprint_ssl` you can reference it using the variables:

* `var(txn.fingerprint_ssl)` => MD5 hash of JA3 fingerprint
* `var(txn.fingerprint_ssl_raw)` => raw JA3 fingerprint

### GeoIP


Expand Down Expand Up @@ -209,6 +230,7 @@ haproxy:
security:
restrict_methods: true
allow_only_methods: ['HEAD', 'GET', 'POST']
fingerprint_ssl: true # create and log the JA3 SSL-fingerprint of clients

# very basic filtering of bad bots based on user-agent matching
block_script_bots: true
Expand Down Expand Up @@ -304,21 +326,11 @@ ansible-playbook -K -D -i inventory/hosts.yml playbook.yml

There are also some useful **tags** available:
* install
* config
* config => only update config and ssl certs
* ssl or acme
* geoip

To debug errors - you can set the 'debug' variable at runtime:
```bash
ansible-playbook -K -D -i inventory/hosts.yml playbook.yml -e debug=yes
```

----

### Roadmap

* Security - Basic bot flagging
* Security - Basic rate limit (GET/HEAD and POST/PUT/DELETE separated)
* 'Interface' for Dict to Map-File translation/creation
* Option to easily Download & Integrate IPLists (*like Tor Exit nodes*)
* Easy way to override the default error-files
8 changes: 8 additions & 0 deletions defaults/main/1_main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ defaults_haproxy:
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: 'ssl-min-ver TLSv1.2 no-tls-tickets'
tune.ssl.capture-buffer-size: 96 # needed for ssl finterprinting

defaults:
log: 'global'
Expand Down Expand Up @@ -82,6 +83,7 @@ defaults_frontend:
ssl_redirect: true
security:
headers: true
fingerprint_ssl: true # create and log the JA3 fingerprint of clients

restrict_methods: false
allow_only_methods: ['HEAD', 'GET', 'POST']
Expand Down Expand Up @@ -128,6 +130,12 @@ defaults_backend:
servers: []
ssl: false
ssl_verify: 'none' # example: 'required ca-file /etc/ssl/certs/my_ca.crt verifyhost host01.intern'
sticky: false # set cookie to stick sessions to a specific backend server
sticky_http:
cookie: 'srv'
sticky_tcp:
match: 'src'
table: 'type ip size 5k expire 120m'

security:
restrict_methods: false
Expand Down
2 changes: 1 addition & 1 deletion meta/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ galaxy_info:
license: 'GPLv3'
issue_tracker_url: 'https://github.com/ansibleguy/infra_haproxy/issues'
min_ansible_version: '2.14'
description: 'Role to deploy HAProxy Community (with GeoIP support built-in)'
description: 'Provision HAProxy Community (with ACME, GeoIP and some WAF-Features)'
platforms:
- name: Debian
versions:
Expand Down
31 changes: 21 additions & 10 deletions templates/etc/haproxy/conf.d/backend.cfg.j2
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,33 @@ backend {{ name }}
mode {{ cnf.mode }}
balance {{ cnf.balance }}

{% if cnf.check | bool and cnf.mode == 'http' %}
{% if cnf.check_http | bool %}
{% if cnf.mode == 'http' %}
{% if cnf.check | bool %}
{% if cnf.check_http | bool %}
option httpchk
{% endif %}
{% if cnf.check_uri | default(none, true) is not none %}
{% endif %}
{% if cnf.check_uri | default(none, true) is not none %}
http-check send meth {{ cnf.check_method }} uri {{ cnf.check_uri }}
{% endif %}
{% if cnf.check_expect | default(none, true) is not none %}
{% endif %}
{% if cnf.check_expect | default(none, true) is not none %}
http-check expect {{ cnf.check_expect }}
{% endif %}
{% endif %}
{% endif %}
{% endif %}

{% if cnf.sticky | bool %}
cookie {{ cnf.sticky_http.cookie }} insert indirect nocache
{% endif %}

{% if cnf.mode == 'http' %}
{% include "inc/security.j2" %}
{% endif %}

{% if cnf.mode == 'tcp' %}
{% if cnf.sticky | bool %}
stick match {{ cnf.sticky_tcp.match }}
stick-table {{ cnf.sticky_tcp.table }}
{% endif %}
{% endif %}

{% if cnf.lines | is_dict %}
{% for section, lines in cnf.lines.items() %}
# SECTION: {{ section }}
Expand All @@ -41,7 +52,7 @@ backend {{ name }}
{% endif %}

{% for server in cnf.servers | ensure_list %}
server {{ server }}{% if cnf.check | bool %} check{% endif %}{% if cnf.ssl | bool %} ssl verify {{ cnf.ssl_verify }}{% endif %}
server {{ server }}{% if cnf.check | bool %} check{% endif %}{% if cnf.ssl | bool %} ssl verify {{ cnf.ssl_verify }}{% endif %}{% if cnf.mode == 'http' and cnf.sticky | bool %} cookie {{ cnf.sticky_http.cookie }}{{ loop.index }}{% endif %}
{% endfor %}

{% endfor %}
Expand Down
1 change: 1 addition & 0 deletions templates/etc/haproxy/conf.d/frontend.cfg.j2
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ frontend {{ name }}
{% endif %}

{% include "inc/security.j2" %}
{% include "inc/security_only_fe.j2" %}
{% endif %}

{% if cnf.log.user_agent | bool %}
Expand Down
4 changes: 2 additions & 2 deletions templates/etc/haproxy/conf.d/inc/security.j2
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{% endif %}

{% if cnf.security.block_script_bots | bool %}
# block well-known script-bots
# block well-known script-bots
http-request deny status {{ cnf.security.block_status_code }} default-errorfiles if !{ req.fhdr(User-Agent) -m found }
{% if HAPROXY_HC.user_agents.script.full | length > 0 %}
http-request deny status {{ cnf.security.block_status_code }} default-errorfiles if { req.fhdr(User-Agent) -m str -i {{ HAPROXY_HC.user_agents.script.full | ensure_list | join(' ') }} }
Expand All @@ -16,7 +16,7 @@
{% endif %}
{% endif %}
{% if cnf.security.block_bad_crawler_bots | bool %}
# block well-known bad-crawler-bots
# block well-known bad-crawler-bots
{% if HAPROXY_HC.user_agents.bad_crawlers.full | length > 0 %}
http-request deny status {{ cnf.security.block_status_code }} default-errorfiles if { req.fhdr(User-Agent) -m str -i {{ HAPROXY_HC.user_agents.bad_crawlers.full | ensure_list | join(' ') }} }
{% endif %}
Expand Down
11 changes: 11 additions & 0 deletions templates/etc/haproxy/conf.d/inc/security_only_fe.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{% if cnf.security.fingerprint_ssl | bool %}
# SSL fingerprint
http-request set-var(txn.fp_ssl_p1) ssl_fc_cipherlist_bin(1),be2dec(-,2)
http-request set-var(txn.fp_ssl_p2) ssl_fc_extlist_bin(1),be2dec(-,2)
http-request set-var(txn.fp_ssl_p3) ssl_fc_eclist_bin(1),be2dec(-,2)
http-request set-var(txn.fp_ssl_p4) ssl_fc_ecformats_bin,be2dec(-,1)
http-request set-var(txn.fingerprint_ssl_raw) "ssl_fc_protocol_hello_id,concat(',',txn.fp_ssl_p1),concat(',',txn.fp_ssl_p2),concat(',',txn.fp_ssl_p3),concat(',',txn.fp_ssl_p4)"
http-request set-var(txn.fingerprint_ssl) var(txn.fingerprint_ssl_raw),digest(md5),hex,lower
http-request capture var(txn.fingerprint_ssl_raw) len 1000
http-request capture var(txn.fingerprint_ssl) len 32
{% endif %}

0 comments on commit f9a920b

Please sign in to comment.