Klaus' Log

So 02 August 2020

How to configure a hidden service while running a Tor relay

Posted by Klaus Eisentraut in howto   

In my last post, I showed how to configure and run a Tor bridge relay on this server. Recently, I read the Tor blog and learned about the #MoreOnionsPorfavor campaign. This blog post describes how I did this for this blog.

First of all, let us talk about how this blog works. The website itself is created with Pelican which creates a HTML website from simple Markdown documents. Therefore, this website is completly static HTML. The HTML pages are served by lighttpd with a TLS certificate from Let's Encrypt CA.

Hidden service and HTTPS

All websites on this server use HTTPS and the web server lighttpd was configured to redirect every HTTP request to HTTPS.

This will not work with a hidden service because most CAs including Let's Encrypt will not issue certificates for .onion domains. Please note that certificates for .onion hidden services are mostly useless anyway because the traffic will be end-to-end encrypted by the Tor network.

The solution to overcome this limitation was easy. First, let us lighttpd serve this blog on a local port over HTTP. This is completly independent from the clearnet services on port 80/443:

$SERVER["socket"] == "127.0.0.1:8081" { 
    server.document-root = "/srv/http/klaus.hohenpoelz.de/"
    accesslog.filename = "/var/log/lighttpd/klaus.hohenpoelz.de_onion_access.log"
}

The lighttpd config file had a snippet like below which redirected every HTTP request.

# use HTTPS only
$HTTP["scheme"] == "http" {
    $HTTP["host"] =~ ".*" {
        url.redirect = (".*" => "https://%0$0")
        url.redirect-code = 301
    }
}

This was fine as long as all pages on this server were using HTTPS. But now we want one - the hidden service - without HTTPS, so we need to modify this statement. I added a new condition for those configuration options in order to redirect only the clearnet version, but not our new service on local port 8081:

$HTTP["scheme"] == "http" {
    $HTTP["host"] == "klaus.hohenpoelz.de" { 
        $HTTP["host"] =~ ".*" {
            url.redirect = (".*" => "https://%0$0")
            url.redirect-code = 301
        }
    }
}

In conclusion, we now have a HTTP version of this blog listeing on localhost port 8081, but we did not change configuration of the clearnet HTTP(S) services which are running on public port 80 and 443.

Pelican and absolute URLs

The next problem was that the blog generator Pelican creates absolute URLs by default. This was because pelican generates absolute URLs and expects the SITEURL parameter to be an absolute path, something like https://klaus.hohenpoelz.de/. One can set the RELATIVE_URL to False, but then, the Atom feed generation will not work.

After fiddling around with Pelican options, I did not find a satisfying method to solve this. So my workaround was to actually generate two versions of this blog, both using absolute URLs:

$ cat upload.sh 
#!/bin/bash

# clearnet version
rm -r /home/klaus/dev/klaus.hohenpoelz.de/output/
cd /home/klaus/dev/klaus.hohenpoelz.de/
pelican -s publish_hohenpoelz.py -o output/ content/
cd output/
rsync -avc --delete * hohenpoelz:/srv/http/klaus.hohenpoelz.de/

# Tor version
rm -r /home/klaus/dev/klaus.hohenpoelz.de/output/
cd /home/klaus/dev/klaus.hohenpoelz.de/
pelican -s publish_tor.py -o output/ content/
cd output/
rsync -avc --delete * hohenpoelz:/srv/http/ha5lnclewm7gyn6hw3feijnzkqwa37bp277wimvkyqzr24rv2erwvpad.onion/

The only thing left was to add this additional webroot to the lighttpd server config:

$SERVER["socket"] == "127.0.0.1:8081" { 
    $HTTP["host"] == "ha5lnclewm7gyn6hw3feijnzkqwa37bp277wimvkyqzr24rv2erwvpad.onion" {
        server.document-root = "/srv/http/ha5lnclewm7gyn6hw3feijnzkqwa37bp277wimvkyqzr24rv2erwvpad.onion/"
        accesslog.filename = "/var/log/lighttpd/onion_klaus.hohenpoelz.de.log"
    }
}

Hidden service on a Tor relay

Now, we only need to tell the tor process that it should expose the local HTTP service on port 127.0.0.1:8081 as a hidden service. According to the docs, this was easy:

HiddenServiceDir /srv/tor/klaus.hohenpoelz.de/
HiddenServiceVersion 3
HiddenServicePort 80 127.0.0.1:8081

But I ran into a new problem. As I said in the beginning, this server actually runs a Tor bridge, too. A bridge is a relay and there are some privacy implications when running a Tor hidden service and a Tor relay on one and the same server. When I restarted Tor with the new configuration, I got the following critical warning and Tor did not start:

Tor is currently configured as a relay and a hidden service. That's not very secure: you should probably run your hidden service in a separate Tor process, at least -- see https://trac.torproject.org/8742

After reading the referenced ticket, I understood that hosting a hidden service on a server which also hosts other content could have serious privacy implications: By simply correlating when the whole server is offline, one could expose the origin of the hidden service.

As my hidden service is not actually "hidden" and I do not host something which I need to hide, I do not care about this privacy decrease. So, all I had to do is to leave the Tor bridge configuration as-is and to run a second Tor process in order to expose this blog as hidden service. This was as easy as creating two files: a second Tor configuration file and a systemd service:

# cat /etc/tor/torrc2-hidden-service 
HiddenServiceDir /srv/tor/klaus.hohenpoelz.de/
HiddenServiceVersion 3
HiddenServicePort 80 127.0.0.1:8081

SOCKSPort 0
SOCKSPolicy reject *
Log notice file /var/log/tor/notices_hiddenservice.log

DataDirectory /var/lib/tor-hidden-service
# cat /etc/systemd/system/hidden-service.service 
[Unit]
Description=Hidden Service for my websites
After=network.target

[Service]
User=tor
Type=simple
ExecStart=/usr/bin/tor -f /etc/tor/torrc2-hidden-service
ExecReload=/usr/bin/kill -HUP $MAINPID
KillSignal=SIGINT
LimitNOFILE=8192
PrivateDevices=yes

[Install]
WantedBy=multi-user.target

After running systemctl enable hidden-service.service and rebooting, the Tor service was online and I could access it with the Tor browser.

Adding the onion-location meta tag

Tor Browser has a built-in mechanism which can notify a user that the page he is currently surfing has a hidden service, too. All one needs to do is to add either a HTTP header, or a HTML meta-tag onion-location. I did the latter by adding one line to the base.html page of my Pelican template.

$ cat voidy-bootstrap/templates/base.html  | grep onion
    <meta http-equiv="onion-location" content="http://ha5lnclewm7gyn6hw3feijnzkqwa37bp277wimvkyqzr24rv2erwvpad.onion" />

Screenshot of Tor Browser

In conclusion, this was very easy to set up and I'm curious if I will get any site visits over the .onion service in the future :) Please try at http://ha5lnclewm7gyn6hw3feijnzkqwa37bp277wimvkyqzr24rv2erwvpad.onion.