From 4615d83430e2617ee468af244eefa96966488645 Mon Sep 17 00:00:00 2001 From: Christophe Mehay Date: Fri, 23 Sep 2016 03:24:11 +0200 Subject: [PATCH] Add support for docker-compose v2 and private keys --- Dockerfile | 2 +- README.md | 56 ++++++++++++++++++++ assets/entrypoint-config.yml | 8 ++- assets/onions/onions/Onions.py | 97 ++++++++++++++++++++++++++++++++-- assets/torrc | 13 ++--- docker-compose.v2.yml | 46 ++++++++++++++++ docker-compose.yml | 5 +- 7 files changed, 210 insertions(+), 17 deletions(-) create mode 100644 docker-compose.v2.yml diff --git a/Dockerfile b/Dockerfile index 87e09aa..e3a41ae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \ tor \ python3-pip -RUN pip3 install pyentrypoint==0.3.7 +RUN pip3 install pyentrypoint==0.3.8 ADD assets/entrypoint-config.yml / ADD assets/onions /usr/local/src/onions diff --git a/README.md b/README.md index 53e473c..a7dd744 100644 --- a/README.md +++ b/README.md @@ -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. +## 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 +__DEPECATED:__ 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. +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 A command line tool `onions` is available in container to get `.onion` url when container is running. diff --git a/assets/entrypoint-config.yml b/assets/entrypoint-config.yml index e09db48..18483c9 100644 --- a/assets/entrypoint-config.yml +++ b/assets/entrypoint-config.yml @@ -3,8 +3,12 @@ command: tor user: debian-tor group: debian-tor -config_files: - - /etc/tor/torrc +secret_env: + - '*_KEY' + - '*_PORTS' + +pre_conf_commands: + - onions --setup-hosts post_conf_commands: - timeout 3s tor > /dev/null || true diff --git a/assets/onions/onions/Onions.py b/assets/onions/onions/Onions.py index 27dac23..efde355 100644 --- a/assets/onions/onions/Onions.py +++ b/assets/onions/onions/Onions.py @@ -3,16 +3,96 @@ import os from json import dumps +from re import match + +from pyentrypoint import DockerLinks + 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/" + 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): - self._get_onions() if 'HIDDEN_SERVICE_DIR' in os.environ: self.hidden_service_dir = os.environ['HIDDEN_SERVICE_DIR'] @@ -21,7 +101,7 @@ class Onions(object): with open(filename, 'r') as hostfile: onion = str(hostfile.read()).strip() - with open('/etc/tor/torrc', 'r') as torfile: + with open(self.torrc, 'r') as torfile: self.onions[service] = [] for line in torfile.readlines(): find = '# PORT {name}'.format(name=service) @@ -33,7 +113,7 @@ class Onions(object): ) ) - def _get_onions(self): + def get_onions(self): self.onions = {} for root, dirs, _ in os.walk(self.hidden_service_dir, topdown=False): @@ -60,8 +140,15 @@ def main(): parser.add_argument('--json', dest='json', action='store_true', help='serialize to json') + parser.add_argument('--setup-hosts', dest='setup', action='store_true', + help='Setup hosts') + args = parser.parse_args() onions = Onions() + if args.setup: + onions.setup_hosts() + return + onions.get_onions() if args.json: print(onions.to_json()) else: diff --git a/assets/torrc b/assets/torrc index d67820c..4f087a8 100644 --- a/assets/torrc +++ b/assets/torrc @@ -1,11 +1,8 @@ -{% for container in containers %} -HiddenServiceDir /var/lib/tor/hidden_service/{{container.names[0]}} -{% for link in container.links %} -{% set port = env['PORT_MAP'] if 'PORT_MAP' in env else link.port %} -{% if link.protocol == 'tcp' %} -# PORT {{container.names[0]}} {{port}} -HiddenServicePort {{port}} {{link.ip}}:{{link.port}} -{% endif %} +{% for service, conf in setup.items() %} +HiddenServiceDir /var/lib/tor/hidden_service/{{service}} +{% for ports in conf['ports'] %} +# PORT {{service}} {{ports[0]}} +HiddenServicePort {{ports[0]}} {{service}}:{{ports[1]}} {% endfor %} {% endfor %} diff --git a/docker-compose.v2.yml b/docker-compose.v2.yml new file mode 100644 index 0000000..f129b1b --- /dev/null +++ b/docker-compose.v2.yml @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index 00d7b35..c0bd290 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,16 @@ # docker-compose.yml example tor: - # image: goldy/tor-hidden-service + image: goldy/tor-hidden-service + # or build: . links: - hello - world environment: PORT_MAP: 80 # Map port to detected service + volumes: + - ./keys:/var/lib/tor/hidden_service/ hello: image: tutum/hello-world