mirror of
https://github.com/cmehay/docker-tor-hidden-service.git
synced 2025-04-21 22:39:10 +00:00
Merge branch 'master' into master
This commit is contained in:
commit
c31c409a1a
38 changed files with 3342 additions and 1058 deletions
|
@ -2,3 +2,5 @@ keys/
|
||||||
*.egg-info
|
*.egg-info
|
||||||
.tox/
|
.tox/
|
||||||
__cache__
|
__cache__
|
||||||
|
__pycache__
|
||||||
|
*/__pycache__
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -105,3 +105,4 @@ ENV/
|
||||||
|
|
||||||
# more
|
# more
|
||||||
key/
|
key/
|
||||||
|
.vscode
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
repos:
|
||||||
- repo: git://github.com/pre-commit/pre-commit-hooks
|
- repo: git://github.com/pre-commit/pre-commit-hooks
|
||||||
sha: v0.9.1
|
rev: v2.5.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
- id: check-docstring-first
|
- id: check-docstring-first
|
||||||
|
@ -10,12 +11,14 @@
|
||||||
args:
|
args:
|
||||||
- --exclude=__init__.py
|
- --exclude=__init__.py
|
||||||
language_version: python3
|
language_version: python3
|
||||||
- id: autopep8-wrapper
|
|
||||||
language_version: python3
|
|
||||||
- id: requirements-txt-fixer
|
- id: requirements-txt-fixer
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-autopep8
|
||||||
|
rev: v1.5.2
|
||||||
|
hooks:
|
||||||
|
- id: autopep8
|
||||||
- repo: git://github.com/asottile/reorder_python_imports
|
- repo: git://github.com/asottile/reorder_python_imports
|
||||||
sha: v0.3.5
|
rev: v2.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: reorder-python-imports
|
- id: reorder-python-imports
|
||||||
language_version: python3
|
language_version: python3
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
sudo: false
|
sudo: false
|
||||||
|
dist: xenial
|
||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
- "3.4"
|
- '3.8'
|
||||||
- "3.5"
|
install: pip install tox-travis pre-commit poetry
|
||||||
- "3.6"
|
|
||||||
install: pip install tox-travis pre-commit
|
|
||||||
script:
|
script:
|
||||||
- pre-commit run --all-files
|
- pre-commit run --all-files
|
||||||
- tox
|
- tox
|
||||||
|
|
38
Dockerfile
38
Dockerfile
|
@ -1,13 +1,18 @@
|
||||||
FROM alpine
|
FROM python:3.10-alpine
|
||||||
|
|
||||||
|
# if omitted, the versions are determined from the git tags
|
||||||
|
ARG tor_version
|
||||||
|
ARG torsocks_version
|
||||||
|
|
||||||
ENV HOME /var/lib/tor
|
ENV HOME /var/lib/tor
|
||||||
|
ENV POETRY_VIRTUALENVS_CREATE=false
|
||||||
|
|
||||||
RUN apk add --no-cache git libevent-dev openssl-dev gcc make automake ca-certificates autoconf musl-dev coreutils zlib zlib-dev && \
|
RUN apk add --no-cache git bind-tools cargo libevent-dev openssl-dev gnupg gcc make automake ca-certificates autoconf musl-dev coreutils libffi-dev zlib-dev && \
|
||||||
mkdir -p /usr/local/src/ && \
|
mkdir -p /usr/local/src/ /var/lib/tor/ && \
|
||||||
git clone https://git.torproject.org/tor.git /usr/local/src/tor && \
|
git clone https://git.torproject.org/tor.git /usr/local/src/tor && \
|
||||||
cd /usr/local/src/tor && \
|
cd /usr/local/src/tor && \
|
||||||
git checkout $(git branch -a | grep 'release' | sort -V | tail -1) && \
|
TOR_VERSION=${tor_version=$(git tag | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1)} && \
|
||||||
head ReleaseNotes | grep version | awk -F"version" '{print $2}' | grep - | awk '{ print $1 }' > /version && \
|
git checkout tor-$TOR_VERSION && \
|
||||||
./autogen.sh && \
|
./autogen.sh && \
|
||||||
./configure \
|
./configure \
|
||||||
--disable-asciidoc \
|
--disable-asciidoc \
|
||||||
|
@ -16,27 +21,20 @@ RUN apk add --no-cache git libevent-dev openssl-dev gcc make automake ca-cer
|
||||||
make && make install && \
|
make && make install && \
|
||||||
cd .. && \
|
cd .. && \
|
||||||
rm -rf tor && \
|
rm -rf tor && \
|
||||||
apk add --no-cache python3 python3-dev && \
|
pip3 install --upgrade pip poetry && \
|
||||||
python3 -m ensurepip && \
|
apk del git libevent-dev openssl-dev gnupg cargo make automake autoconf musl-dev coreutils libffi-dev && \
|
||||||
rm -r /usr/lib/python*/ensurepip && \
|
|
||||||
pip3 install --upgrade pip setuptools pycrypto && \
|
|
||||||
apk del git libevent-dev openssl-dev make automake python3-dev gcc autoconf musl-dev coreutils && \
|
|
||||||
apk add --no-cache libevent openssl
|
apk add --no-cache libevent openssl
|
||||||
|
|
||||||
RUN mkdir -p /etc/tor/
|
|
||||||
|
|
||||||
ADD assets/entrypoint-config.yml /
|
|
||||||
ADD assets/onions /usr/local/src/onions
|
|
||||||
ADD assets/torrc /var/local/tor/torrc.tpl
|
|
||||||
ADD assets/v3onions /usr/bin/v3onions
|
|
||||||
|
|
||||||
RUN chmod +x /usr/bin/v3onions
|
|
||||||
RUN cd /usr/local/src/onions && python3 setup.py install
|
|
||||||
|
|
||||||
RUN mkdir -p ${HOME}/.tor && \
|
RUN mkdir -p ${HOME}/.tor && \
|
||||||
addgroup -S -g 107 tor && \
|
addgroup -S -g 107 tor && \
|
||||||
adduser -S -G tor -u 104 -H -h ${HOME} tor
|
adduser -S -G tor -u 104 -H -h ${HOME} tor
|
||||||
|
|
||||||
|
COPY assets/entrypoint-config.yml /
|
||||||
|
COPY assets/torrc /var/local/tor/torrc.tpl
|
||||||
|
COPY assets/vanguards.conf.tpl /var/local/tor/vanguards.conf.tpl
|
||||||
|
|
||||||
|
ENV VANGUARDS_CONFIG /etc/tor/vanguards.conf
|
||||||
|
|
||||||
VOLUME ["/var/lib/tor/hidden_service/"]
|
VOLUME ["/var/lib/tor/hidden_service/"]
|
||||||
|
|
||||||
ENTRYPOINT ["pyentrypoint"]
|
ENTRYPOINT ["pyentrypoint"]
|
||||||
|
|
57
Makefile
57
Makefile
|
@ -1,23 +1,60 @@
|
||||||
|
.EXPORT_ALL_VARIABLES:
|
||||||
|
|
||||||
|
LAST_TOR_VERSION = $(shell bash last_tor_version.sh)
|
||||||
|
LAST_TORSOCKS_VERSION = $(shell bash last_torsocks_version.sh)
|
||||||
|
TOR_VERSION = $(shell cat current_tor_version)
|
||||||
|
TORSOCKS_VERSION = $(shell cat current_torsocks_version)
|
||||||
|
CUR_COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
|
CUR_TAG = v$(TOR_VERSION)-$(CUR_COMMIT)
|
||||||
|
|
||||||
test:
|
test:
|
||||||
tox
|
tox
|
||||||
|
|
||||||
|
tag:
|
||||||
|
git tag $(CUR_TAG)
|
||||||
|
|
||||||
|
update_tor_version:
|
||||||
|
echo $(LAST_TOR_VERSION) > current_tor_version
|
||||||
|
echo $(LAST_TORSOCKS_VERSION) > current_torsocks_version
|
||||||
|
|
||||||
|
release: test tag
|
||||||
|
git push origin --tags
|
||||||
|
|
||||||
check:
|
check:
|
||||||
pre-commit run --all-files
|
pre-commit run --all-files
|
||||||
|
|
||||||
build:
|
build:
|
||||||
docker-compose build
|
- echo build with tor version $(TOR_VERSION) and torsocks version $(TORSOCKS_VERSION)
|
||||||
|
- echo 'Please run make update_tor_version to build the container with the last tor version'
|
||||||
|
docker-compose -f docker-compose.build.yml build
|
||||||
|
|
||||||
|
rebuild:
|
||||||
|
- echo rebuild with tor version $(TOR_VERSION) and torsocks version $(TORSOCKS_VERSION)
|
||||||
|
- echo 'Please run make update_tor_version to build the container with the last tor version'
|
||||||
|
docker-compose -f docker-compose.build.yml build --no-cache --pull
|
||||||
|
|
||||||
run: build
|
run: build
|
||||||
docker-compose up
|
docker-compose -f docker-compose.v2.yml up --force-recreate
|
||||||
|
|
||||||
build-v2:
|
run-v2-socket: build
|
||||||
docker-compose -f docker-compose.v2.yml build
|
docker-compose -f docker-compose.v2.socket.yml up --force-recreate
|
||||||
|
|
||||||
run-v2: build-v2
|
run-v3: build
|
||||||
docker-compose -f docker-compose.v2.yml up
|
docker-compose -f docker-compose.v3.yml up --force-recreate
|
||||||
|
|
||||||
build-v3:
|
shell-v3: build
|
||||||
docker-compose -f docker-compose.v3.yml build
|
docker-compose -f docker-compose.v3.yml run tor--rm tor sh
|
||||||
|
|
||||||
run-v3: build-v3
|
run-v3-latest:
|
||||||
docker-compose -f docker-compose.v3.yml up
|
docker-compose -f docker-compose.v3.latest.yml up --force-recreate
|
||||||
|
|
||||||
|
run-vanguards: build
|
||||||
|
docker-compose -f docker-compose.vanguards.yml up --force-recreate
|
||||||
|
|
||||||
|
run-vanguards-network: build
|
||||||
|
docker-compose -f docker-compose.vanguards-network.yml up --force-recreate
|
||||||
|
|
||||||
|
publish: build
|
||||||
|
docker tag goldy/tor-hidden-service:$(CUR_TAG) goldy/tor-hidden-service:latest
|
||||||
|
docker push goldy/tor-hidden-service:$(CUR_TAG)
|
||||||
|
docker push goldy/tor-hidden-service:latest
|
||||||
|
|
247
README.md
247
README.md
|
@ -5,136 +5,121 @@
|
||||||
* /version is a text file with the current of Tor version generated with each build
|
* /version is a text file with the current of Tor version generated with each build
|
||||||
* Weekly builds. The Goldy's original image hadn't been updated in some time. Using the latest version of Tor is always best practice.
|
* Weekly builds. The Goldy's original image hadn't been updated in some time. Using the latest version of Tor is always best practice.
|
||||||
|
|
||||||
Create a tor hidden service with a link
|
## Changelog
|
||||||
|
|
||||||
```sh
|
* 26 jul 2022
|
||||||
# run a container with a network application
|
* Update `onions` tool to v0.7.1:
|
||||||
$ docker run -d --name hello_world tutum/hello-world
|
* Fix an issue when restarting a container with control port enabled
|
||||||
|
* Updated to python 3.10
|
||||||
|
* Fix a typo in `docker-compose.vanguards-network.yml`, it works now
|
||||||
|
* Update `tor` to `0.4.7.8`
|
||||||
|
|
||||||
# and just link it to this container
|
* 23 dec 2021
|
||||||
$ docker run -ti --link hello_world goldy/tor-hidden-service
|
* Update `onions` tool to v0.7.0:
|
||||||
```
|
* Drop support of onion v2 adresses as tor network does not accept them anymore
|
||||||
|
* Update `tor` to `0.4.6.9`
|
||||||
The .onion URLs are displayed to stdout at startup.
|
|
||||||
|
|
||||||
To keep onion keys, just mount volume `/var/lib/tor/hidden_service/`
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ docker run -ti --link something --volume /path/to/keys:/var/lib/tor/hidden_service/ goldy/tor-hidden-service
|
|
||||||
```
|
|
||||||
|
|
||||||
Look at the `docker-compose.yml` file to see how to use it.
|
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
### Set private key
|
### Setup hosts
|
||||||
|
|
||||||
Private key is settable by environment or by copying file in `hostname/private_key` in docket volume (`hostname` is the link name).
|
From 2019, new conf to handle tor v3 address has been added. Here an example with `docker-compose` v2+:
|
||||||
|
|
||||||
It's easier to pass key in environment with `docker-compose`.
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
version: "2"
|
||||||
|
|
||||||
|
services:
|
||||||
|
tor:
|
||||||
|
image: goldy/tor-hidden-service:0.3.5.8
|
||||||
links:
|
links:
|
||||||
- hello
|
- hello
|
||||||
- world
|
- world
|
||||||
|
- again
|
||||||
environment:
|
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-----
|
|
||||||
|
|
||||||
|
# hello and again will share the same onion v3 address
|
||||||
|
SERVICE1_TOR_SERVICE_HOSTS: 88:hello:80,8000:world:80
|
||||||
|
# Optional as tor version 2 is not supported anymore
|
||||||
|
SERVICE1_TOR_SERVICE_VERSION: '3'
|
||||||
|
# tor v3 address private key base 64 encoded
|
||||||
|
SERVICE1_TOR_SERVICE_KEY: |
|
||||||
|
PT0gZWQyNTUxOXYxLXNlY3JldDogdHlwZTAgPT0AAACArobDQYyZAWXei4QZwr++
|
||||||
|
j96H1X/gq14NwLRZ2O5DXuL0EzYKkdhZSILY85q+kfwZH8z4ceqe7u1F+0pQi/sM
|
||||||
|
|
||||||
|
world:
|
||||||
|
image: tutum/hello-world
|
||||||
|
hostname: world
|
||||||
|
|
||||||
|
hello:
|
||||||
|
image: tutum/hello-world
|
||||||
|
hostname: hello
|
||||||
```
|
```
|
||||||
|
|
||||||
Options are set using the following pattern: `LINKNAME_KEY`
|
This configuration will output:
|
||||||
|
|
||||||
### Setup port
|
```
|
||||||
|
service1: xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion:88, xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
`xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion:88` will hit `again:80`.
|
||||||
|
`xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion:8000` will hit `wold:80`.
|
||||||
|
|
||||||
|
|
||||||
__Caution__: Using `PORT_MAP` with multiple ports on single service will cause `tor` to fail.
|
#### Environment variables
|
||||||
|
|
||||||
Use link setting in environment with the following pattern: `LINKNAME_PORTS`.
|
##### `{SERVICE}_TOR_SERVICE_HOSTS`
|
||||||
|
|
||||||
Like docker, first port is exposed port and the second one is service internal port.
|
The config patern for this variable is: `{exposed_port}:{hostname}:{port}}`
|
||||||
|
|
||||||
|
For example `80:hello:8080` will expose an onion service on port 80 to the port 8080 of hello hostname.
|
||||||
|
|
||||||
|
Unix sockets are supported too, `80:unix://path/to/socket.sock` will expose an onion service on port 80 to the socket `/path/to/socket.sock`. See `docker-compose.v2.socket.yml` for an example.
|
||||||
|
|
||||||
|
You can concatenate services using comas.
|
||||||
|
|
||||||
|
> **WARNING**: Using sockets and ports in the same service group can lead to issues
|
||||||
|
|
||||||
|
##### `{SERVICE}_TOR_SERVICE_VERSION`
|
||||||
|
|
||||||
|
Optionnal now, can only be `3`. Set the tor address type.
|
||||||
|
|
||||||
|
> **WARNING**: Version 2 is not supported anymore by tor network
|
||||||
|
|
||||||
|
`2` was giving short addresses `5azvyr7dvvr4cldn.onion` and `3` gives long addresses `xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion`
|
||||||
|
|
||||||
|
|
||||||
|
##### `{SERVICE}_TOR_SERVICE_KEY`
|
||||||
|
|
||||||
|
You can set the private key for the current service.
|
||||||
|
|
||||||
|
Tor v3 addresses uses ed25519 binary keys. It should be base64 encoded:
|
||||||
|
```
|
||||||
|
PT0gZWQyNTUxOXYxLXNlY3JldDogdHlwZTAgPT0AAACArobDQYyZAWXei4QZwr++j96H1X/gq14NwLRZ2O5DXuL0EzYKkdhZSILY85q+kfwZH8z4ceqe7u1F+0pQi/sM
|
||||||
|
```
|
||||||
|
##### `TOR_SOCKS_PORT`
|
||||||
|
|
||||||
|
Set tor sock5 proxy port for this tor instance. (Use this if you need to connect to tor network with your service)
|
||||||
|
|
||||||
|
##### `TOR_EXTRA_OPTIONS`
|
||||||
|
|
||||||
|
Add any options in the `torrc` file.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
links:
|
services:
|
||||||
- hello
|
tor:
|
||||||
- world
|
|
||||||
- hey
|
|
||||||
environment:
|
environment:
|
||||||
# Set mapping ports
|
# Add any option you need
|
||||||
HELLO_PORTS: 80:80
|
TOR_EXTRA_OPTIONS: |
|
||||||
|
HiddenServiceNonAnonymousMode 1
|
||||||
# Multiple ports can be coma separated
|
HiddenServiceSingleHopMode 1
|
||||||
WORLD_PORTS: 8000:80,8888:80,22:22
|
|
||||||
|
|
||||||
# Socket mapping is supported
|
|
||||||
HEY_PORTS: 80:unix:/var/run/socket.sock
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
__DEPRECATED:__
|
|
||||||
By default, ports are the same as linked containers, but a default port can be mapped using `PORT_MAP` environment variable.
|
|
||||||
|
|
||||||
#### Socket
|
|
||||||
|
|
||||||
To increase security, it's possible to setup your service through socket between containers and turn off network in your app container. See `docker-compose.v2.sock.yml` for an example.
|
|
||||||
|
|
||||||
__Warning__: Due to a bug in `tor` configuration parser, it's not possible to mix network link and socket link in the same `tor` configuration.
|
|
||||||
|
|
||||||
### Group services
|
|
||||||
|
|
||||||
Multiple services can be hosted behind the same onion address.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
links:
|
|
||||||
- hello
|
|
||||||
- world
|
|
||||||
- hey
|
|
||||||
environment:
|
|
||||||
# Set mapping ports
|
|
||||||
HELLO_PORTS: 80:80
|
|
||||||
|
|
||||||
# Multiple ports can be coma separated
|
|
||||||
WORLD_PORTS: 8000:80,8888:80,22:22
|
|
||||||
|
|
||||||
# Socket mapping is supported
|
|
||||||
HEY_PORTS: 80:unix:/var/run/socket.sock
|
|
||||||
|
|
||||||
# hello and world will share the same onion address
|
|
||||||
# Service name can be any string as long there is not special char
|
|
||||||
HELLO_SERVICE_NAME: foo
|
|
||||||
WORLD_SERVICE_NAME: foo
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
__Warning__: Be carefull to not use the same exposed ports for grouped services.
|
|
||||||
|
|
||||||
### Compose v2 support
|
|
||||||
|
|
||||||
Links setting are required when using docker-compose v2. See `docker-compose.v2.yml` for example.
|
|
||||||
|
|
||||||
### Copose v3 support and secrets
|
|
||||||
|
|
||||||
Links setting are required when using docker-compose v3. See `docker-compose.v3.yml` for example.
|
|
||||||
|
|
||||||
#### Secrets
|
#### Secrets
|
||||||
|
|
||||||
Secret key can be set through docker `secrets`, see `docker-compose.v3.yml` for example.
|
Secret key can be set through docker `secrets`, see `docker-compose.v3.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.
|
||||||
|
@ -142,23 +127,73 @@ A command line tool `onions` is available in container to get `.onion` url when
|
||||||
```sh
|
```sh
|
||||||
# Get services
|
# Get services
|
||||||
$ docker exec -ti torhiddenproxy_tor_1 onions
|
$ docker exec -ti torhiddenproxy_tor_1 onions
|
||||||
hello: vegm3d7q64gutl75.onion:80
|
hello: xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion:80
|
||||||
world: b2sflntvdne63amj.onion:80
|
world: ootceq7skq7qpvvwf2tajeboxovalco7z3ka44vxbtfdr2tfvx5ld7ad.onion:80
|
||||||
|
|
||||||
# Get json
|
# Get json
|
||||||
$ docker exec -ti torhiddenproxy_tor_1 onions --json
|
$ docker exec -ti torhiddenproxy_tor_1 onions --json
|
||||||
{"hello": ["b2sflntvdne63amj.onion:80"], "world": ["vegm3d7q64gutl75.onion:80"]}
|
{"hello": ["xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion:80"], "world": ["ootceq7skq7qpvvwf2tajeboxovalco7z3ka44vxbtfdr2tfvx5ld7ad.onion:80"]}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Auto reload
|
### Auto reload
|
||||||
|
|
||||||
Changing `/etc/tor/torrc` file trigger a `SIGHUP` signal to `tor` to reload configuration.
|
Changing `/etc/tor/torrc` file triggers a `SIGHUP` signal to `tor` to reload configuration.
|
||||||
|
|
||||||
To disable this behavior, add `ENTRYPOINT_DISABLE_RELOAD` in environment.
|
To disable this behavior, add `ENTRYPOINT_DISABLE_RELOAD` in environment.
|
||||||
|
|
||||||
|
### Versions
|
||||||
|
|
||||||
|
Container version will follow tor release versions.
|
||||||
|
|
||||||
### pyentrypoint
|
### pyentrypoint
|
||||||
|
|
||||||
This container is using [`pyentrypoint`](https://github.com/cmehay/pyentrypoint) to generate its setup.
|
This container uses [`pyentrypoint`](https://github.com/cmehay/pyentrypoint) to generate its setup.
|
||||||
|
|
||||||
If you need to use the legacy version, please checkout the `legacy` branch or pull `goldy/tor-hidden-service:legacy`.
|
### pytor
|
||||||
|
|
||||||
|
This containner uses [`pytor`](https://github.com/cmehay/pytor) to mannages tor cryptography, generate keys and compute onion urls.
|
||||||
|
|
||||||
|
## Control port
|
||||||
|
|
||||||
|
Use these environment variables to enable control port
|
||||||
|
* `TOR_CONTROL_PORT`: enable and set control port binding (`ip`, `ip:port` or `unix:/path/to/socket.sock`) (default port is 9051)
|
||||||
|
* `TOR_CONTROL_PASSWORD`: set control port password (in clear, not hashed)
|
||||||
|
* `TOR_DATA_DIRECTORY`: set data directory (default `/run/tor/data`)
|
||||||
|
|
||||||
|
## Vanguards
|
||||||
|
|
||||||
|
For critical hidden services, it's possible to increase security with [`Vanguards`](https://github.com/mikeperry-tor/vanguards) tool.
|
||||||
|
|
||||||
|
|
||||||
|
### Run in the same container
|
||||||
|
|
||||||
|
Check out [`docker-compose.vanguards.yml`](docker-compose.vanguards.yml) for example.
|
||||||
|
|
||||||
|
Add environment variable `TOR_ENABLE_VANGUARDS` to `true` to start `vanguards` daemon beside `tor` process. `Vanguards` logs will be displayed to stdout using `pyentrypoint` logging, if you need raw output, set `ENTRYPOINT_RAW` to `true` in environment.
|
||||||
|
|
||||||
|
In this mode, if `vanguards` exits, sigint is sent to `tor` process to terminate it. If you want to disable this behavior, set `VANGUARD_KILL_TOR_ON_EXIT` to `false` in environment.
|
||||||
|
|
||||||
|
### Run in separate containers
|
||||||
|
Check out[`docker-compose.vanguards-network.yml`](docker-compose.vanguards-network.yml) for an example of increased security setup using docker networks.
|
||||||
|
|
||||||
|
#### settings
|
||||||
|
|
||||||
|
Use the same environment variable as `tor` to configure `vangards` (see upper).
|
||||||
|
* `TOR_CONTROL_PORT`
|
||||||
|
* `TOR_CONTROL_PASSWORD`
|
||||||
|
|
||||||
|
##### more settings
|
||||||
|
|
||||||
|
Use `VANGUARDS_EXTRA_OPTIONS` environment variable to change any settings.
|
||||||
|
|
||||||
|
The following settings cannot me changer with this variable:
|
||||||
|
- `control_ip`:
|
||||||
|
- use `TOR_CONTROL_PORT`
|
||||||
|
- `control_port`:
|
||||||
|
- use `TOR_CONTROL_PORT`
|
||||||
|
- `control_socket`:
|
||||||
|
- use `TOR_CONTROL_PORT`
|
||||||
|
- `control_pass`:
|
||||||
|
- use `TOR_CONTROL_PASSWORD`
|
||||||
|
- `state_file`:
|
||||||
|
- use `VANGUARDS_STATE_FILE`
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
command: tor
|
commands:
|
||||||
|
- tor
|
||||||
|
- vanguards
|
||||||
|
|
||||||
user: tor
|
user: tor
|
||||||
group: tor
|
group: tor
|
||||||
|
@ -7,13 +9,31 @@ secret_env:
|
||||||
- '*_KEY'
|
- '*_KEY'
|
||||||
- '*_PORTS'
|
- '*_PORTS'
|
||||||
- '*_SERVICE_NAME'
|
- '*_SERVICE_NAME'
|
||||||
|
- '*_TOR_SERVICE_*'
|
||||||
|
- 'TOR_SOCKS_PORT'
|
||||||
|
- TOR_CONTROL_PASSWORD
|
||||||
|
|
||||||
|
config_files:
|
||||||
|
- vanguards:
|
||||||
|
- /var/local/tor/vanguards.conf.tpl: /etc/tor/vanguards.conf
|
||||||
|
|
||||||
pre_conf_commands:
|
pre_conf_commands:
|
||||||
|
- tor:
|
||||||
- onions --setup-hosts
|
- onions --setup-hosts
|
||||||
|
|
||||||
post_conf_commands:
|
post_conf_commands:
|
||||||
- chown -R tor:tor $HOME
|
- tor:
|
||||||
|
- mkdir -p /run/tor
|
||||||
|
- chmod -R 700 $HOME /run/tor
|
||||||
|
- chown -R tor:tor $HOME /run/tor
|
||||||
|
|
||||||
|
post_run_commands:
|
||||||
|
- tor:
|
||||||
|
- onions --run-vanguards
|
||||||
|
|
||||||
|
set_environment:
|
||||||
|
- vanguards:
|
||||||
|
- TOR_CONTROL_PORT: onions --resolve-control-port
|
||||||
reload:
|
reload:
|
||||||
files:
|
files:
|
||||||
- /etc/tor/torrc
|
- /etc/tor/torrc
|
||||||
|
|
|
@ -1,284 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from json import dumps
|
|
||||||
from re import match
|
|
||||||
|
|
||||||
from jinja2 import Environment
|
|
||||||
from jinja2 import FileSystemLoader
|
|
||||||
from pyentrypoint import DockerLinks
|
|
||||||
|
|
||||||
from .Service import Service
|
|
||||||
from .Service import ServicesGroup
|
|
||||||
|
|
||||||
|
|
||||||
class Setup(object):
|
|
||||||
|
|
||||||
hidden_service_dir = "/var/lib/tor/hidden_service/"
|
|
||||||
torrc = '/etc/tor/torrc'
|
|
||||||
torrc_template = '/var/local/tor/torrc.tpl'
|
|
||||||
|
|
||||||
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'] = {host: []}
|
|
||||||
if host not in self.setup[host]['ports']:
|
|
||||||
self.setup[host]['ports'][host] = []
|
|
||||||
ports_l = [
|
|
||||||
[
|
|
||||||
int(v) if not v.startswith('unix:') else v
|
|
||||||
for v in sp.split(':', 1)
|
|
||||||
] for sp in ports.split(',')
|
|
||||||
]
|
|
||||||
for port in ports_l:
|
|
||||||
assert len(port) == 2
|
|
||||||
if port not in self.setup[host]['ports'][host]:
|
|
||||||
self.setup[host]['ports'][host].append(port)
|
|
||||||
|
|
||||||
def _get_key(self, host, key):
|
|
||||||
self._add_host(host)
|
|
||||||
assert len(key) > 800
|
|
||||||
self.setup[host]['key'] = key
|
|
||||||
|
|
||||||
def _get_service(self, host, service):
|
|
||||||
self._add_host(host)
|
|
||||||
self.setup[host]['service'] = service
|
|
||||||
|
|
||||||
def find_group_by_service(self, service):
|
|
||||||
for group in self.services:
|
|
||||||
if service in group.services:
|
|
||||||
return group
|
|
||||||
|
|
||||||
def find_group_by_name(self, name):
|
|
||||||
for group in self.services:
|
|
||||||
if name == group.name:
|
|
||||||
return group
|
|
||||||
|
|
||||||
def find_service_by_host(self, host):
|
|
||||||
for group in self.services:
|
|
||||||
service = group.get_service_by_host(host)
|
|
||||||
if service:
|
|
||||||
return service
|
|
||||||
|
|
||||||
def add_empty_group(self, name):
|
|
||||||
if self.find_group_by_name(name):
|
|
||||||
raise Exception('Group {name} already exists'.format(name=name))
|
|
||||||
group = ServicesGroup(name=name)
|
|
||||||
self.services.append(group)
|
|
||||||
return group
|
|
||||||
|
|
||||||
def add_new_service(self, host, name=None, ports=None, key=None):
|
|
||||||
group = self.find_group_by_name(name)
|
|
||||||
service = self.find_service_by_host(host)
|
|
||||||
if not service:
|
|
||||||
service = Service(host=host)
|
|
||||||
if not group:
|
|
||||||
group = ServicesGroup(
|
|
||||||
service=service,
|
|
||||||
name=name,
|
|
||||||
hidden_service_dir=self.hidden_service_dir
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
group.add_service(service)
|
|
||||||
if group not in self.services:
|
|
||||||
self.services.append(group)
|
|
||||||
else:
|
|
||||||
group = self.find_group_by_service(service)
|
|
||||||
if key:
|
|
||||||
group.add_key(key)
|
|
||||||
if ports:
|
|
||||||
service.add_ports(ports)
|
|
||||||
return service
|
|
||||||
|
|
||||||
def _set_service_names(self):
|
|
||||||
'Create groups for services, should be run first'
|
|
||||||
reg = r'([A-Z0-9]*)_SERVICE_NAME'
|
|
||||||
for key, val in os.environ.items():
|
|
||||||
m = match(reg, key)
|
|
||||||
if m:
|
|
||||||
self.add_new_service(host=m.groups()[0].lower(), name=val)
|
|
||||||
|
|
||||||
def _set_ports(self, host, ports):
|
|
||||||
self.add_new_service(host=host, ports=ports)
|
|
||||||
|
|
||||||
def _set_key(self, host, key):
|
|
||||||
self.add_new_service(host=host, key=key)
|
|
||||||
|
|
||||||
def _setup_from_env(self):
|
|
||||||
match_map = (
|
|
||||||
(r'([A-Z0-9]*)_PORTS', self._set_ports),
|
|
||||||
(r'([A-Z0-9]*)_KEY', self._set_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_env(self):
|
|
||||||
self._set_service_names()
|
|
||||||
self._setup_from_env()
|
|
||||||
|
|
||||||
def _get_setup_from_links(self):
|
|
||||||
containers = DockerLinks().to_containers()
|
|
||||||
if not containers:
|
|
||||||
return
|
|
||||||
for container in containers:
|
|
||||||
host = container.names[0]
|
|
||||||
self.add_new_service(host=host)
|
|
||||||
for link in container.links:
|
|
||||||
if link.protocol != 'tcp':
|
|
||||||
continue
|
|
||||||
port_map = os.environ.get('PORT_MAP')
|
|
||||||
self._set_ports(host, '{exposed}:{internal}'.format(
|
|
||||||
exposed=port_map or link.port,
|
|
||||||
internal=link.port,
|
|
||||||
))
|
|
||||||
|
|
||||||
def apply_conf(self):
|
|
||||||
self._write_keys()
|
|
||||||
self._write_torrc()
|
|
||||||
|
|
||||||
def _write_keys(self):
|
|
||||||
for service in self.services:
|
|
||||||
service.write_key()
|
|
||||||
|
|
||||||
def _write_torrc(self):
|
|
||||||
env = Environment(loader=FileSystemLoader('/'))
|
|
||||||
temp = env.get_template(self.torrc_template)
|
|
||||||
with open(self.torrc, mode='w') as f:
|
|
||||||
f.write(temp.render(services=self.services,
|
|
||||||
env=os.environ,
|
|
||||||
type=type,
|
|
||||||
int=int))
|
|
||||||
|
|
||||||
def setup_hosts(self):
|
|
||||||
self.setup = {}
|
|
||||||
self._get_setup_from_env()
|
|
||||||
self._get_setup_from_links()
|
|
||||||
self.check_services()
|
|
||||||
self.apply_conf()
|
|
||||||
|
|
||||||
def check_services(self):
|
|
||||||
for group in self.services:
|
|
||||||
for service in group.services:
|
|
||||||
if not service.ports:
|
|
||||||
raise Exception(
|
|
||||||
'Service {name} has not ports set'.format(
|
|
||||||
name=service.host
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if len(group.services) > 1 and [
|
|
||||||
True for p in service.ports if p.is_socket
|
|
||||||
]:
|
|
||||||
raise Exception(
|
|
||||||
'Cannot use socket and ports '
|
|
||||||
'in the same service'.format(
|
|
||||||
name=service.host
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if len(set(dict(group)['urls'])) != len(dict(group)['urls']):
|
|
||||||
raise Exception(
|
|
||||||
'Same port for multiple services in {name} group'.format(
|
|
||||||
name=group.name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Onions(Setup):
|
|
||||||
"""Onions"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.services = []
|
|
||||||
if 'HIDDEN_SERVICE_DIR' in os.environ:
|
|
||||||
self.hidden_service_dir = os.environ['HIDDEN_SERVICE_DIR']
|
|
||||||
|
|
||||||
def torrc_parser(self):
|
|
||||||
|
|
||||||
def parse_dir(line):
|
|
||||||
_, path = line.split()
|
|
||||||
group_name = os.path.basename(path)
|
|
||||||
group = (self.find_group_by_name(group_name)
|
|
||||||
or self.add_empty_group(group_name))
|
|
||||||
return group
|
|
||||||
|
|
||||||
def parse_port(line, service_group):
|
|
||||||
_, port_from, dest = line.split()
|
|
||||||
service_host, port = dest.split(':')
|
|
||||||
ports_str = '{port_from}:{dest}'
|
|
||||||
name = service_host
|
|
||||||
ports_param = ports_str.format(port_from=port_from,
|
|
||||||
dest=port)
|
|
||||||
if port.startswith('/'):
|
|
||||||
name = service_group.name
|
|
||||||
ports_param = ports_str.format(port_from=port_from,
|
|
||||||
dest=dest)
|
|
||||||
service = (service_group.get_service_by_host(name)
|
|
||||||
or Service(name))
|
|
||||||
service.add_ports(ports_param)
|
|
||||||
if service not in service_group.services:
|
|
||||||
service_group.add_service(service)
|
|
||||||
|
|
||||||
if not os.path.exists(self.torrc):
|
|
||||||
return
|
|
||||||
with open(self.torrc, 'r') as f:
|
|
||||||
for line in f.readlines():
|
|
||||||
if line.startswith('HiddenServiceDir'):
|
|
||||||
service_group = parse_dir(line)
|
|
||||||
if line.startswith('HiddenServicePort'):
|
|
||||||
parse_port(line, service_group)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
if not self.services:
|
|
||||||
return 'No onion site'
|
|
||||||
return '\n'.join([str(service) for service in self.services])
|
|
||||||
|
|
||||||
def to_json(self):
|
|
||||||
service_lst = [dict(service) for service in self.services]
|
|
||||||
return dumps({
|
|
||||||
service['name']: service['urls'] for service in service_lst
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
logging.basicConfig()
|
|
||||||
parser = argparse.ArgumentParser(description='Display onion sites',
|
|
||||||
prog='onions')
|
|
||||||
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()
|
|
||||||
try:
|
|
||||||
onions = Onions()
|
|
||||||
if args.setup:
|
|
||||||
onions.setup_hosts()
|
|
||||||
else:
|
|
||||||
onions.torrc_parser()
|
|
||||||
except BaseException as e:
|
|
||||||
error_msg = str(e)
|
|
||||||
else:
|
|
||||||
error_msg = None
|
|
||||||
if args.json:
|
|
||||||
if error_msg:
|
|
||||||
print(dumps({'error': error_msg}))
|
|
||||||
sys.exit(1)
|
|
||||||
logging.getLogger().setLevel(logging.ERROR)
|
|
||||||
print(onions.to_json())
|
|
||||||
else:
|
|
||||||
if error_msg:
|
|
||||||
logging.error(error_msg)
|
|
||||||
sys.exit(1)
|
|
||||||
print(onions)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
|
@ -1,45 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from setuptools import find_packages
|
|
||||||
from setuptools import setup
|
|
||||||
|
|
||||||
setup(
|
|
||||||
name='onions',
|
|
||||||
|
|
||||||
version='0.4.1',
|
|
||||||
|
|
||||||
packages=find_packages(),
|
|
||||||
|
|
||||||
author="Christophe Mehay",
|
|
||||||
|
|
||||||
author_email="cmehay@nospam.student.42.fr",
|
|
||||||
|
|
||||||
description="Display onion sites hosted",
|
|
||||||
|
|
||||||
include_package_data=True,
|
|
||||||
|
|
||||||
url='http://github.com/cmehay/docker-tor-hidden-service',
|
|
||||||
|
|
||||||
classifiers=[
|
|
||||||
"Programming Language :: Python",
|
|
||||||
"Development Status :: 1 - Planning",
|
|
||||||
"License :: OSI Approved :: BSD License",
|
|
||||||
"Natural Language :: English",
|
|
||||||
"Operating System :: POSIX :: Linux",
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"Programming Language :: Python :: 3.4",
|
|
||||||
"Topic :: System :: Installation/Setup",
|
|
||||||
],
|
|
||||||
|
|
||||||
install_requires=['pyentrypoint==0.5.1',
|
|
||||||
'Jinja2>=2.8',
|
|
||||||
'pycrypto', ],
|
|
||||||
|
|
||||||
entry_points={
|
|
||||||
'console_scripts': [
|
|
||||||
'onions = onions:main',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
license="WTFPL",
|
|
||||||
)
|
|
|
@ -1,408 +0,0 @@
|
||||||
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)
|
|
43
assets/torrc
43
assets/torrc
|
@ -1,5 +1,8 @@
|
||||||
{% for service_group in services %}
|
{% for service_group in onion.services %}
|
||||||
HiddenServiceDir /var/lib/tor/hidden_service/{{service_group.name}}
|
HiddenServiceDir {{service_group.hidden_service_dir}}
|
||||||
|
{% if service_group.version == 3 %}
|
||||||
|
HiddenServiceVersion 3
|
||||||
|
{% endif %}
|
||||||
{% for service in service_group.services %}
|
{% for service in service_group.services %}
|
||||||
{% for port in service.ports %}
|
{% for port in service.ports %}
|
||||||
{% if port.is_socket %}
|
{% if port.is_socket %}
|
||||||
|
@ -11,12 +14,40 @@ HiddenServicePort {{port.port_from}} {{service.host}}:{{port.dest}}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
DataDirectory {{ onion.data_directory }}
|
||||||
{% if 'RELAY' in env %}
|
{% if 'TOR_SOCKS_PORT' in env %}
|
||||||
ORPort 9001
|
SocksPort {{env['TOR_SOCKS_PORT']}}
|
||||||
|
{% else %}
|
||||||
|
SocksPort 0
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
SocksPort 0
|
{% if envtobool('TOR_EXIT_RELAY', False) %}
|
||||||
|
ExitRelay 1
|
||||||
|
{% else %}
|
||||||
|
ExitRelay 0
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if onion.enable_control_port %}
|
||||||
|
{% if onion.control_socket %}
|
||||||
|
ControlPort {{onion.control_socket}}
|
||||||
|
{% endif %}
|
||||||
|
{% if not onion.control_socket %}
|
||||||
|
{% if onion.control_ip_binding.version() == 4 %}
|
||||||
|
ControlPort {{onion.control_ip_binding}}:{{ onion.control_port }}
|
||||||
|
{% endif %}
|
||||||
|
{% if onion.control_ip_binding.version() == 6 %}
|
||||||
|
ControlPort [{{onion.control_ip_binding}}]:{{ onion.control_port }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if onion.control_hashed_password %}
|
||||||
|
HashedControlPassword {{ onion.control_hashed_password }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
{% if 'TOR_EXTRA_OPTIONS' in env %}
|
||||||
|
{{env['TOR_EXTRA_OPTIONS']}}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
# useless line for Jinja bug
|
# useless line for Jinja bug
|
||||||
HiddenServiceVersion 3
|
HiddenServiceVersion 3
|
||||||
|
|
145
assets/vanguards.conf.tpl
Normal file
145
assets/vanguards.conf.tpl
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
## Global options
|
||||||
|
[Global]
|
||||||
|
|
||||||
|
{% if (env.get('TOR_CONTROL_PORT', '')).startswith('unix:') %}
|
||||||
|
{% set _, unix_path = env['TOR_CONTROL_PORT'].split(':', 1) %}
|
||||||
|
{% elif ':' in env.get('TOR_CONTROL_PORT', '') %}
|
||||||
|
{% set host, port = env['TOR_CONTROL_PORT'].split(':', 1) %}
|
||||||
|
{% else %}
|
||||||
|
{% set host = env.get('TOR_CONTROL_PORT') %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
control_ip = {{ host or '' }}
|
||||||
|
|
||||||
|
control_port = {{ port or 9051 }}
|
||||||
|
|
||||||
|
control_socket = {{ unix_path or '' }}
|
||||||
|
|
||||||
|
control_pass = {{ env.get('TOR_CONTROL_PASSWORD', '') }}
|
||||||
|
|
||||||
|
state_file = {{ env.get('VANGUARDS_STATE_FILE', '/run/tor/data/vanguards.state') }}
|
||||||
|
|
||||||
|
|
||||||
|
{% if 'VANGUARDS_EXTRA_OPTIONS' in env %}
|
||||||
|
{% set extra_conf = ConfigParser().read_string(env['VANGUARDS_EXTRA_OPTIONS']) %}
|
||||||
|
{% if 'Global' in extra_conf %}
|
||||||
|
{% for key, val in extra_conf['Global'].items() %}
|
||||||
|
{{key}} = {{val}}
|
||||||
|
{% endfor %}
|
||||||
|
{% set _ = extra_conf.pop('Global') %}
|
||||||
|
{% endif %}
|
||||||
|
{{ extra_conf.to_string() }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{#
|
||||||
|
## Example vanguards configuration file
|
||||||
|
#
|
||||||
|
# All values below are default values and won't appear in final config file
|
||||||
|
# Original here: https://github.com/mikeperry-tor/vanguards/blob/master/vanguards-example.conf
|
||||||
|
#
|
||||||
|
# Enable/disable active vanguard update of layer2 and layer3 guards
|
||||||
|
enable_vanguards = True
|
||||||
|
|
||||||
|
# Enable/disable the bandwidth side channel detection checks:
|
||||||
|
enable_bandguards = True
|
||||||
|
|
||||||
|
# Enable/disable circuit build timeout analysis (informational only):
|
||||||
|
enable_cbtverify = False
|
||||||
|
|
||||||
|
# Enable/disable checks on Rendezvous Point overuse attacks:
|
||||||
|
enable_rendguard = True
|
||||||
|
|
||||||
|
# Close circuits upon suspected attack conditions:
|
||||||
|
close_circuits = True
|
||||||
|
|
||||||
|
# If True, we write (or update/rotate) layer2 and layer3 vanguards in torrc,
|
||||||
|
# then exit. This option disables the bandguards and rendguard defenses.
|
||||||
|
one_shot_vanguards = False
|
||||||
|
|
||||||
|
# The current loglevel:
|
||||||
|
loglevel = NOTICE
|
||||||
|
|
||||||
|
# If specified, log to this file instead of stdout:
|
||||||
|
logfile =
|
||||||
|
|
||||||
|
## Vanguards: layer1, layer2, and layer3 rotation params.
|
||||||
|
[Vanguards]
|
||||||
|
|
||||||
|
# How long to keep our layer1 guard (0 means use Tor default):
|
||||||
|
layer1_lifetime_days = 0
|
||||||
|
|
||||||
|
# The maximum amount of time to keep a layer2 guard:
|
||||||
|
max_layer2_lifetime_hours = 1080
|
||||||
|
|
||||||
|
# The maximum amount of time to keep a layer3 guard:
|
||||||
|
max_layer3_lifetime_hours = 48
|
||||||
|
|
||||||
|
# The minimum amount of time to keep a layer2 guard:
|
||||||
|
min_layer2_lifetime_hours = 24
|
||||||
|
|
||||||
|
# The minimum amount of time to keep a layer3 guard:
|
||||||
|
min_layer3_lifetime_hours = 1
|
||||||
|
|
||||||
|
# The number of layer1 guards:
|
||||||
|
num_layer1_guards = 2
|
||||||
|
|
||||||
|
# The number of layer2 guards:
|
||||||
|
num_layer2_guards = 3
|
||||||
|
|
||||||
|
# The number of layer3 guards:
|
||||||
|
num_layer3_guards = 8
|
||||||
|
|
||||||
|
|
||||||
|
## Bandguards: Mechanisms to detect + mitigate bandwidth side channel attacks.
|
||||||
|
[Bandguards]
|
||||||
|
|
||||||
|
# Maximum number of hours to allow any circuit to remain open
|
||||||
|
# (set to 0 to disable):
|
||||||
|
circ_max_age_hours = 24
|
||||||
|
|
||||||
|
# Maximum amount of kilobytes that can be present in a hidden service
|
||||||
|
# descriptor before we close the circuit (set to 0 to disable):
|
||||||
|
circ_max_hsdesc_kilobytes = 30
|
||||||
|
|
||||||
|
# Total maximum megabytes on any circuit before we close it. Note that
|
||||||
|
# while HTTP GET can resume if this limit is hit, HTTP POST will not.
|
||||||
|
# This means that applications that require large data submission (eg
|
||||||
|
# SecureDrop or onionshare) should set this much higher
|
||||||
|
# (or set to 0 to disable):
|
||||||
|
circ_max_megabytes = 0
|
||||||
|
|
||||||
|
# Warn if we can't build or use circuits for this many seconds.
|
||||||
|
circ_max_disconnected_secs = 30
|
||||||
|
|
||||||
|
# Warn if we are disconnected from the Tor network for this many seconds.
|
||||||
|
conn_max_disconnected_secs = 15
|
||||||
|
|
||||||
|
## Rendguard: Monitors service-side Rendezvous Points to detect misuse/attack
|
||||||
|
[Rendguard]
|
||||||
|
|
||||||
|
# No relay should show up as a Rendezvous Point more often than this ratio
|
||||||
|
# multiplied by its bandwidth weight:
|
||||||
|
rend_use_max_use_to_bw_ratio = 5.0
|
||||||
|
|
||||||
|
# What is percent of the network weight is not in the consensus right now?
|
||||||
|
# Put another way, the max number of rend requests from relays not in the
|
||||||
|
# consensus is rend_use_max_use_to_bw_ratio times this churn rate.
|
||||||
|
rend_use_max_consensus_weight_churn = 1.0
|
||||||
|
|
||||||
|
# Close circuits where the Rendezvous Point appears too often. Note that an
|
||||||
|
# adversary can deliberately cause RP overuse in order to impact availability.
|
||||||
|
# If this is a concern, either set this to false, or raise the ratio
|
||||||
|
# parameter above.
|
||||||
|
rend_use_close_circuits_on_overuse = True
|
||||||
|
|
||||||
|
# Total number of circuits we need before we begin enforcing rendezvous point
|
||||||
|
# ratio limits:
|
||||||
|
rend_use_global_start_count = 1000
|
||||||
|
|
||||||
|
# Number of times a relay must be seen as a Rendezvous Point before applying
|
||||||
|
# ratio limits:
|
||||||
|
rend_use_relay_start_count = 100
|
||||||
|
|
||||||
|
# Divide all relay counts by two once the total circuit count hits this many:
|
||||||
|
rend_use_scale_at_count = 20000
|
||||||
|
#}
|
1
current_tor_version
Normal file
1
current_tor_version
Normal file
|
@ -0,0 +1 @@
|
||||||
|
0.4.7.12
|
1
current_torsocks_version
Normal file
1
current_torsocks_version
Normal file
|
@ -0,0 +1 @@
|
||||||
|
v2.3.0
|
12
docker-compose.build.yml
Normal file
12
docker-compose.build.yml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# docker version 3 builder
|
||||||
|
|
||||||
|
version: "3.1"
|
||||||
|
|
||||||
|
services:
|
||||||
|
tor:
|
||||||
|
image: goldy/tor-hidden-service:$CUR_TAG
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
args:
|
||||||
|
tor_version: $TOR_VERSION
|
||||||
|
torsocks_version: $TORSOCKS_VERSION
|
|
@ -4,13 +4,13 @@ version: "2"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
tor:
|
tor:
|
||||||
image: goldy/tor-hidden-service
|
image: goldy/tor-hidden-service:$CUR_TAG
|
||||||
build: .
|
build: .
|
||||||
links:
|
links:
|
||||||
- world
|
- world
|
||||||
environment:
|
environment:
|
||||||
# Set mapping port to unix socket
|
# Set service hosts to unix socket
|
||||||
WORLD_PORTS: 80:unix:/var/run/nginx.sock
|
WORLD_TOR_SERVICE_HOSTS: 80:unix://var/run/nginx.sock
|
||||||
|
|
||||||
# Mount socket directory from world container
|
# Mount socket directory from world container
|
||||||
volumes_from:
|
volumes_from:
|
||||||
|
|
|
@ -4,40 +4,41 @@ version: "2"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
tor:
|
tor:
|
||||||
image: goldy/tor-hidden-service
|
image: goldy/tor-hidden-service:$CUR_TAG
|
||||||
build: .
|
|
||||||
links:
|
links:
|
||||||
- hello
|
- hello
|
||||||
- world
|
- world
|
||||||
- again
|
- again
|
||||||
environment:
|
environment:
|
||||||
# Set mapping ports
|
######################################################################
|
||||||
HELLO_PORTS: 80:80,800:80,8888:80
|
### TOR ADDRESSES VERSION 2 ARE NOT SUPPORTED ANYMORE ###
|
||||||
# Set private key
|
######################################################################
|
||||||
HELLO_KEY: |
|
# # Set mapping ports
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
# HELLO_TOR_SERVICE_HOSTS: 80:hello:80,800:hello:80,8888:hello:80
|
||||||
MIICXQIBAAKBgQDR8TdQF9fDlGhy1SMgfhMBi9TaFeD12/FK27TZE/tYGhxXvs1C
|
# # Set private key
|
||||||
NmFJy1hjVxspF5unmUsCk0yEsvEdcAdp17Vynz6W41VdinETU9yXHlUJ6NyI32AH
|
# HELLO_TOR_SERVICE_KEY: |
|
||||||
dnFnHEcsllSEqD1hPAAvMUWwSMJaNmBEFtl8DUMS9tPX5fWGX4w5Xx8dZwIDAQAB
|
# -----BEGIN RSA PRIVATE KEY-----
|
||||||
AoGBAMb20jMHxaZHWg2qTRYYJa8LdHgS0BZxkWYefnBUbZn7dOz7mM+tddpX6raK
|
# MIICXQIBAAKBgQDR8TdQF9fDlGhy1SMgfhMBi9TaFeD12/FK27TZE/tYGhxXvs1C
|
||||||
8OSqyQu3Tc1tB9GjPLtnVr9KfVwhUVM7YXC/wOZo+u72bv9+4OMrEK/R8xy30XWj
|
# NmFJy1hjVxspF5unmUsCk0yEsvEdcAdp17Vynz6W41VdinETU9yXHlUJ6NyI32AH
|
||||||
GePXEu95yArE4NucYphxBLWMMu2E4RodjyJpczsl0Lohcn4BAkEA+XPaEKnNA3AL
|
# dnFnHEcsllSEqD1hPAAvMUWwSMJaNmBEFtl8DUMS9tPX5fWGX4w5Xx8dZwIDAQAB
|
||||||
1DXRpSpaa0ukGUY/zM7HNUFMW3UP00nxNCpWLSBmrQ56Suy7iSy91oa6HWkDD/4C
|
# AoGBAMb20jMHxaZHWg2qTRYYJa8LdHgS0BZxkWYefnBUbZn7dOz7mM+tddpX6raK
|
||||||
k0HslnMW5wJBANdz4ehByMJZmJu/b5y8wnFSqep2jmJ1InMvd18BfVoBTQJwGMAr
|
# 8OSqyQu3Tc1tB9GjPLtnVr9KfVwhUVM7YXC/wOZo+u72bv9+4OMrEK/R8xy30XWj
|
||||||
+qwSwNXXK2YYl9VJmCPCfgN0o7h1AEzvdYECQAM5UxUqDKNBvHVmqKn4zShb1ugY
|
# GePXEu95yArE4NucYphxBLWMMu2E4RodjyJpczsl0Lohcn4BAkEA+XPaEKnNA3AL
|
||||||
t1RfS8XNbT41WhoB96MT9P8qTwlniX8UZiwUrvNp1Ffy9n4raz8Z+APNwvsCQQC9
|
# 1DXRpSpaa0ukGUY/zM7HNUFMW3UP00nxNCpWLSBmrQ56Suy7iSy91oa6HWkDD/4C
|
||||||
AuaOsReEmMFu8VTjNh2G+TQjgvqKmaQtVNjuOgpUKYv7tYehH3P7/T+62dcy7CRX
|
# k0HslnMW5wJBANdz4ehByMJZmJu/b5y8wnFSqep2jmJ1InMvd18BfVoBTQJwGMAr
|
||||||
cwbLaFbQhUUUD2DCHdkBAkB6CbB+qhu67oE4nnBCXllI9EXktXgFyXv/cScNvM9Y
|
# +qwSwNXXK2YYl9VJmCPCfgN0o7h1AEzvdYECQAM5UxUqDKNBvHVmqKn4zShb1ugY
|
||||||
FDzzNAAfVc5Nmbmx28Nw+0w6pnpe/3m0Tudbq3nHdHfQ
|
# t1RfS8XNbT41WhoB96MT9P8qTwlniX8UZiwUrvNp1Ffy9n4raz8Z+APNwvsCQQC9
|
||||||
-----END RSA PRIVATE KEY-----
|
# AuaOsReEmMFu8VTjNh2G+TQjgvqKmaQtVNjuOgpUKYv7tYehH3P7/T+62dcy7CRX
|
||||||
|
# cwbLaFbQhUUUD2DCHdkBAkB6CbB+qhu67oE4nnBCXllI9EXktXgFyXv/cScNvM9Y
|
||||||
WORLD_PORTS: 8000:80
|
# FDzzNAAfVc5Nmbmx28Nw+0w6pnpe/3m0Tudbq3nHdHfQ
|
||||||
|
# -----END RSA PRIVATE KEY-----
|
||||||
AGAIN_PORTS: 88:80
|
|
||||||
|
|
||||||
# hello and again will share the same onion_adress
|
# hello and again will share the same onion_adress
|
||||||
AGAIN_SERVICE_NAME: foo
|
FOO_TOR_SERVICE_HOSTS: 88:again:80,8000:world:80
|
||||||
HELLO_SERVICE_NAME: foo
|
# tor v3 address private key base 64 encoded
|
||||||
|
FOO_TOR_SERVICE_KEY: |
|
||||||
|
PT0gZWQyNTUxOXYxLXNlY3JldDogdHlwZTAgPT0AAABYZRzL3zScTEqA8/5wfvHw
|
||||||
|
yLIzmih73lhgPGPh7SuOS6GTou4tXgNlTYSNb/Fvk1ajTTUno4tIQn/jMENO/20G
|
||||||
|
|
||||||
# Keep keys in volumes
|
# Keep keys in volumes
|
||||||
volumes:
|
volumes:
|
||||||
|
|
55
docker-compose.v3.latest.yml
Normal file
55
docker-compose.v3.latest.yml
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# docker version 3 example
|
||||||
|
|
||||||
|
version: "3.1"
|
||||||
|
|
||||||
|
services:
|
||||||
|
tor:
|
||||||
|
image: goldy/tor-hidden-service:latest
|
||||||
|
links:
|
||||||
|
- hello
|
||||||
|
- world
|
||||||
|
- again
|
||||||
|
environment:
|
||||||
|
# Set version 3 on BAR group
|
||||||
|
BAR_TOR_SERVICE_HOSTS: '80:hello:80,88:world:80'
|
||||||
|
# This is now optional as v2 are not supported anymore by tor network
|
||||||
|
BAR_TOR_SERVICE_VERSION: '3'
|
||||||
|
|
||||||
|
# hello and again will share the same onion_adress
|
||||||
|
FOO_TOR_SERVICE_HOSTS: '88:again:80,80:hello:80,800:hello:80,8888:hello:80'
|
||||||
|
|
||||||
|
|
||||||
|
# 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
|
||||||
|
- source: bar
|
||||||
|
target: bar
|
||||||
|
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: ./private_key_foo_v3
|
||||||
|
bar:
|
||||||
|
file: ./private_key_bar_v3
|
|
@ -4,23 +4,20 @@ version: "3.1"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
tor:
|
tor:
|
||||||
image: goldy/tor-hidden-service
|
image: goldy/tor-hidden-service:$CUR_TAG
|
||||||
build: .
|
|
||||||
links:
|
links:
|
||||||
- hello
|
- hello
|
||||||
- world
|
- world
|
||||||
- again
|
- again
|
||||||
environment:
|
environment:
|
||||||
# Set mapping ports
|
# Set version 3 on BAR group
|
||||||
HELLO_PORTS: 80:80,800:80,8888:80
|
BAR_TOR_SERVICE_HOSTS: '80:hello:80,88:world:80'
|
||||||
|
# This is now optional as v2 are not supported anymore by tor network
|
||||||
WORLD_PORTS: 8000:80
|
BAR_TOR_SERVICE_VERSION: '3'
|
||||||
|
|
||||||
AGAIN_PORTS: 88:80
|
|
||||||
|
|
||||||
# hello and again will share the same onion_adress
|
# hello and again will share the same onion_adress
|
||||||
AGAIN_SERVICE_NAME: foo
|
FOO_TOR_SERVICE_HOSTS: '88:again:80,80:hello:80,800:hello:80,8888:hello:80'
|
||||||
HELLO_SERVICE_NAME: foo
|
|
||||||
|
|
||||||
# Keep keys in volumes
|
# Keep keys in volumes
|
||||||
volumes:
|
volumes:
|
||||||
|
@ -28,9 +25,8 @@ services:
|
||||||
|
|
||||||
# Set secret for key, use the same name as the service
|
# Set secret for key, use the same name as the service
|
||||||
secrets:
|
secrets:
|
||||||
- source: foo
|
- foo
|
||||||
target: foo
|
- bar
|
||||||
mode: 0400
|
|
||||||
|
|
||||||
hello:
|
hello:
|
||||||
image: tutum/hello-world
|
image: tutum/hello-world
|
||||||
|
@ -50,4 +46,6 @@ volumes:
|
||||||
|
|
||||||
secrets:
|
secrets:
|
||||||
foo:
|
foo:
|
||||||
file: ./foo_private_key
|
file: ./private_key_foo_v3
|
||||||
|
bar:
|
||||||
|
file: ./private_key_bar_v3
|
||||||
|
|
105
docker-compose.vanguards-network.yml
Normal file
105
docker-compose.vanguards-network.yml
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
# Run secure vanguards using network
|
||||||
|
version: "3.1"
|
||||||
|
|
||||||
|
services:
|
||||||
|
# Tor container
|
||||||
|
tor:
|
||||||
|
image: goldy/tor-hidden-service:$CUR_TAG
|
||||||
|
environment:
|
||||||
|
# Enable control port with ip binding (see networks configuration bellow)
|
||||||
|
# Using network interface instead of 0.0.0.0 help to protect control port from hidden services.
|
||||||
|
TOR_CONTROL_PORT: 172.16.111.10
|
||||||
|
# Set controle port password (optionnal)
|
||||||
|
TOR_CONTROL_PASSWORD: something_secret
|
||||||
|
|
||||||
|
# You can change any options here, excepted control_* ones and state_file
|
||||||
|
VANGUARDS_EXTRA_OPTIONS: |
|
||||||
|
[Global]
|
||||||
|
enable_cbtverify = True
|
||||||
|
loglevel = DEBUG
|
||||||
|
|
||||||
|
HELLO_TOR_SERVICE_HOSTS: '80:hello:80'
|
||||||
|
HELLO_TOR_SERVICE_VERSION: '3'
|
||||||
|
|
||||||
|
# Keep keys in volumes
|
||||||
|
volumes:
|
||||||
|
# Keep keys in volumes
|
||||||
|
- tor-keys:/var/lib/tor/hidden_service/
|
||||||
|
- tor-data:/run/tor/data
|
||||||
|
|
||||||
|
# Set secret for key, use the same name as the service
|
||||||
|
secrets:
|
||||||
|
- source: hello
|
||||||
|
target: hello
|
||||||
|
mode: 0400
|
||||||
|
|
||||||
|
networks:
|
||||||
|
hidden_services:
|
||||||
|
ipv4_address: 172.16.222.10
|
||||||
|
tor_control:
|
||||||
|
# Set an ip address for tor_control network to bind for the good network
|
||||||
|
ipv4_address: 172.16.111.10
|
||||||
|
|
||||||
|
|
||||||
|
# Vanguards container
|
||||||
|
vanguards:
|
||||||
|
depends_on:
|
||||||
|
- tor
|
||||||
|
|
||||||
|
# Use the same image
|
||||||
|
image: goldy/tor-hidden-service:$CUR_TAG
|
||||||
|
|
||||||
|
# Run vanguards
|
||||||
|
command: vanguards
|
||||||
|
|
||||||
|
environment:
|
||||||
|
# Set tor hostname (or ip:port or unix:/path/to/socket.sock)
|
||||||
|
TOR_CONTROL_PORT: tor:9051
|
||||||
|
# set password if needed
|
||||||
|
TOR_CONTROL_PASSWORD: something_secret
|
||||||
|
|
||||||
|
# Vanguards is assigned to tor_control network
|
||||||
|
networks:
|
||||||
|
- tor_control
|
||||||
|
|
||||||
|
# Sharing tor-data volume with tor container
|
||||||
|
volumes:
|
||||||
|
- tor-data:/run/tor/data
|
||||||
|
|
||||||
|
|
||||||
|
# Hidden service container
|
||||||
|
hello:
|
||||||
|
image: tutum/hello-world
|
||||||
|
hostname: hello
|
||||||
|
depends_on:
|
||||||
|
- tor
|
||||||
|
# this hidden service is assigned to hidden_services network
|
||||||
|
networks:
|
||||||
|
- hidden_services
|
||||||
|
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
tor-keys:
|
||||||
|
driver: local
|
||||||
|
tor-data:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
secrets:
|
||||||
|
hello:
|
||||||
|
file: ./private_key_bar_v3
|
||||||
|
|
||||||
|
networks:
|
||||||
|
# This network is used for hidden services
|
||||||
|
hidden_services:
|
||||||
|
driver: bridge
|
||||||
|
ipam:
|
||||||
|
driver: default
|
||||||
|
config:
|
||||||
|
- subnet: 172.16.222.0/24
|
||||||
|
# This network is used for vagrands to get access to tor
|
||||||
|
tor_control:
|
||||||
|
driver: bridge
|
||||||
|
ipam:
|
||||||
|
driver: default
|
||||||
|
config:
|
||||||
|
- subnet: 172.16.111.0/24
|
44
docker-compose.vanguards.yml
Normal file
44
docker-compose.vanguards.yml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# Secure vangard in the same container
|
||||||
|
|
||||||
|
version: "3.1"
|
||||||
|
|
||||||
|
services:
|
||||||
|
tor:
|
||||||
|
image: goldy/tor-hidden-service:$CUR_TAG
|
||||||
|
environment:
|
||||||
|
# Enable Vanguards like this
|
||||||
|
TOR_ENABLE_VANGUARDS: 'true'
|
||||||
|
|
||||||
|
# You can change any options here, excepted control_* ones
|
||||||
|
VANGUARDS_EXTRA_OPTIONS: |
|
||||||
|
[Global]
|
||||||
|
enable_cbtverify = True
|
||||||
|
loglevel = DEBUG
|
||||||
|
|
||||||
|
HELLO_TOR_SERVICE_HOSTS: '80:hello:80'
|
||||||
|
HELLO_TOR_SERVICE_VERSION: '3'
|
||||||
|
|
||||||
|
|
||||||
|
# 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: hello
|
||||||
|
target: hello
|
||||||
|
mode: 0400
|
||||||
|
|
||||||
|
hello:
|
||||||
|
image: tutum/hello-world
|
||||||
|
hostname: hello
|
||||||
|
depends_on:
|
||||||
|
- tor
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
tor-keys:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
secrets:
|
||||||
|
hello:
|
||||||
|
file: ./private_key_bar_v3
|
|
@ -1,19 +0,0 @@
|
||||||
# docker-compose.yml example
|
|
||||||
|
|
||||||
tor:
|
|
||||||
image: goldy/tor-hidden-service
|
|
||||||
links:
|
|
||||||
- hello
|
|
||||||
- world
|
|
||||||
environment:
|
|
||||||
PORT_MAP: 80 # Map port to detected service
|
|
||||||
volumes:
|
|
||||||
- ./keys:/var/lib/tor/hidden_service/
|
|
||||||
|
|
||||||
hello:
|
|
||||||
image: tutum/hello-world
|
|
||||||
hostname: hello
|
|
||||||
|
|
||||||
world:
|
|
||||||
image: tutum/hello-world
|
|
||||||
hostname: world
|
|
|
@ -1,15 +0,0 @@
|
||||||
-----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-----
|
|
5
hooks/build
Normal file
5
hooks/build
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/bash
|
||||||
|
v1="${SOURCE_BRANCH%-*}"
|
||||||
|
tor_version=${v1:1}
|
||||||
|
|
||||||
|
docker build --build-arg tor_version=${tor_version} -f $DOCKERFILE_PATH -t $IMAGE_NAME .
|
4
hooks/post_push
Normal file
4
hooks/post_push
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
docker tag $IMAGE_NAME ${DOCKER_REPO}:latest
|
||||||
|
docker push ${DOCKER_REPO}:latest
|
2
last_tor_version.sh
Executable file
2
last_tor_version.sh
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/bash
|
||||||
|
git ls-remote --tags https://git.torproject.org/tor.git | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1
|
2
last_torsocks_version.sh
Executable file
2
last_torsocks_version.sh
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
git ls-remote --tags https://git.torproject.org/torsocks.git | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1
|
521
onions/Onions.py
Normal file
521
onions/Onions.py
Normal file
|
@ -0,0 +1,521 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from base64 import b64decode
|
||||||
|
from json import dumps
|
||||||
|
from re import match
|
||||||
|
|
||||||
|
from IPy import IP
|
||||||
|
from jinja2 import Environment
|
||||||
|
from jinja2 import FileSystemLoader
|
||||||
|
from pyentrypoint import DockerLinks
|
||||||
|
from pyentrypoint.config import envtobool
|
||||||
|
from pyentrypoint.configparser import ConfigParser
|
||||||
|
|
||||||
|
from .Service import Service
|
||||||
|
from .Service import ServicesGroup
|
||||||
|
|
||||||
|
|
||||||
|
class Setup(object):
|
||||||
|
|
||||||
|
hidden_service_dir = "/var/lib/tor/hidden_service/"
|
||||||
|
data_directory = "/run/tor/data"
|
||||||
|
torrc = '/etc/tor/torrc'
|
||||||
|
torrc_template = '/var/local/tor/torrc.tpl'
|
||||||
|
enable_control_port = False
|
||||||
|
control_port = 9051
|
||||||
|
control_ip_binding = IP('0.0.0.0')
|
||||||
|
control_hashed_password = None
|
||||||
|
control_socket = 'unix:/run/tor/tor_control.sock'
|
||||||
|
enable_vanguards = False
|
||||||
|
vanguards_template = '/var/local/tor/vanguards.conf.tpl'
|
||||||
|
vanguards_conf = '/etc/tor/vanguards.conf'
|
||||||
|
|
||||||
|
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'] = {host: []}
|
||||||
|
if host not in self.setup[host]['ports']:
|
||||||
|
self.setup[host]['ports'][host] = []
|
||||||
|
ports_l = [
|
||||||
|
[
|
||||||
|
int(v) if not v.startswith('unix:') else v
|
||||||
|
for v in sp.split(':', 1)
|
||||||
|
] for sp in ports.split(',')
|
||||||
|
]
|
||||||
|
for port in ports_l:
|
||||||
|
assert len(port) == 2
|
||||||
|
if port not in self.setup[host]['ports'][host]:
|
||||||
|
self.setup[host]['ports'][host].append(port)
|
||||||
|
|
||||||
|
def _hash_control_port_password(self, password):
|
||||||
|
self.control_hashed_password = subprocess.check_output(
|
||||||
|
['/usr/local/bin/tor', '--quiet', '--hash-password', password],
|
||||||
|
env={'HOME': '/tmp'}
|
||||||
|
).decode()
|
||||||
|
|
||||||
|
def _parse_control_port_variable(self, check_ip=True):
|
||||||
|
control_port = os.environ['TOR_CONTROL_PORT']
|
||||||
|
try:
|
||||||
|
if control_port.startswith('unix:'):
|
||||||
|
self.control_socket = control_port
|
||||||
|
return
|
||||||
|
self.control_socket = None
|
||||||
|
if ':' in control_port:
|
||||||
|
host, port = control_port.split(':')
|
||||||
|
self.control_ip_binding = IP(host) if check_ip else host
|
||||||
|
self.control_port = int(port)
|
||||||
|
return
|
||||||
|
self.control_ip_binding = (
|
||||||
|
IP(control_port) if check_ip else control_port
|
||||||
|
)
|
||||||
|
except BaseException as e:
|
||||||
|
logging.error('TOR_CONTROL_PORT environment variable error')
|
||||||
|
logging.exception(e)
|
||||||
|
|
||||||
|
def _setup_control_port(self):
|
||||||
|
if 'TOR_CONTROL_PORT' not in os.environ:
|
||||||
|
return
|
||||||
|
self.enable_control_port = True
|
||||||
|
self._parse_control_port_variable()
|
||||||
|
|
||||||
|
if os.environ.get('TOR_CONTROL_PASSWORD'):
|
||||||
|
self._hash_control_port_password(os.environ[
|
||||||
|
'TOR_CONTROL_PASSWORD'
|
||||||
|
])
|
||||||
|
if envtobool('TOR_DATA_DIRECTORY', False):
|
||||||
|
self.data_directory = os.environ['TOR_DATA_DIRECTORY']
|
||||||
|
|
||||||
|
def _setup_vanguards(self):
|
||||||
|
if not envtobool('TOR_ENABLE_VANGUARDS', False):
|
||||||
|
return
|
||||||
|
self.enable_control_port = True
|
||||||
|
self.enable_vanguards = True
|
||||||
|
os.environ.setdefault('TOR_CONTROL_PORT', self.control_socket)
|
||||||
|
self.kill_tor_on_vanguard_exit = envtobool(
|
||||||
|
'VANGUARD_KILL_TOR_ON_EXIT',
|
||||||
|
True
|
||||||
|
)
|
||||||
|
self.vanguards_state_file = os.path.join(
|
||||||
|
self.data_directory,
|
||||||
|
'vanguards.state'
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_key(self, host, key):
|
||||||
|
self._add_host(host)
|
||||||
|
assert len(key) > 800
|
||||||
|
self.setup[host]['key'] = key
|
||||||
|
|
||||||
|
def _load_keys_in_services(self, secret=True):
|
||||||
|
for service in self.services:
|
||||||
|
service.load_key(secret=secret)
|
||||||
|
|
||||||
|
def _get_service(self, host, service):
|
||||||
|
self._add_host(host)
|
||||||
|
self.setup[host]['service'] = service
|
||||||
|
|
||||||
|
def find_group_by_service(self, service):
|
||||||
|
for group in self.services:
|
||||||
|
if service in group.services:
|
||||||
|
return group
|
||||||
|
|
||||||
|
def find_group_by_name(self, name):
|
||||||
|
for group in self.services:
|
||||||
|
if name == group.name:
|
||||||
|
return group
|
||||||
|
|
||||||
|
def find_service_by_host(self, host):
|
||||||
|
for group in self.services:
|
||||||
|
service = group.get_service_by_host(host)
|
||||||
|
if service:
|
||||||
|
return service
|
||||||
|
|
||||||
|
def add_empty_group(self, name, version=None):
|
||||||
|
if self.find_group_by_name(name):
|
||||||
|
raise Exception('Group {name} already exists'.format(name=name))
|
||||||
|
group = ServicesGroup(name=name, version=version)
|
||||||
|
self.services.append(group)
|
||||||
|
return group
|
||||||
|
|
||||||
|
def add_new_service(self,
|
||||||
|
host,
|
||||||
|
name=None,
|
||||||
|
ports=None,
|
||||||
|
key=None):
|
||||||
|
group = self.find_group_by_name(name)
|
||||||
|
if group:
|
||||||
|
service = group.get_service_by_host(host)
|
||||||
|
else:
|
||||||
|
service = self.find_service_by_host(host)
|
||||||
|
if not service:
|
||||||
|
service = Service(host=host)
|
||||||
|
if not group:
|
||||||
|
group = ServicesGroup(
|
||||||
|
service=service,
|
||||||
|
name=name,
|
||||||
|
hidden_service_dir=self.hidden_service_dir,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
group.add_service(service)
|
||||||
|
if group not in self.services:
|
||||||
|
self.services.append(group)
|
||||||
|
elif group and service not in group.services:
|
||||||
|
group.add_service(service)
|
||||||
|
else:
|
||||||
|
self.find_group_by_service(service)
|
||||||
|
if key:
|
||||||
|
group.add_key(key)
|
||||||
|
if ports:
|
||||||
|
service.add_ports(ports)
|
||||||
|
return service
|
||||||
|
|
||||||
|
def _set_service_names(self):
|
||||||
|
'Create groups for services, should be run first'
|
||||||
|
reg = r'([A-Z0-9]*)_SERVICE_NAME'
|
||||||
|
for key, val in os.environ.items():
|
||||||
|
m = match(reg, key)
|
||||||
|
if m:
|
||||||
|
try:
|
||||||
|
self.add_new_service(host=m.groups()[0].lower(), name=val)
|
||||||
|
except BaseException as e:
|
||||||
|
logging.error(f"Fail to setup from {key} environment")
|
||||||
|
logging.error(e)
|
||||||
|
|
||||||
|
def _set_ports(self, host, ports):
|
||||||
|
self.add_new_service(host=host, ports=ports)
|
||||||
|
|
||||||
|
def _set_key(self, host, key):
|
||||||
|
self.add_new_service(host=host, key=key.encode())
|
||||||
|
|
||||||
|
def _setup_from_env(self, match_map):
|
||||||
|
for reg, call in match_map:
|
||||||
|
for key, val in os.environ.items():
|
||||||
|
m = match(reg, key)
|
||||||
|
# Ignore GPG_KEY env variable to avoid warning
|
||||||
|
# (this is a deprecated setup)
|
||||||
|
if m and key != 'GPG_KEY':
|
||||||
|
try:
|
||||||
|
call(m.groups()[0].lower(), val)
|
||||||
|
except BaseException as e:
|
||||||
|
logging.error(f"Fail to setup from {key} environment")
|
||||||
|
logging.error(e)
|
||||||
|
|
||||||
|
def _setup_keys_and_ports_from_env(self):
|
||||||
|
self._setup_from_env(
|
||||||
|
(
|
||||||
|
(r'([A-Z0-9]+)_PORTS', self._set_ports),
|
||||||
|
(r'([A-Z0-9]+)_KEY', self._set_key),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_or_create_empty_group(self, name, version=None):
|
||||||
|
group = self.find_group_by_name(name)
|
||||||
|
if group:
|
||||||
|
if version:
|
||||||
|
group.set_version(version)
|
||||||
|
return group
|
||||||
|
return self.add_empty_group(name, version)
|
||||||
|
|
||||||
|
def _set_group_version(self, name, version):
|
||||||
|
'Setup groups with version'
|
||||||
|
group = self.get_or_create_empty_group(name, version=version)
|
||||||
|
group.set_version(version)
|
||||||
|
|
||||||
|
def _set_group_key(self, name, key):
|
||||||
|
'Set key for service group'
|
||||||
|
group = self.get_or_create_empty_group(name)
|
||||||
|
if group.version == 3:
|
||||||
|
group.add_key(b64decode(key))
|
||||||
|
else:
|
||||||
|
group.add_key(key)
|
||||||
|
|
||||||
|
def _set_group_hosts(self, name, hosts):
|
||||||
|
'Set services for service groups'
|
||||||
|
self.get_or_create_empty_group(name)
|
||||||
|
for host_map in hosts.split(','):
|
||||||
|
host_map = host_map.strip()
|
||||||
|
port_from, host, port_dest = host_map.split(':', 2)
|
||||||
|
if host == 'unix' and port_dest.startswith('/'):
|
||||||
|
self.add_new_service(host=name, name=name, ports=host_map)
|
||||||
|
else:
|
||||||
|
ports = '{frm}:{dst}'.format(frm=port_from, dst=port_dest)
|
||||||
|
self.add_new_service(host=host, name=name, ports=ports)
|
||||||
|
|
||||||
|
def _setup_services_from_env(self):
|
||||||
|
self._setup_from_env(
|
||||||
|
(
|
||||||
|
(r'([A-Z0-9]+)_TOR_SERVICE_VERSION', self._set_group_version),
|
||||||
|
(r'([A-Z0-9]+)_TOR_SERVICE_KEY', self._set_group_key),
|
||||||
|
(r'([A-Z0-9]+)_TOR_SERVICE_HOSTS', self._set_group_hosts),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_setup_from_env(self):
|
||||||
|
self._set_service_names()
|
||||||
|
self._setup_keys_and_ports_from_env()
|
||||||
|
self._setup_services_from_env()
|
||||||
|
|
||||||
|
def _get_setup_from_links(self):
|
||||||
|
containers = DockerLinks().to_containers()
|
||||||
|
if not containers:
|
||||||
|
return
|
||||||
|
for container in containers:
|
||||||
|
host = container.names[0]
|
||||||
|
self.add_new_service(host=host)
|
||||||
|
for link in container.links:
|
||||||
|
if link.protocol != 'tcp':
|
||||||
|
continue
|
||||||
|
port_map = os.environ.get('PORT_MAP')
|
||||||
|
self._set_ports(host, '{exposed}:{internal}'.format(
|
||||||
|
exposed=port_map or link.port,
|
||||||
|
internal=link.port,
|
||||||
|
))
|
||||||
|
|
||||||
|
def apply_conf(self):
|
||||||
|
self._write_keys()
|
||||||
|
self._write_torrc()
|
||||||
|
if self.enable_vanguards:
|
||||||
|
self._write_vanguards_conf()
|
||||||
|
|
||||||
|
def _write_keys(self):
|
||||||
|
for service in self.services:
|
||||||
|
service.write_key()
|
||||||
|
|
||||||
|
def _write_torrc(self):
|
||||||
|
env = Environment(loader=FileSystemLoader('/'))
|
||||||
|
temp = env.get_template(self.torrc_template)
|
||||||
|
with open(self.torrc, mode='w') as f:
|
||||||
|
f.write(temp.render(onion=self,
|
||||||
|
env=os.environ,
|
||||||
|
envtobool=envtobool,
|
||||||
|
type=type,
|
||||||
|
int=int))
|
||||||
|
|
||||||
|
def _write_vanguards_conf(self):
|
||||||
|
env = Environment(loader=FileSystemLoader('/'))
|
||||||
|
temp = env.get_template(self.vanguards_template)
|
||||||
|
with open(self.vanguards_conf, mode='w') as f:
|
||||||
|
f.write(temp.render(env=os.environ,
|
||||||
|
ConfigParser=ConfigParser,
|
||||||
|
envtobool=envtobool))
|
||||||
|
|
||||||
|
def run_vanguards(self):
|
||||||
|
self._setup_vanguards()
|
||||||
|
if not self.enable_vanguards:
|
||||||
|
return
|
||||||
|
logging.info('Vanguard enabled, starting...')
|
||||||
|
if not self.kill_tor_on_vanguard_exit:
|
||||||
|
os.execvp('vanguards', ['vanguards'])
|
||||||
|
try:
|
||||||
|
subprocess.check_call('vanguards')
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error(str(e))
|
||||||
|
finally:
|
||||||
|
logging.error('Vanguards has exited, killing tor...')
|
||||||
|
os.kill(1, 2)
|
||||||
|
|
||||||
|
def resolve_control_hostname(self):
|
||||||
|
try:
|
||||||
|
addr = socket.getaddrinfo(self.control_ip_binding,
|
||||||
|
None,
|
||||||
|
socket.AF_INET,
|
||||||
|
socket.SOCK_STREAM,
|
||||||
|
socket.IPPROTO_TCP)
|
||||||
|
except socket.gaierror:
|
||||||
|
raise
|
||||||
|
return IP(addr[0][4][0])
|
||||||
|
|
||||||
|
def resolve_control_port(self):
|
||||||
|
if 'TOR_CONTROL_PORT' not in os.environ:
|
||||||
|
return
|
||||||
|
self._parse_control_port_variable(check_ip=False)
|
||||||
|
if self.control_socket:
|
||||||
|
print(os.environ['TOR_CONTROL_PORT'])
|
||||||
|
try:
|
||||||
|
ip = IP(self.control_ip_binding)
|
||||||
|
except ValueError:
|
||||||
|
ip = self.resolve_control_hostname()
|
||||||
|
print(f"{ip}:{self.control_port}")
|
||||||
|
|
||||||
|
def setup_hosts(self):
|
||||||
|
self.setup = {}
|
||||||
|
self._get_setup_from_env()
|
||||||
|
self._get_setup_from_links()
|
||||||
|
self._load_keys_in_services()
|
||||||
|
self.check_services()
|
||||||
|
self._setup_vanguards()
|
||||||
|
self._setup_control_port()
|
||||||
|
self.apply_conf()
|
||||||
|
|
||||||
|
def check_services(self):
|
||||||
|
to_remove = set()
|
||||||
|
for group in self.services:
|
||||||
|
try:
|
||||||
|
for service in group.services:
|
||||||
|
if not service.ports:
|
||||||
|
raise Exception(
|
||||||
|
'Service {name} has not ports set'.format(
|
||||||
|
name=service.host
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if len(group.services) > 1 and [
|
||||||
|
True for p in service.ports if p.is_socket
|
||||||
|
]:
|
||||||
|
raise Exception(
|
||||||
|
f'Cannot use socket and ports '
|
||||||
|
f'in the same {service.host}'
|
||||||
|
)
|
||||||
|
if len(set(dict(group)['urls'])) != len(dict(group)['urls']):
|
||||||
|
raise Exception(
|
||||||
|
f'Same port for multiple services in '
|
||||||
|
f'{group.name} group'
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(e)
|
||||||
|
to_remove.add(group)
|
||||||
|
for group in to_remove:
|
||||||
|
self.services.remove(group)
|
||||||
|
|
||||||
|
|
||||||
|
class Onions(Setup):
|
||||||
|
"""Onions"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.services = []
|
||||||
|
if 'HIDDEN_SERVICE_DIR' in os.environ:
|
||||||
|
self.hidden_service_dir = os.environ['HIDDEN_SERVICE_DIR']
|
||||||
|
if os.environ.get('TOR_DATA_DIRECTORY'):
|
||||||
|
self.data_directory = os.environ['TOR_DATA_DIRECTORY']
|
||||||
|
|
||||||
|
def torrc_parser(self):
|
||||||
|
|
||||||
|
self.torrc_dict = {}
|
||||||
|
|
||||||
|
def parse_dir(line):
|
||||||
|
_, path = line.split()
|
||||||
|
group_name = os.path.basename(path)
|
||||||
|
self.torrc_dict[group_name] = {
|
||||||
|
'services': [],
|
||||||
|
}
|
||||||
|
return group_name
|
||||||
|
|
||||||
|
def parse_port(line, name):
|
||||||
|
_, port_from, dest = line.split()
|
||||||
|
service_host, port = dest.split(':')
|
||||||
|
ports_str = '{port_from}:{dest}'
|
||||||
|
ports_param = ports_str.format(port_from=port_from,
|
||||||
|
dest=port)
|
||||||
|
if port.startswith('/'):
|
||||||
|
service_host = name
|
||||||
|
ports_param = ports_str.format(port_from=port_from,
|
||||||
|
dest=dest)
|
||||||
|
self.torrc_dict[name]['services'].append({
|
||||||
|
'host': service_host,
|
||||||
|
'ports': ports_param,
|
||||||
|
})
|
||||||
|
|
||||||
|
def parse_version(line, name):
|
||||||
|
_, version = line.split()
|
||||||
|
self.torrc_dict[name]['version'] = int(version)
|
||||||
|
|
||||||
|
def setup_services():
|
||||||
|
for name, setup in self.torrc_dict.items():
|
||||||
|
version = setup.get('version', 3)
|
||||||
|
group = (self.find_group_by_name(name)
|
||||||
|
or self.add_empty_group(name, version=version))
|
||||||
|
for service_dict in setup.get('services', []):
|
||||||
|
host = service_dict['host']
|
||||||
|
service = (group.get_service_by_host(host)
|
||||||
|
or Service(host))
|
||||||
|
service.add_ports(service_dict['ports'])
|
||||||
|
if service not in group.services:
|
||||||
|
group.add_service(service)
|
||||||
|
self._load_keys_in_services(secret=False)
|
||||||
|
|
||||||
|
if not os.path.exists(self.torrc):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
with open(self.torrc, 'r') as f:
|
||||||
|
for line in f.readlines():
|
||||||
|
if line.startswith('HiddenServiceDir'):
|
||||||
|
name = parse_dir(line)
|
||||||
|
if line.startswith('HiddenServicePort'):
|
||||||
|
parse_port(line, name)
|
||||||
|
if line.startswith('HiddenServiceVersion'):
|
||||||
|
parse_version(line, name)
|
||||||
|
except BaseException:
|
||||||
|
raise Exception(
|
||||||
|
'Fail to parse torrc file. Please check the file'
|
||||||
|
)
|
||||||
|
setup_services()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if not self.services:
|
||||||
|
return 'No onion site'
|
||||||
|
return '\n'.join([str(service) for service in self.services])
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
service_lst = [dict(service) for service in self.services]
|
||||||
|
return dumps({
|
||||||
|
service['name']: service['urls'] for service in service_lst
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
logging.basicConfig()
|
||||||
|
parser = argparse.ArgumentParser(description='Display onion sites',
|
||||||
|
prog='onions')
|
||||||
|
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')
|
||||||
|
|
||||||
|
parser.add_argument('--run-vanguards', dest='vanguards',
|
||||||
|
action='store_true',
|
||||||
|
help='Run Vanguards in tor container')
|
||||||
|
parser.add_argument('--resolve-control-port', dest='resolve_control_port',
|
||||||
|
action='store_true',
|
||||||
|
help='Resolve ip from host if needed')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
logging.getLogger().setLevel(logging.WARNING)
|
||||||
|
try:
|
||||||
|
onions = Onions()
|
||||||
|
if args.vanguards:
|
||||||
|
onions.run_vanguards()
|
||||||
|
return
|
||||||
|
if args.resolve_control_port:
|
||||||
|
onions.resolve_control_port()
|
||||||
|
return
|
||||||
|
if args.setup:
|
||||||
|
onions.setup_hosts()
|
||||||
|
else:
|
||||||
|
onions.torrc_parser()
|
||||||
|
except BaseException as e:
|
||||||
|
logging.exception(e)
|
||||||
|
error_msg = str(e)
|
||||||
|
else:
|
||||||
|
error_msg = None
|
||||||
|
if args.json:
|
||||||
|
if error_msg:
|
||||||
|
print(dumps({'error': error_msg}))
|
||||||
|
sys.exit(1)
|
||||||
|
print(onions.to_json())
|
||||||
|
else:
|
||||||
|
if error_msg:
|
||||||
|
logging.error(error_msg)
|
||||||
|
sys.exit(1)
|
||||||
|
print(onions)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -1,43 +1,64 @@
|
||||||
'This class define a service link'
|
'This class define a service link'
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
import re
|
import re
|
||||||
from base64 import b32encode
|
|
||||||
from hashlib import sha1
|
|
||||||
|
|
||||||
from Crypto.PublicKey import RSA
|
from pytor import OnionV3
|
||||||
|
from pytor.onion import EmptyDirException
|
||||||
|
|
||||||
|
|
||||||
class ServicesGroup(object):
|
class ServicesGroup(object):
|
||||||
|
|
||||||
name = None
|
name = None
|
||||||
_priv_key = None
|
version = None
|
||||||
_key_in_secrets = False
|
imported_key = False
|
||||||
|
_default_version = 3
|
||||||
|
_onion = None
|
||||||
|
_hidden_service_dir = "/var/lib/tor/hidden_service/"
|
||||||
|
|
||||||
hidden_service_dir = "/var/lib/tor/hidden_service/"
|
def __init__(self,
|
||||||
|
name=None,
|
||||||
def __init__(self, name=None, service=None, hidden_service_dir=None):
|
service=None,
|
||||||
|
version=None,
|
||||||
|
hidden_service_dir=None):
|
||||||
|
|
||||||
name_regex = r'^[a-zA-Z0-9-_]+$'
|
name_regex = r'^[a-zA-Z0-9-_]+$'
|
||||||
|
|
||||||
self.hidden_service_dir = hidden_service_dir or self.hidden_service_dir
|
self.onion_map = {
|
||||||
|
3: OnionV3,
|
||||||
|
}
|
||||||
|
|
||||||
if not name and not service:
|
if not name and not service:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Init service group with a name or service at least'
|
'Init service group with a name or service at least'
|
||||||
)
|
)
|
||||||
self.services = []
|
self.services = []
|
||||||
self.name = name or service.host
|
self.name = name or service.host
|
||||||
|
if hidden_service_dir:
|
||||||
|
self._hidden_service_dir = hidden_service_dir
|
||||||
if not re.match(name_regex, self.name):
|
if not re.match(name_regex, self.name):
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Group {name} has invalid name'.format(name=self.name)
|
'Group {name} has invalid name'.format(name=self.name)
|
||||||
)
|
)
|
||||||
if service:
|
if service:
|
||||||
self.add_service(service)
|
self.add_service(service)
|
||||||
|
self.set_version(version or self._default_version)
|
||||||
self.load_key()
|
|
||||||
if not self._priv_key:
|
|
||||||
self.gen_key()
|
self.gen_key()
|
||||||
|
|
||||||
|
def set_version(self, version):
|
||||||
|
version = int(version)
|
||||||
|
if version not in self.onion_map:
|
||||||
|
raise Exception(
|
||||||
|
f'Url version {version} is not supported'
|
||||||
|
)
|
||||||
|
self.version = version
|
||||||
|
self._onion = self.onion_map[version]()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hidden_service_dir(self):
|
||||||
|
return os.path.join(self._hidden_service_dir, self.name)
|
||||||
|
|
||||||
def add_service(self, service):
|
def add_service(self, service):
|
||||||
if service not in self.services:
|
if service not in self.services:
|
||||||
if self.get_service_by_host(service.host):
|
if self.get_service_by_host(service.host):
|
||||||
|
@ -50,15 +71,18 @@ class ServicesGroup(object):
|
||||||
return service
|
return service
|
||||||
|
|
||||||
def add_key(self, key):
|
def add_key(self, key):
|
||||||
if self._key_in_secrets:
|
if self.imported_key:
|
||||||
logging.warning('Secret key already set, overriding')
|
logging.warning('Secret key already set, overriding')
|
||||||
self._priv_key = key
|
if isinstance(key, str):
|
||||||
self._key_in_secrets = False
|
key = key.encode('ascii')
|
||||||
|
self._onion.set_private_key(key)
|
||||||
|
self.imported_key = True
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
yield 'name', self.name
|
yield 'name', self.name
|
||||||
yield 'onion', self.onion_url
|
yield 'onion', self.onion_url
|
||||||
yield 'urls', list(self.urls)
|
yield 'urls', list(self.urls)
|
||||||
|
yield 'version', self.version
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{name}: {urls}'.format(name=self.name,
|
return '{name}: {urls}'.format(name=self.name,
|
||||||
|
@ -66,16 +90,7 @@ class ServicesGroup(object):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def onion_url(self):
|
def onion_url(self):
|
||||||
"Get onion url from private key"
|
return self._onion.onion_hostname
|
||||||
|
|
||||||
# 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
|
@property
|
||||||
def urls(self):
|
def urls(self):
|
||||||
|
@ -88,30 +103,18 @@ class ServicesGroup(object):
|
||||||
'Write key on disk and set tor service'
|
'Write key on disk and set tor service'
|
||||||
if not hidden_service_dir:
|
if not hidden_service_dir:
|
||||||
hidden_service_dir = self.hidden_service_dir
|
hidden_service_dir = self.hidden_service_dir
|
||||||
serv_dir = os.path.join(hidden_service_dir, self.name)
|
if not os.path.isdir(hidden_service_dir):
|
||||||
os.makedirs(serv_dir, exist_ok=True)
|
pathlib.Path(hidden_service_dir).mkdir(parents=True)
|
||||||
os.chmod(serv_dir, 0o700)
|
self._onion.write_hidden_service(hidden_service_dir, force=True)
|
||||||
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):
|
def _load_key(self, key_file):
|
||||||
if os.path.exists(key_file):
|
with open(key_file, 'rb') as f:
|
||||||
with open(key_file, 'r') as f:
|
self._onion.set_private_key_from_file(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):
|
def load_key(self, override=False, secret=True):
|
||||||
|
if self.imported_key and not override:
|
||||||
|
return
|
||||||
|
if secret:
|
||||||
self.load_key_from_secrets()
|
self.load_key_from_secrets()
|
||||||
self.load_key_from_conf()
|
self.load_key_from_conf()
|
||||||
|
|
||||||
|
@ -122,8 +125,9 @@ class ServicesGroup(object):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
self._load_key(secret_file)
|
self._load_key(secret_file)
|
||||||
self._key_in_secrets = True
|
self.imported_key = True
|
||||||
except BaseException:
|
except BaseException as e:
|
||||||
|
logging.exception(e)
|
||||||
logging.warning('Fail to load key from secret, '
|
logging.warning('Fail to load key from secret, '
|
||||||
'check the key or secret name collision')
|
'check the key or secret name collision')
|
||||||
|
|
||||||
|
@ -131,16 +135,21 @@ class ServicesGroup(object):
|
||||||
'Load key from disk if exists'
|
'Load key from disk if exists'
|
||||||
if not hidden_service_dir:
|
if not hidden_service_dir:
|
||||||
hidden_service_dir = self.hidden_service_dir
|
hidden_service_dir = self.hidden_service_dir
|
||||||
key_file = os.path.join(hidden_service_dir,
|
if not os.path.isdir(hidden_service_dir):
|
||||||
self.name,
|
return
|
||||||
'private_key')
|
try:
|
||||||
self._load_key(key_file)
|
self._onion.load_hidden_service(hidden_service_dir)
|
||||||
|
self.imported_key = True
|
||||||
|
except EmptyDirException:
|
||||||
|
pass
|
||||||
|
|
||||||
def gen_key(self):
|
def gen_key(self):
|
||||||
'Generate new 1024 bits RSA key for hidden service'
|
self.imported_key = False
|
||||||
self._priv_key = RSA.generate(
|
return self._onion.gen_new_private_key()
|
||||||
bits=1024,
|
|
||||||
).exportKey("PEM").decode()
|
@property
|
||||||
|
def _priv_key(self):
|
||||||
|
return self._onion.get_private_key()
|
||||||
|
|
||||||
|
|
||||||
class Ports:
|
class Ports:
|
1285
poetry.lock
generated
Normal file
1285
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
BIN
private_key_bar_v3
Normal file
BIN
private_key_bar_v3
Normal file
Binary file not shown.
BIN
private_key_foo_v3
Normal file
BIN
private_key_foo_v3
Normal file
Binary file not shown.
47
pyproject.toml
Normal file
47
pyproject.toml
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
[tool.poetry]
|
||||||
|
name = "docker-tor-hidden-service"
|
||||||
|
version = "0.7.1"
|
||||||
|
description = "Display onion sites hosted"
|
||||||
|
authors = ["Christophe Mehay <cmehay@nospam.student.42.fr>"]
|
||||||
|
license = "WTFPL"
|
||||||
|
repository = "https://github.com/cmehay/docker-tor-hidden-service"
|
||||||
|
classifiers=[
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Development Status :: 1 - Planning",
|
||||||
|
"License :: OSI Approved :: BSD License",
|
||||||
|
"Natural Language :: English",
|
||||||
|
"Operating System :: POSIX :: Linux",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.7",
|
||||||
|
"Topic :: System :: Installation/Setup",
|
||||||
|
]
|
||||||
|
packages = [
|
||||||
|
{ include = "onions" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.poetry.scripts]
|
||||||
|
onions = "onions:main"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = ">=3.10,<3.11"
|
||||||
|
Jinja2 = ">=2.10"
|
||||||
|
importlib_metadata = ">=1.6.0"
|
||||||
|
vanguards = "^0.3.1"
|
||||||
|
ipy = ">=1.00"
|
||||||
|
pytor = '^0.1.9'
|
||||||
|
pyentrypoint = "^0.8.0"
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
|
autopep8 = ">=1.5.2"
|
||||||
|
tox = ">=3.15.0"
|
||||||
|
cryptography = ">=3.2"
|
||||||
|
pylint = ">=2.5.2"
|
||||||
|
ptpython = ">=3.0.2"
|
||||||
|
black = ">=22.6.0"
|
||||||
|
pre-commit = "^2.20.0"
|
||||||
|
pytest = ">=5.4.2"
|
||||||
|
pyfakefs = ">=4.0.2"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry>=0.12"]
|
||||||
|
build-backend = "poetry.masonry.api"
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
695
tests/onions_test.py
Normal file
695
tests/onions_test.py
Normal file
|
@ -0,0 +1,695 @@
|
||||||
|
import configparser
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import pytest
|
||||||
|
from base64 import b32encode
|
||||||
|
from base64 import b64decode
|
||||||
|
from hashlib import sha1
|
||||||
|
|
||||||
|
from onions import Onions
|
||||||
|
|
||||||
|
|
||||||
|
def get_key_and_onion(version=3):
|
||||||
|
key = {}
|
||||||
|
onion = {}
|
||||||
|
pub = {}
|
||||||
|
|
||||||
|
|
||||||
|
key[
|
||||||
|
3
|
||||||
|
] = """
|
||||||
|
PT0gZWQyNTUxOXYxLXNlY3JldDogdHlwZTAgPT0AAACArobDQYyZAWXei4QZwr++j96H1X/gq14N
|
||||||
|
wLRZ2O5DXuL0EzYKkdhZSILY85q+kfwZH8z4ceqe7u1F+0pQi/sM
|
||||||
|
"""
|
||||||
|
|
||||||
|
pub[
|
||||||
|
3
|
||||||
|
] = """
|
||||||
|
PT0gZWQyNTUxOXYxLXB1YmxpYzogdHlwZTAgPT0AAAC9kzftiea/kb+TWlCEVNpfUJLVk+rFIoMG
|
||||||
|
m9/hW13isA==
|
||||||
|
"""
|
||||||
|
|
||||||
|
onion[3] = "xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion"
|
||||||
|
|
||||||
|
return key[version].strip(), onion[version]
|
||||||
|
|
||||||
|
|
||||||
|
def get_torrc_template():
|
||||||
|
return r"""
|
||||||
|
{% for service_group in onion.services %}
|
||||||
|
HiddenServiceDir {{service_group.hidden_service_dir}}
|
||||||
|
{% if service_group.version == 3 %}
|
||||||
|
HiddenServiceVersion 3
|
||||||
|
{% endif %}
|
||||||
|
{% 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 %}
|
||||||
|
DataDirectory {{ onion.data_directory }}
|
||||||
|
{% if 'TOR_SOCKS_PORT' in env %}
|
||||||
|
SocksPort {{env['TOR_SOCKS_PORT']}}
|
||||||
|
{% else %}
|
||||||
|
SocksPort 0
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if envtobool('TOR_EXIT_RELAY', False) %}
|
||||||
|
ExitRelay 1
|
||||||
|
{% else %}
|
||||||
|
ExitRelay 0
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if onion.enable_control_port %}
|
||||||
|
{% if onion.control_socket %}
|
||||||
|
ControlPort {{onion.control_socket}}
|
||||||
|
{% endif %}
|
||||||
|
{% if not onion.control_socket %}
|
||||||
|
{% if onion.control_ip_binding.version() == 4 %}
|
||||||
|
ControlPort {{onion.control_ip_binding}}:{{ onion.control_port }}
|
||||||
|
{% endif %}
|
||||||
|
{% if onion.control_ip_binding.version() == 6 %}
|
||||||
|
ControlPort [{{onion.control_ip_binding}}]:{{ onion.control_port }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if onion.control_hashed_password %}
|
||||||
|
HashedControlPassword {{ onion.control_hashed_password }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
{% if 'TOR_EXTRA_OPTIONS' in env %}
|
||||||
|
{{env['TOR_EXTRA_OPTIONS']}}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
# useless line for Jinja bug
|
||||||
|
|
||||||
|
|
||||||
|
# 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.create_file("/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_v3(monkeypatch):
|
||||||
|
key, onion_url = get_key_and_onion(version=3)
|
||||||
|
env = {
|
||||||
|
"GROUP1_TOR_SERVICE_HOSTS": "80:service1:80,81:service2:80",
|
||||||
|
"GROUP1_TOR_SERVICE_VERSION": "3",
|
||||||
|
"GROUP1_TOR_SERVICE_KEY": key,
|
||||||
|
}
|
||||||
|
|
||||||
|
monkeypatch.setattr(os, "environ", env)
|
||||||
|
|
||||||
|
onion = Onions()
|
||||||
|
onion._get_setup_from_env()
|
||||||
|
onion._load_keys_in_services()
|
||||||
|
|
||||||
|
assert len(os.environ) == 3
|
||||||
|
assert len(onion.services) == 1
|
||||||
|
|
||||||
|
assert onion.services[0].onion_url == onion_url
|
||||||
|
|
||||||
|
|
||||||
|
def test_key_in_secret(fs, monkeypatch):
|
||||||
|
env = {
|
||||||
|
# "GROUP1_TOR_SERVICE_HOSTS": "80:service1:80",
|
||||||
|
"GROUP2_TOR_SERVICE_HOSTS": "80:service2:80",
|
||||||
|
"GROUP3_TOR_SERVICE_HOSTS": "80:service3:80",
|
||||||
|
"GROUP3_TOR_SERVICE_VERSION": "3",
|
||||||
|
}
|
||||||
|
|
||||||
|
monkeypatch.setattr(os, "environ", env)
|
||||||
|
|
||||||
|
# key_v2, onion_url_v2 = get_key_and_onion()
|
||||||
|
key_v3, onion_url_v3 = get_key_and_onion(version=3)
|
||||||
|
|
||||||
|
fs.create_file("/run/secrets/group3", contents=b64decode(key_v3))
|
||||||
|
|
||||||
|
onion = Onions()
|
||||||
|
onion._get_setup_from_env()
|
||||||
|
onion._load_keys_in_services()
|
||||||
|
|
||||||
|
# group1 = onion.find_group_by_name("group1")
|
||||||
|
group2 = onion.find_group_by_name("group2")
|
||||||
|
group3 = onion.find_group_by_name("group3")
|
||||||
|
|
||||||
|
# assert group1.onion_url == onion_url_v2
|
||||||
|
assert group2.onion_url != onion_url_v3
|
||||||
|
assert group3.onion_url == onion_url_v3
|
||||||
|
|
||||||
|
|
||||||
|
def test_configuration(fs, monkeypatch, tmpdir):
|
||||||
|
extra_options = """
|
||||||
|
HiddenServiceNonAnonymousMode 1
|
||||||
|
HiddenServiceSingleHopMode 1
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
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",
|
||||||
|
"GROUP3_TOR_SERVICE_HOSTS": "80:service4:888,81:service5:8080",
|
||||||
|
"GROUP4_TOR_SERVICE_VERSION": "3",
|
||||||
|
"GROUP4_TOR_SERVICE_HOSTS": "81:unix://unix2.sock",
|
||||||
|
"GROUP3V3_TOR_SERVICE_VERSION": "3",
|
||||||
|
"GROUP3V3_TOR_SERVICE_HOSTS": "80:service4:888,81:service5:8080",
|
||||||
|
"SERVICE5_TOR_SERVICE_HOSTS": "80:service5:80",
|
||||||
|
"TOR_EXTRA_OPTIONS": extra_options,
|
||||||
|
}
|
||||||
|
|
||||||
|
hidden_dir = "/var/lib/tor/hidden_service"
|
||||||
|
|
||||||
|
monkeypatch.setattr(os, "environ", env)
|
||||||
|
monkeypatch.setattr(os, "fchmod", lambda x, y: None)
|
||||||
|
|
||||||
|
torrc_tpl = get_torrc_template()
|
||||||
|
|
||||||
|
fs.create_file("/var/local/tor/torrc.tpl", contents=torrc_tpl)
|
||||||
|
fs.create_file("/etc/tor/torrc")
|
||||||
|
fs.create_dir(hidden_dir)
|
||||||
|
|
||||||
|
onion = Onions()
|
||||||
|
onion._get_setup_from_env()
|
||||||
|
onion._load_keys_in_services()
|
||||||
|
onion.apply_conf()
|
||||||
|
|
||||||
|
onions_urls = {}
|
||||||
|
for dir in os.listdir(hidden_dir):
|
||||||
|
with open(os.path.join(hidden_dir, dir, "hostname"), "r") as f:
|
||||||
|
onions_urls[dir] = f.read().strip()
|
||||||
|
|
||||||
|
with open("/etc/tor/torrc", "r") as f:
|
||||||
|
torrc = f.read()
|
||||||
|
|
||||||
|
print(torrc)
|
||||||
|
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
|
||||||
|
assert "HiddenServiceDir /var/lib/tor/hidden_service/group3" in torrc
|
||||||
|
assert "HiddenServiceDir /var/lib/tor/hidden_service/group4" in torrc
|
||||||
|
assert "HiddenServiceDir /var/lib/tor/hidden_service/group3v3" in torrc
|
||||||
|
assert "HiddenServiceDir /var/lib/tor/hidden_service/service5" in torrc
|
||||||
|
assert torrc.count("HiddenServicePort 80 service4:888") == 2
|
||||||
|
assert torrc.count("HiddenServicePort 81 service5:8080") == 2
|
||||||
|
assert torrc.count("HiddenServicePort 80 service5:80") == 1
|
||||||
|
assert torrc.count("HiddenServicePort 81 unix://unix2.sock") == 1
|
||||||
|
assert torrc.count("HiddenServiceVersion 3") == 6
|
||||||
|
assert "HiddenServiceNonAnonymousMode 1\n" in torrc
|
||||||
|
assert "HiddenServiceSingleHopMode 1\n" in torrc
|
||||||
|
assert "ControlPort" not in torrc
|
||||||
|
|
||||||
|
# Check parser
|
||||||
|
onion2 = Onions()
|
||||||
|
onion2.torrc_parser()
|
||||||
|
|
||||||
|
assert len(onion2.services) == 6
|
||||||
|
|
||||||
|
assert set(
|
||||||
|
group.name
|
||||||
|
for group in onion2.services
|
||||||
|
# ) == set(['group1', 'group2'])
|
||||||
|
) == set(["group1", "group2", "group3", "group4", "group3v3", "service5"])
|
||||||
|
|
||||||
|
for group in onion2.services:
|
||||||
|
if group.name == "group1":
|
||||||
|
assert len(group.services) == 2
|
||||||
|
assert group.version == 3
|
||||||
|
assert group.onion_url == onions_urls[group.name]
|
||||||
|
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 group.version == 3
|
||||||
|
assert group.onion_url == onions_urls[group.name]
|
||||||
|
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")])
|
||||||
|
|
||||||
|
if group.name in ["group3", "group3v3"]:
|
||||||
|
assert len(group.services) == 2
|
||||||
|
assert group.version == 3
|
||||||
|
assert group.onion_url == onions_urls[group.name]
|
||||||
|
assert set(service.host for service in group.services) == set(
|
||||||
|
["service4", "service5"]
|
||||||
|
)
|
||||||
|
for service in group.services:
|
||||||
|
if service.host == "service4":
|
||||||
|
assert len(service.ports) == 1
|
||||||
|
assert set(
|
||||||
|
(port.port_from, port.dest) for port in service.ports
|
||||||
|
) == set([(80, 888)])
|
||||||
|
if service.host == "service5":
|
||||||
|
assert len(service.ports) == 1
|
||||||
|
assert set(
|
||||||
|
(port.port_from, port.dest) for port in service.ports
|
||||||
|
) == set([(81, 8080)])
|
||||||
|
|
||||||
|
if group.name == "group4":
|
||||||
|
assert len(group.services) == 1
|
||||||
|
assert group.version == 3
|
||||||
|
assert group.onion_url == onions_urls[group.name]
|
||||||
|
assert set(service.host for service in group.services) == set(
|
||||||
|
["group4"]
|
||||||
|
)
|
||||||
|
for service in group.services:
|
||||||
|
assert service.host == "group4"
|
||||||
|
assert len(service.ports) == 1
|
||||||
|
assert set(
|
||||||
|
(port.port_from, port.dest) for port in service.ports
|
||||||
|
) == set([(81, "unix://unix2.sock")])
|
||||||
|
|
||||||
|
if group.name == "service5":
|
||||||
|
assert len(group.services) == 1
|
||||||
|
assert group.version == 3
|
||||||
|
assert group.onion_url == onions_urls[group.name]
|
||||||
|
assert set(service.host for service in group.services) == set(
|
||||||
|
["service5"]
|
||||||
|
)
|
||||||
|
for service in group.services:
|
||||||
|
assert service.host == "service5"
|
||||||
|
assert len(service.ports) == 1
|
||||||
|
assert set(
|
||||||
|
(port.port_from, port.dest) for port in service.ports
|
||||||
|
) == set([(80, 80)])
|
||||||
|
|
||||||
|
# bug with fakefs, test everything in the same function
|
||||||
|
|
||||||
|
env = {
|
||||||
|
"TOR_CONTROL_PORT": "172.0.1.0:7867",
|
||||||
|
"TOR_CONTROL_PASSWORD": "secret",
|
||||||
|
}
|
||||||
|
|
||||||
|
def mock_hash(self, password):
|
||||||
|
self.control_hashed_password = "myhashedpassword"
|
||||||
|
|
||||||
|
monkeypatch.setattr(os, "environ", env)
|
||||||
|
monkeypatch.setattr(Onions, "_hash_control_port_password", mock_hash)
|
||||||
|
|
||||||
|
onion = Onions()
|
||||||
|
onion._setup_control_port()
|
||||||
|
onion.apply_conf()
|
||||||
|
|
||||||
|
with open("/etc/tor/torrc", "r") as f:
|
||||||
|
torrc = f.read()
|
||||||
|
|
||||||
|
print(torrc)
|
||||||
|
assert "ControlPort 172.0.1.0:7867" in torrc
|
||||||
|
assert f"HashedControlPassword {onion.control_hashed_password}" in torrc
|
||||||
|
|
||||||
|
env = {
|
||||||
|
"TOR_CONTROL_PORT": "unix:/path/to.socket",
|
||||||
|
}
|
||||||
|
|
||||||
|
monkeypatch.setattr(os, "environ", env)
|
||||||
|
|
||||||
|
torrc_tpl = get_torrc_template()
|
||||||
|
|
||||||
|
onion = Onions()
|
||||||
|
onion._setup_control_port()
|
||||||
|
onion.apply_conf()
|
||||||
|
|
||||||
|
with open("/etc/tor/torrc", "r") as f:
|
||||||
|
torrc = f.read()
|
||||||
|
|
||||||
|
print(torrc)
|
||||||
|
assert "ControlPort unix:/path/to.socket" in torrc
|
||||||
|
|
||||||
|
|
||||||
|
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]{56}.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)
|
||||||
|
|
||||||
|
|
||||||
|
def get_vanguards_template():
|
||||||
|
return r"""
|
||||||
|
## Global options
|
||||||
|
[Global]
|
||||||
|
|
||||||
|
{% if env.get('TOR_CONTROL_PORT', '').startswith('unix:') %}
|
||||||
|
{% set _, unix_path = env['TOR_CONTROL_PORT'].split(':', 1) %}
|
||||||
|
{% elif ':' in env.get('TOR_CONTROL_PORT', '') %}
|
||||||
|
{% set host, port = env['TOR_CONTROL_PORT'].split(':', 1) %}
|
||||||
|
{% else %}
|
||||||
|
{% set host = env.get('TOR_CONTROL_PORT') %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
control_ip = {{ host or '' }}
|
||||||
|
|
||||||
|
control_port = {{ port or 9051 }}
|
||||||
|
|
||||||
|
control_socket = {{ unix_path or '' }}
|
||||||
|
|
||||||
|
control_pass = {{ env.get('TOR_CONTROL_PASSWORD', '') }}
|
||||||
|
|
||||||
|
state_file = {{ env.get('VANGUARDS_STATE_FILE', '/run/tor/data/vanguards.state') }}
|
||||||
|
|
||||||
|
|
||||||
|
{% if 'VANGUARDS_EXTRA_OPTIONS' in env %}
|
||||||
|
{% set extra_conf = ConfigParser().read_string(env['VANGUARDS_EXTRA_OPTIONS']) %}
|
||||||
|
{% if 'Global' in extra_conf %}
|
||||||
|
{% for key, val in extra_conf['Global'].items() %}
|
||||||
|
{{key}} = {{val}}
|
||||||
|
{% endfor %}
|
||||||
|
{% set _ = extra_conf.pop('Global') %}
|
||||||
|
{% endif %}
|
||||||
|
{{ extra_conf.to_string() }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
""".strip() # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def test_vanguards_configuration_sock(fs, monkeypatch):
|
||||||
|
extra_options = """
|
||||||
|
[Global]
|
||||||
|
enable_cbtverify = True
|
||||||
|
loglevel = DEBUG
|
||||||
|
|
||||||
|
[Rendguard]
|
||||||
|
rend_use_max_use_to_bw_ratio = 4.0
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
env = {
|
||||||
|
"TOR_ENABLE_VANGUARDS": "true",
|
||||||
|
"TOR_CONTROL_PORT": "unix:/path/to/sock",
|
||||||
|
"VANGUARDS_EXTRA_OPTIONS": extra_options,
|
||||||
|
}
|
||||||
|
|
||||||
|
monkeypatch.setattr(os, "environ", env)
|
||||||
|
monkeypatch.setattr(os, "fchmod", lambda x, y: None)
|
||||||
|
|
||||||
|
torrc_tpl = get_vanguards_template()
|
||||||
|
|
||||||
|
fs.create_file("/var/local/tor/vanguards.conf.tpl", contents=torrc_tpl)
|
||||||
|
fs.create_file("/etc/tor/vanguards.conf")
|
||||||
|
|
||||||
|
onion = Onions()
|
||||||
|
onion.resolve_control_port()
|
||||||
|
onion._setup_vanguards()
|
||||||
|
onion._write_vanguards_conf()
|
||||||
|
|
||||||
|
vanguard_conf = configparser.ConfigParser()
|
||||||
|
|
||||||
|
with open("/etc/tor/vanguards.conf", "r") as f:
|
||||||
|
print(f.read())
|
||||||
|
|
||||||
|
vanguard_conf.read("/etc/tor/vanguards.conf")
|
||||||
|
|
||||||
|
assert vanguard_conf["Global"]
|
||||||
|
assert not vanguard_conf["Global"]["control_ip"]
|
||||||
|
assert vanguard_conf["Global"]["control_port"] == "9051"
|
||||||
|
assert vanguard_conf["Global"]["control_socket"] == "/path/to/sock"
|
||||||
|
assert not vanguard_conf["Global"]["control_pass"]
|
||||||
|
assert (
|
||||||
|
vanguard_conf["Global"]["state_file"]
|
||||||
|
== "/run/tor/data/vanguards.state"
|
||||||
|
)
|
||||||
|
assert vanguard_conf["Global"]["enable_cbtverify"]
|
||||||
|
assert vanguard_conf["Global"]["loglevel"] == "DEBUG"
|
||||||
|
assert vanguard_conf["Rendguard"]["rend_use_max_use_to_bw_ratio"] == "4.0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_vanguards_configuration_ip(fs, monkeypatch):
|
||||||
|
|
||||||
|
env = {
|
||||||
|
"TOR_ENABLE_VANGUARDS": "true",
|
||||||
|
"TOR_CONTROL_PORT": "127.0.0.1:7864",
|
||||||
|
"TOR_CONTROL_PASSWORD": "secret",
|
||||||
|
}
|
||||||
|
|
||||||
|
monkeypatch.setattr(os, "environ", env)
|
||||||
|
monkeypatch.setattr(os, "fchmod", lambda x, y: None)
|
||||||
|
|
||||||
|
torrc_tpl = get_vanguards_template()
|
||||||
|
|
||||||
|
fs.create_file("/var/local/tor/vanguards.conf.tpl", contents=torrc_tpl)
|
||||||
|
fs.create_file("/etc/tor/vanguards.conf")
|
||||||
|
|
||||||
|
onion = Onions()
|
||||||
|
onion.resolve_control_port()
|
||||||
|
onion._setup_vanguards()
|
||||||
|
onion._write_vanguards_conf()
|
||||||
|
|
||||||
|
vanguard_conf = configparser.ConfigParser()
|
||||||
|
|
||||||
|
with open("/etc/tor/vanguards.conf", "r") as f:
|
||||||
|
print(f.read())
|
||||||
|
|
||||||
|
vanguard_conf.read("/etc/tor/vanguards.conf")
|
||||||
|
|
||||||
|
assert vanguard_conf["Global"]
|
||||||
|
assert vanguard_conf["Global"]["control_ip"] == "127.0.0.1"
|
||||||
|
assert vanguard_conf["Global"]["control_port"] == "7864"
|
||||||
|
assert not vanguard_conf["Global"]["control_socket"]
|
||||||
|
assert vanguard_conf["Global"]["control_pass"] == "secret"
|
||||||
|
assert (
|
||||||
|
vanguard_conf["Global"]["state_file"]
|
||||||
|
== "/run/tor/data/vanguards.state"
|
||||||
|
)
|
15
tox.ini
15
tox.ini
|
@ -1,12 +1,9 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py34, py35, py36
|
isolated_build = true
|
||||||
changedir=assets/onions/
|
envlist = py310
|
||||||
setupdir=assets/onions/
|
|
||||||
skip_missing_interpreters = true
|
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
deps=
|
whitelist_externals = poetry
|
||||||
pytest
|
commands =
|
||||||
pyfakefs
|
poetry install -v
|
||||||
pytest-mock
|
poetry run pytest tests/
|
||||||
commands=pytest -v
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue