def tools_migrations_run(targets=[], skip=False, auto=False, force_rerun=False, accept_disclaimer=False): """ Perform migrations targets A list migrations to run (all pendings by default) --skip Skip specified migrations (to be used only if you know what you are doing) (must explicit which migrations) --auto Automatic mode, won't run manual migrations (to be used only if you know what you are doing) --force-rerun Re-run already-ran migrations (to be used only if you know what you are doing)(must explicit which migrations) --accept-disclaimer Accept disclaimers of migrations (please read them before using this option) (only valid for one migration) """ all_migrations = _get_migrations_list() # Small utility that allows up to get a migration given a name, id or number later def get_matching_migration(target): for m in all_migrations: if m.id == target or m.name == target or m.id.split( "_")[0] == target: return m raise YunohostValidationError("migrations_no_such_migration", id=target) # auto, skip and force are exclusive options if auto + skip + force_rerun > 1: raise YunohostValidationError("migrations_exclusive_options") # If no target specified if not targets: # skip, revert or force require explicit targets if skip or force_rerun: raise YunohostValidationError( "migrations_must_provide_explicit_targets") # Otherwise, targets are all pending migrations targets = [m for m in all_migrations if m.state == "pending"] # If explicit targets are provided, we shall validate them else: targets = [get_matching_migration(t) for t in targets] done = [t.id for t in targets if t.state != "pending"] pending = [t.id for t in targets if t.state == "pending"] if skip and done: raise YunohostValidationError("migrations_not_pending_cant_skip", ids=", ".join(done)) if force_rerun and pending: raise YunohostValidationError("migrations_pending_cant_rerun", ids=", ".join(pending)) if not (skip or force_rerun) and done: raise YunohostValidationError("migrations_already_ran", ids=", ".join(done)) # So, is there actually something to do ? if not targets: logger.info(m18n.n("migrations_no_migrations_to_run")) return # Actually run selected migrations for migration in targets: # If we are migrating in "automatic mode" (i.e. from debian configure # during an upgrade of the package) but we are asked for running # migrations to be ran manually by the user, stop there and ask the # user to run the migration manually. if auto and migration.mode == "manual": logger.warn( m18n.n("migrations_to_be_ran_manually", id=migration.id)) # We go to the next migration continue # Check for migration dependencies if not skip: dependencies = [ get_matching_migration(dep) for dep in migration.dependencies ] pending_dependencies = [ dep.id for dep in dependencies if dep.state == "pending" ] if pending_dependencies: logger.error( m18n.n( "migrations_dependencies_not_satisfied", id=migration.id, dependencies_id=", ".join(pending_dependencies), )) continue # If some migrations have disclaimers (and we're not trying to skip them) if migration.disclaimer and not skip: # require the --accept-disclaimer option. # Otherwise, go to the next migration if not accept_disclaimer: logger.warn( m18n.n( "migrations_need_to_accept_disclaimer", id=migration.id, disclaimer=migration.disclaimer, )) continue # --accept-disclaimer will only work for the first migration else: accept_disclaimer = False # Start register change on system operation_logger = OperationLogger("tools_migrations_migrate_forward") operation_logger.start() if skip: logger.warn(m18n.n("migrations_skip_migration", id=migration.id)) migration.state = "skipped" _write_migration_state(migration.id, "skipped") operation_logger.success() else: try: migration.operation_logger = operation_logger logger.info( m18n.n("migrations_running_forward", id=migration.id)) migration.run() except Exception as e: # migration failed, let's stop here but still update state because # we managed to run the previous ones msg = m18n.n("migrations_migration_has_failed", exception=e, id=migration.id) logger.error(msg, exc_info=1) operation_logger.error(msg) else: logger.success( m18n.n("migrations_success_forward", id=migration.id)) migration.state = "done" _write_migration_state(migration.id, "done") operation_logger.success()
def _certificate_install_letsencrypt( domain_list, force=False, no_checks=False, staging=False ): import yunohost.domain if not os.path.exists(ACCOUNT_KEY_FILE): _generate_account_key() # If no domains given, consider all yunohost domains with self-signed # certificates if domain_list == []: for domain in yunohost.domain.domain_list()["domains"]: status = _get_status(domain) if status["CA_type"]["code"] != "self-signed": continue domain_list.append(domain) # Else, validate that yunohost knows the domains given else: for domain in domain_list: yunohost_domains_list = yunohost.domain.domain_list()["domains"] if domain not in yunohost_domains_list: raise YunohostValidationError("domain_name_unknown", domain=domain) # Is it self-signed? status = _get_status(domain) if not force and status["CA_type"]["code"] != "self-signed": raise YunohostValidationError( "certmanager_domain_cert_not_selfsigned", domain=domain ) if staging: logger.warning( "Please note that you used the --staging option, and that no new certificate will actually be enabled !" ) # Actual install steps for domain in domain_list: if not no_checks: try: _check_domain_is_ready_for_ACME(domain) except Exception as e: logger.error(e) continue logger.info("Now attempting install of certificate for domain %s!", domain) operation_logger = OperationLogger( "letsencrypt_cert_install", [("domain", domain)], args={"force": force, "no_checks": no_checks, "staging": staging}, ) operation_logger.start() try: _fetch_and_enable_new_certificate(domain, staging, no_checks=no_checks) except Exception as e: msg = "Certificate installation for %s failed !\nException: %s" % ( domain, e, ) logger.error(msg) operation_logger.error(msg) if no_checks: logger.error( "Please consider checking the 'DNS records' (basic) and 'Web' categories of the diagnosis to check for possible issues that may prevent installing a Let's Encrypt certificate on domain %s." % domain ) else: logger.success(m18n.n("certmanager_cert_install_success", domain=domain)) operation_logger.success()
def certificate_renew( domain_list, force=False, no_checks=False, email=False, staging=False ): """ Renew Let's Encrypt certificate for given domains (all by default) Keyword argument: domain_list -- Domains for which to renew the certificates force -- Ignore the validity threshold (15 days) no-check -- Disable some checks about the reachability of web server before attempting the renewing email -- Emails root if some renewing failed """ import yunohost.domain # If no domains given, consider all yunohost domains with Let's Encrypt # certificates if domain_list == []: for domain in yunohost.domain.domain_list()["domains"]: # Does it have a Let's Encrypt cert? status = _get_status(domain) if status["CA_type"]["code"] != "lets-encrypt": continue # Does it expire soon? if status["validity"] > VALIDITY_LIMIT and not force: continue # Check ACME challenge configured for given domain if not _check_acme_challenge_configuration(domain): logger.warning( m18n.n("certmanager_acme_not_configured_for_domain", domain=domain) ) continue domain_list.append(domain) if len(domain_list) == 0 and not email: logger.info("No certificate needs to be renewed.") # Else, validate the domain list given else: for domain in domain_list: # Is it in Yunohost dmomain list? if domain not in yunohost.domain.domain_list()["domains"]: raise YunohostValidationError("domain_name_unknown", domain=domain) status = _get_status(domain) # Does it expire soon? if status["validity"] > VALIDITY_LIMIT and not force: raise YunohostValidationError( "certmanager_attempt_to_renew_valid_cert", domain=domain ) # Does it have a Let's Encrypt cert? if status["CA_type"]["code"] != "lets-encrypt": raise YunohostValidationError( "certmanager_attempt_to_renew_nonLE_cert", domain=domain ) # Check ACME challenge configured for given domain if not _check_acme_challenge_configuration(domain): raise YunohostValidationError( "certmanager_acme_not_configured_for_domain", domain=domain ) if staging: logger.warning( "Please note that you used the --staging option, and that no new certificate will actually be enabled !" ) # Actual renew steps for domain in domain_list: if not no_checks: try: _check_domain_is_ready_for_ACME(domain) except Exception as e: logger.error(e) if email: logger.error("Sending email with details to root ...") _email_renewing_failed(domain, e) continue logger.info("Now attempting renewing of certificate for domain %s !", domain) operation_logger = OperationLogger( "letsencrypt_cert_renew", [("domain", domain)], args={ "force": force, "no_checks": no_checks, "staging": staging, "email": email, }, ) operation_logger.start() try: _fetch_and_enable_new_certificate(domain, staging, no_checks=no_checks) except Exception as e: import traceback from io import StringIO stack = StringIO() traceback.print_exc(file=stack) msg = "Certificate renewing for %s failed!" % (domain) if no_checks: msg += ( "\nPlease consider checking the 'DNS records' (basic) and 'Web' categories of the diagnosis to check for possible issues that may prevent installing a Let's Encrypt certificate on domain %s." % domain ) logger.error(msg) operation_logger.error(msg) logger.error(stack.getvalue()) logger.error(str(e)) if email: logger.error("Sending email with details to root ...") _email_renewing_failed(domain, msg + "\n" + str(e), stack.getvalue()) else: logger.success(m18n.n("certmanager_cert_renew_success", domain=domain)) operation_logger.success()
def _certificate_install_selfsigned(domain_list, force=False): for domain in domain_list: operation_logger = OperationLogger( "selfsigned_cert_install", [("domain", domain)], args={"force": force} ) # Paths of files and folder we'll need date_tag = datetime.utcnow().strftime("%Y%m%d.%H%M%S") new_cert_folder = "%s/%s-history/%s-selfsigned" % ( CERT_FOLDER, domain, date_tag, ) conf_template = os.path.join(SSL_DIR, "openssl.cnf") csr_file = os.path.join(SSL_DIR, "certs", "yunohost_csr.pem") conf_file = os.path.join(new_cert_folder, "openssl.cnf") key_file = os.path.join(new_cert_folder, "key.pem") crt_file = os.path.join(new_cert_folder, "crt.pem") ca_file = os.path.join(new_cert_folder, "ca.pem") # Check we ain't trying to overwrite a good cert ! current_cert_file = os.path.join(CERT_FOLDER, domain, "crt.pem") if not force and os.path.isfile(current_cert_file): status = _get_status(domain) if status["summary"]["code"] in ("good", "great"): raise YunohostValidationError( "certmanager_attempt_to_replace_valid_cert", domain=domain ) operation_logger.start() # Create output folder for new certificate stuff os.makedirs(new_cert_folder) # Create our conf file, based on template, replacing the occurences of # "yunohost.org" with the given domain with open(conf_file, "w") as f, open(conf_template, "r") as template: for line in template: f.write(line.replace("yunohost.org", domain)) # Use OpenSSL command line to create a certificate signing request, # and self-sign the cert commands = [ "openssl req -new -config %s -days 3650 -out %s -keyout %s -nodes -batch" % (conf_file, csr_file, key_file), "openssl ca -config %s -days 3650 -in %s -out %s -batch" % (conf_file, csr_file, crt_file), ] for command in commands: p = subprocess.Popen( command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) out, _ = p.communicate() out = out.decode("utf-8") if p.returncode != 0: logger.warning(out) raise YunohostError("domain_cert_gen_failed") else: logger.debug(out) # Link the CA cert (not sure it's actually needed in practice though, # since we append it at the end of crt.pem. For instance for Let's # Encrypt certs, we only need the crt.pem and key.pem) os.symlink(SELF_CA_FILE, ca_file) # Append ca.pem at the end of crt.pem with open(ca_file, "r") as ca_pem, open(crt_file, "a") as crt_pem: crt_pem.write("\n") crt_pem.write(ca_pem.read()) # Set appropriate permissions _set_permissions(new_cert_folder, "root", "root", 0o755) _set_permissions(key_file, "root", "ssl-cert", 0o640) _set_permissions(crt_file, "root", "ssl-cert", 0o640) _set_permissions(conf_file, "root", "root", 0o600) # Actually enable the certificate we created _enable_certificate(domain, new_cert_folder) # Check new status indicate a recently created self-signed certificate status = _get_status(domain) if ( status and status["CA_type"]["code"] == "self-signed" and status["validity"] > 3648 ): logger.success( m18n.n("certmanager_cert_install_success_selfsigned", domain=domain) ) operation_logger.success() else: msg = ( "Installation of self-signed certificate installation for %s failed !" % (domain) ) logger.error(msg) operation_logger.error(msg)