init
This commit is contained in:
		
						commit
						4875e6ef1f
					
				|  | @ -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. | ||||
|  | @ -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 | ||||
|  | @ -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: | ||||
|   # <IfModule ${MOD}> | ||||
|   # </IfModule> | ||||
|   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'] | ||||
|  | @ -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) | ||||
|  | @ -0,0 +1,23 @@ | |||
| --- | ||||
| 
 | ||||
| galaxy_info: | ||||
|   author: 'AnsibleGuy <guy@ansibleguy.net>' | ||||
|   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' | ||||
|  | @ -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 | ||||
|  | @ -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' | ||||
|  | @ -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 | ||||
|  | @ -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 | ||||
|  | @ -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 | ||||
|  | @ -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 | ||||
|  | @ -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 | ||||
|  | @ -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 | ||||
|  | @ -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] | ||||
|  | @ -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' | ||||
|  | @ -0,0 +1,5 @@ | |||
| --- | ||||
| 
 | ||||
| - name: Apache | Processing debian config | ||||
|   ansible.builtin.import_tasks: debian/main.yml | ||||
|   when: "ansible_distribution|lower in ['debian', 'ubuntu']" | ||||
|  | @ -0,0 +1,6 @@ | |||
| <VirtualHost *:80> | ||||
|     ServerName dummy.letsencrypt.localhost | ||||
|     ServerAdmin webmaster@localhost | ||||
|     ErrorLog {{ APACHE_CONFIG.log.path }}/error.log | ||||
|     CustomLog {{ APACHE_CONFIG.log.path }}/access.log combined | ||||
| </VirtualHost> | ||||
|  | @ -0,0 +1,163 @@ | |||
| <VirtualHost *:{{ site.port_plain }}> | ||||
|   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 }} | ||||
| 
 | ||||
| </VirtualHost> | ||||
| 
 | ||||
| <VirtualHost *:{{ site.port_ssl }}> | ||||
|   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 | ||||
|   <IfModule mod_ssl.c> | ||||
|     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 | ||||
|   </IfModule> | ||||
| 
 | ||||
| {% 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 | ||||
|   <IfModule mod_headers.c> | ||||
| {%   for header, value in APACHE_CONFIG.headers.items() %} | ||||
| {%     if 'Header' in header %} | ||||
|     {{ header }} {{ value }} | ||||
| {%     else %} | ||||
|     Header set {{ header }} {{ value }} | ||||
| {%     endif %} | ||||
| {%   endfor %} | ||||
| 
 | ||||
|   </IfModule> | ||||
| {% endif %} | ||||
| 
 | ||||
| {% if site.headers | length > 0 %} | ||||
|   # site-specific headers | ||||
|   <IfModule mod_headers.c> | ||||
| {%   for header, value in site.headers.items() %} | ||||
| {%     if 'Header' in header %} | ||||
|     {{ header }} {{ value }} | ||||
| {%     else %} | ||||
|     Header set {{ header }} {{ value }} | ||||
| {%     endif %} | ||||
| {%   endfor %} | ||||
| 
 | ||||
|   </IfModule> | ||||
| {% endif %} | ||||
| 
 | ||||
|   # security config | ||||
| {% if site.security.restrict_methods %} | ||||
|   <LimitExcept {% for method in apache_restricted_methods %}{{ method }} {% endfor %}> | ||||
|     deny from all | ||||
|   </LimitExcept> | ||||
| {% endif %} | ||||
| {% if site.security.limit_directory_access %} | ||||
|   <Directory /> | ||||
|     Options None | ||||
|     Order deny,allow | ||||
|     Deny from all | ||||
|   </Directory> | ||||
| {% endif %} | ||||
| {% if site.security.disable_directory_access %} | ||||
|   <Directory "="> | ||||
|     Require all denied | ||||
|   </Directory> | ||||
| {% endif %} | ||||
| 
 | ||||
| {% if site.mode == 'redirect' %} | ||||
|   # redirect-mode config | ||||
|   Redirect permanent / {{ site.redirect.target }} | ||||
| {%   if site.redirect.request_uri %} | ||||
|   RedirectMatch permanent ^/(.*)$ {{ site.redirect.target }}/$1 | ||||
| {%   else %} | ||||
|   RedirectMatch permanent ^/(.*)$ {{ site.redirect.target }} | ||||
| {%   endif %} | ||||
| 
 | ||||
| {% elif site.mode == 'serve' %} | ||||
|   # serve-mode config | ||||
|   DocumentRoot {{ site.serve.path }} | ||||
| 
 | ||||
|   # mode-specific security config | ||||
|   <Directory {{ site.serve.path }}> | ||||
| {%   if site.security.disable_ssi_cgi %} | ||||
|     Options -FollowSymLinks -ExecCGI -Includes | ||||
|     AllowOverride None | ||||
|     Require all granted | ||||
| {%   endif %} | ||||
| {%   if site.security.disable_root_index %} | ||||
|     Options -Indexes | ||||
| {%   endif %} | ||||
|   </Directory> | ||||
| 
 | ||||
| 
 | ||||
| {% endif %} | ||||
| 
 | ||||
| {% if site.config_additions | length > 0 %} | ||||
|   # additional lines | ||||
| {% endif %} | ||||
| {% for line in site.config_additions %} | ||||
|   {{ line }} | ||||
| {% endfor %} | ||||
| 
 | ||||
| </VirtualHost> | ||||
| 
 | ||||
| ServerName {{ site.domain }} | ||||
|  | @ -0,0 +1,7 @@ | |||
| [Unit] | ||||
| Description=Service to renew LetsEncrypt Certificates using certbot | ||||
| 
 | ||||
| [Service] | ||||
| Type=oneshot | ||||
| ExecStart=certbot renew -{{ APACHE_CONFIG.letsencrypt.verbosity }} --non-interactive --agree-tos --renew-with-new-domains --rsa-key-size {{ APACHE_CONFIG.letsencrypt.key_size }} | ||||
| SuccessExitStatus=0 | ||||
|  | @ -0,0 +1,10 @@ | |||
| [Unit] | ||||
| Description=Timer to renew LetsEncrypt Certificates using certbot | ||||
| 
 | ||||
| [Timer] | ||||
| OnCalendar={{ APACHE_CONFIG.letsencrypt.renew_timer }} | ||||
| Persistent=false | ||||
| WakeSystem=false | ||||
| 
 | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
		Loading…
	
		Reference in New Issue