mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-03-11 09:04:54 +00:00
tor: Use privileged decorator for actions
- Fixed issue with restarting start when apt transport is updated
Tests:
- Functional tests work
- Initial setup works
- 'plinth' instance is created
- Enabling works
- Firewall ports are updated.
- Disabling works
- Apt transport over Tor is disabled
- Diagnostics work
- Shows all ports for Tor
- Updating configuration works
- Correct value is set in configuration file
- App page shows correct status
- Setting/unsetting each of relay, bridge relay, bridges, hidden service, apt
transport all work.
Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
317e83c38f
commit
a78480c033
@ -1,13 +1,9 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
FreedomBox app to configure Tor.
|
||||
"""
|
||||
|
||||
import json
|
||||
"""FreedomBox app to configure Tor."""
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from plinth import action_utils, actions
|
||||
from plinth import action_utils
|
||||
from plinth import app as app_module
|
||||
from plinth import cfg, menu
|
||||
from plinth.daemon import (Daemon, app_is_running, diagnose_netcat,
|
||||
@ -21,7 +17,7 @@ from plinth.package import Packages
|
||||
from plinth.signals import domain_added, domain_removed
|
||||
from plinth.utils import format_lazy
|
||||
|
||||
from . import manifest, utils
|
||||
from . import manifest, privileged, utils
|
||||
|
||||
_description = [
|
||||
_('Tor is an anonymous communication system. You can learn more '
|
||||
@ -109,13 +105,12 @@ class TorApp(app_module.App):
|
||||
def enable(self):
|
||||
"""Enable the app and update firewall ports."""
|
||||
super().enable()
|
||||
actions.superuser_run('tor', ['update-ports'])
|
||||
privileged.update_ports()
|
||||
update_hidden_service_domain()
|
||||
|
||||
def disable(self):
|
||||
"""Disable APT use of Tor before disabling."""
|
||||
actions.superuser_run('tor',
|
||||
['configure', '--apt-transport-tor', 'disable'])
|
||||
privileged.configure(apt_transport_tor=False)
|
||||
super().disable()
|
||||
update_hidden_service_domain()
|
||||
|
||||
@ -125,8 +120,8 @@ class TorApp(app_module.App):
|
||||
|
||||
results.extend(_diagnose_control_port())
|
||||
|
||||
output = actions.superuser_run('tor', ['get-status'])
|
||||
ports = json.loads(output)['ports']
|
||||
status = privileged.get_status()
|
||||
ports = status['ports']
|
||||
|
||||
results.append([
|
||||
_('Tor relay port available'),
|
||||
@ -169,12 +164,9 @@ class TorApp(app_module.App):
|
||||
def setup(self, old_version):
|
||||
"""Install and configure the app."""
|
||||
super().setup(old_version)
|
||||
actions.superuser_run('tor',
|
||||
['setup', '--old-version',
|
||||
str(old_version)])
|
||||
privileged.setup(old_version)
|
||||
if not old_version:
|
||||
actions.superuser_run(
|
||||
'tor', ['configure', '--apt-transport-tor', 'enable'])
|
||||
privileged.configure(apt_transport_tor=True)
|
||||
|
||||
update_hidden_service_domain()
|
||||
self.enable()
|
||||
|
||||
158
actions/tor → plinth/modules/tor/privileged.py
Executable file → Normal file
158
actions/tor → plinth/modules/tor/privileged.py
Executable file → Normal file
@ -1,21 +1,18 @@
|
||||
#!/usr/bin/python3
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
Configuration helper for the Tor service
|
||||
"""
|
||||
"""Configure Tor service."""
|
||||
|
||||
import argparse
|
||||
import codecs
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
import augeas
|
||||
|
||||
from plinth import action_utils
|
||||
from plinth.actions import privileged
|
||||
from plinth.modules.tor.utils import (APT_TOR_PREFIX, get_augeas,
|
||||
get_real_apt_uri_path, iter_apt_uris)
|
||||
|
||||
@ -25,48 +22,10 @@ TOR_STATE_FILE = '/var/lib/tor-instances/plinth/state'
|
||||
TOR_AUTH_COOKIE = '/var/run/tor-instances/plinth/control.authcookie'
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
"""Return parsed command line arguments as dictionary"""
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
||||
|
||||
setup_parser = subparsers.add_parser('setup',
|
||||
help='Setup Tor configuration')
|
||||
setup_parser.add_argument(
|
||||
'--old-version', type=int, required=True,
|
||||
help='Version number being upgraded from or None if setting up first '
|
||||
'time.')
|
||||
|
||||
subparsers.add_parser('get-status', help='Get Tor status in JSON format')
|
||||
|
||||
configure = subparsers.add_parser('configure', help='Configure Tor')
|
||||
configure.add_argument('--relay', choices=['enable', 'disable'],
|
||||
help='Configure relay')
|
||||
configure.add_argument('--bridge-relay', choices=['enable', 'disable'],
|
||||
help='Configure bridge relay')
|
||||
configure.add_argument('--hidden-service', choices=['enable', 'disable'],
|
||||
help='Configure hidden service')
|
||||
configure.add_argument('--apt-transport-tor',
|
||||
choices=['enable', 'disable'],
|
||||
help='Configure package download over Tor')
|
||||
configure.add_argument('--use-upstream-bridges',
|
||||
choices=['enable', 'disable'],
|
||||
help='Configure use of upstream bridges')
|
||||
configure.add_argument('--upstream-bridges',
|
||||
help='Set list of upstream bridges to use')
|
||||
|
||||
subparsers.add_parser('update-ports',
|
||||
help='Update firewall ports based on what Tor uses')
|
||||
|
||||
subparsers.add_parser('restart', help='Restart Tor')
|
||||
|
||||
subparsers.required = True
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def subcommand_setup(arguments):
|
||||
@privileged
|
||||
def setup(old_version: int):
|
||||
"""Setup Tor configuration after installing it."""
|
||||
if arguments.old_version and arguments.old_version <= 4:
|
||||
if old_version and old_version <= 4:
|
||||
_upgrade_orport_value()
|
||||
return
|
||||
|
||||
@ -98,7 +57,7 @@ def _first_time_setup():
|
||||
aug.set(TOR_CONFIG + '/SocksPort[1]', '[::]:9050')
|
||||
aug.set(TOR_CONFIG + '/SocksPort[2]', '0.0.0.0:9050')
|
||||
aug.set(TOR_CONFIG + '/ControlPort', '9051')
|
||||
_enable_relay(relay='enable', bridge='enable', aug=aug)
|
||||
_enable_relay(relay=True, bridge=True, aug=aug)
|
||||
aug.set(TOR_CONFIG + '/ExitPolicy[1]', 'reject *:*')
|
||||
aug.set(TOR_CONFIG + '/ExitPolicy[2]', 'reject6 *:*')
|
||||
|
||||
@ -163,43 +122,46 @@ def _upgrade_orport_value():
|
||||
action_utils.service_restart('firewalld')
|
||||
|
||||
|
||||
def subcommand_get_status(_):
|
||||
"""Get Tor status in JSON format."""
|
||||
print(json.dumps(get_status()))
|
||||
|
||||
|
||||
def subcommand_configure(arguments):
|
||||
@privileged
|
||||
def configure(use_upstream_bridges: Optional[bool] = None,
|
||||
upstream_bridges: Optional[str] = None,
|
||||
relay: Optional[bool] = None,
|
||||
bridge_relay: Optional[bool] = None,
|
||||
hidden_service: Optional[bool] = None,
|
||||
apt_transport_tor: Optional[bool] = None):
|
||||
"""Configure Tor."""
|
||||
aug = augeas_load()
|
||||
|
||||
_use_upstream_bridges(arguments.use_upstream_bridges, aug=aug)
|
||||
_use_upstream_bridges(use_upstream_bridges, aug=aug)
|
||||
|
||||
if arguments.use_upstream_bridges == 'enable':
|
||||
arguments.relay = 'disable'
|
||||
arguments.bridge_relay = 'disable'
|
||||
if use_upstream_bridges:
|
||||
relay = False
|
||||
bridge_relay = False
|
||||
|
||||
if arguments.upstream_bridges:
|
||||
_set_upstream_bridges(arguments.upstream_bridges, aug=aug)
|
||||
if upstream_bridges:
|
||||
_set_upstream_bridges(upstream_bridges, aug=aug)
|
||||
|
||||
_enable_relay(arguments.relay, arguments.bridge_relay, aug=aug)
|
||||
_enable_relay(relay, bridge_relay, aug=aug)
|
||||
|
||||
if arguments.hidden_service == 'enable':
|
||||
if hidden_service:
|
||||
_enable_hs(aug=aug)
|
||||
elif arguments.hidden_service == 'disable':
|
||||
elif hidden_service is not None:
|
||||
_disable_hs(aug=aug)
|
||||
|
||||
if arguments.apt_transport_tor == 'enable':
|
||||
if apt_transport_tor:
|
||||
_enable_apt_transport_tor()
|
||||
elif arguments.apt_transport_tor == 'disable':
|
||||
elif apt_transport_tor is not None:
|
||||
_disable_apt_transport_tor()
|
||||
|
||||
|
||||
def subcommand_update_ports(_):
|
||||
@privileged
|
||||
def update_ports():
|
||||
"""Update firewall ports based on what Tor uses."""
|
||||
_update_ports()
|
||||
|
||||
|
||||
def subcommand_restart(_):
|
||||
@privileged
|
||||
def restart():
|
||||
"""Restart Tor."""
|
||||
if (action_utils.service_is_enabled('tor@plinth', strict_check=True)
|
||||
and action_utils.service_is_running('tor@plinth')):
|
||||
@ -216,7 +178,8 @@ def subcommand_restart(_):
|
||||
time.sleep(10)
|
||||
|
||||
|
||||
def get_status():
|
||||
@privileged
|
||||
def get_status() -> dict[str, Union[bool, str, dict[str, Any]]]:
|
||||
"""Return dict with Tor status."""
|
||||
aug = augeas_load()
|
||||
return {
|
||||
@ -229,32 +192,32 @@ def get_status():
|
||||
}
|
||||
|
||||
|
||||
def _are_upstream_bridges_enabled(aug):
|
||||
def _are_upstream_bridges_enabled(aug) -> bool:
|
||||
"""Return whether upstream bridges are being used."""
|
||||
use_bridges = aug.get(TOR_CONFIG + '/UseBridges')
|
||||
return use_bridges == '1'
|
||||
|
||||
|
||||
def _get_upstream_bridges(aug):
|
||||
def _get_upstream_bridges(aug) -> str:
|
||||
"""Return upstream bridges separated by newlines."""
|
||||
matches = aug.match(TOR_CONFIG + '/Bridge')
|
||||
bridges = [aug.get(match) for match in matches]
|
||||
return '\n'.join(bridges)
|
||||
|
||||
|
||||
def _is_relay_enabled(aug):
|
||||
def _is_relay_enabled(aug) -> bool:
|
||||
"""Return whether a relay is enabled."""
|
||||
orport = aug.get(TOR_CONFIG + '/ORPort[1]')
|
||||
return bool(orport) and orport != '0'
|
||||
|
||||
|
||||
def _is_bridge_relay_enabled(aug):
|
||||
def _is_bridge_relay_enabled(aug) -> bool:
|
||||
"""Return whether bridge relay is enabled."""
|
||||
bridge = aug.get(TOR_CONFIG + '/BridgeRelay')
|
||||
return bridge == '1'
|
||||
|
||||
|
||||
def _get_ports():
|
||||
def _get_ports() -> dict[str, str]:
|
||||
"""Return dict mapping port names to numbers."""
|
||||
ports = {}
|
||||
try:
|
||||
@ -275,7 +238,7 @@ def _get_ports():
|
||||
return ports
|
||||
|
||||
|
||||
def _get_orport():
|
||||
def _get_orport() -> str:
|
||||
"""Return the ORPort by querying running instance."""
|
||||
cookie = open(TOR_AUTH_COOKIE, 'rb').read()
|
||||
cookie = codecs.encode(cookie, 'hex').decode()
|
||||
@ -296,8 +259,8 @@ QUIT
|
||||
return matches.group(1)
|
||||
|
||||
|
||||
def _get_hidden_service(aug=None):
|
||||
"""Return a string with configured Tor hidden service information"""
|
||||
def _get_hidden_service(aug=None) -> dict[str, Any]:
|
||||
"""Return a string with configured Tor hidden service information."""
|
||||
hs_enabled = False
|
||||
hs_status = 'Ok'
|
||||
hs_hostname = None
|
||||
@ -344,7 +307,8 @@ def _disable():
|
||||
action_utils.service_disable('tor@plinth')
|
||||
|
||||
|
||||
def _use_upstream_bridges(use_upstream_bridges=None, aug=None):
|
||||
def _use_upstream_bridges(use_upstream_bridges: Optional[bool] = None,
|
||||
aug=None):
|
||||
"""Enable use of upstream bridges."""
|
||||
if use_upstream_bridges is None:
|
||||
return
|
||||
@ -352,9 +316,9 @@ def _use_upstream_bridges(use_upstream_bridges=None, aug=None):
|
||||
if not aug:
|
||||
aug = augeas_load()
|
||||
|
||||
if use_upstream_bridges == 'enable':
|
||||
if use_upstream_bridges:
|
||||
aug.set(TOR_CONFIG + '/UseBridges', '1')
|
||||
elif use_upstream_bridges == 'disable':
|
||||
else:
|
||||
aug.set(TOR_CONFIG + '/UseBridges', '0')
|
||||
|
||||
aug.save()
|
||||
@ -383,7 +347,8 @@ def _set_upstream_bridges(upstream_bridges=None, aug=None):
|
||||
aug.save()
|
||||
|
||||
|
||||
def _enable_relay(relay=None, bridge=None, aug=None):
|
||||
def _enable_relay(relay: Optional[bool], bridge: Optional[bool],
|
||||
aug: augeas.Augeas):
|
||||
"""Enable Tor bridge relay."""
|
||||
if relay is None and bridge is None:
|
||||
return
|
||||
@ -393,18 +358,18 @@ def _enable_relay(relay=None, bridge=None, aug=None):
|
||||
|
||||
use_upstream_bridges = _are_upstream_bridges_enabled(aug)
|
||||
|
||||
if relay == 'enable' and not use_upstream_bridges:
|
||||
if relay and not use_upstream_bridges:
|
||||
aug.set(TOR_CONFIG + '/ORPort[1]', '9001')
|
||||
aug.set(TOR_CONFIG + '/ORPort[2]', '[::]:9001')
|
||||
elif relay == 'disable':
|
||||
elif relay is not None:
|
||||
aug.remove(TOR_CONFIG + '/ORPort')
|
||||
|
||||
if bridge == 'enable' and not use_upstream_bridges:
|
||||
if bridge and not use_upstream_bridges:
|
||||
aug.set(TOR_CONFIG + '/BridgeRelay', '1')
|
||||
aug.set(TOR_CONFIG + '/ServerTransportPlugin',
|
||||
'obfs3,obfs4 exec /usr/bin/obfs4proxy')
|
||||
aug.set(TOR_CONFIG + '/ExtORPort', 'auto')
|
||||
elif bridge == 'disable':
|
||||
elif bridge is not None:
|
||||
aug.remove(TOR_CONFIG + '/BridgeRelay')
|
||||
aug.remove(TOR_CONFIG + '/ServerTransportPlugin')
|
||||
aug.remove(TOR_CONFIG + '/ExtORPort')
|
||||
@ -413,7 +378,7 @@ def _enable_relay(relay=None, bridge=None, aug=None):
|
||||
|
||||
|
||||
def _enable_hs(aug=None):
|
||||
"""Enable Tor hidden service"""
|
||||
"""Enable Tor hidden service."""
|
||||
if not aug:
|
||||
aug = augeas_load()
|
||||
|
||||
@ -429,7 +394,7 @@ def _enable_hs(aug=None):
|
||||
|
||||
|
||||
def _disable_hs(aug=None):
|
||||
"""Disable Tor hidden service"""
|
||||
"""Disable Tor hidden service."""
|
||||
if not aug:
|
||||
aug = augeas_load()
|
||||
|
||||
@ -443,13 +408,7 @@ def _disable_hs(aug=None):
|
||||
|
||||
def _enable_apt_transport_tor():
|
||||
"""Enable package download over Tor."""
|
||||
try:
|
||||
aug = get_augeas()
|
||||
except Exception:
|
||||
# If there was an error, don't proceed
|
||||
print('Error: Unable to understand sources format.')
|
||||
exit(1)
|
||||
|
||||
aug = get_augeas()
|
||||
for uri_path in iter_apt_uris(aug):
|
||||
uri_path = get_real_apt_uri_path(aug, uri_path)
|
||||
uri = aug.get(uri_path)
|
||||
@ -527,16 +486,3 @@ def augeas_load():
|
||||
'/etc/tor/instances/plinth/torrc')
|
||||
aug.load()
|
||||
return aug
|
||||
|
||||
|
||||
def main():
|
||||
"""Parse arguments and perform all duties"""
|
||||
arguments = parse_arguments()
|
||||
|
||||
subcommand = arguments.subcommand.replace('-', '_')
|
||||
subcommand_method = globals()['subcommand_' + subcommand]
|
||||
subcommand_method(arguments)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -1,19 +1,17 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
Tor utility functions
|
||||
"""
|
||||
"""Tor utility functions."""
|
||||
|
||||
import glob
|
||||
import itertools
|
||||
import json
|
||||
|
||||
import augeas
|
||||
|
||||
from plinth import actions
|
||||
from plinth import app as app_module
|
||||
from plinth.daemon import app_is_running
|
||||
from plinth.modules.names.components import DomainName
|
||||
|
||||
from . import privileged
|
||||
|
||||
APT_SOURCES_URI_PATHS = ('/files/etc/apt/sources.list/*/uri',
|
||||
'/files/etc/apt/sources.list.d/*/*/uri')
|
||||
APT_TOR_PREFIX = 'tor+'
|
||||
@ -21,8 +19,7 @@ APT_TOR_PREFIX = 'tor+'
|
||||
|
||||
def get_status(initialized=True):
|
||||
"""Return current Tor status."""
|
||||
output = actions.superuser_run('tor', ['get-status'])
|
||||
status = json.loads(output)
|
||||
status = privileged.get_status()
|
||||
|
||||
hs_info = status['hidden_service']
|
||||
hs_services = []
|
||||
|
||||
@ -1,19 +1,17 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
FreedomBox app for configuring Tor.
|
||||
"""
|
||||
"""FreedomBox app for configuring Tor."""
|
||||
|
||||
import logging
|
||||
|
||||
from django.utils.translation import gettext_noop
|
||||
from django.views.generic.edit import FormView
|
||||
|
||||
from plinth import actions
|
||||
from plinth import app as app_module
|
||||
from plinth import operation as operation_module
|
||||
from plinth.modules import tor
|
||||
from plinth.views import AppView
|
||||
|
||||
from . import privileged
|
||||
from . import utils as tor_utils
|
||||
from .forms import TorForm
|
||||
|
||||
@ -81,49 +79,43 @@ def _apply_changes(old_status, new_status):
|
||||
|
||||
def __apply_changes(old_status, new_status):
|
||||
"""Apply the changes."""
|
||||
needs_restart = True
|
||||
arguments = []
|
||||
needs_restart = False
|
||||
arguments = {}
|
||||
|
||||
app = app_module.App.get('tor')
|
||||
is_enabled = app.is_enabled()
|
||||
|
||||
if old_status['relay_enabled'] != new_status['relay_enabled']:
|
||||
arg_value = 'enable' if new_status['relay_enabled'] else 'disable'
|
||||
arguments.extend(['--relay', arg_value])
|
||||
arguments['relay'] = new_status['relay_enabled']
|
||||
needs_restart = True
|
||||
|
||||
if old_status['bridge_relay_enabled'] != \
|
||||
new_status['bridge_relay_enabled']:
|
||||
arg_value = 'enable'
|
||||
if not new_status['bridge_relay_enabled']:
|
||||
arg_value = 'disable'
|
||||
arguments.extend(['--bridge-relay', arg_value])
|
||||
arguments['bridge_relay'] = new_status['bridge_relay_enabled']
|
||||
needs_restart = True
|
||||
|
||||
if old_status['hs_enabled'] != new_status['hs_enabled']:
|
||||
arg_value = 'enable' if new_status['hs_enabled'] else 'disable'
|
||||
arguments.extend(['--hidden-service', arg_value])
|
||||
arguments['hidden_service'] = new_status['hs_enabled']
|
||||
needs_restart = True
|
||||
|
||||
if old_status['apt_transport_tor_enabled'] != \
|
||||
new_status['apt_transport_tor_enabled']:
|
||||
arg_value = 'disable'
|
||||
if is_enabled and new_status['apt_transport_tor_enabled']:
|
||||
arg_value = 'enable'
|
||||
arguments.extend(['--apt-transport-tor', arg_value])
|
||||
needs_restart = False
|
||||
arguments['apt_transport_tor'] = (
|
||||
is_enabled and new_status['apt_transport_tor_enabled'])
|
||||
|
||||
if old_status['use_upstream_bridges'] != \
|
||||
new_status['use_upstream_bridges']:
|
||||
arg_value = 'enable' if new_status[
|
||||
'use_upstream_bridges'] else 'disable'
|
||||
arguments.extend(['--use-upstream-bridges', arg_value])
|
||||
arguments['use_upstream_bridges'] = new_status['use_upstream_bridges']
|
||||
needs_restart = True
|
||||
|
||||
if old_status['upstream_bridges'] != new_status['upstream_bridges']:
|
||||
arguments.extend(
|
||||
['--upstream-bridges', new_status['upstream_bridges']])
|
||||
arguments['upstream_bridges'] = new_status['upstream_bridges']
|
||||
needs_restart = True
|
||||
|
||||
if arguments:
|
||||
actions.superuser_run('tor', ['configure'] + arguments)
|
||||
privileged.configure(**arguments)
|
||||
|
||||
if needs_restart and is_enabled:
|
||||
actions.superuser_run('tor', ['restart'])
|
||||
privileged.restart()
|
||||
status = tor_utils.get_status()
|
||||
tor.update_hidden_service_domain(status)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user