From f97acc0f54f3d0d2210969c066b37a53092e7073 Mon Sep 17 00:00:00 2001 From: Ben Webber Date: Sun, 26 Nov 2017 10:35:45 +0000 Subject: [PATCH 01/17] Support free-form configuration syntax --- filter_plugins/haproxy.py | 94 +++++++++++++++++++++++++++++++++++++++ templates/_macros.j2 | 19 ++++++++ templates/haproxy.cfg.j2 | 12 +++++ 3 files changed, 125 insertions(+) create mode 100644 filter_plugins/haproxy.py create mode 100644 templates/haproxy.cfg.j2 diff --git a/filter_plugins/haproxy.py b/filter_plugins/haproxy.py new file mode 100644 index 0000000..470d318 --- /dev/null +++ b/filter_plugins/haproxy.py @@ -0,0 +1,94 @@ +from __future__ import unicode_literals + +import collections +import sys + + +if sys.version_info.major < 3: + string_types = basestring +else: + string_types = str + + +# Ranks taken from Puppetlabs HAProxy module: +# +RANKS = { + 'acl': -1, + 'tcp-request': 2, + 'block': 3, + 'http-request': 4, + 'reqallow': 5, + 'reqdel': 5, + 'reqdeny': 5, + 'reqidel': 5, + 'reqideny': 5, + 'reqipass': 5, + 'reqirep': 5, + 'reqitarpit': 5, + 'reqpass': 5, + 'reqrep': 5, + 'reqtarpit': 5, + 'reqadd': 6, + 'redirect': 7, + 'use_backend': 8, + 'use-server': 9, + 'server': 100, +} + + +def haproxy_sort(options): + """ + Sort HAProxy options according to their relative ranks. + """ + return collections.OrderedDict( + sorted(options.items(), key=lambda pair: RANKS.get(pair[0], 0)), + ) + + +def flatten(iterable): + """ + Flatten nested iterables to a single level. + """ + for item in iterable: + if isinstance(item, collections.Iterable) and not isinstance(item, string_types): + for subitem in flatten(item): + yield subitem + else: + yield item + + +def expand(options): + """ + Expand a nested configuration dictionary. + """ + for key, value in options.items(): + if isinstance(value, collections.Mapping): + yield (key, value.items()) + elif isinstance(value, string_types): + yield (key, value) + elif isinstance(value, collections.Iterable): + for item in value: + yield (key, item) + else: + yield (key, value) + + +def to_haproxy(options): + """ + Yield HAProxy configuration lines from a nested configuration dictionary. + """ + options = haproxy_sort(options) + for key, value in expand(options): + if value is None: + yield key + continue + if not isinstance(value, collections.Iterable) or isinstance(value, string_types): + value = [value] + yield '{} {}'.format(key, ' '.join(str(v) for v in flatten(value))) + + +class FilterModule(object): + def filters(self): + return { + 'to_haproxy': to_haproxy, + } diff --git a/templates/_macros.j2 b/templates/_macros.j2 index 0d99675..9b92e89 100644 --- a/templates/_macros.j2 +++ b/templates/_macros.j2 @@ -11,3 +11,22 @@ {% endfor -%} {%- endmacro -%} + +{% macro section(section, group, single=False) %} +{% if group is defined %} +{% if single %} + +{{ section }} +{{ group|to_haproxy|join('\n')|indent(indentfirst=True) }} + + +{% else %} +{% for name, options in group.items() %} +{{ section }} {{ name }} +{{ options|to_haproxy|join('\n')|indent(indentfirst=True) }} + +{% endfor %} + +{% endif %} +{% endif %} +{% endmacro %} diff --git a/templates/haproxy.cfg.j2 b/templates/haproxy.cfg.j2 new file mode 100644 index 0000000..aca53f9 --- /dev/null +++ b/templates/haproxy.cfg.j2 @@ -0,0 +1,12 @@ +{%- import '_macros.j2' as macros with context -%} +# {{ ansible_managed }} + +{{ macros.section('global', haproxy_global, single=True) -}} +{{- macros.section('defaults', haproxy_defaults, single=True) -}} +{{- macros.section('peers', haproxy_peers) -}} +{{- macros.section('resolvers', haproxy_resolvers) -}} +{{- macros.section('mailers', haproxy_mailers) -}} +{{- macros.section('userlist', haproxy_userlists) -}} +{{- macros.section('frontend', haproxy_frontends) -}} +{{- macros.section('backend', haproxy_backends) -}} +{{- macros.section('listen', haproxy_listens) -}} From b9600d4b731473c717a1fc8405830c4bfbafbb55 Mon Sep 17 00:00:00 2001 From: Ben Webber Date: Mon, 27 Nov 2017 00:31:55 +0000 Subject: [PATCH 02/17] Replace templates with single file --- tasks/configure.yml | 198 +-------------------------------------- templates/_macros.j2 | 23 +---- templates/backend.cfg | 134 -------------------------- templates/defaults.cfg | 88 ----------------- templates/frontend.cfg | 134 -------------------------- templates/global.cfg | 64 ------------- templates/haproxy.cfg.j2 | 1 + templates/listen.cfg | 115 ----------------------- templates/resolvers.cfg | 18 ---- templates/userlist.cfg | 14 --- 10 files changed, 8 insertions(+), 781 deletions(-) delete mode 100644 templates/backend.cfg delete mode 100644 templates/defaults.cfg delete mode 100644 templates/frontend.cfg delete mode 100644 templates/global.cfg delete mode 100644 templates/listen.cfg delete mode 100644 templates/resolvers.cfg delete mode 100644 templates/userlist.cfg diff --git a/tasks/configure.yml b/tasks/configure.yml index 4d9042d..3b2aadd 100644 --- a/tasks/configure.yml +++ b/tasks/configure.yml @@ -1,199 +1,7 @@ --- - -## ASSEMBLE CONFIG - RESOLVERS - -- name: 'Create directory for the resolvers' - file: - path: "{{ haproxy_config_dir }}/resolvers.d" - state: directory - -- name: "List files for the resolvers" - find: - paths: "{{ haproxy_config_dir }}/resolvers.d" - patterns: "*.cfg" - register: directory_contents - changed_when: false - -- name: "Remove unmanaged files for the resolvers" - file: - path: "{{ item.path }}" - state: absent - when: (item.path | basename) not in (haproxy_resolvers | json_query('[*].name') | map('regex_replace', '(.*)', '\\1.cfg') | list) - with_items: "{{ directory_contents.files }}" - -- name: 'Build up the resolvers' +- name: Configure HAProxy template: - src: "resolvers.cfg" - dest: "{{ haproxy_config_dir }}/resolvers.d/{{ item.name }}.cfg" - with_items: "{{ haproxy_resolvers }}" - when: haproxy_resolvers is defined - -## ASSEMBLE CONFIG - FRONTEND - -- name: 'Create directory for the frontend' - file: - path: "{{ haproxy_config_dir }}/frontends.d" - state: directory - -- name: "List files for the frontends" - find: - paths: "{{ haproxy_config_dir }}/frontends.d" - patterns: "*.cfg" - register: directory_contents - changed_when: false - -- name: "Remove unmanaged files for the frontends" - file: - path: "{{ item.path }}" - state: absent - when: (item.path | basename) not in (haproxy_frontends | json_query('[*].name') | map('regex_replace', '(.*)', '\\1.cfg') | list) - with_items: "{{ directory_contents.files }}" - -- name: 'Build up the frontends' - template: - src: "frontend.cfg" - dest: "{{ haproxy_config_dir }}/frontends.d/{{ item.name }}.cfg" - with_items: "{{ haproxy_frontends }}" - when: haproxy_frontends is defined - -## ASSEMBLE CONFIG - BACKEND - -- name: 'Create directory for the backends' - file: - path: "{{ haproxy_config_dir }}/backends.d" - state: directory - -- name: "List files for the backends" - find: - paths: "{{ haproxy_config_dir }}/backends.d" - patterns: "*.cfg" - register: directory_contents - changed_when: false - -- name: "Remove unmanaged files for the backends" - file: - path: "{{ item.path }}" - state: absent - when: (item.path | basename) not in (haproxy_backends | json_query('[*].name') | map('regex_replace', '(.*)', '\\1.cfg') | list) - with_items: "{{ directory_contents.files }}" - -- name: 'Build up the backends' - template: - src: "backend.cfg" - dest: "{{ haproxy_config_dir }}/backends.d/{{ item.name }}.cfg" - with_items: "{{ haproxy_backends }}" - when: haproxy_backends is defined - -## ASSEMBLE CONFIG - LISTEN - -- name: 'Create directory for the listen sections' - file: - path: "{{ haproxy_config_dir }}/listen.d" - state: directory - -- name: "List files the listen sections" - find: - paths: "{{ haproxy_config_dir }}/listen.d" - patterns: "*.cfg" - register: directory_contents - changed_when: false - -- name: "Remove unmanaged files the listen sections" - file: - path: "{{ item.path }}" - state: absent - when: (item.path | basename) not in (haproxy_listen | json_query('[*].name') | map('regex_replace', '(.*)', '\\1.cfg') | list) - with_items: "{{ directory_contents.files }}" - -- name: 'Build up the listen sections' - template: - src: "listen.cfg" - dest: "{{ haproxy_config_dir }}/listen.d/{{ item.name }}.cfg" - with_items: "{{ haproxy_listen }}" - when: haproxy_listen is defined - -## ASSEMBLE CONFIG - USERLIST - -- name: 'Create directory for the userlists' - file: path={{ haproxy_config_dir }}/userlists.d state=directory - -- name: "List files for the userlists" - find: - paths: "{{ haproxy_config_dir }}/userlists.d" - patterns: "*.cfg" - register: directory_contents - changed_when: false - -- name: "Remove unmanaged files for the userlists" - file: - path: "{{ item.path }}" - state: absent - when: (item.path | basename) not in (haproxy_userlists | json_query('[*].name') | map('regex_replace', '(.*)', '\\1.cfg') | list) - with_items: "{{ directory_contents.files }}" - -- name: 'Build up the userlist sections' - template: - src: userlist.cfg - dest: "{{ haproxy_config_dir }}/userlists.d/{{ item.name }}.cfg" - with_items: "{{ haproxy_userlists }}" - when: haproxy_userlists is defined - -## ASSEMBLE CONFIG - GLOBAL & DEFAULT - -- name: 'Delete the compiled folder' - file: - path: "{{ haproxy_config_dir }}/compiled" - state: absent - -- name: 'Create the compiled folder' - file: - path: "{{ haproxy_config_dir }}/compiled" - state: directory - -- name: 'Build up the global config' - template: - src: "global.cfg" - dest: "{{ haproxy_config_dir }}/compiled/01-global.cfg" - when: haproxy_global is defined - tags: 'test' - -- name: 'Build up the default config' - template: - src: "defaults.cfg" - dest: "{{ haproxy_config_dir }}/compiled/02-defaults.cfg" - when: haproxy_defaults is defined - -## ASSEMBLE FINAL CONFIG - -- name: 'Assemble the resolvers sections configuration file' - assemble: - src: "{{ haproxy_config_dir }}/resolvers.d" - dest: "{{ haproxy_config_dir }}/compiled/03-resolvers.cfg" - -- name: 'Assemble the backends configuration file' - assemble: - src: "{{ haproxy_config_dir }}/backends.d" - dest: "{{ haproxy_config_dir }}/compiled/04-backends.cfg" - -- name: 'Assemble the frontends configuration file' - assemble: - src: "{{ haproxy_config_dir }}/frontends.d" - dest: "{{ haproxy_config_dir }}/compiled/05-frontends.cfg" - -- name: 'Assemble the listen sections configuration file' - assemble: - src: "{{ haproxy_config_dir }}/listen.d" - dest: "{{ haproxy_config_dir }}/compiled/06-listen.cfg" - -- name: 'Assemble the userlists sections configuration file' - assemble: - src: "{{ haproxy_config_dir }}/userlists.d" - dest: "{{ haproxy_config_dir }}/compiled/07-userlists.cfg" - -- name: 'Assemble the final configuration file' - assemble: - src: "{{ haproxy_config_dir }}/compiled" + src: haproxy.cfg.j2 dest: "{{ haproxy_config_file }}" - backup: yes - validate: "haproxy -c -f %s" + validate: haproxy -c -f %s notify: restart haproxy diff --git a/templates/_macros.j2 b/templates/_macros.j2 index 9b92e89..b85a4f4 100644 --- a/templates/_macros.j2 +++ b/templates/_macros.j2 @@ -1,27 +1,12 @@ -{%- macro http_response(responses = []) -%} - {%- for response in responses -%} - http-response {{ response.action }}{% if response.param is defined %} {{ response.param }}{% endif %}{% if response.condition is defined %} {{ response.condition }}{% endif %} - - {% endfor -%} -{%- endmacro -%} - -{%- macro http_request(requests = []) -%} - {%- for request in requests -%} - http-request {{ request.action }}{% if request.param is defined %} {{ request.param }}{% endif %}{% if request.condition is defined %} {{ request.condition }}{% endif %} - - {% endfor -%} -{%- endmacro -%} - -{% macro section(section, group, single=False) %} -{% if group is defined %} +{% macro section(section, instances, single=False) %} +{% if instances is defined %} {% if single %} - {{ section }} -{{ group|to_haproxy|join('\n')|indent(indentfirst=True) }} +{{ instances|to_haproxy|join('\n')|indent(indentfirst=True) }} {% else %} -{% for name, options in group.items() %} +{% for name, options in instances.items() %} {{ section }} {{ name }} {{ options|to_haproxy|join('\n')|indent(indentfirst=True) }} diff --git a/templates/backend.cfg b/templates/backend.cfg deleted file mode 100644 index 0041608..0000000 --- a/templates/backend.cfg +++ /dev/null @@ -1,134 +0,0 @@ -{%- import '_macros.j2' as macros with context -%} - -#{{ ansible_managed }} - backend {{ item.name }} - {% if item.disabled is defined and item.disabled == true -%} - disabled - {% endif -%} - - {% if item.description is defined -%} - description {{ item.description }} - {% endif -%} - - {%- if item.servers is defined -%} - {%- for server in item.servers -%} - server {{ server.name }} {{ server.ip }}{% if server.port is defined %}:{{server.port }}{% endif %} {% if server.maxconn is defined %}maxconn {{server.maxconn }} {% endif %}{% if server.params is defined %}{% for param in server.params %}{{ param }} {% endfor %}{% endif %} - - {% endfor -%} - {% endif -%} - - {% if item.balance is defined -%} - balance {{ item.balance }} - {% endif -%} - - {% if item.cookie is defined -%} - cookie {{ item.cookie }} - {% endif -%} - - {% if item.mode is defined -%} - mode {{ item.mode }} - {% endif -%} - - {% if item.log is defined -%} - log {{ item.log }} - {% endif -%} - - {% if item.retries is defined -%} - retries {{ item.retries }} - {% endif -%} - - {% if item.acl is defined -%} - {% for acl in item.acl -%} - acl {{ acl.name }} {{ acl.condition }} - {% endfor -%} - {% endif -%} - - {%- if item.redirects is defined -%} - {%- for redirect in item.redirects -%} - redirect {{ redirect }} - {% endfor -%} - {% endif -%} - - {% if item.contimeout is defined -%} - contimeout {{ item.contimeout }} - {% endif -%} - - {% if item.http_send_name_header is defined -%} - http-send-name-header {{ item.http_send_name_header }} - {% endif -%} - - {%- if item.http_check_expect is defined -%} - {%- for check_expect in item.http_check_expect -%} - http-check expect {{ check_expect }} - {% endfor -%} - {% endif -%} - - {%- if item.http_request is defined -%} - {{ macros.http_request(item.http_request) }} - {%- endif -%} - - {%- if item.http_response is defined -%} - {{ macros.http_response(item.http_response) }} - {%- endif -%} - - {%- if item.options is defined -%} - {%- for option in item.options -%} - option {{ option }} - {% endfor -%} - {% endif -%} - - {%- if item.timeout is defined -%} - {%- for entry in item.timeout -%} - timeout {{ entry.param }} {{ entry.value }} - {% endfor -%} - {% endif -%} - - {%- if item.reqadd is defined -%} - {%- for reqadd in item.reqadd -%} - reqadd {{ reqadd }} - {% endfor -%} - {% endif -%} - - {%- if item.rspadd is defined -%} - {%- for rspadd in item.rspadd -%} - rspadd {{ rspadd }} - {% endfor -%} - {% endif -%} - - {%- if item.reqrep is defined -%} - {%- for reqrep in item.reqrep -%} - reqrep {{ reqrep }} - {% endfor -%} - {% endif -%} - - {%- if item.reqirep is defined -%} - {%- for reqirep in item.reqirep -%} - reqirep {{ reqirep }} - {% endfor -%} - {% endif -%} - - {%- if item.rsprep is defined -%} - {%- for rsprep in item.rsprep -%} - rsprep {{ rsprep }} - {% endfor -%} - {% endif -%} - - {%- if item.rspirep is defined -%} - {%- for rspirep in item.rspirep -%} - rspirep {{ rspirep }} - {% endfor -%} - {% endif -%} - - {% if item.appsession is defined -%} - appsession {{ item.appsession }} - {% endif -%} - - {%- if item.errorfile is defined -%} - {%- for errorfile in item.errorfile -%} - errorfile {{ errorfile.code }} {{ errorfile.file }} - {% endfor -%} - {% endif -%} - - {% if item.force_persist is defined -%} - force-persist {{ item.force_persist }} - {% endif -%} diff --git a/templates/defaults.cfg b/templates/defaults.cfg deleted file mode 100644 index 132c82f..0000000 --- a/templates/defaults.cfg +++ /dev/null @@ -1,88 +0,0 @@ -#{{ ansible_managed }} -defaults -{% if haproxy_defaults.mode is defined %} - mode {{ haproxy_defaults.mode }} -{% endif -%} -{% if haproxy_defaults.log is defined %} -{% for log in haproxy_defaults.log %} - log {{ log.address }}{% if log.facility is defined %} {{ log.facility }} {% endif %}{% if log.level is defined %} {{log.level }} {% endif %}{% if log.minlevel is defined %} {{ log.minlevel }}{% endif %} - -{% if log.format is defined %} - log-format {{ log.format }} -{% endif %} - -{% endfor %} -{% endif %} -{% if haproxy_defaults.retries is defined %} - retries {{ haproxy_defaults.retries }} -{% endif -%} -{% if haproxy_defaults.timeout is defined -%} -{% for condition in haproxy_defaults.timeout %} - timeout {{ condition.param }} {{ condition.value }} -{% endfor %} -{% endif -%} -{% if haproxy_defaults.maxconn is defined %} - maxconn {{ haproxy_defaults.maxconn }} -{% endif -%} -{% if haproxy_defaults.stats is defined %} -{% if haproxy_defaults.stats.enabled is defined and haproxy_defaults.stats.enabled == True %} - stats enable -{% endif -%} -{% if haproxy_defaults.stats.hide_version is defined and haproxy_defaults.stats.hide_version == true %} - stats hide-version -{% endif -%} -{% if haproxy_defaults.stats.uri is defined %} - stats uri {{ haproxy_defaults.stats.uri }} -{% endif -%} -{% if haproxy_defaults.stats.realm is defined %} - stats realm {{ haproxy_defaults.stats.realm }} -{% endif -%} -{% if haproxy_defaults.stats.auth is defined %} - stats auth {{ haproxy_defaults.stats.auth }} -{% endif -%} -{% if haproxy_defaults.stats.refresh is defined %} - stats refresh {{ haproxy_defaults.stats.refresh }} -{% endif -%} -{% endif %} -{% if haproxy_defaults.options is defined %} -{% for option in haproxy_defaults.options %} - option {{ option }} -{% endfor -%} -{% endif -%} -{% if ansible_distribution != 'CentOS' and ansible_distribution != 'Alpine' %} -{% if haproxy_defaults.errorfile is defined %} -{% for item in haproxy_defaults.errorfile %} - errorfile {{ item.code }} {{ item.file }} -{% endfor %} -{% endif -%} -{% endif -%} -{% if haproxy_defaults.balance is defined %} - balance {{ haproxy_defaults.balance }} -{% endif -%} -{% if haproxy_defaults.cookie is defined -%} - cookie {{ haproxy_defaults.cookie }} -{% endif -%} -{% if haproxy_defaults.compression is defined %} -{% if haproxy_defaults.compression.algo is defined %} - compression algo {{ haproxy_defaults.compression.algo }} -{% endif -%} -{% if haproxy_defaults.compression.type is defined %} - compression type {{ haproxy_defaults.compression.type }} -{% endif -%} -{% if haproxy_defaults.compression.offload is defined and haproxy_defaults.compression.offload == true %} - compression offload -{% endif -%} -{% endif -%} - -{% if haproxy_defaults.http_check is defined %} -{% if haproxy_defaults.http_check.disable_on_404 is defined and haproxy_defaults.http_check.disable_on_404 == true %} - http-check disable-on-404 -{% endif -%} -{% if haproxy_defaults.http_check.expect is defined %} - http-check expect {{ haproxy_defaults.http_check.expect }} -{% endif -%} -{% if haproxy_defaults.http_check.send_state is defined and haproxy_defaults.http_check.send_state == true %} - http-check send-state -{% endif -%} -{% endif -%} - diff --git a/templates/frontend.cfg b/templates/frontend.cfg deleted file mode 100644 index d4dd55a..0000000 --- a/templates/frontend.cfg +++ /dev/null @@ -1,134 +0,0 @@ -{%- import '_macros.j2' as macros with context -%} - -#{{ ansible_managed }} -frontend {{ item.name }} {%if item.ip is defined %}{{ item.ip }}{% endif %}{%if item.port is defined %}:{{ item.port }}{% endif %} - - {% if item.bind is defined -%} - {%- for bind in item.bind -%} - bind {{ bind }}{% if item.ssl is defined %}{% if item.ssl.cert is defined %} ssl crt {{ item.ssl.cert }}{% if item.ssl.ciphers is defined %} ciphers {{ item.ssl.ciphers }}{% endif %}{% endif %}{% endif %} - - {% endfor -%} - {% endif -%} - - {% if item.mode is defined -%} - mode {{ item.mode }} - {% endif -%} - - {% if item.maxconn is defined -%} - maxconn {{ item.maxconn }} - {% endif -%} - - {% if item.timeout is defined -%} - {% for entry in item.timeout %} - timeout {{ entry.param }} {{ entry.value }} - {% endfor %} - {% endif -%} - - {%- if item.acl is defined -%} - {%- for acl in item.acl -%} - acl {{ acl.name }} {{ acl.condition }} - {% endfor -%} - {% endif -%} - - {%- if item.monitor is defined -%} - {% if item.monitor.uri is defined -%} - monitor-uri {{ item.monitor.uri }} - {% endif %} - {%- if item.monitor.fail is defined -%} - {%- for condition in item.monitor.fail -%} - monitor fail {{ condition }} - {% endfor %} - {% endif %} - {% endif -%} - - {%- if item.capture is defined -%} - {%- for capture in item.capture -%} - capture {{ capture }} - {% endfor %} - {% endif -%} - - {%- if item.reqadd is defined -%} - {%- for reqadd in item.reqadd -%} - reqadd {{ reqadd }} - {% endfor -%} - {% endif -%} - - {%- if item.rspadd is defined -%} - {%- for rspadd in item.rspadd -%} - rspadd {{ rspadd }} - {% endfor -%} - {% endif -%} - - {%- if item.reqrep is defined -%} - {%- for reqrep in item.reqrep -%} - reqrep {{ reqrep }} - {% endfor -%} - {% endif -%} - - {%- if item.reqirep is defined -%} - {%- for reqirep in item.reqirep -%} - reqirep {{ reqirep }} - {% endfor -%} - {% endif -%} - - {%- if item.rsprep is defined -%} - {%- for rsprep in item.rsprep -%} - rsprep {{ rsprep }} - {% endfor -%} - {% endif -%} - - {%- if item.rspirep is defined -%} - {%- for rspirep in item.rspirep -%} - rspirep {{ rspirep }} - {% endfor -%} - {% endif -%} - - {%- if item.rate_limit_sessions is defined -%} - rate-limit sessions {{ item.rate_limit_sessions }} - {% endif %} - - {%- if item.block is defined -%} - {%- for block in item.block -%} - block {{ block }} - {% endfor -%} - {% endif -%} - - {% if item.options is defined %} - {% for option in item.options %} - option {{ option }} - {% endfor %} - {% endif -%} - - {%- if item.redirects is defined -%} - {%- for redirect in item.redirects -%} - redirect {{ redirect }} - {% endfor -%} - {% endif -%} - - {%- if item.tcp_request is defined -%} - {%- for request in item.tcp_request -%} - tcp-request {{ request.param }} {{ request.value }}{% if request.condition is defined %} {{ request.condition }}{% endif %} - {% endfor -%} - {% endif -%} - - {%- if item.http_request is defined -%} - {{ macros.http_request(item.http_request) }} - {%- endif -%} - - {%- if item.http_response is defined -%} - {{ macros.http_response(item.http_response) }} - {%- endif -%} - - {%- if item.default_backend is defined -%} - default_backend {{ item.default_backend }} - {% endif -%} - - {%- if item.use_backend is defined -%} - {%- for backend in item.use_backend -%} - use_backend {{ backend.name }} {{ backend.condition }} - {% endfor -%} - {% endif -%} - - {% if item.force_persist is defined -%} - force-persist {{ item.force_persist }} - {% endif -%} diff --git a/templates/global.cfg b/templates/global.cfg deleted file mode 100644 index 2034f8c..0000000 --- a/templates/global.cfg +++ /dev/null @@ -1,64 +0,0 @@ -#{{ ansible_managed }} -global -{% if haproxy_global.chroot is defined and haproxy_global.chroot != false %} - chroot {{ haproxy_global.chroot }} -{% endif -%} -{% if haproxy_global.pidfile is defined %} - pidfile {{ haproxy_global.pidfile }} -{% endif -%} -{% if haproxy_global.maxconn is defined %} - maxconn {{ haproxy_global.maxconn }} -{% endif -%} -{% if haproxy_global.uid is defined %} - uid {{ haproxy_global.uid }} -{% elif haproxy_global.user is defined %} - user {{ haproxy_global.user }} -{% endif -%} -{% if haproxy_global.gid is defined %} - gid {{ haproxy_global.gid }} -{% elif haproxy_global.group is defined %} - group {{ haproxy_global.group }} -{% endif -%} -{% if haproxy_global.daemon is defined and haproxy_global.daemon == true %} - daemon -{% endif -%} -{% if haproxy_global.nbproc is defined %} - nbproc {{ haproxy_global.nbproc }} -{% endif -%} -{% if haproxy_global.spread_checks is defined %} - spread-checks {{ haproxy_global.spread_checks }} -{% endif -%} -{% if haproxy_global.stats is defined %} -{% if haproxy_global.stats.socket is defined %} - stats socket {{ haproxy_global.stats.socket }} -{% endif -%} -{% if haproxy_global.stats.timeout is defined %} - stats timeout {{ haproxy_global.stats.timeout }} -{% endif -%} -{% endif %} -{% if haproxy_global.log is defined %} -{% for log in haproxy_global.log %} - log {{ log.address }} {{ log.facility }}{% if log.level is defined %} {{log.level }}{% endif %}{% if log.minlevel is defined %} {{ log.minlevel }}{% endif %} - -{% if log.format is defined %} - log-format {{ log.format }} -{% endif %} -{% endfor %} -{% endif %} -{% if haproxy_global.ssl_default_bind_options is defined %} - ssl-default-bind-options {{ haproxy_global.ssl_default_bind_options }} -{% endif -%} -{% if haproxy_global.ssl_default_bind_ciphers is defined %} - ssl-default-bind-ciphers {{ haproxy_global.ssl_default_bind_ciphers }} -{% endif -%} -{% if haproxy_global.ssl_default_server_options is defined %} - ssl-default-server-options {{ haproxy_global.ssl_default_server_options }} -{% endif -%} -{% if haproxy_global.ssl_default_server_ciphers is defined %} - ssl-default-server-ciphers {{ haproxy_global.ssl_default_server_ciphers }} -{% endif -%} -{% if haproxy_global.tune is defined %} - {% for param, value in (haproxy_global.tune | flatten).items() -%} - tune.{{ param }} {{ value }} - {% endfor -%} -{% endif %} diff --git a/templates/haproxy.cfg.j2 b/templates/haproxy.cfg.j2 index aca53f9..9b58126 100644 --- a/templates/haproxy.cfg.j2 +++ b/templates/haproxy.cfg.j2 @@ -1,6 +1,7 @@ {%- import '_macros.j2' as macros with context -%} # {{ ansible_managed }} + {{ macros.section('global', haproxy_global, single=True) -}} {{- macros.section('defaults', haproxy_defaults, single=True) -}} {{- macros.section('peers', haproxy_peers) -}} diff --git a/templates/listen.cfg b/templates/listen.cfg deleted file mode 100644 index ab1c5ad..0000000 --- a/templates/listen.cfg +++ /dev/null @@ -1,115 +0,0 @@ -#{{ ansible_managed }} -listen {{ item.name }} -{% if item.bind is defined %} -{% for binding in item.bind %} - bind {{ binding }}{% if item.ssl is defined %}{% if item.ssl.cert is defined %} ssl crt {{ item.ssl.cert }}{% if item.ssl.ciphers is defined %} ciphers {{ item.ssl.ciphers }}{% endif %}{% endif %}{% endif %} - -{% endfor %} -{% endif -%} -{% if item.disabled is defined and item.disabled | bool %} - disabled -{% endif -%} -{% if item.description is defined %} - description {{ item.description }} -{% endif -%} -{% if item.servers is defined %} -{% for server in item.servers %} - server {{ server.name }} {{ server.ip }}{% if server.port is defined %}:{{server.port }}{% endif %} {% if server.maxconn is defined %}maxconn {{server.maxconn }} {% endif %}{% if server.params is defined %}{% for param in server.params %}{{ param }} {% endfor %}{% endif %} - -{% endfor %} -{% endif %} -{% if item.maxconn is defined -%} - maxconn {{ item.maxconn }} -{% endif -%} -{% if item.balance is defined %} - balance {{ item.balance }} -{% endif -%} -{% if item.cookie is defined -%} - cookie {{ item.cookie }} -{% endif -%} -{% if item.mode is defined %} - mode {{ item.mode }} -{% endif -%} -{% if item.log is defined %} - log {{ item.log }} -{% endif -%} -{% if item.retries is defined %} - retries {{ item.retries }} -{% endif -%} -{% if item.contimeout is defined %} - contimeout {{ item.contimeout }} -{% endif -%} -{% if item.http_send_name_header is defined %} - http-send-name-header {{ item.http_send_name_header }} -{% endif -%} -{% if item.http_check_expect is defined %} -{% for check_expect in item.http_check_expect %} - http-check expect {{ check_expect }} -{% endfor %} -{% endif -%} -{% if item.options is defined %} -{% for option in item.options %} - option {{ option }} -{% endfor %} -{% endif -%} -{% if item.timeout is defined -%} -{% for entry in item.timeout %} - timeout {{ entry.param }} {{ entry.value }} -{% endfor %} -{% endif -%} -{%- if item.reqadd is defined -%} -{%- for reqadd in item.reqadd -%} - reqadd {{ reqadd }} -{% endfor -%} -{% endif -%} -{%- if item.rspadd is defined -%} -{%- for rspadd in item.rspadd -%} - rspadd {{ rspadd }} -{% endfor -%} -{% endif -%} -{%- if item.reqrep is defined -%} -{%- for reqrep in item.reqrep -%} - reqrep {{ reqrep }} -{% endfor -%} -{% endif -%} -{%- if item.reqirep is defined -%} -{%- for reqirep in item.reqirep -%} - reqirep {{ reqirep }} -{% endfor -%} -{% endif -%} -{%- if item.rsprep is defined -%} -{%- for rsprep in item.rsprep -%} - rsprep {{ rsprep }} -{% endfor -%} -{% endif -%} -{%- if item.rspirep is defined -%} -{%- for rspirep in item.rspirep -%} - rspirep {{ rspirep }} -{% endfor -%} -{% endif -%} -{% if item.appsession is defined %} - appsession {{ item.appsession }} -{% endif -%} -{% if item.stats is defined %} -{% if item.stats.enabled is defined and item.stats.enabled | bool %} - stats enable -{% endif -%} -{% if item.stats.hide_version is defined and item.stats.hide_version | bool %} - stats hide-version -{% endif -%} -{% if item.stats.uri is defined %} - stats uri {{ item.stats.uri }} -{% endif -%} -{% if item.stats.realm is defined %} - stats realm {{ item.stats.realm }} -{% endif -%} -{% if item.stats.auth is defined %} - stats auth {{ item.stats.auth }} -{% endif -%} -{% if item.stats.refresh is defined %} - stats refresh {{ item.stats.refresh }} -{% endif -%} -{% endif %} -{% if item.force_persist is defined -%} - force-persist {{ item.force_persist }} -{% endif -%} diff --git a/templates/resolvers.cfg b/templates/resolvers.cfg deleted file mode 100644 index 1c7ef55..0000000 --- a/templates/resolvers.cfg +++ /dev/null @@ -1,18 +0,0 @@ -#{{ ansible_managed }} -resolvers {{ item.name }} - {% if item.nameservers is defined -%} - {%- for nameserver in item.nameservers -%} - nameserver {{ nameserver.name }} {{ nameserver.ip }}:{{ nameserver.port }} - {% endfor -%} - {% endif -%} - {% if item.hold is defined -%} - {%- for hold in item.hold -%} - hold {{ hold.status }} {{ hold.period }} - {% endfor -%} - {% endif -%} - {% if item.resolve_retries is defined -%} - resolve_retries {{ item.resolve_retries }} - {% endif -%} - {% if item.timeout_retry is defined -%} - timeout retry {{ timeout.timeout_retry }} - {% endif -%} diff --git a/templates/userlist.cfg b/templates/userlist.cfg deleted file mode 100644 index 5507d85..0000000 --- a/templates/userlist.cfg +++ /dev/null @@ -1,14 +0,0 @@ -#{{ ansible_managed }} -userlist {{ item.name }} -{% if item.groups is defined %} -{% for group in item.groups %} - group {{ group.name }} {% if group.users is defined %} users {{ ','.join(group.users) }}{% endif %} - -{% endfor %} -{% endif %} -{% if item.users is defined %} -{% for user in item.users %} - user {{ user.name }} password {{ user.password }}{% if user.groups is defined %} groups {{ ','.join(user.groups) }}{% endif %} - -{% endfor %} -{% endif %} From 2ed293649f6a882f71c8a9a0b73fd3478d7867d5 Mon Sep 17 00:00:00 2001 From: Ben Webber Date: Mon, 27 Nov 2017 00:36:12 +0000 Subject: [PATCH 03/17] Handle boolean switches --- filter_plugins/haproxy.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/filter_plugins/haproxy.py b/filter_plugins/haproxy.py index 470d318..19c320e 100644 --- a/filter_plugins/haproxy.py +++ b/filter_plugins/haproxy.py @@ -69,6 +69,8 @@ def expand(options): elif isinstance(value, collections.Iterable): for item in value: yield (key, item) + elif value is True: + yield (key, None) else: yield (key, value) From 11b7a5db0a1d9c40f59b2ebfc6bfb7a70c01d1f6 Mon Sep 17 00:00:00 2001 From: Ben Webber Date: Mon, 27 Nov 2017 02:11:09 +0000 Subject: [PATCH 04/17] Test defaults/global section expansion --- filter_plugins/__init__.py | 0 filter_plugins/haproxy.py | 47 +++++++++----------- test/filter_plugins/conftest.py | 4 ++ test/filter_plugins/fixtures/config.yml | 52 +++++++++++++++++++++++ test/filter_plugins/fixtures/expected.yml | 30 +++++++++++++ test/filter_plugins/haproxy_test.py | 29 +++++++++++++ 6 files changed, 134 insertions(+), 28 deletions(-) create mode 100644 filter_plugins/__init__.py create mode 100644 test/filter_plugins/conftest.py create mode 100644 test/filter_plugins/fixtures/config.yml create mode 100644 test/filter_plugins/fixtures/expected.yml create mode 100644 test/filter_plugins/haproxy_test.py diff --git a/filter_plugins/__init__.py b/filter_plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/filter_plugins/haproxy.py b/filter_plugins/haproxy.py index 19c320e..ad0d17c 100644 --- a/filter_plugins/haproxy.py +++ b/filter_plugins/haproxy.py @@ -45,48 +45,39 @@ def haproxy_sort(options): ) -def flatten(iterable): - """ - Flatten nested iterables to a single level. - """ - for item in iterable: - if isinstance(item, collections.Iterable) and not isinstance(item, string_types): - for subitem in flatten(item): - yield subitem - else: - yield item - - def expand(options): """ Expand a nested configuration dictionary. """ for key, value in options.items(): - if isinstance(value, collections.Mapping): - yield (key, value.items()) - elif isinstance(value, string_types): + if not isinstance(value, collections.Container) or isinstance(value, string_types): yield (key, value) - elif isinstance(value, collections.Iterable): + continue + elif isinstance(value, collections.Sequence): for item in value: yield (key, item) - elif value is True: - yield (key, None) - else: - yield (key, value) + continue + elif isinstance(value, collections.Mapping): + yield (key, list(expand(value))) + continue + yield (key, value) def to_haproxy(options): """ Yield HAProxy configuration lines from a nested configuration dictionary. """ - options = haproxy_sort(options) - for key, value in expand(options): - if value is None: - yield key - continue - if not isinstance(value, collections.Iterable) or isinstance(value, string_types): - value = [value] - yield '{} {}'.format(key, ' '.join(str(v) for v in flatten(value))) + options = expand(haproxy_sort(options)) + for key, value in options: + if value is True: + yield str(key) + elif value is False: + yield 'no {}'.format(key) + elif isinstance(value, collections.Sequence) and not isinstance(value, string_types): + for sub_key, sub_value in value: + yield '{} {} {}'.format(key, sub_key, sub_value) + else: + yield '{} {}'.format(key, value) class FilterModule(object): diff --git a/test/filter_plugins/conftest.py b/test/filter_plugins/conftest.py new file mode 100644 index 0000000..6b390e7 --- /dev/null +++ b/test/filter_plugins/conftest.py @@ -0,0 +1,4 @@ +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) diff --git a/test/filter_plugins/fixtures/config.yml b/test/filter_plugins/fixtures/config.yml new file mode 100644 index 0000000..fae55bb --- /dev/null +++ b/test/filter_plugins/fixtures/config.yml @@ -0,0 +1,52 @@ +--- +haproxy_defaults: + log: global + mode: http + option: + - httplog + - dontlognull + timeout: + connect: 5000 + client: 50000 + server: 50000 + errorfile: + 400: /etc/haproxy/errors/400.http + 403: /etc/haproxy/errors/403.http + 408: /etc/haproxy/errors/408.http + 500: /etc/haproxy/errors/500.http + 502: /etc/haproxy/errors/502.http + 503: /etc/haproxy/errors/503.http + 504: /etc/haproxy/errors/504.http + + +haproxy_global: + log: + /dev/log: + - local0 + - local1 notice + chroot: /var/lib/haproxy + stats: + socket: /run/haproxy/admin.sock mode 660 level admin + timeout: 30s + user: haproxy + group: haproxy + daemon: true + ca-base: /etc/ssl/certs + crt-base: /etc/ssl/private + ssl-default-bind-ciphers: ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS + ssl-default-bind-options: no-sslv3 + + +haproxy_mailers: + +haproxy_peers: + +haproxy_resolvers: + +haproxy_userlists: + +haproxy_listens: + +haproxy_frontends: + +haproxy_backends: diff --git a/test/filter_plugins/fixtures/expected.yml b/test/filter_plugins/fixtures/expected.yml new file mode 100644 index 0000000..ddf7324 --- /dev/null +++ b/test/filter_plugins/fixtures/expected.yml @@ -0,0 +1,30 @@ +--- +haproxy_defaults: + - log global + - mode http + - option httplog + - option dontlognull + - timeout connect 5000 + - timeout client 50000 + - timeout server 50000 + - errorfile 400 /etc/haproxy/errors/400.http + - errorfile 403 /etc/haproxy/errors/403.http + - errorfile 408 /etc/haproxy/errors/408.http + - errorfile 500 /etc/haproxy/errors/500.http + - errorfile 502 /etc/haproxy/errors/502.http + - errorfile 503 /etc/haproxy/errors/503.http + - errorfile 504 /etc/haproxy/errors/504.http + +haproxy_global: + - log /dev/log local0 + - log /dev/log local1 notice + - chroot /var/lib/haproxy + - stats socket /run/haproxy/admin.sock mode 660 level admin + - stats timeout 30s + - user haproxy + - group haproxy + - daemon + - ca-base /etc/ssl/certs + - crt-base /etc/ssl/private + - ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS + - ssl-default-bind-options no-sslv3 diff --git a/test/filter_plugins/haproxy_test.py b/test/filter_plugins/haproxy_test.py new file mode 100644 index 0000000..7b97397 --- /dev/null +++ b/test/filter_plugins/haproxy_test.py @@ -0,0 +1,29 @@ +import os + +import pytest +import yaml + +from filter_plugins import haproxy + + +@pytest.fixture +def config(): + config_file = os.path.join(os.path.dirname(__file__), 'fixtures', 'config.yml') + return yaml.load(open(config_file)) + + +@pytest.fixture +def expected(): + results_file = os.path.join(os.path.dirname(__file__), 'fixtures', 'expected.yml') + return yaml.load(open(results_file)) + + +@pytest.mark.parametrize( + ('section',), + [ + ('haproxy_defaults',), + ('haproxy_global',) + ] +) +def test_haproxy_defaults(config, expected, section): + assert list(haproxy.to_haproxy(config[section])) == expected[section] From 9464978212d5e287e68c4cdba167ed846a482f0f Mon Sep 17 00:00:00 2001 From: Ben Webber Date: Mon, 27 Nov 2017 02:38:53 +0000 Subject: [PATCH 05/17] Test expansion of all non-proxy sections --- test/filter_plugins/fixtures/config.yml | 55 +++++++++++++++++++++++ test/filter_plugins/fixtures/expected.yml | 46 +++++++++++++++++++ test/filter_plugins/haproxy_test.py | 18 +++++++- 3 files changed, 117 insertions(+), 2 deletions(-) diff --git a/test/filter_plugins/fixtures/config.yml b/test/filter_plugins/fixtures/config.yml index fae55bb..35529bb 100644 --- a/test/filter_plugins/fixtures/config.yml +++ b/test/filter_plugins/fixtures/config.yml @@ -38,12 +38,67 @@ haproxy_global: haproxy_mailers: + mailers1: + mailer: + smtp1: 192.0.2.1:587 + smtp2: 192.0.2.2:587 + mailers2: + mailer: + smtp1: 192.51.100.1:587 + smtp2: 192.51.100.2:587 + haproxy_peers: + peers1: + peer: + haproxy1: 192.0.2.1:1024 + haproxy2: 192.0.2.2:1024 + haproxy3: 192.0.2.3:1024 + peers2: + peer: + haproxy1: 192.51.100.1:1024 + haproxy2: 192.51.100.2:1024 + haproxy3: 192.51.100.3:1024 + haproxy_resolvers: + resolvers1: + nameserver: + dns1: 192.0.2.1:53 + dns2: 192.0.2.2:53 + resolve_retries: 3 + timeout: + retry: 1s + hold: + valid: 10s + resolvers2: + nameserver: + dns1: 192.51.100.1:53 + dns2: 192.51.100.2:53 + resolve_retries: 3 + timeout: + retry: 1s + hold: + valid: 10s + haproxy_userlists: + userlist1: + group: + - G1 users tiger,scott + - G2 users xdb,scott + user: + - tiger password $6$k6y3o.eP$JlKBx9za9667qe4(...)xHSwRv6J.C0/D7cV91 + - scott insecure-password elgato + - xdb insecure-password hello + userlist2: + group: + - G1 + - G2 + user: + - tiger password $6$k6y3o.eP$JlKBx(...)xHSwRv6J.C0/D7cV91 groups G1 + - scott insecure-password elgato groups G1,G2 + - xdb insecure-password hello groups G2 haproxy_listens: diff --git a/test/filter_plugins/fixtures/expected.yml b/test/filter_plugins/fixtures/expected.yml index ddf7324..0da82f9 100644 --- a/test/filter_plugins/fixtures/expected.yml +++ b/test/filter_plugins/fixtures/expected.yml @@ -28,3 +28,49 @@ haproxy_global: - crt-base /etc/ssl/private - ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS - ssl-default-bind-options no-sslv3 + +haproxy_mailers: + mailers1: + - mailer smtp1 192.0.2.1:587 + - mailer smtp2 192.0.2.2:587 + mailers2: + - mailer smtp1 192.51.100.1:587 + - mailer smtp2 192.51.100.2:587 + +haproxy_peers: + peers1: + - peer haproxy1 192.0.2.1:1024 + - peer haproxy2 192.0.2.2:1024 + - peer haproxy3 192.0.2.3:1024 + peers2: + - peer haproxy1 192.51.100.1:1024 + - peer haproxy2 192.51.100.2:1024 + - peer haproxy3 192.51.100.3:1024 + +haproxy_resolvers: + resolvers1: + - nameserver dns1 192.0.2.1:53 + - nameserver dns2 192.0.2.2:53 + - resolve_retries 3 + - timeout retry 1s + - hold valid 10s + resolvers2: + - nameserver dns1 192.51.100.1:53 + - nameserver dns2 192.51.100.2:53 + - resolve_retries 3 + - timeout retry 1s + - hold valid 10s + +haproxy_userlists: + userlist1: + - group G1 users tiger,scott + - group G2 users xdb,scott + - user tiger password $6$k6y3o.eP$JlKBx9za9667qe4(...)xHSwRv6J.C0/D7cV91 + - user scott insecure-password elgato + - user xdb insecure-password hello + userlist2: + - group G1 + - group G2 + - user tiger password $6$k6y3o.eP$JlKBx(...)xHSwRv6J.C0/D7cV91 groups G1 + - user scott insecure-password elgato groups G1,G2 + - user xdb insecure-password hello groups G2 diff --git a/test/filter_plugins/haproxy_test.py b/test/filter_plugins/haproxy_test.py index 7b97397..5cb2894 100644 --- a/test/filter_plugins/haproxy_test.py +++ b/test/filter_plugins/haproxy_test.py @@ -22,8 +22,22 @@ def expected(): ('section',), [ ('haproxy_defaults',), - ('haproxy_global',) + ('haproxy_global',), ] ) -def test_haproxy_defaults(config, expected, section): +def test_haproxy_single_sections(config, expected, section): assert list(haproxy.to_haproxy(config[section])) == expected[section] + + +@pytest.mark.parametrize( + ('section',), + [ + ('haproxy_mailers',), + ('haproxy_peers',), + ('haproxy_resolvers',), + ('haproxy_userlists',), + ] +) +def test_haproxy_multiple_sections(config, expected, section): + for group, options in config[section].items(): + assert list(haproxy.to_haproxy(options)) == expected[section][group] From 3ea637ab99dca0f6288b0491b8ed035d74e06a8d Mon Sep 17 00:00:00 2001 From: Ben Webber Date: Mon, 27 Nov 2017 03:03:36 +0000 Subject: [PATCH 06/17] Test proxy configuration expansion --- test/filter_plugins/fixtures/config.yml | 52 +++++++++++++++++++++-- test/filter_plugins/fixtures/expected.yml | 46 ++++++++++++++++++++ test/filter_plugins/haproxy_test.py | 1 + 3 files changed, 95 insertions(+), 4 deletions(-) diff --git a/test/filter_plugins/fixtures/config.yml b/test/filter_plugins/fixtures/config.yml index 35529bb..d9a6ef5 100644 --- a/test/filter_plugins/fixtures/config.yml +++ b/test/filter_plugins/fixtures/config.yml @@ -100,8 +100,52 @@ haproxy_userlists: - scott insecure-password elgato groups G1,G2 - xdb insecure-password hello groups G2 -haproxy_listens: - -haproxy_frontends: -haproxy_backends: +haproxy_listens: + listen1: + mode: http + bind: + - :80 + - :443 ssl crt /etc/haproxy/site.pem + acl: + - invalid_src src 0.0.0.0/7 224.0.0.0/3 + - invalid_src src_port 0:1023 + - local_dst hdr(host) -i localhost + - es req.fhdr(accept-language),language(es;fr;en) -m str es + - fr req.fhdr(accept-language),language(es;fr;en) -m str fr + - en req.fhdr(accept-language),language(es;fr;en) -m str en + tcp-request: + connection: + - accept if { src -f /etc/haproxy/whitelist.lst } + - reject if { src_conn_rate gt 10 } + - track-sc0 src + block: + - if invalid_src || local_dst + http-request: + deny: + - if invalid_src || local_dst + reqdel: + - ^X-Forwarded-For:.* + - ^Cookie:.*SERVER= + reqpass: + - ^Host:\ www.private\.local + reqadd: + - X-Proto:\ SSL if is-ssl + redirect: + - prefix https://mysite.com set-cookie SEEN=1 if !cookie_set + - prefix https://mysite.com if login_page !secure + - prefix http://mysite.com drop-query if login_page !uid_given + - location http://mysite.com/ if !login_page secure + - location / clear-cookie USERID= if logout + use_backend: + - backend1 if is-app1 + - backend2 if is-app2 + use-server: + - www if { req_ssl_sni -i www.example.com } + - mail if { req_ssl_sni -i mail.example.com } + - imap if { req_ssl_sni -i imap.example.com } + server: + www: 192.0.2.1:443 weight 0 + mail: 192.0.2.1:587 weight 0 + imap: 192.0.2.1:993 weight 0 + default: 192.0.2.2:443 check diff --git a/test/filter_plugins/fixtures/expected.yml b/test/filter_plugins/fixtures/expected.yml index 0da82f9..d01c056 100644 --- a/test/filter_plugins/fixtures/expected.yml +++ b/test/filter_plugins/fixtures/expected.yml @@ -74,3 +74,49 @@ haproxy_userlists: - user tiger password $6$k6y3o.eP$JlKBx(...)xHSwRv6J.C0/D7cV91 groups G1 - user scott insecure-password elgato groups G1,G2 - user xdb insecure-password hello groups G2 + +haproxy_listens: + listen1: + # rank -1 + - acl invalid_src src 0.0.0.0/7 224.0.0.0/3 + - acl invalid_src src_port 0:1023 + - acl local_dst hdr(host) -i localhost + - acl es req.fhdr(accept-language),language(es;fr;en) -m str es + - acl fr req.fhdr(accept-language),language(es;fr;en) -m str fr + - acl en req.fhdr(accept-language),language(es;fr;en) -m str en + # rank 0, in order + - mode http + - bind :80 + - bind :443 ssl crt /etc/haproxy/site.pem + # rank 2 + - tcp-request connection accept if { src -f /etc/haproxy/whitelist.lst } + - tcp-request connection reject if { src_conn_rate gt 10 } + - tcp-request connection track-sc0 src + # rank 3 + - block if invalid_src || local_dst + # rank 4 + - http-request deny if invalid_src || local_dst + # rank 5, in order + - reqdel ^X-Forwarded-For:.* + - reqdel ^Cookie:.*SERVER= + - reqpass ^Host:\ www.private\.local + # rank 6 + - reqadd X-Proto:\ SSL if is-ssl + # rank 7 + - redirect prefix https://mysite.com set-cookie SEEN=1 if !cookie_set + - redirect prefix https://mysite.com if login_page !secure + - redirect prefix http://mysite.com drop-query if login_page !uid_given + - redirect location http://mysite.com/ if !login_page secure + - redirect location / clear-cookie USERID= if logout + # rank 8 + - use_backend backend1 if is-app1 + - use_backend backend2 if is-app2 + # rank 9 + - use-server www if { req_ssl_sni -i www.example.com } + - use-server mail if { req_ssl_sni -i mail.example.com } + - use-server imap if { req_ssl_sni -i imap.example.com } + # rank 100 + - server www 192.0.2.1:443 weight 0 + - server mail 192.0.2.1:587 weight 0 + - server imap 192.0.2.1:993 weight 0 + - server default 192.0.2.2:443 check diff --git a/test/filter_plugins/haproxy_test.py b/test/filter_plugins/haproxy_test.py index 5cb2894..2949c26 100644 --- a/test/filter_plugins/haproxy_test.py +++ b/test/filter_plugins/haproxy_test.py @@ -36,6 +36,7 @@ def test_haproxy_single_sections(config, expected, section): ('haproxy_peers',), ('haproxy_resolvers',), ('haproxy_userlists',), + ('haproxy_listens',), ] ) def test_haproxy_multiple_sections(config, expected, section): From 085c3b73c8c36bb81589a6075d096ec11f5457bc Mon Sep 17 00:00:00 2001 From: Ben Webber Date: Mon, 27 Nov 2017 03:05:32 +0000 Subject: [PATCH 07/17] Test boolean expansion --- test/filter_plugins/fixtures/config.yml | 3 +++ test/filter_plugins/fixtures/expected.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/test/filter_plugins/fixtures/config.yml b/test/filter_plugins/fixtures/config.yml index d9a6ef5..3774e83 100644 --- a/test/filter_plugins/fixtures/config.yml +++ b/test/filter_plugins/fixtures/config.yml @@ -149,3 +149,6 @@ haproxy_listens: mail: 192.0.2.1:587 weight 0 imap: 192.0.2.1:993 weight 0 default: 192.0.2.2:443 check + listen2: + log: false + disabled: true diff --git a/test/filter_plugins/fixtures/expected.yml b/test/filter_plugins/fixtures/expected.yml index d01c056..c380b88 100644 --- a/test/filter_plugins/fixtures/expected.yml +++ b/test/filter_plugins/fixtures/expected.yml @@ -120,3 +120,6 @@ haproxy_listens: - server mail 192.0.2.1:587 weight 0 - server imap 192.0.2.1:993 weight 0 - server default 192.0.2.2:443 check + listen2: + - no log + - disabled From e332a0e733e74abffe93e177fc572e7a106f2b25 Mon Sep 17 00:00:00 2001 From: Ben Webber Date: Mon, 27 Nov 2017 03:15:28 +0000 Subject: [PATCH 08/17] Update default configuration with new syntax --- defaults/main.yml | 63 +++++-------- vars/main.yml | 233 ---------------------------------------------- 2 files changed, 21 insertions(+), 275 deletions(-) diff --git a/defaults/main.yml b/defaults/main.yml index cad51cd..696d17d 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -14,37 +14,29 @@ _haproxy_ssl_ciphers: 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305 haproxy_global: log: - - address: /dev/log - facility: local0 - - address: /dev/log - facility: local1 - level: notice + /dev/log: + - local0 + - local1 notice chroot: /var/lib/haproxy user: haproxy group: haproxy daemon: true - ssl_default_bind_options: '{{ _haproxy_ssl_options }}' - ssl_default_bind_ciphers: '{{ _haproxy_ssl_ciphers }}' - ssl_default_server_options: '{{ _haproxy_ssl_options }}' - ssl_default_server_ciphers: '{{ _haproxy_ssl_ciphers }}' - tune: - ssl: - default-dh-param: 2048 + ssl-default-bind-options: '{{ _haproxy_ssl_options }}' + ssl-default-bind-ciphers: '{{ _haproxy_ssl_ciphers }}' + ssl-default-server-options: '{{ _haproxy_ssl_options }}' + ssl-default-server-ciphers: '{{ _haproxy_ssl_ciphers }}' + tune.ssl.default-dh-param: 2048 haproxy_defaults: mode: http log: - - address: /dev/log - facility: local1 - level: notice + /dev/log: + - local1 notice timeout: - - param: 'connect' - value: '5000ms' - - param: 'client' - value: '50000ms' - - param: 'server' - value: '50000ms' - options: + connect: 5000ms + client: 50000ms + server: 50000ms + option: - httpclose - forwardfor except 127.0.0.0/8 - redispatch @@ -52,23 +44,10 @@ haproxy_defaults: - httplog - dontlognull errorfile: - - code: 400 - file: /etc/haproxy/errors/400.http - - code: 403 - file: /etc/haproxy/errors/403.http - - code: 408 - file: /etc/haproxy/errors/408.http - - code: 500 - file: /etc/haproxy/errors/500.http - - code: 502 - file: /etc/haproxy/errors/502.http - - code: 503 - file: /etc/haproxy/errors/503.http - - code: 504 - file: /etc/haproxy/errors/504.http - -haproxy_resolvers: [] -haproxy_backends: [] -haproxy_frontends: [] -haproxy_listen: [] -haproxy_userlists: [] + 400: /etc/haproxy/errors/400.http + 403: /etc/haproxy/errors/403.http + 408: /etc/haproxy/errors/408.http + 500: /etc/haproxy/errors/500.http + 502: /etc/haproxy/errors/502.http + 503: /etc/haproxy/errors/503.http + 504: /etc/haproxy/errors/504.http diff --git a/vars/main.yml b/vars/main.yml index 3baea50..0161f47 100644 --- a/vars/main.yml +++ b/vars/main.yml @@ -1,235 +1,2 @@ --- - empty: true -#haproxy_global: -# chroot: -# pidfile: -# maxconn: -# user: -# uid: -# group: -# gid: -# daemon: -# nbproc: -# spread_checks: -# stats: -# socket: -# timeout: -# log: -# - address: -# facility: -# level: -# minlevel: -# format: -# ssl_default_bind_options: -# ssl_default_bind_ciphers: -# tune: -# chksize: 32768 -# ssl: -# default-dh-param: 2048 -# zlib: -# memlevel: 9 -# -#haproxy_defaults: -# mode: -# log: -# - address: -# facility: -# level: -# minlevel: -# format: -# options: -# -