[mod] autopep8

This commit is contained in:
Laurent Peuch 2016-10-30 04:24:54 +01:00
parent d47f5919d6
commit bec8f63479

View file

@ -33,32 +33,34 @@ import grp
import json import json
import smtplib import smtplib
from OpenSSL import crypto from OpenSSL import crypto
from datetime import datetime from datetime import datetime
from tabulate import tabulate from tabulate import tabulate
from acme_tiny import get_crt as sign_certificate from acme_tiny import get_crt as sign_certificate
from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger
import yunohost.domain import yunohost.domain
from yunohost.service import _run_service_command
from yunohost.app import app_ssowatconf from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger
from yunohost.app import app_ssowatconf
from yunohost.service import _run_service_command
logger = getActionLogger('yunohost.certmanager') logger = getActionLogger('yunohost.certmanager')
# Misc stuff we need # Misc stuff we need
cert_folder = "/etc/yunohost/certs/" cert_folder = "/etc/yunohost/certs/"
tmp_folder = "/tmp/acme-challenge-private/" tmp_folder = "/tmp/acme-challenge-private/"
webroot_folder = "/tmp/acme-challenge-public/" webroot_folder = "/tmp/acme-challenge-public/"
selfCA_file = "/etc/ssl/certs/ca-yunohost_crt.pem" selfCA_file = "/etc/ssl/certs/ca-yunohost_crt.pem"
account_key_file = "/etc/yunohost/letsencrypt_account.pem" account_key_file = "/etc/yunohost/letsencrypt_account.pem"
key_size = 2048 key_size = 2048
validity_limit = 15 # days validity_limit = 15 # days
# For tests # For tests
#certification_authority = "https://acme-staging.api.letsencrypt.org" #certification_authority = "https://acme-staging.api.letsencrypt.org"
@ -73,7 +75,8 @@ intermediate_certificate_url = "https://letsencrypt.org/certs/lets-encrypt-x3-cr
# Status # Status
def certificate_status(auth, domainList, full = False):
def certificate_status(auth, domainList, full=False):
""" """
Print the status of certificate for given domains (all by default) Print the status of certificate for given domains (all by default)
@ -83,36 +86,39 @@ def certificate_status(auth, domainList, full = False):
""" """
# If no domains given, consider all yunohost domains # If no domains given, consider all yunohost domains
if (domainList == []) : domainList = yunohost.domain.domain_list(auth)['domains'] if (domainList == []):
domainList = yunohost.domain.domain_list(auth)['domains']
# Else, validate that yunohost knows the domains given # Else, validate that yunohost knows the domains given
else : else:
for domain in domainList : for domain in domainList:
# Is it in Yunohost dmomain list ? # Is it in Yunohost dmomain list ?
if domain not in yunohost.domain.domain_list(auth)['domains']: if domain not in yunohost.domain.domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain))
# Get status for each domain, and prepare display using tabulate # Get status for each domain, and prepare display using tabulate
if not full : if not full:
headers = [ "Domain", "Certificate status", "Authority type", "Days remaining"] headers = ["Domain", "Certificate status", "Authority type", "Days remaining"]
else : else:
headers = [ "Domain", "Certificate subject", "Certificate status", "Authority type", "Authority name", "Days remaining"] headers = ["Domain", "Certificate subject", "Certificate status", "Authority type", "Authority name", "Days remaining"]
lines = [] lines = []
for domain in domainList : for domain in domainList:
status = _get_status(domain) status = _get_status(domain)
line = [] line = []
line.append(domain) line.append(domain)
if (full) : line.append(status["subject"]) if (full):
line.append(status["subject"])
line.append(_summary_code_to_string(status["summaryCode"])) line.append(_summary_code_to_string(status["summaryCode"]))
line.append(status["CAtype"]) line.append(status["CAtype"])
if (full) : line.append(status["CAname"]) if (full):
line.append(status["CAname"])
line.append(status["validity"]) line.append(status["validity"])
lines.append(line) lines.append(line)
print(tabulate(lines, headers=headers, tablefmt="simple", stralign="center")) print(tabulate(lines, headers=headers, tablefmt="simple", stralign="center"))
def certificate_install(auth, domainList, force=False, no_checks=False, self_signed=False) : def certificate_install(auth, domainList, force=False, no_checks=False, self_signed=False):
""" """
Install a Let's Encrypt certificate for given domains (all by default) Install a Let's Encrypt certificate for given domains (all by default)
@ -123,25 +129,23 @@ def certificate_install(auth, domainList, force=False, no_checks=False, self_sig
before attempting the install before attempting the install
self-signed -- Instal self-signed certificates instead of Let's Encrypt self-signed -- Instal self-signed certificates instead of Let's Encrypt
""" """
if (self_signed) : if (self_signed):
certificate_install_selfsigned(domainList, force) certificate_install_selfsigned(domainList, force)
else : else:
certificate_install_letsencrypt(auth, domainList, force, no_checks) certificate_install_letsencrypt(auth, domainList, force, no_checks)
# Install self-signed # Install self-signed
def certificate_install_selfsigned(domainList, force=False) : def certificate_install_selfsigned(domainList, force=False):
for domain in domainList:
for domain in domainList :
# Check we ain't trying to overwrite a good cert ! # Check we ain't trying to overwrite a good cert !
status = _get_status(domain) status = _get_status(domain)
if (status != {}) and (status["summaryCode"] > 0) and (not force) : if (status != {}) and (status["summaryCode"] > 0) and (not force):
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_replace_valid_cert', domain=domain)) raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_replace_valid_cert', domain=domain))
cert_folder_domain = cert_folder + "/" + domain
cert_folder_domain = cert_folder+"/"+domain
# Create cert folder if it does not exists yet # Create cert folder if it does not exists yet
try: try:
@ -157,69 +161,67 @@ def certificate_install_selfsigned(domainList, force=False) :
# FIXME : should refactor this to avoid so many os.system() calls... # FIXME : should refactor this to avoid so many os.system() calls...
# We should be able to do all this using OpenSSL.crypto and os/shutil # We should be able to do all this using OpenSSL.crypto and os/shutil
command_list = [ command_list = [
'cp %s/openssl.cnf %s' % (ssl_dir, cert_folder_domain), 'cp %s/openssl.cnf %s' % (ssl_dir, cert_folder_domain),
'sed -i "s/yunohost.org/%s/g" %s/openssl.cnf' % (domain, cert_folder_domain), 'sed -i "s/yunohost.org/%s/g" %s/openssl.cnf' % (domain, cert_folder_domain),
'openssl req -new -config %s/openssl.cnf -days 3650 -out %s/certs/yunohost_csr.pem -keyout %s/certs/yunohost_key.pem -nodes -batch' 'openssl req -new -config %s/openssl.cnf -days 3650 -out %s/certs/yunohost_csr.pem -keyout %s/certs/yunohost_key.pem -nodes -batch'
% (cert_folder_domain, ssl_dir, ssl_dir), % (cert_folder_domain, ssl_dir, ssl_dir),
'openssl ca -config %s/openssl.cnf -days 3650 -in %s/certs/yunohost_csr.pem -out %s/certs/yunohost_crt.pem -batch' 'openssl ca -config %s/openssl.cnf -days 3650 -in %s/certs/yunohost_csr.pem -out %s/certs/yunohost_crt.pem -batch'
% (cert_folder_domain, ssl_dir, ssl_dir), % (cert_folder_domain, ssl_dir, ssl_dir),
'ln -s /etc/ssl/certs/ca-yunohost_crt.pem %s/ca.pem' % cert_folder_domain, 'ln -s /etc/ssl/certs/ca-yunohost_crt.pem %s/ca.pem' % cert_folder_domain,
'cp %s/certs/yunohost_key.pem %s/key.pem' % (ssl_dir, cert_folder_domain), 'cp %s/certs/yunohost_key.pem %s/key.pem' % (ssl_dir, cert_folder_domain),
'cp %s/newcerts/%s.pem %s/crt.pem' % (ssl_dir, serial, cert_folder_domain), 'cp %s/newcerts/%s.pem %s/crt.pem' % (ssl_dir, serial, cert_folder_domain),
'cat %s/ca.pem >> %s/crt.pem' % (cert_folder_domain, cert_folder_domain) 'cat %s/ca.pem >> %s/crt.pem' % (cert_folder_domain, cert_folder_domain)
] ]
for command in command_list: for command in command_list:
if os.system(command) != 0: if os.system(command) != 0:
raise MoulinetteError(errno.EIO, m18n.n('certmanager_domain_cert_gen_failed')) raise MoulinetteError(errno.EIO, m18n.n('certmanager_domain_cert_gen_failed'))
_set_permissions(cert_folder_domain, "root", "root", 0755); _set_permissions(cert_folder_domain, "root", "root", 0755)
_set_permissions(cert_folder_domain+"/key.pem", "root", "metronome", 0640); _set_permissions(cert_folder_domain + "/key.pem", "root", "metronome", 0640)
_set_permissions(cert_folder_domain+"/crt.pem", "root", "metronome", 0640); _set_permissions(cert_folder_domain + "/crt.pem", "root", "metronome", 0640)
_set_permissions(cert_folder_domain+"/openssl.cnf", "root", "root", 0600); _set_permissions(cert_folder_domain + "/openssl.cnf", "root", "root", 0600)
# Install ACME / Let's Encrypt certificate # Install ACME / Let's Encrypt certificate
def certificate_install_letsencrypt(auth, domainList, force=False, no_checks=False): def certificate_install_letsencrypt(auth, domainList, force=False, no_checks=False):
if not os.path.exists(account_key_file):
if not os.path.exists(account_key_file) :
_generate_account_key() _generate_account_key()
# If no domains given, consider all yunohost domains with self-signed # If no domains given, consider all yunohost domains with self-signed
# certificates # certificates
if (domainList == []) : if (domainList == []):
for domain in yunohost.domain.domain_list(auth)['domains'] : for domain in yunohost.domain.domain_list(auth)['domains']:
# Is it self-signed ? # Is it self-signed ?
status = _get_status(domain) status = _get_status(domain)
if (status["CAtype"] != "Self-signed") : continue if (status["CAtype"] != "Self-signed"):
continue
domainList.append(domain) domainList.append(domain)
# Else, validate that yunohost knows the domains given # Else, validate that yunohost knows the domains given
else : else:
for domain in domainList : for domain in domainList:
# Is it in Yunohost dmomain list ? # Is it in Yunohost dmomain list ?
if domain not in yunohost.domain.domain_list(auth)['domains']: if domain not in yunohost.domain.domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain))
# Is it self-signed ? # Is it self-signed ?
status = _get_status(domain) status = _get_status(domain)
if (not force) and (status["CAtype"] != "Self-signed") : if (not force) and (status["CAtype"] != "Self-signed"):
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_cert_not_selfsigned', domain=domain)) raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_cert_not_selfsigned', domain=domain))
# Actual install steps # Actual install steps
for domain in domainList : for domain in domainList:
logger.info("Now attempting install of certificate for domain "+domain+" !") logger.info("Now attempting install of certificate for domain " + domain + " !")
try : try:
if not no_checks : _check_domain_is_correctly_configured(domain) if not no_checks:
_check_domain_is_correctly_configured(domain)
_backup_current_cert(domain) _backup_current_cert(domain)
_configure_for_acme_challenge(auth, domain) _configure_for_acme_challenge(auth, domain)
_fetch_and_enable_new_certificate(domain) _fetch_and_enable_new_certificate(domain)
@ -227,9 +229,9 @@ def certificate_install_letsencrypt(auth, domainList, force=False, no_checks=Fal
logger.success(m18n.n("certmanager_cert_install_success", domain=domain)) logger.success(m18n.n("certmanager_cert_install_success", domain=domain))
except Exception as e : except Exception as e:
logger.error("Certificate installation for "+domain+" failed !") logger.error("Certificate installation for " + domain + " failed !")
logger.error(str(e)) logger.error(str(e))
@ -250,23 +252,24 @@ def certificate_renew(auth, domainList, force=False, no_checks=False, email=Fals
# If no domains given, consider all yunohost domains with Let's Encrypt # If no domains given, consider all yunohost domains with Let's Encrypt
# certificates # certificates
if (domainList == []) : if (domainList == []):
for domain in yunohost.domain.domain_list(auth)['domains'] : for domain in yunohost.domain.domain_list(auth)['domains']:
# Does it has a Let's Encrypt cert ? # Does it has a Let's Encrypt cert ?
status = _get_status(domain) status = _get_status(domain)
if (status["CAtype"] != "Let's Encrypt") : continue if (status["CAtype"] != "Let's Encrypt"):
continue
# Does it expires soon ? # Does it expires soon ?
if (force) or (status["validity"] <= validity_limit) : if (force) or (status["validity"] <= validity_limit):
domainList.append(domain) domainList.append(domain)
if (len(domainList) == 0) : if (len(domainList) == 0):
logger.info("No certificate needs to be renewed.") logger.info("No certificate needs to be renewed.")
# Else, validate the domain list given # Else, validate the domain list given
else : else:
for domain in domainList : for domain in domainList:
# Is it in Yunohost dmomain list ? # Is it in Yunohost dmomain list ?
if domain not in yunohost.domain.domain_list(auth)['domains']: if domain not in yunohost.domain.domain_list(auth)['domains']:
@ -275,61 +278,56 @@ def certificate_renew(auth, domainList, force=False, no_checks=False, email=Fals
status = _get_status(domain) status = _get_status(domain)
# Does it expires soon ? # Does it expires soon ?
if not ((force) or (status["validity"] <= validity_limit)) : if not ((force) or (status["validity"] <= validity_limit)):
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_valid_cert', domain=domain)) raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_valid_cert', domain=domain))
# Does it has a Let's Encrypt cert ? # Does it has a Let's Encrypt cert ?
if (status["CAtype"] != "Let's Encrypt") : if (status["CAtype"] != "Let's Encrypt"):
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_nonLE_cert', domain=domain)) raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_attempt_to_renew_nonLE_cert', domain=domain))
# Actual renew steps # Actual renew steps
for domain in domainList : for domain in domainList:
logger.info("Now attempting renewing of certificate for domain "+domain+" !") logger.info("Now attempting renewing of certificate for domain " + domain + " !")
try : try:
if not no_checks : _check_domain_is_correctly_configured(domain) if not no_checks:
_check_domain_is_correctly_configured(domain)
_backup_current_cert(domain) _backup_current_cert(domain)
_fetch_and_enable_new_certificate(domain) _fetch_and_enable_new_certificate(domain)
logger.success(m18n.n("certmanager_cert_renew_success", domain=domain)) logger.success(m18n.n("certmanager_cert_renew_success", domain=domain))
except Exception as e : except Exception as e:
logger.error("Certificate renewing for "+domain+" failed !") logger.error("Certificate renewing for " + domain + " failed !")
logger.error(str(e)) logger.error(str(e))
if (email) : if (email):
logger.error("Sending email with details to root ...") logger.error("Sending email with details to root ...")
_email_renewing_failed(domain, e) _email_renewing_failed(domain, e)
############################################################################### ###############################################################################
# Back-end stuff # # Back-end stuff #
############################################################################### ###############################################################################
def _install_cron() : def _install_cron():
cron_job_file = "/etc/cron.weekly/certificateRenewer" cron_job_file = "/etc/cron.weekly/certificateRenewer"
with open(cron_job_file, "w") as f : with open(cron_job_file, "w") as f:
f.write("#!/bin/bash\n") f.write("#!/bin/bash\n")
f.write("yunohost domain cert-renew --email\n") f.write("yunohost domain cert-renew --email\n")
_set_permissions(cron_job_file, "root", "root", 0755); _set_permissions(cron_job_file, "root", "root", 0755)
def _email_renewing_failed(domain, e) :
from_ = "certmanager@"+domain+" (Certificate Manager)" def _email_renewing_failed(domain, e):
to_ = "root" from_ = "certmanager@" + domain + " (Certificate Manager)"
subject_ = "Certificate renewing attempt for "+domain+" failed!" to_ = "root"
subject_ = "Certificate renewing attempt for " + domain + " failed!"
exceptionMessage = str(e) exceptionMessage = str(e)
logs = _tail(50, "/var/log/yunohost/yunohost-cli.log") logs = _tail(50, "/var/log/yunohost/yunohost-cli.log")
@ -357,29 +355,27 @@ Subject: %s
""" % (from_, to_, subject_, text) """ % (from_, to_, subject_, text)
smtp = smtplib.SMTP("localhost") smtp = smtplib.SMTP("localhost")
smtp.sendmail(from_, [ to_ ], message) smtp.sendmail(from_, [to_], message)
smtp.quit() smtp.quit()
def _configure_for_acme_challenge(auth, domain):
def _configure_for_acme_challenge(auth, domain) : nginx_conf_file = "/etc/nginx/conf.d/" + domain + ".d/000-acmechallenge.conf"
nginx_conf_file = "/etc/nginx/conf.d/"+domain+".d/000-acmechallenge.conf"
nginx_configuration = ''' nginx_configuration = '''
location '/.well-known/acme-challenge' location '/.well-known/acme-challenge'
{ {
default_type "text/plain"; default_type "text/plain";
alias '''+webroot_folder+'''; alias ''' + webroot_folder + ''';
} }
''' '''
# Write the conf # Write the conf
if os.path.exists(nginx_conf_file) : if os.path.exists(nginx_conf_file):
logger.info("Nginx configuration file for ACME challenge already exists for domain, skipping.") logger.info("Nginx configuration file for ACME challenge already exists for domain, skipping.")
else : else:
logger.info("Adding Nginx configuration file for Acme challenge for domain " + domain + ".") logger.info("Adding Nginx configuration file for Acme challenge for domain " + domain + ".")
with open(nginx_conf_file, "w") as f : with open(nginx_conf_file, "w") as f:
f.write(nginx_configuration) f.write(nginx_configuration)
# Assume nginx conf is okay, and reload it # Assume nginx conf is okay, and reload it
@ -389,29 +385,31 @@ location '/.well-known/acme-challenge'
app_ssowatconf(auth) app_ssowatconf(auth)
def _fetch_and_enable_new_certificate(domain) :
def _fetch_and_enable_new_certificate(domain):
# Make sure tmp folder exists # Make sure tmp folder exists
logger.debug("Making sure tmp folders exists...") logger.debug("Making sure tmp folders exists...")
if not (os.path.exists(webroot_folder)) : os.makedirs(webroot_folder) if not (os.path.exists(webroot_folder)):
if not (os.path.exists(tmp_folder)) : os.makedirs(tmp_folder) os.makedirs(webroot_folder)
_set_permissions(webroot_folder, "root", "www-data", 0650); if not (os.path.exists(tmp_folder)):
_set_permissions(tmp_folder, "root", "root", 0640); os.makedirs(tmp_folder)
_set_permissions(webroot_folder, "root", "www-data", 0650)
_set_permissions(tmp_folder, "root", "root", 0640)
# Prepare certificate signing request # Prepare certificate signing request
logger.info("Prepare key and certificate signing request (CSR) for "+domain+"...") logger.info("Prepare key and certificate signing request (CSR) for " + domain + "...")
domain_key_file = tmp_folder+"/"+domain+".pem" domain_key_file = tmp_folder + "/" + domain + ".pem"
_generate_key(domain_key_file) _generate_key(domain_key_file)
_set_permissions(domain_key_file, "root", "metronome", 0640); _set_permissions(domain_key_file, "root", "metronome", 0640)
_prepare_certificate_signing_request(domain, domain_key_file, tmp_folder) _prepare_certificate_signing_request(domain, domain_key_file, tmp_folder)
# Sign the certificate # Sign the certificate
logger.info("Now using ACME Tiny to sign the certificate...") logger.info("Now using ACME Tiny to sign the certificate...")
domain_csr_file = tmp_folder+"/"+domain+".csr" domain_csr_file = tmp_folder + "/" + domain + ".csr"
signed_certificate = sign_certificate(account_key_file, signed_certificate = sign_certificate(account_key_file,
domain_csr_file, domain_csr_file,
@ -425,30 +423,28 @@ def _fetch_and_enable_new_certificate(domain) :
# Create corresponding directory # Create corresponding directory
date_tag = datetime.now().strftime("%Y%m%d.%H%M%S") date_tag = datetime.now().strftime("%Y%m%d.%H%M%S")
new_cert_folder = cert_folder+"/" + domain + "." + date_tag new_cert_folder = cert_folder + "/" + domain + "." + date_tag
os.makedirs(new_cert_folder) os.makedirs(new_cert_folder)
_set_permissions(new_cert_folder, "root", "root", 0655); _set_permissions(new_cert_folder, "root", "root", 0655)
# Move the private key # Move the private key
shutil.move(domain_key_file, new_cert_folder+"/key.pem") shutil.move(domain_key_file, new_cert_folder + "/key.pem")
# Write the cert # Write the cert
domain_cert_file = new_cert_folder+"/crt.pem" domain_cert_file = new_cert_folder + "/crt.pem"
with open(domain_cert_file, "w") as f : with open(domain_cert_file, "w") as f:
f.write(signed_certificate) f.write(signed_certificate)
f.write(intermediate_certificate) f.write(intermediate_certificate)
_set_permissions(domain_cert_file, "root", "metronome", 0640); _set_permissions(domain_cert_file, "root", "metronome", 0640)
logger.info("Enabling the new certificate...") logger.info("Enabling the new certificate...")
# Replace (if necessary) the link or folder for live cert # Replace (if necessary) the link or folder for live cert
live_link = cert_folder+"/"+domain live_link = cert_folder + "/" + domain
if not os.path.islink(live_link) : if not os.path.islink(live_link):
shutil.rmtree(live_link) # Well, yep, hopefully that's not too dangerous (directory should have been backuped before calling this command) shutil.rmtree(live_link) # Well, yep, hopefully that's not too dangerous (directory should have been backuped before calling this command)
elif os.path.lexists(live_link) : elif os.path.lexists(live_link):
os.remove(live_link) os.remove(live_link)
os.symlink(new_cert_folder, live_link) os.symlink(new_cert_folder, live_link)
@ -456,18 +452,16 @@ def _fetch_and_enable_new_certificate(domain) :
# Check the status of the certificate is now good # Check the status of the certificate is now good
statusSummaryCode = _get_status(domain)["summaryCode"] statusSummaryCode = _get_status(domain)["summaryCode"]
if (statusSummaryCode < 20) : if (statusSummaryCode < 20):
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_certificate_fetching_or_enabling_failed', domain=domain)) raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_certificate_fetching_or_enabling_failed', domain=domain))
logger.info("Restarting services...") logger.info("Restarting services...")
for s in [ "nginx", "postfix", "dovecot", "metronome" ] : for s in ["nginx", "postfix", "dovecot", "metronome"]:
_run_service_command("restart", s) _run_service_command("restart", s)
def _prepare_certificate_signing_request(domain, key_file, output_folder) : def _prepare_certificate_signing_request(domain, key_file, output_folder):
# Init a request # Init a request
csr = crypto.X509Req() csr = crypto.X509Req()
@ -475,7 +469,7 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder) :
csr.get_subject().CN = domain csr.get_subject().CN = domain
# Set the key # Set the key
with open(key_file, 'rt') as f : with open(key_file, 'rt') as f:
key = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read()) key = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read())
csr.set_pubkey(key) csr.set_pubkey(key)
@ -483,158 +477,169 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder) :
csr.sign(key, "sha256") csr.sign(key, "sha256")
# Save the request in tmp folder # Save the request in tmp folder
csr_file = output_folder+domain+".csr" csr_file = output_folder + domain + ".csr"
logger.info("Saving to "+csr_file+" .") logger.info("Saving to " + csr_file + " .")
with open(csr_file, "w") as f : with open(csr_file, "w") as f:
f.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, csr)) f.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, csr))
def _get_status(domain) : def _get_status(domain):
cert_file = cert_folder + "/" + domain + "/crt.pem"
cert_file = cert_folder+"/"+domain+"/crt.pem" if (not os.path.isfile(cert_file)):
return {}
if (not os.path.isfile(cert_file)) : return {} try:
try :
cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(cert_file).read()) cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(cert_file).read())
except : except:
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cannot_read_cert', domain=domain, file=cert_file)) raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cannot_read_cert', domain=domain, file=cert_file))
certSubject = cert.get_subject().CN certSubject = cert.get_subject().CN
certIssuer = cert.get_issuer().CN certIssuer = cert.get_issuer().CN
validUpTo = datetime.strptime(cert.get_notAfter(),"%Y%m%d%H%M%SZ") validUpTo = datetime.strptime(cert.get_notAfter(), "%Y%m%d%H%M%SZ")
daysRemaining = (validUpTo - datetime.now()).days daysRemaining = (validUpTo - datetime.now()).days
CAtype = None CAtype = None
if (certIssuer == _name_selfCA()) : if (certIssuer == _name_selfCA()):
CAtype = "Self-signed" CAtype = "Self-signed"
elif (certIssuer.startswith("Let's Encrypt")) : elif (certIssuer.startswith("Let's Encrypt")):
CAtype = "Let's Encrypt" CAtype = "Let's Encrypt"
elif (certIssuer.startswith("Fake LE")) : elif (certIssuer.startswith("Fake LE")):
CAtype = "Fake Let's Encrypt" CAtype = "Fake Let's Encrypt"
else : else:
CAtype = "Other / Unknown" CAtype = "Other / Unknown"
# Unknown by default # Unknown by default
statusSummaryCode = 0 statusSummaryCode = 0
# Critical # Critical
if (daysRemaining <= 0) : statusSummaryCode = -30 if (daysRemaining <= 0):
statusSummaryCode = -30
# Warning, self-signed, browser will display a warning discouraging visitors to enter website # Warning, self-signed, browser will display a warning discouraging visitors to enter website
elif (CAtype == "Self-signed") or (CAtype == "Fake Let's Encrypt") : statusSummaryCode = -20 elif (CAtype == "Self-signed") or (CAtype == "Fake Let's Encrypt"):
statusSummaryCode = -20
# Attention, certificate will expire soon (should be renewed automatically if Let's Encrypt) # Attention, certificate will expire soon (should be renewed automatically if Let's Encrypt)
elif (daysRemaining < validity_limit) : statusSummaryCode = -10 elif (daysRemaining < validity_limit):
statusSummaryCode = -10
# CA not known, but still a valid certificate, so okay ! # CA not known, but still a valid certificate, so okay !
elif (CAtype == "Other / Unknown") : statusSummaryCode = 10 elif (CAtype == "Other / Unknown"):
statusSummaryCode = 10
# Let's Encrypt, great ! # Let's Encrypt, great !
elif (CAtype == "Let's Encrypt") : statusSummaryCode = 20 elif (CAtype == "Let's Encrypt"):
statusSummaryCode = 20
return { "domain" : domain, return {"domain": domain,
"subject" : certSubject, "subject": certSubject,
"CAname" : certIssuer, "CAname": certIssuer,
"CAtype" : CAtype, "CAtype": CAtype,
"validity" : daysRemaining, "validity": daysRemaining,
"summaryCode" : statusSummaryCode "summaryCode": statusSummaryCode
} }
############################################################################### ###############################################################################
# Misc small stuff ... # # Misc small stuff ... #
############################################################################### ###############################################################################
def _generate_account_key() :
def _generate_account_key():
logger.info("Generating account key ...") logger.info("Generating account key ...")
_generate_key(account_key_file) _generate_key(account_key_file)
_set_permissions(account_key_file, "root", "root", 0400) _set_permissions(account_key_file, "root", "root", 0400)
def _generate_key(destinationPath) :
def _generate_key(destinationPath):
k = crypto.PKey() k = crypto.PKey()
k.generate_key(crypto.TYPE_RSA, key_size) k.generate_key(crypto.TYPE_RSA, key_size)
with open(destinationPath, "w") as f : with open(destinationPath, "w") as f:
f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k)) f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k))
def _set_permissions(path, user, group, permissions) :
def _set_permissions(path, user, group, permissions):
uid = pwd.getpwnam(user).pw_uid uid = pwd.getpwnam(user).pw_uid
gid = grp.getgrnam(group).gr_gid gid = grp.getgrnam(group).gr_gid
os.chown(path, uid, gid) os.chown(path, uid, gid)
os.chmod(path, permissions) os.chmod(path, permissions)
def _backup_current_cert(domain): def _backup_current_cert(domain):
logger.info("Backuping existing certificate for domain " + domain)
logger.info("Backuping existing certificate for domain "+domain) cert_folder_domain = cert_folder + "/" + domain
cert_folder_domain = cert_folder+"/"+domain
dateTag = datetime.now().strftime("%Y%m%d.%H%M%S") dateTag = datetime.now().strftime("%Y%m%d.%H%M%S")
backup_folder = cert_folder_domain+"-backup-"+dateTag backup_folder = cert_folder_domain + "-backup-" + dateTag
shutil.copytree(cert_folder_domain, backup_folder) shutil.copytree(cert_folder_domain, backup_folder)
def _check_domain_is_correctly_configured(domain) : def _check_domain_is_correctly_configured(domain):
public_ip = yunohost.domain.get_public_ip() public_ip = yunohost.domain.get_public_ip()
# Check if IP from DNS matches public IP # Check if IP from DNS matches public IP
if not _dns_ip_match_public_ip(public_ip, domain) : if not _dns_ip_match_public_ip(public_ip, domain):
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_dns_ip_differs_from_public_ip', domain=domain)) raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_dns_ip_differs_from_public_ip', domain=domain))
# Check if domain seems to be accessible through HTTP ? # Check if domain seems to be accessible through HTTP ?
if not _domain_is_accessible_through_HTTP(public_ip, domain) : if not _domain_is_accessible_through_HTTP(public_ip, domain):
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_http_not_working', domain=domain)) raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_http_not_working', domain=domain))
def _dns_ip_match_public_ip(public_ip, domain) :
try : def _dns_ip_match_public_ip(public_ip, domain):
r = requests.get("http://dns-api.org/A/"+domain) try:
except : r = requests.get("http://dns-api.org/A/" + domain)
except:
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_contacting_dns_api', api="dns-api.org")) raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_contacting_dns_api', api="dns-api.org"))
if (r.text == "[{\"error\":\"NXDOMAIN\"}]") : if (r.text == "[{\"error\":\"NXDOMAIN\"}]"):
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_no_A_dns_record', domain=domain)) raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_no_A_dns_record', domain=domain))
try : try:
dns_ip = json.loads(r.text)[0]["value"] dns_ip = json.loads(r.text)[0]["value"]
except : except:
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_parsing_dns', domain=domain, value=r.text)) raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_parsing_dns', domain=domain, value=r.text))
if (dns_ip != public_ip) : if (dns_ip != public_ip):
return False return False
else : else:
return True return True
def _domain_is_accessible_through_HTTP(ip, domain) :
try : def _domain_is_accessible_through_HTTP(ip, domain):
requests.head("http://"+ip, headers = { "Host" : domain }) try:
except Exception : requests.head("http://" + ip, headers={"Host": domain})
except Exception:
return False return False
return True return True
def _summary_code_to_string(code) :
if (code <= -30) : return "CRITICAL" def _summary_code_to_string(code):
elif (code <= -20) : return "WARNING" if (code <= -30):
elif (code <= -10) : return "Attention" return "CRITICAL"
elif (code <= 0) : return "Unknown?" elif (code <= -20):
elif (code <= 10) : return "Good" return "WARNING"
elif (code <= 20) : return "Great!" elif (code <= -10):
return "Attention"
elif (code <= 0):
return "Unknown?"
elif (code <= 10):
return "Good"
elif (code <= 20):
return "Great!"
return "Unknown?" return "Unknown?"
def _name_selfCA() :
def _name_selfCA():
cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(selfCA_file).read()) cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(selfCA_file).read())
return cert.get_subject().CN return cert.get_subject().CN
def _tail(n, filePath): def _tail(n, filePath):
stdin,stdout = os.popen2("tail -n "+str(n)+" "+filePath) stdin, stdout = os.popen2("tail -n " + str(n) + " " + filePath)
stdin.close() stdin.close()
lines = stdout.readlines(); stdout.close() lines = stdout.readlines()
lines = "".join(lines) stdout.close()
return lines lines = "".join(lines)
return lines