ciao
My Topic seems trivial and maybe it is.
I created a playbook that replaces certificates that are expiring in the next 20 days on different number of nginx webserver (in the host group:webserver).
It first performs a check on the certificate repository different server that the certificate and its private key are correct. It performs a second check that the certificate expires in the following year. If these checks are successful, it copies the certificates from the server repository to a local repo_temp directory in ansible localhost, performs a backup of the certificate present on each destination nginx webserver and copies the certificate to be replaced from ansible repo_temp localhost to all the destination nginx webservers.
The problem that I can’t solve is that if even just in one of the individual webservers the certificate must not be replaced, ansible does not replace it in the others either.
Probably I have not configured something correctly
Thanks Germano
Hi
Should it be possible to share your playbook ?
ciao motorbass
sure … and thanks in advance 
regards
Germano
---
#mailto germano.bosatra@objectway.com
#date 2025-02-18
#script vesion 1.6
####################################the script replaces the certificate and its private key from a source to different destination target web servers:###########################################
# #
#1 check on the destination web server if the certificate expires within the next 20 days or has already expired (otherwise it will not be updated) #
#2 check on the new certificate repository the validity of the certificate to be replaced by verifying that the expiration date is greater than or equal to "the current calendar year +1" #
#3 check on the new certificate repository that the private key has the same hash as the certificate #
#4 copy the certificate and the key from the source server to a local repository that it creates on the ansible server #
#5 perform a key and certificate backup on the destination servers #
#6 From the located repository on the ansible server I copy the key and certificate to the destination server #
#7 check on the destination that the private key matches the certificate and that the certificate expires the following year #
#8 check the nginx configuration nginx -T #
#9 perform a nginx reload nginx -s reload #
#10 verify uri validity with new certificate and key #
#################################################################################################################################################################################################
###############################################################################VAR SET##########################################################################################################
#The name of the certificate file and the private key is defined in the file: ansible/hosts {{ cer_name }} {{ key_name }} #
#The path to the certificate repo and the path to copy to the destination web servers are defined in the file ansible/hosts {{ webserver_path }} {{ repository_path }} {{ repo_temp_ansible }} #
###############################################################################VAR SET ##########################################################################################################
#############################################required configuration of current ansible/hosts file (or we can add variables inside this playbook)) ###############################################
#[all:vars] #
#repo_temp_ansible=/home/ansible/repo_ansible_tmp #
#[webserver] #
#x.x.x.15 repository_path=/opt/repo_crt webserver_path=/etc/nginx/certs cer_name=star_objectway_it.crt key_name=star_objectway_it.key host_uri_to_check=https://example.com #nfs-tst-tm03 #
#x.x.x.16 repository_path=/opt/repo_crt webserver_path=/etc/nginx/certs cer_name=star_objectway_it.crt key_name=star_objectway_it.key host_uri_to_check=https://example.com #nfs-tst-tm03 #
#[repository_crt] #
#x.x.x.14 repository_path=/opt/repo_crt webserver_path=/etc/nginx/certs cer_name=star_objectway_it.crt key_name=star_objectway_it.key #nfs-tst-tm02 #
#################################################################################################################################################################################################
#Check expiration date on certificates installed on web servers (if the "number of actual days before expiration -20 <= 0", the certificate will have to be replaced because it has expired or there are 20 or less than 20 days left before expiration)
#clear the ansible repo dir {{ repo_temp_ansible }} , if doesn't exist create it empty
- name: Delete a non-empty folder on the Ansible control machine
hosts: localhost
gather_facts: no
tasks:
- name: Ensure the {{ repo_temp_ansible }} is removed
file:
path: "{{ repo_temp_ansible }}"
state: absent
- name: Copy certificate from Ansible repo to destination hosts
hosts: webserver
remote_user: ansible
become: yes
gather_facts: yes
tasks:
- name: Check certificate expiration
block:
- name: Get the certificate expiration date
command: openssl x509 -enddate -noout -in {{ webserver_path }}/{{ cer_name }}
register: cert_expiration
ignore_errors: no
- name: Parse the certificate expiration date
set_fact:
expiration_date: "{{ cert_expiration.stdout.split('=')[1] | trim }}"
- name: Convert expiration date to datetime
set_fact:
expiration_datetime: "{{ expiration_date | to_datetime('%b %d %H:%M:%S %Y %Z') }}"
- name: Convert current date to datetime
set_fact:
current_datetime: "{{ ansible_date_time.iso8601 | to_datetime('%Y-%m-%dT%H:%M:%SZ') }}"
- name: Calculate days until expiration
set_fact:
days_until_expiration: "{{ ((expiration_datetime | to_datetime('%Y-%m-%d %H:%M:%S')) - (current_datetime | to_datetime('%Y-%m-%d %H:%M:%S'))).total_seconds() // (60 * 60 * 24) | int }}"
- name: Print days until expiration
debug:
msg: "Days until certificate expiration: {{ days_until_expiration }}"
- name: Calculate days difference with threshold
set_fact:
days_difference: "{{ ( days_until_expiration | int) - 20 }}"
- name: Print days until expiration
debug:
msg: "The certificate can be replaced after : {{ days_difference }} day"
- name: Fail if the certificate will expire in less than or equal to 20 days
fail:
msg: "The certificate will expire in {{ days_difference }} days, which is less than or equal to the 20-day threshold."
when: "( days_difference | int) >= 0 "
#added rescue to let fail next steps only for server that does not meet update conditions
rescue:
- name: Print failure message
debug:
msg: "Skipping just server due to certificate expiration issue."
#connect to the server repository and verify that the certificates to be installed on the web server expire at least one year later and have the same hash as their key
- name: Verify and copy HTTPS certificate
hosts: repository_crt
gather_facts: yes
remote_user: ansible
become: yes
tasks:
- name: Print Variables Set in Hosts Ansible Config File (repository_crt)
debug:
msg: "'repotemp ansible':{{ repo_temp_ansible }} 'repo path':{{ repository_path }} 'webserver path':{{ webserver_path }} 'cer name':{{ cer_name }} 'key name':{{ key_name }}"
- name: Verify and print HTTPS certificate expiration date
command: openssl x509 -enddate -noout -in {{ repository_path }}/{{ cer_name }}
register: cert_expiration
ignore_errors: yes
- name: Parse certificate expiration date
set_fact:
expiration_date: "{{ cert_expiration.stdout.split('=')[1] | trim }}"
when: cert_expiration.stdout is search('notAfter=')
- name: Print certificate expiration date
debug:
msg: "{{ expiration_date }}"
when: cert_expiration.stdout is search('notAfter=')
#check if the certificate expires in at least one year (if the certificate is renewed even for just 6 months the script does not copy it)
- name: Check if certificate expires next year
set_fact:
expires_next_year: "{{ (expiration_date | to_datetime('%b %d %H:%M:%S %Y %Z')).year >= (ansible_date_time.date | to_datetime('%Y-%m-%d')).year + 1 }}"
when: cert_expiration.stdout is search('notAfter=')
- name: debug expires next year
debug:
msg: "{{ expires_next_year }}"
- name: Ensure expires_next_year is defined
set_fact:
expires_next_year: false
when: expires_next_year is not defined
- name: Get repo certificate CN
command: openssl x509 -subject -noout -in {{ repository_path }}/{{ cer_name }}
register: cert_cn
ignore_errors: yes
- name: debug repo certificate CN
debug:
msg: "{{ cert_cn.stdout }}"
- name: Print repo certificate CN and expiration date if it expires this year
debug:
msg: "Certificate CN: {{ cert_cn.stdout.split('=')[-1] | trim }}, Expiration Date: {{ expiration_date }}"
when: cert_expiration.stdout is search('notAfter=') and not expires_next_year
- name: register hash certificate (x509 hash)
shell: openssl x509 -noout -modulus -in {{ repository_path }}/{{ cer_name }} | openssl md5
register: cert_modulus
ignore_errors: yes
- name: register hash key (rsa hash)
shell: openssl rsa -noout -modulus -in {{ repository_path }}/{{ key_name }} | openssl md5
register: key_modulus
ignore_errors: yes
#Verify that the certificate match the key
- name: merge certificate and Prvate key hash
debug:
msg: "x509 hash:{{ cert_modulus.stdout }} ,rsa hash:{{ key_modulus.stdout }} "
- name: Check if the key matches the certificate
set_fact:
key_matches_cert: "{{ cert_modulus.stdout == key_modulus.stdout }}"
when: cert_modulus is succeeded and key_modulus is succeeded
- name: debug key matches the certificate
debug:
msg: "{{ key_matches_cert }}"
- name: Ensure key_matches_cert is defined
set_fact:
key_matches_cert: false
when: key_matches_cert is not defined
- name: Expiration Date check and Secret key and Cert Hash check have to be both True
debug:
msg: "Expiration Date check: {{ expires_next_year }} , secret key and cert check: {{ key_matches_cert }}"
- name: Check if var expires_next_year and key_matches_cert are both True
fail:
msg: "Expiration Date: {{ expiration_date }}, cer hash:{{ cert_modulus.stdout }} ,private key hash:{{ key_modulus.stdout }}"
when: not (expires_next_year and key_matches_cert)
- name: Task that runs only if both variables are True
debug:
msg: "Both expires_next_year and key_matches_cert are True. Proceeding with the task."
when: expires_next_year and key_matches_cert
#ansible run next task just if task name condition is satisfied
- name: Check if both variables are True
fail:
msg: "Both expires_next_year and key_matches_cert must be True. expires_next_year={{ expires_next_year }}, key_matches_cert={{ key_matches_cert }}"
when: not (expires_next_year and key_matches_cert)
- name: Task that runs only if both variables are True
debug:
msg: "Both expires_next_year and key_matches_cert are True. Proceeding with the task."
when: expires_next_year and key_matches_cert
#copy the certificate and the key in the ansible server repo (I create it automatically if it does not exist) and then copy them from the repo to the destination web servers
- name: fetch new certificate from source repo host to ansible repo
fetch:
src: "{{ repository_path }}/{{ cer_name }}"
dest: "{{ repo_temp_ansible }}/"
flat: yes
- name: fetch new key from source repo host to ansible repo
fetch:
src: "{{ repository_path }}/{{ key_name }}"
dest: "{{ repo_temp_ansible }}/"
flat: yes
#Copy certificate from ansible repo to destination hosts #(before make a backup of the old one in destination)
- name: Copy certificate from ansible repo to destination hosts #(before make a backup of the old one in destination)
hosts: webserver
remote_user: ansible
become: yes
gather_facts: yes
tasks:
#delete the old cert and key backup files before running the new backup
- name: Find backup certificate files
find:
paths: "{{ webserver_path }}"
patterns: "{{ cer_name }}.back_*"
register: cert_backup_files
- name: Delete backup certificate files if they exist
file:
path: "{{ item.path }}"
state: absent
loop: "{{ cert_backup_files.files }}"
when: cert_backup_files.matched > 0
- name: Find backup key files
find:
paths: "{{ webserver_path }}"
patterns: "{{ key_name }}.back_*"
register: key_backup_files
- name: Delete backup key files if they exist
file:
path: "{{ item.path }}"
state: absent
loop: "{{ key_backup_files.files }}"
when: key_backup_files.matched > 0
#certificate backup and key on webserver before replace them
- name: backup old certificate in destination web server before copy the newone from source
copy:
src: "{{ webserver_path }}/{{ cer_name }}"
dest: "{{ webserver_path }}/{{ cer_name }}.back_{{ ansible_date_time.date }}"
remote_src: yes
- name: backup old privatekey in destination web server before copy the newone from source
copy:
src: "{{ webserver_path }}/{{ key_name }}"
dest: "{{ webserver_path }}/{{ key_name }}.back_{{ ansible_date_time.date }}"
remote_src: yes
#copy files from repo ansible to destination web servers
#if ansible repo directory doesn't exist ansible create it
- name: Ensure the {{ repo_temp_ansible }} directory exists
file:
path: "{{ repo_temp_ansible }}"
state: directory
mode: '0755'
- name: Copy the certificate from ansible server repo to the destination webserver
copy:
src: "{{ repo_temp_ansible }}/{{ cer_name }}"
dest: "{{ webserver_path }}/"
- name: Copy the Private key from ansible server repo to the destination webserver
copy:
src: "{{ repo_temp_ansible }}/{{ key_name }}"
dest: "{{ webserver_path }}/"
#re-run the certificate and key checks even after copying them on webserver#########################################################################################
- name: Print Variables Set in Hosts Ansible Config File (webserver)
debug:
msg: "'repo path':{{ repository_path }} 'webserver path':{{ webserver_path }} 'cer name':{{ cer_name }} 'key name':{{ key_name }}"
- name: Verify and print HTTPS certificate expiration date
command: openssl x509 -enddate -noout -in {{ webserver_path }}/{{ cer_name }}
register: cert_expiration
ignore_errors: yes
- name: Parse certificate expiration date
set_fact:
expiration_date: "{{ cert_expiration.stdout.split('=')[1] | trim }}"
when: cert_expiration.stdout is search('notAfter=')
- name: Print certificate expiration date
debug:
msg: "{{ expiration_date }}"
when: cert_expiration.stdout is search('notAfter=')
#run the same check as the start script on the repo path,but this time on the webserver path: I check if the certificate expires in at least one year (if the certificate is renewed even for just 6 months the script does not copy it)
- name: Print webserver Variables Set in Hosts Ansible Config File (webserver)
debug:
msg: "'repotemp ansible':{{ repo_temp_ansible }}, 'repo path':{{ repository_path }} 'webserver path':{{ webserver_path }} 'cer name':{{ cer_name }} 'key name':{{ key_name }}"
- name: Verify and print webserver HTTPS certificate expiration date
command: openssl x509 -enddate -noout -in {{ webserver_path }}/{{ cer_name }}
register: cert_expiration
ignore_errors: yes
- name: Parse webserver certificate expiration date
set_fact:
expiration_date: "{{ cert_expiration.stdout.split('=')[1] | trim }}"
when: cert_expiration.stdout is search('notAfter=')
- name: Print webserver certificate expiration date
debug:
msg: "{{ expiration_date }}"
when: cert_expiration.stdout is search('notAfter=')
#check if the certificate expires in at least one year (if the certificate is renewed even for just 6 months the script does not copy it)
- name: Check if webserver certificate expires next year
set_fact:
expires_next_year: "{{ (expiration_date | to_datetime('%b %d %H:%M:%S %Y %Z')).year >= (ansible_date_time.date | to_datetime('%Y-%m-%d')).year + 1 }}"
when: cert_expiration.stdout is search('notAfter=')
- name: debug webserver expires next year
debug:
msg: "{{ expires_next_year }}"
- name: Ensure expires_next_year is defined
set_fact:
expires_next_year: false
when: expires_next_year is not defined
- name: Get webserver certificate CN
command: openssl x509 -subject -noout -in {{ webserver_path }}/{{ cer_name }}
register: cert_cn
ignore_errors: yes
- name: debug new webserver certificate CN
debug:
msg: "{{ cert_cn.stdout }}"
- name: Print webserver certificate CN and expiration date if it expires this year
debug:
msg: "webserver Certificate CN: {{ cert_cn.stdout.split('=')[-1] | trim }}, Expiration Date: {{ expiration_date }}"
when: cert_expiration.stdout is search('notAfter=') and not expires_next_year
- name: Verify that the webserver key matches the certificate (x509 hash)
shell: openssl x509 -noout -modulus -in {{ webserver_path }}/{{ cer_name }} | openssl md5
register: cert_modulus
ignore_errors: yes
- name: Verify that the webserver key matches the certificate (rsa hash)
shell: openssl rsa -noout -modulus -in {{ webserver_path }}/{{ key_name }} | openssl md5
register: key_modulus
ignore_errors: yes
#Verify that the certificate match the key
- name: merge webserver certificate and Prvate key hash
debug:
msg: "x509 hash:{{ cert_modulus.stdout }} ,rsa hash:{{ key_modulus.stdout }} "
- name: Check if the webserver key matches the certificate
set_fact:
key_matches_cert: "{{ cert_modulus.stdout == key_modulus.stdout }}"
when: cert_modulus is succeeded and key_modulus is succeeded
- name: debug webserver key matches the certificate
debug:
msg: "{{ key_matches_cert }}"
- name: Ensure webserver key_matches_cert is defined
set_fact:
key_matches_cert: false
when: key_matches_cert is not defined
- name: webserver Expiration Date check and Secret key and Cert Hash check have to be both True
debug:
msg: "webserver Expiration Date check: {{ expires_next_year }} , secret key and cert check: {{ key_matches_cert }}"
- name: webserver Check if var expires_next_year and key_matches_cert are both True
fail:
msg: "webserver Expiration Date: {{ expiration_date }}, cer hash:{{ cert_modulus.stdout }} ,private key hash:{{ key_modulus.stdout }}"
when: not (expires_next_year and key_matches_cert)
- name: Task that runs only if both variables are True
debug:
msg: "On webserver both expires_next_year and key_matches_cert are True. Proceeding with the task."
when: expires_next_year and key_matches_cert
#ansible run next task just if task name condition is satisfied
- name: Check if both variables are True
fail:
msg: "Both expires_next_year and key_matches_cert must be True. expires_next_year={{ expires_next_year }}, key_matches_cert={{ key_matches_cert }}"
when: not (expires_next_year and key_matches_cert)
- name: Task that runs only if both variables are True
debug:
msg: "Both expires_next_year and key_matches_cert are True. Proceeding with the task."
when: expires_next_year and key_matches_cert
##############################################################################################################
###still to test on nginx server
#verifica file nginx.conf and stop ansible if output command is not successful
- name: Test Nginx configuration
command: nginx -t
register: nginx_test
failed_when: "'test is successful' not in nginx_test.stderr"
- name: Print Nginx test output
debug:
var: nginx_test.stdout
- name: Reload Nginx
command: nginx -s reload
when: "'test is successful' in nginx_test.stderr"
# - name: Verify new expiration date from HTTPS URI
# uri:
# url: "{{ host_uri_to_check }}"
# return_content: yes
# register: uri_result
# when: "'successful' in nginx_test.stderr"
#
# - name: Extract expiration date from URI result
# set_fact:
# uri_expiration_date: "{{ uri_result.content | regex_search('Not After : (.*)') }}"
# when: "'successful' in nginx_test.stderr"
I can’t test it this evening but i guess it stops when one server match a task with fail module isn’t it?
I mean i’m having a doubt if ‘fail’ ends your playbook at the first time one server match the condition
You are right: the script work fine for the 2 checks in repository side ( It checks that the Key and cert hash match and that the expiration date Is next yrar) but of course it checks Just 1 cert and 1 Key.
The issue Is that One you understood … In the 20 days expiring cert checks from webservers side. The check works fine but the playbook fails at First cert that don’t Need to be replaced (and so stop tò check the next webservers certs).
In my environment Is Ubuntu 22.04 os
In conclusione in this Moment The script works fine but Just with 1 webservers.
Don’t worry … i m not rush
Tnx and ciao
Germano
Perhaps there’s more than one thing that could lead to unintended errors or possible points of failure.
-
Instead of using the fail
module in a way that aborts the play for all hosts, you can wrap your certificate-check tasks in a block
with a rescue
clause (as you already did in one section). This way, if the check fails on one host, you simply skip the subsequent copy tasks for that host rather than halting the whole play.
-
By default, Ansible runs with a linear strategy
where a failure on one host might affect the entire play. You can set:
strategy: free
-
Double-check your conditions. The playbook calculates a days_difference
as days_until_expiration - 20
and then fails when this difference is greater than or equal to zero. This logic is counterintuitive. Ideally, you should fail (or skip the server) when the certificate has less than or equal to 20 days remaining.
-
The playbook converts the certificate’s expiration string to a datetime
using one format '%b %d %H:%M:%S %Y %Z'
and then attempts a further conversion with a different format '%Y-%m-%d %H:%M:%S'
. This can lead to mismatches or errors if the formats do not line up.
-
In the playbook, only the values of ansible_date_time
are used, so why use gather_facts
when you can use setup
and considerably reduce the execution time and load?
Try reading this; it might be the right way to solve the problem.
Pay attention. This code has not been tested.
---
- name: Clean temporary repository directory on control node
hosts: localhost
gather_facts: no
tasks:
- name: Ensure repo_temp_ansible directory is removed
file:
path: "{{ repo_temp_ansible }}"
state: absent
- name: Validate certificate expiration on webservers
hosts: webserver
strategy: free
gather_facts: no
remote_user: ansible
become: yes
tasks:
- name: Gather minimal facts (date/time)
setup:
gather_subset:
- ansible_date_time
- name: Check certificate expiration and decide if update is needed
block:
- name: Retrieve certificate expiration date
command: openssl x509 -enddate -noout -in "{{ webserver_path }}/{{ cer_name }}"
register: cert_expiration
changed_when: false
- name: Extract and set certificate expiration date
set_fact:
expiration_date: "{{ cert_expiration.stdout.split('=')[1] | trim }}"
- name: Convert expiration and current date to datetime objects
set_fact:
expiration_datetime: "{{ expiration_date | to_datetime('%b %d %H:%M:%S %Y %Z') }}"
current_datetime: "{{ ansible_date_time.iso8601 | to_datetime('%Y-%m-%dT%H:%M:%SZ') }}"
- name: Calculate days until expiration
set_fact:
days_until_expiration: "{{ ((expiration_datetime - current_datetime).total_seconds() // (60 * 60 * 24)) | int }}"
- name: Debug: Days until expiration
debug:
msg: "Days until expiration: {{ days_until_expiration }}"
- name: Determine if certificate should be updated (<=20 days remaining)
set_fact:
update_cert: "{{ days_until_expiration <= 20 }}"
- name: Fail block if certificate does not need updating
fail:
msg: "Certificate valid for {{ days_until_expiration }} days; skipping update."
when: not update_cert
rescue:
- name: Mark host as not eligible for update due to error
set_fact:
update_cert: false
- name: Log error in certificate check
debug:
msg: "Skipping host due to error in certificate expiration check."
- name: Validate repository certificate and fetch files
hosts: repository_crt
strategy: free
gather_facts: no
remote_user: ansible
become: yes
tasks:
- name: Debug repository variables
debug:
msg: "repo_temp_ansible={{ repo_temp_ansible }}, repository_path={{ repository_path }}, cer_name={{ cer_name }}, key_name={{ key_name }}"
- name: Retrieve repository certificate expiration date
command: openssl x509 -enddate -noout -in "{{ repository_path }}/{{ cer_name }}"
register: repo_cert_expiration
changed_when: false
- name: Set repository certificate expiration date fact
set_fact:
repo_expiration_date: "{{ repo_cert_expiration.stdout.split('=')[1] | trim }}"
when: repo_cert_expiration.stdout is search('notAfter=')
- name: Validate repository certificate expiration (must be at least next year)
set_fact:
repo_cert_valid: >-
{{
(repo_expiration_date | to_datetime('%b %d %H:%M:%S %Y %Z')).year >=
((ansible_date_time.date | to_datetime('%Y-%m-%d')).year + 1)
}}
when: repo_cert_expiration.stdout is search('notAfter=')
- name: Fail if repository certificate expiration is insufficient
fail:
msg: "Repository certificate expires on {{ repo_expiration_date }} which is less than next year."
when: not repo_cert_valid | default(false)
- name: Calculate modulus hash for repository certificate
shell: openssl x509 -noout -modulus -in "{{ repository_path }}/{{ cer_name }}" | openssl md5
register: repo_cert_modulus
changed_when: false
- name: Calculate modulus hash for repository key
shell: openssl rsa -noout -modulus -in "{{ repository_path }}/{{ key_name }}" | openssl md5
register: repo_key_modulus
changed_when: false
- name: Verify repository certificate and key match
set_fact:
repo_cert_key_match: "{{ repo_cert_modulus.stdout == repo_key_modulus.stdout }}"
- name: Fail if repository certificate and key do not match
fail:
msg: "Mismatch: Repository certificate hash ({{ repo_cert_modulus.stdout }}) vs key hash ({{ repo_key_modulus.stdout }})"
when: not repo_cert_key_match
- name: Ensure local temporary repository directory exists
file:
path: "{{ repo_temp_ansible }}"
state: directory
mode: '0755'
- name: Fetch new certificate from repository to local repo
fetch:
src: "{{ repository_path }}/{{ cer_name }}"
dest: "{{ repo_temp_ansible }}/"
flat: yes
- name: Fetch new key from repository to local repo
fetch:
src: "{{ repository_path }}/{{ key_name }}"
dest: "{{ repo_temp_ansible }}/"
flat: yes
- name: Deploy new certificate and key to webservers
hosts: webserver
strategy: free
gather_facts: no
remote_user: ansible
become: yes
tasks:
- name: Gather minimal facts (date/time) for deployment tasks
setup:
gather_subset:
- ansible_date_time
- name: Skip host if certificate update is not required
debug:
msg: "Skipping host; certificate update not required."
when: not update_cert
tags: skip_update
- block:
- name: Delete old backup certificate files
find:
paths: "{{ webserver_path }}"
patterns: "{{ cer_name }}.back_*"
register: cert_backup_files
- name: Remove old certificate backups
file:
path: "{{ item.path }}"
state: absent
loop: "{{ cert_backup_files.files }}"
when: cert_backup_files.matched > 0
- name: Delete old backup key files
find:
paths: "{{ webserver_path }}"
patterns: "{{ key_name }}.back_*"
register: key_backup_files
- name: Remove old key backups
file:
path: "{{ item.path }}"
state: absent
loop: "{{ key_backup_files.files }}"
when: key_backup_files.matched > 0
- name: Backup current certificate file
copy:
src: "{{ webserver_path }}/{{ cer_name }}"
dest: "{{ webserver_path }}/{{ cer_name }}.back_{{ ansible_date_time.date }}"
remote_src: yes
- name: Backup current key file
copy:
src: "{{ webserver_path }}/{{ key_name }}"
dest: "{{ webserver_path }}/{{ key_name }}.back_{{ ansible_date_time.date }}"
remote_src: yes
- name: Copy new certificate from local repo to destination
copy:
src: "{{ repo_temp_ansible }}/{{ cer_name }}"
dest: "{{ webserver_path }}/"
- name: Copy new key from local repo to destination
copy:
src: "{{ repo_temp_ansible }}/{{ key_name }}"
dest: "{{ webserver_path }}/"
- name: Validate new certificate expiration on webserver
command: openssl x509 -enddate -noout -in "{{ webserver_path }}/{{ cer_name }}"
register: new_cert_expiration
changed_when: false
- name: Extract new certificate expiration date
set_fact:
new_expiration_date: "{{ new_cert_expiration.stdout.split('=')[1] | trim }}"
when: new_cert_expiration.stdout is search('notAfter=')
- name: Verify new certificate expiration (must be at least next year)
set_fact:
new_cert_valid: >-
{{
(new_expiration_date | to_datetime('%b %d %H:%M:%S %Y %Z')).year >=
((ansible_date_time.date | to_datetime('%Y-%m-%d')).year + 1)
}}
when: new_cert_expiration.stdout is search('notAfter=')
- name: Compute modulus hash for new certificate on webserver
shell: openssl x509 -noout -modulus -in "{{ webserver_path }}/{{ cer_name }}" | openssl md5
register: new_cert_modulus
changed_when: false
- name: Compute modulus hash for new key on webserver
shell: openssl rsa -noout -modulus -in "{{ webserver_path }}/{{ key_name }}" | openssl md5
register: new_key_modulus
changed_when: false
- name: Verify new certificate and key match
set_fact:
new_cert_key_match: "{{ new_cert_modulus.stdout == new_key_modulus.stdout }}"
- name: Fail if new certificate or key validation fails
fail:
msg: "New certificate invalid. Expiration: {{ new_expiration_date }}, Cert hash: {{ new_cert_modulus.stdout }}, Key hash: {{ new_key_modulus.stdout }}"
when: not (new_cert_valid | default(false) and new_cert_key_match)
when: update_cert
rescue:
- name: Log deployment failure for host
debug:
msg: "Certificate update aborted on this host due to failed validations."
- name: Test and reload Nginx configuration on webservers
hosts: webserver
strategy: free
gather_facts: no
remote_user: ansible
become: yes
tasks:
- name: Skip host if certificate update was not applied
debug:
msg: "Skipping Nginx reload; certificate update not applied."
when: not update_cert
tags: skip_reload
- name: Test Nginx configuration
command: nginx -t
register: nginx_test
changed_when: false
- name: Debug Nginx test output
debug:
msg: "{{ nginx_test.stdout }}"
- name: Reload Nginx if configuration test is successful
command: nginx -s reload
when: "'test is successful' in nginx_test.stderr"
3 Likes
@NomakCooper
The script now works perfectly.
I just changed a little and now it works perfectly (terminating the workflow only for webservers where the conditions are met).
ciao and thanks 1000
Germano
here the right code:
- name: Clean temporary repository directory on control node
hosts: localhost
gather_facts: no
tasks:
- name: Ensure repo_temp_ansible directory is removed
file:
path: "{{ repo_temp_ansible }}"
state: absent
- name: Ensure repo_temp_ansible directory exists
file:
path: "{{ repo_temp_ansible }}"
state: directory
mode: '0755'
- name: Validate certificate expiration on webservers
hosts: webserver
strategy: free
gather_facts: no
remote_user: ansible
become: yes
tasks:
- name: Gather minimal facts (date/time)
setup:
gather_subset:
- date_time
- name: Check certificate expiration and decide if update is needed
block:
- name: Retrieve certificate expiration date
command: openssl x509 -enddate -noout -in "{{ webserver_path }}/{{ cer_name }}"
register: cert_expiration
changed_when: false
- name: Extract and set certificate expiration date
set_fact:
expiration_date: "{{ cert_expiration.stdout.split('=')[1] | trim }}"
- name: Convert expiration and current date to datetime objects
set_fact:
expiration_datetime: "{{ expiration_date | to_datetime('%b %d %H:%M:%S %Y %Z') }}"
current_datetime: "{{ ansible_date_time.date | to_datetime('%Y-%m-%d') }}"
- name: Calculate days until expiration
set_fact:
days_until_expiration: "{{ ((expiration_datetime | to_datetime('%Y-%m-%d %H:%M:%S')) - (current_datetime | to_datetime('%Y-%m-%d %H:%M:%S'))).total_seconds() // (60 * 60 * 24) | int }}"
- name: Debug Days until expiration
debug:
msg: "Days until expiration: {{ days_until_expiration }}"
- name: Determine if certificate should be updated (<=20 days remaining)
set_fact:
update_cert: "{{ (days_until_expiration | int ) <= 20 }}"
- name: Fail block if certificate does not need updating
fail:
msg: "Certificate valid for {{ days_until_expiration }} days; skipping update."
when: not update_cert
rescue:
- name: Mark host as not eligible for update due to error
set_fact:
update_cert: false
- name: Log error in certificate check
debug:
msg: "Skipping host due to error in certificate expiration check."
- name: Validate repository certificate and fetch files
hosts: repository_crt
strategy: free
gather_facts: no
remote_user: ansible
become: yes
tasks:
- name: Gather minimal facts (date/time)
setup:
gather_subset:
- date_time
- name: Debug repository variables
debug:
msg: "repo_temp_ansible={{ repo_temp_ansible }}, repository_path={{ repository_path }}, cer_name={{ cer_name }}, key_name={{ key_name }}"
- name: Retrieve repository certificate expiration date
command: openssl x509 -enddate -noout -in "{{ repository_path }}/{{ cer_name }}"
register: repo_cert_expiration
changed_when: false
- name: Set repository certificate expiration date fact
set_fact:
repo_expiration_date: "{{ repo_cert_expiration.stdout.split('=')[1] | trim }}"
when: repo_cert_expiration.stdout is search('notAfter=')
- name: Validate repository certificate expiration (must be at least next year)
set_fact:
repo_cert_valid: >-
{{
(repo_expiration_date | to_datetime('%b %d %H:%M:%S %Y %Z')).year >=
((ansible_date_time.date | to_datetime('%Y-%m-%d')).year + 1)
}}
when: repo_cert_expiration.stdout is search('notAfter=')
- name: Fail if repository certificate expiration is insufficient
fail:
msg: "Repository certificate expires on {{ repo_expiration_date }} which is less than next year."
when: not repo_cert_valid | default(false)
- name: Calculate modulus hash for repository certificate
shell: openssl x509 -noout -modulus -in "{{ repository_path }}/{{ cer_name }}" | openssl md5
register: repo_cert_modulus
changed_when: false
- name: Calculate modulus hash for repository key
shell: openssl rsa -noout -modulus -in "{{ repository_path }}/{{ key_name }}" | openssl md5
register: repo_key_modulus
changed_when: false
- name: Verify repository certificate and key match
set_fact:
repo_cert_key_match: "{{ repo_cert_modulus.stdout == repo_key_modulus.stdout }}"
- name: Fail if repository certificate and key do not match
fail:
msg: "Mismatch: Repository certificate hash ({{ repo_cert_modulus.stdout }}) vs key hash ({{ repo_key_modulus.stdout }})"
when: not repo_cert_key_match
- name: Ensure local temporary repository directory exists
file:
path: "{{ repo_temp_ansible }}"
state: directory
mode: '0755'
- name: Fetch new certificate from repository to local repo
fetch:
src: "{{ repository_path }}/{{ cer_name }}"
dest: "{{ repo_temp_ansible }}/"
flat: yes
- name: Fetch new key from repository to local repo
fetch:
src: "{{ repository_path }}/{{ key_name }}"
dest: "{{ repo_temp_ansible }}/"
flat: yes
- name: Deploy new certificate and key to webservers
hosts: webserver
strategy: free
gather_facts: no
remote_user: ansible
become: yes
tasks:
- name: Gather minimal facts (date/time) for deployment tasks
setup:
gather_subset:
- date_time
- name: Skip host if certificate update is not required
debug:
msg: "Skipping host; certificate update not required."
when: not update_cert
tags: skip_update
- block:
- name: Delete old backup certificate files
find:
paths: "{{ webserver_path }}"
patterns: "{{ cer_name }}.back_*"
register: cert_backup_files
- name: Remove old certificate backups
file:
path: "{{ item.path }}"
state: absent
loop: "{{ cert_backup_files.files }}"
when: cert_backup_files.matched > 0
- name: Delete old backup key files
find:
paths: "{{ webserver_path }}"
patterns: "{{ key_name }}.back_*"
register: key_backup_files
- name: Remove old key backups
file:
path: "{{ item.path }}"
state: absent
loop: "{{ key_backup_files.files }}"
when: key_backup_files.matched > 0
- name: Backup current certificate file
copy:
src: "{{ webserver_path }}/{{ cer_name }}"
dest: "{{ webserver_path }}/{{ cer_name }}.back_{{ ansible_date_time.date }}"
remote_src: yes
- name: Backup current key file
copy:
src: "{{ webserver_path }}/{{ key_name }}"
dest: "{{ webserver_path }}/{{ key_name }}.back_{{ ansible_date_time.date }}"
remote_src: yes
- name: Copy new certificate from local repo to destination
copy:
src: "{{ repo_temp_ansible }}/{{ cer_name }}"
dest: "{{ webserver_path }}/"
- name: Copy new key from local repo to destination
copy:
src: "{{ repo_temp_ansible }}/{{ key_name }}"
dest: "{{ webserver_path }}/"
- name: Validate new certificate expiration on webserver
command: openssl x509 -enddate -noout -in "{{ webserver_path }}/{{ cer_name }}"
register: new_cert_expiration
changed_when: false
- name: Extract new certificate expiration date
set_fact:
new_expiration_date: "{{ new_cert_expiration.stdout.split('=')[1] | trim }}"
when: new_cert_expiration.stdout is search('notAfter=')
- name: Verify new certificate expiration (must be at least next year)
set_fact:
new_cert_valid: >-
{{
(new_expiration_date | to_datetime('%b %d %H:%M:%S %Y %Z')).year >=
((ansible_date_time.date | to_datetime('%Y-%m-%d')).year + 1)
}}
when: new_cert_expiration.stdout is search('notAfter=')
- name: Compute modulus hash for new certificate on webserver
shell: openssl x509 -noout -modulus -in "{{ webserver_path }}/{{ cer_name }}" | openssl md5
register: new_cert_modulus
changed_when: false
- name: Compute modulus hash for new key on webserver
shell: openssl rsa -noout -modulus -in "{{ webserver_path }}/{{ key_name }}" | openssl md5
register: new_key_modulus
changed_when: false
- name: Verify new certificate and key match
set_fact:
new_cert_key_match: "{{ new_cert_modulus.stdout == new_key_modulus.stdout }}"
- name: Fail if new certificate or key validation fails
fail:
msg: "New certificate invalid. Expiration: {{ new_expiration_date }}, Cert hash: {{ new_cert_modulus.stdout }}, Key hash: {{ new_key_modulus.stdout }}"
when: not (new_cert_valid | default(false) and new_cert_key_match)
when: update_cert
rescue:
- name: Log deployment failure for host
debug:
msg: "Certificate update aborted on this host due to failed validations."
- name: Test and reload Nginx configuration on webservers
hosts: webserver
strategy: free
gather_facts: no
remote_user: ansible
become: yes
tasks:
- name: Skip host if certificate update was not applied
debug:
msg: "Skipping Nginx reload; certificate update not applied."
when: not update_cert
tags: skip_reload
- name: Test Nginx configuration
command: nginx -t
register: nginx_test
changed_when: false
- name: Debug Nginx test output
debug:
msg: "{{ nginx_test.stdout }}"
- name: Reload Nginx if configuration test is successful
command: nginx -s reload
when: "'test is successful' in nginx_test.stderr"```
1 Like