commit d8ec69086d7149e9e4b9dc3982ddb545a3650451 Author: s3lph Date: Mon Apr 26 00:07:37 2021 +0200 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..bd1a264 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Ansible Collection - s3lph.webserver + +WIP \ No newline at end of file diff --git a/galaxy.yml b/galaxy.yml new file mode 100644 index 0000000..20793f1 --- /dev/null +++ b/galaxy.yml @@ -0,0 +1,65 @@ +### REQUIRED +# The namespace of the collection. This can be a company/brand/organization or product namespace under which all +# content lives. May only contain alphanumeric lowercase characters and underscores. Namespaces cannot start with +# underscores or numbers and cannot contain consecutive underscores +namespace: s3lph + +# The name of the collection. Has the same character restrictions as 'namespace' +name: webserver + +# The version of the collection. Must be compatible with semantic versioning +version: 0.1 + +# The path to the Markdown (.md) readme file. This path is relative to the root of the collection +readme: README.md + +# A list of the collection's content authors. Can be just the name or in the format 'Full Name (url) +# @nicks:irc/im.site#channel' +authors: +- s3lph + + +### OPTIONAL but strongly recommended +# A short summary description of the collection +description: Webserver, Apache, Lets Encrypt, ACME, Certbot + +# Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only +# accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file' +license: +- MIT + +# A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character +# requirements as 'namespace' and 'name' +tags: + - email + - mailserver + - postfix + - dovecot + - postfixadmin + - postsrsd + +# Collections that this collection requires to be installed for it to be usable. The key of the dict is the +# collection label 'namespace.name'. The value is a version range +# L(specifiers,https://python-semanticversion.readthedocs.io/en/latest/#requirement-specification). Multiple version +# range specifiers can be set and are separated by ',' +dependencies: + community.general: "2.2.0" + +# The URL of the originating SCM repository +repository: https://gitlab.com/s3lph/ansible-collection-webserver + +# The URL to any online docs +documentation: https://gitlab.com/s3lph/ansible-collection-webserver + +# The URL to the homepage of the collection/project +homepage: https://gitlab.com/s3lph/ansible-collection-webserver + +# The URL to the collection issue tracker +issues: https://gitlab.com/s3lph/ansible-collection-webserver/-/issues + +# A list of file glob-like patterns used to filter any files or directories that should not be included in the build +# artifact. A pattern is matched from the relative path of the file or directory of the collection directory. This +# uses 'fnmatch' to match the files or directories. Some directories and files like 'galaxy.yml', '*.pyc', '*.retry', +# and '.git' are always filtered +build_ignore: [] + diff --git a/roles/apache2/defaults/main.yml b/roles/apache2/defaults/main.yml new file mode 100644 index 0000000..ef4c82e --- /dev/null +++ b/roles/apache2/defaults/main.yml @@ -0,0 +1,31 @@ +--- + +apache2_vhost_serveradmin: root@localhost +apache2_vhost_serveraliases: [] +apache2_vhost_documentroot: /var/www/html +apache2_vhost_loglevel: warn +apache2_vhost_errorlog: "${APACHE_LOG_DIR}/error.log" +apache2_vhost_accesslog: "${APACHE_LOG_DIR}/access.log combined" + +apache2_vhost_http_enabled: true +apache2_vhost_https_enabled: true +apache2_vhost_http_redirect_to_https: true + +apache2_tls_certfile: "/etc/letsencrypt/live/{{ inventory_hostname }}/fullchain.pem" +apache2_tls_keyfile: "/etc/letsencrypt/live/{{ inventory_hostname }}/privkey.pem" +apache2_tls_noacme_fallback_certfile: /etc/ssl/certs/ssl-cert-snakeoil.pem +apache2_tls_noacme_fallback_keyfile: /etc/ssl/private/ssl-cert-snakeoil.key +# generated 2021-04-22, Mozilla Guideline v5.6, Apache 2.4.41, OpenSSL 1.1.1d, intermediate configuration, no OCSP +# https://ssl-config.mozilla.org/#server=apache&version=2.4.41&config=intermediate&openssl=1.1.1d&ocsp=false&guideline=5.6 +apache2_tls_protocols: "all -SSLv3 -TLSv1 -TLSv1.1" +apache2_tls_ciphersuite: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384" +apache2_tls_honor_cipher_order: false +apache2_tls_session_tickets: false + +apache2_modules: + - ssl + - proxy_http + - headers + - rewrite + +apache2_sites: {} diff --git a/roles/apache2/handlers/main.yml b/roles/apache2/handlers/main.yml new file mode 100644 index 0000000..e436f6f --- /dev/null +++ b/roles/apache2/handlers/main.yml @@ -0,0 +1,11 @@ +--- + +- name: restart apache2 + service: + name: apache2 + state: restarted + +- name: reload apache2 + service: + name: apache2 + state: reloaded diff --git a/roles/apache2/tasks/config.yml b/roles/apache2/tasks/config.yml new file mode 100644 index 0000000..a585ccb --- /dev/null +++ b/roles/apache2/tasks/config.yml @@ -0,0 +1,52 @@ +--- + +- name: enable apache2 modules + community.general.apache2_module: + name: "{{ item }}" + loop: "{{ apache2_modules }}" + notify: restart apache2 + +- name: flush handlers + meta: + flush_handlers: {} + +- name: check for tls keypair existence + stat: + path: "{{ item }}" + follow: yes + loop: | + {%- set files = [] -%} + {%- for name, site in apache2_sites | dict2items -%} + {%- if site.https_enabled | default(apache2_vhost_https_enabled) -%} + {%- endif -%} + {%- set _x = files.append(site.tls_certfile | default(apache2_tls_certfile)) -%} + {%- set _x = files.append(site.tls_keytfile | default(apache2_tls_keyfile)) -%} + {%- endfor -%} + {{- files | unique | list -}} + register: apache2_register_stat_tls_keypairs + +- name: render apache site configs + template: + src: etc/apache2/sites-available/site.conf.j2 + dest: "/etc/apache2/sites-available/{{ item.key }}.conf" + owner: root + group: root + mode: 0644 + vars: + name: "{{ item.key }}" + site: "{{ item.value }}" + certfile_exists: "{{ apache2_register_stat_tls_keypairs[item.value.tls_certfile | default(apache2_tls_certfile)].stat.exists }}" + keyfile_exists: "{{ apache2_register_stat_tls_keypairs[item.value.tls_keyfile | default(apache2_tls_keyfile)].stat.exists }}" + loop: "{{ apache2_sites | dict2items }}" + notify: reload apache2 + +- name: enable apache2 sites + file: + path: "/etc/apache2/sites-enabled/{{ item }}.conf" + state: link + src: "../sites-available/{{ item }}.conf" + owner: root + group: root + mode: 0777 + loop: "{{ apache2_sites }}" + notify: reload apache2 diff --git a/roles/apache2/tasks/install.yml b/roles/apache2/tasks/install.yml new file mode 100644 index 0000000..bb0fbd1 --- /dev/null +++ b/roles/apache2/tasks/install.yml @@ -0,0 +1,13 @@ +--- + +- name: install apache2 and related packages + apt: + name: + - apache2 + - ssl-cert # snakeoil cert used for optional tls bootstrapping + +- name: start and enable apache2 + service: + name: apache2 + state: started + enabled: yes diff --git a/roles/apache2/tasks/main.yml b/roles/apache2/tasks/main.yml new file mode 100644 index 0000000..55278e7 --- /dev/null +++ b/roles/apache2/tasks/main.yml @@ -0,0 +1,11 @@ +--- + +- import_tasks: install.yml + tags: + - "role::apache2" + - "role::apache2:install" + +- import_tasks: config.yml + tags: + - "role::apache2" + - "role::apache2:config" diff --git a/roles/apache2/templates/etc/apache2/sites-available/site.conf.j2 b/roles/apache2/templates/etc/apache2/sites-available/site.conf.j2 new file mode 100644 index 0000000..4c3f33f --- /dev/null +++ b/roles/apache2/templates/etc/apache2/sites-available/site.conf.j2 @@ -0,0 +1,55 @@ +{{ ansible_managed | comment }} + +{% if site.http_enabled | default(apache2_vhost_http_enabled) %} + + + ServerAdmin {{ site.serveradmin | default(apache2_vhost_serveradmin) }} + ServerName {{ name }} +{% for alias in site.aliases %} + ServerAlias {{ alias }} +{% endfor %} + + LogLevel {{ site.loglevel | default(apache2_vhost_loglevel) }} + ErrorLog {{ site.errorlog | default(apache2_vhost_errorlog) }} + CustomLog {{ site.accesslog | default(apache2_vhost_accesslog) }} + +{% if site.http_redirect_to_https | default(apache2_vhost_http_redirect_to_https) %} + Redirect permanent / https://{{ name }}/ +{% else %} + DocumentRoot {{ site.documentroot | default(apache2_vhost_documentroot) }} + + {{ site.additional_config | indent(8) }} +{% endif %} + + +{% endif %} + + +{% if site.https_enabled | default(apache2_vhost_https_enabled) %} + + + ServerAdmin {{ site.serveradmin | default(apache2_vhost_serveradmin) }} + ServerName {{ name }} +{% for alias in site.aliases %} + ServerAlias {{ alias }} +{% endfor %} + + LogLevel {{ site.loglevel | default(apache2_vhost_loglevel) }} + ErrorLog {{ site.errorlog | default(apache2_vhost_errorlog) }} + CustomLog {{ site.accesslog | default(apache2_vhost_accesslog) }} + + SSLEngine on +{% if certfile_exists and keyfile_exists %} + SSLCertificateFile {{ site.tls_certfile | default(apache2_tls_certfile) }} + SSLCertificateKeyFile {{ site.tls_keyfile | default(apache2_tls_keyfile) }} +{% else %} + SSLCertificateFile {{ apache2_tls_noacme_fallback_certfile }} + SSLCertificateKeyFile {{ apache2_tls_noacme_fallback_keyfile }} +{% endif %} + + DocumentRoot {{ site.documentroot | default(apache2_vhost_documentroot) }} + + {{ site.additional_config | indent(8) }} + + +{% endif %} diff --git a/roles/certbot/defaults/main.yml b/roles/certbot/defaults/main.yml new file mode 100644 index 0000000..b1747f9 --- /dev/null +++ b/roles/certbot/defaults/main.yml @@ -0,0 +1,9 @@ +--- + +certbot_webroot: /var/www/html +certbot_rsa_key_size: 4096 +certbot_email: null +certbot_acme_server: "https://acme-v02.api.letsencrypt.org/directory" +certbot_challenge: webroot + +certbot_certificates: {} diff --git a/roles/certbot/tasks/install.yml b/roles/certbot/tasks/install.yml new file mode 100644 index 0000000..b9eaee4 --- /dev/null +++ b/roles/certbot/tasks/install.yml @@ -0,0 +1,7 @@ +--- + +- name: install certbot + apt: + name: + - certbot + diff --git a/roles/certbot/tasks/issue.yml b/roles/certbot/tasks/issue.yml new file mode 100644 index 0000000..766178b --- /dev/null +++ b/roles/certbot/tasks/issue.yml @@ -0,0 +1,38 @@ +--- + +- name: issue certificates + command: >- + /usr/bin/certbot certonly + --server {{ cert.server | default(certbot_acme_server) }} + --agree-tos + {% if cert.email | default(certbot_email) is none %} + --register-unsafely-without-email + {% else %} + --email {{ cert.email | default(certbot_email) }} + {% endif %} + --cert-name {{ name }} + --rsa-key-size {{ cert.rsa_key_size | default(certbot_rsa_key_size) }} + + {% if cert.challenge | default(certbot_challenge) == 'webroot' %} + + --webroot + {% if cert.webroot_map is defined %} + --webroot-map '{{ cert.webroot_map | to_json(width=99999) }}' + {% else %} + --webroot {{ cert.webroot }} + {% for domain in cert.domains | default([name]) %} + --domain {{ domain }} + {% endfor %} + {% endif %} + + {% else %} + + --{{ cert.challenge | default(certbot_challenge) }} + {{ cert.challenge_freeform_arguments }} + + {% endif %} + creates: "/etc/letsencrypt/live/{{ name }}/fullchain.pem" + vars: + name: "{{ item.key }}" + cert: "{{ item.value }}" + loop: "{{ certbot_certificates | dict2items }}" diff --git a/roles/certbot/tasks/main.yml b/roles/certbot/tasks/main.yml new file mode 100644 index 0000000..1bfc3c2 --- /dev/null +++ b/roles/certbot/tasks/main.yml @@ -0,0 +1,11 @@ +--- + +- import_tasks: install.yml + tags: + - "role::certbot" + - "role::certbot:install" + +- import_tasks: issue.yml + tags: + - "role::certbot" + - "role::certbot:issue"