From d796158c61d7e89b8f8c9f9777660b2490730fc0 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Tue, 29 Apr 2025 00:10:10 +0200 Subject: [PATCH] Optimized cloudflare implementation --- group_vars/all/00_general.yml | 4 +- requirements.yml | 1 + roles/dns-records-cloudflare/README.md | 37 +++++++++++++++++++ roles/dns-records-cloudflare/meta/main.yml | 26 +++++++++++++ roles/dns-records-cloudflare/tasks/main.yml | 18 +++++++++ roles/docker-bigbluebutton/meta/main.yml | 2 +- roles/docker-bigbluebutton/tasks/main.yml | 9 +++-- roles/docker-bigbluebutton/templates/env.j2 | 2 - roles/docker-bigbluebutton/vars/main.yml | 22 +++++------ .../docker-central-database/vars/database.yml | 4 +- .../templates/nginx.stream.conf.j2 | 5 +-- roles/letsencrypt/templates/ssl_header.j2 | 15 +++++--- .../tasks/flavors/san.yml | 1 + roles/nginx-redirect-www/tasks/main.yml | 18 +++++++-- tasks/constructor.yml | 30 +++++++++------ templates/vars/applications.yml.j2 | 5 ++- 16 files changed, 151 insertions(+), 48 deletions(-) create mode 100644 roles/dns-records-cloudflare/README.md create mode 100644 roles/dns-records-cloudflare/meta/main.yml create mode 100644 roles/dns-records-cloudflare/tasks/main.yml diff --git a/group_vars/all/00_general.yml b/group_vars/all/00_general.yml index b218ae13..434554d2 100644 --- a/group_vars/all/00_general.yml +++ b/group_vars/all/00_general.yml @@ -79,11 +79,13 @@ activate_all_timers: false # Activates all timers, indep # You SHOULD NOT enable this on production servers enable_debug: false +dns_provider: cloudflare # The DNS Provider\Registrar for the domain + # Which ACME method to use: webroot, cloudflare, or hetzner certbot_acme_challenge_method: "cloudflare" certbot_credentials_dir: /etc/certbot certbot_credentials_file: "{{ certbot_credentials_dir }}/{{ certbot_acme_challenge_method }}.ini" -# certbot_dns_api_token # Define in inventory file +# certbot_dns_api_token # Define in inventory file certbot_dns_propagation_wait_seconds: 40 # How long should the script wait for DNS propagation before continuing certbot_flavor: san # Possible options: san (recommended, with a dns flavor like cloudflare, or hetzner), wildcard(doesn't function with www redirect), deicated certbot_webroot_path: "/var/lib/letsencrypt/" # Path used by Certbot to serve HTTP-01 ACME challenges diff --git a/requirements.yml b/requirements.yml index 2440e357..b39faafe 100644 --- a/requirements.yml +++ b/requirements.yml @@ -1,5 +1,6 @@ collections: - name: kewlfft.aur + - name: community.general pacman: - ansible - python-passlib diff --git a/roles/dns-records-cloudflare/README.md b/roles/dns-records-cloudflare/README.md new file mode 100644 index 00000000..975307a4 --- /dev/null +++ b/roles/dns-records-cloudflare/README.md @@ -0,0 +1,37 @@ +# Cloudflare DNS Records + +## Description + +This Ansible role automates the management of DNS A-records in Cloudflare zones. It uses the [community.general.cloudflare_dns](https://docs.ansible.com/ansible/latest/collections/community/general/cloudflare_dns_module.html) module to create or update A-records for a list of domains, automatically detects the correct zone for each record, and supports configurable proxy settings. + +## Overview + +Looping over a provided list of domains (`cloudflare_domains`), this role: +- Determines the zone name by extracting the last two labels of each domain. +- Ensures an A-record for each domain points to the specified IP (`cloudflare_target_ip`). +- Honors the `proxied` flag to switch between DNS-only and Cloudflare-proxied modes. +- Provides an optional debug task (`enable_debug`) to output the domain list before changes. + +Ideal for environments where bulk or dynamic DNS updates are needed, this role abstracts away the complexity of Cloudflare’s zone and record API. + +## Purpose + +Cloudflare DNS Records delivers an idempotent, scalable solution for managing A-records across multiple Cloudflare zones. Whether you need to onboard hundreds of domains or toggle proxy settings in CI/CD pipelines, this role handles the orchestration and ensures consistency. + +## Features + +- **Automatic Zone Detection:** Parses each domain to derive its zone (`example.com`) without manual intervention. +- **Bulk Record Management:** Creates or updates A-records for all entries in `cloudflare_domains`. +- **Proxy Toggle:** Configure `proxied: true` or `false` per record to switch between DNS-only and proxied modes. +- **Debug Support:** Enable `enable_debug` to print the domain list for validation before execution. +- **Flexible Authentication:** Supports both API token (`api_token`) and Global API key + email. +- **Low-TTL Option:** Use `ttl: 1` for rapid DNS propagation during dynamic updates. + +## Author + +Kevin Veen-Birkenbach + +## License + +CyMaIS NonCommercial License (CNCL) + \ No newline at end of file diff --git a/roles/dns-records-cloudflare/meta/main.yml b/roles/dns-records-cloudflare/meta/main.yml new file mode 100644 index 00000000..de1e5c77 --- /dev/null +++ b/roles/dns-records-cloudflare/meta/main.yml @@ -0,0 +1,26 @@ +--- +galaxy_info: + author: "Kevin Veen-Birkenbach" + description: "Manages DNS A-records in Cloudflare zones." + license: "CyMaIS NonCommercial License (CNCL)" + license_url: "https://s.veen.world/cncl" + company: | + Kevin Veen-Birkenbach + Consulting & Coaching Solutions + https://www.veen.world + min_ansible_version: "2.9" + platforms: + - name: "All" + versions: + - "all" + galaxy_tags: + - "cloudflare" + - "dns" + - "records" + - "ansible" + - "network" + - "automation" + repository: "https://s.veen.world/cymais" + issue_tracker_url: "https://s.veen.world/cymaisissues" + documentation: "https://s.veen.world/cymais" +dependencies: [] \ No newline at end of file diff --git a/roles/dns-records-cloudflare/tasks/main.yml b/roles/dns-records-cloudflare/tasks/main.yml new file mode 100644 index 00000000..89300c7c --- /dev/null +++ b/roles/dns-records-cloudflare/tasks/main.yml @@ -0,0 +1,18 @@ +- name: "Debug: cloudflare_domains" + debug: + var: cloudflare_domains + when: enable_debug + +- name: Create or update Cloudflare A-record for {{ item }} + community.general.cloudflare_dns: + api_token: "{{ cloudflare_api_token }}" + zone: "{{ item.split('.')[-2:] | join('.') }}" + state: present + type: A + name: "{{ item }}" + content: "{{ cloudflare_target_ip }}" + ttl: 1 + proxied: "{{ cloudflare_target_ip }}" + loop: "{{ cloudflare_domains }}" + loop_control: + label: "{{ item }}" diff --git a/roles/docker-bigbluebutton/meta/main.yml b/roles/docker-bigbluebutton/meta/main.yml index 8b97f592..6688dbfd 100644 --- a/roles/docker-bigbluebutton/meta/main.yml +++ b/roles/docker-bigbluebutton/meta/main.yml @@ -28,4 +28,4 @@ galaxy_info: issue_tracker_url: https://s.veen.world/cymaisissues documentation: https://s.veen.world/cymais logo: - class: "fa-solid fa-chalkboard-teacher" + class: "fa-solid fa-chalkboard-teacher" \ No newline at end of file diff --git a/roles/docker-bigbluebutton/tasks/main.yml b/roles/docker-bigbluebutton/tasks/main.yml index 7d94731b..acba9f14 100644 --- a/roles/docker-bigbluebutton/tasks/main.yml +++ b/roles/docker-bigbluebutton/tasks/main.yml @@ -1,12 +1,13 @@ --- -- name: "include role nginx-domain-setup for {{application_id}}" - include_role: - name: nginx-domain-setup - +# Docker Central Database Role can't be used here - name: "include docker-compose role" include_role: name: docker-compose +- name: "include role nginx-domain-setup for {{application_id}}" + include_role: + name: nginx-domain-setup + - name: pull docker repository git: repo: "https://github.com/bigbluebutton/docker.git" diff --git a/roles/docker-bigbluebutton/templates/env.j2 b/roles/docker-bigbluebutton/templates/env.j2 index 049c9d7a..72c90b34 100644 --- a/roles/docker-bigbluebutton/templates/env.j2 +++ b/roles/docker-bigbluebutton/templates/env.j2 @@ -33,8 +33,6 @@ RAILS_SECRET={{applications.bigbluebutton.credentials.rails_secret}} POSTGRESQL_SECRET={{applications.bigbluebutton.credentials.postgresql_secret}} FSESL_PASSWORD={{applications.bigbluebutton.credentials.fsesl_password}} - - # ==================================== # CONNECTION # ==================================== diff --git a/roles/docker-bigbluebutton/vars/main.yml b/roles/docker-bigbluebutton/vars/main.yml index 425e461d..d0396bcc 100644 --- a/roles/docker-bigbluebutton/vars/main.yml +++ b/roles/docker-bigbluebutton/vars/main.yml @@ -1,15 +1,13 @@ -application_id: "bigbluebutton" -bbb_repository_directory: "{{ docker_compose.directories.services }}" -docker_compose_file_origine: "{{ docker_compose.directories.services }}docker-compose.yml" -docker_compose_file_final: "{{ docker_compose.directories.instance }}docker-compose.yml" +application_id: "bigbluebutton" +bbb_repository_directory: "{{ docker_compose.directories.services }}" +docker_compose_file_origine: "{{ docker_compose.directories.services }}docker-compose.yml" +docker_compose_file_final: "{{ docker_compose.directories.instance }}docker-compose.yml" # Database configuration -database_instance: "bigbluebutton" -database_name: "multiple_databases" -database_username: "postgres" -database_password: "{{ applications.bigbluebutton.credentials.postgresql_secret }}" +database_type: "postgres" +database_password: "{{ applications.bigbluebutton.credentials.postgresql_secret }}" -domain: "{{ domains[application_id] }}" -http_port: "{{ ports.localhost.http[application_id] }}" -bbb_env_file_link: "{{ docker_compose.directories.instance }}.env" -bbb_env_file_origine: "{{ bbb_repository_directory }}.env" \ No newline at end of file +domain: "{{ domains[application_id] }}" +http_port: "{{ ports.localhost.http[application_id] }}" +bbb_env_file_link: "{{ docker_compose.directories.instance }}.env" +bbb_env_file_origine: "{{ bbb_repository_directory }}.env" \ No newline at end of file diff --git a/roles/docker-central-database/vars/database.yml b/roles/docker-central-database/vars/database.yml index 7c18daaa..3e066032 100644 --- a/roles/docker-central-database/vars/database.yml +++ b/roles/docker-central-database/vars/database.yml @@ -1,7 +1,7 @@ database_instance: "{{ 'central-' + database_type if applications[application_id].features.database | bool else application_id }}" database_host: "{{ 'central-' + database_type if applications[application_id].features.database | bool else 'database' }}" -database_name: "{{ application_id }}" -database_username: "{{ application_id }}" +database_name: "{{ applications[application_id].credentials.database.name | default( application_id ) }}" # The overwritte configuration is needed by bigbluebutton +database_username: "{{ applications[application_id].credentials.database.username | default( application_id )}}" # The overwritte configuration is needed by bigbluebutton database_port: "{{ 3306 if database_type == 'mariadb' else 5432 }}" database_env: "{{docker_compose.directories.env}}{{database_type}}.env" database_url_jdbc: "jdbc:{{ database_type if database_type == 'mariadb' else 'postgresql' }}://{{ database_host }}:{{ database_port }}/{{ database_name }}" diff --git a/roles/docker-ldap/templates/nginx.stream.conf.j2 b/roles/docker-ldap/templates/nginx.stream.conf.j2 index c5709773..6ff239bd 100644 --- a/roles/docker-ldap/templates/nginx.stream.conf.j2 +++ b/roles/docker-ldap/templates/nginx.stream.conf.j2 @@ -1,9 +1,6 @@ server { listen {{ports.public.ldaps.ldap}}ssl; proxy_pass 127.0.0.1:{{ports.localhost.ldap.ldap}}; - - # SSL Configuration for LDAPS + {% include 'roles/letsencrypt/templates/ssl_credentials.j2' %} - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers HIGH:!aNULL:!MD5; } diff --git a/roles/letsencrypt/templates/ssl_header.j2 b/roles/letsencrypt/templates/ssl_header.j2 index c9d79b67..012b2ddf 100644 --- a/roles/letsencrypt/templates/ssl_header.j2 +++ b/roles/letsencrypt/templates/ssl_header.j2 @@ -1,12 +1,15 @@ -listen 443 ssl; -listen [::]:443 ssl; -http2 on; +listen 443 ssl http2; +listen [::]:443 ssl http2; + +ssl_protocols TLSv1.2 TLSv1.3; +ssl_ecdh_curve X25519:P-256; +ssl_prefer_server_ciphers on; +ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384'; + ssl_session_timeout 1d; ssl_session_cache shared:SSL:50m; ssl_session_tickets on; -ssl_prefer_server_ciphers on; add_header Strict-Transport-Security max-age=15768000; ssl_stapling on; ssl_stapling_verify on; -{% include 'roles/letsencrypt/templates/ssl_credentials.j2' %} - +{% include 'roles/letsencrypt/templates/ssl_credentials.j2' %} \ No newline at end of file diff --git a/roles/nginx-https-get-cert/tasks/flavors/san.yml b/roles/nginx-https-get-cert/tasks/flavors/san.yml index 3f23d1f4..685ad684 100644 --- a/roles/nginx-https-get-cert/tasks/flavors/san.yml +++ b/roles/nginx-https-get-cert/tasks/flavors/san.yml @@ -11,6 +11,7 @@ --domains "{{ all_domains | join(',') }}" --certbot-email "{{ users.administrator.email }}" --certbot-acme-challenge-method "{{ certbot_acme_challenge_method }}" + --chunk-size 100 {% if certbot_acme_challenge_method != 'webroot' %} --certbot-credentials-file "{{ certbot_credentials_file }}" --certbot-dns-propagation-seconds "{{ certbot_dns_propagation_wait_seconds }}" diff --git a/roles/nginx-redirect-www/tasks/main.yml b/roles/nginx-redirect-www/tasks/main.yml index 8aa6c638..9b8f1ec6 100644 --- a/roles/nginx-redirect-www/tasks/main.yml +++ b/roles/nginx-redirect-www/tasks/main.yml @@ -1,10 +1,13 @@ --- -# 1. Filter all domains with the “www.” prefix +- name: "Debug: all_domains" + debug: + var: all_domains + when: enable_debug + - name: Filter www-prefixed domains from all_domains set_fact: www_domains: "{{ all_domains | select('match', '^www\\.') | list }}" -# 2. Build redirect mappings (www.domain → domain) - name: Build redirect mappings for www domains set_fact: domain_mappings: >- @@ -14,10 +17,19 @@ | list }} -# 3. Include the nginx-redirect-domain role to apply these mappings - name: Include nginx-redirect-domain role for www-to-bare redirects include_role: name: nginx-redirect-domain vars: domain_mappings: "{{ domain_mappings }}" when: certbot_flavor == 'dedicated' + +- name: Include DNS role to set redirects + include_role: + name: dns-records-cloudflare + vars: + cloudflare_api_token: "{{ certbot_dns_api_token }}" + cloudflare_domains: "{{ www_domains }}" + cloudflare_target_ip: "{{ networks.internet.ip4 }}" + cloudflare_proxied_false: false + when: dns_provider == 'cloudflare' \ No newline at end of file diff --git a/tasks/constructor.yml b/tasks/constructor.yml index e836d930..cddc9855 100644 --- a/tasks/constructor.yml +++ b/tasks/constructor.yml @@ -62,22 +62,28 @@ set_fact: service_provider: "{{ defaults_service_provider | combine(service_provider | default({}, true), recursive=True) }}" - - name: Collect all domains (domains, redirect sources + www) + - name: Gather base domains (without www) set_fact: - all_domains: >- + base_domains: >- {{ - ( - ( - domains.values() | flatten - + (redirect_domain_mappings | map(attribute='source') | list) - ) - + ( - domains.values() | flatten - + (redirect_domain_mappings | map(attribute='source') | list) - ) | map('regex_replace', '^(.*)$', 'www.\\1') | list - ) | unique | sort + domains.values() + | flatten + + (redirect_domain_mappings | map(attribute='source') | list) }} + - name: Initialise all_domains as empty list + set_fact: + all_domains: [] + + - name: Build all_domains with base + www via loop + set_fact: + all_domains: "{{ all_domains + [ item, 'www.' ~ item ] }}" + loop: "{{ base_domains }}" + + - name: Deduplicate and sort all_domains + set_fact: + all_domains: "{{ all_domains | unique | sort }}" + - name: "Merged Variables" # Add new merged variables here debug: diff --git a/templates/vars/applications.yml.j2 b/templates/vars/applications.yml.j2 index eb3d909a..3bb0f7ce 100644 --- a/templates/vars/applications.yml.j2 +++ b/templates/vars/applications.yml.j2 @@ -66,7 +66,7 @@ defaults_applications: 'iframe': true, 'ldap': false, 'oidc': true, - 'database': true, + 'database': false, }) }}{% raw %} credentials: # shared_secret: # Needs to be defined in inventory file @@ -75,6 +75,9 @@ defaults_applications: # postgresql_secret: # Needs to be defined in inventory file # fsesl_password: # Needs to be defined in inventory file # turn_secret: # Needs to be defined in inventory file + database: + name: "multiple_databases" + username: "postgres2" urls: api: "{{ web_protocol }}://{{domains.bigbluebutton}}/bigbluebutton/" # API Address used by Nextcloud Integration