From 7e83b86db1ea831d266888996b8ef72ff426d202 Mon Sep 17 00:00:00 2001 From: AnsibleGuy Date: Sun, 25 Aug 2024 16:57:14 +0200 Subject: [PATCH] prepare for JA4 ssl-fingerprint --- defaults/main/0_hardcoded.yml | 2 + defaults/main/1_main.yml | 3 +- filter_plugins/utils.py | 13 +++++ tasks/debian/install.yml | 17 +++++-- .../haproxy/conf.d/inc/security_only_fe.j2 | 5 ++ templates/etc/haproxy/haproxy.cfg.j2 | 9 +++- templates/etc/haproxy/lua/ja3n.lua.j2 | 51 ------------------- 7 files changed, 44 insertions(+), 56 deletions(-) delete mode 100644 templates/etc/haproxy/lua/ja3n.lua.j2 diff --git a/defaults/main/0_hardcoded.yml b/defaults/main/0_hardcoded.yml index e167856..2ca41ba 100644 --- a/defaults/main/0_hardcoded.yml +++ b/defaults/main/0_hardcoded.yml @@ -50,6 +50,8 @@ HAPROXY_HC: geoip_maxmind_country: "https://download.maxmind.com/geoip/databases/GeoLite2-ASN/download?suffix=tar.gz" geoip_maxmind_asn: "https://download.maxmind.com/geoip/databases/GeoLite2-ASN/download?suffix=tar.gz" acme_script: "https://github.com/dehydrated-io/dehydrated/releases/download/v{{ version_dehydrated }}/dehydrated-{{ version_dehydrated }}.tar.gz" + ja3n_script: 'https://raw.githubusercontent.com/O-X-L/haproxy-ja3n/latest/ja3n.lua' + ja4_script: 'https://raw.githubusercontent.com/O-X-L/haproxy-ja4/latest/ja4.lua' valid_geoip_providers: ['ipinfo', 'maxmind'] user_geoip: 'haproxy-geoip' diff --git a/defaults/main/1_main.yml b/defaults/main/1_main.yml index b87f7d7..4eb7cff 100644 --- a/defaults/main/1_main.yml +++ b/defaults/main/1_main.yml @@ -89,7 +89,8 @@ defaults_frontend: ssl_redirect: true security: headers: true - fingerprint_ssl: false # create and log the JA3 fingerprint of clients + fingerprint_ssl: false # create and log the JA3/JA4 fingerprint of clients + fingerprint_ssl_type: 'ja3n' # WARNING: ja4 is not yet in a usable state! restrict_methods: false allow_only_methods: ['HEAD', 'GET', 'POST'] diff --git a/filter_plugins/utils.py b/filter_plugins/utils.py index 2ca6948..6f2aa43 100644 --- a/filter_plugins/utils.py +++ b/filter_plugins/utils.py @@ -10,6 +10,7 @@ def filters(self): "is_dict": self.is_dict, "safe_key": self.safe_key, "ssl_fingerprint_active": self.ssl_fingerprint_active, + "ssl_fingerprint_ja4": self.ssl_fingerprint_ja4, "build_route": self.build_route, "join_w_excludes": self.join_w_excludes, } @@ -46,6 +47,18 @@ def ssl_fingerprint_active(frontends: dict) -> bool: return False + @staticmethod + def ssl_fingerprint_ja4(frontends: dict) -> bool: + for fe_cnf in frontends.values(): + try: + if fe_cnf['security']['fingerprint_ssl_type'].lower() == 'ja4': + return True + + except KeyError: + continue + + return False + @staticmethod def is_truthy(v: (bool, str, int)) -> bool: return v in [True, 'yes', 'y', 'Yes', 'YES', 'true', 1, '1'] diff --git a/tasks/debian/install.yml b/tasks/debian/install.yml index 1ede097..24a0821 100644 --- a/tasks/debian/install.yml +++ b/tasks/debian/install.yml @@ -98,11 +98,22 @@ name: 'haproxy.service' enabled: true -- name: HAProxy | Install | Add LUA SSL-Fingerprint (JA3N) module - ansible.builtin.template: - src: "templates{{ HAPROXY_HC.path.lua }}/ja3n.lua.j2" +- name: HAProxy | Install | Download SSL-Fingerprint plugin (JA3N) + ansible.builtin.get_url: + url: "{{ HAPROXY_HC.url.ja3n_script }}" dest: "{{ HAPROXY_HC.path.lua }}/ja3n.lua" owner: 'root' group: 'haproxy' mode: 0750 tags: lua + +- name: HAProxy | Install | Download SSL-Fingerprint plugin (JA4) + ansible.builtin.get_url: + url: "{{ HAPROXY_HC.url.ja4_script }}" + dest: "{{ HAPROXY_HC.path.lua }}/ja4.lua" + owner: 'root' + group: 'haproxy' + mode: 0750 + tags: lua + +# todo: opt-in for JA4-DB lookups + map update service (https://github.com/O-X-L/haproxy-ja4) diff --git a/templates/etc/haproxy/conf.d/inc/security_only_fe.j2 b/templates/etc/haproxy/conf.d/inc/security_only_fe.j2 index 0716404..482cd2d 100644 --- a/templates/etc/haproxy/conf.d/inc/security_only_fe.j2 +++ b/templates/etc/haproxy/conf.d/inc/security_only_fe.j2 @@ -6,6 +6,11 @@ {% endif %} {% if cnf.security.fingerprint_ssl | bool %} # SSL fingerprint +{% if cnf.security.fingerprint_ssl_type | lower == 'ja4' %} + http-request lua.fingerprint_ja4 + http-request capture var(txn.fingerprint_ssl) len 36 +{% else %} http-request lua.fingerprint_ja3n http-request capture var(txn.fingerprint_ssl) len 32 +{% endif %} {% endif %} diff --git a/templates/etc/haproxy/haproxy.cfg.j2 b/templates/etc/haproxy/haproxy.cfg.j2 index 4aa4579..3973687 100644 --- a/templates/etc/haproxy/haproxy.cfg.j2 +++ b/templates/etc/haproxy/haproxy.cfg.j2 @@ -10,9 +10,16 @@ global lua-load {{ HAPROXY_HC.path.lua }}/geoip.lua {% endif %} {% if HAPROXY_CONFIG.frontends | ssl_fingerprint_active %} +{% if HAPROXY_CONFIG.frontends | ssl_fingerprint_ja4 %} + lua-load {{ HAPROXY_HC.path.lua }}/ja4.lua +{% if 'tune.ssl.capture-buffer-size' not in HAPROXY_CONFIG.global %} + tune.ssl.capture-buffer-size 128 +{% endif %} +{% else %} lua-load {{ HAPROXY_HC.path.lua }}/ja3n.lua -{% if 'tune.ssl.capture-buffer-size' not in HAPROXY_CONFIG.global %} +{% if 'tune.ssl.capture-buffer-size' not in HAPROXY_CONFIG.global %} tune.ssl.capture-buffer-size 96 +{% endif %} {% endif %} {% endif %} diff --git a/templates/etc/haproxy/lua/ja3n.lua.j2 b/templates/etc/haproxy/lua/ja3n.lua.j2 deleted file mode 100644 index 39f43b7..0000000 --- a/templates/etc/haproxy/lua/ja3n.lua.j2 +++ /dev/null @@ -1,51 +0,0 @@ --- {{ ansible_managed }} --- ansibleguy.infra_haproxy --- source: https://gist.github.com/superstes/0d0a94cb70f2e2713f4a90fa88160795 --- see: https://tlsfingerprint.io/norm_fp --- JA3N = sorted extensions to tackle browsers randomizing their order --- examples: --- before (JA3): 51-27-0-16-17513-65281-23-65037-11-45-43-5-35-10-18-13 --- after (JA3N): 0-5-10-11-13-16-18-23-27-35-43-45-51-17513-65037-65281 - -function split_string(str, delimiter) - local result = {} - local from = 1 - local delim_from, delim_to = string.find(str, delimiter, from) - while delim_from do - table.insert(result, string.sub(str, from , delim_from-1)) - from = delim_to + 1 - delim_from, delim_to = string.find(str, delimiter, from) - end - table.insert(result, string.sub(str, from)) - return result -end - -local function sortNumbers(a, b) - if (a == nil or b == nil) - then - return false - end - - return tonumber(a) < tonumber(b) -end - -function fingerprint_ja3n(txn) - local p1 = tostring(txn.f:ssl_fc_protocol_hello_id()) - local p2 = tostring(txn.c:be2dec(txn.f:ssl_fc_cipherlist_bin(1),"-",2)) - - local p3u = tostring(txn.c:be2dec(txn.f:ssl_fc_extlist_bin(1),"-",2)) - local p3l = split_string(p3u, "-") - table.sort(p3l, sortNumbers) - local p3 = table.concat(p3l, "-") - - local p4 = tostring(txn.c:be2dec(txn.f:ssl_fc_eclist_bin(1),"-",2)) - local p5 = tostring(txn.c:be2dec(txn.f:ssl_fc_ecformats_bin(),"-",1)) - - local fingerprint = p1 .. "," .. p2 .. "," .. p3 .. "," .. p4 .. "," .. p5 - local fingerprint_hash = string.lower(tostring(txn.c:hex(txn.c:digest(fingerprint, "md5")))) - - txn:set_var('txn.fingerprint_ssl_raw', fingerprint) - txn:set_var('txn.fingerprint_ssl', fingerprint_hash) -end - -core.register_action('fingerprint_ja3n', {'tcp-req', 'http-req'}, fingerprint_ja3n)