From 4f3f9e0fa3a1f4707749c58434da40cb2ab6e234 Mon Sep 17 00:00:00 2001 From: uumas Date: Tue, 14 Mar 2023 01:59:35 +0200 Subject: [PATCH] vhost: add support for locations, headers, and responds --- roles/vhost/{vars => defaults}/main.yml | 6 ++ roles/vhost/meta/argument_specs.yml | 117 +++++++++++++++++++++++- roles/vhost/tasks/caddy.yml | 32 +++++-- roles/vhost/tasks/main.yml | 40 +++++++- 4 files changed, 180 insertions(+), 15 deletions(-) rename roles/vhost/{vars => defaults}/main.yml (52%) diff --git a/roles/vhost/vars/main.yml b/roles/vhost/defaults/main.yml similarity index 52% rename from roles/vhost/vars/main.yml rename to roles/vhost/defaults/main.yml index 01795d9..acdec77 100644 --- a/roles/vhost/vars/main.yml +++ b/roles/vhost/defaults/main.yml @@ -2,7 +2,13 @@ web_server: caddy +vhost_locations: [] +vhost_headers: {} + proxy_target_protocol: http proxy_target_host: localhost redirect_type: temporary +redirect_preserve_path: false + +respond_content_type: plain diff --git a/roles/vhost/meta/argument_specs.yml b/roles/vhost/meta/argument_specs.yml index 978903e..13d0ee8 100644 --- a/roles/vhost/meta/argument_specs.yml +++ b/roles/vhost/meta/argument_specs.yml @@ -14,6 +14,7 @@ argument_specs: choices: - reverse_proxy - redirect + - respond vhost_domains: type: list required: true @@ -26,26 +27,134 @@ argument_specs: choices: - caddy - none + vhost_headers: + description: dict of headers and their values + type: dict + required: false + default: {} proxy_target_port: - description: Required and only applicable if vhost_type is reverse_proxy + description: Port where to proxy requests to. Only applicable if vhost_type is reverse_proxy type: int + required: "{{ vhost_type == 'reverse_proxy' }}" proxy_target_host: - description: Only applicable if vhost_type is reverse_proxy + description: Host where to proxy requests to. Only applicable if vhost_type is reverse_proxy type: str + required: false default: localhost proxy_target_protocol: - description: Only applicable if vhost_type is reverse_proxy + description: Protocol to use for proxy requests. Only applicable if vhost_type is reverse_proxy type: str + required: false default: http + choices: + - http + - https redirect_target: - description: "Required and only applicable if vhost_type is redirect. Example: https://www.domain.tld/location" + description: "Only applicable if vhost_type is redirect. Example: https://www.domain.tld/location" type: str + required: "{{ vhost_type == 'redirect' }}" + redirect_preserve_path: + description: Whether to keep the original request path + type: bool + required: false + default: false redirect type: description: Only applicable if vhost_type is reverse_proxy type: str + required: false default: temporary choices: - temporary - permanent + + respond_content: + description: Content to respond with. Json content can be set as yaml as long as respond_content_type is set to json + type: str + required: "{{ vhost_type == 'respond' }}" + respond_content_type: + description: Type of the respond content + type: str + required: false + default: plain + choices: + - plain + - json + + vhost_locations: + description: List of locations to handle differently from the default for vhost + type: list + required: false + default: [] + elements: dict + options: + path: + description: Path to match. Only supports full paths for now. + type: str + required: true + type: + type: str + required: false + default: "{{ vhost_type }}" + choices: + - reverse_proxy + - redirect + - respond + headers: + description: dict of headers and their values + type: dict + required: false + default: "{{ vhost_headers }}" + + proxy_target_port: + description: Port where to proxy requests to. Only applicable if type is reverse_proxy. + type: int + required: false + default: "{{ proxy_target_port if vhost_type == 'reverse_proxy' else 0 }}" + proxy_target_host: + description: Host where to proxy requests to. Only applicable if type is reverse_proxy + type: str + required: false + default: "{{ proxy_target_host }}" + proxy_target_protocol: + description: Protocol to use for proxy requests. Only applicable if type is reverse_proxy + type: str + required: false + default: "{{ proxy_target_protocol }}" + choices: + - http + - https + + redirect_target: + description: "Only applicable if vhost_type is redirect. Example: https://www.domain.tld/location" + type: str + required: false + default: "{{ redirect_target if vhost_type == 'redirect' else '' }}" + redirect_preserve_path: + description: Whether to keep the original request path + type: bool + required: false + default: "{{ redirect_preserve_path }}" + redirect type: + description: Only applicable if vhost_type is reverse_proxy + type: str + required: false + default: "{{ redirect_type }}" + choices: + - temporary + - permanent + + respond_content: + description: Content to respond with. Json content can be set as yaml as long as respond_content_type is set to json + type: str + required: false + default: "{{ respond_content if vhost_type == 'respond' else '' }}" + respond_content_type: + description: Type of the respond content + type: str + required: false + default: "{{ respond_content_type }}" + choices: + - plain + - json diff --git a/roles/vhost/tasks/caddy.yml b/roles/vhost/tasks/caddy.yml index e24b7ff..20d449f 100644 --- a/roles/vhost/tasks/caddy.yml +++ b/roles/vhost/tasks/caddy.yml @@ -6,18 +6,30 @@ marker: "# {mark} ANSIBLE MANAGED BLOCK {{ vhost_id }}" block: | {{ vhost_domains | join(' ') }} { - {% if vhost_type == 'reverse_proxy' %} - reverse_proxy {{ proxy_target_protocol }}://{{ proxy_target_host }}:{{ proxy_target_port }} { - {% if proxy_target_protocol == 'https' and proxy_target_host == 'localhost' %} - transport http { - tls_insecure_skip_verify + {% for location in vhost_locations_all %} + handle {{ location.path }} { + {% for header in location.headers | dict2items %} + header {{ header.key }} `{{ header.value }}` + {% endfor %} + {% if location.type == 'reverse_proxy' %} + reverse_proxy {{ location.proxy_target_protocol }}://{{ location.proxy_target_host }}:{{ location.proxy_target_port }} { + {% if location.proxy_target_protocol == 'https' and location.proxy_target_host == 'localhost' %} + transport http { + tls_insecure_skip_verify + } + {% endif %} } - {% endif %} + {% elif location.type == 'redirect' %} + redir {{ location.redirect_target }}{{ '{uri}' if location.redirect_preserve_path }} {{ location.redirect_type }} + {% elif location.type == 'respond' %} + {% if location.respond_content_type == 'json' %} + respond `{{ location.respond_content | to_json }}` + {% else %} + respond `{{ location.respond_content }}` + {% endif %} + {% endif %} } - {% endif %} - {% if vhost_type == 'redirect' %} - redir {{ redirect_target }} {{ redirect_type }} - {% endif %} + {% endfor %} } validate: 'caddy validate --config %s --adapter caddyfile' backup: true diff --git a/roles/vhost/tasks/main.yml b/roles/vhost/tasks/main.yml index 58cfa3d..31a7ed6 100644 --- a/roles/vhost/tasks/main.yml +++ b/roles/vhost/tasks/main.yml @@ -1,6 +1,44 @@ --- +- name: Fail if redirect_target is a relative path and redirect_preserve_path is true + fail: + msg: redirect_target must be an absolute url or absolute path if redirect_preserve_path is true + when: + - redirect_preserve_path + - redirect_target.split('://') | length < 2 + - not redirect_target.startswith('/') -- name: "Setup {{ vhost_id }} vhost on {{ web_server }}" +- name: Fail if redirect_tartget ends with / and redirect_preserve_path is true + fail: + msg: redirect_target must not end with / if redirect_preserve_path is true + when: + - redirect_preserve_path + - redirect_target.endswith('/') + +- name: Reset vhost_locations_all + set_fact: + vhost_locations_all: [] +- name: Set vhost_locations_all reverse proxies + set_fact: + vhost_locations_all: > + {{ vhost_locations_all + [{ + 'path': item.path, + 'type': item.type | default(vhost_type), + 'headers': item.headers | default(vhost_headers), + + 'proxy_target_port': item.proxy_target_port | default(proxy_target_port if vhost_type == 'reverse_proxy' else ''), + 'proxy_target_host': item.proxy_target_host | default(proxy_target_host), + 'proxy_target_protocol': item.proxy_target_protocol | default(proxy_target_protocol), + + 'redirect_target': item.redirect_target | default(redirect_target if vhost_type == 'redirect' else ''), + 'redirect_preserve_path': item.redirect_preserve_path | default(redirect_preserve_path), + 'redirect_type': item.redirect_type | default(redirect_type), + + 'respond_content': item.respond_content | default(respond_content if vhost_type == 'respond' else ''), + 'respond_content_type': item.respond_content_type | default(respond_content_type) + }] }} + loop: "{{ vhost_locations + [{'path': ''}] }}" + +- name: "Setup {{ vhost_id + ' vhost on ' + web_server }}" include_tasks: "{{ web_server }}.yml" when: web_server != 'none'