Example #1
0
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()
Example #2
0
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()
Example #3
0
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()
Example #4
0
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)