mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-21 07:55:00 +00:00
Mask disabled Tor systemd services to prevent services starting by the Tor
master service after system reboot.
Also:
- Fix torproxy app always enabled after setup.
- Minor privileged code cleanup - removed unused functions.
Tests performed on Debian stable and testing:
- Installed and disabled the apps, rebooted the system, then applied the patch.
Ensured that apps are upgraded successfully and apps are disabled after
upgrade. Ensured that tor@default, tor@plinth and tor@fbxlocal services are
masked and not running.
- After 1)enabling and 2)disabling both apps and 3)rebooting the system:
- Ensured that the tor@default service is not running and is masked.
- Ensured that tor@plinth or tor@fbxproxy service states match the states
of the app.
- Uninstalled the apps, ensured that only the tor@default service masked
state remains in the systemd.
- All the tor and torproxy tests pass.
Closes #2369, #2454.
Signed-off-by: Veiko Aasa <veiko17@disroot.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
170 lines
6.6 KiB
Python
170 lines
6.6 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
"""FreedomBox app to configure Tor Proxy."""
|
|
|
|
import json
|
|
import logging
|
|
|
|
from django.urls import reverse_lazy
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.utils.translation import gettext_noop
|
|
|
|
from plinth import app as app_module
|
|
from plinth import cfg, frontpage, kvstore, menu
|
|
from plinth.daemon import Daemon
|
|
from plinth.diagnostic_check import DiagnosticCheck
|
|
from plinth.modules.apache.components import diagnose_url
|
|
from plinth.modules.backups.components import BackupRestore
|
|
from plinth.modules.firewall.components import Firewall
|
|
from plinth.modules.users.components import UsersAndGroups
|
|
from plinth.package import Packages
|
|
from plinth.privileged import service as service_privileged
|
|
from plinth.utils import format_lazy
|
|
|
|
from . import manifest, privileged
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
PREINSTALL_CONFIG_KEY = 'torproxy_preinstall_config'
|
|
|
|
_description = [
|
|
_('Tor is an anonymous communication system. You can learn more '
|
|
'about it from the <a href="https://www.torproject.org/">Tor '
|
|
'Project</a> website. For best protection when web surfing, the '
|
|
'Tor Project recommends that you use the '
|
|
'<a href="https://www.torproject.org/download/download-easy.html.en">'
|
|
'Tor Browser</a>.'),
|
|
format_lazy(
|
|
_('This app provides a web proxy on your {box_name} for internal '
|
|
'networks on TCP port 9050 using the SOCKS protocol. This can be '
|
|
'used by various apps to access the internet via the Tor network. '
|
|
'ISP censorship can be circumvented using upstream bridges.'),
|
|
box_name=_(cfg.box_name))
|
|
]
|
|
|
|
|
|
class TorProxyApp(app_module.App):
|
|
"""FreedomBox app for Tor Proxy."""
|
|
|
|
app_id = 'torproxy'
|
|
|
|
_version = 2
|
|
|
|
def __init__(self) -> None:
|
|
"""Create components for the app."""
|
|
super().__init__()
|
|
|
|
info = app_module.Info(app_id=self.app_id, version=self._version,
|
|
name=_('Tor Proxy'), icon_filename='torproxy',
|
|
short_description=_('Anonymity Network'),
|
|
description=_description,
|
|
manual_page='TorProxy',
|
|
clients=manifest.clients, tags=manifest.tags,
|
|
donation_url='https://donate.torproject.org/')
|
|
self.add(info)
|
|
|
|
menu_item = menu.Menu('menu-torproxy', info.name,
|
|
info.short_description, info.icon_filename,
|
|
'torproxy:index', parent_url_name='apps')
|
|
self.add(menu_item)
|
|
|
|
shortcut = frontpage.Shortcut(
|
|
'shortcut-torproxy', info.name,
|
|
short_description=info.short_description, icon=info.icon_filename,
|
|
description=info.description, manual_page=info.manual_page,
|
|
configure_url=reverse_lazy('torproxy:index'), login_required=True)
|
|
self.add(shortcut)
|
|
|
|
packages = Packages('packages-torproxy', [
|
|
'tor', 'tor-geoipdb', 'torsocks', 'obfs4proxy', 'apt-transport-tor'
|
|
])
|
|
self.add(packages)
|
|
|
|
firewall = Firewall('firewall-torproxy-socks', _('Tor Socks Proxy'),
|
|
ports=['tor-socks'], is_external=False)
|
|
self.add(firewall)
|
|
|
|
daemon = Daemon(
|
|
'daemon-torproxy', 'tor@fbxproxy', strict_check=True,
|
|
listen_ports=[(9050, 'tcp4'), (9050, 'tcp6'), (9040, 'tcp4'),
|
|
(9040, 'tcp6'), (9053, 'udp4'), (9053, 'udp6')])
|
|
self.add(daemon)
|
|
|
|
users_and_groups = UsersAndGroups('users-and-groups-torproxy',
|
|
reserved_usernames=['debian-tor'])
|
|
self.add(users_and_groups)
|
|
|
|
backup_restore = BackupRestore('backup-restore-torproxy',
|
|
**manifest.backup)
|
|
self.add(backup_restore)
|
|
|
|
def enable(self):
|
|
"""Enable app."""
|
|
service_privileged.unmask('tor@fbxproxy')
|
|
super().enable()
|
|
|
|
def disable(self):
|
|
"""Disable APT use of Tor before disabling."""
|
|
privileged.configure(apt_transport_tor=False)
|
|
super().disable()
|
|
service_privileged.mask('tor@fbxproxy')
|
|
|
|
def diagnose(self) -> list[DiagnosticCheck]:
|
|
"""Run diagnostics and return the results."""
|
|
results = super().diagnose()
|
|
results.append(_diagnose_url_via_tor('http://www.debian.org', '4'))
|
|
results.append(_diagnose_url_via_tor('http://www.debian.org', '6'))
|
|
results.append(_diagnose_tor_use('https://check.torproject.org', '4'))
|
|
results.append(_diagnose_tor_use('https://check.torproject.org', '6'))
|
|
return results
|
|
|
|
def setup(self, old_version):
|
|
"""Install and configure the app."""
|
|
super().setup(old_version)
|
|
privileged.setup()
|
|
if not old_version:
|
|
logger.info('Enabling apt-transport-tor')
|
|
config = kvstore.get_default(PREINSTALL_CONFIG_KEY, '{}')
|
|
config = json.loads(config)
|
|
config = {
|
|
'use_upstream_bridges': config.get('use_upstream_bridges'),
|
|
'upstream_bridges': config.get('upstream_bridges'),
|
|
'apt_transport_tor': config.get('apt_transport_tor', True),
|
|
}
|
|
privileged.configure(**config)
|
|
logger.info('Enabling Tor Proxy app')
|
|
self.enable()
|
|
kvstore.delete(PREINSTALL_CONFIG_KEY, ignore_missing=True)
|
|
|
|
# Version 2 masks the tor@fbproxy service when disabling the app to
|
|
# prevent the service starting by the Tor master service after system
|
|
# reboot.
|
|
if old_version and old_version < 2 and not self.is_enabled():
|
|
self.disable()
|
|
|
|
def uninstall(self):
|
|
"""De-configure and uninstall the app."""
|
|
super().uninstall()
|
|
privileged.uninstall()
|
|
|
|
|
|
def _diagnose_url_via_tor(url: str,
|
|
kind: str | None = None) -> DiagnosticCheck:
|
|
"""Diagnose whether a URL is reachable via Tor."""
|
|
result = diagnose_url(url, kind=kind, wrapper='torsocks')
|
|
result.check_id = 'torproxy-url'
|
|
result.description = gettext_noop('Access URL {url} on tcp{kind} via Tor')
|
|
|
|
return result
|
|
|
|
|
|
def _diagnose_tor_use(url: str, kind: str | None = None) -> DiagnosticCheck:
|
|
"""Diagnose whether webpage at URL reports that we are using Tor."""
|
|
expected_output = 'Congratulations. This browser is configured to use Tor.'
|
|
result = diagnose_url(url, kind=kind, wrapper='torsocks',
|
|
expected_output=expected_output)
|
|
result.check_id = 'torproxy-using-tor'
|
|
result.description = gettext_noop(
|
|
'Confirm Tor usage at {url} on tcp{kind}')
|
|
|
|
return result
|