tor


Torifying a Static Web Site with Docker and Ubuntu 20

This documents the easiest way to Tor-ify a static website with Docker. These instructions were executed on a default Ubuntu 20 Server installation.

The Website

Navigate to your preferred working directory and execute the following:

1
mkdir -p tor-site/www && cd tor-site

docker-compose.yml

Create a file called docker-compose.yml and paste:

1
2
3
4
5
6
7
8
9
10
11
12
13
version: "3.9"
services:
nginx:
image: nginx
restart: unless-stopped
expose:
- "80"
volumes:
- ./www:/usr/share/nginx/html
networks:
default:
external:
name: tor-proxy_default

This sets up the nginx container, which will serve the static website. The website itself will go into the www/ directory. Create a new file called index.html inside the www directory:

1
touch www/index.html

Paste this into www/index.html:

1
2
3
4
5
6
7
8
<html>
<head>
<title>Bonjour</title>
</head>
<body>
<h1>Bienvenue sur le darkweb</h1>
</body>
</html>

Deploy the Server:

If you’re familiar with Docker, you will have noticed a default network called tor-proxy_default. This doesn’t exist yet. You only need to execute the following one time:

1
docker network create tor-proxy_default

Now, your website should be ready to go.

1
docker compose up -d

The Tor Proxy

Navigate back to your preferred working directory:

1
cd ..

And create a new directory alongside your tor-site/:

1
mkdir -p tor-proxy/config && cd tor-proxy

This is where you set up the Tor proxy itself. It is in a seperate directory from where you’ve set up your static nginx website.

Dockerfile

Create a file called Dockerfile and paste:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
FROM debian
ENV NPM_CONFIG_LOGLEVEL warn

ENV DEBIAN_FRONTEND noninteractive

EXPOSE 9050

RUN apt-get update

# `apt-utils` squelches a configuration warning
# `gnupg` is required for adding the `apt` key
RUN apt-get -y install apt-utils wget gnupg apt-transport-https

#
# Info from: https://packages.debian.org/stretch/amd64/libevent-2.0-5/download
#
RUN echo "deb http://ftp.ca.debian.org/debian stretch main" | tee -a /etc/apt/sources.list.d/torproject.list

#
# From https://support.torproject.org/apt/
#
RUN echo "deb [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] https://deb.torproject.org/torproject.org stretch main" | tee -a /etc/apt/sources.list.d/torproject.list
RUN echo "deb-src [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] https://deb.torproject.org/torproject.org stretch main" | tee -a /etc/apt/sources.list.d/torproject.list
RUN wget -qO- https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc | gpg --dearmor | tee /usr/share/keyrings/tor-archive-keyring.gpg >/dev/null
RUN apt-get update
RUN apt-get -y upgrade
RUN apt-get -y install tor deb.torproject.org-keyring

# The debian image does not create a default user
RUN useradd -m user
USER user

# Run the Tor service
CMD /usr/bin/tor -f /etc/tor/torrc

This creates the Docker image.

docker-compose.yml

I think it’s best to manage the created image with docker-compose. Create a new file called docker-compose.yml and paste:

1
2
3
4
5
6
7
version: "3.9"                 
services:
tor:
build: .
restart: unless-stopped
volumes:
- ./config/torrc:/etc/tor/torrc

Configure the Tor Proxy

You can host several websites or web applications through a Tor proxy. Notice the shared volume declared in the docker-compose.yml file. You need to create a file called ./config/torrc:

1
touch config/torrc

This is read from inside the Tor Docker container. Paste the following into ./config/torrc:

1
2
3
HiddenServiceDir /home/user/.tor/hidden_app_1/
HiddenServicePort 80 tor-site-nginx-1:80
HiddenServiceVersion 3

Deploy the Tor Proxy

1
docker compose up -d

You can find your .onion hostnames in the .tor directory in the container:

1
docker compose exec tor cat /home/user/.tor/hidden_app_1/hostname

You’ll see an address that looks like this:

1
257472yzjwmkcts7mmp2nl4v32brv5gq7ybx2ac6luv6ddjnipwv2xyd.onion

Navigate to your Torified website by entering it into your Tor-enabled browser.

Network Troubleshooting

Whoa! I had never run into this issue before…

I discovered a frustrating error that took some effort to solve. Basically, when it comes to sharing networking with the host, a Docker image defaults to an MTU of 1500. The network adapter on my server has an MTU of 1280. The symptom of this was that the Tor Docker container couldn’t run apt-get because the connection would always time out.

This is the resource that solved the problem: https://www.civo.com/learn/fixing-networking-for-docker

To determine if you have this issue, execute the following in the tor-proxy directory:

1
ip a

You will see something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# ...

2: enp2s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1280 qdisc mq state UP group default qlen 1000
link/ether 74:d4:35:e9:5b:7e brd ff:ff:ff:ff:ff:ff
inet 192.168.2.8/24 brd 192.168.2.255 scope global enp2s0
valid_lft forever preferred_lft forever
inet6 fe80::76d4:35ff:fee9:5b7e/64 scope link
valid_lft forever preferred_lft forever

# ...


5: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:a3:b9:76:b0 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:a3ff:feb9:76b0/64 scope link
valid_lft forever preferred_lft foreve

The values associated with mtu need to match. This resource explains how you might fix the problem.


Dockerizing Tor to serve up multiple hidden web services

This post documents an improvement made to the method demonstrated in A Dockerized Torified Express Application Served with Nginx. The previous configuration only deploys one hidden Tor service. I want to be able to deploy a bunch of hidden services behind a general Tor proxy.

Here I use Docker and Compose to build a Tor container behind which multiple Express applications are served.

Express Apps

Let’s suppose there are two express apps. Each will have their own Dockerfile and docker-compose.yml configurations.

Dockerfile

Assuming that each app is setup with all dependencies installed, a simple express Dockerfile might look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FROM node
ENV NPM_CONFIG_LOGLEVEL warn
EXPOSE 3000

# App setup
USER node
ENV HOME=/home/node

WORKDIR $HOME

ENV PATH $HOME/app/node_modules/.bin:$PATH

ADD package.json $HOME
RUN NODE_ENV=production npm install

CMD ["node", "./app.js"]

This defines the container in which the express app runs. Here, port 3000 will be open to apps on the network bridge (see below). Each app will need its own port. For example, the second app may EXPOSE 3001.

docker-compose.yml

docker-compose will build the express app image and serve it up on localhost. It will be connected to the same Docker network as the Tor container. A docker-compose.yml for a simple express app might look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '3'
services:
node:
build: .
restart: always
environment:
- NODE_ENV=production
volumes:
- .:/home/node
- /home/node/node_modules
networks:
default:
external:
name: torproxy_default

Deploy Apps

Once the apps have been Dockerized, each may be brought online with this:

1
docker-compose up -d

Tor

Tor will use the same Dockerfile/docker-compose.yml approach to deploying the service. This will provide the public (hidden) access point.

The Tor proxy container should be setup in its own directory apart from the apps. E.g.,

1
mkdir tor-proxy && cd tor-proxy

Docker

Paste the following to Dockerfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
FROM debian
ENV NPM_CONFIG_LOGLEVEL warn

ENV DEBIAN_FRONTEND noninteractive

EXPOSE 9050

# `apt-utils` squelches a configuration warning
# `gnupg2` is required for adding the `apt` key
RUN apt-get update
RUN apt-get -y install apt-utils gnupg2

#
# Here's where the `tor` stuff gets baked into the container
#
# Keys and repository stuff accurate as of 2017-10-25
# See: https://www.torproject.org/docs/debian.html.en#ubuntu
RUN echo "deb http://deb.torproject.org/torproject.org stretch main" | tee -a /etc/apt/sources.list.d/torproject.list
RUN echo "deb-src http://deb.torproject.org/torproject.org stretch main" | tee -a /etc/apt/sources.list.d/torproject.list
RUN gpg --keyserver keys.gnupg.net --recv A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89
RUN gpg --export A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89 | apt-key add -
RUN apt-get update
RUN apt-get -y upgrade
RUN apt-get -y install tor deb.torproject.org-keyring

# The debian image does not create a default user
RUN useradd -m user
USER user

# Run the Tor service
CMD /usr/bin/tor -f /etc/tor/torrc

docker-compose.yml

This builds and deploys the Tor container. Paste into docker-compose.yml:

1
2
3
4
5
6
7
version: '3'
services:
tor:
build: .
restart: always
volumes:
- ./config/torrc:/etc/tor/torrc

Configuration

As declared above (in docker-compose.yml), the container shares a volume on the host called /config/torrc and connects to the torproxy_default network. It’s in the torrc file that you set the ports for your hidden service. The network allows the external hidden apps to connect to the tor-proxy container. To find the hosts for each hidden service, simply execute:

1
docker ps

You should see something like this:

1
2
3
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
94816844b40b torapp2_node "npm start" 11 minutes ago Up 11 minutes 3001/tcp torapp2_node_1
8c11fb2c9167 torapp1_node "npm start" 12 minutes ago Up 12 minutes 3000/tcp torapp1_node_1

The items listed under the NAMES column serve as your hostnames. So, in this two app configuration, ./config/torrc looks like this:

1
2
3
4
5
HiddenServiceDir /home/user/.tor/hidden_app_1/
HiddenServicePort 80 torapp1_node_1:3000

HiddenServiceDir /home/user/.tor/hidden_app_2/
HiddenServicePort 80 torapp2_node_1:3001

Note the different ports on each of the hidden services. These correspond to the exposed ports in each app’s docker-compose.yml file.

Deploy Tor Container

Bring Tor online with this:

1
docker-compose up -d

If the container reports any sort of directory permissions issues, refer to the notes pertaining to the RUN usermod -u 1001 user command in the tor-proxy Dockerfile.

Assuming everything is built and deployed correctly, you can find your .onion hostnames in the .tor directory in the container:

1
2
docker-compose exec tor cat /home/user/.tor/hidden_app_1/hostname
docker-compose exec tor cat /home/user/.tor/hidden_app_2/hostname

Assuming all goes well, welcome to the darkweb.


A Dockerized, Torified, Express Application

Dark Web chatter is picking up. I’m interested in providing cool web services anonymously. This is my first attempt at using Docker Compose to stay ahead of this trend.

Assumption: all the software goodies are setup and ready to go on an Ubuntu 16.04 server (node, docker, docker-compose, et al).

Set up an Express App

The Express Application Generator strikes me as a little bloated, but I use it anyway because I’m super lazy.

1
sudo npm install express-generator -g

Once installed, set up a vanilla express project:

1
2
express --view=ejs tor-app
cd tor-app && npm install

The express-generator will tell you to run the app like this:

1
DEBUG=tor-app:* npm start

This, of course, is only useful for development. From here, we’ll Dockerize for deployment and Torify for anonymity.

Tor pre-configuration

In anticipation of setting up the actual Torified app container, create a new file called config/torrc. This file will be used by Tor inside the Docker container to serve up our app. Paste the following into config/torrc:

1
2
HiddenServiceDir /home/node/.tor/hidden_service/
HiddenServicePort 80 127.0.0.1:3000

Docker

Copy and paste the following into a new file called Dockerfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
FROM node:stretch
ENV NPM_CONFIG_LOGLEVEL warn

ENV DEBIAN_FRONTEND noninteractive

EXPOSE 9050

# `apt-utils` squelches a configuration warning
RUN apt-get update
RUN apt-get -y install apt-utils

#
# Here's where the `tor` stuff gets baked into the container
#
# Keys and repository stuff accurate as of 2017-10-20
# See: https://www.torproject.org/docs/debian.html.en#ubuntu
RUN echo "deb http://deb.torproject.org/torproject.org stretch main" | tee -a /etc/apt/sources.list.d/torproject.list
RUN echo "deb-src http://deb.torproject.org/torproject.org stretch main" | tee -a /etc/apt/sources.list.d/torproject.list
RUN gpg --keyserver keys.gnupg.net --recv A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89
RUN gpg --export A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89 | apt-key add -
RUN apt-get update
RUN apt-get -y upgrade
RUN apt-get -y install tor deb.torproject.org-keyring

#
# Tor raises some tricky directory permissions issues. Once started, Tor will
# write the hostname and private key into a directory on the host system. If
# the `node` user in the container does not have the same UID as the user on
# the host system, Tor will not be able to create and write to these
# directories. Execute `id -u` on the host to determine your UID.
#
# RUN usermod -u 1001 node

# App setup
USER node
ENV HOME=/home/node

WORKDIR $HOME

ENV PATH $HOME/app/node_modules/.bin:$PATH

ADD package.json $HOME
RUN NODE_ENV=production npm install

# Run the Tor service alongside the app itself
CMD /usr/bin/tor -f /etc/tor/torrc & npm start

Container/Host Permissions

Take special note of the comment posted above the RUN usermode -u 1001 node instruction in Dockerfile. If you get any errors on the container build/execute step described below, you’ll need to make sure your host user’s UID is the same as your container user’s UID (i.e., the node user).

Usually the user in the container has a UID of 1000. To determine the host user’s UID, execute id -u. If it’s not 1000, uncomment the usermod instruction in Dockerfile and make sure the numbers match.

Docker Compose

docker-compose does all of the heavy lifting for building the Dockerfile and start-up/shut-down operations. Paste the following into a file called docker-compose.yml:

1
2
3
4
5
6
7
8
9
10
11
version: '3'
services:
node:
build: .
restart: always
environment:
- NODE_ENV=production
volumes:
- .:/home/node
- /home/node/node_modules
- ./config/torrc:/etc/tor/torrc

Bring the whole thing online by running

1
docker-compose up -d

Every now and then I get an error trying to obtain the GPG key:

1
gpg: keyserver receive failed: Cannot assign requested address

This usually solves itself on subsequent calls to docker-compose up.

Assuming the build and execution was successful, you can determine your .onion address like this:

1
docker-compose exec node cat /home/node/.tor/hidden_service/hostname

You should now be able to access your app from favourite Tor web browser.

If you’re interested in poking around inside the container, access the bash prompt like this:

1
docker-compose exec node bash

Notes

This is the first step in configuring and deploying a hidden service on the Tor network. Since working out the initial details, I’ve already thought of potential improvements to this approach. As it stands, only one hidden service can be deployed. It would be far better to create a Tor container able to proxy multiple apps. I will also be looking into setting up .onion vanity URLs and HTTPS.