๐Ÿ“— Ansible playbooks and roles for building an idempotent, interconnected and scalable infrastructure
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
ansible-playbooks/roles/acme/tasks/main.yml

202 lines
7.8 KiB

- name: install certbot
include_tasks: tasks/install_packages.yml
vars:
package: certbot
- name: create certbot directories
file:
path: "{{ item }}"
state: directory
loop:
- "{{ acme_directory }}"
- "{{ acme_directory }}/cron"
- name: change certbot directory permissions
file:
path: "{{ acme_directory ~ '/' ~ item }}"
state: directory
mode: "g+rx,o+rx"
loop:
- archive
- live
- name: check if acme-dns auth hook already exists
stat:
path: "{{ acme_directory }}/acme-dns-auth.py"
register: result
- name: download acme-dns auth hook
get_url:
url: "https://raw.githubusercontent.com/RangeForce/acme-dns-certbot-joohoi/master/acme-dns-auth.py"
dest: "{{ acme_directory }}/acme-dns-auth.py"
force: no
mode: "+x"
when: result.stat.exists == false
- name: update python interpreter in acme-dns-auth to python3
lineinfile:
path: "{{ acme_directory }}/acme-dns-auth.py"
regexp: '^#!\/usr\/bin\/env python\s*$'
line: '#!/usr/bin/env python3'
- name: clear acme fqdn list
set_fact:
acme_domain_list: "{{ [] }}"
- name: build acme fqdn list
set_fact:
acme_domain_list: "{{ (acme_domain_list | d([])) +
([item.fqdn | d((item.hostname | d(host_name)) ~ '.' ~ (item.tld | d(host_tld)))] if item is mapping else
[item]) }}"
loop: "{{ acme_hosts if (acme_hosts | type_debug == 'list') else [] }}"
- name: build single acme fqdn
set_fact:
acme_domain_list: "{%- if acme_fqdn is defined and acme_fqdn != None -%}\
{{ [ acme_fqdn ] }}\
{%- elif (acme_hostname is defined and acme_hostname != None) or (acme_tld is defined and acme_tld != None) -%}\
{{ [((acme_hostname is defined and acme_hostname != None) | ternary(acme_hostname, host_name)) ~ '.' ~
((acme_tld is defined and acme_tld != None) | ternary(acme_tld, host_tld))] }}\
{%- else -%}\
{{ [ host_fqdn ] }}\
{%- endif -%}"
when: (acme_hosts is not defined) or (acme_hosts | type_debug != 'list')
- name: set acme parameters
set_fact:
acme_cert_name: "{{ acme_id if (acme_id is defined) and (acme_id != None) else (host_name ~ ('-ecc' if (acme_ecc | d(false) == true) else '')) }}"
acme_target_server: "{%- if (acme_server is defined) and (acme_server != None) -%}\
{{ acme_server }}\
{%- else -%}\
{{ (services.acme_dns.protocol | d('https')) ~ '://' ~ services.acme_dns.hostname ~ '.' ~ (services.acme_dns.tld | d(int_tld)) ~
((':' ~ services.acme_dns.port) if services.acme_dns.port is defined else '') }}\
{%- endif -%}"
- name: set certbot parameters
set_fact:
acme_params: "{{ ['--manual',
'--manual-auth-hook ' ~ ((acme_directory ~ '/acme-dns-auth.py') | quote),
'--preferred-challenges dns',
'--debug-challenges',
('--key-type ecdsa' if (acme_ecc | d(false) == true) else ''),
('--staging' if (acme_staging | d(false) == true) else ''),
('--force-renewal' if (acme_force | d(false) == true) else ''),
('--must-staple' if (acme_stapling | d(false) == true) else ''),
'--cert-name ' ~ (acme_cert_name | quote),
'--non-interactive',
'--agree-tos',
'--email ' ~ ((acme_email | d(maintainer_email)) | quote),
'--no-eff-email',
(('--preferred-chain ' ~ (acme_preferred_chain | quote)) if acme_preferred_chain is defined else ''),
'--max-log-backups ' ~ (acme_max_log_backups | quote)
] | select() | list | join(' ') }}"
- block:
- name: issue cert with dns mode
shell:
cmd: "certbot certonly {{ acme_params }} -d {{ acme_domain_list | map('quote') | join(' -d ') }}"
chdir: /usr/bin
environment:
ACMEDNS_URL: "{{ acme_target_server }}"
register: result
changed_when: ('Successfully received certificate' in result.stdout)
notify: "{{ acme_notify if (acme_notify is defined) and (acme_notify != None) else omit }}"
rescue:
- name: wait for user interaction (CNAME record must be set manually)
pause:
prompt: "{{ result.stdout }}"
- name: try again to issue cert with dns mode
shell:
cmd: "certbot certonly {{ acme_params }} -d {{ acme_domain_list | map('quote') | join(' -d ') }}"
chdir: /usr/bin
environment:
ACMEDNS_URL: "{{ acme_target_server }}"
register: result
changed_when: ('Successfully received certificate' in result.stdout)
notify: "{{ acme_notify if (acme_notify is defined) and (acme_notify != None) else omit }}"
- name: create symlinks
file:
path: "{{ item.dest }}"
src: "{{ acme_directory ~ '/live/' ~ acme_cert_name ~ '/' ~ item.src }}"
state: link
force: yes
when: (item.dest is string) and (item.dest | length > 0) and (acme_use_symlinks | d(true) == true)
loop:
- { src: 'fullchain.pem', dest: "{{ acme_cert | d(None) }}" }
- { src: 'privkey.pem', dest: "{{ acme_key | d(None) }}" }
- { src: 'cert.pem', dest: "{{ acme_cert_single | d(None) }}" }
- { src: 'chain.pem', dest: "{{ acme_chain | d(None) }}" }
notify: "{{ acme_notify if (acme_notify is defined) and (acme_notify != None) else omit }}"
- name: fix ownership on archive dir
file:
path: "{{ acme_directory ~ '/archive/' ~ acme_cert_name }}"
follow: no
recurse: yes
owner: "{{ acme_owner if (acme_owner is defined) and (acme_owner != None) else omit }}"
group: "{{ acme_group if (acme_group is defined) and (acme_group != None) else omit }}"
- name: copy certs
copy:
src: "{{ acme_directory ~ '/live/' ~ acme_cert_name ~ '/' ~ item.src }}"
dest: "{{ item.dest }}"
remote_src: yes
mode: 0600
owner: "{{ acme_owner if (acme_owner is defined) and (acme_owner != None) else omit }}"
group: "{{ acme_group if (acme_group is defined) and (acme_group != None) else omit }}"
when: (item.dest is string) and (item.dest | length > 0) and (acme_use_symlinks | d(true) == false)
loop:
- { src: 'fullchain.pem', dest: "{{ acme_cert | d(None) }}" }
- { src: 'privkey.pem', dest: "{{ acme_key | d(None) }}" }
- { src: 'cert.pem', dest: "{{ acme_cert_single | d(None) }}" }
- { src: 'chain.pem', dest: "{{ acme_chain | d(None) }}" }
notify: "{{ acme_notify | d(omit) }}"
- name: edit renewal file
lineinfile:
path: "{{ acme_directory ~ '/renewal/' ~ acme_cert_name ~ '.conf' }}"
regexp: '^{{ item.name | regex_escape }}(\s+)='
line: '{{ item.name }} = {{ item.value }}'
insertafter: '^\[renewalparams\]'
create: no
firstmatch: yes
when: (item.value is string) and (item.value | length > 0) and
((item.extra_condition is not defined) or (item.extra_condition | d(true)))
loop:
- { name: 'renew_hook', value: "{{ acme_directory ~ '/cron/' ~ acme_cert_name ~ '.sh' }}" }
- name: create custom renewal hook file
template:
src: renewal.j2
dest: "{{ acme_directory ~ '/cron/' ~ acme_cert_name ~ '.sh' }}"
force: yes
mode: 0500
lstrip_blocks: yes
- name: add certbot to crontab
cron:
name: "certbot renewal ({{ acme_cert_name ~ ' on ' ~ acme_target_server }})"
job: "ACMEDNS_URL={{ acme_target_server | quote }} \
/usr/bin/certbot renew --cert-name {{ acme_cert_name | quote }} --max-log-backups {{ acme_max_log_backups | quote }}"
hour: "{{ 4 | random(start=1, seed=(host_name ~ acme_cert_name)) }}"
minute: "{{ 59 | random(seed=(host_name ~ acme_cert_name)) }}"