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 <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2025-05-15 14:08:01 -07:00 committed by James Valleroy
parent d76a371f57
commit 007d8de346
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
3 changed files with 53 additions and 24 deletions

View File

@ -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

View File

@ -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)

View File

@ -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')