From 4875e6ef1fed42ddf7e08e6f05b147aa5e4e55ff Mon Sep 17 00:00:00 2001 From: AnsibleGuy Date: Tue, 2 Nov 2021 22:06:52 +0100 Subject: [PATCH] init --- LICENSE.txt | 21 +++ README.md | 50 ++++++ defaults/main.yml | 148 ++++++++++++++++ filter_plugins/utils.py | 18 ++ meta/main.yml | 23 +++ playbook.yml | 9 + requirements.yml | 9 + tasks/debian/add_site.yml | 68 ++++++++ tasks/debian/letsencrypt/cleanup.yml | 13 ++ tasks/debian/letsencrypt/dependencies.yml | 23 +++ tasks/debian/letsencrypt/domain.yml | 45 +++++ tasks/debian/letsencrypt/domain_new.yml | 26 +++ tasks/debian/letsencrypt/main.yml | 41 +++++ tasks/debian/main.yml | 100 +++++++++++ tasks/debian/rm_site.yml | 24 +++ tasks/main.yml | 5 + .../apache2/sites-available/le_dummy.conf.j2 | 6 + .../etc/apache2/sites-available/site.conf.j2 | 163 ++++++++++++++++++ ...infra_apache.LetsEncryptCertbot.service.j2 | 7 + ...y.infra_apache.LetsEncryptCertbot.timer.j2 | 10 ++ 20 files changed, 809 insertions(+) create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 defaults/main.yml create mode 100644 filter_plugins/utils.py create mode 100644 meta/main.yml create mode 100644 playbook.yml create mode 100644 requirements.yml create mode 100644 tasks/debian/add_site.yml create mode 100644 tasks/debian/letsencrypt/cleanup.yml create mode 100644 tasks/debian/letsencrypt/dependencies.yml create mode 100644 tasks/debian/letsencrypt/domain.yml create mode 100644 tasks/debian/letsencrypt/domain_new.yml create mode 100644 tasks/debian/letsencrypt/main.yml create mode 100644 tasks/debian/main.yml create mode 100644 tasks/debian/rm_site.yml create mode 100644 tasks/main.yml create mode 100644 templates/etc/apache2/sites-available/le_dummy.conf.j2 create mode 100644 templates/etc/apache2/sites-available/site.conf.j2 create mode 100644 templates/systemd/system/ansibleguy.infra_apache.LetsEncryptCertbot.service.j2 create mode 100644 templates/systemd/system/ansibleguy.infra_apache.LetsEncryptCertbot.timer.j2 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..3c645d7 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 AnsibleGuy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d2d5859 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# Apache2 Ansible Role +Ansible role to install apache2 sites on the target server. + +**Tested:** +* Debian 11 + +## Functionality + +* Package installation + * Ansible dependencies (_minimal_) + * Apache2 +* Configuration + * + * Default opt-in: + * + * Default opt-outs: + * + * Default config: + * + +## Info + +* **Note:** Most of this functionality can be opted in or out using the main defaults file and variables! + + +* **Note:** this role currently only supports debian-based systems + +## Requirements + +* Community collection: ```ansible-galaxy install -r requirements.yml``` + + +## Usage +Run the playbook: +```bash +ansible-playbook -K -D -i inventory/hosts.yml playbook.yml +``` + +You need to define your instances by configuring the 'mariadb' dictionary! + +```yaml +apache + +``` + +There are also some useful **tags** available: +* base => only configure basics; sites will not be touched +* sites +* config => configuration (base and instances) +* certs diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..d1bbf04 --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,148 @@ +--- + +# main switches +configure_anti_ddos: true # mod_evasive +configure_security: true # https://www.digitalocean.com/community/tutorials/how-to-set-up-mod_security-with-apache-on-debian-ubuntu + +# default config => is overwritten by provided config +default_apache: + sites: {} + + log: + path: '/var/log/apache2' + per_site: true + syslog: true + syslog_host: + syslog_port: 514 + syslog_max_size: '4KiB' # see: https://manpages.ubuntu.com/manpages/xenial/man1/logger.1.html + prefix_ue: 'apache_plain_' + prefix_ssl: 'apache_ssl_' + + user: 'www-data' + group: 'www-data' + + # additions to the main apache config + config: # see: https://httpd.apache.org/docs/2.4/mod/core.html + ServerTokens: 'Prod' + ServerSignature: 'Off' + FileETag: 'None' + KeepAlive: 'On' + KeepAliveTimeout: 5 + MaxKeepAliveRequests: 100 + LimitRequestBody: 51200000 # 50MB => if you use file-uploads you might need to change this (0=unlimited, max=2147483647 [2GB]) + LimitRequestFields: 50 + LimitRequestFieldSize: 8190 + LimitRequestLine: 8190 + LimitXMLRequestBody: 1000000 + TimeOut: 60 + TraceEnable: 'off' + # ssl option => see: https://httpd.apache.org/docs/2.4/mod/mod_ssl.html + SSLProtocol: 'ALL -TLSv1.1 -TLSv1 -SSLv2 -SSLv3' + SSLCipherSuite: 'ALL:+HIGH:!ADH:!EXP:!SSLv2:!SSLv3:!MEDIUM:!LOW:!NULL:!aNULL' + SSLHonorCipherOrder: 'on' + SSLOptions: '+StrictRequire' + SSLSessionTickets: 'off' + SSLCompression: 'off' + + headers: # https://htaccessbook.com/important-security-headers/ | https://geekflare.com/http-header-implementation/ + 'Header always set Strict-Transport-Security': '"max-age=31536000; includeSubDomains; preload"' + 'Referrer-Policy': '"same-origin"' + 'Content-Security-Policy': "\"default-src 'self';\"" + 'X-Frame-Options': 'SAMEORIGIN' + 'X-Content-Type-Options': 'nosniff' + 'X-Permitted-Cross-Domain-Policies': '"none"' + 'X-XSS-Protection': '"1; mode=block"' + 'Header always edit Set-Cookie ^(.*)$': '$1;HttpOnly;Secure;SameSite=None' + # 'Header set Permissions-Policy': '"none"' + # 'Header set Content-Security-Policy': '"default-src https:; font-src https:; img-src https:; script-src https:; style-src https:;"' + + + modules: + present: ['ssl', 'headers', 'rewrite'] + absent: ['autoindex'] + + letsencrypt: + key_size: 4096 + path: '/etc/letsencrypt' + path_key: '/etc/ssl/private' + path_cert: '/etc/ssl/certs' + renew_timer: 'Mon *-*-* 00:00:00' + verbosity: 'v' + +APACHE_CONFIG: "{{ default_apache | combine(apache, recursive=true) }}" + +# site-specific config +default_site_config: + mode: 'serve' + admin: 'apache@template.ansibleguy.net' + port_plain: 80 + port_ssl: 443 + + config: {} # site-specific setting-value pairs + config_additions: [] # lines that will 1-to-1 be appended to the site-config + + security: # https://www.nixpal.com/apache-httpd-hardening/ + disable_root_index: true + disable_directory_access: true + disable_ssi_cgi: true + limit_directory_access: true + + redirect: + target: 'https://github.com/ansibleguy' + request_uri: true + + serve: + path: '/var/www/html' + + ssl: + mode: 'letsencrypt' # local/selfsigned/letsencrypt + file_pub: '/etc/apache2/ssl/DOMAIN.crt' # should use the certificate chain => top is server cert; bottom root cert + file_key: '/etc/apache2/ssl/DOMAIN.key' + file_csr: '/etc/apache2/ssl/DOMAIN.csr' + file_ca: + csr_data: + country: 'AT' + org: 'AnsibleGuy' + email: 'apache@template.ansibleguy.net' + cn: 'Apache Certificate' + +default_modules: + # + # + prefork: # see: https://httpd.apache.org/docs/2.4/mod/mpm_common.html + ifname: 'prefork.c' + settings: + StartServers: 5 + MinSpareServers: 5 + MaxSpareServers: 10 + MaxRequestWorkers: 256 + MaxConnectionsPerChild: 0 + mod_evasive: + ifname: 'mod_evasive20.c' + settings: + DOSHashTableSize: 4096 + DOSPageCount: 25 + DOSSiteCount: 100 + DOSPageInterval: 1 + DOSSiteInterval: 1 + DOSBlockingPeriod: 60 + DOSLogDir: "{{ CONFIG.log.path }}" + # DOSSystemCommand: + # DOSEmailNotify: mail@yourdomain.com + DOSWhitelist: [ + '127.0.0.*', '192.168.*.*', '10.*.*.*', '172.16.*.*', '172.17.*.*', '172.18.*.*', '172.19.*.*', + '172.20.*.*', '172.21.*.*', '172.22.*.*', '172.23.*.*', '172.24.*.*', '172.25.*.*', '172.26.*.*', + '172.27.*.*', '172.28.*.*', '172.29.*.*', '172.30.*.*', '172.31.*.*', '172.32.*.*', + ] + +APACHE_MODULES: "{{ default_modules | combine(modules, recursive=true) }}" + +packages: + apache: ['apache2'] + letsencrypt: ['python3-certbot-apache'] + +apache_config_graylist: [ + 'SSLEngine', 'SSLCertificateKeyFile', 'SSLCertificateFile', 'SSLCertificateChainFile', 'ErrorLog', 'CustomLog', 'ServerAdmin', + 'ServerAlias', 'ServerName', 'Redirect' +] +apache_restricted_methods: ['GET', 'POST', 'HEAD'] diff --git a/filter_plugins/utils.py b/filter_plugins/utils.py new file mode 100644 index 0000000..67971d1 --- /dev/null +++ b/filter_plugins/utils.py @@ -0,0 +1,18 @@ +from re import sub as regex_replace + + +class FilterModule(object): + + def filters(self): + return { + "safe_key": self.safe_key, + "all_true": self.all_true, + } + + @staticmethod + def safe_key(key: str) -> str: + return regex_replace('[^0-9a-zA-Z]+', '', key.replace(' ', '_')) + + @staticmethod + def all_true(data: list) -> bool: + return all(data) diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..17be099 --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,23 @@ +--- + +galaxy_info: + author: 'AnsibleGuy ' + readme: 'README.md' + license: 'MIT' + repository: 'https://github.com/ansibleguy/infra_apache' + issue_tracker_url: 'https://github.com/ansibleguy/infra_apache/issues' + github_branch: 'stable' + min_ansible_version: 2.9.0 + description: 'Role to deploy apache2 sites on a linux server' + platforms: + - name: Debian + versions: + - bullseye + galaxy_tags: + - 'web' + - 'webserver' + - 'apache' + +collections: + - 'community.crypto' + - 'community.general' diff --git a/playbook.yml b/playbook.yml new file mode 100644 index 0000000..d2c198f --- /dev/null +++ b/playbook.yml @@ -0,0 +1,9 @@ +--- + +# ansible-playbook -K -D -i inventory/hosts.yml playbook.yml + +- hosts: all # should be limited to web-servers + become: true + gather_facts: yes + roles: + - ansibleguy.infra_apache diff --git a/requirements.yml b/requirements.yml new file mode 100644 index 0000000..3cfb7b6 --- /dev/null +++ b/requirements.yml @@ -0,0 +1,9 @@ +# external roles and collections to download +# install: ansible-galaxy install -r requirements.yml + +collections: + - name: 'community.crypto' + source: 'https://galaxy.ansible.com' + + - name: 'community.general' + source: 'https://galaxy.ansible.com' diff --git a/tasks/debian/add_site.yml b/tasks/debian/add_site.yml new file mode 100644 index 0000000..5448f5f --- /dev/null +++ b/tasks/debian/add_site.yml @@ -0,0 +1,68 @@ +--- + +- name: "Apache | Debian | Config | Site '{{ name }}' | Configuring listen-ports" + ansible.builtin.blockinfile: + path: '/etc/apache2/ports.conf' + block: | + Listen {{ port }} + marker: "# {mark} ANSIBLE MANAGED BLOCK - port '{{ port }}'" + insertafter: '# /etc/apache2/sites-enabled/000-default.conf' + ignore_errors: true + when: + - port != 80 + - port != 443 + - port != '80' + - port != '443' + loop_control: + loop_var: port + with_items: + - "{{ site.port_plain }}" + - "{{ site.port_ssl }}" + +- name: "Apache | Debian | Config | Site '{{ name }}' | Create root directory" + ansible.builtin.file: + path: "{{ site.serve.path }}" + state: directory + owner: "{{ APACHE_CONFIG.user }}" + group: "{{ APACHE_CONFIG.group }}" + mode: 0755 + when: site.mode == 'serve' + +- name: "Apache | Debian | Config | Site '{{ name }}' | Configuring site" + ansible.builtin.template: + src: 'templates/etc/apache2/sites-available/site.conf.j2' + dest: "/etc/apache2/sites-available/site_{{ name }}.conf" + owner: 'root' + group: 'root' + mode: 0644 + validate: 'apachectl -t -f %s' + register: apache_config_deployment + ignore_errors: yes + +- name: "Apache | Debian | Config | Site '{{ name }}' | Ask user" + ansible.builtin.pause: + prompt: "The apache config validation failed! Sometimes this is a false-negative. + Do you want to force the deployment? (yes/no)" + register: force_deploy + when: apache_config_deployment.failed + +- name: "Apache | Debian | Config | Site '{{ name }}' | Configuring site (forced)" + ansible.builtin.template: + src: 'templates/etc/apache2/sites-available/site.conf.j2' + dest: "/etc/apache2/sites-available/site_{{ name }}.conf" + owner: 'root' + group: 'root' + mode: 0644 + backup: true + when: + - apache_config_deployment.failed + - force_deploy.user_input == 'yes' + +- name: "Apache | Debian | Config | Site '{{ name }}' | Enabling site" + ansible.builtin.file: + state: link + src: "/etc/apache2/sites-available/site_{{ name }}.conf" + dest: "/etc/apache2/sites-enabled/site_{{ name }}.conf" + owner: 'root' + group: 'root' + mode: 0644 diff --git a/tasks/debian/letsencrypt/cleanup.yml b/tasks/debian/letsencrypt/cleanup.yml new file mode 100644 index 0000000..239ccc2 --- /dev/null +++ b/tasks/debian/letsencrypt/cleanup.yml @@ -0,0 +1,13 @@ +--- + +- name: Apache | Debian | LetsEncrypt Certbot | Cleanup | Disable temporary apache site + ansible.builtin.file: + state: absent + dest: '/etc/apache2/sites-enabled/tmp_le_dummy.conf' + register: tmp_site_config + +- name: Apache | Debian | LetsEncrypt Certbot | Cleanup | Reload apache + ansible.builtin.systemd: + name: 'apache2.service' + state: reloaded + when: tmp_site_config.changed diff --git a/tasks/debian/letsencrypt/dependencies.yml b/tasks/debian/letsencrypt/dependencies.yml new file mode 100644 index 0000000..bf562cc --- /dev/null +++ b/tasks/debian/letsencrypt/dependencies.yml @@ -0,0 +1,23 @@ +--- + +- name: Apache | Debian | LetsEncrypt Certbot | Dependencies | Deploying temporary apache site + ansible.builtin.template: + src: 'templates/etc/apache2/sites-available/le_dummy.conf.j2' + dest: '/etc/apache2/sites-available/tmp_le_dummy.conf' + owner: 'root' + group: 'root' + mode: 0644 + +- name: Apache | Debian | LetsEncrypt Certbot | Dependencies | Enable apache site + ansible.builtin.file: + state: link + src: '/etc/apache2/sites-available/tmp_le_dummy.conf' + dest: '/etc/apache2/sites-enabled/tmp_le_dummy.conf' + owner: 'root' + group: 'root' + mode: 0644 + +- name: Apache | Debian | LetsEncrypt Certbot | Dependencies | Reload apache + ansible.builtin.systemd: + name: 'apache2.service' + state: reloaded diff --git a/tasks/debian/letsencrypt/domain.yml b/tasks/debian/letsencrypt/domain.yml new file mode 100644 index 0000000..c1a3006 --- /dev/null +++ b/tasks/debian/letsencrypt/domain.yml @@ -0,0 +1,45 @@ +--- + +- name: "Apache | Debian | LetsEncrypt Certbot | Checking if cert for domain '{{ site.domain }}' exists" + ansible.builtin.shell: 'certbot certificates' + register: domain_cert + changed_when: false + +# todo: check domains registered in current certificate (certbot certificates) and remove it if there are more than configured before re-configuring it + +- name: "Apache | Debian | LetsEncrypt Certbot | Set key/cert paths for domain '{{ site.domain }}'" + ansible.builtin.set_fact: + _path_key: "{{ APACHE_CONFIG.letsencrypt.path_key }}/{{ name }}" + _path_cert: "{{ APACHE_CONFIG.letsencrypt.path_cert }}/{{ name }}" + _path_live: "{{ APACHE_CONFIG.letsencrypt.path }}/live/{{ name }}" + +- name: "Apache | Debian | LetsEncrypt Certbot | Creating key/cert directories for domain '{{ site.domain }}'" + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: 'root' + group: 'root' + mode: 0755 + with_items: + - "{{ _path_key }}" + - "{{ _path_cert }}" + +- name: Apache | Debian | LetsEncrypt Certbot | Getting cert + ansible.builtin.include_tasks: domain_new.yml + when: domain_cert.stdout.find(site.domain) == -1 + +- name: "Apache | Debian | LetsEncrypt Certbot | Linking certificates for domain '{{ site.domain }}'" + ansible.builtin.file: + state: link + src: "{{ item.value.src }}" + dest: "{{ item.value.dst }}" + owner: "{{ APACHE_CONFIG.user }}" + group: "{{ APACHE_CONFIG.group }}" + mode: 0400 + follow: yes + with_dict: + - {'config': {'dst': "{{ _path_key }}/privkey.pem", 'src': "{{ _path_live }}/privkey.pem"}} + - {'config': {'dst': "{{ _path_cert }}/cert.pem", 'src': "{{ _path_live }}/cert.pem"}} + - {'config': {'dst': "{{ _path_cert }}/chain.pem", 'src': "{{ _path_live }}/chain.pem"}} + - {'config': {'dst': "{{ _path_cert }}/fullchain.pem", 'src': "{{ _path_live }}/fullchain.pem"}} + ignore_errors: yes diff --git a/tasks/debian/letsencrypt/domain_new.yml b/tasks/debian/letsencrypt/domain_new.yml new file mode 100644 index 0000000..ad3a373 --- /dev/null +++ b/tasks/debian/letsencrypt/domain_new.yml @@ -0,0 +1,26 @@ +--- + +- name: "Apache | Debian | LetsEncrypt Certbot | Creating alternative name string (1/3)" + ansible.builtin.set_fact: + _aliases: "{{ site.aliases | join(' --domain ') }}" + when: apache_aliases | length > 0 + +- name: "Apache | Debian | LetsEncrypt Certbot | Creating alternative name string (2/3)" + ansible.builtin.set_fact: + _apache_aliases: "{{ '--domain ' + _aliases }}" + when: apache_aliases | length > 0 + +- name: "Apache | Debian | LetsEncrypt Certbot | Creating alternative name string (3/3)" + ansible.builtin.set_fact: + _apache_aliases: '' + when: apache_aliases | length == 0 + +- name: debug + ansible.builtin.debug: + msg: "certbot certonly --apache -{{ APACHE_CONFIG.letsencrypt.verbosity }} --non-interactive --agree-tos --email {{ site.admin }} --cert-name {{ name }} + --rsa-key-size {{ APACHE_CONFIG.letsencrypt.key_size }} --no-redirect --domain {{ site.domain }} {{ _apache_aliases }}" + +- name: "Apache | Debian | LetsEncrypt Certbot | Starting certbot for domain '{{ site.domain }}'" + ansible.builtin.shell: "certbot certonly --apache -{{ APACHE_CONFIG.letsencrypt.verbosity }} --non-interactive --agree-tos --email {{ site.admin }} --cert-name {{ name }} + --rsa-key-size {{ APACHE_CONFIG.letsencrypt.key_size }} --no-redirect --domain {{ site.domain }} {{ _apache_aliases }}" + ignore_errors: yes diff --git a/tasks/debian/letsencrypt/main.yml b/tasks/debian/letsencrypt/main.yml new file mode 100644 index 0000000..d243781 --- /dev/null +++ b/tasks/debian/letsencrypt/main.yml @@ -0,0 +1,41 @@ +--- + +- name: Apache | Debian | LetsEncrypt Certbot | Install package + ansible.builtin.apt: + name: "{{ packages.letsencrypt }}" + state: present + +- name: Apache | Debian | LetsEncrypt Certbot | Check if a apache virtualhost is available + ansible.builtin.shell: 'ls /etc/apache2/sites-enabled/' + register: enabled_apache_sites + +- name: Apache | Debian | LetsEncrypt Certbot | Checking dependencies + ansible.builtin.include_tasks: dependencies.yml + when: enabled_apache_sites.stdout == '' + +- name: Apache | Debian | LetsEncrypt Certbot | Processing apache sites + ansible.builtin.include_tasks: domain.yml + vars: + site: "{{ default_site_config | combine(site_item, recursive=true) }}" + name: "{{ site_item.key | safe_key }}" + loop_control: + loop_var: site_item + with_dict: "{{ APACHE_CONFIG.sites }}" + +- name: Apache | Debian | LetsEncrypt Certbot | Cleanup dependencies + ansible.builtin.include_tasks: cleanup.yml + +- name: Apache | Debian | LetsEncrypt Certbot | Adding systemd files for certbot renewal + ansible.builtin.template: + src: "templates/etc/systemd/system/{{ item }}.j2" + dest: "/etc/systemd/system/{{ item }}" + with_items: + - 'ansibleguy.infra_apache.LetsEncryptCertbot.service' + - 'ansibleguy.infra_apache.LetsEncryptCertbot.timer' + +- name: Apache | Debian | LetsEncrypt Certbot | Enabling cert-renewal systemd timer + ansible.builtin.systemd: + daemon_reload: yes + name: 'LetsEncryptCertbot.timer' + enabled: yes + state: started diff --git a/tasks/debian/main.yml b/tasks/debian/main.yml new file mode 100644 index 0000000..e327e95 --- /dev/null +++ b/tasks/debian/main.yml @@ -0,0 +1,100 @@ +--- + +- name: Apache | Debian | Install apache + ansible.builtin.apt: + name: "{{ packages.apache }}" + state: present + +- name: Apache | Debian | Checking if all sites exist (1/2) + ansible.builtin.stat: + path: "/etc/apache2/sites-available/site_{{ item.key | safe_key }}.conf" + register: sites_exist_raw + with_dict: "{{ APACHE_CONFIG.sites }}" + +- name: Apache | Debian | Checking if all sites exist (2/2) + ansible.builtin.set_fact: + sites_exist: "{{ sites_exist_raw | json_query('[*].results.stat.exists') | all_true }}" + +- name: Apache | Debian | Getting certificate via LetsEncrypt + ansible.builtin.import_tasks: letsencrypt/main.yml + when: > + (APACHE_CONFIG.ssl.renew or + not sites_exist) and + APACHE_CONFIG.ssl.mode == 'letsencrypt' + +- name: Apache | Debian | Enabling apache modules + community.general.apache2_module: + state: present + name: "{{ item }}" + when: item not in APACHE_CONFIG.modules.absent + loop: "{{ APACHE_CONFIG.modules.present }}" + +- name: Apache | Debian | Disabling apache modules + community.general.apache2_module: + state: absent + name: "{{ item }}" + loop: "{{ APACHE_CONFIG.modules.absent }}" + +# todo: configure module settings + +# todo: check if apache2.conf editing is still needed +#- name: Apache | Debian | Adding global config +# ansible.builtin.blockinfile: +# path: '/etc/apache2/apache2.conf' +# block: | +# {% for setting, value in apache_config_additions_default.items() %} +# {{ setting }} {{ value }} +# {% endfor %} +# {% for setting, value in apache_config_additions.items() %} +# {{ setting }} {{ value }} +# {% endfor %} +# marker: "# {mark} ANSIBLE MANAGED BLOCK - global config" +# validate: 'apachectl -t -f %s' + +- name: Apache | Debian | Disabling default apache sites + ansible.builtin.file: + state: absent + dest: "/etc/apache2/sites-enabled/{{ item }}" + with_items: + - '000-default.conf' + - 'default-ssl.conf' + +- name: Apache | Debian | Removing apache site + ansible.builtin.include_tasks: rm_site.yml + vars: + site: "{{ default_site_config | combine(site_item, recursive=true) }}" + name: "{{ site_item.key | safe_key }}" + when: site_item.state | default('present') != 'present' + loop_control: + loop_var: site_item + with_dict: "{{ APACHE_CONFIG.sites }}" + +- name: Apache | Debian | Reloading apache + ansible.builtin.systemd: + name: 'apache2.service' + state: reloaded + tags: [base, config, sites, certs] + +- name: Apache | Debian | Adding apache site + ansible.builtin.include_tasks: add_site.yml + vars: + site: "{{ default_site_config | combine(site_item, recursive=true) }}" + name: "{{ site_item.key | safe_key }}" + when: site_item.state | default('present') == 'present' + loop_control: + loop_var: site_item + with_dict: "{{ APACHE_CONFIG.sites }}" + +- name: Apache | Debian | Starting/Enabling apache + ansible.builtin.systemd: + name: 'apache2.service' + enabled: yes + state: started + tags: [base] + +- name: Apache | Debian | Reloading apache + ansible.builtin.systemd: + name: 'apache2.service' + enabled: yes + state: reloaded + tags: [base, config, sites, certs] diff --git a/tasks/debian/rm_site.yml b/tasks/debian/rm_site.yml new file mode 100644 index 0000000..9baf434 --- /dev/null +++ b/tasks/debian/rm_site.yml @@ -0,0 +1,24 @@ +--- + +# ports will be left configured since I found no clean way to manage them statefully + +- name: "Apache | Debian | Config | Site '{{ name }}' | Removing web-root" + ansible.builtin.file: + path: "{{ site.serve.path }}" + state: absent + force: yes + when: site.mode == 'serve' + +- name: "Apache | Debian | Config | Site '{{ name }}' | Removing/Disabling site" + ansible.builtin.template: + path: "{{ item }}" + state: absent + loop: + - "/etc/apache2/sites-available/site_{{ name }}.conf" + - "/etc/apache2/sites-enabled/site_{{ name }}.conf" + +- name: "Apache | Debian | Config | Site '{{ name }}' | Removing certificate from certbot" + ansible.builtin.shell: "certbot certonly --apache -{{ APACHE_LE_CONFIG.verbosity }} --non-interactive --agree-tos --email {{ site.admin }} --cert-name {{ name }} + --rsa-key-size {{ APACHE_LE_CONFIG.key_size }} --no-redirect --domain {{ site.domain }} {{ _apache_aliases }}" + ignore_errors: yes + when: site.ssl.mode == 'letsencrypt' diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..4fc8048 --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,5 @@ +--- + +- name: Apache | Processing debian config + ansible.builtin.import_tasks: debian/main.yml + when: "ansible_distribution|lower in ['debian', 'ubuntu']" diff --git a/templates/etc/apache2/sites-available/le_dummy.conf.j2 b/templates/etc/apache2/sites-available/le_dummy.conf.j2 new file mode 100644 index 0000000..4f47676 --- /dev/null +++ b/templates/etc/apache2/sites-available/le_dummy.conf.j2 @@ -0,0 +1,6 @@ + + ServerName dummy.letsencrypt.localhost + ServerAdmin webmaster@localhost + ErrorLog {{ APACHE_CONFIG.log.path }}/error.log + CustomLog {{ APACHE_CONFIG.log.path }}/access.log combined + diff --git a/templates/etc/apache2/sites-available/site.conf.j2 b/templates/etc/apache2/sites-available/site.conf.j2 new file mode 100644 index 0000000..3ca4008 --- /dev/null +++ b/templates/etc/apache2/sites-available/site.conf.j2 @@ -0,0 +1,163 @@ + + ServerName {{ site.domain }} + +{% if site.aliases | length > 0 %} + ServerAlias {% for name in site.aliases %} {{ name }} {% endfor %} +{% endif %} + ServerAdmin {{ site.admin }} + + # log config +{% if APACHE_CONFIG.log.syslog and APACHE_CONFIG.log.syslog_host is not none %} + ErrorLog "| /usr/bin/logger -n {{ APACHE_CONFIG.log.syslog_host }} -P {{ APACHE_CONFIG.log.syslog_port }} -p local1.error -t {{ APACHE_CONFIG.log.prefix_ue }}{{ name }}" + CustomLog "| /usr/bin/logger -n {{ APACHE_CONFIG.log.syslog_host }} -P {{ APACHE_CONFIG.log.syslog_port }} -p local1.info -t {{ APACHE_CONFIG.log.prefix_ue }}{{ name }}" combined +{% elif APACHE_CONFIG.log.syslog %} + ErrorLog "| /usr/bin/logger -p local1.error -t {{ APACHE_CONFIG.log.prefix_ue }}{{ name }}" + CustomLog "| /usr/bin/logger -p local1.info -t {{ APACHE_CONFIG.log.prefix_ue }}{{ name }}" combined +{% elif APACHE_CONFIG.log.per_site %} + ErrorLog {{ APACHE_CONFIG.log.path }}/{{ name }}_error.log + CustomLog {{ APACHE_CONFIG.log.path }}/{{ name }}_access.log combined +{% else %} + ErrorLog {{ APACHE_CONFIG.log.path }}/error.log + CustomLog {{ APACHE_CONFIG.log.path }}/access.log combined +{% endif %} + + # redirect all to secure connection + Redirect permanent / https://{{ site.domain }} + + + + + ServerName {{ site.domain }} + +{% if site.aliases | length > 0 %} + ServerAlias {% for name in site.aliases %} {{ name }} {% endfor %} +{% endif %} + + ServerAdmin {{ site.admin }} + + # log config +{% if APACHE_CONFIG.log.syslog and APACHE_CONFIG.log.syslog_host is not none %} + ErrorLog "| /usr/bin/logger -n {{ APACHE_CONFIG.log.syslog_host }} -P {{ APACHE_CONFIG.log.syslog_port }} -p local1.error -t {{ APACHE_CONFIG.log.prefix_ue }}{{ name }}" + CustomLog "| /usr/bin/logger -n {{ APACHE_CONFIG.log.syslog_host }} -P {{ APACHE_CONFIG.log.syslog_port }} -p local1.info -t {{ APACHE_CONFIG.log.prefix_ue }}{{ name }}" combined +{% elif APACHE_CONFIG.log.syslog %} + ErrorLog "| /usr/bin/logger -p local1.error -t {{ APACHE_CONFIG.log.prefix_ue }}{{ name }}" + CustomLog "| /usr/bin/logger -p local1.info -t {{ APACHE_CONFIG.log.prefix_ue }}{{ name }}" combined +{% elif APACHE_CONFIG.log.per_site %} + ErrorLog {{ APACHE_CONFIG.log.path }}/{{ name }}_error.log + CustomLog {{ APACHE_CONFIG.log.path }}/{{ name }}_access.log combined +{% else %} + ErrorLog {{ APACHE_CONFIG.log.path }}/error.log + CustomLog {{ APACHE_CONFIG.log.path }}/access.log combined +{% endif %} + + # ssl config + + SSLEngine on + SSLCertificateKeyFile /etc/ssl/private/{{ apache_site }}/privkey.pem + SSLCertificateFile /etc/ssl/certs/{{ apache_site }}/cert.pem + SSLCertificateChainFile /etc/ssl/certs/{{ apache_site }}/fullchain.pem + + +{% if APACHE_CONFIG.config | length > 0 %} + # global config +{% for setting, value in APACHE_CONFIG.config.items() %} +{% if setting not in apache_config_graylist %} + {{ setting }} {{ value }} +{% endif %} +{% endfor %} +{% endif %} + +{% if site.config | length > 0 %} + # site-specific config +{% for setting, value in site.config.items() %} +{% if setting not in apache_config_graylist %} + {{ setting }} {{ value }} +{% endif %} +{% endfor %} +{% endif %} + +{% if APACHE_CONFIG.headers | length > 0 %} + # global headers + +{% for header, value in APACHE_CONFIG.headers.items() %} +{% if 'Header' in header %} + {{ header }} {{ value }} +{% else %} + Header set {{ header }} {{ value }} +{% endif %} +{% endfor %} + + +{% endif %} + +{% if site.headers | length > 0 %} + # site-specific headers + +{% for header, value in site.headers.items() %} +{% if 'Header' in header %} + {{ header }} {{ value }} +{% else %} + Header set {{ header }} {{ value }} +{% endif %} +{% endfor %} + + +{% endif %} + + # security config +{% if site.security.restrict_methods %} + + deny from all + +{% endif %} +{% if site.security.limit_directory_access %} + + Options None + Order deny,allow + Deny from all + +{% endif %} +{% if site.security.disable_directory_access %} +