From 6bf7fabcdffd5543d74b0d496421fca5b44e39d8 Mon Sep 17 00:00:00 2001 From: M Rizwan Shaik Date: Fri, 10 Jul 2026 09:07:21 +0200 Subject: [PATCH 1/7] Introduce exclude cidrs list from rate-limiting feature --- jobs/haproxy/spec | 10 +++ jobs/haproxy/templates/haproxy.config.erb | 16 ++++- .../rate_limit_exclusion_cidrs.txt.erb | 25 ++++++++ .../haproxy_config/rate_limit_spec.rb | 28 ++++++++ .../rate_limit_exclusion_cidrs.txt_spec.rb | 64 +++++++++++++++++++ 5 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 jobs/haproxy/templates/rate_limit_exclusion_cidrs.txt.erb create mode 100644 spec/haproxy/templates/rate_limit_exclusion_cidrs.txt_spec.rb diff --git a/jobs/haproxy/spec b/jobs/haproxy/spec index ef407dc2..7da59dee 100644 --- a/jobs/haproxy/spec +++ b/jobs/haproxy/spec @@ -26,6 +26,7 @@ templates: whitelist_cidrs.txt.erb: config/whitelist_cidrs.txt expect_proxy_cidrs.txt.erb: config/expect_proxy_cidrs.txt trusted_domain_cidrs.txt.erb: config/trusted_domain_cidrs.txt + rate_limit_exclusion_cidrs.txt.erb: config/rate_limit_exclusion_cidrs.txt consumes: - name: http_backend @@ -827,3 +828,12 @@ properties: ha_proxy.connections_rate_limit.block: description: Whether or not to block connections. See docs/rate_limiting.md default: false + ha_proxy.connections_rate_limit.exclude_cidrs: + description: "List of CIDRs (e.g. private/NAT ranges) to exclude from connection based rate-limiting. Excluded sources are still tracked in the stick-table but are never rejected. Format is string array of CIDRs or single string of base64 encoded gzip. See docs/rate_limiting.md" + default: ~ + example: + connections_rate_limit: + exclude_cidrs: + - 10.0.0.0/8 + - 192.168.0.0/16 + - 2001:db8::/32 diff --git a/jobs/haproxy/templates/haproxy.config.erb b/jobs/haproxy/templates/haproxy.config.erb index 12ef57d7..bc538737 100644 --- a/jobs/haproxy/templates/haproxy.config.erb +++ b/jobs/haproxy/templates/haproxy.config.erb @@ -239,6 +239,12 @@ end end end + # Connections rate-limit exclusion: sources matching exclude_cidrs are tracked but never rejected + conn_rate_limit_exclude = "" + if_p("ha_proxy.connections_rate_limit.exclude_cidrs") do + conn_rate_limit_exclude = " !rate_limit_exclude" + end + backend_servers = [] backend_servers_local = [] backend_port = nil @@ -451,9 +457,12 @@ frontend http-in acl layer4_block src -f /var/vcap/jobs/haproxy/config/blocklist_cidrs_tcp.txt tcp-request <%= tcp_request_phase %> reject if layer4_block <%- if_p("ha_proxy.connections_rate_limit.table_size", "ha_proxy.connections_rate_limit.window_size") do -%> + <%- if_p("ha_proxy.connections_rate_limit.exclude_cidrs") do -%> + acl rate_limit_exclude src -f /var/vcap/jobs/haproxy/config/rate_limit_exclusion_cidrs.txt + <%- end -%> tcp-request <%= tcp_request_phase %> track-sc0 src table st_tcp_conn_rate # use sub() converter as variable references are only accepted as arguments to converters - tcp-request <%= tcp_request_phase %> reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 } + tcp-request <%= tcp_request_phase %> reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 }<%= conn_rate_limit_exclude %> <%- end -%> <%- if_p("ha_proxy.requests_rate_limit.table_size", "ha_proxy.requests_rate_limit.window_size") do -%> http-request track-sc1 src table st_http_req_rate @@ -582,9 +591,12 @@ frontend https-in acl layer4_block src -f /var/vcap/jobs/haproxy/config/blocklist_cidrs_tcp.txt tcp-request <%= tcp_request_phase %> reject if layer4_block <%- if_p("ha_proxy.connections_rate_limit.table_size", "ha_proxy.connections_rate_limit.window_size") do -%> + <%- if_p("ha_proxy.connections_rate_limit.exclude_cidrs") do -%> + acl rate_limit_exclude src -f /var/vcap/jobs/haproxy/config/rate_limit_exclusion_cidrs.txt + <%- end -%> tcp-request <%= tcp_request_phase %> track-sc0 src table st_tcp_conn_rate # use sub() converter as variable references are only accepted as arguments to converters - tcp-request <%= tcp_request_phase %> reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 } + tcp-request <%= tcp_request_phase %> reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 }<%= conn_rate_limit_exclude %> <%- end -%> <%- if_p("ha_proxy.requests_rate_limit.table_size", "ha_proxy.requests_rate_limit.window_size") do -%> http-request track-sc1 src table st_http_req_rate diff --git a/jobs/haproxy/templates/rate_limit_exclusion_cidrs.txt.erb b/jobs/haproxy/templates/rate_limit_exclusion_cidrs.txt.erb new file mode 100644 index 00000000..c477c0e0 --- /dev/null +++ b/jobs/haproxy/templates/rate_limit_exclusion_cidrs.txt.erb @@ -0,0 +1,25 @@ +# generated from rate_limit_exclusion_cidrs.txt.erb +<% +require "base64" +require 'zlib' +require 'stringio' + +if_p("ha_proxy.connections_rate_limit.exclude_cidrs") do |cidrs| + uncompressed = '' + if cidrs.is_a?(Array) + uncompressed << "\# detected cidrs provided as array in cleartext format\n" + cidrs.each do |cidr| + uncompressed << cidr << "\n" + end + else + gzplain = Base64.decode64(cidrs) + gz = Zlib::GzipReader.new(StringIO.new(gzplain)) + uncompressed = gz.read + end +%> +# BEGIN rate limit exclusion cidrs +<%= uncompressed %> +# END rate limit exclusion cidrs +<% +end +%> diff --git a/spec/haproxy/templates/haproxy_config/rate_limit_spec.rb b/spec/haproxy/templates/haproxy_config/rate_limit_spec.rb index f041cf90..e4a6a59e 100644 --- a/spec/haproxy/templates/haproxy_config/rate_limit_spec.rb +++ b/spec/haproxy/templates/haproxy_config/rate_limit_spec.rb @@ -113,6 +113,34 @@ expect(haproxy_conf['global']).not_to include('set-var proc.connections_rate_limit_connections') end + it 'does not add the exclusion acl or negate the reject rule when exclude_cidrs is not set' do + expect(frontend_http).not_to include('acl rate_limit_exclude src -f /var/vcap/jobs/haproxy/config/rate_limit_exclusion_cidrs.txt') + expect(frontend_https).not_to include('acl rate_limit_exclude src -f /var/vcap/jobs/haproxy/config/rate_limit_exclusion_cidrs.txt') + expect(frontend_http).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 }') + expect(frontend_https).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 }') + end + + context 'when connections_rate_limit.exclude_cidrs is provided' do + let(:properties) do + temp_properties.deep_merge({ 'connections_rate_limit' => { 'exclude_cidrs' => ['10.0.0.0/8', '192.168.0.0/16'] } }) + end + + it 'adds the exclusion acl to http-in and https-in frontends' do + expect(frontend_http).to include('acl rate_limit_exclude src -f /var/vcap/jobs/haproxy/config/rate_limit_exclusion_cidrs.txt') + expect(frontend_https).to include('acl rate_limit_exclude src -f /var/vcap/jobs/haproxy/config/rate_limit_exclusion_cidrs.txt') + end + + it 'still tracks excluded sources in the stick-table' do + expect(frontend_http).to include('tcp-request connection track-sc0 src table st_tcp_conn_rate') + expect(frontend_https).to include('tcp-request connection track-sc0 src table st_tcp_conn_rate') + end + + it 'negates the reject rule with the exclusion acl so excluded sources are never rejected' do + expect(frontend_http).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 } !rate_limit_exclude') + expect(frontend_https).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 } !rate_limit_exclude') + end + end + context 'when proxy protocol used' do let(:properties) do temp_properties.deep_merge({ 'accept_proxy' => true }) diff --git a/spec/haproxy/templates/rate_limit_exclusion_cidrs.txt_spec.rb b/spec/haproxy/templates/rate_limit_exclusion_cidrs.txt_spec.rb new file mode 100644 index 00000000..b5e53eba --- /dev/null +++ b/spec/haproxy/templates/rate_limit_exclusion_cidrs.txt_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'rspec' + +describe 'config/rate_limit_exclusion_cidrs.txt' do + let(:template) { haproxy_job.template('config/rate_limit_exclusion_cidrs.txt') } + + context 'when ha_proxy.connections_rate_limit.exclude_cidrs is provided' do + context 'when an array of cidrs is provided' do + it 'has the correct contents' do + expect(template.render({ + 'ha_proxy' => { + 'connections_rate_limit' => { + 'exclude_cidrs' => [ + '10.0.0.0/8', + '192.168.2.0/24' + ] + } + } + })).to eq(<<~EXPECTED) + # generated from rate_limit_exclusion_cidrs.txt.erb + + # BEGIN rate limit exclusion cidrs + # detected cidrs provided as array in cleartext format + 10.0.0.0/8 + 192.168.2.0/24 + + # END rate limit exclusion cidrs + + EXPECTED + end + end + + context 'when a base64-encoded, gzipped config is provided' do + it 'has the correct contents' do + expect(template.render({ + 'ha_proxy' => { + 'connections_rate_limit' => { + 'exclude_cidrs' => gzip_and_b64_encode(<<~INPUT) + 10.0.0.0/8 + 192.168.2.0/24 + INPUT + } + } + })).to eq(<<~EXPECTED) + # generated from rate_limit_exclusion_cidrs.txt.erb + + # BEGIN rate limit exclusion cidrs + 10.0.0.0/8 + 192.168.2.0/24 + + # END rate limit exclusion cidrs + + EXPECTED + end + end + end + + context 'when ha_proxy.connections_rate_limit.exclude_cidrs is not provided' do + it 'is empty' do + expect(template.render({})).to be_a_blank_string + end + end +end \ No newline at end of file From cd8112696d80344ab9b7660025c573ef3f2a2476 Mon Sep 17 00:00:00 2001 From: M Rizwan Shaik Date: Fri, 10 Jul 2026 11:19:05 +0200 Subject: [PATCH 2/7] add acceptance test for exclude cidrs list --- acceptance-tests/rate_limit_test.go | 48 +++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/acceptance-tests/rate_limit_test.go b/acceptance-tests/rate_limit_test.go index 2400d173..1d02009c 100644 --- a/acceptance-tests/rate_limit_test.go +++ b/acceptance-tests/rate_limit_test.go @@ -165,6 +165,54 @@ var _ = Describe("Rate-Limiting", func() { Expect(successfulRequestCount).To(Equal(connLimit)) }) + It("Excluded CIDRs are never connection rate-limited", func() { + connLimit := 5 + // exclude_cidrs covers all IPv4 sources so the test runner's egress IP is + // guaranteed to match, proving the negated reject rule lets excluded sources through + opsfileConnRateLimitExclude := fmt.Sprintf(`--- +- type: replace + path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit?/connections + value: %d +- type: replace + path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/window_size? + value: 100s +- type: replace + path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/table_size? + value: 100 +- type: replace + path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/block? + value: true +- type: replace + path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/exclude_cidrs? + value: + - 0.0.0.0/0 +`, connLimit) + haproxyBackendPort := 12000 + haproxyInfo, _ := deployHAProxy(baseManifestVars{ + haproxyBackendPort: haproxyBackendPort, + haproxyBackendServers: []string{"127.0.0.1"}, + deploymentName: deploymentNameForTestNode(), + }, []string{opsfileConnRateLimitExclude}, map[string]interface{}{}, true) + + closeLocalServer, localPort := startDefaultTestServer() + defer closeLocalServer() + + closeTunnel := setupTunnelFromHaproxyToTestServer(haproxyInfo, haproxyBackendPort, localPort) + defer closeTunnel() + + By("Sending more connections than the limit, expecting none to be blocked because the source is excluded") + testRequestCount := connLimit * 3 + for i := 0; i < testRequestCount; i++ { + rt := &http.Transport{ + DisableKeepAlives: true, + } + client := &http.Client{Transport: rt} + resp, err := client.Get(fmt.Sprintf("http://%s/foo", haproxyInfo.PublicIP)) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + } + }) + It("Connection Based Limiting Works with Proxy Protocol enabled", func() { connLimit := 5 opsfileConnRateLimitWithProxyProtocol := fmt.Sprintf(`--- From bb072752936be0291d0e735b533e42577a8ac0c2 Mon Sep 17 00:00:00 2001 From: M Rizwan Shaik Date: Wed, 15 Jul 2026 16:25:01 +0200 Subject: [PATCH 3/7] Introduce default exclude_cidrs to empty array --- jobs/haproxy/templates/haproxy.config.erb | 14 ++-------- .../rate_limit_exclusion_cidrs.txt.erb | 28 ++++++++----------- .../haproxy_config/rate_limit_spec.rb | 28 +++++++++---------- .../rate_limit_exclusion_cidrs.txt_spec.rb | 19 +++++++------ 4 files changed, 38 insertions(+), 51 deletions(-) diff --git a/jobs/haproxy/templates/haproxy.config.erb b/jobs/haproxy/templates/haproxy.config.erb index bc538737..03d334e0 100644 --- a/jobs/haproxy/templates/haproxy.config.erb +++ b/jobs/haproxy/templates/haproxy.config.erb @@ -239,12 +239,6 @@ end end end - # Connections rate-limit exclusion: sources matching exclude_cidrs are tracked but never rejected - conn_rate_limit_exclude = "" - if_p("ha_proxy.connections_rate_limit.exclude_cidrs") do - conn_rate_limit_exclude = " !rate_limit_exclude" - end - backend_servers = [] backend_servers_local = [] backend_port = nil @@ -457,12 +451,10 @@ frontend http-in acl layer4_block src -f /var/vcap/jobs/haproxy/config/blocklist_cidrs_tcp.txt tcp-request <%= tcp_request_phase %> reject if layer4_block <%- if_p("ha_proxy.connections_rate_limit.table_size", "ha_proxy.connections_rate_limit.window_size") do -%> - <%- if_p("ha_proxy.connections_rate_limit.exclude_cidrs") do -%> acl rate_limit_exclude src -f /var/vcap/jobs/haproxy/config/rate_limit_exclusion_cidrs.txt - <%- end -%> tcp-request <%= tcp_request_phase %> track-sc0 src table st_tcp_conn_rate # use sub() converter as variable references are only accepted as arguments to converters - tcp-request <%= tcp_request_phase %> reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 }<%= conn_rate_limit_exclude %> + tcp-request <%= tcp_request_phase %> reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 } !rate_limit_exclude <%- end -%> <%- if_p("ha_proxy.requests_rate_limit.table_size", "ha_proxy.requests_rate_limit.window_size") do -%> http-request track-sc1 src table st_http_req_rate @@ -591,12 +583,10 @@ frontend https-in acl layer4_block src -f /var/vcap/jobs/haproxy/config/blocklist_cidrs_tcp.txt tcp-request <%= tcp_request_phase %> reject if layer4_block <%- if_p("ha_proxy.connections_rate_limit.table_size", "ha_proxy.connections_rate_limit.window_size") do -%> - <%- if_p("ha_proxy.connections_rate_limit.exclude_cidrs") do -%> acl rate_limit_exclude src -f /var/vcap/jobs/haproxy/config/rate_limit_exclusion_cidrs.txt - <%- end -%> tcp-request <%= tcp_request_phase %> track-sc0 src table st_tcp_conn_rate # use sub() converter as variable references are only accepted as arguments to converters - tcp-request <%= tcp_request_phase %> reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 }<%= conn_rate_limit_exclude %> + tcp-request <%= tcp_request_phase %> reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 } !rate_limit_exclude <%- end -%> <%- if_p("ha_proxy.requests_rate_limit.table_size", "ha_proxy.requests_rate_limit.window_size") do -%> http-request track-sc1 src table st_http_req_rate diff --git a/jobs/haproxy/templates/rate_limit_exclusion_cidrs.txt.erb b/jobs/haproxy/templates/rate_limit_exclusion_cidrs.txt.erb index c477c0e0..35911f84 100644 --- a/jobs/haproxy/templates/rate_limit_exclusion_cidrs.txt.erb +++ b/jobs/haproxy/templates/rate_limit_exclusion_cidrs.txt.erb @@ -4,22 +4,18 @@ require "base64" require 'zlib' require 'stringio' -if_p("ha_proxy.connections_rate_limit.exclude_cidrs") do |cidrs| - uncompressed = '' - if cidrs.is_a?(Array) - uncompressed << "\# detected cidrs provided as array in cleartext format\n" - cidrs.each do |cidr| - uncompressed << cidr << "\n" - end - else - gzplain = Base64.decode64(cidrs) - gz = Zlib::GzipReader.new(StringIO.new(gzplain)) - uncompressed = gz.read +cidrs = p("ha_proxy.connections_rate_limit.exclude_cidrs", []) +uncompressed = '' +if cidrs.is_a?(Array) && cidrs.any? + uncompressed << "\# detected cidrs provided as array in cleartext format\n" + cidrs.each do |cidr| + uncompressed << cidr << "\n" end -%> -# BEGIN rate limit exclusion cidrs -<%= uncompressed %> -# END rate limit exclusion cidrs -<% +elsif cidrs.is_a?(String) + gzplain = Base64.decode64(cidrs) + gz = Zlib::GzipReader.new(StringIO.new(gzplain)) + uncompressed = gz.read end %> +# This list contains CIDRs excluded from connection based rate-limiting (tracked but never rejected). +<%= uncompressed %> diff --git a/spec/haproxy/templates/haproxy_config/rate_limit_spec.rb b/spec/haproxy/templates/haproxy_config/rate_limit_spec.rb index e4a6a59e..0d14b1d1 100644 --- a/spec/haproxy/templates/haproxy_config/rate_limit_spec.rb +++ b/spec/haproxy/templates/haproxy_config/rate_limit_spec.rb @@ -104,8 +104,8 @@ end it 'always emits the reject rule (even without connections or block set in manifest)' do - expect(frontend_http).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 }') - expect(frontend_https).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 }') + expect(frontend_http).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 } !rate_limit_exclude') + expect(frontend_https).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 } !rate_limit_exclude') end it 'always sets proc.connections_rate_limit_block to false in global when block is not configured in manifest' do @@ -113,11 +113,11 @@ expect(haproxy_conf['global']).not_to include('set-var proc.connections_rate_limit_connections') end - it 'does not add the exclusion acl or negate the reject rule when exclude_cidrs is not set' do - expect(frontend_http).not_to include('acl rate_limit_exclude src -f /var/vcap/jobs/haproxy/config/rate_limit_exclusion_cidrs.txt') - expect(frontend_https).not_to include('acl rate_limit_exclude src -f /var/vcap/jobs/haproxy/config/rate_limit_exclusion_cidrs.txt') - expect(frontend_http).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 }') - expect(frontend_https).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 }') + it 'always adds the exclusion acl and negates the reject rule (empty exclusion file is a no-op by default)' do + expect(frontend_http).to include('acl rate_limit_exclude src -f /var/vcap/jobs/haproxy/config/rate_limit_exclusion_cidrs.txt') + expect(frontend_https).to include('acl rate_limit_exclude src -f /var/vcap/jobs/haproxy/config/rate_limit_exclusion_cidrs.txt') + expect(frontend_http).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 } !rate_limit_exclude') + expect(frontend_https).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 } !rate_limit_exclude') end context 'when connections_rate_limit.exclude_cidrs is provided' do @@ -130,7 +130,7 @@ expect(frontend_https).to include('acl rate_limit_exclude src -f /var/vcap/jobs/haproxy/config/rate_limit_exclusion_cidrs.txt') end - it 'still tracks excluded sources in the stick-table' do + it 'still emits the track-sc0 directive so excluded sources remain tracked (not skipped)' do expect(frontend_http).to include('tcp-request connection track-sc0 src table st_tcp_conn_rate') expect(frontend_https).to include('tcp-request connection track-sc0 src table st_tcp_conn_rate') end @@ -158,9 +158,9 @@ end it 'adds tcp-request connection reject using process variables to http-in and https-in frontends' do - expect(frontend_http).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 }') + expect(frontend_http).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 } !rate_limit_exclude') expect(frontend_http).to include('tcp-request connection track-sc0 src table st_tcp_conn_rate') - expect(frontend_https).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 }') + expect(frontend_https).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 } !rate_limit_exclude') expect(frontend_https).to include('tcp-request connection track-sc0 src table st_tcp_conn_rate') end end @@ -176,8 +176,8 @@ end it 'still emits reject rule (rejection controlled at runtime via proc.connections_rate_limit_block variable)' do - expect(frontend_http).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 }') - expect(frontend_https).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 }') + expect(frontend_http).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 } !rate_limit_exclude') + expect(frontend_https).to include('tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 } !rate_limit_exclude') end end @@ -197,9 +197,9 @@ end it 'adds tcp-request session reject using process variables to http-in and https-in frontends' do - expect(frontend_http).to include('tcp-request session reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 }') + expect(frontend_http).to include('tcp-request session reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 } !rate_limit_exclude') expect(frontend_http).to include('tcp-request session track-sc0 src table st_tcp_conn_rate') - expect(frontend_https).to include('tcp-request session reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 }') + expect(frontend_https).to include('tcp-request session reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 } !rate_limit_exclude') expect(frontend_https).to include('tcp-request session track-sc0 src table st_tcp_conn_rate') end end diff --git a/spec/haproxy/templates/rate_limit_exclusion_cidrs.txt_spec.rb b/spec/haproxy/templates/rate_limit_exclusion_cidrs.txt_spec.rb index b5e53eba..62284e47 100644 --- a/spec/haproxy/templates/rate_limit_exclusion_cidrs.txt_spec.rb +++ b/spec/haproxy/templates/rate_limit_exclusion_cidrs.txt_spec.rb @@ -20,13 +20,11 @@ })).to eq(<<~EXPECTED) # generated from rate_limit_exclusion_cidrs.txt.erb - # BEGIN rate limit exclusion cidrs + # This list contains CIDRs excluded from connection based rate-limiting (tracked but never rejected). # detected cidrs provided as array in cleartext format 10.0.0.0/8 192.168.2.0/24 - # END rate limit exclusion cidrs - EXPECTED end end @@ -45,20 +43,23 @@ })).to eq(<<~EXPECTED) # generated from rate_limit_exclusion_cidrs.txt.erb - # BEGIN rate limit exclusion cidrs + # This list contains CIDRs excluded from connection based rate-limiting (tracked but never rejected). 10.0.0.0/8 192.168.2.0/24 - # END rate limit exclusion cidrs - EXPECTED end end end context 'when ha_proxy.connections_rate_limit.exclude_cidrs is not provided' do - it 'is empty' do - expect(template.render({})).to be_a_blank_string + it 'renders only the header comment (empty exclusion list is a no-op)' do + expect(template.render({})).to eq(<<~EXPECTED) + # generated from rate_limit_exclusion_cidrs.txt.erb + + # This list contains CIDRs excluded from connection based rate-limiting (tracked but never rejected). + + EXPECTED end end -end \ No newline at end of file +end From 0f83611e1246045b3da66da436157df3451e7558 Mon Sep 17 00:00:00 2001 From: M Rizwan Shaik Date: Wed, 22 Jul 2026 13:36:02 +0200 Subject: [PATCH 4/7] Refactor rate_limit_test opsfiles into reusable helpers --- acceptance-tests/rate_limit_test.go | 194 ++++++++++------------------ 1 file changed, 65 insertions(+), 129 deletions(-) diff --git a/acceptance-tests/rate_limit_test.go b/acceptance-tests/rate_limit_test.go index 1d02009c..a81b4727 100644 --- a/acceptance-tests/rate_limit_test.go +++ b/acceptance-tests/rate_limit_test.go @@ -3,6 +3,7 @@ package acceptance_tests import ( "fmt" "net/http" + "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -11,32 +12,15 @@ import ( var _ = Describe("Rate-Limiting", func() { It("Connections/Requests aren't blocked when block config isn't set", func() { rateLimit := 5 - opsfileConnectionsRateLimit := fmt.Sprintf(`--- -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit?/requests - value: %d -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit/window_size? - value: 10s -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit/table_size? - value: 1k -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit?/connections - value: %d -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/window_size? - value: 10s -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/table_size? - value: 1k -`, rateLimit, rateLimit) haproxyBackendPort := 12000 haproxyInfo, _ := deployHAProxy(baseManifestVars{ haproxyBackendPort: haproxyBackendPort, haproxyBackendServers: []string{"127.0.0.1"}, deploymentName: deploymentNameForTestNode(), - }, []string{opsfileConnectionsRateLimit}, map[string]interface{}{}, true) + }, []string{ + requestsRateLimitOps(rateLimit, "10s", "1k", false), + connectionsRateLimitOps(rateLimit, "10s", "1k", false, nil), + }, map[string]interface{}{}, true) closeLocalServer, localPort := startDefaultTestServer() defer closeLocalServer() @@ -60,27 +44,13 @@ var _ = Describe("Rate-Limiting", func() { It("Request Based Limiting Works", func() { requestLimit := 5 - opsfileRequestRateLimit := fmt.Sprintf(`--- -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit?/requests - value: %d -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit/window_size? - value: 10s -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit/table_size? - value: 100 -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit/block? - value: true -`, requestLimit) haproxyBackendPort := 12000 haproxyInfo, _ := deployHAProxy(baseManifestVars{ haproxyBackendPort: haproxyBackendPort, haproxyBackendServers: []string{"127.0.0.1"}, deploymentName: deploymentNameForTestNode(), - }, []string{opsfileRequestRateLimit}, map[string]interface{}{}, true) + }, []string{requestsRateLimitOps(requestLimit, "10s", "100", true)}, map[string]interface{}{}, true) closeLocalServer, localPort := startDefaultTestServer() defer closeLocalServer() @@ -112,26 +82,12 @@ var _ = Describe("Rate-Limiting", func() { It("Connection Based Limiting Works", func() { connLimit := 5 - opsfileConnectionsRateLimit := fmt.Sprintf(`--- -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit?/connections - value: %d -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/window_size? - value: 10s -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/table_size? - value: 1k -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/block? - value: true -`, connLimit) haproxyBackendPort := 12000 haproxyInfo, _ := deployHAProxy(baseManifestVars{ haproxyBackendPort: haproxyBackendPort, haproxyBackendServers: []string{"127.0.0.1"}, deploymentName: deploymentNameForTestNode(), - }, []string{opsfileConnectionsRateLimit}, map[string]interface{}{}, true) + }, []string{connectionsRateLimitOps(connLimit, "10s", "1k", true, nil)}, map[string]interface{}{}, true) closeLocalServer, localPort := startDefaultTestServer() defer closeLocalServer() @@ -169,30 +125,12 @@ var _ = Describe("Rate-Limiting", func() { connLimit := 5 // exclude_cidrs covers all IPv4 sources so the test runner's egress IP is // guaranteed to match, proving the negated reject rule lets excluded sources through - opsfileConnRateLimitExclude := fmt.Sprintf(`--- -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit?/connections - value: %d -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/window_size? - value: 100s -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/table_size? - value: 100 -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/block? - value: true -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/exclude_cidrs? - value: - - 0.0.0.0/0 -`, connLimit) haproxyBackendPort := 12000 haproxyInfo, _ := deployHAProxy(baseManifestVars{ haproxyBackendPort: haproxyBackendPort, haproxyBackendServers: []string{"127.0.0.1"}, deploymentName: deploymentNameForTestNode(), - }, []string{opsfileConnRateLimitExclude}, map[string]interface{}{}, true) + }, []string{connectionsRateLimitOps(connLimit, "100s", "100", true, []string{"0.0.0.0/0"})}, map[string]interface{}{}, true) closeLocalServer, localPort := startDefaultTestServer() defer closeLocalServer() @@ -215,31 +153,21 @@ var _ = Describe("Rate-Limiting", func() { It("Connection Based Limiting Works with Proxy Protocol enabled", func() { connLimit := 5 - opsfileConnRateLimitWithProxyProtocol := fmt.Sprintf(`--- -# Enable Proxy Protocol -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/accept_proxy? - value: true -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit?/connections - value: %d -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/window_size? - value: 100s -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/table_size? - value: 100 + opsfileAcceptProxy := fmt.Sprintf(`--- - type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/block? + path: %s/accept_proxy? value: true -`, connLimit) +`, rateLimitPropsPath) haproxyBackendPort := 12000 haproxyInfo, _ := deployHAProxy(baseManifestVars{ haproxyBackendPort: haproxyBackendPort, haproxyBackendServers: []string{"127.0.0.1"}, deploymentName: deploymentNameForTestNode(), - }, []string{opsfileConnRateLimitWithProxyProtocol}, map[string]interface{}{}, true) + }, []string{ + opsfileAcceptProxy, + connectionsRateLimitOps(connLimit, "100s", "100", true, nil), + }, map[string]interface{}{}, true) closeLocalServer, localPort := startDefaultTestServer() defer closeLocalServer() @@ -272,38 +200,15 @@ var _ = Describe("Rate-Limiting", func() { requestLimit := 5 connLimit := 6 // needs to be higher than request limit for this test // connection based rate-limiting has priority over request based rate-limiting so we expect some sucesses, then one status 429 response, then no response at all - opsfileConnectionsRateLimit := fmt.Sprintf(`--- -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit?/requests - value: %d -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit/window_size? - value: 10s -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit/table_size? - value: 100 -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit/block? - value: true -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit?/connections - value: %d -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/window_size? - value: 100s -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/table_size? - value: 100 -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/block? - value: true -`, requestLimit, connLimit) haproxyBackendPort := 12000 haproxyInfo, _ := deployHAProxy(baseManifestVars{ haproxyBackendPort: haproxyBackendPort, haproxyBackendServers: []string{"127.0.0.1"}, deploymentName: deploymentNameForTestNode(), - }, []string{opsfileConnectionsRateLimit}, map[string]interface{}{}, true) + }, []string{ + requestsRateLimitOps(requestLimit, "10s", "100", true), + connectionsRateLimitOps(connLimit, "100s", "100", true, nil), + }, map[string]interface{}{}, true) closeLocalServer, localPort := startDefaultTestServer() defer closeLocalServer() @@ -336,26 +241,12 @@ var _ = Describe("Rate-Limiting", func() { It("Connection Based Limiting works via manifest and can be overridden at runtime via socket", func() { connLimit := 5 - opsfileConnectionsRateLimit := fmt.Sprintf(`--- -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit?/connections - value: %d -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/window_size? - value: 10s -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/table_size? - value: 100 -- type: replace - path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/block? - value: true -`, connLimit) haproxyBackendPort := 12000 haproxyInfo, _ := deployHAProxy(baseManifestVars{ haproxyBackendPort: haproxyBackendPort, haproxyBackendServers: []string{"127.0.0.1"}, deploymentName: deploymentNameForTestNode(), - }, []string{opsfileConnectionsRateLimit}, map[string]interface{}{}, true) + }, []string{connectionsRateLimitOps(connLimit, "10s", "100", true, nil)}, map[string]interface{}{}, true) closeLocalServer, localPort := startDefaultTestServer() defer closeLocalServer() @@ -429,3 +320,48 @@ var _ = Describe("Rate-Limiting", func() { Expect(successfulRequestCount).To(Equal(newLimit)) }) }) + +const rateLimitPropsPath = "/instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy" + +// requestsRateLimitOps builds an opsfile fragment configuring the requests_rate_limit properties. +func requestsRateLimitOps(requests int, windowSize, tableSize string, block bool) string { + return fmt.Sprintf(`--- +- type: replace + path: %[1]s/requests_rate_limit?/requests + value: %[2]d +- type: replace + path: %[1]s/requests_rate_limit/window_size? + value: %[3]s +- type: replace + path: %[1]s/requests_rate_limit/table_size? + value: %[4]s +- type: replace + path: %[1]s/requests_rate_limit/block? + value: %[5]t +`, rateLimitPropsPath, requests, windowSize, tableSize, block) +} + +// connectionsRateLimitOps builds an opsfile fragment configuring the connections_rate_limit properties. +func connectionsRateLimitOps(connections int, windowSize, tableSize string, block bool, excludeCIDRs []string) string { + quotedCIDRs := make([]string, len(excludeCIDRs)) + for i, cidr := range excludeCIDRs { + quotedCIDRs[i] = fmt.Sprintf("%q", cidr) + } + return fmt.Sprintf(`--- +- type: replace + path: %[1]s/connections_rate_limit?/connections + value: %[2]d +- type: replace + path: %[1]s/connections_rate_limit/window_size? + value: %[3]s +- type: replace + path: %[1]s/connections_rate_limit/table_size? + value: %[4]s +- type: replace + path: %[1]s/connections_rate_limit/block? + value: %[5]t +- type: replace + path: %[1]s/connections_rate_limit/exclude_cidrs? + value: [%[6]s] +`, rateLimitPropsPath, connections, windowSize, tableSize, block, strings.Join(quotedCIDRs, ", ")) +} From 4c3a04c816fdf6c0acd380886819d0ff211b4251 Mon Sep 17 00:00:00 2001 From: M Rizwan Shaik Date: Wed, 22 Jul 2026 13:37:34 +0200 Subject: [PATCH 5/7] Document exclude_cidrs for connection rate limiting --- docs/rate_limiting.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/rate_limiting.md b/docs/rate_limiting.md index 2dd4265d..57e2d71e 100644 --- a/docs/rate_limiting.md +++ b/docs/rate_limiting.md @@ -30,6 +30,40 @@ This will not result in a log statement on the HAProxy side, which can make trac > If both rate-limits are reached simultaneously (e.g. if they are configured identically and every incoming HTTP request uses a new TCP connection), connection based rate-limiting will come into effect first, resulting in a dropped TCP connection. +## Excluding CIDRs from Connection Rate Limiting +Some sources should never be rate-limited on connections — for example internal/NAT ranges, health checkers, or trusted peers whose traffic all appears to originate from a small set of IPs. `connections_rate_limit.exclude_cidrs` takes a list of CIDRs that are exempt from connection based rate limiting. + +Excluded sources are **still tracked** in the `st_tcp_conn_rate` stick-table (so they remain visible via `show table st_tcp_conn_rate`), but they are **never rejected**, regardless of the configured threshold or the runtime `block` setting. + +The list is rendered to `/var/vcap/jobs/haproxy/config/rate_limit_exclusion_cidrs.txt` and referenced by an ACL that negates the reject rule on both the `http-in` and `https-in` frontends. + +#### Configuration +```yml +config: + # [...] + connections_rate_limit: + connections: 10 + window_size: 10s + table_size: 1m + block: true + exclude_cidrs: + - 10.0.0.0/8 + - 192.168.0.0/16 + - 2001:db8::/32 +``` + +The value may also be provided as a single base64-encoded, gzipped string (useful for very long lists), matching the format accepted by `cidr_whitelist` and `cidr_blocklist_tcp`. + +#### Resulting `haproxy.config` +```ini +frontend http-in + # [...] + acl rate_limit_exclude src -f /var/vcap/jobs/haproxy/config/rate_limit_exclusion_cidrs.txt + tcp-request connection track-sc0 src table st_tcp_conn_rate + tcp-request connection reject if { var(proc.connections_rate_limit_block) -m bool } { var(proc.connections_rate_limit_connections) -m int gt 0 } { sc_conn_rate(0),sub(proc.connections_rate_limit_connections) gt 0 } !rate_limit_exclude +``` + + ## Configuration Examples > Note: > The following examples assume only an `http-in` frontend is configured; an `https-in` frontend would behave identically. From f5e21eb1914c127135b19ec4b4265c11b04bb824 Mon Sep 17 00:00:00 2001 From: M Rizwan Shaik Date: Thu, 23 Jul 2026 09:23:22 +0200 Subject: [PATCH 6/7] Adress review comments --- acceptance-tests/rate_limit_test.go | 48 ++++++++++++++--------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/acceptance-tests/rate_limit_test.go b/acceptance-tests/rate_limit_test.go index a81b4727..c6ab4548 100644 --- a/acceptance-tests/rate_limit_test.go +++ b/acceptance-tests/rate_limit_test.go @@ -153,11 +153,11 @@ var _ = Describe("Rate-Limiting", func() { It("Connection Based Limiting Works with Proxy Protocol enabled", func() { connLimit := 5 - opsfileAcceptProxy := fmt.Sprintf(`--- + opsfileAcceptProxy := `--- - type: replace - path: %s/accept_proxy? + path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/accept_proxy? value: true -`, rateLimitPropsPath) +` haproxyBackendPort := 12000 haproxyInfo, _ := deployHAProxy(baseManifestVars{ @@ -321,24 +321,22 @@ var _ = Describe("Rate-Limiting", func() { }) }) -const rateLimitPropsPath = "/instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy" - // requestsRateLimitOps builds an opsfile fragment configuring the requests_rate_limit properties. func requestsRateLimitOps(requests int, windowSize, tableSize string, block bool) string { return fmt.Sprintf(`--- - type: replace - path: %[1]s/requests_rate_limit?/requests - value: %[2]d + path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit?/requests + value: %d - type: replace - path: %[1]s/requests_rate_limit/window_size? - value: %[3]s + path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit/window_size? + value: %s - type: replace - path: %[1]s/requests_rate_limit/table_size? - value: %[4]s + path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit/table_size? + value: %s - type: replace - path: %[1]s/requests_rate_limit/block? - value: %[5]t -`, rateLimitPropsPath, requests, windowSize, tableSize, block) + path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit/block? + value: %t +`, requests, windowSize, tableSize, block) } // connectionsRateLimitOps builds an opsfile fragment configuring the connections_rate_limit properties. @@ -349,19 +347,19 @@ func connectionsRateLimitOps(connections int, windowSize, tableSize string, bloc } return fmt.Sprintf(`--- - type: replace - path: %[1]s/connections_rate_limit?/connections - value: %[2]d + path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit?/connections + value: %d - type: replace - path: %[1]s/connections_rate_limit/window_size? - value: %[3]s + path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/window_size? + value: %s - type: replace - path: %[1]s/connections_rate_limit/table_size? - value: %[4]s + path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/table_size? + value: %s - type: replace - path: %[1]s/connections_rate_limit/block? - value: %[5]t + path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/block? + value: %t - type: replace - path: %[1]s/connections_rate_limit/exclude_cidrs? - value: [%[6]s] -`, rateLimitPropsPath, connections, windowSize, tableSize, block, strings.Join(quotedCIDRs, ", ")) + path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/exclude_cidrs? + value: [%s] +`, connections, windowSize, tableSize, block, strings.Join(quotedCIDRs, ", ")) } From 1d917c6aa4cda5defa088db27c61646baccc1705 Mon Sep 17 00:00:00 2001 From: M Rizwan Shaik Date: Thu, 23 Jul 2026 09:49:19 +0200 Subject: [PATCH 7/7] add hard coded values for window size and table size --- acceptance-tests/rate_limit_test.go | 34 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/acceptance-tests/rate_limit_test.go b/acceptance-tests/rate_limit_test.go index c6ab4548..53ac6a67 100644 --- a/acceptance-tests/rate_limit_test.go +++ b/acceptance-tests/rate_limit_test.go @@ -18,8 +18,8 @@ var _ = Describe("Rate-Limiting", func() { haproxyBackendServers: []string{"127.0.0.1"}, deploymentName: deploymentNameForTestNode(), }, []string{ - requestsRateLimitOps(rateLimit, "10s", "1k", false), - connectionsRateLimitOps(rateLimit, "10s", "1k", false, nil), + requestsRateLimitOps(rateLimit, false), + connectionsRateLimitOps(rateLimit, false, nil), }, map[string]interface{}{}, true) closeLocalServer, localPort := startDefaultTestServer() @@ -50,7 +50,7 @@ var _ = Describe("Rate-Limiting", func() { haproxyBackendPort: haproxyBackendPort, haproxyBackendServers: []string{"127.0.0.1"}, deploymentName: deploymentNameForTestNode(), - }, []string{requestsRateLimitOps(requestLimit, "10s", "100", true)}, map[string]interface{}{}, true) + }, []string{requestsRateLimitOps(requestLimit, true)}, map[string]interface{}{}, true) closeLocalServer, localPort := startDefaultTestServer() defer closeLocalServer() @@ -87,7 +87,7 @@ var _ = Describe("Rate-Limiting", func() { haproxyBackendPort: haproxyBackendPort, haproxyBackendServers: []string{"127.0.0.1"}, deploymentName: deploymentNameForTestNode(), - }, []string{connectionsRateLimitOps(connLimit, "10s", "1k", true, nil)}, map[string]interface{}{}, true) + }, []string{connectionsRateLimitOps(connLimit, true, nil)}, map[string]interface{}{}, true) closeLocalServer, localPort := startDefaultTestServer() defer closeLocalServer() @@ -130,7 +130,7 @@ var _ = Describe("Rate-Limiting", func() { haproxyBackendPort: haproxyBackendPort, haproxyBackendServers: []string{"127.0.0.1"}, deploymentName: deploymentNameForTestNode(), - }, []string{connectionsRateLimitOps(connLimit, "100s", "100", true, []string{"0.0.0.0/0"})}, map[string]interface{}{}, true) + }, []string{connectionsRateLimitOps(connLimit, true, []string{"0.0.0.0/0"})}, map[string]interface{}{}, true) closeLocalServer, localPort := startDefaultTestServer() defer closeLocalServer() @@ -166,7 +166,7 @@ var _ = Describe("Rate-Limiting", func() { deploymentName: deploymentNameForTestNode(), }, []string{ opsfileAcceptProxy, - connectionsRateLimitOps(connLimit, "100s", "100", true, nil), + connectionsRateLimitOps(connLimit, true, nil), }, map[string]interface{}{}, true) closeLocalServer, localPort := startDefaultTestServer() @@ -206,8 +206,8 @@ var _ = Describe("Rate-Limiting", func() { haproxyBackendServers: []string{"127.0.0.1"}, deploymentName: deploymentNameForTestNode(), }, []string{ - requestsRateLimitOps(requestLimit, "10s", "100", true), - connectionsRateLimitOps(connLimit, "100s", "100", true, nil), + requestsRateLimitOps(requestLimit, true), + connectionsRateLimitOps(connLimit, true, nil), }, map[string]interface{}{}, true) closeLocalServer, localPort := startDefaultTestServer() @@ -246,7 +246,7 @@ var _ = Describe("Rate-Limiting", func() { haproxyBackendPort: haproxyBackendPort, haproxyBackendServers: []string{"127.0.0.1"}, deploymentName: deploymentNameForTestNode(), - }, []string{connectionsRateLimitOps(connLimit, "10s", "100", true, nil)}, map[string]interface{}{}, true) + }, []string{connectionsRateLimitOps(connLimit, true, nil)}, map[string]interface{}{}, true) closeLocalServer, localPort := startDefaultTestServer() defer closeLocalServer() @@ -322,25 +322,25 @@ var _ = Describe("Rate-Limiting", func() { }) // requestsRateLimitOps builds an opsfile fragment configuring the requests_rate_limit properties. -func requestsRateLimitOps(requests int, windowSize, tableSize string, block bool) string { +func requestsRateLimitOps(requests int, block bool) string { return fmt.Sprintf(`--- - type: replace path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit?/requests value: %d - type: replace path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit/window_size? - value: %s + value: 10s - type: replace path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit/table_size? - value: %s + value: 1k - type: replace path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/requests_rate_limit/block? value: %t -`, requests, windowSize, tableSize, block) +`, requests, block) } // connectionsRateLimitOps builds an opsfile fragment configuring the connections_rate_limit properties. -func connectionsRateLimitOps(connections int, windowSize, tableSize string, block bool, excludeCIDRs []string) string { +func connectionsRateLimitOps(connections int, block bool, excludeCIDRs []string) string { quotedCIDRs := make([]string, len(excludeCIDRs)) for i, cidr := range excludeCIDRs { quotedCIDRs[i] = fmt.Sprintf("%q", cidr) @@ -351,15 +351,15 @@ func connectionsRateLimitOps(connections int, windowSize, tableSize string, bloc value: %d - type: replace path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/window_size? - value: %s + value: 10s - type: replace path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/table_size? - value: %s + value: 1k - type: replace path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/block? value: %t - type: replace path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/connections_rate_limit/exclude_cidrs? value: [%s] -`, connections, windowSize, tableSize, block, strings.Join(quotedCIDRs, ", ")) +`, connections, block, strings.Join(quotedCIDRs, ", ")) }