--- - name: Zertifikate auf Mailcow (vom NPM) aktualisieren hosts: localhost gather_facts: false vars: # Host-Konfiguration debug_dane: true nginxproxymanager_host: npm mailcow_host: mailcow tlsa_host_label: "_25._tcp.mail" # Netcup Variablen # Netcup API netcup_customer_number: "XXXXXX" netcup_api_key: "Y...T" netcup_api_password: "W...E" netcup_domain: "domain.de" # Mailcow Variablen mailcow_cert_path: "/opt/mailcow-dockerized/data/assets/ssl/cert.pem" mailcow_key_path: "/opt/mailcow-dockerized/data/assets/ssl/key.pem" mailcow_owner: "root" mailcow_group: "root" tasks: ################################################################ # 1) Neues Zertifikat/Key vom NginxProxyManager einlesen ################################################################ - name: "Neues Zertifikat vom NginxProxyManager slurpen" slurp: src: "{{ letsencrypt_cert }}" delegate_to: "{{ nginxproxymanager_host }}" register: new_cert_slurp - name: "Neuen Key vom NginxProxyManager slurpen" slurp: src: "{{ letsencrypt_key }}" delegate_to: "{{ nginxproxymanager_host }}" register: new_key_slurp ################################################################ # 2) Mailcow-Check und -Update ################################################################ - name: "Mailcow: Altes Zertifikat (cert.pem) auslesen" slurp: src: "{{ mailcow_cert_path }}" delegate_to: "{{ mailcow_host }}" register: mailcow_old_cert ignore_errors: true - name: "Mailcow: Alter Key (key.pem) auslesen" slurp: src: "{{ mailcow_key_path }}" delegate_to: "{{ mailcow_host }}" register: mailcow_old_key ignore_errors: true - name: "Mailcow: Prüfen, ob Zertifikat oder Key neu sind" set_fact: mailcow_cert_is_new: >- {{ (new_cert_slurp.content | b64decode) != (mailcow_old_cert.content is defined and (mailcow_old_cert.content | b64decode) or '') or (new_key_slurp.content | b64decode) != (mailcow_old_key.content is defined and (mailcow_old_key.content | b64decode) or '') }} - name: "Mailcow: Debug-Ausgabe, ob das Zertifikat neu ist" debug: msg: "Mailcow: Zertifikat neu? {{ mailcow_cert_is_new }}" - name: "Mailcow: Zertifikatsupdate" block: - name: "Mailcow: Docker Compose stoppen" ansible.builtin.shell: | docker compose down args: chdir: "/opt/mailcow-dockerized/" delegate_to: "{{ mailcow_host }}" - name: "Mailcow: Neues Zertifikat (cert.pem) kopieren" copy: content: "{{ new_cert_slurp.content | b64decode }}" dest: "{{ mailcow_cert_path }}" owner: "{{ mailcow_owner }}" group: "{{ mailcow_group }}" mode: "0644" delegate_to: "{{ mailcow_host }}" - name: "Mailcow: Neuen Key (key.pem) kopieren" copy: content: "{{ new_key_slurp.content | b64decode }}" dest: "{{ mailcow_key_path }}" owner: "{{ mailcow_owner }}" group: "{{ mailcow_group }}" mode: "0644" delegate_to: "{{ mailcow_host }}" - name: "Mailcow: Docker Compose neu starten (force-recreate)" ansible.builtin.shell: | docker compose up -d --force-recreate args: chdir: "/opt/mailcow-dockerized/" delegate_to: "{{ mailcow_host }}" when: mailcow_cert_is_new ################################################################ # 3) TLSA-Record bei Netcup aktualisieren ################################################################ - name: "DANE: TLSA-Hash aus slurped cert.pem erzeugen (3 1 1)" shell: | echo "{{ mailcow_old_cert.content | b64decode }}" | openssl x509 -pubkey -noout | openssl pkey -pubin -outform DER | openssl dgst -sha256 -binary | hexdump -ve '1/1 "%.2x"' register: tlsa_hash_result changed_when: false delegate_to: localhost - name: "DANE: CA Issuer URI aus slurped cert.pem extrahieren" shell: | echo "{{ mailcow_old_cert.content | b64decode }}" | openssl x509 -text -noout | grep "CA Issuers" | sed -n 's/.*URI://p' register: ca_uri_result changed_when: false delegate_to: localhost - name: "DANE: TLSA-Hash der CA erzeugen (2 1 1)" shell: | wget -qO- "{{ ca_uri_result.stdout }}" | openssl x509 -inform DER -outform PEM | openssl x509 -pubkey -noout | openssl pkey -pubin -outform DER | openssl dgst -sha256 -binary | hexdump -ve '1/1 "%.2x"' register: tlsa_hash_ca_result changed_when: false when: ca_uri_result.stdout | length > 0 delegate_to: localhost - name: "DANE: TLSA-Hashes speichern" set_fact: tlsa_hash_current: "{{ tlsa_hash_result.stdout }}" tlsa_hash_ca: "{{ tlsa_hash_ca_result.stdout | default('') }}" - name: "DANE: Netcup API Login" uri: url: "https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON" method: POST body_format: json return_content: true body: action: "login" param: customernumber: "{{ netcup_customer_number }}" apikey: "{{ netcup_api_key }}" apipassword: "{{ netcup_api_password }}" register: netcup_login delegate_to: localhost - name: "DANE: Session-ID sichern" set_fact: netcup_session_id: "{{ netcup_login.json.responsedata.apisessionid }}" - name: "DANE: DNS-Records abrufen" uri: url: "https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON" method: POST body_format: json return_content: true body: action: "infoDnsRecords" param: customernumber: "{{ netcup_customer_number }}" apikey: "{{ netcup_api_key }}" apisessionid: "{{ netcup_session_id }}" domainname: "{{ netcup_domain }}" register: netcup_dns_info delegate_to: localhost - name: "DANE: Bestehende TLSA-Records extrahieren" set_fact: existing_tlsa_record: >- {{ (netcup_dns_info.json.responsedata.dnsrecords | selectattr('hostname', 'equalto', tlsa_host_label) | selectattr('type', 'equalto', 'TLSA') | selectattr('destination', 'search', '^3 1 1 ') | list | first) }} existing_tlsa_record_ca: >- {{ (netcup_dns_info.json.responsedata.dnsrecords | selectattr('hostname', 'equalto', tlsa_host_label) | selectattr('type', 'equalto', 'TLSA') | selectattr('destination', 'search', '^2 1 1 ') | list | first) }} - name: "DANE: TLSA-Record (3 1 1) aktualisieren" block: - name: "DANE: TLSA-Record (3 1 1) wird aktualisiert" uri: url: "https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON" method: POST body_format: json status_code: 200 body: action: "updateDnsRecords" param: customernumber: "{{ netcup_customer_number }}" apikey: "{{ netcup_api_key }}" apisessionid: "{{ netcup_session_id }}" domainname: "{{ netcup_domain }}" dnsrecordset: dnsrecords: - id: "{{ existing_tlsa_record.id if existing_tlsa_record is defined else omit }}" hostname: "{{ tlsa_host_label }}" type: "TLSA" destination: "3 1 1 {{ tlsa_hash_current }}" state: "yes" delegate_to: localhost - name: "DANE: TLSA-Record (3 1 1) prüfen" uri: url: "https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON" method: POST body_format: json return_content: true body: action: "infoDnsRecords" param: customernumber: "{{ netcup_customer_number }}" apikey: "{{ netcup_api_key }}" apisessionid: "{{ netcup_session_id }}" domainname: "{{ netcup_domain }}" register: netcup_verify_311 delegate_to: localhost - name: "DANE: TLSA-Record (3 1 1) verifizieren" set_fact: verified_tlsa_311: >- {{ (netcup_verify_311.json.responsedata.dnsrecords | selectattr('hostname', 'equalto', tlsa_host_label) | selectattr('type', 'equalto', 'TLSA') | selectattr('destination', 'equalto', '3 1 1 ' ~ tlsa_hash_current) | list | length > 0) }} when: existing_tlsa_record is not defined or existing_tlsa_record.destination != ('3 1 1 ' ~ tlsa_hash_current) - name: "DANE: TLSA-Record (2 1 1) aktualisieren" block: - name: "DANE: TLSA-Record (2 1 1) wird aktualisiert" uri: url: "https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON" method: POST body_format: json status_code: 200 body: action: "updateDnsRecords" param: customernumber: "{{ netcup_customer_number }}" apikey: "{{ netcup_api_key }}" apisessionid: "{{ netcup_session_id }}" domainname: "{{ netcup_domain }}" dnsrecordset: dnsrecords: - id: "{{ existing_tlsa_record_ca.id if existing_tlsa_record_ca is defined else omit }}" hostname: "{{ tlsa_host_label }}" type: "TLSA" destination: "2 1 1 {{ tlsa_hash_ca }}" state: "yes" delegate_to: localhost - name: "DANE: TLSA-Record (2 1 1) prüfen" uri: url: "https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON" method: POST body_format: json return_content: true body: action: "infoDnsRecords" param: customernumber: "{{ netcup_customer_number }}" apikey: "{{ netcup_api_key }}" apisessionid: "{{ netcup_session_id }}" domainname: "{{ netcup_domain }}" register: netcup_verify_211 delegate_to: localhost - name: "DANE: TLSA-Record (2 1 1) verifizieren" set_fact: verified_tlsa_211: >- {{ (netcup_verify_211.json.responsedata.dnsrecords | selectattr('hostname', 'equalto', tlsa_host_label) | selectattr('type', 'equalto', 'TLSA') | selectattr('destination', 'equalto', '2 1 1 ' ~ tlsa_hash_ca) | list | length > 0) }} when: tlsa_hash_ca | length > 0 and (existing_tlsa_record_ca is not defined or existing_tlsa_record_ca.destination != ('2 1 1 ' ~ tlsa_hash_ca))