mallory.computer

HOWTO: Install Transmission on OpenBSD 7.7

In this HOWTO I'll explain the process of installing Transmission on OpenBSD with a WireGuard VPN to conceal our real IP addresses. I'll use OpenBSD's acme-client to fetch a TLS certificate from Let's Encrypt, OpenBSD's httpd to serve a homepage and the downloads directory, and OpenBSD's relayd to forward HTTP requests to transmission-daemon or httpd depending on the URL.

I'm assuming this will run on a home network, where IPv6 will simplify things since we won't need to worry about our internal vs external IPv4 addresses being different, and having DNS point to the same server.

Installing OpenBSD

You can mostly use the default options. I'll note where that differs.

  • I'll set my hostname to torrent00 (I'll use this style throughout this HOWTO for any values which you should change to suit your situation)
  • I'm assigning my DNS records to point at an IPv6 address, so let's use autoconf when prompted for both IPv4 and IPv6 configurations
  • I'll set the root user's password to foobar since I'll never be authenticating with that account (except via the VM's console)
  • When prompted, I'll create a user called openbsd with no password
  • Ensure that sshd is enabled during the installation
  • At the end of the installation, I'll choose the (S)hell option to do some extra configuration before rebooting:
    • Run echo torrent00.mydomain.invalid > /mnt/etc/myname to tell OpenBSD your FQDN
    • If you're installing in a VM, you may want to go ahead and set up your SSH key before first boot by running cat > /mnt/home/openbsd/.ssh/authorized_keys and then pasting in your public key (press ^D on an empty line to finish writing the file)
    • Run chroot /mnt /usr/sbin/user mod -G wheel openbsd to add the user to the wheel group
    • Run echo permit nopass :wheel > /mnt/etc/doas.conf to allow members of the wheel group to use doas without a password
    • Run echo inet6 alias 2001:db8::1234/128 > /mnt/etc/hostname.vio0 to set a static GUA, ensuring that 2001:db8::1234 is an address within the range your router can use, and setting vio0 to the name of the interface
    • Create a DNS record to point the AAAA record of torrent00.mydomain.invalid at your address. You'll then be able to use ssh openbsd@torrent00.mydomain.invalid to SSH in

First boot and miscellaneous configuration

  • Run doas syspatch because rc.firsttime says you should
  • Use Quad9's unfiltered DNS resolver: echo -en 'nameserver 2620:fe::fe\nnameserver 9.9.9.10\nlookup file bind\n' | doas tee /etc/resolv.conf
  • Install additional packages: doas pkg_add transmission wireguard-tools
  • You'll need to configure your firewall to allow incoming connections to torrent00.mydomain.invalid port 80, TCP. This is necessary for getting a certificate using the ACME protocol

Configuring httpd, relayd, acme-client, and Transmission

  • doas mkdir /var/www/htdocs/downloads
  • doas chown _transmission:_transmission /var/www/htdocs/downloads
  • doas rcctl set transmission_daemon flags '--download-dir=/var/www/htdocs/downloads --global-seedratio=5'
  • cat <<EOF | doas tee /etc/acme-client.conf
    authority letsencrypt {
            api url "https://acme-v02.api.letsencrypt.org/directory"
            account key "/etc/acme/letsencrypt-privkey.pem"
    }
    
    domain torrent00.mydomain.invalid {
    	domain key "/etc/ssl/private/torrent00.mydomain.invalid.key"
    	domain full chain certificate "/etc/ssl/torrent00.mydomain.invalid.crt"
            sign with letsencrypt
    }
    EOF
  • cat <<EOF | doas tee /etc/httpd.conf
    # Handle ACME challenges and redirect to https://
    server "torrent00.mydomain.invalid" {
            listen on * port 80
            location "/.well-known/acme-challenge/*" {
                    root "/acme"
                    request strip 2
            }
            location * {
                    block return 302 "https://\$HTTP_HOST\$REQUEST_URI"
            }
    }
    
    # Serve the homepage and downloads directory
    server "torrent00.mydomain.invalid" {
            listen on * port 8080
            location "/downloads/*" {
                    directory auto index
            }
    }
    EOF
  • cat <<EOF | doas tee /etc/relayd.conf
    table <transmission> { 127.0.0.1 }
    table <httpd> { ::1 }
    http protocol https {
            tls { keypair torrent00.mydomain.invalid }
    
            match request path "/" header set "Host" value "[::1]:8080"
            match request path "/" forward to <httpd>
    
            match request path "/downloads/*" header set "Host" value "[::1]:8080"
            match request path "/downloads/*" forward to <httpd>
    
            match request path "/transmission/*" header set "Host" value "127.0.0.1:9091"
            match request path "/transmission/*" forward to <transmission>
    }
    relay httpproxy {
            protocol https
            listen on :: port 443 tls
            forward to <httpd> port 8080
            forward to <transmission> port 9091
    }
    EOF
  • cat <<EOF | doas tee /var/www/htdocs/index.html
    <title>torrent00.mydomain.invalid</title>
    <link rel="icon" href="/transmission/web/images/favicon.png">
    <h1>torrent00.mydomain.invalid</h1>
    <ul>
    <li><a href="/transmission/web/">Transmission</a></li>
    <li><a href="/downloads/">Downloads</a></li>
    </ul>
    EOF
  • doas rcctl enable relayd httpd transmission_daemon
  • doas rcctl start httpd transmission_daemon
  • Set up acme-client to run once an hour, at a random minute: doas crontab -e
    Then add the following:
    ~ * * * * acme-client torrent00.mydomain.invalid && rcctl reload relayd httpd
  • doas acme-client torrent00.mydomain.invalid && doas rcctl reload httpd && doas rcctl start relayd

Configuring WireGuard

  • doas mkdir /etc/wireguard
  • doas cp my-wireguard-config.conf /etc/wireguard/wg0.conf
    Note: While writing this guide, I found that when Endpoint in the configuration file is an IPv6 address, WireGuard doesn't work.
  • doas chmod 600 /etc/wireguard/wg0.conf
  • cat <<EOF | doas tee /etc/rc.d/wireguard
    #!/bin/ksh
    
    daemon="/usr/local/bin/wg-quick"
    daemon_flags="wg0"
    
    . /etc/rc.d/rc.subr
    
    rc_start() {
            "\${daemon}" up "\${daemon_flags}"
    }
    
    rc_stop() {
            "\${daemon}" down "\${daemon_flags}"
    }
    
    rc_check() {
            ifconfig "\${daemon_flags}"
    }
    
    rc_reload() {
            rc_stop
            rc_start
    }
    
    rc_cmd \$1
    EOF
  • doas chmod 755 /etc/rc.d/wireguard
  • doas rcctl enable wireguard
  • doas rcctl start wireguard
  • Verify your IPv4 and IPv6 addresses have changed: ftp -VMo- https://ip4only.me/api/ ; ftp -VMo- https://ip6only.me/api/

Configuring the packet filter

This is necessary to prevent Transmission from accessing the internet when WireGuard is down.

  • cat <<EOF | doas tee -a /etc/pf.conf
    
    # Block all incoming connections from wg0
    block in on wg0
    
    # Prevent leaks from Transmission
    block proto tcp user _transmission
    block proto udp user _transmission
    pass on wg0 proto tcp user _transmission
    pass on wg0 proto udp user _transmission
    EOF
  • Reload PF's configuration with doas pfctl -f /etc/pf.conf
  • Now we'll turn off WireGuard to check that the Transmission user can't call out:
    doas rcctl stop wireguard
    doas -u _transmission sh -c 'ftp -VMo- https://ip4only.me/api/ ; ftp -VMo- https://ip6only.me/api/'
    And you should see:
    ftp: ip4only.me: no address associated with name
    ftp: ip6only.me: no address associated with name
  • Then we'll switch WireGuard back on and make sure we see our VPN's IP address:
    doas rcctl start wireguard
    doas -u _transmission sh -c 'ftp -VMo- https://ip4only.me/api/ ; ftp -VMo- https://ip6only.me/api/'

Finishing touches

  • Run ln -s /var/www/htdocs/downloads to put a symlink to the downloads directory in ~
  • cat <<EOF > test.sh
    #!/bin/ksh
    
    whoami
    ftp -VMo- http://ip6only.me/api/ | cut -d, -f2
    ftp -VMo- http://ip4only.me/api/ | cut -d, -f2
    
    doas -u _transmission whoami
    doas -u _transmission ftp -VMo- http://ip6only.me/api/ | cut -d, -f2
    doas -u _transmission ftp -VMo- http://ip4only.me/api/ | cut -d, -f2
    EOF
  • chmod 755 test.sh
  • doas reboot to ensure when we reboot everything comes up
  • One final test: ./test.sh && doas rcctl stop wireguard && ./test.sh && doas rcctl start wireguard
  • Now your Transmission server is running at https://torrent00.mydomain.invalid!