certbot(role): support DNS-01 certs using acme-dns
All checks were successful
/ Ansible Lint (push) Successful in 3m36s
All checks were successful
/ Ansible Lint (push) Successful in 3m36s
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.
This commit is contained in:
parent
21f51ea2d7
commit
8bf6dfbefb
9 changed files with 188 additions and 18 deletions
|
|
@ -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: <https://wiki.ccchh.net/infrastructure:zertifikate>.
|
Also see the following documentation for a full How-to on how to get certificates using this role in the context of our infra: <https://wiki.ccchh.net/infrastructure:zertifikate>.
|
||||||
|
|
||||||
|
## 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 <https://acmedns.hamburg.ccc.de/>, 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
|
## Required Arguments
|
||||||
|
|
||||||
- `certbot__acme_account_email_address`: The E-Mail address to use for the ACME account.
|
- `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__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.
|
- `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).
|
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.
|
- `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 (`[ ]`).
|
Defaults to the empty list (`[ ]`).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
certbot__certificate_domains: [ ]
|
certbot__certificate_domains: [ ]
|
||||||
certbot__http_01_port: 31820
|
certbot__http_01_port: 31820
|
||||||
|
certbot__certs: [ ]
|
||||||
certbot__new_cert_commands: [ ]
|
certbot__new_cert_commands: [ ]
|
||||||
|
|
|
||||||
14
roles/certbot/files/manual_auth_scripts/dns-01-acme-dns.sh
Normal file
14
roles/certbot/files/manual_auth_scripts/dns-01-acme-dns.sh
Normal file
|
|
@ -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 @-
|
||||||
|
|
@ -13,6 +13,25 @@ argument_specs:
|
||||||
type: str
|
type: str
|
||||||
required: false
|
required: false
|
||||||
default: 31820
|
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:
|
certbot__new_cert_commands:
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,46 @@
|
||||||
- name: get expiry date before
|
- 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
|
ignore_errors: true
|
||||||
become: true
|
become: true
|
||||||
changed_when: false
|
changed_when: false
|
||||||
register: certbot__cert_expiry_before
|
register: certbot__cert_expiry_before
|
||||||
|
|
||||||
- name: obtain the certificate using certbot
|
- name: ensure directory for cert configs exists
|
||||||
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 }}"
|
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
|
become: true
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
- name: get expiry date after
|
- 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
|
become: true
|
||||||
changed_when: false
|
changed_when: false
|
||||||
register: certbot__cert_expiry_after
|
register: certbot__cert_expiry_after
|
||||||
|
|
||||||
# Doesn't work anymore. Dunno why.
|
- name: potentially report changed
|
||||||
# TODO: Fix
|
ansible.builtin.debug:
|
||||||
# - name: potentially report changed
|
msg: "If this reports changed, then the certificate expiry date and therefore the certificate changed."
|
||||||
# ansible.builtin.debug:
|
changed_when: certbot__cert_expiry_before.stdout != certbot__cert_expiry_after.stdout
|
||||||
# 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
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,14 @@
|
||||||
- name: obtain certificates
|
- name: obtain http-01 challenge certificates
|
||||||
loop: "{{ certbot__certificate_domains }}"
|
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:
|
ansible.builtin.include_tasks:
|
||||||
file: main/cert.yaml
|
file: main/cert.yaml
|
||||||
|
|
|
||||||
24
roles/certbot/tasks/main/http_01_cert.yaml
Normal file
24
roles/certbot/tasks/main/http_01_cert.yaml
Normal file
|
|
@ -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
|
||||||
|
|
@ -1,11 +1,30 @@
|
||||||
- name: make sure the `openssl` package is installed
|
- name: ensure relevant packages are installed
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
name: openssl
|
name:
|
||||||
|
- openssl
|
||||||
|
- certbot
|
||||||
|
- jq
|
||||||
state: present
|
state: present
|
||||||
become: true
|
become: true
|
||||||
|
|
||||||
- name: make sure the `certbot` package is installed
|
- name: ensure manual auth scripts are deployed
|
||||||
ansible.builtin.apt:
|
block:
|
||||||
name: certbot
|
- name: ensure manual auth scripts directory exists
|
||||||
state: present
|
ansible.builtin.file:
|
||||||
become: true
|
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"
|
||||||
|
|
|
||||||
31
roles/certbot/tasks/main/validate_cert.yaml
Normal file
31
roles/certbot/tasks/main/validate_cert.yaml
Normal file
|
|
@ -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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue