mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
[mod] autopep8
This commit is contained in:
parent
d47f5919d6
commit
bec8f63479
1 changed files with 210 additions and 205 deletions
|
@ -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)
|
||||||
|
|
||||||
|
@ -122,31 +128,29 @@ def certificate_install(auth, domainList, force=False, no_checks=False, self_sig
|
||||||
no-check -- Disable some checks about the reachability of web server
|
no-check -- Disable some checks about the reachability of web server
|
||||||
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:
|
||||||
os.listdir(cert_folder_domain)
|
os.listdir(cert_folder_domain)
|
||||||
except OSError:
|
except OSError:
|
||||||
os.makedirs(cert_folder_domain)
|
os.makedirs(cert_folder_domain)
|
||||||
|
|
||||||
# Get serial
|
# Get serial
|
||||||
|
@ -157,79 +161,77 @@ 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+" !")
|
|
||||||
|
|
||||||
try :
|
logger.info("Now attempting install of certificate for domain " + domain + " !")
|
||||||
|
|
||||||
if not no_checks : _check_domain_is_correctly_configured(domain)
|
try:
|
||||||
|
|
||||||
|
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)
|
||||||
_install_cron()
|
_install_cron()
|
||||||
|
|
||||||
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,86 +252,82 @@ 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']:
|
||||||
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain))
|
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain))
|
||||||
|
|
||||||
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+" !")
|
|
||||||
|
|
||||||
try :
|
logger.info("Now attempting renewing of certificate for domain " + domain + " !")
|
||||||
|
|
||||||
if not no_checks : _check_domain_is_correctly_configured(domain)
|
try:
|
||||||
|
|
||||||
|
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 :
|
|
||||||
r = requests.get("http://dns-api.org/A/"+domain)
|
def _dns_ip_match_public_ip(public_ip, domain):
|
||||||
except :
|
try:
|
||||||
|
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
|
||||||
|
|
Loading…
Add table
Reference in a new issue