Merge pull request #4 from cmehay/v2

Add support for docker-compose v2 and private keys
This commit is contained in:
Christophe Mehay 2016-09-23 03:29:13 +02:00 committed by GitHub
commit 5463744e49
7 changed files with 210 additions and 17 deletions

View file

@ -7,7 +7,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
tor \ tor \
python3-pip python3-pip
RUN pip3 install pyentrypoint==0.3.7 RUN pip3 install pyentrypoint==0.3.8
ADD assets/entrypoint-config.yml / ADD assets/entrypoint-config.yml /
ADD assets/onions /usr/local/src/onions ADD assets/onions /usr/local/src/onions

View file

@ -20,12 +20,68 @@ $ docker run -ti --link something --volume /path/to/keys:/var/lib/tor/hidden_ser
Look at the `docker-compose.yml` file to see how to use it. Look at the `docker-compose.yml` file to see how to use it.
## Setup
### Set private key
Private key is settable by environment or by copying file in `hostname/private_key` in docket volume (`hostname` is the link name).
It's easier to pass key in environment with `docker-compose`.
```yaml
links:
- hello
- world
environment:
# Set private key
HELLO_KEY: |
-----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-----
```
Options are set using the following pattern: `LINKNAME_KEY`
### Setup port ### Setup port
__DEPECATED:__
By default, ports are the same as linked containers, but a default port can be mapped using `PORT_MAP` environment variable. By default, ports are the same as linked containers, but a default port can be mapped using `PORT_MAP` environment variable.
__Caution__: Using `PORT_MAP` with multiple ports on single service will cause `tor` to fail. __Caution__: Using `PORT_MAP` with multiple ports on single service will cause `tor` to fail.
Use link setting in environment with the following pattern: `LINKNAME_PORTS`.
Like docker, first port is exposed port and the second one is service internal port.
```yaml
links:
- hello
- world
environment:
# Set mapping ports
HELLO_PORTS: 80:80
WORLD_PORTS: 8000:80
```
### Compose v2 support
Links setting are required when using docker-compose v2. See `docker-compose.v2.yml` for example.
### Tools ### Tools
A command line tool `onions` is available in container to get `.onion` url when container is running. A command line tool `onions` is available in container to get `.onion` url when container is running.

View file

@ -3,8 +3,12 @@ command: tor
user: debian-tor user: debian-tor
group: debian-tor group: debian-tor
config_files: secret_env:
- /etc/tor/torrc - '*_KEY'
- '*_PORTS'
pre_conf_commands:
- onions --setup-hosts
post_conf_commands: post_conf_commands:
- timeout 3s tor > /dev/null || true - timeout 3s tor > /dev/null || true

View file

@ -3,16 +3,96 @@
import os import os
from json import dumps from json import dumps
from re import match
from pyentrypoint import DockerLinks
import argparse import argparse
from jinja2 import Environment
from jinja2 import FileSystemLoader
class Onions(object):
"""Onions""" class Setup(object):
hidden_service_dir = "/var/lib/tor/hidden_service/" hidden_service_dir = "/var/lib/tor/hidden_service/"
torrc = '/etc/tor/torrc'
def _add_host(self, host):
if host not in self.setup:
self.setup[host] = {}
def _get_ports(self, host, ports):
self._add_host(host)
if 'ports' not in self.setup[host]:
self.setup[host]['ports'] = []
port = [int(p) for p in ports.split(':')]
assert len(port) == 2
if port not in self.setup[host]['ports']:
self.setup[host]['ports'].append(port)
def _get_key(self, host, key):
self._add_host(host)
assert len(key) > 800
self.setup[host]['key'] = key
def _get_setup_from_env(self):
match_map = (
(r'([A-Z0-9]*)_PORTS', self._get_ports),
(r'([A-Z0-9]*)_KEY', self._get_key),
)
for key, val in os.environ.items():
for reg, call in match_map:
m = match(reg, key)
if m:
call(m.groups()[0].lower(), val)
def _get_setup_from_links(self):
containers = DockerLinks().to_containers()
if not containers:
return
for container in containers:
host = container.names[0]
self._add_host(host)
for link in container.links:
if link.protocol != 'tcp':
continue
port_map = os.environ.get('PORT_MAP')
self._get_ports(host, '{exposed}:{internal}'.format(
exposed=port_map or link.port,
internal=link.port,
))
def _set_keys(self):
for link, conf in self.setup.items():
if 'key' in conf:
serv_dir = os.path.join(self.hidden_service_dir, link)
os.makedirs(serv_dir, exist_ok=True)
with open(os.path.join(serv_dir, 'private_key'), 'w') as f:
f.write(conf['key'])
def _set_conf(self):
env = Environment(loader=FileSystemLoader('/'))
temp = env.get_template(self.torrc)
with open(self.torrc, mode='w') as f:
f.write(temp.render(setup=self.setup,
env=os.environ))
def setup_hosts(self):
self.setup = {}
try:
self._get_setup_from_env()
self._get_setup_from_links()
self._set_keys()
self._set_conf()
except:
raise Exception('Something wrongs with setup')
class Onions(Setup):
"""Onions"""
def __init__(self): def __init__(self):
self._get_onions()
if 'HIDDEN_SERVICE_DIR' in os.environ: if 'HIDDEN_SERVICE_DIR' in os.environ:
self.hidden_service_dir = os.environ['HIDDEN_SERVICE_DIR'] self.hidden_service_dir = os.environ['HIDDEN_SERVICE_DIR']
@ -21,7 +101,7 @@ class Onions(object):
with open(filename, 'r') as hostfile: with open(filename, 'r') as hostfile:
onion = str(hostfile.read()).strip() onion = str(hostfile.read()).strip()
with open('/etc/tor/torrc', 'r') as torfile: with open(self.torrc, 'r') as torfile:
self.onions[service] = [] self.onions[service] = []
for line in torfile.readlines(): for line in torfile.readlines():
find = '# PORT {name}'.format(name=service) find = '# PORT {name}'.format(name=service)
@ -33,7 +113,7 @@ class Onions(object):
) )
) )
def _get_onions(self): def get_onions(self):
self.onions = {} self.onions = {}
for root, dirs, _ in os.walk(self.hidden_service_dir, for root, dirs, _ in os.walk(self.hidden_service_dir,
topdown=False): topdown=False):
@ -60,8 +140,15 @@ def main():
parser.add_argument('--json', dest='json', action='store_true', parser.add_argument('--json', dest='json', action='store_true',
help='serialize to json') help='serialize to json')
parser.add_argument('--setup-hosts', dest='setup', action='store_true',
help='Setup hosts')
args = parser.parse_args() args = parser.parse_args()
onions = Onions() onions = Onions()
if args.setup:
onions.setup_hosts()
return
onions.get_onions()
if args.json: if args.json:
print(onions.to_json()) print(onions.to_json())
else: else:

View file

@ -1,11 +1,8 @@
{% for container in containers %} {% for service, conf in setup.items() %}
HiddenServiceDir /var/lib/tor/hidden_service/{{container.names[0]}} HiddenServiceDir /var/lib/tor/hidden_service/{{service}}
{% for link in container.links %} {% for ports in conf['ports'] %}
{% set port = env['PORT_MAP'] if 'PORT_MAP' in env else link.port %} # PORT {{service}} {{ports[0]}}
{% if link.protocol == 'tcp' %} HiddenServicePort {{ports[0]}} {{service}}:{{ports[1]}}
# PORT {{container.names[0]}} {{port}}
HiddenServicePort {{port}} {{link.ip}}:{{link.port}}
{% endif %}
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}

46
docker-compose.v2.yml Normal file
View file

@ -0,0 +1,46 @@
# docker version 2 example
version: "2"
services:
tor:
image: goldy/tor-hidden-service
# or
build: .
links:
- hello
- world
environment:
# Set mapping ports
HELLO_PORTS: 80:80
# Set private key
HELLO_KEY: |
-----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-----
WORLD_PORTS: 8000:80
# Keep keys in volumes
volumes:
- ./keys:/var/lib/tor/hidden_service/
hello:
image: tutum/hello-world
hostname: hello
world:
image: tutum/hello-world
hostname: world

View file

@ -1,13 +1,16 @@
# docker-compose.yml example # docker-compose.yml example
tor: tor:
# image: goldy/tor-hidden-service image: goldy/tor-hidden-service
# or
build: . build: .
links: links:
- hello - hello
- world - world
environment: environment:
PORT_MAP: 80 # Map port to detected service PORT_MAP: 80 # Map port to detected service
volumes:
- ./keys:/var/lib/tor/hidden_service/
hello: hello:
image: tutum/hello-world image: tutum/hello-world