Protecting the Pi: Restricting SSH to the USB gadget


ssh is a remote login that is pretty much the only way to work on a headless Raspberry Pi without an attached keyboard/monitor or serial console cable. Remote login capability is a security risk even with something like SSH.  There are some good articles that describe how to lock down SSH via password change or other security measures.

I decided to limit SSH to specific network interfaces. Raspberry PIs can have several network interfaces. The only hardwire network interfaces Raspberry Pi Zero when it is in Network Gadget mode. I only truly trust the USB private LAN (usb0) since it requires a direct connection and cannot be directly seen by any other device.


Our Raspberry PI can have several different Network Interfaces.  All of them are candidates for allowing or denying SSH or other inbound access.

Linux lets us manage ssh by letting us configure port related ALLOW / DENY on all network interfaces. We can create inbound iptables firewall rules.  The script below blocks ssh wireless traffic and leave SSH enabled for wired connections. It is based on iptables / ip6tables. Some folks may wish to disable SSH over specific hardwire ethernet connections. These are some of the standard Raspberry Pi network devices.
  1. usb0: Raspberry Pi Zero running in gadget mode with a hard wire (USB) connection to a PC.
  2. eth0:  Raspberry Pi 3 and 3+ hard-wire Ethernet jacks.
  3. wlan0: Raspberry Pi Zero W, Raspberry Pi 3, 3+ with wi-fi enabled or Raspberry Pi (2, Zero...) with USB network adapter and no built-in wi-fi networking.
  4. wlan1: Raspberry Pi Zero W, Pi 3 with built-in wi-fi networking and USB wi-fi adapter

Finding the Right Interface

A Raspberry Pi running the network gadget has more than one active network interface.  They also have two network addresses per interface, one IPV4 and one IPV6. The Pi broadcasts its' DNS information for all interfaces so it can be picked up by mDNS / Bonjour.  A Pi with two network interfaces will have 4 addresses, two IP4 and two IP6. You normally don't care about this because the target service is enabled across all devices. 

Our ssh driven change removes ssh access from some of the devices. Now we have forced ssh to the interface/ip that is still enabled. You must use dns-sd to explicitly find the set of IPs for all active network devices on the Raspberry Pi.  
  • dns-sd -G v4v6 <hostname>.local
The following dns-sd command shows two network interfaces. I know I want to use the 169.x.x.x address because it represents the private connection between the PC and Raspberry PI over the USB gadget link.  In this case, we would ssh into the IP address.
C:\Users\joe>dns-sd -G v4v6 pi-520863f1.local Timestamp A/R Flags if Hostname Address TTL 21:57:00.586 Add 3 20 pi-520863f1.local. FE80:0000:0000:0000:1B31:2156:F706:AFB8%ethernet_32785 120 21:57:00.590 Add 2 20 pi-520863f1.local. 120 21:57:00.674 Add 3 13 pi-520863f1.local. FE80:0000:0000:0000:1BE6:83EB:185C:D72F%ethernet_32777 120 21:57:00.676 Add 2 13 pi-520863f1.local.

Connection delays on the private LAN

I have found that ssh can take a while the first time. It feels like the machine first tries to use the main interface.  Then there is some kind of network timeout after which ssh quickly connects. Normally I just ping <hostname.local> until the ping is successful. 

Better living through scripting

The following script enables ssh and then blocks inbound ssh on wireless connections wlan0 and wlan1.  It can also block ssh on eth0 for those that only allow ssh over the gadget interface. The most recent source code can be found on GitHub

# is rerunable
# Portions styled from 
# ref:
# ref:
# ref:
# ref:
# ref:

# eth0 hardware, 
# wlan0 PZero, P3, P3+ builtin or P2 USB Wi-fi
# wlan1 P3, P3+ USB Wi-Fi

if ! [ $(id -u) = 0 ]; then
   echo "The script need to be run as root." >&2
   exit 1

if [ $SUDO_USER ]; then

# Commands that you don't want run with root would be invoked
# with: sudo -u $real_user
# So they will be ran as the user who invoked the sudo command
# Keep in mind, if the user is using a root shell (they're logged in as root),
# then $real_user is actually root
# sudo -u $real_user non-root-command
# Commands that need to be ran with root would be invoked without sudo

# log the current rules
echo "----------------------------------------------"
echo inbound rules before more reset and ssh block
iptables --list --verbose
ip6tables --list --verbose
echo "----------------------------------------------"
# reset the inbound rules
iptables -F INPUT
ip6tables -F INPUT
echo "Reset Input Rules"

# enable iptables inbound rule blocking SSH on wlan0/eth0
if [ "$disable_wlan0" = true ]; then
    if [ -e /sys/class/net/wlan0 ]; then
        echo "wlan0: Blocking port 22"
        iptables -A INPUT -p tcp --dport 22 -i wlan0 -j DROP
        ip6tables -A INPUT -p tcp --dport 22 -i wlan0 -j DROP
    echo "wlan0: No block requested"
if [ "$disable_wlan1" = true ]; then
    if [ -e /sys/class/net/wlan1 ]; then
        echo "wlan1: Blocking port 22"
        iptables -A INPUT -p tcp --dport 22 -i wlan1 -j DROP
        ip6tables -A INPUT -p tcp --dport 22 -i wlan1 -j DROP
    echo "wlan1: No block requested"
if [ "$disable_eth0" = true ]; then
    if [ -e /sys/class/net/eth0 ]; then
        echo "eth0: Blocking port 22"
        iptables -A INPUT -p tcp --dport 22 -i eth0 -j DROP
        ip6tables -A INPUT -p tcp --dport 22 -i eth0 -j DROP
    echo "eth0: No block requested"

# log the current rules
echo "----------------------------------------------"
echo inbound rules after blocking ssh on  wlan0/eth0
iptables --list --verbose
ip6tables --list --verbose
echo "----------------------------------------------"

# install if not already there
PKG_OK=$(dpkg-query -W --showformat='${Status}\n' iptables-persistent | grep "install ok installed")
#echo Determined status for iptables-persistent: '$PKG_OK'
if [ -z "$PKG_OK" ]; then
  echo "No iptables-persistent. Installing iptables-persistent."
  apt-get --yes install iptables-persistent
  # persist the changes.
  echo "iptables-persistent present so just reconfigure."
  iptables-save >/etc/iptables/rules.v4
  ip6tables-save >/etc/iptables/rules.v6

if [ "`systemctl is-active ssh`" != "active" ] 
    echo "starting ssh"
    systemctl enable ssh
    systemctl start ssh
    echo "ssh already started"




Popular posts from this blog

Understanding your WSL2 RAM and swap - Changing the default 50%-25%

Installing the RNDIS driver on Windows 11 to use USB Raspberry Pi as network attached

DNS for Azure Point to Site (P2S) VPN - getting the internal IPs