Skip to content
Jesús Daniel Colmenares Oviedo edited this page Mar 6, 2025 · 2 revisions

Creating a VM tunneled through TOR

Creating a VM is a good way to isolate applications, but in some cases isolating traffic is a necessity. In this case we will create a VM tunneled through TOR. The choice of TOR is for simplicity, but you can choose a VPN provider or use your own VPN server depending on your needs.

Before we continue, a few notes:

  • If you think you are anonymizing by simply using the following settings, you are probably wrong. Anonymizing is much more complex and the tradeoff is that you must take into account what applications (aka processes) you plan to run and what they do, specifically what connections they made. The part you need to be aware of is when they use plaintext connections, which of course can reveal who you are. But even encrypted connections to protocols or applications (e.g. WWW browsers) can easily reveal patterns about who you are.
  • If you plan to take it more seriously, use something like Tails Linux or Whonix.
  • I recommend this method of isolating applications for when you plan to use an application and do not want to reveal your IP easily by malware. I guess an email client is a good example: a suspicious person who looks "legitimate" may send you an email with a suspicious attachment.

See also:

  1. Create an environment file.

    .env:

    ENTRYPOINT=http://127.0.0.1:8888
    TOKEN=<access token>
    TS_AUTH_KEY=<tailscale auth key>
    TIMEZONE=UTC
    SSH_KEY=<SSH public key>
    
  2. Create a deployment file for metadata.

    torVM-metadata.yml:

    kind: metadata
    datacenters:
      main:
        entrypoint: !ENV '${ENTRYPOINT}'
        access_token: !ENV '${TOKEN}'
    deployIn:
      labels:
        - desktop
    metadata:
      ts_auth_key: !ENV '${TS_AUTH_KEY}'
      resolv.conf: |
        options usevc
        nameserver 208.67.222.222
        nameserver 208.67.220.220
      timezone: !ENV '${TIMEZONE}'
      loader.conf: |
        nvme_load="YES"
        if_bridge_load="YES"
        bridgestp_load="YES"
        if_wg_load="YES"
        kern.racct.enable=1
      ssh_key: !ENV '${SSH_KEY}'
      sshd_config: |
        # Ports
        Port 22
    
        # Authentication
        PubkeyAuthentication yes
        AuthenticationMethods publickey
        PermitRootLogin prohibit-password
        PrintMotd no
    
        # Forwarding
        X11Forwarding no
        AllowAgentForwarding yes
    
        # Connection checks
        ClientAliveCountMax 3
        ClientAliveInterval 15
    
        # Compression
        Compression no
    
        # Limits
        LoginGraceTime 40
    
        # Public keys
        AuthorizedKeysFile      /etc/ssh/authorized_keys
    
        # SFTP
        Subsystem sftp internal-sftp
      sysctl.conf: |
        # A bit of hardening
        security.bsd.see_other_uids=0
        security.bsd.see_other_gids=0
        security.bsd.see_jail_proc=0
        kern.randompid=1
        
        # Allow packet filtering in if_bridge(4)
        net.link.bridge.pfil_member=1
        net.link.bridge.pfil_bridge=1
      pkg.conf: |
        FreeBSD: {
          url: "pkg+http://pkg.FreeBSD.org/${ABI}/latest",
          mirror_type: "srv",
          signature_type: "fingerprints",
          fingerprints: "/usr/share/keys/pkg",
          enabled: yes
        }
  3. Create a deployment file for the VM.

    torVM.yml:

    kind: vmJail
    datacenters:
      main:
        entrypoint: !ENV '${ENTRYPOINT}'
        access_token: !ENV '${TOKEN}'
    deployIn:
      labels:
        - desktop
    vmName: 'torVM'
    makejail: 'gh+DtxdF/vm-makejail --file Makejail.tor'
    template:
      loader: 'bhyveload'
      cpu: '4'
      memory: '2G'
      network0_type: 'virtio-net'
      network0_switch: 'public'
      wired_memory: 'YES'
    diskLayout:
      driver: 'nvme'
      size: '40G'
      from:
        type: 'components'
        components:
          - base.txz
          - kernel.txz
        osArch: amd64
        osVersion: 14.2-RELEASE
      disk:
        scheme: 'gpt'
        partitions:
          - type: 'freebsd-boot'
            size: '512k'
            alignment: '1m'
          - type: 'freebsd-swap'
            size: '2g'
            alignment: '1m'
          - type: 'freebsd-ufs'
            size: '20g'
            alignment: '1m'
            format:
              flags: '-Uj'
          - type: 'freebsd-ufs'
            alignment: '1m'
            format:
              flags: '-Uj'
        bootcode:
          bootcode: '/boot/pmbr'
          partcode: '/boot/gptboot'
          index: 1
      fstab:
        - device: '/dev/nda0p3'
          mountpoint: '/'
          type: 'ufs'
          options: 'rw,sync'
          dump: 1
          pass: 1
        - device: '/dev/nda0p2'
          mountpoint: 'none'
          type: 'swap'
          options: 'sw'
          dump: 0
          pass: 0
    script: |
      set -xe
      set -o pipefail
    
      sysrc -f /mnt/etc/rc.conf ifconfig_vtnet0="inet 192.168.8.2/24"
      sysrc -f /mnt/etc/rc.conf defaultrouter="192.168.8.1"
      sysrc -f /mnt/etc/rc.conf fsck_y_enable="YES"
      sysrc -f /mnt/etc/rc.conf clear_tmp_enable="YES"
      sysrc -f /mnt/etc/rc.conf dumpdev="NO"
      sysrc -f /mnt/etc/rc.conf moused_nondefault_enable="NO"
      sysrc -f /mnt/etc/rc.conf hostname=torVM
    
      if [ -f "/metadata/resolv.conf" ]; then
        cp -a /metadata/resolv.conf /mnt/etc/resolv.conf
      fi
    
      if [ -f "/metadata/loader.conf" ]; then
        cp /metadata/loader.conf /mnt/boot/loader.conf
      fi
    
      if [ -f "/metadata/ts_auth_key" ]; then
        pkg -c /mnt install -y tailscale
    
        ts_auth_key=`head -1 -- "/metadata/ts_auth_key"`
    
        echo "/usr/local/bin/tailscale up --accept-dns=false --auth-key=\"${ts_auth_key}\" && rm -f /etc/rc.local" > /mnt/etc/rc.local
    
        sysrc -f /mnt/etc/rc.conf tailscaled_enable="YES"
      fi
    
      if [ -f "/metadata/timezone" ]; then
        timezone=`head -1 -- "/metadata/timezone"`
    
        ln -fs "/usr/share/zoneinfo/${timezone}" /mnt/etc/localtime
      fi
    
      if [ -f "/metadata/sshd_config" ]; then
        sysrc -f /mnt/etc/rc.conf sshd_enable="YES"
        cp /metadata/sshd_config /mnt/etc/ssh/sshd_config
      fi
    
      if [ -f "/metadata/ssh_key" ]; then
        cp /metadata/ssh_key /mnt/etc/ssh/authorized_keys
      fi
    
      if [ -f "/metadata/sysctl.conf" ]; then
        cp /metadata/sysctl.conf /mnt/etc/sysctl.conf
      fi
    
      if [ -f "/metadata/pkg.conf" ]; then
        mkdir -p /mnt/usr/local/etc/pkg/repos
        cp /metadata/pkg.conf /mnt/usr/local/etc/pkg/repos/Latest.conf
      fi
    metadata:
      - resolv.conf
      - loader.conf
      - ts_auth_key
      - timezone
      - sshd_config
      - ssh_key
      - sysctl.conf
      - pkg.conf
  4. Deploy.

    overlord apply -f torVM-metadata.yml
    overlord apply -f torVM.yml
  5. Once the VM is deployed, you should have a new node in your tailnet.

    $ tailscale status
    tailscale status
    ...
    xxx.xx.xxx.xx   torvm                REDACTED@    freebsd -
    ...
  6. Connect to the virtual machine and check if you can see your public IP address.

    $ ssh root@xxx.xx.xxx.xx fetch -qo - http://ip-api.com/json | jq
    {
      "status": "success",
      "country": "Germany",
      "countryCode": "DE",
      "region": "BB",
      "regionName": "Brandenburg",
      "city": "Brandenburg",
      "zip": "14621",
      "lat": 52.6171,
      "lon": 13.1207,
      "timezone": "Europe/Berlin",
      "isp": "Stiftung Erneuerbare Freiheit",
      "org": "CIA TRIAD SECURITY LLC",
      "as": "AS60729 Stiftung Erneuerbare Freiheit",
      "query": "185.220.101.174"
    }

Attentive readers noted a few things:

  • I have used Tailscale, but you can choose not to use it, however, your pf.conf(5) configuration needs to differ to allow SSH connections. This is a bit more complicated than simply relying on Tailscale, Zerotier, Headscale or similar.
  • You may have noticed that I have used options usevc which forces the FreeBSD DNS resolver to use TCP instead of UDP. You can opt not to use it and use the TOR DNS server, the problem is that it is limited to resolving only A, AAAA and PTR records, but records that may be needed such as SRV (think pkg(8)) or MX will fail. All TCP connections will be forwarded to TOR and UDP connections will be blocked, although there are some exceptions (see below).
  • CoreDNS (listen on *:53) is installed and configured to forward DNS messages to TOR via its DNS server (listen on 127.0.0.1:5353). pf.conf(5) is configured to allow both TCP and UDP connections when using the bridge's IPv4 address (currently 192.168.8.1).
  • The pf.conf(5) file is configured to allow connections to 192.168.8.1:9050 which is currently listening for connections to the socks proxy provided by the TOR service. This allows applications such as the Tor Browser to configure that node as the socks proxy instead of using the one installed on the system, thus avoiding sending packets through the TOR network twice and decreasing performance.

Clone this wiki locally