Raspberry Pi: setting up alpine, lighttpd and letsencrypt

Categories: alpine raspberry pi lighttpd linux

Introduction

In this post I will explain how to set up Alpine Linux for the RPi, with the necessary configuration for the RPi to power a USB hard drive, how to install lighttpd and configure automatic renewal of TLS certificates with lestencrypt.

Alpine Linux

Alpine Linux can be installed on te RPi following the wiki guide.

After instalation, we add a new user which we will use for logging in:

adduser green

After logging in with our new user (using password) we’ll add some ssh public keys for future logins:

vi /home/green/.ssh/authorized_keys

I will also download some configurations files for Vim and tmux:

curl https://gitlab.com/dhole/dot_files/raw/master/.tmux.conf -o ~/.tmux.conf
curl https://gitlab.com/dhole/dot_files/raw/master/.airline_tmux -o ~/.airline_tmux
curl https://gitlab.com/dhole/dot_files/raw/master/.vimrc_basic -o ~/.vimrc

Now we will log in as root and store the files persistently:

lbu add /home/green/.ssh/authorized_keys
lbu add /home/green/.vimrc
lbu add /home/green/.tmux.conf
lbu add /home/green/.airline_tmux

From now on everything will be done as root. For convenience I open a tmux session after logging in as my regular user, and get a root shell in one tmux window.

First we will configure the boot process of the RPI to allow the USB connections to offer the maximum power allowed, otherwise the external hard drive will not work properly. We are also assigning the minimum amount of RAM to the GPU because we’ll be using the RPI as a headless server.

# remount sd-card writeable
mount -o remount,rw /media/mmcblk0p1

# create rpi2/3 config
cat << EOF > /media/mmcblk0p1/usercfg.txt
disable_splash=1
boot_delay=0
start_x=0
max_usb_current=1
gpu_mem=16
EOF

sync
reboot

Now we install the required packages for our needs, and delete the default HTTP server that comes with busybox.

apk add vim sudo openssl bash lighttpd-mod_auth rsync
apk del mini_httpd

I will use a script to decrypt the USB hard disk partition. I will be running this script manually every time I reboot the RPI.

cat << EOF > /root/startup.sh
#! /bin/sh

cryptsetup luksOpen /dev/sda1 disk
mkdir -p /mnt/disk
mount /dev/mapper/disk /mnt/disk/

rc-service lighttpd start
EOF

lbu add /root/startup.sh

Lighttpd

We run the previous script to mount the encrypted partition in /mnt/disk and we’ll move some private folders there:

/root/startup.sh
mkdir /mnt/disk/alpine-root
cd /mnt/disk/alpine-root
mkdir -p etc/dehydrated var/log/lighttpd var/www
ln -sf /mnt/disk/alpine-root/etc/dehydrated/ /etc/dehydrated
ln -sf /mnt/disk/alpine-root/var/www/ /var/www
ln -sf /mnt/disk/alpine-root/var/log/lighttpd /var/log/
chown -R lighttpd:lighttpd /var/log/lighttpd

I want to enable HTTP auth for some paths in the HTTP server, so I’ll use a script to add new triplets of user, realm and password.

mkdir /etc/lighttpd/.htpasswd
cd /etc/lighttpd/.htpasswd/

cat << EOF > hash.sh
#!/bin/sh
user=$1
realm=$2
pass=$3
hash=`echo -n "$user:$realm:$pass" | md5sum | cut -b -32`
echo "$user:$realm:$hash"
EOF

chmod 755 hash.sh

After this I can add a username and password for the ‘private’ realm.

./hash.sh 'username' 'private' 'password' > /etc/lighttpd/.htpasswd/lighttpd-htdigest.username

Now it’s time to configure lighttpd. The file is already populated with the default configuration, so I’m just showing the changes I added, copy them where they belong as needed. In the following configuration I’m configuring the server to listen on port 80 to serve redirections to https; and I’m listening on port 443 for the primary usage with sane security configurations. I’ll be using the certificate generated by the dehydrated letsencrypt client, which will be an elliptic curve key. Finally I’m enabling http auth with the previous user and password for all paths that start with /private/

vim /etc/lighttpd/lighttpd.conf
server.modules = (
...
    "mod_redirect",
    "mod_access",
    "mod_auth",
    "mod_setenv",
...
)
...
...
# {{{ includes
...
include "cgit.conf"
...
# }}}
...
...
$SERVER["socket"] == ":443" {
  ssl.engine    = "enable"
  ssl.pemfile   = "/etc/dehydrated/certs/lizard.kyasuka.com/combined.pem"
  ssl.ca-file   = "/etc/dehydrated/certs/lizard.kyasuka.com/chain.pem"

  #### Mitigate BEAST attack:

  # A stricter base cipher suite. For details see:
  # http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-3389
  # or
  # http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-3389

  ssl.cipher-list = "EECDH+AESGCM:EDH+AESGCM:AES128+EECDH:AES128+EDH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK"
  #
  # Make the server prefer the order of the server side cipher suite instead of the client suite.
  # This is necessary to mitigate the BEAST attack (unless you disable all non RC4 algorithms).
  # This option is enabled by default, but only used if ssl.cipher-list is set.
  ssl.honor-cipher-order = "enable"

  # Mitigate CVE-2009-3555 by disabling client triggered renegotiation
  # This option is enabled by default.
  #
  ssl.disable-client-renegotiation = "enable"
  #

  ssl.use-compression = "disable"
  ssl.use-sslv2 = "disable"
  ssl.use-sslv3 = "disable"

  ssl.dh-file = "/etc/ssl/dhparam.pem"
  ssl.ec-curve = "prime256v1"

  setenv.add-response-header = ( "Strict-Transport-Security" => "max-age=15768000") # six months
}
...
...
$HTTP["url"] =~ "^/private/(.*)" {
  auth.backend = "htdigest"
  auth.backend.htdigest.userfile = "/etc/lighttpd/.htpasswd/lighttpd-htdigest.green"
  auth.require = ( "" =>
      (
      "method"  => "digest",
      "realm"   => "private",
      "require" => "valid-user"
      ),
  )
}
...
...

In order to protect against the Logjam Attack we’ll generate a new Diffie-Hellman group of 4096 bits. I first tried this on the RPi but after 12h it hadn’t finished, so I did it on my laptop and transfered the file.

openssl dhparam -out /etc/ssl/dhparam.pem 4096

Letsencrypt

Now we install the dehydrated letsencrypt client. I’m choosing this one instead of the official one to avoid pulling all the python dependencies, and to avoid running it as root. dehydrated is written entirely in bash.

mkdir /mnt/disk/alpine-root/git
cd /mnt/disk/alpine-root/git/
git clone https://github.com/lukas2511/dehydrated

mkdir /etc/dehydrated
cp /mnt/disk/alpine-root/git/dehydrated/docs/examples/config /etc/dehydrated/config
mkdir -p /var/www/localhost/htdocs/.well-known/acme-challenge

chown lighttpd:lighttpd -R /var/www

lbu inc /var/www

Now we edit the default dehydrated config to use a different path to store the challenge and to generate elliptic curve keys, using the NIST P-256 curve. I would have preferred using the Ed25519 curve, but it’s not yet part of the TLS standard :(

vim /etc/dehydrated/config
...
WELLKNOWN="/var/www/localhost/htdocs/.well-known/acme-challenge/"
...
KEY_ALGO=prime256v1
...

Then we add the list of domains and subdomains that we want plan to use. Every line should be a space separated list of subdomains belonging to the same domain. I’m only using one subdomain for one domain.

cat << EOF > /etc/dehydrated/domains.txt
lizard.kyasuka.com
EOF

Next we run the letsencrypt client to generate and sign the certificates, and generate a file with the private key and certificate that lighttpd will use.

chown lighttpd:lighttpd -R /etc/dehydrated/

sudo -u lighttpd /mnt/disk/alpine-root/git/dehydrated/dehydrated -c

sudo -u lighttpd cat /etc/dehydrated/certs/lizard.kyasuka.com/privkey.pem \
/etc/dehydrated/certs/lizard.kyasuka.com/cert.pem \
> /etc/dehydrated/certs/lizard.kyasuka.com/combined.pem

To automate the renewal process we’ll add an entry to the lighttpd user crontab.

sudo -u lighttpd crontab -e
42      5       *       *       *       /mnt/disk/alpine-root/git/dehydrated/dehydrated -c && \
                                        cat /etc/dehydrated/certs/lizard.kyasuka.com/privkey.pem \
                                        /etc/dehydrated/certs/lizard.kyasuka.com/cert.pem \
                                        > /etc/dehydrated/certs/lizard.kyasuka.com/combined.pem && \
                                        /mnt/disk/alpine-root/git/dehydrated/dehydrated -gc

Last details

Finally, considering that Apline Linux runs from RAM we realize that if the RPi powers off, we’ll lose al logs (except for lighttpd’s logs, which we are writing directly to our encrypted partition). It’s useful to read the logs after our server goes down, so we add a crontab that will rsync the logs to the encrypted parititon.

crontab -e
...
...
*/15    *       *       *       *       ls /mnt/disk/alpine-root && \
                                        rsync -a /var/log/dmesg /var/log/messages /mnt/disk/alpine-root/var/log/

Finally, commit all the changes to store them permanently:

lbu commit

In the next post I will explain how to use the RPi as a git server, with cgit a the web interface.

Written by Dhole