This commit is contained in:
Alexandre Aubin 2016-11-24 10:59:25 -05:00
parent 1b20899f0e
commit ddcc57eb9d

View file

@ -73,8 +73,8 @@ DNS_RESOLVERS = [
"80.67.169.12", # FDN "80.67.169.12", # FDN
"80.67.169.40", # "80.67.169.40", #
"89.234.141.66", # ARN "89.234.141.66", # ARN
"141.255.128.100", # Aquilenet "141.255.128.100", # Aquilenet
"141.255.128.101", # "141.255.128.101",
"89.234.186.18", # Grifon "89.234.186.18", # Grifon
"80.67.188.188" # LDN "80.67.188.188" # LDN
] ]
@ -107,7 +107,8 @@ def certificate_status(auth, domain_list, full=False):
for domain in domain_list: for domain in domain_list:
# Is it in Yunohost domain list ? # Is it in Yunohost domain list ?
if domain not in yunohost_domains_list: if domain not in yunohost_domains_list:
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_domain_unknown', domain=domain)) raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_domain_unknown', domain=domain))
certificates = {} certificates = {}
@ -124,11 +125,10 @@ def certificate_status(auth, domain_list, full=False):
del status["domain"] del status["domain"]
certificates[domain] = status certificates[domain] = status
return {"certificates" : certificates} return {"certificates": certificates}
def certificate_install(auth, domain_list, force=False, no_checks=False, self_signed=False, staging=False): def certificate_install(auth, domain_list, force=False, no_checks=False, self_signed=False, staging=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)
@ -145,11 +145,11 @@ def certificate_install(auth, domain_list, force=False, no_checks=False, self_si
# not used anymore # not used anymore
_check_old_letsencrypt_app() _check_old_letsencrypt_app()
if self_signed: if self_signed:
_certificate_install_selfsigned(domain_list, force) _certificate_install_selfsigned(domain_list, force)
else: else:
_certificate_install_letsencrypt(auth, domain_list, force, no_checks, staging) _certificate_install_letsencrypt(
auth, domain_list, force, no_checks, staging)
def _certificate_install_selfsigned(domain_list, force=False): def _certificate_install_selfsigned(domain_list, force=False):
@ -158,7 +158,8 @@ def _certificate_install_selfsigned(domain_list, force=False):
# Paths of files and folder we'll need # Paths of files and folder we'll need
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 = "%s/%s-history/%s-selfsigned" % (CERT_FOLDER, domain, date_tag) new_cert_folder = "%s/%s-history/%s-selfsigned" % (
CERT_FOLDER, domain, date_tag)
original_ca_file = '/etc/ssl/certs/ca-yunohost_crt.pem' original_ca_file = '/etc/ssl/certs/ca-yunohost_crt.pem'
ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA' ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA'
@ -175,17 +176,18 @@ def _certificate_install_selfsigned(domain_list, force=False):
if (not force) and (os.path.isfile(current_cert_file)): if (not force) and (os.path.isfile(current_cert_file)):
status = _get_status(domain) status = _get_status(domain)
if status["summary"]["code"] in ('good', 'great') : if status["summary"]["code"] in ('good', 'great'):
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))
# Create output folder for new certificate stuff # Create output folder for new certificate stuff
os.makedirs(new_cert_folder) os.makedirs(new_cert_folder)
# Create our conf file, based on template, replacing the occurences of # Create our conf file, based on template, replacing the occurences of
# "yunohost.org" with the given domain # "yunohost.org" with the given domain
with open(conf_file, "w") as f : with open(conf_file, "w") as f:
with open(conf_template, "r") as template : with open(conf_template, "r") as template:
for line in template : for line in template:
f.write(line.replace("yunohost.org", domain)) f.write(line.replace("yunohost.org", domain))
# Use OpenSSL command line to create a certificate signing request, # Use OpenSSL command line to create a certificate signing request,
@ -196,13 +198,15 @@ def _certificate_install_selfsigned(domain_list, force=False):
commands.append("openssl ca -config %s -days 3650 -in %s -out %s -batch" commands.append("openssl ca -config %s -days 3650 -in %s -out %s -batch"
% (conf_file, csr_file, crt_file)) % (conf_file, csr_file, crt_file))
for command in commands : for command in commands:
p = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) p = subprocess.Popen(
command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
out, _ = p.communicate() out, _ = p.communicate()
if p.returncode != 0: if p.returncode != 0:
logger.warning(out) logger.warning(out)
raise MoulinetteError(errno.EIO, m18n.n('domain_cert_gen_failed')) raise MoulinetteError(
else : errno.EIO, m18n.n('domain_cert_gen_failed'))
else:
logger.info(out) logger.info(out)
# Link the CA cert (not sure it's actually needed in practice though, # Link the CA cert (not sure it's actually needed in practice though,
@ -229,10 +233,11 @@ def _certificate_install_selfsigned(domain_list, force=False):
status = _get_status(domain) status = _get_status(domain)
if status and status["CA_type"]["code"] == "self-signed" and status["validity"] > 3648: if status and status["CA_type"]["code"] == "self-signed" and status["validity"] > 3648:
logger.success(m18n.n("certmanager_cert_install_success_selfsigned", domain=domain)) logger.success(
else : m18n.n("certmanager_cert_install_success_selfsigned", domain=domain))
logger.error("Installation of self-signed certificate installation for %s failed !", domain) else:
logger.error(
"Installation of self-signed certificate installation for %s failed !", domain)
def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=False, staging=False): def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=False, staging=False):
@ -255,20 +260,24 @@ def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=F
for domain in domain_list: for domain in domain_list:
yunohost_domains_list = yunohost.domain.domain_list(auth)['domains'] yunohost_domains_list = yunohost.domain.domain_list(auth)['domains']
if domain not in yunohost_domains_list: if domain not in yunohost_domains_list:
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["CA_type"]["code"] != "self-signed": if not force and status["CA_type"]["code"] != "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))
if (staging): if (staging):
logger.warning("Please note that you used the --staging option, and that no new certificate will actually be enabled !") logger.warning(
"Please note that you used the --staging option, and that no new certificate will actually be enabled !")
# Actual install steps # Actual install steps
for domain in domain_list: for domain in domain_list:
logger.info("Now attempting install of certificate for domain %s!", domain) logger.info(
"Now attempting install of certificate for domain %s!", domain)
try: try:
if not no_checks: if not no_checks:
@ -278,7 +287,8 @@ def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=F
_fetch_and_enable_new_certificate(domain, staging) _fetch_and_enable_new_certificate(domain, staging)
_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 %s failed !", domain) logger.error("Certificate installation for %s failed !", domain)
@ -325,31 +335,37 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal
# 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 expire soon ? # Does it expire 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 have a Let's Encrypt cert ? # Does it have a Let's Encrypt cert ?
if status["CA_type"]["code"] != "lets-encrypt": if status["CA_type"]["code"] != "lets-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))
if (staging): if (staging):
logger.warning("Please note that you used the --staging option, and that no new certificate will actually be enabled !") logger.warning(
"Please note that you used the --staging option, and that no new certificate will actually be enabled !")
# Actual renew steps # Actual renew steps
for domain in domain_list: for domain in domain_list:
logger.info("Now attempting renewing of certificate for domain %s !", domain) logger.info(
"Now attempting renewing of certificate for domain %s !", domain)
try: try:
if not no_checks: if not no_checks:
_check_domain_is_ready_for_ACME(domain) _check_domain_is_ready_for_ACME(domain)
_fetch_and_enable_new_certificate(domain, staging) _fetch_and_enable_new_certificate(domain, staging)
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:
import traceback import traceback
@ -375,7 +391,8 @@ def _check_old_letsencrypt_app():
if "letsencrypt" not in installedAppIds: if "letsencrypt" not in installedAppIds:
return return
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_old_letsencrypt_app_detected')) raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_old_letsencrypt_app_detected'))
def _install_cron(): def _install_cron():
@ -436,21 +453,25 @@ location '/.well-known/acme-challenge'
} }
''' % WEBROOT_FOLDER ''' % WEBROOT_FOLDER
# Check there isn't a conflicting file for the acme-challenge well-known uri # Check there isn't a conflicting file for the acme-challenge well-known
# uri
for path in glob.glob('%s/*.conf' % nginx_conf_folder): for path in glob.glob('%s/*.conf' % nginx_conf_folder):
if (path == nginx_conf_file) : if (path == nginx_conf_file):
continue continue
with open(path) as f: with open(path) as f:
contents = f.read() contents = f.read()
if ('/.well-known/acme-challenge' in contents) : if ('/.well-known/acme-challenge' in contents):
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_conflicting_nginx_file', filepath=path)) raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_conflicting_nginx_file', filepath=path))
# 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.")
return return
logger.info("Adding Nginx configuration file for Acme challenge for domain %s.", domain) logger.info(
"Adding Nginx configuration file for Acme challenge for domain %s.", 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)
@ -477,7 +498,8 @@ def _fetch_and_enable_new_certificate(domain, staging=False):
_set_permissions(TMP_FOLDER, "root", "root", 0640) _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 %s...", domain) logger.info(
"Prepare key and certificate signing request (CSR) for %s...", domain)
domain_key_file = "%s/%s.pem" % (TMP_FOLDER, domain) domain_key_file = "%s/%s.pem" % (TMP_FOLDER, domain)
_generate_key(domain_key_file) _generate_key(domain_key_file)
@ -503,13 +525,16 @@ def _fetch_and_enable_new_certificate(domain, staging=False):
CA=certification_authority) CA=certification_authority)
except ValueError as e: except ValueError as e:
if ("urn:acme:error:rateLimited" in str(e)): if ("urn:acme:error:rateLimited" in str(e)):
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_hit_rate_limit', domain=domain)) raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_hit_rate_limit', domain=domain))
else: else:
logger.error(str(e)) logger.error(str(e))
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cert_signing_failed')) raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_cert_signing_failed'))
except Exception as e: except Exception as e:
logger.error(str(e)) logger.error(str(e))
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cert_signing_failed')) raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_cert_signing_failed'))
intermediate_certificate = requests.get(INTERMEDIATE_CERTIFICATE_URL).text intermediate_certificate = requests.get(INTERMEDIATE_CERTIFICATE_URL).text
@ -524,7 +549,8 @@ def _fetch_and_enable_new_certificate(domain, staging=False):
else: else:
folder_flag = "letsencrypt" folder_flag = "letsencrypt"
new_cert_folder = "%s/%s-history/%s-%s" % (CERT_FOLDER, domain, date_tag, folder_flag) new_cert_folder = "%s/%s-history/%s-%s" % (
CERT_FOLDER, domain, date_tag, folder_flag)
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)
@ -550,7 +576,8 @@ def _fetch_and_enable_new_certificate(domain, staging=False):
status_summary = _get_status(domain)["summary"] status_summary = _get_status(domain)["summary"]
if status_summary["code"] != "great": if status_summary["code"] != "great":
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))
def _prepare_certificate_signing_request(domain, key_file, output_folder): def _prepare_certificate_signing_request(domain, key_file, output_folder):
@ -582,14 +609,17 @@ def _get_status(domain):
cert_file = os.path.join(CERT_FOLDER, domain, "crt.pem") cert_file = os.path.join(CERT_FOLDER, domain, "crt.pem")
if not os.path.isfile(cert_file): if not os.path.isfile(cert_file):
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_no_cert_file', domain=domain, file=cert_file)) raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_no_cert_file', domain=domain, file=cert_file))
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 Exception as exception: except Exception as exception:
import traceback import traceback
traceback.print_exc(file=sys.stdout) traceback.print_exc(file=sys.stdout)
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_cannot_read_cert', domain=domain, file=cert_file, reason=exception)) raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_cannot_read_cert', domain=domain, file=cert_file, reason=exception))
cert_subject = cert.get_subject().CN cert_subject = cert.get_subject().CN
cert_issuer = cert.get_issuer().CN cert_issuer = cert.get_issuer().CN
@ -626,7 +656,7 @@ def _get_status(domain):
"verbose": "CRITICAL", "verbose": "CRITICAL",
} }
elif CA_type["code"] in ("self-signed","fake-lets-encrypt"): elif CA_type["code"] in ("self-signed", "fake-lets-encrypt"):
status_summary = { status_summary = {
"code": "warning", "code": "warning",
"verbose": "WARNING", "verbose": "WARNING",
@ -699,13 +729,13 @@ def _set_permissions(path, user, group, permissions):
os.chmod(path, permissions) os.chmod(path, permissions)
def _enable_certificate(domain, new_cert_folder) : def _enable_certificate(domain, new_cert_folder):
logger.info("Enabling the certificate for domain %s ...", domain) logger.info("Enabling the certificate for domain %s ...", domain)
live_link = os.path.join(CERT_FOLDER, domain) live_link = os.path.join(CERT_FOLDER, domain)
# If a live link (or folder) already exists # If a live link (or folder) already exists
if os.path.exists(live_link) : if os.path.exists(live_link):
# If it's not a link ... expect if to be a folder # If it's not a link ... expect if to be a folder
if not os.path.islink(live_link): if not os.path.islink(live_link):
# Backup it and remove it # Backup it and remove it
@ -741,11 +771,13 @@ def _check_domain_is_ready_for_ACME(domain):
# 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): def _dns_ip_match_public_ip(public_ip, domain):
@ -754,7 +786,8 @@ def _dns_ip_match_public_ip(public_ip, domain):
resolver.nameservers = DNS_RESOLVERS resolver.nameservers = DNS_RESOLVERS
answers = resolver.query(domain, "A") answers = resolver.query(domain, "A")
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN): except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_error_no_A_record', domain=domain)) raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_error_no_A_record', domain=domain))
dns_ip = str(answers[0]) dns_ip = str(answers[0])
@ -771,7 +804,8 @@ def _domain_is_accessible_through_HTTP(ip, domain):
def _name_self_CA(): def _name_self_CA():
cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(SELF_CA_FILE).read()) cert = crypto.load_certificate(
crypto.FILETYPE_PEM, open(SELF_CA_FILE).read())
return cert.get_subject().CN return cert.get_subject().CN