From DNSmasq to Unbound with DNSSEC and ISC's DHCP server.
Fiddling with dnsmasq
to enable DNSSEC for my LAN convinced me of replacing it with unbound
as DNS resolver and the ISC dhpcd
.
Network overview: WAN 192.168.1.0/24
, LAN 192.168.21.0/24
, the router's hostname is raspi
and its LAN IP 192.168.21.1
. The local domain is lan
.
$ apt-get install unbound unbound-host resolvconf isc-dhcp-server
After installing the necessary software, configure the unbound
DNS server for LAN and loopback device. As unbound
doesn't parse /etc/hosts
, any LAN hosts to be resolved need to be defined as local-data
.
### File: /etc/unbound/unbound.conf
#include: "/etc/unbound/unbound.conf.d/*.conf"
server:
interface: 0.0.0.0
access-control: 192.168.21.0/24 allow
do-ip6: no
verbosity: 1
auto-trust-anchor-file: "/var/lib/unbound/root.key"
local-zone: "lan" static
local-data: "raspi.lan. IN A 192.168.21.1"
local-data: "$HOSTNAME.lan. IN A 192.168.21.10"
local-data: "$HOSTNAME1.lan. IN A 192.168.21.11"
private-address: 192.168.21.0/24
private-domain: "lan"
forward-zone:
name: "."
forward-addr: $DNS_IP
forward-addr: $DNS_IP1
forward-addr: $DNS_IP2
The $HOSTNAME
s und $DNS_IP
s need to be defined.
Note: The OpenNIC DNS servers don't support DNSSEC yet. To improve the experience, you may want to add the line "harden-dnssec-stripped: no
" to the server configuration, which makes dnssec validation optional. Otherwise many sites will fail to resolve due to missing DNSSEC data.
To test the configuration, disable dnsmasq
's DNS by adding the line port=0
to /etc/dnsmaq.conf
and restart the daemon (we still need its dhcp), before starting unbound
:
$ /etc/init.d/dnsmasq restart
$ /etc/init.d/unbound start
$ /etc/init.d/resolvconf restart
Note: After rebooting my Raspberry Pi, the auto-trust-anchor-file
mechanism failed to verify the anchor, due to the lack of a hardware clock while openntpd
not yet being ready with setting the time - so unbound
failed to start. Adding openntpd
to the Required-Start section of unbound
's init script solves this issue, if openntpd
is run with the -s
flag set. For Devuan's SysV-Init:
### File: /etc/init.d/unbound:
# ...
# Required-Start: $network $remote_fs $syslog $openntpd
# ...
and
### File: /etc/default/openntpd:
DAEMON_OPTS="-s -f /etc/openntpd/ntpd.conf"
If DNS works for the router itself and on the LAN, it's time to turn towards the new DHCP server. A simple configuration as the only authoritative DHCP Server on one subnet and with two static leases - the "empty" subnet definition for the uplink saves some lines of daemon.log
:
### File: /etc/dhcp/dhcpd.conf
authoritative;
default-lease-time 7200;
max-lease-time 14400;
subnet 192.168.1.0 netmask 255.255.255.0 {
}
subnet 192.168.21.0 netmask 255.255.255.0 {
range 192.168.21.100 192.168.21.120;
option domain-name "lan";
option domain-name-servers 192.168.21.1;
}
host $HOSTNAME {
hardware ethernet 00:11:22:33:44:55;
fixed-address 192.168.21.10;
}
host $HOSTNAME1 {
hardware ethernet 01:11:22:33:44:55;
fixed-address 192.168.21.11;
}
Now fully disable dnsmasq
by setting ENABLED=0
in /etc/default/dnsmasq
and run
$ /etc/init.d/dnsmasq restart
$ /etc/init.d/isc-dhcp-server start
Any typos in the configuration? Be prepared to edit your workstations /etc/network/interfaces
when you now request a new DHCP lease!
When back on the network, you can test the DNSSEC authentication chain by running dig
from the dnsutils
package
$ dig com. SOA +dnssec | grep flags
(dig
's output, on the router as on its DHCP clients, should include the ad
flag.), respectively unbound-host
on the router itself:
$ unbound-host devuan.org -C /etc/unbound/unbound.conf -rd
Also nice to have is the DNSSEC/TLSA Validator Mozilla add-on by cz.nic.
Devuan Jessie (beta) on a Raspberry Pi 2 as LAN router with WiFi uplink.
Featuring dnsmasq
, openntpd
and the simple webfsd
HTTP server. As I don't need a full-grown mail server on the LAN, exim4-daemon-light
is configured to only deliver local mail, which will be served by dovecot-imapd
to mail clients on the LAN. Finally, the RPi's underclocking capabilities will be enabled to save idle CPU cycles.
Image used: devuan_jessie_1.0.0-beta_armhf_raspi2.img.xz
from files.devuan.org.
Basic setup
xzcat
the image to an SD card and boot the Pi with connected WiFi adapter and ethernet. Connect via wired LAN, login with ssh
(root:toor) and adjust the defaults with the command raspi-config
. Create an unprivileged user "piuser
" and change the root password:
$ adduser piuser
$ passwd
Disable ssh
root login and restart sshd
to apply the changes:
### File: /etc/ssh/sshd_config
PermitRootLogin no
Hint: Connect with ssh
as the new user and su
before logging off from the current root shell.
Configuring apt
Add Devuan's security repository:
### File: /etc/apt/sources.list
deb http://auto.mirror.devuan.org/merged/ jessie-security main
deb-src http://auto.mirror.devuan.org/merged/ jessie-security main
Avoid the automatic installation of recommended packages:
### File /etc/apt/apt.conf.d/01norecommends
APT::Install-Recommends "0";
APT::Install-Suggests "0";
Update the system:
$ apt-get update && apt-get dist-upgrade
Install additional software:
$ apt-get install dnsmasq wireless-tools iptables-persistent \
dovecot-imapd exim4-daemon-light webfsd cpufrequtils \
openntpd
Local mail transport
Create a mail alias to forward root's mail to the newly created unprivileged user:
$ echo "root: piuser" >> /etc/aliases
To configure exim4
, run "dpkg-reconfigure exim4-config
" and change the respective options to:
"local delivery only; not on a network"
"Maildir format in home directory"
Test local mail delivery:
$ echo test | mailx -s test1 postmaster@localhost
Network setup
Enable port forwarding:
### File: /etc/sysctl.conf
net.ipv4.ip_forward=1
Configure the network interfaces:
### File: /etc/network/interfaces
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet static
address 192.168.21.1
netmask 255.255.255.0
network 192.168.21.0
broadcast 192.168.21.255
auto wlan0
iface wlan0 inet dhcp
wpa-ssid $WIFI_SSID
wpa-psk $WIFI_PASSWD
Note: SSID and passphrase for wlan0
need to be defined.
Disable IPv6 by blacklisting the IPv6 kernel module:
$ echo "install ipv6 /bin/true" >> /etc/modprobe.d/blacklist.conf
Configure iptables
with some basic NAT and filtering rules:
### File: /etc/iptables/rules.v4
*nat
#-A PREROUTING -i wlan0 -p tcp --dport 10022 -j DNAT --to 192.168.21.10:22
-A POSTROUTING -o wlan0 -j MASQUERADE
COMMIT
*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -i eth0 -m state --state NEW -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
#-A INPUT -p tcp --dport 8000 -j ACCEPT
-A INPUT -j DROP
-A FORWARD -i eth0 -o wlan0 -m state --state NEW -j ACCEPT
-A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
#-A FORWARD -p tcp -d 192.168.21.10 --dport 22 -j ACCEPT
-A FORWARD -j DROP
COMMIT
NOTE: Rules for webfsd
listening at port 8000 and ssh
port forwarding from the WAN interface to 192.168.21.10 are commented out.
Optionally, change the IPv6 default rules:
### File: /etc/iptables/rules.v6
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
COMMIT
Configure the dnsmasq
DHCP and DNS server:
### File: /etc/dnsmasq.conf
domain-needed
bogus-priv
no-resolv
local=/lan/
interface=eth0
listen-address=192.168.21.1
expand-hosts
domain=lan
dhcp-range=192.168.21.100,192.168.21.150,24h
Add some nearby DNS servers (See here for a full OpenNic DNS server list.)
### File: /etc/dnsmasq.d/10opennic
server=$IP1
server=$IP2
server=...
Define static DHCP leases:
### File: /etc/dnsmasq.d/20static_leases
dhcp-host=00:11:22:33:44:55,$HOSTNAME,192.168.21.10,24h
dhcp-host=01:11:22:33:44:55,$HOSTNAME,192.168.21.11,24h
As a workaround for the RPi's lack of a hardwareclock, add the -s
flag to the DAEMON_OPTS
variable in /etc/default/openntpd
.
To apply the previously made changes and turn the RPi from DHCP client to server / router mode, run:
$ sysctl -p /etc/sysctl.conf
$ iptables-restore < /etc/iptables/rules.v4
$ ip6tables-restore < /etc/iptables/rules.v6
$ /etc/init.d openntpd restart
$ /etc/init.d/dnsmasq restart
Note: To unload the IPv6 kernel module, a reboot may be necessary.
Now it's time to take the RPi off the LAN and connect your workstation, again wired, directly to your new RPi2 Devuan router. Log back in:
$ ssh piuser@192.168.21.1
$ su -
If everything went fine, you can now ping
LAN and WAN from the Pi, as well as access the WAN from within the LAN.
The webfsd
http server
There's not much configuration needed to bring webfsd
up:
### File: /etc/webfsd.conf
web_root="/path/to/export/"
web_port="8000"
web_user="www-data"
web_group="www-data"
web_extras="-4 -b user:pass"
Start the daemon:
$ /etc/init.d/webfs restart
Sufficient file permissions given, the content of $web_root
will now be served at port 8000 on all interfaces; login with user:pass
.
Setting up the dovecot
IMAP server
Generate a self-signed SSL certificate:
$ openssl genrsa -out /etc/dovecot/private/dovecot.key 1024
$ openssl req -new -x509 -key /etc/dovecot/private/dovecot.key \
-out /etc/dovecot/dovecot.pem -days 365
Some minimal configuration:
### File: /etc/dovecot/dovecot.conf AND/OR /etc/dovecot/conf.d/*
disable_plaintext_auth = yes
auth_mechanisms = plain
listen = 192.168.21.1
mail_location = maildir:~/Maildir
protocols = "imap"
ssl_cert = </etc/dovecot/dovecot.pem
ssl_key = </etc/dovecot/private/dovecot.key
By disabling any auth_mechanisms
but plain
while setting disable_plaintext_auth
, SSL/TLS login will be forced. Note: This won't prevent misconfigured clients from sending unencrypted passwords. To start the server, run "/etc/init.d/dovecot restart
".
CPU underclocking
(This might void the RPi's warranty.)
Mount the boot partition:
$ mount /dev/mmcblk0p1 /mnt/
Edit / create the following file; the values here have proven to not cause instabilities:
### File: /mnt/config.txt
arm_freq=800
arm_freq_min=100
core_freq=300
core_freq_min=75
sdram_freq=400
over_voltage=0
Although cpufrequtils
defaults to the governor ondemand, it can be explicitely defined or changed by creating the following file; avilable governors are listed in /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors
.
### File: /etc/default/cpufrequtils
GOVERNOR="ondemand"
Reboot to activate and watch the changes in /sys/devices/system/cpu/cpu[0-3]/cpufreq/cpuinfo_cur_freq
.
Automatic system update
To keep the new Devuan system up to date, I let cron
execute my update-script every four hours: Open/edit the crontab with the command "cronatb -e
".
### File: root crontab (/var/spool/cron/crontabs/root)
25 */4 * * * PATH='/usr/sbin:/usr/bin:/sbin:/bin' /path/to/safe-upgrade.sh
Testing
Test the upgrade script and mail transport, e.g. by downgrading the tzdata
package, then running safe-upgrade.sh
and receiving the upgrade log with an IMAP client (StartTLS/Port143 or SSL/Port993 with piuser
login credentials) over the new LAN.
Final steps
Delete the content of /var/cache/apt/archives
, then "dd if=/dev/zero
" the free space on the SD card, shut down the Pi and pull a disk image!
APT: Prevent upgrading of particular packages
There are cases in which it is desirable to keep a certain package version, be it a forced downgrade or a custom build. Here I show several ways to achieve this.
The most simple possibility is to mark the package hold in the dpkg
status file /var/lib/dpkg/status
:
$ echo "PACKAGE_NAME hold" | dpkg set-selections
or, using apt-mark
as wrapper:
$ apt-mark hold PACKAGE_NAME
To undo this and return the selected package to apt
's default workflow, run
$ echo "PACKAGE_NAME installed" | dpkg set-selections
or, respectively:
$ apt-mark unhold PACKAGE_NAME
This is a very static solution and apt
provides a much more flexible way of package handling, called pinning. It is configured in the file /etc/apt/preferences
resp. in an arbitrary file in the /etc/apt/preferences.d/
directory.
Pinnig allows prioritization of package versions depending on factors like version number, repository or release name - wildcards and regular expressions are allowed. The most simple configuration for a single package, analog to the dpkg
way described above, would look like this:
### File: /etc/apt/preferences
### OR
### /etc/apt/preferences.d/SOMEFILE
Package: PACKAGE_NAME
Pin: version PACKAGE_VERSION
Pin-Priority: 1001
See the manpage apt_preferences(5)
for a detailed description of all pinning possibilities.
Finally, for local builds it is possible to assign them a local version number, as I have described in this post, and pin, if necessary, based on that.
Auto-upgrade for Debian based systems
A reliable auto-updater for home use. It can be executed e.g. at boot from /etc/rc.local
to keep apt
based systems up-to-date. Some output will be logged to /var/log/safe-upgrade.log
, in case of upgraded or held packages as well as errors a report will be sent to root@localhost.
Update August, 2020: Command line option "--full" for Dist-Upgrade.
Update June, 2022: sleep
-loop for delayed connections (dial-up, WiFi).
#!/bin/sh
#
# safe-upgrade.sh
#
# automated "safe-upgrade" for apt-based distributions with logging
# to /var/log/safe-upgrade.log and a mail to root@localhost in case
# of upgraded packages or problems
# optional command line parameter "--full" will run a dist-upgrade
pingtest=debian.org
hostname=`hostname`
logfile=/var/log/safe-upgrade.log
mailto=root@localhost
if [ X"$@" = X"--full" ] ; then
task=dist-upgrade
else
task=upgrade
fi
sleep 15
echo ----- `date` ----- >> "$logfile"
ping -c 1 "$pingtest" > /dev/null 2>> "$logfile" ||\
{
echo "System upgrade cancelled: No internet connection." |\
tee -a "$logfile" |\
mailx -s "$hostname"\ update\ \*\*\*ERROR\*\*\* "$mailto"
exit 0
}
DEBIAN_FRONTEND=noninteractive
apt-get -q=2 update 2>&1 |\
tee -a "$logfile" |\
mailx -E -s "$hostname"\ update\ \*\*\*ERROR\*\*\* "$mailto"
apt-get "$task" -y -q 2>&1 |\
grep -ve "^(\{0,1\}Reading" \
-e "^Building" \
-e "^Calculating" |\
tee -a "$logfile" |\
grep -ve "^0 upgraded.*0 not upgraded\.$" |\
mailx -E -s "$hostname"\ upgrade\ log "$mailto" &&
echo ----- DONE ----- >> "$logfile"
exit 0
To tame the daily growing log file, add the following lines to /etc/logrotate.conf
or into an own file in /etc/logrotate.d/
. This will keep compressed archives of the last three months' log files.
### File: /etc/logrotate.conf
/var/log/safe-upgrade.log {
monthly
create
rotate 3
}
If you want the script to be executed by cron, it is necessary to set the $PATH
variable - in the script itself, or in the crontab
. This example runs every three hours at 45':
### File: root crontab, open to edit with "crontab -e"
# m h dom mon dow command
45 */3 * * * PATH='/usr/sbin:/usr/bin:/sbin:/bin' /path/to/safe-upgrade.sh