From 47543b19b764bf085b91ccdd2d721f6abffd986c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Feb 2022 01:39:29 +0100 Subject: [PATCH] configpanels: Iterating on action POC to create a certificat section in domain config panels --- conf/nginx/server.tpl.conf | 8 ++-- share/config_domain.toml | 68 ++++++++++++++++++++++++++++++--- src/certificate.py | 77 +++++++++++--------------------------- src/domain.py | 28 ++++++++++++++ 4 files changed, 116 insertions(+), 65 deletions(-) diff --git a/conf/nginx/server.tpl.conf b/conf/nginx/server.tpl.conf index 379b597a7..4ee20a720 100644 --- a/conf/nginx/server.tpl.conf +++ b/conf/nginx/server.tpl.conf @@ -44,10 +44,10 @@ server { ssl_certificate /etc/yunohost/certs/{{ domain }}/crt.pem; ssl_certificate_key /etc/yunohost/certs/{{ domain }}/key.pem; - {% if domain_cert_ca != "Self-signed" %} + {% if domain_cert_ca != "selfsigned" %} more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; {% endif %} - {% if domain_cert_ca == "Let's Encrypt" %} + {% if domain_cert_ca == "letsencrypt" %} # OCSP settings ssl_stapling on; ssl_stapling_verify on; @@ -99,10 +99,10 @@ server { ssl_certificate /etc/yunohost/certs/{{ domain }}/crt.pem; ssl_certificate_key /etc/yunohost/certs/{{ domain }}/key.pem; - {% if domain_cert_ca != "Self-signed" %} + {% if domain_cert_ca != "selfsigned" %} more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; {% endif %} - {% if domain_cert_ca == "Let's Encrypt" %} + {% if domain_cert_ca == "letsencrypt" %} # OCSP settings ssl_stapling on; ssl_stapling_verify on; diff --git a/share/config_domain.toml b/share/config_domain.toml index 65e755365..e7f43f84d 100644 --- a/share/config_domain.toml +++ b/share/config_domain.toml @@ -16,7 +16,7 @@ i18n = "domain_config" type = "app" filter = "is_webapp" default = "_none" - + [feature.mail] #services = ['postfix', 'dovecot'] @@ -28,17 +28,17 @@ i18n = "domain_config" [feature.mail.mail_out] type = "boolean" default = 1 - + [feature.mail.mail_in] type = "boolean" default = 1 - + #[feature.mail.backup_mx] #type = "tags" #default = [] #pattern.regexp = '^([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,})$' #pattern.error = "pattern_error" - + [feature.xmpp] [feature.xmpp.xmpp] @@ -46,7 +46,7 @@ i18n = "domain_config" default = 0 [dns] - + [dns.registrar] optional = true @@ -58,3 +58,61 @@ i18n = "domain_config" # type = "number" # min = 0 # default = 3600 + + +[cert] + + [cert.status] + + [cert.status.cert_summary] + type = "alert" + # Automatically filled by DomainConfigPanel + + [cert.status.cert_validity] + type = "number" + readonly = true + # Automatically filled by DomainConfigPanel + + [cert.cert] + + [cert.cert.cert_issuer] + type = "string" + readonly = true + visible = "false" + # Automatically filled by DomainConfigPanel + + [cert.cert.acme_eligible] + type = "boolean" + readonly = true + visible = "false" + # Automatically filled by DomainConfigPanel + + [cert.cert.acme_eligible_explain] + type = "alert" + visible = "acme_eligible == false" + # FIXME: improve messaging ... + ask = "Uhoh, domain isnt ready for ACME challenge according to the diagnosis" + + [cert.cert.cert_no_checks] + ask = "Ignore diagnosis checks" + type = "boolean" + default = false + visible = "acme_eligible == false" + + [cert.cert.cert_install] + ask = "Install Let's Encrypt certificate" + type = "button" + icon = "star" + style = "success" + visible = "issuer != 'letsencrypt'" + enabled = "acme_eligible or cert_no_checks" + # ??? api = "PUT /domains/{domain}/cert?force&" + + [cert.cert.cert_renew] + ask = "Renew Let's Encrypt certificate" + help = "The certificate should be automatically renewed by YunoHost a few days before it expires." + type = "button" + icon = "refresh" + style = "warning" + visible = "issuer == 'letsencrypt'" + enabled = "acme_eligible or cert_no_checks" diff --git a/src/certificate.py b/src/certificate.py index 2a9fb4ce9..137a0aba0 100644 --- a/src/certificate.py +++ b/src/certificate.py @@ -95,8 +95,6 @@ def certificate_status(domains, full=False): if not full: del status["subject"] del status["CA_name"] - status["CA_type"] = status["CA_type"]["verbose"] - status["summary"] = status["summary"]["verbose"] if full: try: @@ -154,7 +152,7 @@ def _certificate_install_selfsigned(domain_list, force=False): if not force and os.path.isfile(current_cert_file): status = _get_status(domain) - if status["summary"]["code"] in ("good", "great"): + if status["summary"] == "success": raise YunohostValidationError( "certmanager_attempt_to_replace_valid_cert", domain=domain ) @@ -216,7 +214,7 @@ def _certificate_install_selfsigned(domain_list, force=False): if ( status - and status["CA_type"]["code"] == "self-signed" + and status["CA_type"] == "selfsigned" and status["validity"] > 3648 ): logger.success( @@ -241,7 +239,7 @@ def _certificate_install_letsencrypt(domains, force=False, no_checks=False): for domain in domain_list()["domains"]: status = _get_status(domain) - if status["CA_type"]["code"] != "self-signed": + if status["CA_type"] != "selfsigned": continue domains.append(domain) @@ -253,7 +251,7 @@ def _certificate_install_letsencrypt(domains, force=False, no_checks=False): # Is it self-signed? status = _get_status(domain) - if not force and status["CA_type"]["code"] != "self-signed": + if not force and status["CA_type"] != "selfsigned": raise YunohostValidationError( "certmanager_domain_cert_not_selfsigned", domain=domain ) @@ -314,7 +312,7 @@ def certificate_renew(domains, force=False, no_checks=False, email=False): # Does it have a Let's Encrypt cert? status = _get_status(domain) - if status["CA_type"]["code"] != "lets-encrypt": + if status["CA_type"] != "letsencrypt": continue # Does it expire soon? @@ -349,7 +347,7 @@ def certificate_renew(domains, force=False, no_checks=False, email=False): ) # Does it have a Let's Encrypt cert? - if status["CA_type"]["code"] != "lets-encrypt": + if status["CA_type"] != "letsencrypt": raise YunohostValidationError( "certmanager_attempt_to_renew_nonLE_cert", domain=domain ) @@ -539,7 +537,7 @@ def _fetch_and_enable_new_certificate(domain, no_checks=False): # Check the status of the certificate is now good status_summary = _get_status(domain)["summary"] - if status_summary["code"] != "great": + if status_summary != "success": raise YunohostError( "certmanager_certificate_fetching_or_enabling_failed", domain=domain ) @@ -634,58 +632,25 @@ def _get_status(domain): days_remaining = (valid_up_to - datetime.utcnow()).days if cert_issuer in ["yunohost.org"] + yunohost.domain.domain_list()["domains"]: - CA_type = { - "code": "self-signed", - "verbose": "Self-signed", - } - + CA_type = "selfsigned" elif organization_name == "Let's Encrypt": - CA_type = { - "code": "lets-encrypt", - "verbose": "Let's Encrypt", - } - + CA_type = "letsencrypt" else: - CA_type = { - "code": "other-unknown", - "verbose": "Other / Unknown", - } + CA_type = "other" if days_remaining <= 0: - status_summary = { - "code": "critical", - "verbose": "CRITICAL", - } - - elif CA_type["code"] in ("self-signed", "fake-lets-encrypt"): - status_summary = { - "code": "warning", - "verbose": "WARNING", - } - + summary = "danger" + elif CA_type == "selfsigned": + summary = "warning" elif days_remaining < VALIDITY_LIMIT: - status_summary = { - "code": "attention", - "verbose": "About to expire", - } - - elif CA_type["code"] == "other-unknown": - status_summary = { - "code": "good", - "verbose": "Good", - } - - elif CA_type["code"] == "lets-encrypt": - status_summary = { - "code": "great", - "verbose": "Great!", - } - + summary = "warning" + elif CA_type == "other": + summary = "success" + elif CA_type == "letsencrypt": + summary = "success" else: - status_summary = { - "code": "unknown", - "verbose": "Unknown?", - } + # shouldnt happen, because CA_type can be only selfsigned, letsencrypt, or other + summary = "" return { "domain": domain, @@ -693,7 +658,7 @@ def _get_status(domain): "CA_name": cert_issuer, "CA_type": CA_type, "validity": days_remaining, - "summary": status_summary, + "summary": summary, } diff --git a/src/domain.py b/src/domain.py index 29040ced8..4456d972c 100644 --- a/src/domain.py +++ b/src/domain.py @@ -499,6 +499,19 @@ class DomainConfigPanel(ConfigPanel): self.registar_id = toml["dns"]["registrar"]["registrar"]["value"] del toml["dns"]["registrar"]["registrar"]["value"] + # Cert stuff + if not filter_key or filter_key[0] == "cert": + + from yunohost.certificate import certificate_status + status = certificate_status([self.entity], full=True)["certificates"][self.entity] + + toml["cert"]["status"]["cert_summary"]["style"] = status["summary"] + # FIXME: improve message + toml["cert"]["status"]["cert_summary"]["ask"] = f"Status is {status['summary']} ! (FIXME: improve message depending on summary / issuer / validity ..." + + # FIXME: Ugly hack to save the cert status and reinject it in _load_current_values ... + self.cert_status = status + return toml def _load_current_values(self): @@ -511,6 +524,21 @@ class DomainConfigPanel(ConfigPanel): if not filter_key or filter_key[0] == "dns": self.values["registrar"] = self.registar_id + # FIXME: Ugly hack to save the cert status and reinject it in _load_current_values ... + if not filter_key or filter_key[0] == "cert": + self.values["cert_validity"] = self.cert_status["validity"] + self.values["cert_issuer"] = self.cert_status["CA_type"] + self.values["acme_eligible"] = self.cert_status["ACME_eligible"] + + def _run_action(self, action): + + if action == "cert_install": + from yunohost.certificate import certificate_install as action_func + elif action == "cert_renew": + from yunohost.certificate import certificate_renew as action_func + + action_func([self.entity], force=True, no_checks=self.new_values["cert_no_checks"]) + def _get_domain_settings(domain: str) -> dict: