Merge pull request #15 from cmehay/multiple
Rewrite Onions.py and add support for multiple services per addresspull/17/head
commit
7090945990
@ -1 +1,4 @@
|
||||
keys/
|
||||
*.egg-info
|
||||
.tox/
|
||||
__cache__
|
||||
|
@ -0,0 +1,107 @@
|
||||
|
||||
# Created by https://www.gitignore.io/api/python
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# virtualenv
|
||||
.venv
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# End of https://www.gitignore.io/api/python
|
||||
|
||||
# more
|
||||
key/
|
@ -0,0 +1,21 @@
|
||||
- repo: git://github.com/pre-commit/pre-commit-hooks
|
||||
sha: v0.9.1
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-docstring-first
|
||||
- id: check-merge-conflict
|
||||
- id: check-yaml
|
||||
- id: end-of-file-fixer
|
||||
- id: flake8
|
||||
args:
|
||||
- --exclude=__init__.py
|
||||
language_version: python3
|
||||
- id: autopep8-wrapper
|
||||
language_version: python3
|
||||
- id: requirements-txt-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: git://github.com/asottile/reorder_python_imports
|
||||
sha: v0.3.5
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
language_version: python3
|
@ -0,0 +1,10 @@
|
||||
sudo: false
|
||||
language: python
|
||||
python:
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
install: pip install tox-travis pre-commit
|
||||
script:
|
||||
- pre-commit run --all-files
|
||||
- tox
|
@ -0,0 +1,23 @@
|
||||
test:
|
||||
tox
|
||||
|
||||
check:
|
||||
pre-commit run --all-files
|
||||
|
||||
build:
|
||||
docker-compose build
|
||||
|
||||
run: build
|
||||
docker-compose up
|
||||
|
||||
build-v2:
|
||||
docker-compose -f docker-compose.v2.yml build
|
||||
|
||||
run-v2: build-v2
|
||||
docker-compose -f docker-compose.v2.yml up
|
||||
|
||||
build-v3:
|
||||
docker-compose -f docker-compose.v3.yml build
|
||||
|
||||
run-v3: build-v3
|
||||
docker-compose -f docker-compose.v3.yml up
|
@ -0,0 +1,177 @@
|
||||
'This class define a service link'
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from base64 import b32encode
|
||||
from hashlib import sha1
|
||||
|
||||
from Crypto.PublicKey import RSA
|
||||
|
||||
|
||||
class ServicesGroup(object):
|
||||
|
||||
name = None
|
||||
_priv_key = None
|
||||
_key_in_secrets = False
|
||||
|
||||
hidden_service_dir = "/var/lib/tor/hidden_service/"
|
||||
|
||||
def __init__(self, name=None, service=None, hidden_service_dir=None):
|
||||
|
||||
name_regex = r'^[a-zA-Z0-9-_]+$'
|
||||
|
||||
self.hidden_service_dir = hidden_service_dir or self.hidden_service_dir
|
||||
if not name and not service:
|
||||
raise Exception(
|
||||
'Init service group with a name or service at least'
|
||||
)
|
||||
self.services = []
|
||||
self.name = name or service.host
|
||||
if not re.match(name_regex, self.name):
|
||||
raise Exception(
|
||||
'Group {name} has invalid name'.format(name=self.name)
|
||||
)
|
||||
if service:
|
||||
self.add_service(service)
|
||||
|
||||
self.load_key()
|
||||
if not self._priv_key:
|
||||
self.gen_key()
|
||||
|
||||
def add_service(self, service):
|
||||
if service not in self.services:
|
||||
if self.get_service_by_host(service.host):
|
||||
raise Exception('Duplicate service name')
|
||||
self.services.append(service)
|
||||
|
||||
def get_service_by_host(self, host):
|
||||
for service in self.services:
|
||||
if host == service.host:
|
||||
return service
|
||||
|
||||
def add_key(self, key):
|
||||
if self._key_in_secrets:
|
||||
logging.warning('Secret key already set, overriding')
|
||||
self._priv_key = key
|
||||
self._key_in_secrets = False
|
||||
|
||||
def __iter__(self):
|
||||
yield 'name', self.name
|
||||
yield 'onion', self.onion_url
|
||||
yield 'urls', list(self.urls)
|
||||
|
||||
def __str__(self):
|
||||
return '{name}: {urls}'.format(name=self.name,
|
||||
urls=', '.join(self.urls))
|
||||
|
||||
@property
|
||||
def onion_url(self):
|
||||
"Get onion url from private key"
|
||||
|
||||
# Convert private RSA to public DER
|
||||
priv = RSA.importKey(self._priv_key.strip())
|
||||
der = priv.publickey().exportKey("DER")
|
||||
|
||||
# hash key, keep first half of sha1, base32 encode
|
||||
onion = b32encode(sha1(der[22:]).digest()[:10])
|
||||
|
||||
return '{onion}.onion'.format(onion=onion.decode().lower())
|
||||
|
||||
@property
|
||||
def urls(self):
|
||||
for service in self.services:
|
||||
for ports in service.ports:
|
||||
yield '{onion}:{port}'.format(onion=self.onion_url,
|
||||
port=ports.port_from)
|
||||
|
||||
def write_key(self, hidden_service_dir=None):
|
||||
'Write key on disk and set tor service'
|
||||
if not hidden_service_dir:
|
||||
hidden_service_dir = self.hidden_service_dir
|
||||
serv_dir = os.path.join(hidden_service_dir, self.name)
|
||||
os.makedirs(serv_dir, exist_ok=True)
|
||||
os.chmod(serv_dir, 0o700)
|
||||
with open(os.path.join(serv_dir, 'private_key'), 'w') as f:
|
||||
f.write(self._priv_key)
|
||||
os.fchmod(f.fileno(), 0o600)
|
||||
with open(os.path.join(serv_dir, 'hostname'), 'w') as f:
|
||||
f.write(self.onion_url)
|
||||
|
||||
def _load_key(self, key_file):
|
||||
if os.path.exists(key_file):
|
||||
with open(key_file, 'r') as f:
|
||||
key = f.read().encode()
|
||||
if not len(key):
|
||||
return
|
||||
try:
|
||||
rsa = RSA.importKey(key)
|
||||
self._priv_key = rsa.exportKey("PEM").decode()
|
||||
except BaseException:
|
||||
raise('Fail to load key for {name} services'.format(
|
||||
name=self.name
|
||||
))
|
||||
|
||||
def load_key(self):
|
||||
self.load_key_from_secrets()
|
||||
self.load_key_from_conf()
|
||||
|
||||
def load_key_from_secrets(self):
|
||||
'Load key from docker secret using service name'
|
||||
secret_file = os.path.join('/run/secrets', self.name)
|
||||
if not os.path.exists(secret_file):
|
||||
return
|
||||
try:
|
||||
self._load_key(secret_file)
|
||||
self._key_in_secrets = True
|
||||
except BaseException:
|
||||
logging.warning('Fail to load key from secret, '
|
||||
'check the key or secret name collision')
|
||||
|
||||
def load_key_from_conf(self, hidden_service_dir=None):
|
||||
'Load key from disk if exists'
|
||||
if not hidden_service_dir:
|
||||
hidden_service_dir = self.hidden_service_dir
|
||||
key_file = os.path.join(hidden_service_dir,
|
||||
self.name,
|
||||
'private_key')
|
||||
self._load_key(key_file)
|
||||
|
||||
def gen_key(self):
|
||||
'Generate new 1024 bits RSA key for hidden service'
|
||||
self._priv_key = RSA.generate(
|
||||
bits=1024,
|
||||
).exportKey("PEM").decode()
|
||||
|
||||
|
||||
class Ports:
|
||||
|
||||
port_from = None
|
||||
dest = None
|
||||
|
||||
def __init__(self, port_from, dest):
|
||||
self.port_from = int(port_from)
|
||||
self.dest = dest if dest.startswith('unix:') else int(dest)
|
||||
|
||||
@property
|
||||
def is_socket(self):
|
||||
return self.dest and type(self.dest) is not int
|
||||
|
||||
def __iter__(self):
|
||||
yield 'port_from', str(self.port_from)
|
||||
yield 'dest', str(self.dest)
|
||||
yield 'is_socket', self.is_socket
|
||||
|
||||
|
||||
class Service:
|
||||
|
||||
def __init__(self, host):
|
||||
self.host = host
|
||||
self.ports = []
|
||||
|
||||
def add_ports(self, ports):
|
||||
p = [Ports(*sp.split(':', 1)) for sp in ports.split(',')]
|
||||
self.ports.extend(p)
|
||||
|
||||
def __iter__(self):
|
||||
yield 'host', self.host
|
||||
yield 'ports', [dict(p) for p in self.ports]
|
@ -1 +1,5 @@
|
||||
from .Onions import Onions, main
|
||||
from .Onions import main
|
||||
from .Onions import Onions
|
||||
from .Service import Ports
|
||||
from .Service import Service
|
||||
from .Service import ServicesGroup
|
||||
|
@ -0,0 +1,408 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from base64 import b32encode
|
||||
from hashlib import sha1
|
||||
|
||||
import pytest
|
||||
from Crypto.PublicKey import RSA
|
||||
from onions import Onions
|
||||
|
||||
|
||||
def get_key_and_onion():
|
||||
key = '''
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQCsMP4gl6g1Q313miPhb1GnDr56ZxIWGsO2PwHM1infkbhlBakR
|
||||
6DGQfpE31L1ZKTUxY0OexKbW088v8qCOfjD9Zk1i80JP4xzfWQcwFZ5yM/0fkhm3
|
||||
zLXqXdEahvRthmFsS8OWusRs/04U247ryTm4k5S0Ch5OTBuvMLzQ8W0yDwIDAQAB
|
||||
AoGAAZr3U5B2ZgC6E7phKUHjbf5KMlPxrDkVqAZQWvuIKmhuYqq518vlYmZ7rhyS
|
||||
o1kqAMrfH4TP1WLmJJlLe+ibRk2aonR4e0GbW4x151wcJdT1V3vdWAsVSzG3+dqX
|
||||
PiGT//DIe0OPSH6ecI8ftFRLODd6f5iGkF4gsUSTcVzAFgkCQQDTY67dRpOD9Ozw
|
||||
oYH48xe0B9NQCw7g4NSH85jPurJXnpn6lZ6bcl8x8ioAdgLyomR7fO/dJFYLw6uV
|
||||
LZLqZsVbAkEA0Iei3QcpsJnYgcQG7l5I26Sq3LwoiGRDFKRI6k0e+en9JQJgA3Ay
|
||||
tsLpyCHv9jQ762F6AVXFru5DmZX40F6AXQJBAIHoKac8Xx1h4FaEuo4WPkPZ50ey
|
||||
dANIx/OAhTFrp3vnMPNpDV60K8JS8vLzkx4vJBcrkXDSirqSFhkIN9grLi8CQEO2
|
||||
l5MQPWBkRKK2pc2Hfj8cdIMi8kJ/1CyCwE6c5l8etR3sbIMRTtZ76nAbXRFkmsRv
|
||||
La/7Syrnobngsh/vX90CQB+PSSBqiPSsK2yPz6Gsd6OLCQ9sdy2oRwFTasH8sZyl
|
||||
bhJ3M9WzP/EMkAzyW8mVs1moFp3hRcfQlZHl6g1U9D8=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
'''
|
||||
|
||||
onion = b32encode(
|
||||
sha1(
|
||||
RSA.importKey(
|
||||
key.strip()
|
||||
).publickey().exportKey(
|
||||
"DER"
|
||||
)[22:]
|
||||
).digest()[:10]
|
||||
).decode().lower() + '.onion'
|
||||
|
||||
return key.strip(), onion
|
||||
|
||||
|
||||
def get_torrc_template():
|
||||
return r'''
|
||||
{% for service_group in services %}
|
||||
HiddenServiceDir /var/lib/tor/hidden_service/{{service_group.name}}
|
||||
{% for service in service_group.services %}
|
||||
{% for port in service.ports %}
|
||||
{% if port.is_socket %}
|
||||
HiddenServicePort {{port.port_from}} {{port.dest}}
|
||||
{% endif %}
|
||||
{% if not port.is_socket %}
|
||||
HiddenServicePort {{port.port_from}} {{service.host}}:{{port.dest}}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
{% if 'RELAY' in env %}
|
||||
ORPort 9001
|
||||
{% endif %}
|
||||
|
||||
SocksPort 0
|
||||
|
||||
# useless line for Jinja bug
|
||||
'''.strip()
|
||||
|
||||
|
||||
def test_ports(monkeypatch):
|
||||
env = {
|
||||
'SERVICE1_PORTS': '80:80',
|
||||
'SERVICE2_PORTS': '80:80,81:8000',
|
||||
'SERVICE3_PORTS': '80:unix://unix.socket',
|
||||
}
|
||||
|
||||
monkeypatch.setattr(os, 'environ', env)
|
||||
|
||||
onion = Onions()
|
||||
onion._get_setup_from_env()
|
||||
assert len(os.environ) == 3
|
||||
assert len(onion.services) == 3
|
||||
check = 0
|
||||
for service_group in onion.services:
|
||||
assert len(service_group.services) == 1
|
||||
service = service_group.services[0]
|
||||
if service.host == 'service1':
|
||||
check += 1
|
||||
assert len(service.ports) == 1
|
||||
assert service.ports[0].port_from == 80
|
||||
assert service.ports[0].dest == 80
|
||||
assert not service.ports[0].is_socket
|
||||
if service.host == 'service2':
|
||||
check += 3
|
||||
assert len(service.ports) == 2
|
||||
assert service.ports[0].port_from == 80
|
||||
assert service.ports[0].dest == 80
|
||||
assert service.ports[1].port_from == 81
|
||||
assert service.ports[1].dest == 8000
|
||||
if service.host == 'service3':
|
||||
check += 6
|
||||
assert len(service.ports) == 1
|
||||
assert service.ports[0].port_from == 80
|
||||
assert service.ports[0].dest == 'unix://unix.socket'
|
||||
assert service.ports[0].is_socket
|
||||
|
||||
assert check == 10
|
||||
|
||||
|
||||
def test_docker_links(fs, monkeypatch):
|
||||
|
||||
env = {
|
||||
'HOSTNAME': 'test_env',
|
||||
'COMPOSE_SERVICE1_1_PORT': 'tcp://172.17.0.2:80',
|
||||
'COMPOSE_SERVICE1_1_PORT_80_TCP': 'tcp://172.17.0.2:80',
|
||||
'COMPOSE_SERVICE1_1_PORT_80_TCP_ADDR': '172.17.0.2',
|
||||
'COMPOSE_SERVICE1_1_PORT_80_TCP_PORT': '80',
|
||||
'COMPOSE_SERVICE1_1_PORT_80_TCP_PROTO': 'tcp',
|
||||
'COMPOSE_SERVICE1_1_PORT_8000_TCP': 'tcp://172.17.0.2:8000',
|
||||
'COMPOSE_SERVICE1_1_PORT_8000_TCP_ADDR': '172.17.0.2',
|
||||
'COMPOSE_SERVICE1_1_PORT_8000_TCP_PORT': '8000',
|
||||
'COMPOSE_SERVICE1_1_PORT_8000_TCP_PROTO': 'tcp',
|
||||
'COMPOSE_SERVICE1_1_NAME': '/compose_env_1/compose_service1_1',
|
||||
'SERVICE1_PORT': 'tcp://172.17.0.2:80',
|
||||
'SERVICE1_PORT_80_TCP': 'tcp://172.17.0.2:80',
|
||||
'SERVICE1_PORT_80_TCP_ADDR': '172.17.0.2',
|
||||
'SERVICE1_PORT_80_TCP_PORT': '80',
|
||||
'SERVICE1_PORT_80_TCP_PROTO': 'tcp',
|
||||
'SERVICE1_PORT_8000_TCP': 'tcp://172.17.0.2:8000',
|
||||
'SERVICE1_PORT_8000_TCP_ADDR': '172.17.0.2',
|
||||
'SERVICE1_PORT_8000_TCP_PORT': '8000',
|
||||
'SERVICE1_PORT_8000_TCP_PROTO': 'tcp',
|
||||
'SERVICE1_NAME': '/compose_env_1/service1',
|
||||
'SERVICE1_1_PORT': 'tcp://172.17.0.2:80',
|
||||
'SERVICE1_1_PORT_80_TCP': 'tcp://172.17.0.2:80',
|
||||
'SERVICE1_1_PORT_80_TCP_ADDR': '172.17.0.2',
|
||||
'SERVICE1_1_PORT_80_TCP_PORT': '80',
|
||||
'SERVICE1_1_PORT_80_TCP_PROTO': 'tcp',
|
||||
'SERVICE1_1_PORT_8000_TCP': 'tcp://172.17.0.2:8000',
|
||||
'SERVICE1_1_PORT_8000_TCP_ADDR': '172.17.0.2',
|
||||
'SERVICE1_1_PORT_8000_TCP_PORT': '8000',
|
||||
'SERVICE1_1_PORT_8000_TCP_PROTO': 'tcp',
|
||||
'SERVICE1_1_NAME': '/compose_env_1/service1_1',
|
||||
}
|
||||
|
||||
etc_host = '''
|
||||
127.0.0.1 localhost
|
||||
::1 localhost ip6-localhost ip6-loopback
|
||||
fe00::0 ip6-localnet
|
||||
ff00::0 ip6-mcastprefix
|
||||
ff02::1 ip6-allnodes
|
||||
ff02::2 ip6-allrouters
|
||||
172.17.0.2 service1 bf447f22cdba compose_service1_1
|
||||
172.17.0.2 service1_1 bf447f22cdba compose_service1_1
|
||||
172.17.0.2 compose_service1_1 bf447f22cdba
|
||||
'''.strip()
|
||||
|
||||
fs.CreateFile('/etc/hosts', contents=etc_host)
|
||||
|
||||
monkeypatch.setattr(os, 'environ', env)
|
||||
|
||||
onion = Onions()
|
||||
onion._get_setup_from_links()
|
||||
|
||||
assert len(onion.services) == 1
|
||||
group = onion.services[0]
|
||||
assert len(group.services) == 1
|
||||
service = group.services[0]
|
||||
assert len(service.ports) == 2
|
||||
assert set(
|
||||
(port.port_from, port.dest) for port in service.ports
|
||||
) == set([(80, 80), (8000, 8000)])
|
||||
|
||||
|
||||
def test_key(monkeypatch):
|
||||
|
||||
key, onion_url = get_key_and_onion()
|
||||
env = {
|
||||
'SERVICE1_KEY': key
|
||||
}
|
||||
|
||||
monkeypatch.setattr(os, 'environ', env)
|
||||
|
||||
onion = Onions()
|
||||
onion._get_setup_from_env()
|
||||
|
||||
assert len(os.environ) == 1
|
||||
assert len(onion.services) == 1
|
||||
|
||||
assert onion.services[0].onion_url == onion_url
|
||||
|
||||
|
||||
def test_key_in_secret(fs, monkeypatch):
|
||||
env = {
|
||||
'SERVICE1_SERVICE_NAME': 'group1',
|
||||
'SERVICE2_SERVICE_NAME': 'group1',
|
||||
'SERVICE3_SERVICE_NAME': 'group2',
|
||||
'SERVICE1_PORTS': '80:80',
|
||||
'SERVICE2_PORTS': '81:80,82:8000',
|
||||
'SERVICE3_PORTS': '80:unix://unix.socket',
|
||||
}
|
||||
|
||||
monkeypatch.setattr(os, 'environ', env)
|
||||
|
||||
key, onion_url = get_key_and_onion()
|
||||
|
||||
fs.CreateFile('/run/secrets/group1', contents=key)
|
||||
|
||||
onion = Onions()
|
||||
onion._get_setup_from_env()
|
||||
|
||||
group1 = onion.find_group_by_name('group1')
|
||||
group2 = onion.find_group_by_name('group2')
|
||||
|
||||
# assert group._priv_key == key
|
||||
assert group1.onion_url == onion_url
|
||||
assert group2.onion_url != onion_url
|
||||
|
||||
|
||||
def test_configuration(fs, monkeypatch):
|
||||
env = {
|
||||
'SERVICE1_SERVICE_NAME': 'group1',
|
||||
'SERVICE2_SERVICE_NAME': 'group1',
|
||||
'SERVICE3_SERVICE_NAME': 'group2',
|
||||
'SERVICE1_PORTS': '80:80',
|
||||
'SERVICE2_PORTS': '81:80,82:8000',
|
||||
'SERVICE3_PORTS': '80:unix://unix.socket',
|
||||
}
|
||||
|
||||
monkeypatch.setattr(os, 'environ', env)
|
||||
monkeypatch.setattr(os, 'fchmod', lambda x, y: None)
|
||||
|
||||
key, onion_url = get_key_and_onion()
|
||||
torrc_tpl = get_torrc_template()
|
||||
|
||||
fs.CreateFile('/var/local/tor/torrc.tpl', contents=torrc_tpl)
|
||||
fs.CreateFile('/etc/tor/torrc')
|
||||
|
||||
onion = Onions()
|
||||
onion._get_setup_from_env()
|
||||
onion.apply_conf()
|
||||
|
||||
with open('/etc/tor/torrc', 'r') as f:
|
||||
torrc = f.read()
|
||||
|
||||
assert 'HiddenServiceDir /var/lib/tor/hidden_service/group1' in torrc
|
||||
assert 'HiddenServicePort 80 service1:80' in torrc
|
||||
assert 'HiddenServicePort 81 service2:80' in torrc
|
||||
assert 'HiddenServicePort 82 service2:8000' in torrc
|
||||
assert 'HiddenServiceDir /var/lib/tor/hidden_service/group2' in torrc
|
||||
assert 'HiddenServicePort 80 unix://unix.socket' in torrc
|
||||
|
||||
# Check parser
|
||||
onion2 = Onions()
|
||||
onion2.torrc_parser()
|
||||
|
||||
assert len(onion2.services) == 2
|
||||
|
||||
assert set(
|
||||
group.name for group in onion2.services
|
||||
) == set(['group1', 'group2'])
|
||||
|
||||
for group in onion2.services:
|
||||
if group.name == 'group1':
|
||||
assert len(group.services) == 2
|
||||
assert set(
|
||||
service.host for service in group.services
|
||||
) == set(['service1', 'service2'])
|
||||
for service in group.services:
|
||||
if service.host == 'service1':
|
||||
assert len(service.ports) == 1
|
||||
assert set(
|
||||
(port.port_from, port.dest) for port in service.ports
|
||||
) == set([(80, 80)])
|
||||
if service.host == 'service2':
|
||||
assert len(service.ports) == 2
|
||||
assert set(
|
||||
(port.port_from, port.dest) for port in service.ports
|
||||
) == set([(81, 80), (82, 8000)])
|
||||
if group.name == 'group2':
|
||||
assert len(group.services) == 1
|
||||
assert set(
|
||||
service.host for service in group.services
|
||||
) == set(['group2'])
|
||||
service = group.services[0]
|
||||
assert len(service.ports) == 1
|
||||
assert set(
|
||||
(port.port_from, port.dest) for port in service.ports
|
||||
) == set([(80, 'unix://unix.socket')])
|
||||
|
||||
|
||||
def test_groups(monkeypatch):
|
||||
env = {
|
||||
'SERVICE1_SERVICE_NAME': 'group1',
|
||||
'SERVICE2_SERVICE_NAME': 'group1',
|
||||
'SERVICE3_SERVICE_NAME': 'group2',
|
||||
'SERVICE1_PORTS': '80:80',
|
||||
'SERVICE2_PORTS': '81:80,82:8000',
|
||||
'SERVICE3_PORTS': '80:unix://unix.socket',
|
||||
}
|
||||
|
||||
monkeypatch.setattr(os, 'environ', env)
|
||||
|
||||
onion = Onions()
|
||||
onion._get_setup_from_env()
|
||||
|
||||
onion_match = r'^[a-z2-7]{16}.onion$'
|
||||
|
||||
assert len(os.environ) == 6
|
||||
assert len(onion.services) == 2
|
||||
|
||||
assert set(
|
||||
group.name for group in onion.services
|
||||
) == set(['group1', 'group2'])
|
||||
|
||||
for group in onion.services:
|
||||
if group.name == 'group1':
|
||||
assert len(group.services) == 2
|
||||
assert set(
|
||||
service.host for service in group.services
|
||||
) == set(['service1', 'service2'])
|
||||
|
||||
if group.name == 'group2':
|
||||
assert len(group.services) == 1
|
||||
assert set(
|
||||
service.host for service in group.services
|
||||
) == set(['service3'])
|
||||
|
||||
assert re.match(onion_match, group.onion_url)
|
||||
|
||||
|
||||
def test_json(monkeypatch):
|
||||
env = {
|
||||
'SERVICE1_SERVICE_NAME': 'group1',
|
||||
'SERVICE2_SERVICE_NAME': 'group1',
|
||||
'SERVICE3_SERVICE_NAME': 'group2',
|
||||
'SERVICE1_PORTS': '80:80',
|
||||
'SERVICE2_PORTS': '81:80,82:8000',
|
||||
'SERVICE3_PORTS': '80:unix://unix.socket',
|
||||
}
|
||||
|
||||
monkeypatch.setattr(os, 'environ', env)
|
||||
|
||||
onion = Onions()
|
||||
onion._get_setup_from_env()
|
||||
onion.check_services()
|
||||
|
||||
jsn = json.loads(onion.to_json())
|
||||
|
||||
assert len(jsn) == 2
|
||||
assert len(jsn['group1']) == 3
|
||||
assert len(jsn['group2']) == 1
|
||||
|
||||
|
||||
def test_output(monkeypatch):
|
||||
env = {
|
||||
'SERVICE1_SERVICE_NAME': 'group1',
|
||||
'SERVICE2_SERVICE_NAME': 'group1',
|
||||
'SERVICE3_SERVICE_NAME': 'group2',
|
||||
'SERVICE1_PORTS': '80:80',
|
||||
'SERVICE2_PORTS': '81:80,82:8000',
|
||||
'SERVICE3_PORTS': '80:unix://unix.socket',
|
||||
}
|
||||
|
||||
monkeypatch.setattr(os, 'environ', env)
|
||||
|
||||
onion = Onions()
|
||||
onion._get_setup_from_env()
|
||||
|
||||
for item in ['group1', 'group2', '.onion', ',']:
|
||||
assert item in str(onion)
|
||||
|
||||
|
||||
def test_not_valid_share_port(monkeypatch):
|
||||
env = {
|
||||
'SERVICE1_SERVICE_NAME': 'group1',
|
||||
'SERVICE2_SERVICE_NAME': 'group1',
|
||||
'SERVICE3_SERVICE_NAME': 'group2',
|
||||
'SERVICE1_PORTS': '80:80',
|
||||
'SERVICE2_PORTS': '80:80,82:8000',
|
||||
'SERVICE3_PORTS': '80:unix://unix.socket',
|
||||
}
|
||||
|
||||
monkeypatch.setattr(os, 'environ', env)
|
||||
|
||||
onion = Onions()
|
||||
onion._get_setup_from_env()
|
||||
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
onion.check_services()
|
||||
assert 'Same port for multiple services' in str(excinfo.value)
|
||||
|
||||
|
||||
def test_not_valid_no_services(monkeypatch):
|
||||
env = {
|
||||
'SERVICE1_SERVICE_NAME': 'group1',
|
||||
'SERVICE2_SERVICE_NAME': 'group1',
|
||||
'SERVICE3_SERVICE_NAME': 'group2',
|
||||
}
|
||||
|
||||
monkeypatch.setattr(os, 'environ', env)
|
||||
|
||||
onion = Onions()
|
||||
onion._get_setup_from_env()
|
||||
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
onion.check_services()
|
||||
assert 'has not ports set' in str(excinfo.value)
|
@ -0,0 +1,53 @@
|
||||
# docker version 3 example
|
||||
|
||||
version: "3.1"
|
||||
|
||||
services:
|
||||
tor:
|
||||
image: goldy/tor-hidden-service
|
||||
build: .
|
||||
links:
|
||||
- hello
|
||||
- world
|
||||
- again
|
||||
environment:
|
||||
# Set mapping ports
|
||||
HELLO_PORTS: 80:80,800:80,8888:80
|
||||
|
||||
WORLD_PORTS: 8000:80
|
||||
|
||||
AGAIN_PORTS: 88:80
|
||||
|
||||
# hello and again will share the same onion_adress
|
||||
AGAIN_SERVICE_NAME: foo
|
||||
HELLO_SERVICE_NAME: foo
|
||||
|
||||
# Keep keys in volumes
|
||||
volumes:
|
||||
- tor-keys:/var/lib/tor/hidden_service/
|
||||
|
||||
# Set secret for key, use the same name as the service
|
||||
secrets:
|
||||
- source: foo
|
||||
target: foo
|
||||
mode: 0400
|
||||
|
||||
hello:
|
||||
image: tutum/hello-world
|
||||
hostname: hello
|
||||
|
||||
world:
|
||||
image: tutum/hello-world
|
||||
hostname: world
|
||||
|
||||
again:
|
||||
image: tutum/hello-world
|
||||
hostname: again
|
||||
|
||||
volumes:
|
||||
tor-keys:
|
||||
driver: local
|
||||
|
||||
secrets:
|
||||
foo:
|
||||
file: ./foo_private_key
|
@ -0,0 +1,15 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQDR8TdQF9fDlGhy1SMgfhMBi9TaFeD12/FK27TZE/tYGhxXvs1C
|
||||
NmFJy1hjVxspF5unmUsCk0yEsvEdcAdp17Vynz6W41VdinETU9yXHlUJ6NyI32AH
|
||||
dnFnHEcsllSEqD1hPAAvMUWwSMJaNmBEFtl8DUMS9tPX5fWGX4w5Xx8dZwIDAQAB
|
||||
AoGBAMb20jMHxaZHWg2qTRYYJa8LdHgS0BZxkWYefnBUbZn7dOz7mM+tddpX6raK
|
||||
8OSqyQu3Tc1tB9GjPLtnVr9KfVwhUVM7YXC/wOZo+u72bv9+4OMrEK/R8xy30XWj
|
||||
GePXEu95yArE4NucYphxBLWMMu2E4RodjyJpczsl0Lohcn4BAkEA+XPaEKnNA3AL
|
||||
1DXRpSpaa0ukGUY/zM7HNUFMW3UP00nxNCpWLSBmrQ56Suy7iSy91oa6HWkDD/4C
|
||||
k0HslnMW5wJBANdz4ehByMJZmJu/b5y8wnFSqep2jmJ1InMvd18BfVoBTQJwGMAr
|
||||
+qwSwNXXK2YYl9VJmCPCfgN0o7h1AEzvdYECQAM5UxUqDKNBvHVmqKn4zShb1ugY
|
||||
t1RfS8XNbT41WhoB96MT9P8qTwlniX8UZiwUrvNp1Ffy9n4raz8Z+APNwvsCQQC9
|
||||
AuaOsReEmMFu8VTjNh2G+TQjgvqKmaQtVNjuOgpUKYv7tYehH3P7/T+62dcy7CRX
|
||||
cwbLaFbQhUUUD2DCHdkBAkB6CbB+qhu67oE4nnBCXllI9EXktXgFyXv/cScNvM9Y
|
||||
FDzzNAAfVc5Nmbmx28Nw+0w6pnpe/3m0Tudbq3nHdHfQ
|
||||
-----END RSA PRIVATE KEY-----
|
Loading…
Reference in New Issue