tor: Use augeas to modify configuration.

- Use json to get info from action script.
- Add SSH port to hidden service.
- Enable hidden service during setup.
This commit is contained in:
James Valleroy 2016-01-02 17:14:33 -05:00
parent cd82188efd
commit a6417c59a7
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
7 changed files with 200 additions and 152 deletions

View File

@ -22,7 +22,9 @@ Configuration helper for the Tor service
""" """
import argparse import argparse
import augeas
import codecs import codecs
import json
import os import os
import re import re
import socket import socket
@ -33,7 +35,7 @@ from plinth.modules.tor import is_enabled, is_running, get_augeas, \
get_real_apt_uri_path, iter_apt_uris, APT_TOR_PREFIX get_real_apt_uri_path, iter_apt_uris, APT_TOR_PREFIX
SERVICE_FILE = '/etc/firewalld/services/tor-{0}.xml' SERVICE_FILE = '/etc/firewalld/services/tor-{0}.xml'
TOR_CONFIG = '/etc/tor/torrc' TOR_CONFIG = '/files/etc/tor/torrc'
TOR_STATE_FILE = '/var/lib/tor/state' TOR_STATE_FILE = '/var/lib/tor/state'
TOR_AUTH_COOKIE = '/var/run/tor/control.authcookie' TOR_AUTH_COOKIE = '/var/run/tor/control.authcookie'
@ -44,8 +46,10 @@ def parse_arguments():
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command') subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
subparsers.add_parser('setup', help='Setup Tor configuration') subparsers.add_parser('setup', help='Setup Tor configuration')
subparsers.add_parser('get-ports', help='Get list of Tor ports') subparsers.add_parser('get-ports',
subparsers.add_parser('get-hs', help='Get hidden service') help='Get Tor ports in JSON format')
subparsers.add_parser('get-hs',
help='Get hidden service information in JSON format')
configure = subparsers.add_parser('configure', help='Configure Tor') configure = subparsers.add_parser('configure', help='Configure Tor')
configure.add_argument('--service', choices=['enable', 'disable'], configure.add_argument('--service', choices=['enable', 'disable'],
@ -61,50 +65,63 @@ def parse_arguments():
def subcommand_setup(_): def subcommand_setup(_):
"""Setup Tor configuration after installing it.""" """Setup Tor configuration after installing it."""
# XXX: Performing this as a post-install step instead of aug = augeas_load()
# pre-install setup for now. Creating a configuration before hand
# leads dpkg to ask question about configuration overwrite which
# makes aptcc backend of packagekit to wait forever even with
# interactive=False.
lines = """
# Run as non-exit bridge relay
SocksPort [::]:9050
SocksPort 0.0.0.0:9050
ORPort auto
ControlPort 9051
BridgeRelay 1
Exitpolicy reject *:*
Exitpolicy reject6 *:*
# Enable obfsproxy aug.set(TOR_CONFIG + '/#comment[last() + 1]',
ServerTransportPlugin obfs3,obfs4 exec /usr/bin/obfs4proxy 'Run as non-exit bridge relay')
ExtORPort auto aug.set(TOR_CONFIG + '/SocksPort[1]', '[::]:9050')
aug.set(TOR_CONFIG + '/SocksPort[2]', '0.0.0.0:9050')
aug.set(TOR_CONFIG + '/ORPort', 'auto')
aug.set(TOR_CONFIG + '/ControlPort', '9051')
aug.set(TOR_CONFIG + '/BridgeRelay', '1')
aug.set(TOR_CONFIG + '/ExitPolicy[1]', 'reject *:*')
aug.set(TOR_CONFIG + '/ExitPolicy[2]', 'reject6 *:*')
# Enable transparent proxy aug.set(TOR_CONFIG + '/#comment[last() + 1]', 'Enable obfsproxy')
VirtualAddrNetworkIPv4 10.192.0.0/10 aug.set(TOR_CONFIG + '/ServerTransportPlugin',
AutomapHostsOnResolve 1 'obfs3,obfs4 exec /usr/bin/obfs4proxy')
TransPort 127.0.0.1:9040 aug.set(TOR_CONFIG + '/ExtORPort', 'auto')
TransPort [::1]:9040
DNSPort 127.0.0.1:9053 aug.set(TOR_CONFIG + '/#comment[last() + 1]', 'Enable transparent proxy')
DNSPort [::1]:9053 aug.set(TOR_CONFIG + '/VirtualAddrNetworkIPv4', '10.192.0.0/10')
""" aug.set(TOR_CONFIG + '/AutomapHostsOnResolve', '1')
with open(TOR_CONFIG, 'w') as conffile: aug.set(TOR_CONFIG + '/TransPort[1]', '127.0.0.1:9040')
conffile.writelines(lines) aug.set(TOR_CONFIG + '/TransPort[2]', '[::1]:9040')
aug.set(TOR_CONFIG + '/DNSPort[1]', '127.0.0.1:9053')
aug.set(TOR_CONFIG + '/DNSPort[2]', '[::1]:9053')
aug.set(TOR_CONFIG + '/HiddenServiceDir',
'/var/lib/tor/hidden_service')
aug.set(TOR_CONFIG + '/HiddenServicePort[1]',
'22 127.0.0.1:22')
aug.set(TOR_CONFIG + '/HiddenServicePort[2]',
'80 127.0.0.1:80')
aug.set(TOR_CONFIG + '/HiddenServicePort[3]',
'443 127.0.0.1:443')
aug.save()
action_utils.service_restart('tor') action_utils.service_restart('tor')
_update_ports() _update_ports()
# wait until hidden service information is available
tries = 0
while not get_hidden_service()['enabled']:
tries += 1
if tries >= 12:
return
time.sleep(10)
def subcommand_get_ports(_): def subcommand_get_ports(_):
"""Get list of Tor ports.""" """Get Tor ports in JSON format."""
ports = get_ports() print(json.dumps(get_ports()))
for name, number in ports.items():
print(name, number)
def subcommand_get_hs(_): def subcommand_get_hs(_):
"""Print currently configured Tor hidden service information""" """Get hidden service information in JSON format."""
print(get_hidden_service()) print(json.dumps(get_hidden_service()))
def subcommand_configure(arguments): def subcommand_configure(arguments):
@ -171,29 +188,32 @@ QUIT
def get_hidden_service(): def get_hidden_service():
"""Return a string with configured Tor hidden service information""" """Return a string with configured Tor hidden service information"""
hs_dir = None hs_enabled = False
hs_status = 'Ok'
hs_hostname = None
hs_ports = [] hs_ports = []
try: aug = augeas_load()
with open(TOR_CONFIG, 'r') as conf_file: hs_dir = aug.get(TOR_CONFIG + '/HiddenServiceDir')
for line in conf_file: hs_port_paths = aug.match(TOR_CONFIG + '/HiddenServicePort')
if line.startswith('HiddenServiceDir'):
hs_dir = line.split()[1] for hs_port_path in hs_port_paths:
elif line.startswith('HiddenServicePort'): port_info = aug.get(hs_port_path).split()
hs_ports.append(line.split()[1]) hs_ports.append({'virtport': port_info[0],
except FileNotFoundError: 'target': port_info[1]})
return 'error'
if not hs_dir: if not hs_dir:
return '' hs_status = 'Not Configured'
else:
try:
with open(os.path.join(hs_dir, 'hostname'), 'r') as conf_file:
hs_hostname = conf_file.read().strip()
hs_enabled = True
except Exception:
hs_status = 'Not available (Run Tor at least once)'
try: return {'enabled': hs_enabled, 'status': hs_status,
with open(os.path.join(hs_dir, 'hostname'), 'r') as conf_file: 'hostname': hs_hostname, 'ports': hs_ports}
hs_hostname = conf_file.read().strip()
except Exception:
return 'error'
return hs_hostname + ' ' + ','.join(hs_ports)
def _enable(): def _enable():
@ -210,20 +230,19 @@ def _disable():
def _enable_hs(restart=True): def _enable_hs(restart=True):
"""Enable Tor hidden service""" """Enable Tor hidden service"""
if get_hidden_service(): if get_hidden_service()['enabled']:
return return
with open(TOR_CONFIG, 'r') as conffile: aug = augeas_load()
lines = conffile.readlines() aug.set(TOR_CONFIG + '/HiddenServiceDir',
'/var/lib/tor/hidden_service')
lines.append('# Hidden Service configured by Plinth\n') aug.set(TOR_CONFIG + '/HiddenServicePort[1]',
lines.append('HiddenServiceDir /var/lib/tor/hidden_service/\n') '22 127.0.0.1:22')
lines.append('HiddenServicePort 80 127.0.0.1:80\n') aug.set(TOR_CONFIG + '/HiddenServicePort[2]',
lines.append('HiddenServicePort 443 127.0.0.1:443\n') '80 127.0.0.1:80')
lines.append('# end of Plinth Hidden Service config\n') aug.set(TOR_CONFIG + '/HiddenServicePort[3]',
'443 127.0.0.1:443')
with open(TOR_CONFIG, 'w') as conffile: aug.save()
conffile.writelines(lines)
if restart: if restart:
if is_enabled() and is_running(): if is_enabled() and is_running():
@ -231,7 +250,7 @@ def _enable_hs(restart=True):
# wait until hidden service information is available # wait until hidden service information is available
tries = 0 tries = 0
while get_hidden_service() in ('', 'error'): while not get_hidden_service()['enabled']:
tries += 1 tries += 1
if tries >= 12: if tries >= 12:
return return
@ -241,35 +260,13 @@ def _enable_hs(restart=True):
def _disable_hs(restart=True): def _disable_hs(restart=True):
"""Disable Tor hidden service""" """Disable Tor hidden service"""
if not get_hidden_service(): if not get_hidden_service()['enabled']:
return return
with open(TOR_CONFIG, 'r') as conffile: aug = augeas_load()
lines = conffile.readlines() aug.remove(TOR_CONFIG + '/HiddenServiceDir')
aug.remove(TOR_CONFIG + '/HiddenServicePort')
filtered_lines = [] aug.save()
removing = False
for line in lines:
if removing:
if line.startswith('# end of Plinth Hidden Service config'):
# last line of Plinth hidden service block
# stop removing after this line
removing = False
elif not line.startswith('HiddenService'):
# end of Plinth hidden service block
# stop removing lines
removing = False
filtered_lines.append(line)
else:
if line.startswith('# Hidden Service configured by Plinth'):
# start of Plinth hidden service block
# remove following HiddenService lines
removing = True
else:
filtered_lines.append(line)
with open(TOR_CONFIG, 'w') as conffile:
conffile.writelines(filtered_lines)
if restart: if restart:
if is_enabled() and is_running(): if is_enabled() and is_running():
@ -347,6 +344,16 @@ def _update_ports():
action_utils.service_restart('firewalld') action_utils.service_restart('firewalld')
def augeas_load():
"""Initialize Augeas."""
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
augeas.Augeas.NO_MODL_AUTOLOAD)
aug.set('/augeas/load/Tor/lens', 'Tor.lns')
aug.set('/augeas/load/Tor/incl[last() + 1]', '/etc/tor/torrc')
aug.load()
return aug
def main(): def main():
"""Parse arguments and perform all duties""" """Parse arguments and perform all duties"""
arguments = parse_arguments() arguments = parse_arguments()

View File

@ -0,0 +1,11 @@
module Test_Tor =
test Tor.lns get "\n" = { }
test Tor.lns get "# comment\n" = { "#comment" = "comment" }
test Tor.lns get "SocksPort 9050\n" = { "SocksPort" = "9050" }
test Tor.lns get "SocksPort 0.0.0.0:9050\n" = { "SocksPort" = "0.0.0.0:9050" }
test Tor.lns get "SocksPort [::]:9050\n" = { "SocksPort" = "[::]:9050" }
test Tor.lns get "ExitPolicy reject *:*\n" = { "ExitPolicy" = "reject *:*" }
test Tor.lns get "VirtualAddrNetworkIPv4 10.192.0.0/10\n" = { "VirtualAddrNetworkIPv4" = "10.192.0.0/10" }
test Tor.lns get "ServerTransportPlugin obfs3,obfs4 exec /usr/bin/obfs4proxy\n" = { "ServerTransportPlugin" = "obfs3,obfs4 exec /usr/bin/obfs4proxy" }
test Tor.lns get "HiddenServiceDir /var/lib/tor/hidden_service/\n" = { "HiddenServiceDir" = "/var/lib/tor/hidden_service/" }

View File

@ -0,0 +1,36 @@
(* Tor lens for Augeas
Author: James Valleroy <jvalleroy@mailbox.org>
About: Reference
Online Tor configuration manual:
https://www.torproject.org/docs/tor-manual.html.en
About: License
This file is licensed under the LGPL v2+.
About: Configuration files
This lens applies to /etc/tor/torrc. See <filter>.
*)
module Tor =
autoload xfm
let eol = Util.eol
let ws = /[ \t]/
let kc = /[A-Za-z0-9_.,:*]/
let vc = /[A-Za-z0-9_.,:*\/ ]/
let keyname = kc+
let val = /[[\/]*/ . kc . (vc* . /[]]*/ . vc* . kc . /[\/]*/)?
let entry = [ key keyname . del ws+ " " . store val . eol ]
let lns = (entry|Util.comment|Util.empty_dos)*
let filter = (incl "/etc/tor/torrc")
let xfm = transform lns filter

View File

@ -23,13 +23,14 @@ import augeas
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
import glob import glob
import itertools import itertools
import json
from plinth import actions from plinth import actions
from plinth import action_utils from plinth import action_utils
from plinth import cfg from plinth import cfg
from plinth import service as service_module from plinth import service as service_module
from plinth.modules.names import SERVICES from plinth.modules.names import SERVICES
from plinth.signals import domain_added from plinth.signals import domain_added, domain_removed
version = 1 version = 1
@ -72,20 +73,23 @@ def init():
is_external=True, enabled=is_enabled()) is_external=True, enabled=is_enabled())
# Register hidden service name with Name Services module. # Register hidden service name with Name Services module.
(hs_enabled, hs_hostname, hs_ports) = get_hs() hs_info = get_hs()
hostname = hs_info['hostname']
hs_virtports = [port['virtport'] for port in hs_info['ports']]
if is_enabled() and is_running() and hs_enabled and hs_hostname: if is_enabled() and is_running() and \
hs_info['enabled'] and hs_info['hostname']:
hs_services = [] hs_services = []
for service_type in SERVICES: for service_type in SERVICES:
if str(service_type[2]) in hs_ports: if str(service_type[2]) in hs_virtports:
hs_services.append(service_type[0]) hs_services.append(service_type[0])
else: else:
hs_hostname = None hostname = None
hs_services = None hs_services = None
domain_added.send_robust( domain_added.send_robust(
sender='tor', domain_type='hiddenservice', sender='tor', domain_type='hiddenservice',
name=hs_hostname, description=_('Tor Hidden Service'), name=hostname, description=_('Tor Hidden Service'),
services=hs_services) services=hs_services)
@ -98,6 +102,23 @@ def setup(helper, old_version=None):
['configure', '--apt-transport-tor', 'enable']) ['configure', '--apt-transport-tor', 'enable'])
helper.call('post', socks_service.notify_enabled, None, True) helper.call('post', socks_service.notify_enabled, None, True)
helper.call('post', bridge_service.notify_enabled, None, True) helper.call('post', bridge_service.notify_enabled, None, True)
helper.call('post', update_hidden_service_domain)
def update_hidden_service_domain(status=None):
"""Update HS domain with Name Services module."""
if not status:
status = get_status()
domain_removed.send_robust(
sender='tor', domain_type='hiddenservice')
if status['enabled'] and status['is_running'] and \
status['hs_enabled'] and status['hs_hostname']:
domain_added.send_robust(
sender='tor', domain_type='hiddenservice',
name=status['hs_hostname'], description=_('Tor Hidden Service'),
services=status['hs_services'])
def is_enabled(): def is_enabled():
@ -113,45 +134,30 @@ def is_running():
def get_status(): def get_status():
"""Return current Tor status.""" """Return current Tor status."""
output = actions.superuser_run('tor', ['get-ports']) output = actions.superuser_run('tor', ['get-ports'])
port_info = output.split('\n') ports = json.loads(output)
ports = {}
for line in port_info:
try:
(key, val) = line.split()
ports[key] = val
except ValueError:
continue
(hs_enabled, hs_hostname, hs_ports) = get_hs() hs_info = get_hs()
hs_services = []
hs_virtports = [port['virtport'] for port in hs_info['ports']]
for service_type in SERVICES:
if str(service_type[2]) in hs_virtports:
hs_services.append(service_type[0])
return {'enabled': is_enabled(), return {'enabled': is_enabled(),
'is_running': is_running(), 'is_running': is_running(),
'ports': ports, 'ports': ports,
'hs_enabled': hs_enabled, 'hs_enabled': hs_info['enabled'],
'hs_hostname': hs_hostname, 'hs_status': hs_info['status'],
'hs_ports': hs_ports, 'hs_hostname': hs_info['hostname'],
'hs_ports': hs_info['ports'],
'hs_services': hs_services,
'apt_transport_tor_enabled': is_apt_transport_tor_enabled()} 'apt_transport_tor_enabled': is_apt_transport_tor_enabled()}
def get_hs(): def get_hs():
"""Return hidden service status.""" """Return hidden service status."""
output = actions.superuser_run('tor', ['get-hs']) output = actions.superuser_run('tor', ['get-hs'])
output = output.strip() return json.loads(output)
if output == '':
hs_enabled = False
hs_hostname = 'Not Configured'
hs_ports = ''
elif output == 'error':
hs_enabled = False
hs_hostname = 'Not available (Run Tor at least once)'
hs_ports = ''
else:
hs_enabled = True
hs_info = output.split()
hs_hostname = hs_info[0]
hs_ports = hs_info[1]
return (hs_enabled, hs_hostname, hs_ports)
def get_augeas(): def get_augeas():
@ -239,8 +245,7 @@ def diagnose():
results.extend(_diagnose_control_port()) results.extend(_diagnose_control_port())
output = actions.superuser_run('tor', ['get-ports']) output = actions.superuser_run('tor', ['get-ports'])
ports = [line.split() for line in output.splitlines()] ports = json.loads(output)
ports = {port_type: int(port) for port_type, port in ports}
results.append([_('Tor relay port available'), results.append([_('Tor relay port available'),
'passed' if 'orport' in ports else 'failed']) 'passed' if 'orport' in ports else 'failed'])

View File

@ -62,13 +62,19 @@
<thead> <thead>
<tr> <tr>
<th>{% trans "Hidden Service" %}</th> <th>{% trans "Hidden Service" %}</th>
<th>{% trans "Port" %}</th> <th>{% trans "Status" %}</th>
<th>{% trans "Ports" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>{{ status.hs_hostname }}</td> <td>{{ status.hs_hostname }}</td>
<td>{{ status.hs_ports }}</td> <td>{{ status.hs_status }}</td>
<td>
{% for service in status.hs_services %}
{{ service }}
{% endfor %}
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -27,8 +27,6 @@ from .forms import TorForm
from plinth import actions from plinth import actions
from plinth.errors import ActionError from plinth.errors import ActionError
from plinth.modules import tor from plinth.modules import tor
from plinth.modules.names import SERVICES
from plinth.signals import domain_added, domain_removed
config_process = None config_process = None
@ -115,22 +113,7 @@ def _collect_config_result(request):
tor.socks_service.notify_enabled(None, status['enabled']) tor.socks_service.notify_enabled(None, status['enabled'])
tor.bridge_service.notify_enabled(None, status['enabled']) tor.bridge_service.notify_enabled(None, status['enabled'])
tor.update_hidden_service_domain(status)
# Update hidden service name registered with Name Services module.
domain_removed.send_robust(
sender='tor', domain_type='hiddenservice')
if status['enabled'] and status['is_running'] and \
status['hs_enabled'] and status['hs_hostname']:
hs_services = []
for service in SERVICES:
if str(service[2]) in status['hs_ports']:
hs_services.append(service[0])
domain_added.send_robust(
sender='tor', domain_type='hiddenservice',
name=status['hs_hostname'], description=_('Tor Hidden Service'),
services=hs_services)
if not return_code: if not return_code:
messages.success(request, _('Configuration updated.')) messages.success(request, _('Configuration updated.'))

View File

@ -214,9 +214,9 @@ setuptools.setup(
('/usr/share/man/man1', ['doc/plinth.1']), ('/usr/share/man/man1', ['doc/plinth.1']),
('/etc/plinth', ['data/etc/plinth/plinth.config']), ('/etc/plinth', ['data/etc/plinth/plinth.config']),
('/usr/share/augeas/lenses', ('/usr/share/augeas/lenses',
['data/usr/share/augeas/lenses/pagekite.aug']), glob.glob('data/usr/share/augeas/lenses/*.aug')),
('/usr/share/augeas/lenses/tests', ('/usr/share/augeas/lenses/tests',
['data/usr/share/augeas/lenses/tests/test_pagekite.aug']), glob.glob('data/usr/share/augeas/lenses/tests/test_*.aug')),
('/etc/plinth/modules-enabled', ('/etc/plinth/modules-enabled',
glob.glob(os.path.join('data/etc/plinth/modules-enabled', glob.glob(os.path.join('data/etc/plinth/modules-enabled',
'*')))], '*')))],