From 007d8de346749d9507c015d3d195633981d3c175 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Thu, 15 May 2025 14:08:01 -0700 Subject: [PATCH] apache, letsencrypt: Create a site specific config for all domains - Instead of just the sites that have successfully obtain certificate. This allows customization of configuration for those sites (especially useful when testing where LE certs are not obtained). Tests: - When a domain is added to the system, an apache TLS configuration is created for the domain even though the domain does not have a successfully obtained LE cert. - When a domain is removed, the TLS configuration for the domain is removed. - Add a domain without the patches. Apply the patches and restart the service. The domain added signals are fired during the startup. This results in site specific TLS configuration files getting created and Apache reloads. When the service is restarted, the files are not created and Apache is not reloaded. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/modules/apache/__init__.py | 20 ++++++++++++++ plinth/modules/apache/privileged.py | 33 ++++++++++++++++++++++++ plinth/modules/letsencrypt/privileged.py | 24 ----------------- 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/plinth/modules/apache/__init__.py b/plinth/modules/apache/__init__.py index 3e2ccd7d2..a73483c76 100644 --- a/plinth/modules/apache/__init__.py +++ b/plinth/modules/apache/__init__.py @@ -12,6 +12,7 @@ from plinth.daemon import Daemon, RelatedDaemon from plinth.modules.firewall.components import Firewall from plinth.modules.letsencrypt.components import LetsEncrypt from plinth.package import Packages +from plinth.signals import domain_added, domain_removed from plinth.utils import format_lazy, is_valid_user_name from . import privileged @@ -64,6 +65,12 @@ class ApacheApp(app_module.App): related_daemon = RelatedDaemon('related-daemon-apache', 'uwsgi') self.add(related_daemon) + @staticmethod + def post_init(): + """Perform post initialization operations.""" + domain_added.connect(_on_domain_added) + domain_removed.connect(_on_domain_removed) + def setup(self, old_version): """Install and configure the app.""" super().setup(old_version) @@ -71,6 +78,19 @@ class ApacheApp(app_module.App): self.enable() +def _on_domain_added(sender, domain_type, name='', description='', + services=None, **kwargs): + """Add site specific configuration for a domain.""" + if name: + privileged.domain_setup(name) + + +def _on_domain_removed(sender, domain_type, name='', **kwargs): + """Remove site specific configuration for a domain.""" + if name: + privileged.domain_remove(name) + + # (U)ser (W)eb (S)ites diff --git a/plinth/modules/apache/privileged.py b/plinth/modules/apache/privileged.py index 1093ae989..2568aa82e 100644 --- a/plinth/modules/apache/privileged.py +++ b/plinth/modules/apache/privileged.py @@ -214,3 +214,36 @@ def uwsgi_enable(name: str): def uwsgi_disable(name: str): """Disable uWSGI configuration and reload.""" action_utils.uwsgi_disable(name) + + +@privileged +def domain_setup(domain: str): + """Add site specific configuration for a domain.""" + if '/' in domain: + raise ValueError('Invalid domain') + + path = pathlib.Path('/etc/apache2/sites-available/') + path = path / (domain + '.conf') + configuration = 'Use FreedomBoxTLSSiteMacro {domain}\n' + if path.is_file(): + return # File already exists. Assume it to be correct one. + + path.write_text(configuration.format(domain=domain)) + + with action_utils.WebserverChange() as webserver: + webserver.enable('freedombox-tls-site-macro', kind='config') + webserver.enable(domain, kind='site') + + +@privileged +def domain_remove(domain: str): + """Remove site specific configuration for a domain.""" + if '/' in domain: + raise ValueError('Invalid domain') + + with action_utils.WebserverChange() as webserver: + webserver.disable(domain, kind='site') + + path = pathlib.Path('/etc/apache2/sites-available/') + path = path / (domain + '.conf') + path.unlink(missing_ok=True) diff --git a/plinth/modules/letsencrypt/privileged.py b/plinth/modules/letsencrypt/privileged.py index e9dff41a0..0993c02d4 100644 --- a/plinth/modules/letsencrypt/privileged.py +++ b/plinth/modules/letsencrypt/privileged.py @@ -2,7 +2,6 @@ """Configure Let's Encrypt.""" import filecmp -import glob import importlib import inspect import os @@ -24,10 +23,6 @@ LE_DIRECTORY = '/etc/letsencrypt/' ETC_SSL_DIRECTORY = '/etc/ssl/' AUTHENTICATOR = 'webroot' WEB_ROOT_PATH = '/var/www/html' -APACHE_PREFIX = '/etc/apache2/sites-available/' -APACHE_CONFIGURATION = ''' -Use FreedomBoxTLSSiteMacro {domain} -''' def _get_certificate_expiry(domain: str) -> str: @@ -139,9 +134,6 @@ def obtain(domain: str): subprocess.run(command, check=True) - with action_utils.WebserverChange() as webserver_change: - _setup_webserver_config(domain, webserver_change) - @privileged def copy_certificate(managing_app: str, source_private_key: str, @@ -259,19 +251,3 @@ def delete(domain: str): command = ['certbot', 'delete', '--non-interactive', '--cert-name', domain] subprocess.run(command, check=True) action_utils.webserver_disable(domain, kind='site') - - -def _setup_webserver_config(domain, webserver_change): - """Create SSL web server configuration for a domain. - - Do so only if there is no configuration existing. - """ - file_name = os.path.join(APACHE_PREFIX, domain + '.conf') - if os.path.isfile(file_name): - os.rename(file_name, file_name + '.fbx-bak') - - with open(file_name, 'w', encoding='utf-8') as file_handle: - file_handle.write(APACHE_CONFIGURATION.format(domain=domain)) - - webserver_change.enable('freedombox-tls-site-macro', kind='config') - webserver_change.enable(domain, kind='site')