twingate

Awesome zero-trust VPN that several of our clients have. We're fans.

Generate/install connector for a Proxmox LXC container

For our pentest dropboxes, we love using the twingate-connector LXC.

  1. Run the install script (from Proxmox GUI, not SSH console).
  2. From the Twingate admin console, click Remote Networks > (click your remote network) and under Connectors click Add Connector.
  3. Click the connector (rename it if you want).
  4. Under Select a Deployment Method, click Proxmox and then copy/paste the appropriate tokens into the LXC install.
  5. Make sure for the network name, you choose a network name that is configured under Network > Remote Networks.

Edit/refresh tokens

The tokens are stored here:

/etc/twingate/connector.conf

Uninstall a twingate connector

sudo apt purge twingate-connector

Uninstall a twingate docker connector

Get the image name (look under the NAMES column):

sudo docker ps -a

Stop it:

sudo docker rm twingate-unique-name-goes-here

Remove it:

sudo docker rm twingate-unique-name-goes-here

Update twingate connector

sudo apt update
sudo apt install -yq twingate-connector
sudo systemctl restart twingate-connector

Check that the version got updated:

twingate-connector -V

More info on the update process here

Troubleshooting connectivity

If your twingate connector isn't phoning home, check out this page.

See what's happening in the logs:

journalctl -u twingate-connector -f

Turn logging up

If the logs are kind of boring, this page well help you turn logging up to 11 (well, actually...7):

nano /etc/twingate/connector.conf

# Add this line:
echo "TWINGATE_LOG_LEVEL=7" | sudo tee -a /etc/twingate/connector.conf

Restart connector:

sudo systemctl restart twingate-connector

If things still aren't happy, review the firewall logs.

Scanning with nmap and other tools through the Twingate connector

After working with Twingate a while, I discovered that if you're connected via the VPN and try to nmap-scan a customer's network that is shared out as a resource via Twingate, the scan won't work. Here is why. Basically this article recommends you do an application-level scan of endpoints to figure out what is truly open.

When I deploy a NUC into a customer environment and let's say only the Twingate connector checks in with IP 192.168.1.7, I use a script like this to specify 192.168.1.0/24 and which ports I want to look for (like port 8006 for my Proxmox box):

#!/bin/bash

# Prompt for CIDR and ports
read -p "Enter target subnet in CIDR notation (e.g. 10.0.20.0/24): " cidr
read -p "Enter port(s) to scan, space-separated (e.g. 8006 443 22): " ports_input

# Parse CIDR
IFS='/' read -ra parts <<< "$cidr"
base_ip="${parts[0]}"
prefix="${parts[1]}"

# Parse octets
IFS='.' read -ra octets <<< "$base_ip"
IFS=' ' read -ra ports <<< "$ports_input"

# Scan functions
scan_range() {
  local sub=$1
  for i in $(seq 1 254); do
    ip="${sub}.$i"
    (
      if ping -c 1 -W 1 $ip &>/dev/null 2>&1 || \
         curl -sk --max-time 1 https://$ip -o /dev/null 2>/dev/null || \
         curl -sk --max-time 1 http://$ip -o /dev/null 2>/dev/null; then
        echo "ALIVE $ip"
      fi
    ) &
  done
}

scan_ports() {
  local sub=$1
  for i in $(seq 1 254); do
    ip="${sub}.$i"
    (
      hits=()
      for port in "${ports[@]}"; do
        result=$(curl -sk --max-time 2 https://$ip:$port -o /dev/null -w "%{http_code}" 2>/dev/null)
        if [ "$result" == "000" ]; then
          result=$(curl -sk --max-time 2 http://$ip:$port -o /dev/null -w "%{http_code}" 2>/dev/null)
        fi
        if [ "$result" != "000" ]; then
          hits+=("port $port (HTTP $result)")
        fi
      done
      if [ ${#hits[@]} -gt 0 ]; then
        echo "OPEN $ip => ${hits[*]}"
      fi
    ) &
  done
}

# Determine subnets to scan based on prefix
base="${octets[0]}.${octets[1]}"
third="${octets[2]}"

if [ "$prefix" -eq 24 ]; then
  subnets=("${base}.${third}")
elif [ "$prefix" -eq 23 ]; then
  subnets=("${base}.${third}" "${base}.$((third + 1))")
elif [ "$prefix" -eq 22 ]; then
  subnets=("${base}.${third}" "${base}.$((third + 1))" "${base}.$((third + 2))" "${base}.$((third + 3))")
else
  echo "Supported prefixes: /22, /23, /24"
  exit 1
fi

echo ""
echo "=== Phase 1: Finding live hosts on ${cidr} ==="
for sub in "${subnets[@]}"; do
  scan_range "$sub"
done
wait

echo ""
echo "=== Phase 2: Checking ports [${ports[*]}] on ${cidr} ==="
for sub in "${subnets[@]}"; do
  scan_ports "$sub"
done
wait