helm-media-servarr

Routing Traffic through a VPN

Here is a guide how you can route all of your torrent traffic through a VPN for transmission in kubernetes.

This guide uses gluetun to simplify the process of forwarding traffic from your transmission container.

It is heavily recommended to use a VPN such as ProtonVPN that supports Port Forwarding!

Configuring Gluetun will be slightly different for everybody, so please referr to their documentation along the way.

Configuration

All configuration can be applied within your values.yaml file used for provisioning resources from this helm chart.

Transmission

First, we must add a securityContext to transmission so that it can perform network-related administrative tasks alongside gluetun.

deployment:
  container:
    securityContext:
      - 'NET_ADMIN'

Gluetun

We’ll need to add a sidecar container to run Gluetun, with the same security context.

Configuration specifics, in terms of environment variables, will be different for everybody. Consult the Gluetun Wiki!

deployment:
   ...
  sideCarContainers:
  - name: 'gluetun'
    image: 'qmcgaw/gluetun'
    ports:
      - containerPort: 8000
    securityContext:
      capabilities:
        add:
          - 'NET_ADMIN'
    env:
      # Gluetun API Key
      # Read Note below
      - name: 'HTTP_CONTROL_SERVER_AUTH_DEFAULT_ROLE'
        value: '{"auth":"none"}'
      # VPN connection
      #  This is different depending on your provider. Consult Gluetun documentation
      - name: 'VPN_SERVICE_PROVIDER'
        value: 'protonvpn'
      - name: 'VPN_TYPE'
        value: 'wireguard'
      - name: 'WIREGUARD_PRIVATE_KEY'
        value: ''
        # valueFrom:
        #   secretKeyRef:
        #     name: 'proton-wireguard'
        #     key: 'privateKey'
      - name: 'SERVER_COUNTRIES'
        value: 'United Kingdom'
      # Port Forwarding
      #  A port
      - name: 'VPN_PORT_FORWARDING'
        value: 'on'
      - name: 'PORT_FORWARD_ONLY'
        value: 'on'
      # Kubernetes specific config:
      # DNS
      - name: 'DNS_KEEP_NAMESERVER'
        value: 'on'
      # FIREWALL
      - name: 'FIREWALL_OUTBOUND_SUBNETS'
        value: '10.42.0.0/15'

![WARNING] It is generally recommended to set up an API key for accessing your Gluetun instance, as a toml file - mounted at /gluetun/auth/config.yaml. For simplicity, in this example, it is disabled with the HTTP_CONTROL_SERVER_AUTH_DEFAULT_ROLE just for this example

Deployment

By adding these two items deploying your helm chart should now enable Transmission traffic to flow through your chosen VPN provider!

Verify your setup with these commands:

# Verify your allocated IP in Gluetun
kubectl exec -it \
  <transmission_pod> \
  --container gluetun \
  -- cat /tmp/gluetun/ip

# Verify your assigned forwarded port in Gluetun
kubectl exec -it \
  <transmission_pod> \
  --container gluetun \
  -- cat /tmp/gluetun/forwarded_port

# Verify your IP address in Transmission
kubectl exec -it \
  <transmission_pod> \
  --container transmission \
  -- curl ifconfig.me

Within the transmission UI, it is worth updating your peer port by going to Menu > Edit Preferences > Network, and the portchecker should verify this is an open port.

[!NOTE] Assigned forwarded ports are random each time, so you must update the peer port from the UI. Alternatively, you could add another sidecar to automate this process for you

Bonus

Auto update peer port

It can get a bit annoying managing the peer port manually through the transmission UI due to this port being random each time.

A way around this is by adding another sidecar with a bash script to automate this process for you! It uses the Gluetun Control Server and Transmission RPC to achieve this.

The following sidecar container achieves this by:

  1. Fetching the SESSION ID from Transmission
  2. Fetching the allocated port from Gluetun
  3. Updating the Peer Port on Transmission via RPC
  4. Repeating every two minutes
deployment:
  ...
  sideCarContainers:
    ...
    - name: 'update-peer-port'
      image: 'alpine:latest'
      # If using an API key for access (recommended)
      env:
        - name: 'GLUETUN_API_KEY'
          valueFrom:
            secretKeyRef:
              name: 'gluetun-apikey'
              key: 'apikey'
      securityContext:
        capabilities:
          add:
            - 'NET_ADMIN'
      command:
        - '/bin/sh'
        - '-c'
        - |
          apk update
          apk add --no-cache jq curl
          sleep 30

          # Transmission RPC URL
          TRANSMISSION_URL="http://localhost:9092/transmission-vpn/rpc/"
          # Gluetun Control Server URL
          GLUETUN_URL="http://localhost:8000/v1"
          # Initial Transmission RPC request with an invalid session ID to start
          SESSION_ID=""
          # How quickly to retry after a failure
          ERROR_RETRY_S=60
          # How often to check for updated ports
          UPDATE_CHECK_S=120

          log() {
            echo "[$(date)] $1"
          }

          err() {
            echo "[$(date)] [ERROR] $1" 1>&2
          }

          while true; do
            echo "[$(date)] Fetching Transmission Session ID"
            SESSION_ID=$(curl -s -X POST "$TRANSMISSION_URL" \
              -H "Content-Type: application/json" \
              -d "{\"arguments\":{\"peer-port\":$PORT},\"method\":\"session-set\"}" \
              | sed -n 's/.*X-Transmission-Session-Id: \([^<]*\).*/\1/p')

            if [ -z "$SESSION_ID" ]; then
              err "Session ID not found. Trying again in ${ERROR_RETRY_S}s"
              sleep $ERROR_RETRY_S
              continue
            fi

            log "Transmission Session ID: $SESSION_ID"

            while true; do
              log "Fetching port from Gluetun"

              PORT_RESPONSE="$(curl -fsS -H "X-API-Key: ${GLUETUN_API_KEY}" "${GLUETUN_URL}/portforward" 2>&1)"
              PORT="$(printf '%s' "$PORT_RESPONSE" | jq -r '.port // empty' 2>/dev/null)"

              if [ -z "$PORT" ]; then
                err "Port forward not found from Gluetun response: ${PORT_RESPONSE}. Trying again in ${ERROR_RETRY_S}s"
                sleep $ERROR_RETRY_S
                continue
              fi

              log "Gluetun forwarding Port: ${PORT}"
              log "Sending request to Transmission RPC..."

              RESPONSE=$(curl -s -w "%{http_code}" -o /dev/null -X POST "$TRANSMISSION_URL" \
                -H "Content-Type: application/json" \
                -H "X-Transmission-Session-Id: $SESSION_ID" \
                -d "{\"arguments\":{\"peer-port\":$PORT},\"method\":\"session-set\"}")

              if [ "$RESPONSE" = "200" ]; then
                log "Transmission RPC request successful. Peer port updated to $PORT."
              elif [ "$RESPONSE" = "409" ]; then
                err "Received 409 Conflict. Fetching new Transmission session ID..."
                break
              else
                err "Unexpected response: $RESPONSE from Transmission. Trying again in ${ERROR_RETRY_S}s"
                sleep $ERROR_RETRY_S
                continue
              fi

              log "Updating again in ${UPDATE_CHECK_S}s"
              sleep $UPDATE_CHECK_S
            done
          done