Posted by
ads' corner
on
Sunday, 2019-10-06 Posted in [Ansible][Lets-Encrypt][Linux][Python][Software]
In one of my Ansible Playbooks I’m updating Let’s Encrypt certificates. Because the Playbook is rather long, I want to make sure that Ansible is not spending cycles on a certificate which is not about to expire. Hence I wrote a small filter plugin, which receives the certificate path and the number of days the certificate must be still valid.
This plugin is used to filter out any “good” certificate which does not need to be updated.
#!/usr/bin/env pythonfromansible.errorsimport AnsibleError
importosimportsysfromansible.module_utils._textimport to_native
fromansible.module_utils._textimport to_text
importsubprocessclassFilterModule(object):
''' Query filter '''deffilters(self):
return {
'check_cert_age': self.check_cert_age
}
defcheck_cert_age(self, cert_path, remaining_days):
'''Query a certificate (1st parameter) and check if the remaining valid
time is at least as many days as specified in the 2nd parameter
Returns 0 if the cert is valid for the specified number of days
Returns 0 if the cert is not valid for that time
'''# first check if the cert existsif (os.path.exists(cert_path) isFalse):
raise AnsibleError('Not found: %s'% to_native(cert_path))
if (os.access(cert_path, os.R_OK) isFalse):
raise AnsibleError('No access: %s'% to_native(cert_path))
# there is a slight chance that the file goes away between the time# existence and access is checked, but that's ok# raising the proper error in the majority of cases is more important
remaining_time =int(remaining_days) *86400
d0 =open(os.devnull, 'w')
result = subprocess.call(['/usr/bin/openssl', 'x509', '-in', cert_path, '-noout', '-checkend', str(remaining_time)], stdout = d0, stderr = d0)
if (result !=0):
# make sure we return '1' for an expiring certificate, or for every error
result =1return(to_text(str(result)))
The function name is check_cert_age(), and the resulting file (check_cert_age.py) must be placed in the filter_plugins folder in the Playbook directory. If the directory does not exist, create it. Ansible will load all plugins from this directory.
How can this plugin be used?
In my case, there is a list of websites defined in a separate file websites.yml:
I also define a variable which holds the number days the certificate must still be valid: $letsencrypt_remaining_days. It’s set to 45 in my case - half the time Let’s Encrypt uses as certificate expiration. If you have a look at the actual code, you see that openssl uses number of seconds, not number of days. But the plugin takes care of that.
Plus I need a number of additional temporary variables: