From 5253e25ef89794ba6723a1ba7997aca8a845a1fd Mon Sep 17 00:00:00 2001 From: AnsibleGuy Date: Fri, 31 May 2024 06:58:57 +0200 Subject: [PATCH] update security-header handling --- ExampleAcme.md | 10 +++++----- ExampleGeoIP.md | 10 +++++----- ExampleWAF.md | 10 +++++----- defaults/main/1_main.yml | 7 ++++--- templates/etc/haproxy/conf.d/frontend.cfg.j2 | 1 + templates/etc/haproxy/conf.d/inc/security.j2 | 2 +- templates/etc/haproxy/conf.d/inc/security_only_fe.j2 | 2 +- 7 files changed, 22 insertions(+), 20 deletions(-) diff --git a/ExampleAcme.md b/ExampleAcme.md index ad5bfde..888a7a7 100644 --- a/ExampleAcme.md +++ b/ExampleAcme.md @@ -103,11 +103,11 @@ root@test-ag-haproxy-acme:/# cat /etc/haproxy/conf.d/frontend.cfg > http-request deny status 405 default-errorfiles if { method TRACE CONNECT } > > # Security headers -> http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;" -> http-response set-header X-Frame-Options "DENY" -> http-response set-header X-Content-Type-Options "nosniff" -> http-response set-header X-Permitted-Cross-Domain-Policies "none" -> http-response set-header X-XSS-Protection "1; mode=block" +> http-response add-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;" if !{ res.hdr(Strict-Transport-Security) -m found } +> http-response add-header X-Frame-Options "SAMEORIGIN" if !{ res.hdr(X-Frame-Options) -m found } +> http-response add-header X-Content-Type-Options "nosniff" if !{ res.hdr(X-Content-Type-Options) -m found } +> http-response add-header X-Permitted-Cross-Domain-Policies "none" if !{ res.hdr(X-Permitted-Cross-Domain-Policies) -m found } +> http-response add-header X-XSS-Protection "1; mode=block" if !{ res.hdr(X-XSS-Protection) -m found } > > http-request capture req.fhdr(User-Agent) len 200 > diff --git a/ExampleGeoIP.md b/ExampleGeoIP.md index 5ea41e8..9b64e7a 100644 --- a/ExampleGeoIP.md +++ b/ExampleGeoIP.md @@ -118,11 +118,11 @@ root@test-ag-haproxy-geoip:/# cat /etc/haproxy/conf.d/frontend.cfg > http-request capture var(txn.geoip_asn) len 10 > > # Security headers -> http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;" -> http-response set-header X-Frame-Options "DENY" -> http-response set-header X-Content-Type-Options "nosniff" -> http-response set-header X-Permitted-Cross-Domain-Policies "none" -> http-response set-header X-XSS-Protection "1; mode=block" +> http-response add-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;" if !{ res.hdr(Strict-Transport-Security) -m found } +> http-response add-header X-Frame-Options "SAMEORIGIN" if !{ res.hdr(X-Frame-Options) -m found } +> http-response add-header X-Content-Type-Options "nosniff" if !{ res.hdr(X-Content-Type-Options) -m found } +> http-response add-header X-Permitted-Cross-Domain-Policies "none" if !{ res.hdr(X-Permitted-Cross-Domain-Policies) -m found } +> http-response add-header X-XSS-Protection "1; mode=block" if !{ res.hdr(X-XSS-Protection) -m found } > > http-request capture req.fhdr(User-Agent) len 200 > diff --git a/ExampleWAF.md b/ExampleWAF.md index 425d588..7cab44e 100644 --- a/ExampleWAF.md +++ b/ExampleWAF.md @@ -136,11 +136,11 @@ root@test-ag-haproxy-waf:/# cat /etc/haproxy/conf.d/frontend.cfg > http-request capture var(txn.bot) len 1 > > # Security headers -> http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;" -> http-response set-header X-Frame-Options "DENY" -> http-response set-header X-Content-Type-Options "nosniff" -> http-response set-header X-Permitted-Cross-Domain-Policies "none" -> http-response set-header X-XSS-Protection "1; mode=block" +> http-response add-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;" if !{ res.hdr(Strict-Transport-Security) -m found } +> http-response add-header X-Frame-Options "SAMEORIGIN" if !{ res.hdr(X-Frame-Options) -m found } +> http-response add-header X-Content-Type-Options "nosniff" if !{ res.hdr(X-Content-Type-Options) -m found } +> http-response add-header X-Permitted-Cross-Domain-Policies "none" if !{ res.hdr(X-Permitted-Cross-Domain-Policies) -m found } +> http-response add-header X-XSS-Protection "1; mode=block" if !{ res.hdr(X-XSS-Protection) -m found } > # SSL fingerprint > http-request lua.fingerprint_ja3n > http-request capture var(txn.fingerprint_ssl) len 32 diff --git a/defaults/main/1_main.yml b/defaults/main/1_main.yml index 8630872..b1f679d 100644 --- a/defaults/main/1_main.yml +++ b/defaults/main/1_main.yml @@ -101,7 +101,7 @@ defaults_frontend: flag_bots: false flag_bots_lines: [] # additional checks you want to append; you could p.e. check if a cookie set by JS exists - # prepend 'http-request set-var(txn.bot) int(1) if !{ var(txn.bot) -m found } ' before your conditions + # is auto-prepended: 'http-request set-var(txn.bot) int(1) if !{ var(txn.bot) -m found } ' before your conditions log: user_agent: true @@ -159,7 +159,7 @@ defaults_backend: flag_bots: false flag_bots_lines: [] # additional checks you want to append; you could p.e. check if a cookie set by JS exists - # prepend 'http-request set-var(txn.bot) int(1) if !{ var(txn.bot) -m found } ' before your conditions + # is auto-prepended: 'http-request set-var(txn.bot) int(1) if !{ var(txn.bot) -m found } ' before your conditions # for health-checks see: https://www.haproxy.com/blog/how-to-enable-health-checks-in-haproxy # more complex ones should be implemented by supplying the raw config-lines @@ -169,9 +169,10 @@ defaults_backend: check_uri: check_expect: +# NOTE: if your application adds these response headers - they will not be overwritten defaults_security_headers: Strict-Transport-Security: 'max-age=16000000; includeSubDomains; preload;' - X-Frame-Options: 'DENY' # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options + X-Frame-Options: 'SAMEORIGIN' # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options X-Content-Type-Options: 'nosniff' # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options X-Permitted-Cross-Domain-Policies: 'none' X-XSS-Protection: '1; mode=block' diff --git a/templates/etc/haproxy/conf.d/frontend.cfg.j2 b/templates/etc/haproxy/conf.d/frontend.cfg.j2 index 2d4abb2..f731c77 100644 --- a/templates/etc/haproxy/conf.d/frontend.cfg.j2 +++ b/templates/etc/haproxy/conf.d/frontend.cfg.j2 @@ -24,6 +24,7 @@ frontend {{ name }} {% endif %} {% if cnf.mode == 'http' %} + http-request set-header X-Forwarded-For %[src] {% include "inc/security.j2" %} {% include "inc/security_only_fe.j2" %} diff --git a/templates/etc/haproxy/conf.d/inc/security.j2 b/templates/etc/haproxy/conf.d/inc/security.j2 index 3fc3af2..4a8cb0b 100644 --- a/templates/etc/haproxy/conf.d/inc/security.j2 +++ b/templates/etc/haproxy/conf.d/inc/security.j2 @@ -35,7 +35,7 @@ http-request set-var(txn.bot) int(1) if !{ var(txn.bot) -m found } !{ req.fhdr(User-Agent) -m found } {% for line in cnf.security.flag_bots_lines %} - {{ line }} + http-request set-var(txn.bot) int(1) if !{ var(txn.bot) -m found } {{ line }} {% endfor %} http-request set-var(txn.bot) int(0) if !{ var(txn.bot) -m found } 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 002b273..03c7863 100644 --- a/templates/etc/haproxy/conf.d/inc/security_only_fe.j2 +++ b/templates/etc/haproxy/conf.d/inc/security_only_fe.j2 @@ -1,7 +1,7 @@ {% if cnf.security.headers | bool %} # Security headers {% for header, value in defaults_security_headers.items() %} - http-response set-header {{ header }} "{{ value }}" + http-after-response add-header {{ header }} "{{ value }}" !{ res.hdr({{ header }}) -m found } {% endfor %} {% endif %} {% if cnf.security.fingerprint_ssl | bool %}