Important
Disclaimer
This page is part of the MydriaTech Knowledge Base. There is no guarantee of correctness or value of the provided content. Send any feedback and/or corrections to kb at mydriatech dot se.
Copyright © 2017 MydriaTech AB. All rights reserved, but feel free to get inspired by the content.

Background and scope

Raspberry Pi (RPi) is a cheap and easy way to do quick product prototyping.

This article will show you how to install a fairly minimalistic headless RPi3 base system using the Linux distribution Raspbian Stretch Lite and configure it with some basic security to be used as base for prototyping over SSH.

The intended audience of this article is assumed to be fairly familiar with Debian systems, OpenSSH and the Linux console.

Warning This is by no means secure enough for a production system, but is rather intended to at least prevent some unfocused drive-by hacking of a test environment.
Important Rasbian currently runs armhf even though the ARM Cortex-A53 CPU of the RPi3 would support arm64. Since Rasbian is based on Debian this page might contain useful updates.

If you prefer a scripted setup, see the appendix for how to automate this.

Raspbian operating system installation

Download the latest Raspbian Stretch Lite from the Raspberry Pi Foundation and follow their instructions for flashing the image to an SD card.

Summary: Download the image, unzip it, locate the inserted SD card device (/dev/sdX in the example), flash the OS image to the card, flush all writes to the SD card.

wget https://downloads.raspberrypi.org/raspbian_lite_latest
unzip 2017-11-29-raspbian-stretch-lite.zip
lsblk
export SDCARD_DEVICE="/dev/sdX"
sudo dd bs=32M if=2017-11-29-raspbian-stretch-lite.img of=${SDCARD_DEVICE}
sync

Once flashing has completed, mount the /boot partition of the SD card

sudo mkdir /mnt/rpi-boot
sudo mount ${SDCARD_DEVICE}1 /mnt/rpi-boot/

Create the empty file named ssh so the RPi comes up with SSH enabled once booted and connected to the network.

sudo touch /mnt/rpi-boot/ssh

and unmount the partition.

sudo umount /mnt/rpi-boot/
sudo rmdir /mnt/rpi-boot
sync

(Optional) Enable 2.4 GHz WPA2 WiFi

The BCM43438 chip include 2.4 GHz WLAN IEEE 802.11 b/g/n MAC/baseband/radio support. Ensure that the WiFi settings reflect connection to such network. Also, use CCMP security whenever possible since TKIP is based on the RC4 stream cipher.

While you have your SD card’s /boot partition mounted, add the following file:

/<mount path>/boot/wpa_supplicant.conf
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    scan_ssid=1
    ssid="SSID"
    psk="PASSWORD"
    proto=RSN
    key_mgmt=WPA-PSK
    pairwise=CCMP
    group=CCMP
}
# Secondary location network
#network={
#    scan_ssid=1
#    ssid="SSID2"
#    psk="PASSWORD2"
#    proto=RSN
#    key_mgmt=WPA-PSK
#    pairwise=CCMP
#    group=CCMP
#}

At the next (and first) boot of the system /boot/wpa_supplicant.conf will be moved to /etc/wpa_supplicant/wpa_supplicant.conf.

Find the RPi on the network

Assuming your Local Area Network (LAN) is using DHCP, the new RPi will be assigned a new IP address that is unknown to you once it is booted.

If multicast Domain Name System (mDNS) is enabled on your Local Area Network, just connect (using password raspberry) with

ssh -o "UserKnownHostsFile /dev/null" -o "StrictHostKeyChecking no" pi@raspberrypi.local

The additional settings for the options UserKnownHostsFile and StrictHostKeyChecking will prevent checking and saving of the host key that we will replace later in this guide.

If you are unable to use mDNS, please refer to the troubleshooting section for alternative ways of finding your RPi.

Update system and stay up to date

Enable HTTPS instead of plain HTTP transport for communication to update servers to make it harder for a Man In The Middle (MITM) to replay an older repository and prevent security updates from happening. Confidentiality will still be very limited by the fact that package sizes are fairly unique and a MITM will be able to reasonable determine by package sizes which packages are installed or updated. Unfortunately, the main mirroring system does not support HTTPS yet.

#sudo sed -i 's/ http:\/\// https:\/\//g' /etc/apt/sources.list
sudo sed -i 's/ http:\/\// https:\/\//g' /etc/apt/sources.list.d/raspi.list

Get and apply all updates since the operating system image was released.

sudo rpi-update
sudo apt-get update
sudo apt-get -y upgrade
sudo reboot

Ensure that the box stays up to date by enabling unattended upgrades.

sudo apt-get install unattended-upgrades

Remove unused dependencies after the upgrade and reboot nightly if needed to apply security fixes.

/etc/apt/apt.conf.d/50unattended-upgrades
...
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "02:00";
Unattended-Upgrade::SyslogEnable "true";
Unattended-Upgrade::SyslogFacility "daemon";

Configure the system for headless use

Perform basic settings on the host like

  • Set a non-default temporary password for the pi user (sudo passwd pi)

  • Set hostname to your liking (modify /etc/hostname and the entry for 127.0.1.1 in /etc/hosts)

  • Change the amount of RAM allocated to the GPU to 16MiB (set gpu_mem=16 in /boot/config.txt)

or simply use the tool provided by Raspbian

sudo raspi-config

Disable unused hardware

In /boot/config.txt you can additionally disable functionality that you wont use to save power and/or limit the attack surface.

Disable audio

dtparam=audio=off

Disable BCM43438 chip’s 2.4 GHz WLAN IEEE 802.11 b/g/n WIFI support (unless you are using it).

dtoverlay=pi3-disable-wifi

Disable BCM43438 chip’s Bluetooth 4.1 support.

dtoverlay=pi3-disable-bt

Saves ~25 mA by turning off the HDMI connector during boot from /etc/rc.local.

echo "@reboot root /usr/bin/tvservice --off" |  sudo tee /etc/cron.d/tvserviceoff

Reboot automatically if the systems enters a bad state

Enabling the hardware watch dog is a double edged sword. When configured correctly, you will reboot the system when an unexpected event happens that would otherwise force you to gain physical access to the SD card to debug. When configured incorrectly, you will reboot the system seemingly randomly when it is actually doing hard work that you might have asked it for. Also, repeatedly doing things is not great for power consumption on a system that would idle otherwise.

Add the following to /boot/config.txt.

dtparam=watchdog=on

Install the service that keeps pooking the hardware watchdog when the system is still alive.

sudo apt-get install -y watchdog

Be fairly conservative and only reboot if average system load is very high, we have run out of memory or the CPU temperature reaches 75°C.

echo "
max-load-1  = 48
max-load-5  = 36
max-load-15 = 24
min-memory  = 1
watchdog-device = /dev/watchdog
#realtime = yes
#priority = 1
watchdog-timeout=15
interval=5
temperature-sensor = /sys/class/thermal/thermal_zone0/temp
max-temperature = 75
" | sudo tee -a /etc/watchdog.conf

Prevent any kind of login except for SSH access

Since we are running the system headless, we don’t expect any kind of local login.

Important Note that this will effectively prevent any kind of recovery by connecting a keyboard, monitor and/or serial cable. It is also not a replacement for physical security, since directly plugging in to the circuit board would allow a different level of system tampering.

Remove both serial line and regular console and prevent panic root shell.

sudo sed -i 's/ console=serial0,115200 console=tty1/ panic=0/g' /boot/cmdline.txt

No tty is considered secure for root login.

sudo mv /etc/securetty /etc/securetty.bak
sudo touch /etc/securetty

Disable local tty1

sudo systemctl disable getty@tty1.service

Disable serial line tty

sudo systemctl disable serial-getty@ttyAMA0.service

Disable automatic spawning of new ttys

sudo sed -i '#NAutoVTs=6 a NAutoVTs=0' /etc/systemd/logind.conf

Random security (pun intended)

Cryptography based security and confidentiality requires a proper entropy source to prevent an attacker from guessing keys. It will be used for transport (SSH), disk encryption and also operating system functions like Address space layout randomization ASLR.

In Linux there are two random devices - a blocking (/dev/random and a non-blocking /dev/urandom). Internally they both deliver a stream of random bits originating from the same hidden state using Psuedo Random Number Generator (PRNG) based on a hash functions. The big difference between the two is that the blocking device will only return the requested number of bit once enough bits of entropy has been added to the internal state. If the hash function is broken in the future, the random bits provided by the blocking device might still be hard to predict if the internal state is compromised.

Not having a sufficient entropy will however prevent applications using the blocking device from functioning at full speed, since they have to wait for the pool to fill up before continuing their operation.

Enable the RPi3 hardware random generator

Install entropy gathering daemon to make the internal /dev/random state harder to predict compared to a when the original host keys were generated and will provide entropy at a much higher rate than without.

sudo apt-get install rng-tools

Verify status.

echo $(cat /proc/sys/kernel/random/entropy_avail)/$(cat /proc/sys/kernel/random/poolsize)
sudo pkill -USR1 rngd; tail -15 /var/log/syslog
Important This article is in no way trying to suggest that the quality of the HW RNG on the RPi3 is sufficient for security applications. Entropy quality is hard measure and beyond the scope of this article.

Regenerate SSH hosts keys

Once a harder to predict entropy source is active, we should regenerate the host keys that were created on first boot.

sudo ssh-keygen -t rsa -b 4096 -N "" -f /etc/ssh/ssh_host_rsa_key
sudo ssh-keygen -t ecdsa -b 521 -N "" -f /etc/ssh/ssh_host_ecdsa_key
sudo ssh-keygen -t ed25519 -N "" -f /etc/ssh/ssh_host_ed25519_key

List the new host key fingerprints so you know that you connect back to the right host.

sudo ssh-keygen -l -f /etc/ssh/ssh_host_rsa_key
sudo ssh-keygen -l -f /etc/ssh/ssh_host_ecdsa_key
sudo ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key

Restart SSH and logout.

sudo service ssh restart
logout

Log back in again and verify that the host key fingerprint matches one of the outputs from ssh-keygen -l above.

ssh pi@new-rpi-hostname.local

Only allow public key authentication over SSH

A good way to prevent unauthorized access to your box is to only allow public key authentication over SSH since it currently is much harder to attack than passwords.

Before you continue, generate client SSH keys if you don’t already have done so.

ssh-keygen -b 4096 -t rsa -C "Lab environment key" -f ~/.ssh/id_rsa_env_lab

Copy your public key to the box using for example

ssh-copy-id -i ~/.ssh/id_rsa_env_lab pi@new-rpi-hostname.local

Login to the box again and edit the SSH configuration (sudo nano /etc/ssh/sshd_config).

/etc/ssh/sshd_config
...
SyslogFacility AUTH
LogLevel INFO
...
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
...
X11Forwarding no
...
#Subsystem sftp /usr/lib/openssh/sftp-server
...
UseDNS no
TcpKeepAlive yes
...
ClientAliveInterval 30
ClientAliveCountMax 1
...

Restart SSHd.

sudo service ssh restart

Logout and login again to verify that it works.

logout
ssh -i ~/.ssh/id_rsa_env_lab pi@new-rpi-hostname.local

Optionally simplify your life by adding a new entry in ~/.ssh/config on the client side.

~/.ssh/config
...
Host new-rpi-hostname
    Hostname new-rpi-hostname.local
    User pi
    IdentityFile ~/.ssh/id_rsa_env_lab
    ServerAliveInterval 30

Try the new short version of the previous command.

ssh new-rpi-hostname

Disable password login for the pi user

Assuming that you will allow sudo without password, you can simply disable the password authentication for the pi user to prevent its use through any unintended login path accidently left open.

sudo passwd -l pi

Please refer to the appendix for how to instead use a password for sudo.

Encrypt swap

Given the RPi’s limited I/O capabilities it is wise to avoid swapping in the first place, but when it can’t be avoided encrypting the swap will prevent unintentionally persisting sensitive data from RAM.

Remove the on demand swap allocation used by Rasbian.

sudo apt-get -y purge dphys-swapfile
sudo apt-get -y autoremove

Pre-allocate a 1 GiB file /opt/swap-device.bin to use as encrypted swap device. (This is the the same size as the available RAM on the RPi3.)

sudo dd if=/dev/urandom of=/opt/swap-device.bin bs=1M count=1024 iflag=fullblock

The allocated file should be mapped as a dm-crypt swap device on boot. (sudo nano /etc/crypttab)

/etc/crypttab
swap      /opt/swap-device.bin    /dev/urandom   swap,cipher=aes-xts-plain64:sha256,size=256

Instruct system to use the mapped encrypted device as swap on boot. (sudo nano /etc/fstab)

/etc/fstab
...
/dev/mapper/swap    swap    swap    defaults 0 0

Reboot and verify that change.

sudo reboot
grep SwapTotal /proc/meminfo

Enable firewall

Enable a basic iptables firewall that only allows the basic services enabled for the scope of this article. The default policy will be to drop everything unless permitted (even internal localhost traffic). Outgoing connections for a service will only by allowed per owner (system user) to potentially prevent successfully injected malware from phoning home.

sudo apt-get install iptables-persistent

Reconfigure IP and ARP settings to avoid leaking information.

/etc/sysctl.d/50-netcustom.conf
# Always use strict mode Source Address Verification
net/ipv4/conf/default/rp_filter=1
net/ipv4/conf/all/rp_filter=1

# Don't allow IP source route packets
net/ipv4/conf/default/accept_source_route=0
net/ipv4/conf/all/accept_source_route=0
net/ipv6/conf/default/accept_source_route=0
net/ipv6/conf/all/accept_source_route=0

# Don't allow ICMP redirects
net/ipv4/conf/default/accept_redirects=0
net/ipv4/conf/all/accept_redirects=0
net/ipv6/conf/default/accept_redirects=0
net/ipv6/conf/all/accept_redirects=0

# Don't allow bogus ICMP errors
net/ipv4/icmp_echo_ignore_broadcasts=1
net/ipv4/icmp_ignore_bogus_error_responses=1
net/ipv4/icmp_echo_ignore_all=0

# Don't log Martian Packets
net/ipv4/conf/default/log_martians=0
net/ipv4/conf/all/log_martians=0

# Clean up failing connections more quickly
net/ipv4/tcp_fin_timeout=30

# Keep various sane defaults
net/ipv4/tcp_syncookies=1
net/ipv4/tcp_keepalive_intvl=75
net/ipv4/tcp_sack=1

# Don't allow IPv6 auto-configuration
net/ipv6/conf/default/autoconf=0
net/ipv6/conf/all/autoconf=0

# Always use the best local address for this target.
net/ipv4/conf/default/arp_announce = 0
net/ipv4/conf/all/arp_announce = 2
# Only respond to ARPs if kernel would route packets through this iface for this IP
# (If two NICs are connected to the same network, policy/source routing must be used,
#  so the setting here is intended to prevent leaks through a VPN or similar.)
net/ipv4/conf/default/arp_filter = 1
net/ipv4/conf/all/arp_filter = 0
net/ipv4/conf/eth0/arp_filter = 0
net/ipv4/conf/wlan0/arp_filter = 0
# Reply only if the target IP address is local address configured on the incoming interface
net/ipv4/conf/default/arp_ignore = 0
net/ipv4/conf/all/arp_ignore = 1

Configure IPv4 firewall rules. The following services will be made available:

  • Allow outgoing HTTP and HTTPS from the package manager for download of updates.

  • Allow outgoing Network Time Protocol (NTP) requests.

  • Allow outgoing Domain Name System (DNS) queries from package manager and NTP service.

  • Allow outgoing Dynamic Host Configuration Protocol (DHCP) requests.

  • Allow incoming and outgoing Multicast DNS (mDNS).

  • Allow incoming Secure Shell (SSH) connections.

  • Allow outgoing Internet Control Message Protocol (ICMP) ping by the default pi user.

  • Allow incoming ICMP ping

  • Log all other packets that will be dropped at a maximum rate of 20 log entries per minute.

Note that this implies that by default:

  • Prevent the 'pi' user to make DNS queries or HTTP/HTTPS requests.

  • Prevent internal traffic over localhost unless it is one of the allowed services.

  • Prevent ICMP destination-unreachable responses in reply to a ping request.

/etc/iptables/rules.v4
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]

# Drop invalid packets right away
-A INPUT -m conntrack --ctstate INVALID -j DROP

# Drop fragmented ICMP packets
-A INPUT --fragment -p icmp -j DROP

# Respond to ICMP ping
-A INPUT  -i eth0  -p icmp --icmp-type echo-request -m state --state NEW,ESTABLISHED -j ACCEPT
-A INPUT  -i wlan0 -p icmp --icmp-type echo-request -m state --state NEW,ESTABLISHED -j ACCEPT
-A OUTPUT -o eth0  -p icmp --icmp-type echo-reply -m state --state ESTABLISHED -j ACCEPT
-A OUTPUT -o wlan0 -p icmp --icmp-type echo-reply -m state --state ESTABLISHED -j ACCEPT

# Allow outgoing ICMP ping from the user ('pi', UID=1000)
-A OUTPUT -o eth0  -p icmp --icmp-type echo-request -m state --state NEW,ESTABLISHED -m owner --uid-owner 1000 -j ACCEPT
-A OUTPUT -o wlan0 -p icmp --icmp-type echo-request -m state --state NEW,ESTABLISHED -m owner --uid-owner 1000 -j ACCEPT
# And successful ICMP pong responses
-A INPUT  -i eth0  -p icmp --icmp-type echo-reply -m state --state ESTABLISHED -j ACCEPT
-A INPUT  -i wlan0 -p icmp --icmp-type echo-reply -m state --state ESTABLISHED -j ACCEPT

# Allow DHCP
-A OUTPUT -o eth0  -p udp --sport 68 --dport 67 -m state --state NEW,ESTABLISHED -j ACCEPT
-A OUTPUT -o wlan0 -p udp --sport 68 --dport 67 -m state --state NEW,ESTABLISHED -j ACCEPT
# Allow result of DHCP requests to be returned
-A INPUT  -i eth0  -p udp --sport 67 --dport 68 -m state --state ESTABLISHED -j ACCEPT
-A INPUT  -i wlan0 -p udp --sport 67 --dport 68 -m state --state ESTABLISHED -j ACCEPT

# Allow mDNS from the avahi daemon ('avahi', UID=108)
-A OUTPUT -o eth0  -p udp -m owner --uid-owner 108 --dport 5353 -j ACCEPT
-A OUTPUT -o wlan0 -p udp -m owner --uid-owner 108 --dport 5353 -j ACCEPT
-A INPUT  -i eth0  -p udp -d 224.0.0.251 --dport 5353 -j ACCEPT
-A INPUT  -i wlan0 -p udp -d 224.0.0.251 --dport 5353 -j ACCEPT

# Allow DNS queries by required apps daemon ('systemd-timesync', UID=100; '_apt', UID=104)
-A OUTPUT -o eth0  -p udp --dport 53 --sport 1024:65535 -m state --state NEW,ESTABLISHED -m owner --uid-owner 100 -j ACCEPT
-A OUTPUT -o wlan0 -p udp --dport 53 --sport 1024:65535 -m state --state NEW,ESTABLISHED -m owner --uid-owner 100 -j ACCEPT
-A OUTPUT -o eth0  -p udp --dport 53 --sport 1024:65535 -m state --state NEW,ESTABLISHED -m owner --uid-owner 104 -j ACCEPT
-A OUTPUT -o wlan0 -p udp --dport 53 --sport 1024:65535 -m state --state NEW,ESTABLISHED -m owner --uid-owner 104 -j ACCEPT
# Allow result of DNS queries to be returned
-A INPUT  -i eth0  -p udp --sport 53 --dport 1024:65535 -m state --state ESTABLISHED -j ACCEPT
-A INPUT  -i wlan0 -p udp --sport 53 --dport 1024:65535 -m state --state ESTABLISHED -j ACCEPT

# Allow NTP queries by NTP daemon ('systemd-timesync', UID=100)
-A OUTPUT -o eth0  -p udp --dport 123 -m state --state NEW,ESTABLISHED -m owner --uid-owner 100 -j ACCEPT
-A OUTPUT -o wlan0 -p udp --dport 123 -m state --state NEW,ESTABLISHED -m owner --uid-owner 100 -j ACCEPT
# Allow result of NTP queries to be returned
-A INPUT  -i eth0  -p udp --sport 123 -m state --state ESTABLISHED -j ACCEPT
-A INPUT  -i wlan0 -p udp --sport 123 -m state --state ESTABLISHED -j ACCEPT

# Allow incoming SSH connections
-A INPUT  -i eth0  -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
-A INPUT  -i wlan0 -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
-A OUTPUT -o eth0  -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
-A OUTPUT -o wlan0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT

# Allow APT to fetch updates over HTTP and HTTPS. ('_apt', UID=104)
-A OUTPUT -o eth0  -p tcp --dport  80 -m state --state NEW,ESTABLISHED -m owner --uid-owner 104 -j ACCEPT
-A OUTPUT -o wlan0 -p tcp --dport  80 -m state --state NEW,ESTABLISHED -m owner --uid-owner 104 -j ACCEPT
-A OUTPUT -o eth0  -p tcp --dport 443 -m state --state NEW,ESTABLISHED -m owner --uid-owner 104 -j ACCEPT
-A OUTPUT -o wlan0 -p tcp --dport 443 -m state --state NEW,ESTABLISHED -m owner --uid-owner 104 -j ACCEPT
# Allow result of HTTP/HTTPS to be returned
-A INPUT  -i eth0  -p tcp --sport  80 -m state --state ESTABLISHED -j ACCEPT
-A INPUT  -i wlan0 -p tcp --sport  80 -m state --state ESTABLISHED -j ACCEPT
-A INPUT  -i eth0  -p tcp --sport 443 -m state --state ESTABLISHED -j ACCEPT
-A INPUT  -i wlan0 -p tcp --sport 443 -m state --state ESTABLISHED -j ACCEPT

# Log anything that will hit the default DROP-policy
-A INPUT   -m limit --limit 20/min -j LOG --log-prefix \"iptables dropped: \" --log-level 7
-A OUTPUT  -m limit --limit 20/min -j LOG --log-prefix \"iptables dropped: \" --log-level 7 --log-uid
-A FORWARD -m limit --limit 20/min -j LOG --log-prefix \"iptables dropped: \" --log-level 7 --log-uid

COMMIT

Configure IPv6 (DROP all) firewall rules.

/etc/iptables/rules.v6
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
COMMIT

Test and enable firewall rules.

sudo iptables-restore --test /etc/iptables/rules.v4
sudo iptables-restore --test /etc/iptables/rules.v6
sudo systemctl enable netfilter-persistent

Appendix A: Troubleshooting

Alternative ways of finding the IP of your device

Alternative A: Further debug multicast Domain Name System

If your system is running avahi-daemon and your LAN allows multicast Domain Name System (mDNS), you can list all enabled hosts with

avahi-browse -a -t -k

If you find raspberrypi.local device, you could find the IP address of the new RPi using

avahi-resolve --name raspberrypi.local

Alternative B: Find IP by checking your router

Checking your router for new network devices where the MAC address starts with b8:27:eb:xx:xx:xx. It is likely that this is your new RPi. Since you probably wont be using mDNS Take the opportunity to configure a static IP for the new device.

Alternative C: Ping scan your LAN for a new device

Use namp to ping-scan the LAN for a host named raspberrypi:

sudo apt-get install -y nmap
export LAN_NETWORK=$(ip route | grep kernel | grep $(ip route | grep default | cut -d' ' -f5) | cut -d' ' -f1)
echo "${LAN_NETWORK}"
nmap -sn "${LAN_NETWORK}" | grep raspberry

Later when ping is disabled by the firewall, you can scan for open SSH ports instead.

nmap -sT -p 22 "${LAN_NETWORK}"

Appendix B: Common tasks

Use ACT LED to indicate WLAN carrier

Add a DHCP hook that will use the ACT LED to indicate a carrier on the wlan0 interface. This is extremely useful if you plan to deploy your headless RPi3 at the edge of WiFi coverage.

/lib/dhcpcd/dhcpcd-hooks/99-carrier-act-led
# Turn on ACT LED when WLAN carrier has been detected
if [ "$interface" = "wlan0" ] ; then
    if [ "$reason" = "CARRIER" ] ; then
        echo none > /sys/class/leds/led0/trigger
        echo 1 > /sys/class/leds/led0/brightness
    fi
    if [ "$reason" = "NOCARRIER" ] ; then
        echo none > /sys/class/leds/led0/trigger
        echo 0 > /sys/class/leds/led0/brightness
    fi
fi

Install your new favorite editor vim

sudo apt-get -y install vim

Optionally you can also revert some of the new default behaviors to make it easier to work with the mouse (vim ~/.vimrc).

~/.vimrc
" Avoid automatic indention during paste
set paste
" Allow crtl+shift+c for copying mouse selects instead of visual mode
set mouse-=a

To apply the same for the root user, copy the file.

sudo cp /home/pi/.vimrc /root/.vimrc

If you just blindly followed this guide and are currently panicking over how to get out of vim, just type

:q!

and hit enter.

Prevent password-less sudo

Even if you don’t use a password for the pi user for SSH authentication, you can choose to use it for sudo authentication.

Set a sufficient strong password for the pi user

sudo passwd pi

Edit and comment out the special rule for the pi user. (sudo nano /etc/sudoers.d/010_pi-nopasswd)

/etc/sudoers.d/010_pi-nopasswd
#pi ALL=(ALL) NOPASSWD: ALL

Even if you really use a proper password (which you should), increasing the delay between failed attempts will significantly make the password harder to brute force.

Delay each failed attempt by 10 (000 000 micro-) seconds.

sudo sed -i '/pam_unix.so/ i auth optional pam_faildelay.so delay=10000000' /etc/pam.d/common-auth

Lock account for 5.5 minutes after 3 failed attempts.

sudo sed -i '$ a auth required pam_tally2.so deny=3 onerr=fail even_deny_root unlock_time=630' /etc/pam.d/common-auth
sudo sed -i '$ a account required pam_tally2.so' /etc/pam.d/common-account

The result will be that 3 attempts is allowed every 6 minutes. As an example (which you should not use), a simple 4 digit PIN would could be expected to be broken on average after 10^4/2=5000 attempts and would on average resist brute force with these settings for 7 days.

Choose a sufficiently strong randomly generated password and adapt these values to ensure that the system will never be broken this way during its life-time so you have one less thing to worry about.

Inform authorized users to avoid human errors

Informing authorized users about what this particular system is for, to avoid "human errors". This wont hurt if you are multiple people working on a project or if your future self easily forgets what it did a while back.

echo "
*************************************************************************************
* Test System X created under project Y yyyy-MM-dd by John Doe.                     *
* See https://wiki.organisation.intranet/ProjectY/TestSystemX for more information. *
*************************************************************************************" \
    | sudo tee -a /etc/motd

Pull more up to date packages from Debian

Import Debian Stretch release keys (since we are running Raspbian Stretch).

export DEFAULT_DEV="`ip route show | grep default | cut -d' ' -f5`"
sudo iptables -A OUTPUT -o ${DEFAULT_DEV} -p tcp --dport 11371 -j ACCEPT
sudo apt-get install -y dirmngr
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 7638D0442B90D010
sudo iptables -D OUTPUT -o ${DEFAULT_DEV} -p tcp --dport 11371 -j ACCEPT

Add repository.

echo 'deb http://httpredir.debian.org/debian stretch main contrib non-free' | sudo tee -a /etc/apt/sources.list.d/stretch.list

Configure priorities so that the Raspbian repository has precedences over the Debian ones except for the packages we want. (sudo nano /etc/apt/preferences.d/stretch.pref)

/etc/apt/preferences.d/stretch.pref
Package: *
Pin: origin httpredir.debian.org
Pin-Priority: 10

Package: openvswitch-switch openvswitch-common
Pin: origin httpredir.debian.org
Pin-Priority: 1000

Update package list and show the policy we just configured.

sudo apt-get update
sudo apt-cache policy

Go ahead and install the packages from Debian, e.g.

sudo apt-get install openvswitch-switch

Making the device accessible from the big bad Internet

Making the device accessible from the big bad Internet is usually a large part of why a system is setup. In the examples below, TCP port 22 of the RPi will be made available via port 12345 on the public IP.

Leverage another Internet accessible device as incoming SSH proxy

The idea is to use OpenSSH to make an outgoing connection to another host that has a static public IP address and forward all traffic from one of the public IP’s ports back to an localhost port of the RPi. This will work even if you are connected over for example a mobile carrier that wont allow your LAN to have a public IP. Since the public IP is (by the definition above) static, it makes it easy to find your way back to the RPi even if it is moved to a different LAN.

Install autossh to keep the OpenSSH tunnel alive.

sudo apt-get install -y autossh

Prepare the remote proxy by editing /etc/ssh/sshd_config and adding

GatewayPorts yes

Setup SSH tunnel at server boot by adding the following to /etc/rc.local just before exit 0.

autossh -fNT -i /root/.ssh/id_rsa_tunnel -R PUBLIC_IP:12345:127.0.0.1:22 user@public-host.hostingservice.com &

Test by connecting back via the proxy

ssh pi@public-host.hostingservice.com -p 12345 ~/.ssh/id_rsa_env_lab

Setup port forwarding in your router

This works best if you assign a static IP to your device. Either configure a static IP directly on the RPi or configure the DHCP server to always assign the same IP to your device’s MAC address. Please consider you router’s and/or DHCP server’s documentation on advice how to do this.

Poking a hole with the UPnP protocol

A UPnP enabled router can be requested to dynamically add port forwarding for a specified number of seconds. Adding a script invoked by crond every 5 minutes to request renewal of the port forwarding for 5.5 more minutes will keep the port forwarding alive for as long as the RPi is up.

Warning Enabling UPnP in your router will allow rough code running in your network to open up additional attack vectors to your network. Don’t enable it unless you have to.

Install the minimalistic UPnP client miniupnpc.

sudo apt-get install -y miniupnpc

Create client script. (sudo nano /opt/setup-port-forward.sh)

/opt/setup-port-forward.sh
#!/bin/bash
####
#### Setup port forwarding of SSH to local host in UPnP supporting router.
####
####
externalPort="12345"
## Require root access
if [[ $EUID -ne 0 ]]; then
    echo "This script must be run as root." 1>&2
    exit 1
fi
# Temporarily allow UPnP discovery traffic
ipDefaultRoute="`ip route show | grep default | cut -d' ' -f3`"
ipDefaultDevice="`ip route show | grep default | cut -d' ' -f5`"
# Allow UPnP discovery
iptables -A INPUT  -i "${ipDefaultDevice}" -p udp -d 239.255.255.250 --dport 1900 -j ACCEPT
iptables -A OUTPUT -o "${ipDefaultDevice}" -p udp -d 239.255.255.250 --dport 1900 -j ACCEPT
iptables -A OUTPUT -o "${ipDefaultDevice}" -p tcp -d "${ipDefaultRoute}" -j ACCEPT
# Attempt UPnP discovery
eater="`upnpc -l`"
upnpcError="$?"
if [ "$upnpcError" == "0" ] ; then
     xmlRootDescriptorUrl=$(echo "${eater}" | \
         grep -B 1 "schemas-upnp-org:device:InternetGatewayDevice" | grep desc | \
         sed 's/ desc: //g')
     lanIp=$(echo "${eater}" | grep "Local LAN ip address" | \
         sed 's/Local LAN ip address : //g')
     wanIp=$(echo "${eater}" | grep "ExternalIPAddress" | \
         sed 's/ExternalIPAddress = //g')
     nodeName="`hostname`"
     # Setup port forward in gateway back to port 22 of this host that last
     # for 5 minutes and 30 seconds (and we run this every 5 minutes)
     upnpc -u ${xmlRootDescriptorUrl} -a ${lanIp} 22 ${externalPort} TCP 330
     # Save our current external IP address (so can push it to peers later
     # that want to connect back here)
     echo "${wanIp}" > /opt/ip-wan-${nodeName}
else
     echo "Unable to setup port forwarding in UPNP Internet Gateway Device."
fi
# Disallow UPnP discovery
iptables -D INPUT  -i "${ipDefaultDevice}" -p udp -d 239.255.255.250 --dport 1900 -j ACCEPT
iptables -D OUTPUT -o "${ipDefaultDevice}" -p udp -d 239.255.255.250 --dport 1900 -j ACCEPT
iptables -D OUTPUT -o "${ipDefaultDevice}" -p tcp -d "${ipDefaultRoute}" -j ACCEPT

Make script runnable by root.

sudo chmod 700 /opt/setup-port-forward.sh

Keep forward alive as long as host is up. (sudo nano /etc/cron.d/setup-port-forward)

/etc/cron.d/setup-port-forward
*/5 * * * * root /opt/setup-port-forward.sh >/dev/null 2>&1

$ sudo service cron reload

Find external IP address

Unless you use the reverse SSH tunnel + external proxy method, you need to find the devices public IP address.

Find external IP address using OpenDNS and publish

A real duct tape fix would be to find the public IP address of your device and publish it for example in an IRC channel.

Install dig.

sudo apt-get install -y dnsutils

Do lookup to a special resolver from opendns.com.

sudo iptables -A OUTPUT -o eth0  -p udp --dport 53 --sport 1024:65535 -m state --state NEW,ESTABLISHED -m owner --uid-owner 1000 -j ACCEPT
sudo iptables -A OUTPUT -o wlan0 -p udp --dport 53 --sport 1024:65535 -m state --state NEW,ESTABLISHED -m owner --uid-owner 1000 -j ACCEPT
dig @208.67.222.220 myip.opendns.com +short 2> /dev/null
sudo iptables -D OUTPUT -o eth0  -p udp --dport 53 --sport 1024:65535 -m state --state NEW,ESTABLISHED -m owner --uid-owner 1000 -j ACCEPT
sudo iptables -D OUTPUT -o wlan0 -p udp --dport 53 --sport 1024:65535 -m state --state NEW,ESTABLISHED -m owner --uid-owner 1000 -j ACCEPT

Publish…​

Find external IP address using dynamic DNS service

See xdd.se for an anonymous dynamic DNS service provider to easily find your device’s IP.

Network performance testing with iperf

Install the iperf network performance testing tool.

sudo apt-get install -y iperf

To run an iperf server accepting traffic on the default network interface during the test.

export DEFAULT_DEV="`ip route show | grep default | cut -d' ' -f5`"
sudo iptables -A INPUT  -i ${DEFAULT_DEV} -p tcp --dport 5001 -m state --state NEW,ESTABLISHED -j ACCEPT
sudo iptables -A OUTPUT -o ${DEFAULT_DEV} -p tcp --sport 5001 -m state --state ESTABLISHED -j ACCEPT
iperf -e -f M -s
sudo iptables -D INPUT  -i ${DEFAULT_DEV} -p tcp --dport 5001 -m state --state NEW,ESTABLISHED -j ACCEPT
sudo iptables -D OUTPUT -o ${DEFAULT_DEV} -p tcp --sport 5001 -m state --state ESTABLISHED -j ACCEPT

To run an iperf client (also allowing the outgoing traffic through the firewall during the test).

export DEFAULT_DEV="`ip route show | grep default | cut -d' ' -f5`"
sudo iptables -A OUTPUT -o ${DEFAULT_DEV} -p tcp --dport 5001 -j ACCEPT
sudo iptables -A INPUT  -i ${DEFAULT_DEV} -p tcp --sport 5001 -m state --state ESTABLISHED -j ACCEPT
iperf -f M -d -n 1024M -F /dev/urandom -c ranet-s2n1-int
sudo iptables -D OUTPUT -o ${DEFAULT_DEV} -p tcp --dport 5001 -j ACCEPT
sudo iptables -D INPUT  -i ${DEFAULT_DEV} -p tcp --sport 5001 -m state --state ESTABLISHED -j ACCEPT

Appendix C: Scripted setup

Run the following one-liner to detect the new /dev/sdX device that will be assumed to be your SD card.

a=""; b=""; while [ "${b}" = "" ] || [[ "${a#"$b"}" = "" || ${#a} -lt ${#b} ]] ; do b=${a}; a=$(echo "$(ls /dev/sd?)"); done ; \
    c="${a#"$b"}"; c=${c/$'\n'}; echo "Detected ${c}." ; lsblk ${c} ; export SDCARD_DEVICE=${c}

Download Raspbian and flash the ${SDCARD_DEVICE} (e.g. /dev/sdc). Make sure this is really is the SD card and that is not mounted.

wget https://downloads.raspberrypi.org/raspbian_lite_latest -O raspbian_lite_latest.zip
udisksctl unmount -b ${SDCARD_DEVICE}1
udisksctl unmount -b ${SDCARD_DEVICE}2
unzip -p raspbian_lite_latest.zip | sudo dd bs=32M of=${SDCARD_DEVICE} && sync

After flashing the image, mount the root file system partition

sudo mkdir /mnt/rpi-rootfs
sudo mount ${SDCARD_DEVICE}2 /mnt/rpi-rootfs/

and add

  • a trigger for script that runs on initial boot and syslogs its operations

    echo "@reboot root /root/rpisetup.sh 2>&1 | /usr/bin/logger -t rpisetup" |  sudo tee /mnt/rpi-rootfs/etc/cron.d/rpisetup
  • a configuration file for the device

/mnt/rpi-rootfs/root/rpisetup.conf
hostname="raspberrypi"
ssh_pubkey="ssh-rsa AAA... ..."
local_access_enable="false"
watchdog_enable="true"
wifi_enable="true"
wifi_wpa_supplicant_conf="
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
    scan_ssid=1
    ssid=\"SSID\"
    psk=\"PASSWORD\"
    proto=RSN
    key_mgmt=WPA-PSK
    pairwise=CCMP
    group=CCMP
}
#network={
#    ...
#}
"
also_apt_get_packages="vim screen dnsutils wget"
additional_iptables_rules="
# Allow default user to make HTTP and HTTPS requests. ('pi', UID=1000)
-A OUTPUT -o eth0  -p tcp --dport  80 -m state --state NEW,ESTABLISHED -m owner --uid-owner 1000 -j ACCEPT
-A OUTPUT -o wlan0 -p tcp --dport  80 -m state --state NEW,ESTABLISHED -m owner --uid-owner 1000 -j ACCEPT
-A OUTPUT -o eth0  -p tcp --dport 443 -m state --state NEW,ESTABLISHED -m owner --uid-owner 1000 -j ACCEPT
-A OUTPUT -o wlan0 -p tcp --dport 443 -m state --state NEW,ESTABLISHED -m owner --uid-owner 1000 -j ACCEPT
# Allow default user to make DNS queries. ('pi', UID=1000)
-A OUTPUT -o eth0  -p udp --dport 53 --sport 1024:65535 -m state --state NEW,ESTABLISHED -m owner --uid-owner 1000 -j ACCEPT
-A OUTPUT -o wlan0 -p udp --dport 53 --sport 1024:65535 -m state --state NEW,ESTABLISHED -m owner --uid-owner 1000 -j ACCEPT
"
  • a script that performs the initial configuration

/mnt/rpi-rootfs/root/rpisetup.sh
#!/bin/bash -x
echo "Waiting 10 seconds for most of the system to boot..."
sleep 10
###
### Load configuration
###
source ./rpisetup.conf
export DEBIAN_FRONTEND=noninteractive
export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
###
### Configure hardware and networking
###
if [ ! -f ./rpisetup-flag-part1 ] ; then
touch ./rpisetup-flag-part1
echo "*** Reconfiguring WiFi... [wifi_enable=${wifi_enable}]"
if [ "${wifi_enable}" = "true" ] ; then
  echo "${wifi_wpa_supplicant_conf}" > /boot/wpa_supplicant.conf
  echo "# Turn on ACT LED when WLAN carrier has been detected
if [ \"\$interface\" = \"wlan0\" ] ; then
    if [ \"\$reason\" = \"CARRIER\" ] ; then
        echo none > /sys/class/leds/led0/trigger
        echo 1 > /sys/class/leds/led0/brightness
    fi
    if [ \"\$reason\" = \"NOCARRIER\" ] ; then
        echo none > /sys/class/leds/led0/trigger
        echo 0 > /sys/class/leds/led0/brightness
    fi
fi
  " > /lib/dhcpcd/dhcpcd-hooks/99-carrier-act-led
else
  echo "dtoverlay=pi3-disable-wifi" >> /boot/config.txt
fi
echo "*** Disabling audio, bluetooth and setting minimum GPU memory usage..."
sed -i 's/dtparam=audio=on/dtparam=audio=off/g' /boot/config.txt
echo "dtoverlay=pi3-disable-bt" >> /boot/config.txt
echo "gpu_mem=16" >> /boot/config.txt
if [ "${watchdog_enable}" = "true" ] ; then
  echo "*** Enabling HW watchdog..."
  echo "dtparam=watchdog=on" >> /boot/config.txt
fi
echo "*** Changing hostname..."
sed -i "s/$(cat /etc/hostname)/${hostname}/g" /etc/hosts
echo ${hostname} > /etc/hostname
echo "*** Reconfiguring SSH to only use public key authentication..."
sed -i 's/#SyslogFacility AUTH/SyslogFacility AUTH/g' /etc/ssh/sshd_config
sed -i 's/#LogLevel INFO/LogLevel INFO/g' /etc/ssh/sshd_config
sed -i 's/#PasswordAuthentication no/PasswordAuthentication no/g' /etc/ssh/sshd_config
sed -i 's/X11Forwarding yes/X11Forwarding no/g' /etc/ssh/sshd_config
sed -i 's/Subsystem/#Subsystem/g' /etc/ssh/sshd_config
sed -i 's/#UseDNS no/UseDNS no/g' /etc/ssh/sshd_config
sed -i 's/#ClientAliveInterval 0/ClientAliveInterval 30/g' /etc/ssh/sshd_config
sed -i 's/#ClientAliveCountMax 3/ClientAliveCountMax 1/g' /etc/ssh/sshd_config
mkdir /home/pi/.ssh
echo "${ssh_pubkey}" > /home/pi/.ssh/authorized_keys
chown -R pi:pi /home/pi/.ssh/
chmod 700 /home/pi/.ssh/
chmod 500 /home/pi/.ssh/authorized_keys
systemctl enable ssh
if [ "${local_access_enable}" != "true" ] ; then
  echo "*** Disabling local access and turning off HDMI on startup... [local_access_enable=${local_access_enable}]"
  echo "@reboot root /usr/bin/tvservice --off" > /etc/cron.d/tvserviceoff
  sed -i 's/ console=serial0,115200 console=tty1/ panic=0/g' /boot/cmdline.txt
  mv /etc/securetty /etc/securetty.bak && touch /etc/securetty
  systemctl disable getty@tty1.service
  systemctl disable serial-getty@ttyAMA0.service
  sed -i '#NAutoVTs=6 a NAutoVTs=0' /etc/systemd/logind.conf
  passwd -l pi
fi
sync
echo "System will now reboot..."
reboot
exit 0
fi

if [ ! -f ./rpisetup-flag-part2 ] ; then
touch ./rpisetup-flag-part2

echo "Waiting 20 more seconds for network..."
sleep 20
###
### Install software
###
echo "*** Upgrading system and setting up unattended upgrades..."
## Upgrade first
sed -i 's/ http:\/\// https:\/\//g' /etc/apt/sources.list.d/raspi.list
rpi-update
apt-get update
apt-get -y upgrade
apt-get -y install unattended-upgrades
echo "
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "02:00";
Unattended-Upgrade::SyslogEnable "true";
Unattended-Upgrade::SyslogFacility "daemon";
" >> /etc/apt/apt.conf.d/50unattended-upgrades
echo "*** Enable HW RNG..."
apt-get -y install rng-tools
echo "*** Regenerating SSH host keys..."
ssh-keygen -t rsa -b 4096 -N "" -f /etc/ssh/ssh_host_rsa_key-new
ssh-keygen -t ecdsa -b 521 -N "" -f /etc/ssh/ssh_host_ecdsa_key-new
ssh-keygen -t ed25519 -N "" -f /etc/ssh/ssh_host_ed25519_key-new
mv /etc/ssh/ssh_host_rsa_key-new /etc/ssh/ssh_host_rsa_key
mv /etc/ssh/ssh_host_ecdsa_key-new /etc/ssh/ssh_host_ecdsa_key
mv /etc/ssh/ssh_host_ed25519_key-new /etc/ssh/ssh_host_ed25519_key
echo "*** Encrypting swap..."
apt-get -y purge dphys-swapfile dc
dd if=/dev/urandom of=/opt/swap-device.bin bs=1M count=1024 iflag=fullblock
echo "swap /opt/swap-device.bin /dev/urandom swap,cipher=aes-xts-plain64:sha256,size=256" > /etc/crypttab
echo "/dev/mapper/swap swap swap defaults 0 0" >> /etc/fstab
echo "*** Enabling firewall..."
export DEBIAN_FRONTEND="noninteractive"
apt-get install -y iptables-persistent
if [ "${wifi_enable}" = "true" ] ; then
  skipDevice="nonexistingpattern"
else
  skipDevice="wlan0"
fi
echo "
# Default to block anything IPv6
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
COMMIT
" | grep -v ${skipDevice} > /etc/iptables/rules.v6
echo "# DROP everything that isn't explicitly permitted
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]

# Drop invalid packets right away
-A INPUT -m conntrack --ctstate INVALID -j DROP

# Drop fragmented ICMP packets
-A INPUT --fragment -p icmp -j DROP

# Respond to ICMP ping
-A INPUT  -i eth0  -p icmp --icmp-type echo-request -m state --state NEW,ESTABLISHED -j ACCEPT
-A INPUT  -i wlan0 -p icmp --icmp-type echo-request -m state --state NEW,ESTABLISHED -j ACCEPT
-A OUTPUT -o eth0  -p icmp --icmp-type echo-reply -m state --state ESTABLISHED -j ACCEPT
-A OUTPUT -o wlan0 -p icmp --icmp-type echo-reply -m state --state ESTABLISHED -j ACCEPT

# Allow outgoing ICMP ping from the user ('pi', UID=1000)
-A OUTPUT -o eth0  -p icmp --icmp-type echo-request -m state --state NEW,ESTABLISHED -m owner --uid-owner 1000 -j ACCEPT
-A OUTPUT -o wlan0 -p icmp --icmp-type echo-request -m state --state NEW,ESTABLISHED -m owner --uid-owner 1000 -j ACCEPT
# And successful ICMP pong responses
-A INPUT  -i eth0  -p icmp --icmp-type echo-reply -m state --state ESTABLISHED -j ACCEPT
-A INPUT  -i wlan0 -p icmp --icmp-type echo-reply -m state --state ESTABLISHED -j ACCEPT

# Allow DHCP
-A OUTPUT -o eth0  -p udp --sport 68 --dport 67 -m state --state NEW,ESTABLISHED -j ACCEPT
-A OUTPUT -o wlan0 -p udp --sport 68 --dport 67 -m state --state NEW,ESTABLISHED -j ACCEPT
# Allow result of DHCP requests to be returned
-A INPUT  -i eth0  -p udp --sport 67 --dport 68 -m state --state ESTABLISHED -j ACCEPT
-A INPUT  -i wlan0 -p udp --sport 67 --dport 68 -m state --state ESTABLISHED -j ACCEPT

# Allow mDNS from the avahi daemon ('avahi', UID=108)
-A OUTPUT -o eth0  -p udp -m owner --uid-owner 108 --dport 5353 -j ACCEPT
-A OUTPUT -o wlan0 -p udp -m owner --uid-owner 108 --dport 5353 -j ACCEPT
-A INPUT  -i eth0  -p udp -d 224.0.0.251 --dport 5353 -j ACCEPT
-A INPUT  -i wlan0 -p udp -d 224.0.0.251 --dport 5353 -j ACCEPT

# Allow DNS queries by required apps daemon ('systemd-timesync', UID=100; '_apt', UID=104)
-A OUTPUT -o eth0  -p udp --dport 53 --sport 1024:65535 -m state --state NEW,ESTABLISHED -m owner --uid-owner 100 -j ACCEPT
-A OUTPUT -o wlan0 -p udp --dport 53 --sport 1024:65535 -m state --state NEW,ESTABLISHED -m owner --uid-owner 100 -j ACCEPT
-A OUTPUT -o eth0  -p udp --dport 53 --sport 1024:65535 -m state --state NEW,ESTABLISHED -m owner --uid-owner 104 -j ACCEPT
-A OUTPUT -o wlan0 -p udp --dport 53 --sport 1024:65535 -m state --state NEW,ESTABLISHED -m owner --uid-owner 104 -j ACCEPT
# Allow result of DNS queries to be returned
-A INPUT  -i eth0  -p udp --sport 53 --dport 1024:65535 -m state --state ESTABLISHED -j ACCEPT
-A INPUT  -i wlan0 -p udp --sport 53 --dport 1024:65535 -m state --state ESTABLISHED -j ACCEPT

# Allow NTP queries by NTP daemon ('systemd-timesync', UID=100)
-A OUTPUT -o eth0  -p udp --dport 123 -m state --state NEW,ESTABLISHED -m owner --uid-owner 100 -j ACCEPT
-A OUTPUT -o wlan0 -p udp --dport 123 -m state --state NEW,ESTABLISHED -m owner --uid-owner 100 -j ACCEPT
# Allow result of NTP queries to be returned
-A INPUT  -i eth0  -p udp --sport 123 -m state --state ESTABLISHED -j ACCEPT
-A INPUT  -i wlan0 -p udp --sport 123 -m state --state ESTABLISHED -j ACCEPT

# Allow incoming SSH connections
-A INPUT  -i eth0  -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
-A INPUT  -i wlan0 -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
-A OUTPUT -o eth0  -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
-A OUTPUT -o wlan0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT

# Allow APT to fetch updates over HTTP and HTTPS. ('_apt', UID=104)
-A OUTPUT -o eth0  -p tcp --dport  80 -m state --state NEW,ESTABLISHED -m owner --uid-owner 104 -j ACCEPT
-A OUTPUT -o wlan0 -p tcp --dport  80 -m state --state NEW,ESTABLISHED -m owner --uid-owner 104 -j ACCEPT
-A OUTPUT -o eth0  -p tcp --dport 443 -m state --state NEW,ESTABLISHED -m owner --uid-owner 104 -j ACCEPT
-A OUTPUT -o wlan0 -p tcp --dport 443 -m state --state NEW,ESTABLISHED -m owner --uid-owner 104 -j ACCEPT
# Allow result of HTTP/HTTPS to be returned
-A INPUT  -i eth0  -p tcp --sport  80 -m state --state ESTABLISHED -j ACCEPT
-A INPUT  -i wlan0 -p tcp --sport  80 -m state --state ESTABLISHED -j ACCEPT
-A INPUT  -i eth0  -p tcp --sport 443 -m state --state ESTABLISHED -j ACCEPT
-A INPUT  -i wlan0 -p tcp --sport 443 -m state --state ESTABLISHED -j ACCEPT

# Log anything that will hit the default DROP-policy
-A INPUT   -m limit --limit 20/min -j LOG --log-prefix \"iptables dropped: \" --log-level 7
-A OUTPUT  -m limit --limit 20/min -j LOG --log-prefix \"iptables dropped: \" --log-level 7 --log-uid
-A FORWARD -m limit --limit 20/min -j LOG --log-prefix \"iptables dropped: \" --log-level 7 --log-uid

${additional_iptables_rules}

COMMIT
" | grep -v ${skipDevice} > /etc/iptables/rules.v4
iptables-restore --test /etc/iptables/rules.v4
iptables-restore --test /etc/iptables/rules.v6
if [ "${watchdog_enable}" = "true" ] ; then
  echo "*** Enabling HW watchdog service..."
  apt-get install -y watchdog
  echo "
 max-load-1  = 48
 max-load-5  = 36
 max-load-15 = 24
 min-memory  = 1
 watchdog-device = /dev/watchdog
 watchdog-timeout=15
 interval=5
 temperature-sensor = /sys/class/thermal/thermal_zone0/temp
 max-temperature = 75
  " >> /etc/watchdog.conf
fi
echo "*** Enable NTP..."
timedatectl set-ntp true
echo "*** Setting a up a message on login..."
echo "
*****************************************************************************
  Raspbian base system configured by rpisetup.sh `date --rfc-3339=seconds`.
  See /var/log/syslog for installation history.
  Run /home/pi/verify.sh to quickly verify configuration.
*****************************************************************************" >> /etc/motd
echo "*** Adding verification script..."
echo "#!/bin/bash
echo \"**** Time and date ****\"
timedatectl status
echo \"**** Network interfaces ****\"
ip addr list
echo \"**** Swap ****\"
grep SwapTotal /proc/meminfo
echo \"**** Disk usage ****\"
df -h
echo \"**** Firewall ****\"
sudo systemctl status netfilter-persistent
sudo iptables -L -n
sudo ip6tables -L -n
echo \"**** OpenSSH configuration ****\"
grep -v \"#\" /etc/ssh/sshd_config | sed '/^$/d'
echo \"**** Account status ****\"
passwd --status pi
sudo passwd --status root
" > /home/pi/verify.sh
chown pi:pi /home/pi/verify.sh
chmod 500 /home/pi/verify.sh
if [ "x${also_apt_get_packages}" != "x" ] ; then
  echo "*** Installing additional requested packages..."
  apt-get install -y ${also_apt_get_packages}
  if [[ $also_apt_get_packages == *"vim"* ]]; then
    echo "
\" Avoid automatic indention during paste
set paste
\" Allow crtl+shift+c for copying mouse selects instead of visual mode
set mouse-=a
    " | tee /root/.vimrc | tee /home/pi/.vimrc
    chown pi:pi /home/pi/.vimrc
  fi
fi
echo "*** Adding verification script..."
###
### Clean up
###
rm /etc/cron.d/rpisetup ./rpisetup-flag-part1 ./rpisetup-flag-part2 ./rpisetup.conf "${0}"
sync
echo "*** Done! System will now reboot..."
reboot
fi

Once added, unmount and deploy the RPi3 with the SD card.

sync
sudo umount /mnt/rpi-rootfs/
sudo rmdir /mnt/rpi-rootfs
sudo eject ${SDCARD_DEVICE}

Your RPI3 should come up after a few minutes with the chosen hostname.