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" />
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.