Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions jobs/haproxy/spec
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ templates:
blacklist_cidrs.txt.erb: config/blacklist_cidrs.txt
whitelist_cidrs.txt.erb: config/whitelist_cidrs.txt
expect_proxy_cidrs.txt.erb: config/expect_proxy_cidrs.txt
cidrs_to_exclude_from_blocking.txt.erb: config/cidrs_to_exclude_from_blocking.txt
trusted_domain_cidrs.txt.erb: config/trusted_domain_cidrs.txt

consumes:
Expand Down Expand Up @@ -816,6 +817,13 @@ properties:
description: Window size for counting connections. See docs/rate_limiting.md
ha_proxy.connections_rate_limit.table_size:
description: Size of the stick table in which the IPs and counters are stored. See docs/rate_limiting.md
ha_proxy.connections_rate_limit.cidrs_to_exclude:
description: List of CIDRs to exclude during connection rate limiting. Format is a string array of CIDRs or a single string of base64 encoded gzip.
default: ~
example:
cidrs_to_exclude:
- 10.0.0.0/8
- 192.168.2.0/32
ha_proxy.connections_rate_limit.block:
description: Whether or not to block connections. See docs/rate_limiting.md
default: false
25 changes: 25 additions & 0 deletions jobs/haproxy/templates/cidrs_to_exclude_from_blocking.txt.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# generated from cidrs_to_exclude_from_blocking.txt.erb
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to rename the file to cidrs_to_exclude_from_rate_limiting.txt to show that it is connected to rate limiting.

<%
require "base64"
require 'zlib'
require 'stringio'

if_p("ha_proxy.connections_rate_limit.cidrs_to_exclude") 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 cidrs to exclude from tcp rejection because of connection rate limiting
<%= uncompressed %>
# END cidrs to exclude from tcp rejection because of connection rate limiting
<%
end
%>
6 changes: 4 additions & 2 deletions jobs/haproxy/templates/haproxy.config.erb
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,8 @@ frontend http-in
tcp-request connection track-sc0 src table st_tcp_conn_rate
<%- if_p("ha_proxy.connections_rate_limit.block", "ha_proxy.connections_rate_limit.connections") do |block, connections| -%>
<%-if block -%>
tcp-request connection reject if { sc_conn_rate(0) gt <%= connections %> }
acl cidr_list_to_exclude src -f /var/vcap/jobs/haproxy/config/cidrs_to_exclude_from_blocking.txt
tcp-request connection reject if { sc_conn_rate(0) gt <%= connections %> } !cidr_list_to_exclude
<%- end -%>
<%- end -%>
<%- end -%>
Expand Down Expand Up @@ -564,7 +565,8 @@ frontend https-in
tcp-request connection track-sc0 src table st_tcp_conn_rate
<%- if_p("ha_proxy.connections_rate_limit.block", "ha_proxy.connections_rate_limit.connections") do |block, connections| -%>
<%-if block -%>
tcp-request connection reject if { sc_conn_rate(0) gt <%= connections %> }
acl cidr_list_to_exclude src -f /var/vcap/jobs/haproxy/config/cidrs_to_exclude_from_blocking.txt
tcp-request connection reject if { sc_conn_rate(0) gt <%= connections %> } !cidr_list_to_exclude
<%- end -%>
<%- end -%>
<%- end -%>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# frozen_string_literal: true

require 'rspec'

describe 'config/cidrs_to_exclude_from_blocking.txt' do
let(:template) { haproxy_job.template('config/cidrs_to_exclude_from_blocking.txt') }

context 'when ha_proxy.connections_rate_limit.cidrs_to_exclude 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' => {
'window_size' => '10s',
'table_size' => '10m',
'cidrs_to_exclude' => ['10.0.0.0/8', '3.22.12.3/32']
}
}
})).to eq(<<~EXPECTED)
# generated from cidrs_to_exclude_from_blocking.txt.erb

# BEGIN cidrs to exclude from tcp rejection because of connection rate limiting
# detected cidrs provided as array in cleartext format
10.0.0.0/8
3.22.12.3/32

# END cidrs to exclude from tcp rejection because of connection rate limiting

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' => {
'cidrs_to_exclude' => gzip_and_b64_encode(<<~INPUT)
10.0.0.0/8
3.22.12.3/32
INPUT
}
}
})).to eq(<<~EXPECTED)
# generated from cidrs_to_exclude_from_blocking.txt.erb

# BEGIN cidrs to exclude from tcp rejection because of connection rate limiting
10.0.0.0/8
3.22.12.3/32

# END cidrs to exclude from tcp rejection because of connection rate limiting

EXPECTED
end
end
end

context 'when ha_proxy.connections_rate_limit.cidrs_to_exclude is not provided' do
it 'is empty' do
expect(template.render({})).to be_a_blank_string
end
end
end
27 changes: 24 additions & 3 deletions spec/haproxy/templates/haproxy_config/rate_limit_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,31 @@
temp_properties.deep_merge({ 'connections_rate_limit' => { 'connections' => '5', 'block' => 'true' } })
end

it 'adds http-request deny condition to http-in and https-in frontends' do
expect(frontend_http).to include('tcp-request connection reject if { sc_conn_rate(0) gt 5 }')
it 'adds tcp-request connection reject condition to http-in and https-in frontends' do
expect(frontend_http).to include('tcp-request connection reject if { sc_conn_rate(0) gt 5 } !cidr_list_to_exclude')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The acl check is missing here as the acl will exist but the file will be empty

expect(frontend_http).to include('acl cidr_list_to_exclude src -f /var/vcap/jobs/haproxy/config/cidrs_to_exclude_from_blocking.txt')

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 { sc_conn_rate(0) gt 5 } !cidr_list_to_exclude')
expect(frontend_https).to include('tcp-request connection track-sc0 src table st_tcp_conn_rate')
end
end
context 'when "connections", "block" and "cidrs_to_exclude" are also provided' do
let(:properties) do
temp_properties.deep_merge(
{ 'connections_rate_limit' =>
{
'connections' => '5',
'block' => 'true',
'cidrs_to_exclude' => ['10.0.0.0/8', '3.22.12.3/32'],
}
})
end

it 'adds tcp-request connection reject condition to http-in and https-in frontends' do
expect(frontend_http).to include('acl cidr_list_to_exclude src -f /var/vcap/jobs/haproxy/config/cidrs_to_exclude_from_blocking.txt')
expect(frontend_http).to include('tcp-request connection reject if { sc_conn_rate(0) gt 5 } !cidr_list_to_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 { sc_conn_rate(0) gt 5 }')
expect(frontend_https).to include('acl cidr_list_to_exclude src -f /var/vcap/jobs/haproxy/config/cidrs_to_exclude_from_blocking.txt')
expect(frontend_https).to include('tcp-request connection reject if { sc_conn_rate(0) gt 5 } !cidr_list_to_exclude')
expect(frontend_https).to include('tcp-request connection track-sc0 src table st_tcp_conn_rate')
end
end
Expand Down