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 augeas
import codecs
import json
import os
import re
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
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_AUTH_COOKIE = '/var/run/tor/control.authcookie'
@ -44,8 +46,10 @@ def parse_arguments():
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
subparsers.add_parser('setup', help='Setup Tor configuration')
subparsers.add_parser('get-ports', help='Get list of Tor ports')
subparsers.add_parser('get-hs', help='Get hidden service')
subparsers.add_parser('get-ports',
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.add_argument('--service', choices=['enable', 'disable'],
@ -61,50 +65,63 @@ def parse_arguments():
def subcommand_setup(_):
"""Setup Tor configuration after installing it."""
# XXX: Performing this as a post-install step instead of
# 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 *:*
aug = augeas_load()
# Enable obfsproxy
ServerTransportPlugin obfs3,obfs4 exec /usr/bin/obfs4proxy
ExtORPort auto
aug.set(TOR_CONFIG + '/#comment[last() + 1]',
'Run as non-exit bridge relay')
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
VirtualAddrNetworkIPv4 10.192.0.0/10
AutomapHostsOnResolve 1
TransPort 127.0.0.1:9040
TransPort [::1]:9040
DNSPort 127.0.0.1:9053
DNSPort [::1]:9053
"""
with open(TOR_CONFIG, 'w') as conffile:
conffile.writelines(lines)
aug.set(TOR_CONFIG + '/#comment[last() + 1]', 'Enable obfsproxy')
aug.set(TOR_CONFIG + '/ServerTransportPlugin',
'obfs3,obfs4 exec /usr/bin/obfs4proxy')
aug.set(TOR_CONFIG + '/ExtORPort', 'auto')
aug.set(TOR_CONFIG + '/#comment[last() + 1]', 'Enable transparent proxy')
aug.set(TOR_CONFIG + '/VirtualAddrNetworkIPv4', '10.192.0.0/10')
aug.set(TOR_CONFIG + '/AutomapHostsOnResolve', '1')
aug.set(TOR_CONFIG + '/TransPort[1]', '127.0.0.1:9040')
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')
_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(_):
"""Get list of Tor ports."""
ports = get_ports()
for name, number in ports.items():
print(name, number)
"""Get Tor ports in JSON format."""
print(json.dumps(get_ports()))
def subcommand_get_hs(_):
"""Print currently configured Tor hidden service information"""
print(get_hidden_service())
"""Get hidden service information in JSON format."""
print(json.dumps(get_hidden_service()))
def subcommand_configure(arguments):
@ -171,29 +188,32 @@ QUIT
def get_hidden_service():
"""Return a string with configured Tor hidden service information"""
hs_dir = None
hs_enabled = False
hs_status = 'Ok'
hs_hostname = None
hs_ports = []
try:
with open(TOR_CONFIG, 'r') as conf_file:
for line in conf_file:
if line.startswith('HiddenServiceDir'):
hs_dir = line.split()[1]
elif line.startswith('HiddenServicePort'):
hs_ports.append(line.split()[1])
except FileNotFoundError:
return 'error'
aug = augeas_load()
hs_dir = aug.get(TOR_CONFIG + '/HiddenServiceDir')
hs_port_paths = aug.match(TOR_CONFIG + '/HiddenServicePort')
for hs_port_path in hs_port_paths:
port_info = aug.get(hs_port_path).split()
hs_ports.append({'virtport': port_info[0],
'target': port_info[1]})
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:
with open(os.path.join(hs_dir, 'hostname'), 'r') as conf_file:
hs_hostname = conf_file.read().strip()
except Exception:
return 'error'
return hs_hostname + ' ' + ','.join(hs_ports)
return {'enabled': hs_enabled, 'status': hs_status,
'hostname': hs_hostname, 'ports': hs_ports}
def _enable():
@ -210,20 +230,19 @@ def _disable():
def _enable_hs(restart=True):
"""Enable Tor hidden service"""
if get_hidden_service():
if get_hidden_service()['enabled']:
return
with open(TOR_CONFIG, 'r') as conffile:
lines = conffile.readlines()
lines.append('# Hidden Service configured by Plinth\n')
lines.append('HiddenServiceDir /var/lib/tor/hidden_service/\n')
lines.append('HiddenServicePort 80 127.0.0.1:80\n')
lines.append('HiddenServicePort 443 127.0.0.1:443\n')
lines.append('# end of Plinth Hidden Service config\n')
with open(TOR_CONFIG, 'w') as conffile:
conffile.writelines(lines)
aug = augeas_load()
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()
if restart:
if is_enabled() and is_running():
@ -231,7 +250,7 @@ def _enable_hs(restart=True):
# wait until hidden service information is available
tries = 0
while get_hidden_service() in ('', 'error'):
while not get_hidden_service()['enabled']:
tries += 1
if tries >= 12:
return
@ -241,35 +260,13 @@ def _enable_hs(restart=True):
def _disable_hs(restart=True):
"""Disable Tor hidden service"""
if not get_hidden_service():
if not get_hidden_service()['enabled']:
return
with open(TOR_CONFIG, 'r') as conffile:
lines = conffile.readlines()
filtered_lines = []
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)
aug = augeas_load()
aug.remove(TOR_CONFIG + '/HiddenServiceDir')
aug.remove(TOR_CONFIG + '/HiddenServicePort')
aug.save()
if restart:
if is_enabled() and is_running():
@ -347,6 +344,16 @@ def _update_ports():
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():
"""Parse arguments and perform all duties"""
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 _
import glob
import itertools
import json
from plinth import actions
from plinth import action_utils
from plinth import cfg
from plinth import service as service_module
from plinth.modules.names import SERVICES
from plinth.signals import domain_added
from plinth.signals import domain_added, domain_removed
version = 1
@ -72,20 +73,23 @@ def init():
is_external=True, enabled=is_enabled())
# 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 = []
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])
else:
hs_hostname = None
hostname = None
hs_services = None
domain_added.send_robust(
sender='tor', domain_type='hiddenservice',
name=hs_hostname, description=_('Tor Hidden Service'),
name=hostname, description=_('Tor Hidden Service'),
services=hs_services)
@ -98,6 +102,23 @@ def setup(helper, old_version=None):
['configure', '--apt-transport-tor', 'enable'])
helper.call('post', socks_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():
@ -113,45 +134,30 @@ def is_running():
def get_status():
"""Return current Tor status."""
output = actions.superuser_run('tor', ['get-ports'])
port_info = output.split('\n')
ports = {}
for line in port_info:
try:
(key, val) = line.split()
ports[key] = val
except ValueError:
continue
ports = json.loads(output)
(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(),
'is_running': is_running(),
'ports': ports,
'hs_enabled': hs_enabled,
'hs_hostname': hs_hostname,
'hs_ports': hs_ports,
'hs_enabled': hs_info['enabled'],
'hs_status': hs_info['status'],
'hs_hostname': hs_info['hostname'],
'hs_ports': hs_info['ports'],
'hs_services': hs_services,
'apt_transport_tor_enabled': is_apt_transport_tor_enabled()}
def get_hs():
"""Return hidden service status."""
output = actions.superuser_run('tor', ['get-hs'])
output = output.strip()
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)
return json.loads(output)
def get_augeas():
@ -239,8 +245,7 @@ def diagnose():
results.extend(_diagnose_control_port())
output = actions.superuser_run('tor', ['get-ports'])
ports = [line.split() for line in output.splitlines()]
ports = {port_type: int(port) for port_type, port in ports}
ports = json.loads(output)
results.append([_('Tor relay port available'),
'passed' if 'orport' in ports else 'failed'])

View File

@ -62,13 +62,19 @@
<thead>
<tr>
<th>{% trans "Hidden Service" %}</th>
<th>{% trans "Port" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Ports" %}</th>
</tr>
</thead>
<tbody>
<tr>
<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>
</tbody>
</table>

View File

@ -27,8 +27,6 @@ from .forms import TorForm
from plinth import actions
from plinth.errors import ActionError
from plinth.modules import tor
from plinth.modules.names import SERVICES
from plinth.signals import domain_added, domain_removed
config_process = None
@ -115,22 +113,7 @@ def _collect_config_result(request):
tor.socks_service.notify_enabled(None, status['enabled'])
tor.bridge_service.notify_enabled(None, status['enabled'])
# 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)
tor.update_hidden_service_domain(status)
if not return_code:
messages.success(request, _('Configuration updated.'))

View File

@ -214,9 +214,9 @@ setuptools.setup(
('/usr/share/man/man1', ['doc/plinth.1']),
('/etc/plinth', ['data/etc/plinth/plinth.config']),
('/usr/share/augeas/lenses',
['data/usr/share/augeas/lenses/pagekite.aug']),
glob.glob('data/usr/share/augeas/lenses/*.aug')),
('/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',
glob.glob(os.path.join('data/etc/plinth/modules-enabled',
'*')))],