Protecting the Pi: Restricting SSH to the USB gadget


SSH is a security hole but is pretty much the only way to work on a Raspberry Pi without an attached keyboard/monitor or serial console cable.  There are some good articles that describe how to lock down SSH via password change or other security measure.

I chose a slightly different approach that limits SSH based on the interface.   I only truly trust the USB private LAN (usb0) since it requires a direct connection and cannot be directly seen by any other device.


The system can ALLOW / DENY ssh on any network interface. We can create inbound firewall rules.  The script below uses iptables / ip6tables to block ssh wireless traffic and leaving SSH enabled for wired connections. Some folks may wish to further disable SSH over hardwire ethernet connections.

usb0: Raspberry Pi Zero running in gadget mode with a hard wire (USB) connection to a PC.

eth0:  Raspberry Pi 3 and 3+ hard-wire Ethernet jacks.

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.

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 have 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 force 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

Accelerate Storage Spaces with SSDs in Windows 10 Storage Pool tiers

Docker on a Chromebook on Crostini - Neverware CloudReady is ready

Java 8 development on Linux/WSL with Visual Studio Code on Windows 10