diff --git a/jobs/haproxy/spec b/jobs/haproxy/spec index 0df72fe7..3f719d5b 100644 --- a/jobs/haproxy/spec +++ b/jobs/haproxy/spec @@ -39,6 +39,28 @@ consumes: optional: true properties: + ha_proxy.pre_start_script: + description: | + This script will be appended to the pre-start script and run before the job starts. + The pre-start script allows the job to prepare the machine before starting HAProxy, + for example, by setting the MTU to a custom value. + default: ~ + example: | + # customize MTU + CUST_MTU=((custom_mtu)) + INTERFACE=$(ip -4 route get 8.8.8.8 | grep -Po '(?<=dev )\S+') #' + CURR_MTU=$(ip link show $INTERFACE | grep -Po 'mtu \K\d+') + if [[ $CURR_MTU -ne $CUST_MTU ]]; then + sudo ip link set dev $INTERFACE mtu $CUST_MTU + echo "MTU changed from $CURR_MTU to $CUST_MTU, interface: $INTERFACE." + fi + CURR_MTU=$(ip link show $INTERFACE | grep -Po 'mtu \K\d+') + echo "MTU: $CURR_MTU, interface: $INTERFACE" + ha_proxy.config_mode: + description: | + 'auto' - utilizes raw_config if defined; otherwise, it uses traditional configuration mixed with raw_blocks, + 'raw_blocks_only' - uses only raw_blocks, ignoring other configurations. + default: auto ha_proxy.nbthread: description: "Optional number of threads per VM" default: 1 @@ -237,21 +259,32 @@ properties: ha_proxy.default_dh_param: default: 2048 description: "Maximum size of DH params when generating epmehmeral keys during key exchange" + ha_proxy.ssl_min_ver: + example: "TLSv1.2" + description: | + This option enforces the use of 'version' or higher for SSL connections initiated from this listener. + The recommended value is 'TLSv1.2'. It is not the default due to backward compatibility concerns with + the 'disable_tls_*' options. If this option is set, the 'disable_tls_*' options will be ignored. + ha_proxy.ssl_max_ver: + example: "TLSv1.3" + description: | + This option enforces the use of 'version' or lower for SSL connections initiated from this listener. + It will only be set if 'ssl_min_ver' is specified, as the default HAProxy ssl-min-ver may change in future. ha_proxy.disable_tls_tickets: default: true - description: "Improve (Perfect) Forward Secrecy by disabling TLS tickets" + description: "Improve (Perfect) Forward Secrecy by disabling TLS tickets. Use 'ssl_min_ver' and 'ssl_max_ver' instead." ha_proxy.disable_tls_10: default: false - description: "Disable TLS 1.0 in HA Proxy" + description: "Disable TLS 1.0 in HA Proxy. Use 'ssl_min_ver' and 'ssl_max_ver' instead." ha_proxy.disable_tls_11: default: false - description: "Disable TLS 1.1 in HA Proxy" + description: "Disable TLS 1.1 in HA Proxy. Use 'ssl_min_ver' and 'ssl_max_ver' instead." ha_proxy.disable_tls_12: default: false - description: "Disable TLS 1.2 in HA Proxy" + description: "Disable TLS 1.2 in HA Proxy. Use 'ssl_min_ver' and 'ssl_max_ver' instead." ha_proxy.disable_tls_13: default: false - description: "Disable TLS 1.3 in HA Proxy" + description: "Disable TLS 1.3 in HA Proxy. Use 'ssl_min_ver' and 'ssl_max_ver' instead." ha_proxy.backend_match_http_protocol: default: false description: Uses the same version of HTTP for backend connections that was used for frontend connections (ie HTTP 1.1 or HTTP 2). Ignores the value of enable_http2. HTTP2 backend connections require that `ha_proxy.backend_ssl` is not `off`. @@ -673,11 +706,15 @@ properties: you want. Use at your own risk. ha_proxy.raw_blocks: description: | - A hash of block types, where each type contains a hash of specific block names with their respective configurations. + A hash of block types, where each type contains either a configuration + or a hash of specific block names with their respective configurations. The configurations are provided as either multiline text blobs or arrays of lines. This structure will be appended to the end of the HAProxy configuration file. Use at your own risk. example: + defaults: | + log global + timeout http-request 10s listen: my-listen-x: | bind :81 @@ -687,20 +724,6 @@ properties: - bind :82 - mode http - server-template srv 1-3 q-s0.web.default.deployment-y.bosh:8080 check inter 1000 - frontend: - my-frontend-x: | - bind :83 - use_backend my-backend-x if { hdr(host) -i x.example.com } - my-frontend-y: - - bind :84 - - use_backend my-backend-y if { hdr(host) -i y.example.com } - backend: - my-backend-x: | - mode http - server-template srv-x 1-3 q-s0.web.default.deployment-x.bosh:8080 check inter 1000 - my-backend-y: - - mode http - - server-template srv-y 1-3 q-s0.web.default.deployment-y.bosh:8080 check inter 1000 ha_proxy.max_open_files: description: The number of file descriptors HAProxy can have open at one time diff --git a/jobs/haproxy/templates/haproxy.config.erb b/jobs/haproxy/templates/haproxy.config.erb index 9018b04e..2bac865a 100644 --- a/jobs/haproxy/templates/haproxy.config.erb +++ b/jobs/haproxy/templates/haproxy.config.erb @@ -17,7 +17,8 @@ end end - if properties.ha_proxy.raw_config -%> + if properties.ha_proxy.config_mode == "auto" + if properties.ha_proxy.raw_config -%> <%= p("ha_proxy.raw_config") %> <%- else -%> <%- @@ -26,8 +27,8 @@ require "digest" # Stats Binding Variables {{{ stat = p("ha_proxy.stats_bind").split(':') -stat_prefix = stat[0] + ":"; -stat_port = stat[1].to_i; +stat_prefix = stat[0] + ":" +stat_port = stat[1].to_i # }}} # Accept Proxy {{{ accept_proxy = "" @@ -40,21 +41,33 @@ if p("ha_proxy.disable_tcp_accept_proxy") end # }}} # Global SSL Flags {{{ -ssl_flags = "no-sslv3" -if p("ha_proxy.disable_tls_10") - ssl_flags = "#{ssl_flags} no-tlsv10" -end -if p("ha_proxy.disable_tls_11") - ssl_flags = "#{ssl_flags} no-tlsv11" -end -if p("ha_proxy.disable_tls_12") - ssl_flags = "#{ssl_flags} no-tlsv12" -end -if p("ha_proxy.disable_tls_13") - ssl_flags = "#{ssl_flags} no-tlsv13" +ssl_flags = "" +use_disable_ssl = true +if_p("ha_proxy.ssl_min_ver") do |ssl_min_ver| + use_disable_ssl = false + ssl_flags = "ssl-min-ver #{ssl_min_ver}" + if_p("ha_proxy.ssl_max_ver") do |ssl_max_ver| + ssl_flags = "#{ssl_flags} ssl-max-ver #{ssl_max_ver}" + end end -if p("ha_proxy.disable_tls_tickets") - ssl_flags = "#{ssl_flags} no-tls-tickets" + +if use_disable_ssl + ssl_flags = "no-sslv3" + if p("ha_proxy.disable_tls_10") + ssl_flags = "#{ssl_flags} no-tlsv10" + end + if p("ha_proxy.disable_tls_11") + ssl_flags = "#{ssl_flags} no-tlsv11" + end + if p("ha_proxy.disable_tls_12") + ssl_flags = "#{ssl_flags} no-tlsv12" + end + if p("ha_proxy.disable_tls_13") + ssl_flags = "#{ssl_flags} no-tlsv13" + end + if p("ha_proxy.disable_tls_tickets") + ssl_flags = "#{ssl_flags} no-tls-tickets" + end end # }}} # TLS Bind Options {{{ @@ -334,7 +347,9 @@ listen stats stats hide-version stats realm "Haproxy Statistics" stats uri /<%= p("ha_proxy.stats_uri") %> + <%- if_p("ha_proxy.stats_user") do -%> stats auth <%= p("ha_proxy.stats_user") %>:<%= p("ha_proxy.stats_password") %> + <% end -%> <% end -%> <% if p("ha_proxy.enable_health_check_http") %> @@ -975,6 +990,7 @@ listen health_check_http_tcp-<%= tcp_proxy["name"] %> # }}} <% end -%> +<%- end -%> <%- if properties.ha_proxy.raw_blocks && !properties.ha_proxy.raw_blocks.empty? -%> # raw blocks {{{ <%- @@ -984,10 +1000,18 @@ listen health_check_http_tcp-<%= tcp_proxy["name"] %> additional_types = raw_blocks.keys - correct_types_order all_found_types = ordered_blocks + additional_types all_found_types.each do |block_type| - raw_blocks[block_type].each do |block_id, block_raw_config| + raw_block = raw_blocks[block_type] + if raw_block.is_a?(Hash) + raw_block.each do |block_id, block_raw_config| %> <%= block_type %> <%= block_id %> <%= format_indented_multiline_config(block_raw_config) %> +<%- + end + else +%> +<%= block_type %> + <%= format_indented_multiline_config(raw_block) %> <%- end end diff --git a/jobs/haproxy/templates/pre-start.erb b/jobs/haproxy/templates/pre-start.erb index 565e837f..a71f5db6 100644 --- a/jobs/haproxy/templates/pre-start.erb +++ b/jobs/haproxy/templates/pre-start.erb @@ -20,3 +20,5 @@ fi if [ ! -e /usr/local/bin/socat ]; then sudo ln -s /var/vcap/packages/haproxy/bin/socat /usr/local/bin/socat fi + +<%= p("ha_proxy.pre_start_script") %> \ No newline at end of file diff --git a/spec/haproxy/templates/haproxy_config/global_and_default_options_spec.rb b/spec/haproxy/templates/haproxy_config/global_and_default_options_spec.rb index d13eb3ce..8dba2eef 100644 --- a/spec/haproxy/templates/haproxy_config/global_and_default_options_spec.rb +++ b/spec/haproxy/templates/haproxy_config/global_and_default_options_spec.rb @@ -120,6 +120,56 @@ end end + context 'when ha_proxy.ssl_min_ver is provided' do + let(:properties) do + { + 'ssl_min_ver' => 'TLSv1.2', + 'disable_tls_10' => true, + 'disable_tls_11' => true, + 'disable_tls_12' => true, + 'disable_tls_13' => true, + 'disable_tls_tickets' => true + } + end + + it 'enables ssl-min-ver and ignores tls_disable_ properties' do + expect(global).to include('ssl-default-server-options ssl-min-ver TLSv1.2') + expect(global).to include('ssl-default-bind-options ssl-min-ver TLSv1.2') + end + end + + context 'when ha_proxy.ssl_min_ver is not provided and ha_proxy.ssl_max_ver is provided' do + let(:properties) do + { + 'ssl_max_ver' => 'TLSv1.3', + 'disable_tls_10' => false, + 'disable_tls_11' => false, + 'disable_tls_12' => false, + 'disable_tls_13' => false, + 'disable_tls_tickets' => false + } + end + + it 'ignores ssl-min/max-ver properties, tls_disable_ properties are used' do + expect(global).to include('ssl-default-server-options no-sslv3') + expect(global).to include('ssl-default-bind-options no-sslv3') + end + end + + context 'when ha_proxy.ssl_min_ver and ha_proxy.ssl_max_ver are provided' do + let(:properties) do + { + 'ssl_min_ver' => 'TLSv1.2', + 'ssl_max_ver' => 'TLSv1.3' + } + end + + it 'enables ssl-min/max-ver and ignores tls_disable_ properties' do + expect(global).to include('ssl-default-server-options ssl-min-ver TLSv1.2 ssl-max-ver TLSv1.3') + expect(global).to include('ssl-default-bind-options ssl-min-ver TLSv1.2 ssl-max-ver TLSv1.3') + end + end + context 'when ha_proxy.disable_tls_10 is provided' do let(:properties) do { diff --git a/spec/haproxy/templates/haproxy_config/raw_blocks_spec.rb b/spec/haproxy/templates/haproxy_config/raw_blocks_spec.rb index 09b318d7..b927cedd 100644 --- a/spec/haproxy/templates/haproxy_config/raw_blocks_spec.rb +++ b/spec/haproxy/templates/haproxy_config/raw_blocks_spec.rb @@ -7,10 +7,13 @@ parse_haproxy_config(template.render({ 'ha_proxy' => properties })) end - context 'when multiline configurations are provided for some raw blocks' do + context 'when multiline configurations are provided for global, defaults and some raw blocks with ids' do let(:properties) do { + 'config_mode' => 'raw_blocks_only', 'raw_blocks' => { + 'global' => "line 1\nline 2\nline 3", + 'defaults' => ['line 1', 'line 2', 'line 3'], 'some' => { 'raw-block-1' => "line 1\nline 2\nline 3", 'raw-block-2' => "\n\nline 1\nline 2\nline 3\n\n", @@ -22,15 +25,18 @@ it 'formats the configuration as expected' do expected_block_content = ['line 1', 'line 2', 'line 3'] + expect(haproxy_conf['global']).to eq(expected_block_content) + expect(haproxy_conf['defaults']).to eq(expected_block_content) expect(haproxy_conf['some raw-block-1']).to eq(expected_block_content) expect(haproxy_conf['some raw-block-2']).to eq(expected_block_content) expect(haproxy_conf['some raw-block-3']).to eq(expected_block_content) end end - context 'when there are many types of raw blocks' do + context 'when there are many types of raw blocks, ha_proxy.config_mode=raw_blocks_only' do let(:properties) do { + 'config_mode' => 'raw_blocks_only', 'raw_blocks' => { 'unknown' => { 'raw-test-1' => 'test', @@ -42,18 +48,46 @@ 'backend' => { 'raw-test' => 'test' }, 'frontend' => { 'raw-test' => 'test' }, 'listen' => { 'raw-test' => 'test' }, - 'defaults' => { '# raw-test' => 'test' }, - 'global' => { '# raw-test' => 'test' } + 'defaults' => 'test', + 'global' => 'test' } } end - it 'arranges them all in the correct order' do - raw_keys = haproxy_conf.keys.select { |key| key.include?('raw-test') } - expect(raw_keys).to eq(['global # raw-test', 'defaults # raw-test', + it 'return only raw blocks and arranges them in the correct order' do + raw_keys = haproxy_conf.keys + expect(raw_keys).to eq(['global', 'defaults', 'listen raw-test', 'frontend raw-test', 'backend raw-test', 'resolvers raw-test', 'peers raw-test', 'mailers raw-test', 'unknown raw-test-1', 'unknown raw-test-2']) end end + + context 'when there are many types of raw blocks, classic config mode' do + let(:properties) do + { + 'raw_blocks' => { + 'unknown' => { + 'raw-test-1' => 'test', + 'raw-test-2' => 'test' + }, + 'mailers' => { 'raw-test' => 'test' }, + 'peers' => { 'raw-test' => 'test' }, + 'resolvers' => { 'raw-test' => 'test' }, + 'backend' => { 'raw-test' => 'test' }, + 'frontend' => { 'raw-test' => 'test' }, + 'listen' => { 'raw-test' => 'test' } + } + } + end + + it 'return static block and then raw blocks arranged in the correct order' do + raw_keys = haproxy_conf.keys + expect(raw_keys).to eq(['global', 'defaults', 'frontend http-in', 'backend http-routers-http1', + 'listen raw-test', 'frontend raw-test', 'backend raw-test', + 'resolvers raw-test', 'peers raw-test', 'mailers raw-test', + 'unknown raw-test-1', 'unknown raw-test-2']) + end + end + end