From c16d5904d2735bf897d6b940a0d38d75fd9d780a Mon Sep 17 00:00:00 2001 From: Renovate Date: Tue, 31 Mar 2026 00:32:24 +0000 Subject: [PATCH 1/4] Update docker.io/pretalx/standalone Docker tag to v2025.2.3 --- resources/chaosknoten/pretalx/docker_compose/compose.yaml.j2 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/chaosknoten/pretalx/docker_compose/compose.yaml.j2 b/resources/chaosknoten/pretalx/docker_compose/compose.yaml.j2 index 0bbfcb8..78dba42 100644 --- a/resources/chaosknoten/pretalx/docker_compose/compose.yaml.j2 +++ b/resources/chaosknoten/pretalx/docker_compose/compose.yaml.j2 @@ -33,7 +33,7 @@ services: - pretalx_net pretalx: - image: docker.io/pretalx/standalone:v2025.1.0 + image: docker.io/pretalx/standalone:v2025.2.3 entrypoint: gunicorn command: - "pretalx.wsgi" @@ -78,7 +78,7 @@ services: - pretalx_net celery: - image: docker.io/pretalx/standalone:v2025.1.0 + image: docker.io/pretalx/standalone:v2025.2.3 command: - taskworker restart: unless-stopped From 8bf6dfbefb9f88a89f18a48608921e576b9dabf3 Mon Sep 17 00:00:00 2001 From: June Date: Tue, 31 Mar 2026 16:48:00 +0200 Subject: [PATCH 2/4] certbot(role): support DNS-01 certs using acme-dns Introduce new configuration structure called certbot__certs, which allows for different challenge types per cert with the first challenge type supported being dns-01-acme-dns. --- roles/certbot/README.md | 30 +++++++++++++ roles/certbot/defaults/main.yaml | 1 + .../manual_auth_scripts/dns-01-acme-dns.sh | 14 +++++++ roles/certbot/meta/argument_specs.yaml | 19 +++++++++ roles/certbot/tasks/main/cert.yaml | 42 ++++++++++++++----- roles/certbot/tasks/main/certs.yaml | 12 +++++- roles/certbot/tasks/main/http_01_cert.yaml | 24 +++++++++++ roles/certbot/tasks/main/install.yaml | 33 +++++++++++---- roles/certbot/tasks/main/validate_cert.yaml | 31 ++++++++++++++ 9 files changed, 188 insertions(+), 18 deletions(-) create mode 100644 roles/certbot/files/manual_auth_scripts/dns-01-acme-dns.sh create mode 100644 roles/certbot/tasks/main/http_01_cert.yaml create mode 100644 roles/certbot/tasks/main/validate_cert.yaml diff --git a/roles/certbot/README.md b/roles/certbot/README.md index 3ad35bf..d9b7e73 100644 --- a/roles/certbot/README.md +++ b/roles/certbot/README.md @@ -6,6 +6,24 @@ Note: This role doesn't take care of deleting certificates. Also see the following documentation for a full How-to on how to get certificates using this role in the context of our infra: . +## Challenge Types + +For the `certbot__certs` configuration, depending on the challenge type, different preparation needs to be done. + +### `dns-01-acme-dns` + +For the `dns-01-acme-dns` challenge type, ensure that: + +1. An acme-dns entry got registered, so you have access to the `subdomain`, `apiUser` and `apiKey` for the configuration. The `apiKey` should be stored in some kind of secret, which then gets referenced in the relevant `certbot__certs` configuration. + For our acme-dns instance, go to , sign-in and register a new entry. +2. CNAME the `_acme-challenge` domain of the domain you want to obtain a certificate for (`certbot__certs.*.commonName`) to the full domain from the registration. + It should look something like this: `_acme-challenge.domainiwantcertfor.tld. CNAME 3ed25037-79f1-4a89-8934-db3e162fe2bc.auth.acmedns.hamburg.ccc.de.` + +For more info see: + +- [The ACME DNS hamburg.ccc.de Wiki page](https://wiki.hamburg.ccc.de/infrastructure:services:acme_dns) +- [The acme-dns GitHub](https://github.com/acme-dns/acme-dns) + ## Required Arguments - `certbot__acme_account_email_address`: The E-Mail address to use for the ACME account. @@ -15,6 +33,18 @@ Also see the following documentation for a full How-to on how to get certificate - `certbot__certificate_domains`: The domains for which to obtain a certificate using the HTTP-01 challenge. - `certbot__http_01_port`: The port number the bot listens on. Should be `80` if directly exposed to the internet. Defaults to `31820` (for the public-reverse-proxy setup). +- `certbot__certs`: Certificates to create. + Defaults to the empty list (`[ ]`). +- `certbot__certs.*.commonName`: The common name to issue the certificate for. +- `certbot__certs.*.challengeType`: The challenge type to use for getting the certificate. Challenge type-specific configuration must be provided as well. + Should be one of: + - `dns-01-acme-dns` +- `certbot__certs.*.dns_01_acme_dns`: Configuration for the `dns-01-acme-dns` challenge type. +- `certbot__certs.*.dns_01_acme_dns.serverUrl`: The acme-dns server API URL. + Defaults to `https://acmedns.hamburg.ccc.de`. +- `certbot__certs.*.dns_01_acme_dns.subdomain`: The acme-dns subdomain to use. +- `certbot__certs.*.dns_01_acme_dns.apiUser`: The acme-dns API user to use. +- `certbot__certs.*.dns_01_acme_dns.apiKey`: The acme-dns API key to use. - `certbot__new_cert_commands`: A list of commands to execute after getting a new certificate. Will be added into a bash script. Defaults to the empty list (`[ ]`). diff --git a/roles/certbot/defaults/main.yaml b/roles/certbot/defaults/main.yaml index 9e6551e..ab48293 100644 --- a/roles/certbot/defaults/main.yaml +++ b/roles/certbot/defaults/main.yaml @@ -1,3 +1,4 @@ certbot__certificate_domains: [ ] certbot__http_01_port: 31820 +certbot__certs: [ ] certbot__new_cert_commands: [ ] diff --git a/roles/certbot/files/manual_auth_scripts/dns-01-acme-dns.sh b/roles/certbot/files/manual_auth_scripts/dns-01-acme-dns.sh new file mode 100644 index 0000000..1b39bd6 --- /dev/null +++ b/roles/certbot/files/manual_auth_scripts/dns-01-acme-dns.sh @@ -0,0 +1,14 @@ +# #!/usr/bin/env bash + +CERT_CONFIG_FILE="/etc/ansible_certbot/cert_configs/$CERTBOT_DOMAIN.json" +ACME_DNS_SERVER_URL=$( jq -er '.dns_01_acme_dns.serverUrl' "$CERT_CONFIG_FILE" ) +export ACME_DNS_SUBDOMAIN=$( jq -er '.dns_01_acme_dns.subdomain' "$CERT_CONFIG_FILE" ) +ACME_DNS_API_USER=$( jq -er '.dns_01_acme_dns.apiUser' "$CERT_CONFIG_FILE" ) +ACME_DNS_API_KEY=$( jq -er '.dns_01_acme_dns.apiKey' "$CERT_CONFIG_FILE" ) + +jq -nec '{ "subdomain": env.ACME_DNS_SUBDOMAIN, "txt": env.CERTBOT_VALIDATION }' | curl "$ACME_DNS_SERVER_URL/update" \ + --request POST \ + --fail-with-body \ + --header "X-Api-User: $ACME_DNS_API_USER" \ + --header "X-Api-Key: $ACME_DNS_API_KEY" \ + --json @- diff --git a/roles/certbot/meta/argument_specs.yaml b/roles/certbot/meta/argument_specs.yaml index b895b5f..4899ea8 100644 --- a/roles/certbot/meta/argument_specs.yaml +++ b/roles/certbot/meta/argument_specs.yaml @@ -13,6 +13,25 @@ argument_specs: type: str required: false default: 31820 + certbot__certs: + type: list + elements: dict + required: false + default: [ ] + options: + commonName: + type: str + required: true + # ToDo: subjectAlternativeNames: + challengeType: + type: str + required: true + choices: + - dns-01-acme-dns + dns_01_acme_dns: + type: dict + required: false + # Further checking done in tasks/validate_cert.yaml certbot__new_cert_commands: type: list elements: str diff --git a/roles/certbot/tasks/main/cert.yaml b/roles/certbot/tasks/main/cert.yaml index d829fb1..61f6d45 100644 --- a/roles/certbot/tasks/main/cert.yaml +++ b/roles/certbot/tasks/main/cert.yaml @@ -1,24 +1,46 @@ - name: get expiry date before - ansible.builtin.command: /usr/bin/openssl x509 -enddate -noout -in /etc/letsencrypt/live/{{ item }}/fullchain.pem + ansible.builtin.command: /usr/bin/openssl x509 -enddate -noout -in /etc/letsencrypt/live/{{ item.commonName }}/fullchain.pem ignore_errors: true become: true changed_when: false register: certbot__cert_expiry_before -- name: obtain the certificate using certbot - ansible.builtin.command: /usr/bin/certbot certonly --keep-until-expiring --agree-tos --non-interactive --email "{{ certbot__acme_account_email_address }}" --no-eff-email --standalone --http-01-port "{{ certbot__http_01_port }}" -d "{{ item }}" +- name: ensure directory for cert configs exists + ansible.builtin.file: + path: "/etc/ansible_certbot/cert_configs/" + state: directory + owner: root + group: root + mode: "0750" + become: true + +- name: ensure cert config is stored + ansible.builtin.copy: + content: "{{ cert_config_defaults[item.challengeType] | combine(item, recursive=True) | ansible.builtin.to_nice_json }}" + dest: "/etc/ansible_certbot/cert_configs/{{ item.commonName }}.json" + owner: root + group: root + mode: "0640" + become: true + vars: + cert_config_defaults: + dns-01-acme-dns: + dns_01_acme_dns: + serverUrl: "https://acmedns.hamburg.ccc.de" + +# # https://eff-certbot.readthedocs.io/en/stable/using.html#manual +- name: obtain the certificate using certbot and the manual auth hook + ansible.builtin.command: /usr/bin/certbot certonly --keep-until-expiring --agree-tos --non-interactive --email "{{ certbot__acme_account_email_address }}" --no-eff-email --manual --preferred-challenge dns --manual-auth-hook "/usr/local/lib/ansible_certbot/manual_auth_scripts/{{ item.challengeType }}.sh" -d "{{ item.commonName }}" become: true changed_when: false - name: get expiry date after - ansible.builtin.command: /usr/bin/openssl x509 -enddate -noout -in /etc/letsencrypt/live/{{ item }}/fullchain.pem + ansible.builtin.command: /usr/bin/openssl x509 -enddate -noout -in /etc/letsencrypt/live/{{ item.commonName }}/fullchain.pem become: true changed_when: false register: certbot__cert_expiry_after -# Doesn't work anymore. Dunno why. -# TODO: Fix -# - name: potentially report changed -# ansible.builtin.debug: -# msg: "If this reports changed, then the certificate expiry date and therefore the certificate changed." -# changed_when: certbot__cert_expiry_before.stdout != certbot__cert_expiry_after.stdout +- name: potentially report changed + ansible.builtin.debug: + msg: "If this reports changed, then the certificate expiry date and therefore the certificate changed." + changed_when: certbot__cert_expiry_before.stdout != certbot__cert_expiry_after.stdout diff --git a/roles/certbot/tasks/main/certs.yaml b/roles/certbot/tasks/main/certs.yaml index 2b91184..16271b7 100644 --- a/roles/certbot/tasks/main/certs.yaml +++ b/roles/certbot/tasks/main/certs.yaml @@ -1,4 +1,14 @@ -- name: obtain certificates +- name: obtain http-01 challenge certificates loop: "{{ certbot__certificate_domains }}" + ansible.builtin.include_tasks: + file: main/http_01_cert.yaml + +- name: validate certs config + loop: "{{ certbot__certs }}" + ansible.builtin.include_tasks: + file: main/validate_cert.yaml + +- name: obtain certs + loop: "{{ certbot__certs }}" ansible.builtin.include_tasks: file: main/cert.yaml diff --git a/roles/certbot/tasks/main/http_01_cert.yaml b/roles/certbot/tasks/main/http_01_cert.yaml new file mode 100644 index 0000000..d829fb1 --- /dev/null +++ b/roles/certbot/tasks/main/http_01_cert.yaml @@ -0,0 +1,24 @@ +- name: get expiry date before + ansible.builtin.command: /usr/bin/openssl x509 -enddate -noout -in /etc/letsencrypt/live/{{ item }}/fullchain.pem + ignore_errors: true + become: true + changed_when: false + register: certbot__cert_expiry_before + +- name: obtain the certificate using certbot + ansible.builtin.command: /usr/bin/certbot certonly --keep-until-expiring --agree-tos --non-interactive --email "{{ certbot__acme_account_email_address }}" --no-eff-email --standalone --http-01-port "{{ certbot__http_01_port }}" -d "{{ item }}" + become: true + changed_when: false + +- name: get expiry date after + ansible.builtin.command: /usr/bin/openssl x509 -enddate -noout -in /etc/letsencrypt/live/{{ item }}/fullchain.pem + become: true + changed_when: false + register: certbot__cert_expiry_after + +# Doesn't work anymore. Dunno why. +# TODO: Fix +# - name: potentially report changed +# ansible.builtin.debug: +# msg: "If this reports changed, then the certificate expiry date and therefore the certificate changed." +# changed_when: certbot__cert_expiry_before.stdout != certbot__cert_expiry_after.stdout diff --git a/roles/certbot/tasks/main/install.yaml b/roles/certbot/tasks/main/install.yaml index 40ccf75..d0fa58d 100644 --- a/roles/certbot/tasks/main/install.yaml +++ b/roles/certbot/tasks/main/install.yaml @@ -1,11 +1,30 @@ -- name: make sure the `openssl` package is installed +- name: ensure relevant packages are installed ansible.builtin.apt: - name: openssl + name: + - openssl + - certbot + - jq state: present become: true -- name: make sure the `certbot` package is installed - ansible.builtin.apt: - name: certbot - state: present - become: true +- name: ensure manual auth scripts are deployed + block: + - name: ensure manual auth scripts directory exists + ansible.builtin.file: + path: "/usr/local/lib/ansible_certbot/manual_auth_scripts" + state: directory + owner: root + group: root + mode: "0755" + become: true + + - name: ensure manual auth scripts are deployed + ansible.builtin.copy: + src: "manual_auth_scripts/{{ item }}.sh" + dest: "/usr/local/lib/ansible_certbot/manual_auth_scripts/{{ item }}.sh" + owner: root + group: root + mode: "0754" + become: true + loop: + - "dns-01-acme-dns" diff --git a/roles/certbot/tasks/main/validate_cert.yaml b/roles/certbot/tasks/main/validate_cert.yaml new file mode 100644 index 0000000..a13b3b9 --- /dev/null +++ b/roles/certbot/tasks/main/validate_cert.yaml @@ -0,0 +1,31 @@ +- name: validate dns-01-acme-dns challenge type config + when: item.challengeType == "dns-01-acme-dns" + block: + - name: assert dns_01_acme_dns config exists + ansible.builtin.assert: + that: item.dns_01_acme_dns is defined + + - name: assert dns_01_acme_dns config is valid + ansible.builtin.validate_argument_spec: + argument_spec: "{{ required_data }}" + provided_arguments: + dns_01_acme_dns: "{{ item.dns_01_acme_dns }}" + vars: + required_data: + dns_01_acme_dns: + type: dict + required: true + options: + serverUrl: + type: str + required: false + default: https://acmedns.hamburg.ccc.de + subdomain: + type: str + required: true + apiUser: + type: str + required: true + apiKey: + type: str + required: true From 73ed238a281d3be12fc4b18dbc1b9e7260710db1 Mon Sep 17 00:00:00 2001 From: June Date: Tue, 31 Mar 2026 16:59:02 +0200 Subject: [PATCH 3/4] sunders(host): move to dns-01-acme-dns --- inventories/chaosknoten/host_vars/sunders.sops.yaml | 7 ++++--- inventories/chaosknoten/host_vars/sunders.yaml | 9 +++++++-- .../public-reverse-proxy/nginx/acme_challenge.conf | 1 - 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/inventories/chaosknoten/host_vars/sunders.sops.yaml b/inventories/chaosknoten/host_vars/sunders.sops.yaml index 904a8fb..75b2d10 100644 --- a/inventories/chaosknoten/host_vars/sunders.sops.yaml +++ b/inventories/chaosknoten/host_vars/sunders.sops.yaml @@ -2,6 +2,7 @@ ansible_pull__age_private_key: ENC[AES256_GCM,data:tP84jDYh2zeWjf7wqDoefm9zaeg/Q secret__sunders_db_root_password: ENC[AES256_GCM,data:m3Xt6dOKibRflon/rWG9KmdBPHEBbqE/GIpKdFI1Di7Lpl/THxzrgx12mTK6aZnwDrM=,iv:hD/UGwo88ye9CxyTCEQ0SVon2+ipPjeA9NF2/OhYwmc=,tag:DRdQ5hvTgUO5FVae/ul7kQ==,type:str] secret__sunders_db_camera_password: ENC[AES256_GCM,data:tOt4ImpedgfGvRpcThPO30YyEl/bP244ruJQzAYodJIsEhFuk5LxHpPASEnsqlN6m3M=,iv:rQXBjiYWZlzeUdaqDdTlrdbSSqGaPDeZOPhUaMjgcjU=,tag:lkSlIdJWFowyPfWEjpC/Zg==,type:str] secret__sunders_db_camera_select_password: ENC[AES256_GCM,data:PveGcD2WmvpMc8bafGY1c45aQ2XH/ym2yj5YacauQPeZO6Xem3kaxU0kwjs0Wd26ugc=,iv:tk288L9i0lxsJbTFq5ET5IiKkJfMQwc6uKNFXILcD7o=,tag:hOIivp3mOtDNBCsKvrSrBw==,type:str] +secret__acme_dns_api_key_sunders_hamburg_ccc_de: ENC[AES256_GCM,data:OXGasHc1qf4r8wj0N2qqjZzWO3zg5L5ZGRjAwmlEXi9/gaTpUWT1xw==,iv:pKaNajQ9JxxUwuv/scJZhP6UdSob3GrODShk2t75580=,tag:3atM6IddvYhoAmNlqooooQ==,type:str] sops: age: - recipient: age1na0nh9ndnr9cxpnlvstrxskr4fxf4spnkw48ufl7m43f98y40y7shhnvgd @@ -13,8 +14,8 @@ sops: KzBPS0Y3OXBUSjFWRVQwaGQ0M2M4ajgKYGgcfiDsBsJQmSodfPejhAcAekatqkJ9 Xa6Y7mWUiAKcbYY9to7/7/u5FUW3JYdv1GX7vAC2ZtZv6WXGLtjdwg== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-11-01T16:32:10Z" - mac: ENC[AES256_GCM,data:8Q6DBSFtzwHuVxduRlZYxlRWO0trSoesNGUR8r/dWnp9ashFBSZqVyffXb4Vq6DB5thANJ6/b3PCNsHdiAKn6Ai2UT8G0HimFjUUgNpZxo4xoNGmDhDvfdBgUL6O2pHhY+ojjguUXDYeYc99+eaxfKqZ3w+PAPaySltKm99foz8=,iv:ILOErdiWbUjk9kovXXZYcAqZFQp2Wo1Tm14sgK3niWg=,tag:Q2gT6wbQyhDXjoQEG2Lngw==,type:str] + lastmodified: "2026-03-31T14:49:48Z" + mac: ENC[AES256_GCM,data:Ln//LDvcSqRknJGQvdzwoRvZOK/ZBqfTJqnxm9fhQsfZudUchZ5u7PXQf/0y3GnrYVx9agGGoKQN3VFehrZfpUE4ygjvmLHiqJSZVREaD4Yq0PuaY54/wN4YX8H1nE7ILdm1QvFRzpMUpSnTM1Yrr6cMIFMHcRRpL73LfG1nwPY=,iv:NXT/cPTnbg7ESiI7Aj3OOpCLMM4j355NAMVJNsIppzY=,tag:iLFTfiFdDxpwvC88fLf3Mw==,type:str] pgp: - created_at: "2026-03-05T19:17:55Z" enc: |- @@ -216,4 +217,4 @@ sops: -----END PGP MESSAGE----- fp: 41FFAF3D519CF5C039FBD8414BCC213729AF0E49 unencrypted_suffix: _unencrypted - version: 3.11.0 + version: 3.12.1 diff --git a/inventories/chaosknoten/host_vars/sunders.yaml b/inventories/chaosknoten/host_vars/sunders.yaml index 4563a46..1fd5c40 100644 --- a/inventories/chaosknoten/host_vars/sunders.yaml +++ b/inventories/chaosknoten/host_vars/sunders.yaml @@ -1,8 +1,13 @@ docker_compose__compose_file_content: "{{ lookup('ansible.builtin.template', 'resources/chaosknoten/sunders/docker_compose/compose.yaml.j2') }}" certbot__acme_account_email_address: le-admin@hamburg.ccc.de -certbot__certificate_domains: - - "sunders.hamburg.ccc.de" +certbot__certs: + - commonName: "sunders.hamburg.ccc.de" + challengeType: "dns-01-acme-dns" + dns_01_acme_dns: + subdomain: a5ee8a99-3cdf-4212-972e-c0b6fda1242f + apiUser: 45e4ea1c-8322-4d92-9f52-adf52b521131 + apiKey: "{{ secret__acme_dns_api_key_sunders_hamburg_ccc_de }}" certbot__new_cert_commands: - "systemctl reload nginx.service" diff --git a/resources/chaosknoten/public-reverse-proxy/nginx/acme_challenge.conf b/resources/chaosknoten/public-reverse-proxy/nginx/acme_challenge.conf index d79f299..7bb4993 100644 --- a/resources/chaosknoten/public-reverse-proxy/nginx/acme_challenge.conf +++ b/resources/chaosknoten/public-reverse-proxy/nginx/acme_challenge.conf @@ -29,7 +29,6 @@ map $host $upstream_acme_challenge_host { wiki.hamburg.ccc.de wiki.hosts.hamburg.ccc.de:31820; www.hamburg.ccc.de 172.31.17.151:31820; tickets.hamburg.ccc.de tickets.hosts.hamburg.ccc.de:31820; - sunders.hamburg.ccc.de sunders.hosts.hamburg.ccc.de:31820; zammad.hamburg.ccc.de zammad.hosts.hamburg.ccc.de:31820; eh03.easterhegg.eu 172.31.17.151:31820; eh05.easterhegg.eu 172.31.17.151:31820; From a1aa7a4cd8e8c5d17724e537e86a5cda13dd1cd9 Mon Sep 17 00:00:00 2001 From: Renovate Date: Tue, 31 Mar 2026 15:00:57 +0000 Subject: [PATCH 4/4] Update docker.io/pretalx/standalone Docker tag to v2025.2.3 --- resources/chaosknoten/pretalx/docker_compose/compose.yaml.j2 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/chaosknoten/pretalx/docker_compose/compose.yaml.j2 b/resources/chaosknoten/pretalx/docker_compose/compose.yaml.j2 index 0bbfcb8..78dba42 100644 --- a/resources/chaosknoten/pretalx/docker_compose/compose.yaml.j2 +++ b/resources/chaosknoten/pretalx/docker_compose/compose.yaml.j2 @@ -33,7 +33,7 @@ services: - pretalx_net pretalx: - image: docker.io/pretalx/standalone:v2025.1.0 + image: docker.io/pretalx/standalone:v2025.2.3 entrypoint: gunicorn command: - "pretalx.wsgi" @@ -78,7 +78,7 @@ services: - pretalx_net celery: - image: docker.io/pretalx/standalone:v2025.1.0 + image: docker.io/pretalx/standalone:v2025.2.3 command: - taskworker restart: unless-stopped