From 901523ddbba9f097661361cb1fd216b32d1c4a6f Mon Sep 17 00:00:00 2001 From: Dmitriy Rabotyagov Date: Wed, 3 Aug 2022 18:08:28 +0200 Subject: [PATCH] Allow haproxy to bind on the interface In some user scenarious (like implementing DNS RR) it might be useful to bind on 0.0.0.0 but at the same time do not conflict with other services that are binded to the same ports. For that, we can specify a specific interface, on which haproxy will be binded to 0.0.0.0. In netstat it would be represented like `0.0.0.0%br-mgmt:5000`. With that we also allow to fully override `vip_binds` if assumtions that role make are not valid for some reason. Change-Id: Ic4c58ef53abc5f454b6fbebbd87292a932d173ae --- defaults/main.yml | 13 +++++++++++++ handlers/main.yml | 5 +++-- .../notes/bind_interface-8f7a123d4ab1219a.yaml | 12 ++++++++++++ templates/service.j2 | 17 ++++++++++++----- vars/main.yml | 18 +++++++++--------- 5 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 releasenotes/notes/bind_interface-8f7a123d4ab1219a.yaml diff --git a/defaults/main.yml b/defaults/main.yml index ebbc737..322af2e 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -235,6 +235,19 @@ haproxy_bind_external_lb_vip_address: "{{ external_lb_vip_address }}" # Option to override which address haproxy binds to for internal vip. haproxy_bind_internal_lb_vip_address: "{{ internal_lb_vip_address }}" +# Option to define if you need haproxy to bind on specific interface. +haproxy_bind_external_lb_vip_interface: +haproxy_bind_internal_lb_vip_interface: + +# Option to override haproxy frontend binds +# Example: +# haproxy_tls_vip_binds: +# - address: '*' +# interface: bond0 +# - address: '192.168.0.10' + +haproxy_tls_vip_binds: "{{ _haproxy_tls_vip_binds }}" + # Make the log socket available to the chrooted filesystem haproxy_log_socket: "/dev/log" haproxy_log_mount_point: "/var/lib/haproxy/dev/log" diff --git a/handlers/main.yml b/handlers/main.yml index 36fcbe3..eea7e2c 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -17,9 +17,10 @@ shell: > cat {{ item_base_path ~ '.crt' }} $(test -f {{ item_base_path ~ '-ca.crt' }} && echo {{ item_base_path ~ '-ca.crt' }}) {{ item_base_path ~ '.key' }} > {{ item_base_path ~ '.pem' }} notify: Reload haproxy - with_items: "{{ _haproxy_tls_vip_binds }}" vars: - item_base_path: "{{ haproxy_ssl_cert_path ~ '/haproxy_' ~ ansible_facts['hostname'] ~ '-' ~ item }}" + item_name: "{{ ('interface' in item and item['interface'] is truthy) | ternary(item['address'] ~ '-' ~ item['interface'], item['address']) }}" + item_base_path: "{{ haproxy_ssl_cert_path ~ '/haproxy_' ~ ansible_facts['hostname'] ~ '-' ~ item_name }}" + with_items: "{{ haproxy_tls_vip_binds }}" listen: - cert installed diff --git a/releasenotes/notes/bind_interface-8f7a123d4ab1219a.yaml b/releasenotes/notes/bind_interface-8f7a123d4ab1219a.yaml new file mode 100644 index 0000000..5802151 --- /dev/null +++ b/releasenotes/notes/bind_interface-8f7a123d4ab1219a.yaml @@ -0,0 +1,12 @@ +--- + +features: + - | + Added variables ``haproxy_bind_external_lb_vip_interface`` and + ``haproxy_bind_internal_lb_vip_interface`` that allows deployer to bind + haproxy on the specific interface only. + - | + Added variable ``haproxy_tls_vip_binds`` that allows to fully override + haproxy bindings, that are generated by the role if some assumptions are + not valid for some scenarios. It is list of mappings, that include address + and interface. Interface key is optional and can be ommited. diff --git a/templates/service.j2 b/templates/service.j2 index 939cf40..4e92c7c 100644 --- a/templates/service.j2 +++ b/templates/service.j2 @@ -15,16 +15,23 @@ {% if item.service.haproxy_bind is defined %} {% set vip_binds = item.service.haproxy_bind %} {% else %} -{% set vip_binds = _haproxy_tls_vip_binds + extra_lb_vip_addresses %} +{% set vip_binds = haproxy_tls_vip_binds + extra_lb_vip_addresses %} {% endif %} {% if not item.service.haproxy_backend_only | default(false) %} {% for vip_bind in vip_binds %} +{% if vip_bind is not string and vip_bind is mapping %} +{% set vip_address = vip_bind['address'] %} +{% set vip_interface = vip_bind['interface'] %} +{% else %} +{% set vip_address = vip_bind %} +{% set vip_interface = '' %} +{% endif %} {% if item.service.haproxy_redirect_http_port is defined and item.service.haproxy_ssl %} {% if (loop.index == 1 or item.service.haproxy_ssl_all_vips | default(false) | bool) %} frontend {{ item.service.haproxy_service_name }}-redirect-front-{{ loop.index }} -bind {{ vip_bind }}:{{ item.service.haproxy_redirect_http_port }} +bind {{ vip_address }}:{{ item.service.haproxy_redirect_http_port }}{{ (vip_interface is truthy) | ternary(' interface ' ~ vip_interface, '') }} mode http redirect scheme {{ item.service.haproxy_redirect_scheme | default('https if !{ ssl_fc }') }} {% if item.service.haproxy_frontend_acls is defined %} @@ -38,11 +45,11 @@ bind {{ vip_bind }}:{{ item.service.haproxy_redirect_http_port }} {# TODO: remove if and section inside if after HTTPS upgrade #} {# During an upgrade of internal frontends from HTTP to HTTPS, need to accept both HTTP and HTTPS until client config has been changed #} -{% if (item.service.haproxy_tcp_upgrade_frontend | default(false)) and not (loop.index == 1 or vip_bind in extra_lb_tls_vip_addresses) and (item.service.haproxy_ssl_all_vips | default(false)) %} +{% if (item.service.haproxy_tcp_upgrade_frontend | default(false)) and not (loop.index == 1 or vip_address in extra_lb_tls_vip_addresses) and (item.service.haproxy_ssl_all_vips | default(false)) %} {% include 'service-redirect.j2' %} {% else %} frontend {{ item.service.haproxy_service_name }}-front-{{ loop.index }} - bind {{ vip_bind }}:{{ item.service.haproxy_port }} {% if (item.service.haproxy_ssl | default(false) | bool) and (loop.index == 1 or vip_bind in extra_lb_tls_vip_addresses or (item.service.haproxy_ssl_all_vips | default(false) | bool and vip_bind not in extra_lb_vip_addresses)) %}ssl crt {{ haproxy_ssl_cert_path }}/haproxy_{{ ansible_facts['hostname'] }}-{{ vip_bind }}.pem {% endif %} + bind {{ vip_address }}:{{ item.service.haproxy_port }}{{ (vip_interface is truthy) | ternary(' interface ' ~ vip_interface, '') }} {% if (item.service.haproxy_ssl | default(false) | bool) and (loop.index == 1 or vip_address in extra_lb_tls_vip_addresses or (item.service.haproxy_ssl_all_vips | default(false) | bool and vip_address not in extra_lb_vip_addresses)) %}ssl crt {{ haproxy_ssl_cert_path }}/haproxy_{{ ansible_facts['hostname'] }}-{{ (vip_interface is truthy) | ternary(vip_address ~ '-' ~ vip_interface, vip_address) }}.pem {% endif %} {% if request_option == "http" %} option httplog @@ -69,7 +76,7 @@ frontend {{ item.service.haproxy_service_name }}-front-{{ loop.index }} {% endif %} {% endfor %} {% endif %} -{% if (item.service.haproxy_ssl | default(false) | bool) and request_option == 'http' and (loop.index == 1 or vip_bind in extra_lb_tls_vip_addresses or (item.service.haproxy_ssl_all_vips | default(false) | bool and vip_bind not in extra_lb_vip_addresses)) %} +{% if (item.service.haproxy_ssl | default(false) | bool) and request_option == 'http' and (loop.index == 1 or vip_address in extra_lb_tls_vip_addresses or (item.service.haproxy_ssl_all_vips | default(false) | bool and vip_address not in extra_lb_vip_addresses)) %} http-request add-header X-Forwarded-Proto https {% endif %} mode {{ item.service.haproxy_balance_type }} diff --git a/vars/main.yml b/vars/main.yml index 9dc4448..d14c02e 100644 --- a/vars/main.yml +++ b/vars/main.yml @@ -14,24 +14,24 @@ # limitations under the License. _haproxy_tls_vip_binds: | - {% set vip_binds = [haproxy_bind_external_lb_vip_address] %} - {% if haproxy_bind_internal_lb_vip_address != haproxy_bind_external_lb_vip_address %} - {% set _ = vip_binds.append(haproxy_bind_internal_lb_vip_address) %} + {% set vip_binds = [{'address': haproxy_bind_external_lb_vip_address, 'interface': haproxy_bind_external_lb_vip_interface}] %} + {% if haproxy_bind_internal_lb_vip_address != haproxy_bind_external_lb_vip_address or haproxy_bind_external_lb_vip_interface != haproxy_bind_internal_lb_vip_interface %} + {% set _ = vip_binds.append({'address': haproxy_bind_internal_lb_vip_address, 'interface': haproxy_bind_internal_lb_vip_interface}) %} {% endif %} {% for vip_address in extra_lb_tls_vip_addresses %} - {% set _ = vip_binds.append(vip_address) %} + {% set _ = vip_binds.append({'address': vip_address}) %} {% endfor %} {{ vip_binds }} _haproxy_pki_certificates: | {% set _pki_certs = [] %} - {% for vip in _haproxy_tls_vip_binds %} + {% for vip in haproxy_tls_vip_binds %} {% set _ = _pki_certs.append( { - 'name': 'haproxy_' ~ ansible_facts['hostname'] ~ '-' ~ vip, + 'name': 'haproxy_' ~ ansible_facts['hostname'] ~ '-' ~ ('interface' in vip and vip['interface'] is truthy) | ternary(vip['address'] ~ '-' ~ vip['interface'], vip['address']), 'provider': 'ownca', 'cn': ansible_facts['hostname'], - 'san': 'DNS:' ~ ansible_facts['hostname'] ~ ',DNS:' ~ ansible_facts['fqdn'] ~ ',' ~ (vip | ansible.utils.ipaddr) | ternary('IP:', 'DNS:') ~ vip, + 'san': 'DNS:' ~ ansible_facts['hostname'] ~ ',DNS:' ~ ansible_facts['fqdn'] ~ ',' ~ (vip['address'] | ansible.utils.ipaddr) | ternary('IP:', 'DNS:') ~ vip['address'], 'signed_by': haproxy_pki_intermediate_cert_name, } ) %} @@ -40,8 +40,8 @@ _haproxy_pki_certificates: | _haproxy_pki_install_certificates: | {% set _pki_install = [] %} - {% for vip in _haproxy_tls_vip_binds %} - {% set _cert_basename = '/haproxy_' ~ ansible_facts['hostname'] ~ '-' ~ vip %} + {% for vip in haproxy_tls_vip_binds %} + {% set _cert_basename = '/haproxy_' ~ ansible_facts['hostname'] ~ '-' ~ ('interface' in vip and vip['interface'] is truthy) | ternary(vip['address'] ~ '-' ~ vip['interface'], vip['address']) %} {% set _ = _pki_install.append( { 'src': haproxy_user_ssl_cert | default(haproxy_pki_certs_path ~ _cert_basename ~ '.crt'),