From 4007990d91cc65fbf7d317322de904e679184f53 Mon Sep 17 00:00:00 2001 From: SveDec Date: Wed, 10 Jun 2026 18:44:55 +0200 Subject: [PATCH 1/9] Add invisible cert authority settings in security --- locales/en.json | 3 +++ share/config_global.toml | 14 ++++++++++++++ src/certificate.py | 3 ++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 0b5a0c1000..ffd8aedd00 100644 --- a/locales/en.json +++ b/locales/en.json @@ -490,6 +490,9 @@ "global_settings_setting_backup_compress_tar_archives": "Compress backups", "global_settings_setting_backup_compress_tar_archives_help": "When creating new backups, compress the archives (.tar.gz) instead of uncompressed archives (.tar). N.B. : enabling this option means create lighter backup archives, but the initial backup procedure will be significantly longer and heavy on CPU.", "global_settings_setting_backup_name": "Backup", + "global_settings_setting_certauth.certification_authority": "Identifier of the certification authority", + "global_settings_setting_certauth.certification_authority_fullname": "Full name of the certification authority", + "global_settings_setting_certauth.certification_authority_acme_url": "ACME URL of the certification authority", "global_settings_setting_dns_custom_resolvers_enabled": "Use custom DNS resolvers", "global_settings_setting_dns_custom_resolvers_enabled_help": "By default, YunoHost uses a list of trustworthy resolvers located in Europe. Advanced users may want to specify custom resolvers instead.", "global_settings_setting_dns_custom_resolvers_list": "Custom resolvers' addresses", diff --git a/share/config_global.toml b/share/config_global.toml index cbdb4f4c86..8720ddf7b5 100644 --- a/share/config_global.toml +++ b/share/config_global.toml @@ -105,6 +105,20 @@ i18n = "global_settings_setting" type = "boolean" default = false + [security.certauth] + visible = false + [security.certauth.certification_authority] + type = "string" + default = "letsencrypt" + + [security.certauth.certification_authority_fullname] + type = "string" + default = "Let's Encrypt" + + [security.certauth.certification_authority_acme_url] + type = "url" + default = "https://acme-v02.api.letsencrypt.org" + [email] [email.pop3] [email.pop3.pop3_enabled] diff --git a/src/certificate.py b/src/certificate.py index b15be931f0..4b85de3b93 100644 --- a/src/certificate.py +++ b/src/certificate.py @@ -33,6 +33,7 @@ from .log import OperationLogger from .regenconf import regen_conf from .service import _run_service_command +from .settings import settings_get from .utils.error import YunohostError, YunohostValidationError from .utils.file_utils import chmod, chown, read_file from .utils.network import get_public_ip @@ -60,7 +61,7 @@ VALIDITY_LIMIT = 15 # days # For prod -PRODUCTION_CERTIFICATION_AUTHORITY = "https://acme-v02.api.letsencrypt.org" +PRODUCTION_CERTIFICATION_AUTHORITY = settings_get("security.certauth.certification_authority_acme_url") # # Front-end stuff # From d903a32c6fd0f05f69138960bfcb30c4c021c281 Mon Sep 17 00:00:00 2001 From: SveDec Date: Wed, 10 Jun 2026 18:46:01 +0200 Subject: [PATCH 2/9] Add the domain in the SAN list of the CSR, as some CA reject an empty SAN list --- src/certificate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/certificate.py b/src/certificate.py index 4b85de3b93..d07f34493a 100644 --- a/src/certificate.py +++ b/src/certificate.py @@ -599,7 +599,7 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): # Set the domain csr.get_subject().CN = domain - sanlist = [] + sanlist = [domain] hook_results = hook_callback("cert_alternate_names", env={"domain": domain}) for hook_name, results in hook_results.items(): # From 2223dc5b493c8733bbe408f991ecf4decaefcb77 Mon Sep 17 00:00:00 2001 From: SveDec Date: Fri, 19 Jun 2026 18:16:40 +0200 Subject: [PATCH 3/9] CA name variabilized in certificate.py --- locales/en.json | 3 +++ src/certificate.py | 55 ++++++++++++++++++++++++---------------------- src/domain.py | 4 ++-- 3 files changed, 34 insertions(+), 28 deletions(-) diff --git a/locales/en.json b/locales/en.json index ffd8aedd00..da61dd84ed 100644 --- a/locales/en.json +++ b/locales/en.json @@ -386,6 +386,7 @@ "domain_config_cert_renew_help": "Certificate will be automatically renewed during the last 15 days of validity. You can manually renew it if you want to. (Not recommended).", "domain_config_cert_summary": "Certificate status", "domain_config_cert_summary_abouttoexpire": "Current certificate is about to expire. It should soon be renewed automatically.", + "domain_config_cert_summary_certauth": "Great! You're using a valid certificate!", "domain_config_cert_summary_expired": "CRITICAL: Current certificate is not valid! HTTPS won't work at all!", "domain_config_cert_summary_letsencrypt": "Great! You're using a valid Let's Encrypt certificate!", "domain_config_cert_summary_ok": "Okay, current certificate looks good!", @@ -616,6 +617,8 @@ "log_backup_create": "Create a backup archive", "log_backup_restore_app": "Restore '{}' from a backup archive", "log_backup_restore_system": "Restore system from a backup archive", + "log_certauth_cert_install": "Install a certificate from the certification authority on '{}' domain", + "log_certauth_cert_renew": "Renew '{}' certificate from the certification authority", "log_corrupted_md_file": "The YAML metadata file associated with logs is damaged: '{md_file}\nError: {error}'", "log_diagnosis_run": "Run diagnosis", "log_does_exists": "There is no operation log with the name '{log}', use 'yunohost log list' to see all available operation logs", diff --git a/src/certificate.py b/src/certificate.py index d07f34493a..d9a145dee8 100644 --- a/src/certificate.py +++ b/src/certificate.py @@ -47,12 +47,18 @@ else: logger = getLogger("yunohost.certmanager") +# For prod +PRODUCTION_CERTIFICATION_AUTHORITY = settings_get("security.certauth") +CERT_AUTH_NAME = PRODUCTION_CERTIFICATION_AUTHORITY['security.certauth.certification_authority']['value'] +CERT_AUTH_FULLNAME = PRODUCTION_CERTIFICATION_AUTHORITY['security.certauth.certification_authority_fullname']['value'] +CERT_AUTH_ACME_URL = PRODUCTION_CERTIFICATION_AUTHORITY['security.certauth.certification_authority_acme_url']['value'] + CERT_FOLDER = "/etc/yunohost/certs/" TMP_FOLDER = "/var/www/.well-known/acme-challenge-private/" WEBROOT_FOLDER = "/var/www/.well-known/acme-challenge-public/" SELF_CA_FILE = "/etc/ssl/certs/ca-yunohost_crt.pem" -ACCOUNT_KEY_FILE = "/etc/yunohost/letsencrypt_account.pem" +ACCOUNT_KEY_FILE = "/etc/yunohost/" + CERT_AUTH_NAME + "_account.pem" SSL_DIR = "/usr/share/yunohost/ssl" @@ -60,9 +66,6 @@ VALIDITY_LIMIT = 15 # days -# For prod -PRODUCTION_CERTIFICATION_AUTHORITY = settings_get("security.certauth.certification_authority_acme_url") - # # Front-end stuff # # @@ -135,20 +138,20 @@ def certificate_install( self_signed: bool = False, ) -> None: """ - Install a Let's Encrypt certificate for given domains (all by default) + Install a certificate from the certification authority for given domains (all by default) Keyword argument: domain_list -- Domains on which to install certificates force -- Install even if current certificate is not self-signed no-check -- Disable some checks about the reachability of web server before attempting the install - self-signed -- Instal self-signed certificates instead of Let's Encrypt + self-signed -- Instal self-signed certificates instead of from the CA """ if self_signed: _certificate_install_selfsigned(domain_list, force) else: - _certificate_install_letsencrypt(domain_list, force, no_checks) + _certificate_install_certauth(domain_list, force, no_checks) def _certificate_install_selfsigned(domain_list, force=False): @@ -254,7 +257,7 @@ def _certificate_install_selfsigned(domain_list, force=False): ) -def _certificate_install_letsencrypt(domains, force=False, no_checks=False): +def _certificate_install_certauth(domains, force=False, no_checks=False): from .domain import _assert_domain_exists, domain_list if not os.path.exists(ACCOUNT_KEY_FILE): @@ -295,7 +298,7 @@ def _certificate_install_letsencrypt(domains, force=False, no_checks=False): logger.info("Now attempting install of certificate for domain %s!", domain) operation_logger = OperationLogger( - "letsencrypt_cert_install", + "certauth_cert_install", [("domain", domain)], args={"force": force, "no_checks": no_checks}, ) @@ -309,7 +312,7 @@ def _certificate_install_letsencrypt(domains, force=False, no_checks=False): operation_logger.error(msg) if no_checks: logger.error( - f"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 {domain}." + f"Please consider checking the 'DNS records' (basic) and 'Web' categories of the diagnosis to check for possible issues that may prevent installing a {CERT_AUTH_FULLNAME} certificate on domain {domain}." ) failed_cert_install.append(domain) else: @@ -330,7 +333,7 @@ def certificate_renew( email: bool = False, ) -> None: """ - Renew Let's Encrypt certificate for given domains (all by default) + Renew certificate from the certification authority for given domains (all by default) Keyword argument: domains -- Domains for which to renew the certificates @@ -342,13 +345,13 @@ def certificate_renew( from .domain import _assert_domain_exists, domain_list - # If no domains given, consider all yunohost domains with Let's Encrypt - # certificates + # If no domains given, consider all yunohost domains with certificates + # from the certification authority if domains == []: for domain in domain_list()["domains"]: - # Does it have a Let's Encrypt cert? + # Does it have a cert from the CA? status = _get_status(domain) - if status["CA_type"] != "letsencrypt": + if status["CA_type"] != CERT_AUTH_NAME: continue # Does it expire soon? @@ -381,8 +384,8 @@ def certificate_renew( "certmanager_attempt_to_renew_valid_cert", domain=domain ) - # Does it have a Let's Encrypt cert? - if status["CA_type"] != "letsencrypt": + # Does it have a cert from the CA? + if status["CA_type"] != CERT_AUTH_NAME: raise YunohostValidationError( "certmanager_attempt_to_renew_nonLE_cert", domain=domain ) @@ -409,7 +412,7 @@ def certificate_renew( logger.info("Now attempting renewing of certificate for domain %s !", domain) operation_logger = OperationLogger( - "letsencrypt_cert_renew", + "certauth_cert_renew", [("domain", domain)], args={ "force": force, @@ -429,7 +432,7 @@ def certificate_renew( traceback.print_exc(file=stack) msg = f"Certificate renewing for {domain} failed!" if no_checks: - msg += f"\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 {domain}." + msg += f"\nPlease consider checking the 'DNS records' (basic) and 'Web' categories of the diagnosis to check for possible issues that may prevent installing a {CERT_AUTH_FULLNAME} certificate on domain {domain}." logger.error(msg) operation_logger.error(msg) logger.error(stack.getvalue()) @@ -538,7 +541,7 @@ def _fetch_and_enable_new_certificate(domain, no_checks=False): WEBROOT_FOLDER, log=logger, disable_check=no_checks, - CA=PRODUCTION_CERTIFICATION_AUTHORITY, + CA=CERT_AUTH_ACME_URL, ) except ValueError as e: if "urn:acme:error:rateLimited" in str(e): @@ -558,7 +561,7 @@ def _fetch_and_enable_new_certificate(domain, no_checks=False): # Create corresponding directory date_tag = datetime.utcnow().strftime("%Y%m%d.%H%M%S") - new_cert_folder = f"{CERT_FOLDER}/{domain}-history/{date_tag}-letsencrypt" + new_cert_folder = f"{CERT_FOLDER}/{domain}-history/{date_tag}-{CERT_AUTH_NAME}" os.makedirs(new_cert_folder) @@ -682,8 +685,8 @@ def _get_status(domain): # is actually a symlink to a dir ending with -selfsigned if os.path.realpath(os.path.join(CERT_FOLDER, domain)).endswith("-selfsigned"): CA_type = "selfsigned" - elif organization_name == "Let's Encrypt": - CA_type = "letsencrypt" + elif organization_name == CERT_AUTH_FULLNAME: + CA_type = CERT_AUTH_NAME else: CA_type = "other" @@ -699,11 +702,11 @@ def _get_status(domain): elif CA_type == "other": style = "success" summary = "ok" - elif CA_type == "letsencrypt": + elif CA_type == CERT_AUTH_NAME: style = "success" - summary = "letsencrypt" + summary = "certauth" else: - # shouldnt happen, because CA_type can be only selfsigned, letsencrypt, or other + # shouldnt happen, because CA_type can be only selfsigned, the CA name, or other style = "" summary = "wat" diff --git a/src/domain.py b/src/domain.py index ad081dc703..f9d76be4db 100644 --- a/src/domain.py +++ b/src/domain.py @@ -291,7 +291,7 @@ def domain_add( """ from .app import app_ssowatconf from .certificate import ( - _certificate_install_letsencrypt, + _certificate_install_certauth, _certificate_install_selfsigned, certificate_status, ) @@ -407,7 +407,7 @@ def domain_add( if can_install_letsencrypt: try: - _certificate_install_letsencrypt([domain], force=True, no_checks=True) + _certificate_install_certauth([domain], force=True, no_checks=True) except Exception: failed_letsencrypt_cert_install = True else: From d336f2e8cd1fc2abfae966a27c913380a308f046 Mon Sep 17 00:00:00 2001 From: SveDec Date: Fri, 26 Jun 2026 17:07:59 +0200 Subject: [PATCH 4/9] CA name variabilized in domain.py --- locales/en.json | 16 ++++++++-------- src/domain.py | 22 +++++++++++----------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/locales/en.json b/locales/en.json index da61dd84ed..d280f46557 100644 --- a/locales/en.json +++ b/locales/en.json @@ -168,18 +168,18 @@ "certmanager_attempt_to_renew_valid_cert": "The certificate for the domain '{domain}' is not about to expire! (You may use --force if you know what you're doing)", "certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain}! (Use --force to bypass)", "certmanager_cannot_read_cert": "Something wrong happened when trying to open current certificate for domain {domain} (file: {file}), reason: {reason}", - "certmanager_cert_install_failed": "Let's Encrypt certificate install failed for {domains}", + "certmanager_cert_install_failed": "CA certificate install failed for {domains}", "certmanager_cert_install_failed_selfsigned": "Self-signed certificate install failed for {domains}", - "certmanager_cert_install_success": "Let's Encrypt certificate now installed for the domain '{domain}'", + "certmanager_cert_install_success": "CA certificate now installed for the domain '{domain}'", "certmanager_cert_install_success_selfsigned": "Self-signed certificate now installed for the domain '{domain}'", - "certmanager_cert_renew_failed": "Let's Encrypt certificate renew failed for {domains}", - "certmanager_cert_renew_success": "Let's Encrypt certificate renewed for the domain '{domain}'", + "certmanager_cert_renew_failed": "CA certificate renew failed for {domains}", + "certmanager_cert_renew_success": "CA certificate renewed for the domain '{domain}'", "certmanager_cert_signing_failed": "Could not sign the new certificate", "certmanager_certificate_fetching_or_enabling_failed": "Trying to use the new certificate for {domain} did not work…", "certmanager_domain_cert_not_selfsigned": "The certificate for domain {domain} is not self-signed. Are you sure you want to replace it? (Use '--force' to do so.)", "certmanager_domain_dns_ip_differs_from_public_ip": "The DNS records for domain '{domain}' are different to this server's IP. Please check the 'DNS records' (basic) category in the diagnosis for more info. If you recently modified your A record, please wait for it to propagate (some DNS propagation checkers are available online). (If you know what you are doing, use '--no-checks' to turn off these checks.)", "certmanager_domain_http_not_working": "Domain {domain} does not seem to be accessible through HTTP. Please check the 'Web' category in the diagnosis for more info. (If you know what you are doing, use '--no-checks' to turn off these checks.)", - "certmanager_domain_not_diagnosed_yet": "There is no diagnosis result for domain {domain} yet. Please re-run a diagnosis for categories 'DNS records' and 'Web' in the diagnosis section to check if the domain is ready for Let's Encrypt. (Or if you know what you are doing, use '--no-checks' to turn off these checks.)", + "certmanager_domain_not_diagnosed_yet": "There is no diagnosis result for domain {domain} yet. Please re-run a diagnosis for categories 'DNS records' and 'Web' in the diagnosis section to check if the domain is ready for a CA certificate. (Or if you know what you are doing, use '--no-checks' to turn off these checks.)", "certmanager_hit_rate_limit": "Too many certificates already issued for this exact set of domains {domain} recently. Please try again later. See https://letsencrypt.org/docs/rate-limits/ for more details", "certmanager_no_cert_file": "Could not read the certificate file for the domain {domain} (file: {file})", "certmanager_self_ca_conf_file_not_found": "Could not find configuration file for self-signing authority (file: {file})", @@ -369,7 +369,7 @@ "domain_cannot_remove_main_add_new_one": "You cannot remove '{domain}' since it's the main domain and your only domain, you need to first add another domain using 'yunohost domain add ', then set is as the main domain using 'yunohost domain main-domain -n ' and then you can remove the domain '{domain}' using 'yunohost domain remove {domain}'.", "domain_cert_gen_failed": "Could not generate certificate", "domain_config_acme_eligible": "ACME eligibility", - "domain_config_acme_eligible_explain": "This domain doesn't seem ready for a Let's Encrypt certificate. Please check your DNS configuration and HTTP server reachability. The 'DNS records' and 'Web' section in the diagnosis page can help you understand what is misconfigured.", + "domain_config_acme_eligible_explain": "This domain doesn't seem ready for a certification authority delivered certificate. Please check your DNS configuration and HTTP server reachability. The 'DNS records' and 'Web' section in the diagnosis page can help you understand what is misconfigured.", "domain_config_api_protocol": "API protocol", "domain_config_auth_application_key": "Application key", "domain_config_auth_application_secret": "Application secret key", @@ -378,11 +378,11 @@ "domain_config_auth_key": "Authentication key", "domain_config_auth_secret": "Authentication secret", "domain_config_auth_token": "Authentication token", - "domain_config_cert_install": "Install Let's Encrypt certificate", + "domain_config_cert_install": "Install a certificate", "domain_config_cert_issuer": "Certification authority", "domain_config_cert_name": "Certificate", "domain_config_cert_no_checks": "Ignore diagnosis checks", - "domain_config_cert_renew": "Renew Let's Encrypt certificate", + "domain_config_cert_renew": "Renew a certificate", "domain_config_cert_renew_help": "Certificate will be automatically renewed during the last 15 days of validity. You can manually renew it if you want to. (Not recommended).", "domain_config_cert_summary": "Certificate status", "domain_config_cert_summary_abouttoexpire": "Current certificate is about to expire. It should soon be renewed automatically.", diff --git a/src/domain.py b/src/domain.py index f9d76be4db..0c79441b60 100644 --- a/src/domain.py +++ b/src/domain.py @@ -276,7 +276,7 @@ def domain_add( domain: str, dyndns_recovery_password=None, ignore_dyndns=False, - install_letsencrypt_cert=False, + install_certauth_cert=False, skip_tos=False, ): """ @@ -287,7 +287,7 @@ def domain_add( dyndns -- Subscribe to DynDNS dyndns_recovery_password -- Password used to later unsubscribe from DynDNS ignore_dyndns -- If we want to just add the DynDNS domain to the list, without subscribing - install_letsencrypt_cert -- If adding a subdomain of an already added domain, try to install a Let's Encrypt certificate + install_certauth_cert -- If adding a subdomain of an already added domain, try to install a certificate from the certification authority """ from .app import app_ssowatconf from .certificate import ( @@ -395,32 +395,32 @@ def domain_add( pass raise e - failed_letsencrypt_cert_install = False - if install_letsencrypt_cert: + failed_certauth_cert_install = False + if install_certauth_cert: parent_domain = _get_parent_domain_of(domain) - can_install_letsencrypt = ( + can_install_certauth = ( parent_domain and certificate_status([parent_domain], full=True)["certificates"][ parent_domain ]["has_wildcards"] ) - if can_install_letsencrypt: + if can_install_certauth: try: _certificate_install_certauth([domain], force=True, no_checks=True) except Exception: - failed_letsencrypt_cert_install = True + failed_certauth_cert_install = True else: logger.warning( - "Skipping Let's Encrypt certificate attempt because there's no wildcard configured on the parent domain's DNS records." + "Skipping certificate attempt from the CA because there's no wildcard configured on the parent domain's DNS records." ) - failed_letsencrypt_cert_install = True + failed_certauth_cert_install = True hook_callback("post_domain_add", args=[domain]) logger.success(m18n.n("domain_created")) - if failed_letsencrypt_cert_install: + if failed_certauth_cert_install: logger.warning(m18n.n("certmanager_cert_install_failed", domains=domain)) @@ -792,7 +792,7 @@ def _get_raw_config(self) -> "RawConfig": # i18n: domain_config_cert_summary_selfsigned # i18n: domain_config_cert_summary_abouttoexpire # i18n: domain_config_cert_summary_ok - # i18n: domain_config_cert_summary_letsencrypt + # i18n: domain_config_cert_summary_certauth raw_config["cert"]["cert_"]["cert_summary"]["ask"] = m18n.n( f"domain_config_cert_summary_{status['summary']}" ) From bbc3edfcfb3e18189d34481bc8d659805e84d0f1 Mon Sep 17 00:00:00 2001 From: SveDec Date: Fri, 26 Jun 2026 17:15:51 +0200 Subject: [PATCH 5/9] Variable name made generic --- locales/en.json | 1 + src/certificate.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index d280f46557..8024987cfc 100644 --- a/locales/en.json +++ b/locales/en.json @@ -164,6 +164,7 @@ "cannot_open_file": "Could not open file {file} (reason: {error})", "cannot_write_file": "Could not write file {file} (reason: {error})", "certmanager_acme_not_configured_for_domain": "The ACME challenge cannot be run for {domain} right now because its nginx conf lacks the corresponding code snippet… Please make sure that your nginx configuration is up to date using `yunohost tools regen-conf nginx --dry-run --with-diff`.", + "certmanager_attempt_to_renew_nonCA_cert": "The certificate for the domain '{domain}' is not issued by the certification authority. Cannot renew it automatically!", "certmanager_attempt_to_renew_nonLE_cert": "The certificate for the domain '{domain}' is not issued by Let's Encrypt. Cannot renew it automatically!", "certmanager_attempt_to_renew_valid_cert": "The certificate for the domain '{domain}' is not about to expire! (You may use --force if you know what you're doing)", "certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain}! (Use --force to bypass)", diff --git a/src/certificate.py b/src/certificate.py index d9a145dee8..5dff511e18 100644 --- a/src/certificate.py +++ b/src/certificate.py @@ -387,7 +387,7 @@ def certificate_renew( # Does it have a cert from the CA? if status["CA_type"] != CERT_AUTH_NAME: raise YunohostValidationError( - "certmanager_attempt_to_renew_nonLE_cert", domain=domain + "certmanager_attempt_to_renew_nonCA_cert", domain=domain ) # Check ACME challenge configured for given domain From 0182ea88341c01c47f607c0e5f00a9fe92767321 Mon Sep 17 00:00:00 2001 From: SveDec Date: Fri, 3 Jul 2026 16:30:03 +0200 Subject: [PATCH 6/9] CA name variabilized in config_domain.toml --- share/config_domain.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/config_domain.toml b/share/config_domain.toml index 3218c2af97..5f2f8e28d8 100644 --- a/share/config_domain.toml +++ b/share/config_domain.toml @@ -114,12 +114,12 @@ i18n = "domain_config" type = "button" icon = "star" style = "success" - visible = "cert_issuer != 'letsencrypt'" + visible = "cert_issuer != certification_authority" enabled = "acme_eligible || cert_no_checks" [cert.cert_.cert_renew] type = "button" icon = "refresh" style = "warning" - visible = "cert_issuer == 'letsencrypt'" + visible = "cert_issuer == certification_authority" enabled = "acme_eligible || cert_no_checks" From c3d9f08ca64f8fea196dfc4556db922f69c26564 Mon Sep 17 00:00:00 2001 From: SveDec Date: Fri, 3 Jul 2026 17:10:14 +0200 Subject: [PATCH 7/9] DNS CAA value variabilized --- share/config_global.toml | 4 ++++ src/dns.py | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/share/config_global.toml b/share/config_global.toml index 8720ddf7b5..7035ab1855 100644 --- a/share/config_global.toml +++ b/share/config_global.toml @@ -119,6 +119,10 @@ i18n = "global_settings_setting" type = "url" default = "https://acme-v02.api.letsencrypt.org" + [security.certauth.certification_authority_caa_value] + type = "string" + default = "letsencrypt.org" + [email] [email.pop3] [email.pop3.pop3_enabled] diff --git a/src/dns.py b/src/dns.py index cb5d6c4617..61ebe081d1 100644 --- a/src/dns.py +++ b/src/dns.py @@ -38,6 +38,7 @@ ) from .hook import hook_callback from .log import OperationLogger, is_unit_operation +from .settings import settings_get from .utils.dns import dig, is_special_use_tld, is_yunohost_dyndns_domain from .utils.error import YunohostError, YunohostValidationError from .utils.file_utils import mkdir, read_file, read_toml, write_to_file @@ -145,7 +146,7 @@ def _build_dns_conf( {"type": "A", "name": "*", "content": "123.123.123.123", "ttl": 3600}, # if ipv6 available {"type": "AAAA", "name": "*", "content": "valid-ipv6", "ttl": 3600}, - {"type": "CAA", "name": "@", "content": "0 issue \"letsencrypt.org\"", "ttl": 3600}, + {"type": "CAA", "name": "@", "content": "0 issue \"the-ca-identifier\"", "ttl": 3600}, ], "example_of_a_custom_rule": [ {"type": "SRV", "name": "_matrix", "content": "domain.tld.", "ttl": 3600} @@ -186,6 +187,7 @@ def _build_dns_conf( # foo.sub.domain.tld # sub.domain.tld # foo # .foo # basename = _get_relative_name_for_dns_zone(domain, base_dns_zone) suffix = f".{basename}" if basename != "@" else "" + caa_value = settings_get("security.certauth.certification_authority_caa_value") # ttl = settings["ttl"] ttl = 3600 @@ -235,7 +237,7 @@ def _build_dns_conf( elif include_empty_AAAA_if_no_ipv6: extra.append((f"*{suffix}", ttl, "AAAA", None)) # type: ignore[arg-type] - extra.append((basename, ttl, "CAA", '0 issue "letsencrypt.org"')) + extra.append((basename, ttl, "CAA", f"0 issue \"{caa_value}\"")) #################### # Standard records # From eece4d76ec1ae99268c4a0e2d4d8321cbf448098 Mon Sep 17 00:00:00 2001 From: SveDec Date: Fri, 3 Jul 2026 17:17:34 +0200 Subject: [PATCH 8/9] Comment variabilized --- hooks/conf_regen/01-yunohost | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/conf_regen/01-yunohost b/hooks/conf_regen/01-yunohost index aae3a70eeb..51e74923c2 100755 --- a/hooks/conf_regen/01-yunohost +++ b/hooks/conf_regen/01-yunohost @@ -244,7 +244,7 @@ EOF sleep \$((RANDOM%3600)); yunohost tools update apps > /dev/null EOF - # Cron job that renew lets encrypt certificates if there's any that needs renewal + # Cron job that renew certificates from the CA if there's any that needs renewal cat > "$pending_dir/etc/cron.daily/yunohost-certificate-renew" << EOF #!/bin/bash yunohost domain cert renew --email From eaa1bc2fc62f35d13795792fe20c0a1f8b6b0556 Mon Sep 17 00:00:00 2001 From: SveDec Date: Fri, 3 Jul 2026 17:33:48 +0200 Subject: [PATCH 9/9] CA name made generic in the actionsmap --- share/actionsmap.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/share/actionsmap.yml b/share/actionsmap.yml index 4664aa04f1..013497daa1 100755 --- a/share/actionsmap.yml +++ b/share/actionsmap.yml @@ -576,7 +576,7 @@ domain: extra: pattern: *pattern_password --install-letsencrypt-cert: - help: If adding a subdomain of an already added domain, try to install a Let's Encrypt certificate + help: If adding a subdomain of an already added domain, try to install a certificate from the certification authority action: store_true ### domain_remove() @@ -812,7 +812,7 @@ domain: ### certificate_install() install: - action_help: Install Let's Encrypt certificates for given domains (all by default). + action_help: Install certificates from the certification authority for given domains (all by default). api: PUT /domains//cert arguments: domain_list: @@ -827,12 +827,12 @@ domain: help: Does not perform any check that your domain seems correctly configured (DNS, reachability) before attempting to install. (Not recommended) action: store_true --self-signed: - help: Install self-signed certificate instead of Let's Encrypt + help: Install self-signed certificate instead of issued by the certification authority action: store_true ### certificate_renew() renew: - action_help: Renew the Let's Encrypt certificates for given domains (all by default). + action_help: Renew the certification authority certificates for given domains (all by default). api: PUT /domains//cert/renew arguments: domain_list: