Managing an SSH public keys ring with git

Using ssh-updatekeys, you can set up and maintain ~/.ssh/authorized_keys with specific sets on the fly.

You just have  to put your public keys on a public git repository. The script will fetch the keys, either by git + SSH (for write access) or just git + https (for read access).

It can handle different sets of keys (for instance you may want to differenciate keys with or without passphrares). In the git repository, any directory with a name starting by set (set0, setA, setTest, etc) will be treated as a set.

(ssh-updatekeys.sh is part of my -utils package).

Update : you can now grab it with the command

wget ssh.rien.pl -O ssh-updatekeys.sh

Advertisements

Setting up a silent/low energy consumption home server (DHCP/DNS/SMB/UPnP)

Most users are probably fine with their ISP modem/box that even provides an hard disk. But having it’s own home server gives full control over the process and it’s not something utterly frivolous: no storage space real limit (except budget), finely tuned firewall, etc. In the past, it was at the expense of silence, energy consumption and space, but no longer, as described here.

Hardware setup:

The hardware is the following:
– board (APU) Intel DN2800MT
– RAM: 2 x 2 Go PC8500 DDR3 SODIMM
– Hard drive: Western Digital WD Green 3,5″ – SATA III 6 Gb/s – 2 To (Caviar)
– Secondary ethernet: StarTech.com ST1000SMPEX (Mini PCI-E)
– Wifi: TP-Link TL-WDN4800 (PCI-E)
+ a laptop adapter (16V, 4A)
+ a small case

The APU itself have a thermal design power (TDP) inferior to 10W. The hard drive is of the “Green” typen (RPM is lower than usual, etc). It’s important to note the RAM is of the SO-DIMM type (usually for laptops) PC8500 (max frequency supported by this board/CPU) and an laptop power charger/adapter is necessary instead of a regular power supply unit. Any case designed for the mini-ITX form factor could do. Low energy consumption, silent and small.

I was, actually, looking towards Sapphire Mini xxxx hardware at first, but it’s quite painy to get it shipped. So I went instead for the Intel Nano based hardware, despite its obvious drawbacks, which are supporting SATA II instead of 3, the SODIMM 4Gb RAM max and being known to be poorly supported on the target system, which is Debian GNU/Linux. I actually don’t care much for the GPU support, 4 Gb is more than enough for a home server and SATA II acceptable enough, so it should be fine anyway.

(Obviously, you should plug a hub on the secondary ethernet otherwise you’ll only be able to connect one box over ethernet)

Software setup:

Picking softwares:

Most obvious: we’ll run Debian stable on it, so to say Wheezy, the about-to-be-released-and-frozen one. The stable model in itself makes this distro the best choice for a server: this is stable and kept secure over time.

It’s supposed to work with an heterogenous network: GNU/Linux, MS Windows, over ethernet or wireless. So we’ll want:
– OpenSSH as secure shell, for the administrator
– any dhcpd server to provide IPs on the fly
– Samba for networked filesystems – and only, as we want each box to keep it’s original setup and not getting specific
– Bind to act as DNS cache and manage the domain
– Nginx as http server to provide basic sysinfo (phpsysinfo) and basic sysadmin (mostly: reset Samba passwords and connected wireless devices surveillance)
– transmission-daemon plus my torrent-watch.pl script to provide a networked BitTorrent client
– minidlna to make files available to non computer networked devices

Start with Debian netinst base install:

Obviously we’ll want some SWAP space. 2 Gb should be more than enough. Then we’ll want three ext4 filesystems. One for user data, one for the system, one for a system copy, as fallback. If we had two different disk, obviously the system copy would be the second one.

We’ll start the basic debian installation with that in mind: we’ll anyway just install the debian base stuff with OpenSSH.

/etc/default/rcS:

FSCKFIX=yes

/etc/default/grub:

GRUB_FALLBACK=2

Setting up basic functionalities/networking after reboot:

First, we’ll install some useful utilities:

apt-get install lm-sensors hddtemp cpufrequtils debfoster etckeeper localepurge
ethtool emacs23-nox ntp wget

Regarding sensors, you should configure hddtemp to run as a daemon listening on 127.0.0.1 and run:

sensors-detect

At this point, network devices should be known to the system. We have quite usual hardware so correct modules should already be loaded. lspci should return:

01:00.0 Ethernet controller: Intel Corporation 82574L Gigabit Network Connection
02:00.0 Network controller: Atheros Communications Inc. AR9300 Wireless LAN adaptor (rev 01)
03:00.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL8111/8168B PCI Express Gigabit Ethernet controller (rev 06)

Edit the NAME strings in /etc/udev/rules.d/70-persistent-net.rules in order to have eth0 being the internet device, eth1 and wlan1 the intranet ones, for clarity sake. You may unload and reload modules of these devices in order for them to get their definitive name.

We’ll use hostapd to provide Wifi access.

apt-get install hostapd

/etc/default/hostapd:

DAEMON_CONF="/etc/hostapd/hostapd.conf"

/etc/hostapd/hostapd.conf:

## base
interface=wlan1
ssid=whatever
channel=3

## wifi mode
hw_mode=g
ieee80211n=1

## access with WPA PSK
wpa=2
wpa_passphrase=WHATEVERYOUWANTSOFAR
wpa_key_mgmt=WPA-PSK
#wpa_pairwise=TKIP
rsn_pairwise=CCMP
auth_algs=1

# hw address filter (relaxed, as it is not real security)
macaddr_acl=0
deny_mac_file=/etc/hostapd/hostapd.deny

# EOF
touch /etc/hostapd/hostapd.deny

(this enable WPA2 access, if you want also WPA1, you must set wpa=3 and uncomment wpa_pairwise)

Then we’ll configure the network, defining a different subnet for wired and wireless connectivity. Some tutorials on the web propose to bridge the wireless to the wired. We won’t do that, we actually want to be able to easily distinguish the source of any request. Regarding security, the safe bet is to assume that wireless is always on the verge of getting cracked, so it must be kept confined.
editing /etc/network/interface:

# internet
auto eth0 iface
allow-hotplug eth0
iface eth0 inet dhcp

# intranet (wired)
auto eth1 iface
eth1 inet static 
   address 10.0.0.1
   netmask 255.255.255.0
   broadcast 10.0.0.255
   network 10.0.0.0

# intranet (wireless) 
auto wlan1 iface
wlan1 inet static
   address 10.0.1.1
   netmask 255.255.255.0
   broadcast 10.0.1.255
   network 10.0.1.0
# EOF

We need a working dhcp daemon, able to dynamically register new boxes:

apt-get install isc-dhcp-server

In /etc/default/isc-dhcp-server:

INTERFACES="eth1 wlan1"

In /etc/dhcp/dhcpd.conf:

option domain-name "mynetworkname.ici";
option domain-name-servers 10.0.0.1;
option routers 10.0.0.1;

log-facility local7;
authoritative;

# wired
subnet 10.0.0.0 netmask 255.255.255.0 {
range 10.0.0.25 10.0.0.125;
}

# wireless
subnet 10.0.1.0 netmask 255.255.255.0 {
range 10.0.1.125 10.0.1.225;
option routers 10.0.1.1;
}

(it’s best to add, as fallback, to the domain-name-servers option the defaults DNS provided by your ISP, as shown in /etc/resolv.conf)

The dhcp client must be tuned a bit, /etc/dhcp/dhclient.conf:

prepend domain-name-servers 10.0.0.1;
supersede domain-name "mynetworkname.ici";

We obviously need ip forwarding, editing /etc/sysctl.conf:

net.ipv4.ip_forward=1

and also immediately doing a:

echo 1 > /proc/sys/net/ipv4/ip_forward

We also need iptables

apt-get install iptables-persistent
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
/etc/init.d/iptables-persistent save

(I actually reused a perl script that also does some nice firewalling instead of simply doing this)

ifup eth1
ifup wlan1
invoke-rc.d hostapd restart
invoke-rc.d isc-dhcp-server restart

At this point, you should be able to log in with SSH on a distant box.

Provide local (dynamic) domain name server:

apt-get install bind9

Set up forwarders with your ISP’s DNS (as in /etc/resolv.conf) in /etc/bind/named.conf.options. Don’t bother doing that, /etc/bind/named.conf.options will be automatically generated by a script installed at the latest step. Instead, remove it so the script will make sure it is set proper at its first run:

rm -f /etc/bind/named.conf.options

You need to create zones (named as you like) in /etc/bin/named.conf.local:

zone "mynetworkname.ici" {
type master;
notify no;
file "/etc/bind/db.mynetworkname.ici";
allow-update { key dhcpupdate; };
};

zone "0.10.in-addr.arpa" {
type master;
notify no;
file "/etc/bind/db.10.0.0";
allow-update { key dhcpupdate; };
};
cd /etc/bind && cp db.local db.mynetworkname.ici

db.mynetworkname.ici:

$TTL    64800
@           IN      SOA      gate.mynetworkname.ici. root.mynetworkname.ici. (
2         ; Serial
604800         ; Refresh
86400         ; Retry
2419200         ; Expire
604800 )       ; Negative Cache TTL

IN      NS      nano.mynetworkname.ici.
mynetworkname.ici.                     IN      A       10.0.0.1
mynetworkname.ici.    IN    MX         10    10.0.0.1
nano        IN    A    10.0.0.1
gate            IN      CNAME   nano
cp db.255 db.10.0

db.10.0:

;
; BIND reverse data file
;
@       IN    SOA    nano.mynetworkname.ici. root.mynetworkname.ici. (
1                     ; Serial
604800         ; Refresh
8600               ; Retry
2419200               ; Expire
604800 ) ; Negative Cache TTL

0.10.in-addr.arpa.         NS  nano.mynetworkname.ici.
1.0                        PTR nano.mynetworkname.ici.

Now we add support for dynamic updates:

cd /etc/dhcp
dnssec-keygen -a hmac-md5 -b 256 -n USER dhcpupdate

/etc/bind/named.conf:

key dhcpupdate {
algorithm hmac-md5;
secret "YOURKEYGOESHERE";
};

(the secret being the latest string of .key file we’ve just generated)

/etc/dhcp/dhcpd.conf:

ddns-domainname "mynetworkname.ici";
ddns-rev-domainname "in-addr.arpa.";
ddns-update-style interim;
ignore client-updates;
update-static-leases on;

key dhcpupdate {
algorithm hmac-md5;
secret "YOURKEYGOESHERE";
}
zone mynetworkname.ici. {
primary 127.0.0.1;
key dhcpupdate;
}
zone 0.10.in-addr.arpa. {
primary 127.0.0.1;
key dhcpupdate;
}

Restrict read access to files containing the secret key and restart all:

chmod o-r /etc/bind/named.conf.local
chmod o-r /etc/dhcp/dhcpd.conf
rm /etc/dhcp/Kdhcpupdate.*.key /etc/dhcp/Kdhcpupdate.*.private

invoke-rc.d isc-dhcp-server restart
invoke-rc.d bind9 restart

Put user data in place:

User data will go in /srv. So we’ll add a few symlinks, after mounting the partition.

mkdir /srv/home /srv/common
rm -r /home && ln -s /srv/home /home

We then add default dirs:

mkdir /srv/common/torrents /srv/common/download /srv/common/musique /srv/common/films /srv/common/temp
cd /srv/common && chmod a+w * -R

We’ll also make sure any new user get a ~/samba directory.

mkdir /etc/skel/samba

Make it accessible over Samba:

Users will access files with Samba: anonymous in r+w in common, user only in their ~/samba (we don’t allow direct access to ~/ to block any tampering with directories like ~/.ssh)

apt-get install samba libpam-smbpass

/etc/samba/smb.conf:

workgroup = MYNETWORKNAME.ICI
interfaces = eth1 wlan1
bind interfaces only = yes
security = user
invalid users = root
unix password sync = yes
pam password change = yes
map to guest = bad user
# discard filename mangling backward compatibility, see https://yeupou.wordpress.com/2015/11/22/preventing-filenames-with-semicolon-to-be-garbled-by-samba/
mangle case = no
mangled names = no

[homes]
comment = Données protégées
path = /srv/home/%S/samba
writable = yes

[commun]
comment = Commun
path = /srv/common
browseable = yes
public = yes
force group = users
force user = nobody
guest ok = yes
writable = yes

[media]
comment = clef USB, etc
path = /media
browseable = yes
public = yes
force group = users
force user = nobody
guest ok = yes
writable = yes

We also want to use unix passwords for Samba instead of having two passwords databases.

/etc/pam.d/samba:

@include common-password

Make it accessible with UPnP-AV/DLNA:

apt-get install minidlna

/etc/minidlna.conf:

media_dir=/srv/common
network_interface=eth0
friendly_name=nano
inotify=yes

Once set up, we regenerate the database properly:

rm -f /var/lib/minidlna/files.db
invoke-rc.d minidlna restart

We add relevant iptables rules where SRC is the IP of your dlna client (you may want to alter this, for instance by using –source-range IP-IP instead of –src IP):

apt-get install iptables-persistent
iptables -A INPUT -i eth0 --src SRC -p udp --dport 1900 -j ACCEPT
iptables -A INPUT -i eth0 --src SRC -p tcp --dport 8200 -j ACCEPT
/etc/init.d/iptables-persistent save

Provide torrent client:

apt-get install transmission-daemon libtimedate-perl
invoke-rc.d transmission-daemon stop

mkdir /home/torrent
ln -s /srv/common/torrents /home/torrent/watch
usermod -d /home/torrent Debian-transmission

cd /usr/local/bin && wget https://github.com/yeupou/stalag13/raw/master/usr/local/bin/torrent-watch.pl && chmod +x torrent-watch.pl
cd /etc/cron.d && wget https://github.com/yeupou/stalag13/raw/master/etc/cron.d/torrent
cd /etc/cron.weekly && wget https://github.com/yeupou/stalag13/raw/master/etc/cron.weekly/torrent

Edit /etc/cron.d/torrent (uncomment, check pathes – you may want to add ~/watch/ instead of ~/watch if symlinks are involveed, etc).

Edit /etc/transmission-daemon/settings.json

"alt-speed-down": 120,
"alt-speed-enabled": false,
"alt-speed-up": 1,
"blocklist-enabled": true,
"download-dir": "/srv/common/download",
"message-level": 0,
"peer-port-random-on-start": true,
"port-forwarding-enabled": true,
"rpc-authentication-required": false,
invoke-rc.d transmission-daemon start

And log rotation /etc/logrotate.d/torrent:

/srv/common/torrents/log {
weekly
missingok
rotate 2
su debian-transmission users
nocompress
notifempty
}

Provide basic info and management:

The following will provides reminders of upgrades to be performed.

apt-get install libapt-pkg-perl
cd /etc/cron.daily && wget https://github.com/yeupou/stalag13/raw/master/etc/cron.daily/apt-warn && chmod +x apt-warn
phpsysinfo : basic system infos

phpsysinfo : basic system infos

We’ll use phpsysinfo to provide an overview of the system and a homemade script to allow distant administration.

apt-get install nginx phpsysinfo php5-cgi spawn-fcgi libfcgi-perl mysql-server libemail-sender-perl
cd /etc/init.d && wget https://github.com/yeupou/stalag13/raw/master/etc/init.d/php-fcgi && chmod +x php-fcgi && update-rc.d php-fcgi defaults
wget http://nginxlibrary.com/downloads/perl-fcgi/fastcgi-wrapper -O /usr/bin/fastcgi-wrapper.pl && wget http://nginxlibrary.com/downloads/perl-fcgi/perl-fcgi -O /etc/init.d/perl-fcgi && chmod +x /usr/bin/fastcgi-wrapper.pl /etc/init.d/perl-fcgi && update-rc.d perl-fcgi defaults

mkdir /srv/www
ln -s /usr/share/phpsysinfo/ /srv/www/sysinfo

/etc/nginx/sites-available/default:

listen 80; ## listen for ipv4; this line is default and implied
listen [::]:80 default_server ipv6only=on; ## listen for ipv6

root /srv/www;
index index.html index.htm index.php index.pl;
autoindex on;
server_name localhost nano nano.mynetworkname.ici;

# restrict to local wired network
allow 10.0.0.0/24;
allow 127.0.0.1;
deny all;

# pass the  scripts to FastCGI server listening on 127.0.0.1
location ~ ^/sysinfo/(.*)\.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
#       # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
}
location /sysadmin/index.pl {
fastcgi_pass  127.0.0.1:8999;
fastcgi_index index.pl;
fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_na\
me;
include fastcgi_params;
}

/etc/php5/cgi/php.ini:

cgi.fix_pathinfo = 0;

/etc/phpsysinfo/config.php:

define('PSI_ADD_PATHS', '/bin,/usr/bin,/sbin,/usr/sbin');
define('PSI_BYTE_FORMAT', 'auto_binary');
define('PSI_SENSOR_PROGRAM', 'LMSensors');
define('PSI_HDD_TEMP', 'tcp');
define('PSI_SHOW_MOUNT_OPTION', false);
define('PSI_HIDE_FS_TYPES', 'tmpfs,usbfs,devtmpfs');
define('PSI_HIDE_DISKS', '/dev/disk/by-uuid/8f7f616e-9140-4876-890a-cd6abfde837\
f');
define('PSI_HIDE_NETWORK_INTERFACE', 'lo,mon.wlan0');
define('PSI_SHOW_NETWORK_INFOS', true);
sysadmin : admin unix/samba passwords and watch wifi connections

sysadmin : admin unix/samba passwords and watch wifi connections

Follows the specific sysadmin web interface:

apt-get install passwdqc liburi-encode-perl libdata-password-perl libdbd-mysql-perl libemail-send-perl
cd /srv/www
mkdir sysadmin

cd /srv/www/sysadmin && wget https://raw.github.com/yeupou/calaboose.sysadmin/master/index.pl
cd /usr/local/bin && wget https://raw.github.com/yeupou/calaboose.sysadmin/master/sysadmin-update.pl
chgrp www-data /srv/www/sysadmin/index.pl
chmod +x /srv/www/sysadmin/index.pl /usr/local/bin/sysadmin-update.pl
chmod o-rwx /srv/www/sysadmin/index.pl /usr/local/bin/sysadmin-update.pl
mysql -e "CREATE DATABASE sysadmin"
mysql -e "CREATE TABLE sambaclients (ip_address varchar(32) NOT NULL default '0', user_name text NOT NULL, PRIMARY KEY (ip_address))" sysadmin
mysql -e "CREATE TABLE wificlients (hw_address varchar(32) NOT NULL default '0', status varchar(32) NOT NULL default 'S', PRIMARY KEY (hw_address), ip_address varchar(32), hostname varchar(128))" sysadmin
mysql -e "CREATE USER 'www-data'@'localhost'"
mysql -e "SET PASSWORD FOR 'www-data'@'localhost' = PASSWORD('kdkadkda')"
mysql -e "GRANT ALL ON sysadmin.* TO 'www-data'@'localhost'"

/srv/www/sysadmin/index.pl:

my $db_password = "kdkadkda";

/usr/local/bin/sysadmin-update.pl:

my $db_password = "kdkadkda";

It requires a cronjob to be set up in /etc/cron.d/sysadmin:

* * * * * root /usr/local/bin/sysadmin-update.pl
invoke-rc.d nginx restart
invoke-rc.d php-fcgi restart
invoke-rc.d perl-fcgi restart

Both http://nano/sysinfo and http://nano/sysadmin should work. The sysadmin script allows to change, on-the-fly UNIX passwords, by sending random ones by mail. It means that anyone within the intranet could sniff them out. That obviously won’t do if your legit users aren’t trustworthy.

(note : the sysadmin interface is in French but the strings can easily be translated in English. Adding gettext support would have been overkill here)

Create backup system:

With only one disk, having a redundant system is not optimal. But it’s still an okay failsafe.

The following assumes you gave a label to your root partition, something like wd2Tdebian64 here. Create a filesystem on the backup partition:

mkfs.ext4 -L wd2Tdebian64bak /dev/sda7
mkdir /mnt/sysclone

Add /etc/cron.weekly/backup-system (based on https://github.com/yeupou/stalag13/blob/master/etc/cron.weekly/stalag13-backups):

if [ `hostname` != "nano" ]; then exit; fi

## system cloning
sys=wd2Tdebian64
bak=wd2Tdebian64bak
mount=/mnt/sysclone
ignore="dev lost+found media proc run sys tmp srv"

# determines which partition is currently / by reading /etc/fstab
orig=`cat /etc/fstab | grep $sys | cut -f 1 | cut -f 2 -d = | sed 's/ //g'`
case $orig in
$sys)
dest=$bak
;;
$bak)
dest=$sys
;;
*)
echo "Unable to determine whether we are currently using $sys or $bak, we found $orig. Exiting!"
exit
;;
esac

# then proceed

# easy reminder of the last cloning run
date > /etc/.lastclone
echo "$orig > $dest" >> /etc/.lastclone
etckeeper commit "cloning system from $orig to $dest" >/dev/null 2>/dev/null

# mount clone system
if [ ! -d $mount ]; then exit; fi
mount -L $dest $mount

# set up ignore list
for dir in $ignore; do
touch /$dir.ignore
done

# do copy
for dir in /*; do
if [ -d $dir ]; then
if [ ! -e $dir.ignore ]; then
# update if not set to be ignored
/usr/bin/rsync --archive --one-file-system --delete $dir $mount/
else
# otherwise just make sure the directory actually exists
if [ ! -e $mount/$dir ]; then mkdir $mount/$dir; fi
rm $dir.ignore
fi
fi
done

# update filesystem data
sed -i s/^LABEL\=$orig/LABEL\=$dest/g $mount/etc/fstab

# make system bootable (use --force: gpt partition table)
/usr/sbin/grub-mkdevicemap 2>/dev/null
/usr/sbin/update-grub 2>/dev/null
/usr/sbin/grub-install --force `blkid -L $orig | tr -d [:digit:]` >/dev/null 2>/dev/null

# (sleep to avoid weird timeout after rsync)
sleep 10s

# then cleanup
umount $mount
fsck -a LABEL=$dest > /dev/null

## EOF

Final tuning: set mails, restrict SSH access, etc:

We activate exim4 for direct SMTP (and make sure the ISP does not block the relevant traffic) with the command:

dpkg-reconfigure exim4-config

Then we want some specific SSH access model. We already set up the sysadmin interface to change users password – both Samba and unix. But we actually have only one admin here. He’s own account will be the only one given SSH access. No root direct access. And he’ll be able to connect with a password only from wired intranet (eth1). Otherwise, internet (eth0) or wireless intranet (wlan1) will require a pair of SSH keys. To achieve this, we’ll actually restrict SSH to members of the staff unix group (just in case, at some point, we want to add a second one).

To achieve this easily, will plug OpenSSH into xinetd.

We have a few terminals open on the server. We shut SSH down (opened sessions wont be affected) and forbid the init script to start it anymore:

invoke-rc.d ssh stop
touch /etc/ssh/sshd_not_to_be_run

We change a bit the default configuration in /etc/ssh/sshd_config:

PermitRootLogin no
X11Forwarding no
AllowGroups staff
PasswordAuthentication no

We add the relevant user to the group:

adduser thisguy staff

Then we set up xinetd to run it:

apt-get install xinetd

Edit /etc/xinetd.d/ssh_intranet:

# To work, sshd must not run by itself, so /etc/ssh/sshd_not_to_be_run
# should exists

# only from local wired network
service ssh
{
socket_type     = stream
protocol        = tcp
wait            = no
user        = root
bind            = 10.0.0.1
only_from    = 10.0.0.0/24
server          = /usr/sbin/sshd
server_args     = -i -o PasswordAuthentication=yes
log_on_success  = HOST USERID
}

# from local wireless network
service ssh
{
socket_type     = stream
protocol        = tcp
wait            = no
user        = root
bind            = 10.0.1.1
only_from       = 10.0.1.0/24
server          = /usr/sbin/sshd
server_args     = -i
log_on_success  = HOST USERID
}

# EOF

This set up only access for intranet interfaces (eth1 and wlan1 if you named them as recommended in this page). Internet interface IP is obtained with DHCP so it would be a pain in the ass to keep it up to date, especially if we’re behind a dynamic IP. However, xinetd does not allow to set interface by device name but wants an IP. So we need to script this. And, at the same time, we’ll deal with Bind DNS forwarders so it does proper caching. So we’ll add /etc/dhcp/dhclient-exit-hooks.d/xinetd-bind:

XINETD_CONFFILE=/etc/xinetd.d/ssh_internet
BIND_CONFFILE=/etc/bind/named.conf.options

# SSH over xinetd requires the IP to be hardcoded
if [ -n "$new_ip_address" ]; then 

    # change only if we have a new ip and if this one mismatch the old
    if [ "$new_ip_address" != "$old_ip_address" ] || 
         [ ! -e $XINETD_CONFFILE ]; then

        echo "# DO NOT EDIT, automatically generated by $0
# (IP changed from $old_ip_address to $new_ip_address)
# `date`
service ssh
{
  socket_type     = stream
  protocol        = tcp
  wait            = no
  user            = root
  bind            = $new_ip_address
  server          = /usr/sbin/sshd
  server_args     = -i
  cps             = 30 10
  per_source      = 5
  log_on_success  = HOST USERID
}

# EOF" > $XINETD_CONFFILE

        # now reload xinetd
        invoke-rc.d xinetd restart >/dev/null 2>&1
        fi
fi


# Bind DNS cache need forwarders similar to the content of resolv.conf
if [ -n "$new_domain_name_servers" ]; then
    
    # change only if we have DNS
    if [ "$new_domain_name_servers" != "$old_domain_name_servers" ] ||
        [ ! -e $XINETD_CONFFILE ]; then

         echo "// DO NOT EDIT, automatically generated by $0
// (IPs changed from $old_domain_name_servers to $new_domain_name_servers) 
// `date`
options {
        directory \"/var/cache/bind\";

        // If there is a firewall between you and nameservers you want
        // to talk to, you may need to fix the firewall to allow multiple
        // ports to talk.  See http://www.kb.cert.org/vuls/id/800113

        // If your ISP provided one or more IP addresses for stable 
        // nameservers, you probably want to use them as forwarders.  
        // Uncomment the following block, and insert the addresses replacing 
        // the all-0's placeholder.

        forward first;
        forwarders {" > $BIND_CONFFILE
         

        # add valid forwarders
        for server in $new_domain_name_servers; do
            # (verbose) skip local ips
            if [ ! -n "`ifconfig | grep ":$server "`" ]; then 
                echo "                $server;" >> $BIND_CONFFILE
            else 
                echo "                //SKIP THIS LOCAL IP! $server;" >> $BIND_CONFFILE

            fi
        done

        echo "        };

        auth-nxdomain no;    # conform to RFC1035
        listen-on-v6 { any; };
};

// EOF" >> $BIND_CONFFILE
        
       # now reload bind
       # (this may be useles because another script may do that already)
       invoke-rc.d bind9 restart >/dev/null 2>&1
       fi
fi

It should modify conffiles and restart daemons only if there is an actual change. You can test that it works properly doing:

ifdown eth0 && ifup eth0

Then you can make a few SSH login test and see results in /var/log/auth.log.

At this point, you should realize that this perfectly working setup has an obvious drawback: if you’re wirelessly connected (subnet 10.0.1.0) `ssh nano` will, thanks to the DNS, actually do a `ssh 10.0.0.1`. And per our xinetd rules, you’ll get kicked out, as we accept on this IP only clients from the same subnet (10.0.0.0). So you’ll have to manually type ssh 10.0.1.1 to be able to connect. We’ll add an iptable rule to fix this: we’ll say that whenever we try to connect to 10.0.0.1 over ssh from wireless interface, we’ll redirect to 10.0.1.1 same port. So we’ll do:

iptables -t nat -A PREROUTING -p tcp -i wlan1 --destination 10.0.0.1 --dport 22 -j DNAT --to 10.0.1.1:22
/etc/init.d/iptables-persistent save

Updates/Quickfix:

Update 1: Yeah, just published and already patched. Ahem. I noticed that, on reboot, sometimes hostapd is not working as expected. Users can connect but never get an IP. The LSB  init script of hostapd looks odd to me, since it actually makes it starting before dhcpd. I modified /etc/init.d/hostapd so isc-dhcp-server $network gets in Required-Start and then ran rc-update.d hostapd.

Update 2: /media was configured to be served over Samba but no automount was set for USB mass storage devices. Here it is, (not thoroughly tested as I don’t use such devices much), edit /etc/udev/rules.d/80-removable-usb.rules:

ACTION=="add", SUBSYSTEMS=="usb", KERNEL=="sd*", ENV{ID_FS_TYPE}!="", SYMLINK+="usb%k"
ACTION=="add", SUBSYSTEMS=="usb", KERNEL=="sd*", ENV{ID_FS_TYPE}!="", RUN+="/bin/mkdir /media/usb%k"
ACTION=="add", SUBSYSTEMS=="usb", KERNEL=="sd*", ENV{ID_FS_TYPE}=="vfat|ntfs", ENV{mount_extra_options}="dmask=0000,fmask=0111,"
ACTION=="add", SUBSYSTEMS=="usb", KERNEL=="sd*", ENV{ID_FS_TYPE}!="", RUN+="/bin/mount -t auto -s -o $env{mount_extra_options}noatime,nodiratime,noexec,nodev /dev/usb%k /media/usb%k", OPTIONS="last_rule"
ACTION=="remove", SUBSYSTEMS=="usb", KERNEL=="sd*", ENV{ID_FS_TYPE}!="", RUN+="/bin/umount /media/usb%k"
ACTION=="remove", SUBSYSTEMS=="usb", KERNEL=="sd*", ENV{ID_FS_TYPE}!="", RUN+="/bin/rmdir /media/usb%k", OPTIONS="last_rule"

Update 3: I added /srv to the list of directories to be ignored by the backup script, as it contains data.

Update 4: Now /etc/xinet.d/ssh is split between ssh_intranet and ssh_internet, the later being generated by a script in /etc/dhcp/dhclient-exit-hooks.d/. This avoids us to hardcode IPs by hand. Still, it implies hardcoding IP in conffiles, so it must be kept in mind when doing major software upgrade that may imply conffile syntax change, etc.

Update 5: I noticed auto eth0 was missing in /etc/network/interfaces. I added it (and maybe Update 1 was related to that).

Update 6: I added sample firewall rules for minidlna.

Update 7: In case you have no static IP from your ISP, you may want to create a free account on no-ip and install a client:

apt-get install ddclient

And configure /etc/ddclient.conf

protocol=noip
use=web
server=dynupdate.no-ip.com
login=youremail@provide
password='yourpassword'
hereitis.servebeer.com
invoke-rc.d ddclient restart

Update 8: Now debian packages are provided, notably for torrents over SAMBA management.

Update 9: Use usual listen statement in /etc/nginx/site-available/default

Update 10: Deactivate Samba backward compatibility filename mangling

Reminder, needs to be changed checked whenever the server is relocated:

(obviously you should not use any sample password provided in this page)

We avoided hardcoding IPs but it was not always possible. Yes we did. However, in case of an ISP/main network change, which usually implies IP changes, make sure the following are properly updated by the dhclient:

/etc/bind/named.conf.option: ISP DNS IPs as in /etc/resolv.conf
/etc/xinetd.d/ssh_internet: internet IP as provided by ifconfig

Disclaimer: this whole setup has been made to be maintainable by people that have not much experience in computer system administration – but enough to log in via SSH without being completely lost in limbo. As such, you’ll probably notice I made some tradeoff between security and easiness, for instance by providing in clear text the Wifi passphrase on the web sysadmin page. Anyway I think most important pieces are rock solid and secondary one does not matter much (Wifi is insecure by design, by concept I would even dare to say, using it is itself such an obvious tradeoff).

(this is still being tested, I may update this page soon; it’s likely I forgot to mention a few apt-get of perl packages required by the scripts; please mail me if you find any flaws or obvious issues with what is proposed here)

Securing and improving internet services, including SSH and SMTP, using xinetd

As stated by its manpage, xinetd performs the same function as inetd: it starts programs that provide Internet services. Instead of having such servers started at system initialization time, and be dormant until a connection request arrives, xinetd is the only daemon process started and it listens on all service ports for the services listed in its configuration file. When a request comes in, xinetd starts the appropriate server. Because of the way it operates, xinetd (as well as inetd) is also referred to as a super-server.

The X in the name stands for extended. Which means the following is really for xinetd, not openbsd-inetd 🙂

Still according to its manpage, so far, the only reason for the existence of a super-server was to conserve system resources by avoiding to fork a lot of processes. While fulfilling this function, xinetd takes advantage of the idea of a super-server to provide features such as access control and logging. Some people will say, and they’ll be right, that running all services through a wrapper implies, instead of conserving resources, somekind of overhead: in conserves resources since it avoids running concurrently as many services as available on the server, right, but this makes no sense on a server with a wide audience that actually have enough users so all services are anyway almost always running concurrently. In this case, some people would be correct to assume more efficient to use standalone servers for each service .

But, and that’s the point, access control makes a difference. Sure, standalone servers have also access control. OpenSSH does ; and using OpenSSH via xinetd should not discourage to look into /etc/ssh/sshd_config. Nonetheless, xinetd access control applies to any service running through it. And that’s prett-ay, pretty good. For instance, you do not need to configure each standalone server to be hardened enough against DDoS, if xinetd is, you should be fine.

So let’s get to business. We assume here you have xinetd up an running. Shouldn’t be a big deal, xinetd is standard on many GNU/Linux systems.

Normally, you should have a /etc/xinetd.d where you can add bits of config for xinetd (if it does not exists, well, you could still use the default config file /etc/xinetd.conf).

There you have basic standard stuff: chargen, daytime, discard, echo, time. If you do not want to provide these, sure make sure each entry in these files got the line:

disable = yes

For each following example to work, you must indeed shut down the standalone server, otherwise the service port won’t be available to xinetd. Also, in the following examples, you’ll have to edit the IPs according to your network.

This is for OpenSSH, with a specific port for root login (probably a nuisance on a distant server supposed to be frequently accessed as root – but a safe pick for a local network server rarely accessed as root from the web):

# To work, sshd must not run by itself,
# so /etc/ssh/sshd_not_to_be_run
# should exists

# allows unrestricted SSH only to local network
service ssh
{
socket_type = stream
protocol = tcp
wait = no
user = root
bind = 192.168.0.1
only_from = 192.168.0.0
server = /usr/sbin/sshd
server_args = -i
}

# allows SSH from the web but restricted to users listed
# (root being forcefully disallowed)
# restrict also to only 5 connections per IP (per-source)
# and limit the rate of incoming connections (cps)
service ssh
{
socket_type = stream
protocol = tcp
port = 22
wait = no
user = root
bind = 88.???.???.???
server = /usr/sbin/sshd
server_args = -i -o PermitRootLogin=no -o AllowUsers=thisuser
cps = 30 10
per_source = 5
log_on_success = HOST USERID
}

# allow SSH from the web only for root, on port 33333
# requires /etc/services to include lines:
# rootexternalssh 33333/tcp
# rootexternalssh 33333/udp
service rootexternalssh
{
socket_type = stream
protocol = tcp
port = 33333
wait = no
user = root
server = /usr/sbin/sshd
server_args = -i -p 33333 -o AllowUsers=root
cps = 30 10
per_source = 3
log_on_success = HOST USERID
}

This is for Dovecot, an IMAPS server. This setup listen on the local network. You can easily tune it following the example given above:


service imaps
{
socket_type = stream
protocol = tcp
wait = no
user = root
bind = 192.168.0.1 127.0.0.1
only_from = 192.168.0.0 127.0.0.1
server = /usr/lib/dovecot/imap-login
flags = IPv4
server_args = --ssl
}

I won’t provide an exhaustive list of services that you can run with xinetd. You can surely find for yourself what suits you best! 🙂 But the presentation wouldn’t be complete if I missed traps. Yes, you can set up traps with xinetd. For instance if you do not use ftp, irc, telnetd, etc, you can safely assume that someone trying to connect on these services ports is trying to do something he shouldn’t. And you can then decide to disallow further connections.

# bind must be set so we do not shut off clients from the
# local network that made dumb scan
service ftp
{
socket_type = stream
wait = no
user = root
bind = 88.???.???.???
flags = SENSOR
type = INTERNAL
log_on_success = HOST PID
deny_time = 1440
}

service sftp
{
socket_type = stream
wait = no
user = root
bind = 88.???.???.???
flags = SENSOR
type = INTERNAL
log_on_success = HOST PID
deny_time = 1440
}

service irc
{
socket_type = stream
wait = no
user = root
bind = 88.???.???.???
flags = SENSOR
type = INTERNAL
log_on_success = HOST PID
deny_time = 1440
}

service telnet
{
socket_type = stream
wait = no
user = root
bind = 88.???.???.???
flags = SENSOR
type = INTERNAL
log_on_success = HOST PID
deny_time = 1440
}

These are basic examples. You can do more.

For instance, years ago, I wrote SeeYouLater, denying access to spammers with hosts.deny, a production-ready software that looks in the SMTP daemon logs for identified spam sources IPs and then ban them via /etc/hosts.deny (which xinetd handles).
With the SMTP daemon run through xinetd, any identified spam source will no longer even be able to connect (which presents plenty of advantages, as it is costless by comparison to discarding the spam sources during SMTP transaction).
SeeYouLater depends on perl and MySQL, the debian apt source is

deb http://dl.gna.org/seeyoulater/ ./

and there is a cookbook entry covering setup with Exim.

I’d like to mention that I ran xinetd on Gna! main servers (for mail with exim, or for CVS/SVN/Arch, etc – mail server was moved and I did not follow the way it is set up) and the overhead mentioned above was unnoticeable while the number of connections per minutes was quite higher than what you would expect on a small business network server or whatever.