Category Archives: Linux

Full UEFI secure boot on Fedora using signed initrd and systemd-boot

Also posted on Medium.

“Security Circus” by Alexandre Dulaunoy is licensed under CC BY-SA 2.0.

Although secure boot support has been in place in most distros for a while, most, if not all, still have a missing part in the chain, namely the missing signing of the initrd image. This allows an attacker to tamper with the initrd image without detection. Lennart Pottering wrote a very good blog post about the issue and outlined a solution that can be implemented.

Until measures as outlined there or similar are implemented in the distros, there a couple of solutions ready to set up now. They do however require a little work from the end user.

Option one is to encrypt the boot partition. This works, however you need to use grub and grub still only supports luks version 1. Also you need to decrypt the boot partition using a password or passphrase, as this is the only method grub supports, not other methods such as fido2 or tpm, which is supported by systemd-cryptenroll.

The second option is the one I’m going to describe, that is to sign and verify the initrd image. That way, it does not matter that the boot partition is unencrypted, as there is no secrets in the initrd image, and any tampering will break the signature and the boot process.


In order to sign and validate the initrd image, you will first need to set up and install your own secure boot signing key. To ease setup and use, we use sbctl. First install required packages (if you don’t want to install asciidoc and go on your system, you can use toolbox)

sudo dnf install asciidoc golang

Download the sbctl release file, unpack and install (change version as needed)

VERSION=0.9
cd /tmp
curl -L "https://github.com/Foxboron/sbctl/releases/download/${VERSION}/sbctl-${VERSION}.tar.gz" | tar zxvf -
cd "sbctl-${VERSION}"
make
sudo make install

and generate the key by running

sudo sbctl create-keys

If your computer supports it, you can install the key by running

sudo sbctl enroll-keys

If successfull, reboot. If this fails, you will need to manually install the key. This can vary from system to system, but in most cases the following will work

  • Runsudo openssl x509 -in /usr/share/secureboot/keys/db/db.pem -outform DER -out /boot/efi/EFI/fedora/DB.cer
  • Reboot into BIOS. There will usually be an option under secure boot to add your own key to the DB database from file, where you can browse the EFI partition and find the DB.cer file in EFI/fedora

After adding the key, sudo sbctl status should now a checkmark for installed and secure boot, similar to

Installed:	✔ Sbctl is installed
Owner GUID: a9fbbdb7-a05f-48d5-b63a-08c5df45ee70
Setup Mode: ✔ Disabled
Secure Boot: ✔ Enabled

The next step is setting up systemd-boot and automatic signing of the initrd on kernel upgrades. First we need to change the efi mount from /boot/efi to /efi

sudo umount /boot/efi
sudo mkdir /efi
sudo sed -i `s-/boot/efi-/efi-' /etc/fstab
sudo mount /efi
sudo ln -s /efi /boot/efi

The last command is because some packages such as fwupd expects to find the efi mount at /boot/efi

Setup systemd-boot and sign it by running

sudo bootctl install
sudo sbctl sign /efi/EFI/systemd/systemd-bootx64.efi

You might want to edit /efi/loader/loader.conf and add timeout 3 or similar, in order to get a prompt to choose images when booting.

To make sure systemd-boot keeps being signed on updates, we will need the sbsigntoolspackage installed

sudo dnf install sbsigntools

Then run sudo systemctl edit systemd-boot-update.service and add the following

[Service]
ExecStart=/bin/sh -c 'sbverify --cert /usr/share/secureboot/keys/db/db.pem /efi/EFI/systemd/systemd-bootx64.efi || sbctl sign /efi/EFI/systemd/systemd-bootx64.efi'

Next, we add dracut configuration for creating a combined and signed file containing efi stub loader, kernel and initrd. Edit /etc/dracut.conf.d/local.conf and add

uefi=yes
uefi_secureboot_cert=/usr/share/secureboot/keys/db/db.pem
uefi_secureboot_key=/usr/share/secureboot/keys/db/db.key
dracut_rescue_image=no

The reason we disable the rescue image, is that this is very big and will most likely fill your EFI partition.

The last piece is to add a kernel install script to change the systemd-boot entry to load only the combined image. Edit /etc/kernel/install.d/99-use-signed-image.install with the content

#!/bin/bash
COMMAND="$1"
KERNEL_VERSION="$2"
ENTRY_DIR_ABS="$3"
if ! [[ $COMMAND == add ]]; then
exit 1
fi
MACHINE_ID="$KERNEL_INSTALL_MACHINE_ID"
BOOT_ROOT="$KERNEL_INSTALL_BOOT_ROOT"
if [[ -f /etc/kernel/tries ]]; then
read -r TRIES </etc/kernel/tries
if ! [[ "$TRIES" =~ ^[0-9]+$ ]] ; then
echo "/etc/kernel/tries does not contain an integer." >&2
exit 1
fi
LOADER_ENTRY="$BOOT_ROOT/loader/entries/$MACHINE_ID-$KERNEL_VERSION+$TRIES.conf"
else
LOADER_ENTRY="$BOOT_ROOT/loader/entries/$MACHINE_ID-$KERNEL_VERSION.conf"
fi
sed -i "/^initrd/d" "${LOADER_ENTRY}"
sed -i "/^linux/s/linux$/initrd/" "${LOADER_ENTRY}"

Make the script executable by running sudo chmod 755 /etc/kernel/install.d/99-use-signed-image.install

Everything should now be up and running, the last thing to do is re-run kernel install for the current kernel in order to get a new updated and signd initrd image by running

sudo /bin/kernel-install -v  add $(uname -r) /lib/modules/$(uname -r)/vmlinuz

Reboot and you should have a boot that is signed all the way. Run sudo bootctl status to verify. Current Boot Loader should say systemd-boot and under Default Boot Loader Entry the linux line should point to initrd (this is the combined and signed initrd).

You can now remove the grub boot entry, so that there is no way to boot using the unsigned initrd image. Run sudo efibootmgr -v to list current boot entries. Then run sudo efibootmgr -B -b XX where XX is the number, for instance 0003

Finally, edit /etc/fwupd/uefi_capsule.conf and set EnableGrubChainLoad=false

Running cec-client with Raspberry PI on Debian or Ubuntu 64-bit

Using cec-client with a raspberry pi on debian or ubuntu does not work out of the box, as the cec-client in the debian and ubuntu repositories has not been compiled with support for the propriary raspberry libraries. In addition, these libraries are 32-bit only. This is a guide in how to get this working.

First you need to set up a 32-bit chroot environment. Install debootstrap and then run

debootstrap --variant=buildd --arch=armhf bullseye /opt/cec-client/

You will then need to download the raspberry pi libraries. Unpack /opt/vc from the tarball into /opt/cec-client (replace url with newer version if needed):

cd /tmp
curl -L https://github.com/raspberrypi/firmware/archive/1.20210108.tar.gz | tar zxf -
cp -r firmware*/opt/ /opt/cec-client/

Add the raspberry pi lib to ldconfig by running

echo "/opt/vc/lib" > /opt/cec-client/etc/ld.so.conf.d/rpi.conf

You can now compile libcec and cec-utils:

chroot /opt/cec-client
ldconfig
apt-get update
apt-get install cmake libudev-dev libxrandr-dev python3-dev swig git 
cd /tmp/ 
git clone https://github.com/Pulse-Eight/platform.git
mkdir platform/build
cd platform/build
cmake .. 
make 
make install
cd /tmp/
git clone https://github.com/Pulse-Eight/libcec.git
mkdir libcec/build
cd libcec/build
cmake -DRPI_INCLUDE_DIR=/opt/vc/include -DRPI_LIB_DIR=/opt/vc/lib ..
make -j4 
make install
ldconfig
cd /tmp/
mkdir libcec/src/cec-client/build/
cd libcec/src/cec-client/build/
make
make install

In order to run cec-client inside the chroot, you will also need access to devices. You can do this by mounting this up before running chroot, but an easier and better way is to use systemd. Many of the examples you will find of how to use cec-client adds the -s flag to run a single command, but without this, cec-client runs until told to quit and listens on commands on stdin. We will use this and let systemd set up a fifo for commands for us. Create the socket and service files:

[Unit]
Description=CEC client socket

[Socket]
ListenFIFO=/run/cec.fifo

[Install]
WantedBy=sockets.target
[Unit]
Description=CEC client
After=network.target

[Service]
Type=simple
Restart=no
RootDirectory=/opt/cec-client
ExecStart=/usr/local/bin/cec-client -d 1
ExecStop=/bin/bash -c "echo q > /run/cec.fifo"
StandardInput=socket
StandardOutput=journal
MountAPIVFS=yes

[Install]
WantedBy=multi-user.target

Then reload systemd, enable and start:

systemctl daemon-reload
systemctl enable cec-client.socket
systemctl start cec-client.socket

You can now send cec-client commands by writing to /run/cec.fifo, for instance to turn on the tv with address 0.0.0.0 run

echo 'on 0.0.0.0' > /run/cec.fifo

Building a Debian GNU/Linux IPv6 home router

Introduction

This short post describes how i configured my own IPv6 home router using Debian GNU/Linux. I used a Dreamplug, but any form of device with at least 2 NICs should be usable. Allthough this guide describes the setup using Debian, it should be no problem using another distribution or one of the BSD variants.

I will not go into to many details about the configurations, for more information you should read the man pages and/or documentation.

Sources

This blog post from Phil Dibowitz was very helpful setting me on the right track and gives you more details about what is going on. Also I recommend reading Recommended Simple Security Capabilities in Customer Premises Equipment for Providing Residential IPv6 Internet Service.

DNS

At the minimum you want to have a recursive server for your clients. I also configured a zone and allowing dynamic updates so that dhcpd can add clients and also adding the routers own IP-addresses.

Installing bind

aptitude install bind9

Creating a key for dynamic updates

ddns-confgen -a hmac-md5 -r test -r /dev/urandom -q -k dhcp_updater > /etc/bind/dns-dhcp.key
chown root:bind /etc/bind/dns-dhcp.key
chown 640 /etc/bind/dns-dhcp.key

/etc/bind/named.conf.options

options {
        directory "/var/cache/bind";

        forwarders { };

        //========================================================================
        // If BIND logs error messages about the root key being expired,
        // you will need to update your keys.  See https://www.isc.org/bind-keys
        //========================================================================
        dnssec-validation yes;

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

        max-cache-size 44040192;
};

The empty forwarders statement will be filled in later by a post-up script. Set max-cache-size to a suitable size or leave it out to use the defaults.

Setting up zones

I’m using bifrost.haavard.name as my DNS zone for the network and 192.168.0.0/24 for the internal IPv4 network. My router is called heimdal. Change accordingly for your needs or skip this if you don’t want an internal DNS zone.

mkdir /etc/bind/zones
chown root:bind /etc/bind/zones
chmod 2770 /etc/bind/zones
cat<<"EOD" > /etc/bind/zones/db.bifrost.haavard.name
$ORIGIN .
$TTL 3600       ; 1 hour
bifrost.haavard.name    IN SOA  heimdal.bifrost.haavard.name. post.haavard.name. (
                                1025       ; serial
                                604800     ; refresh (1 week)
                                86400      ; retry (1 day)
                                2419200    ; expire (4 weeks)
                                86400      ; minimum (1 day)
                                )
                        NS      heimdal.bifrost.haavard.name.
heimdal                 A       192.168.0.1
EOD
cat<<"EOD" > /etc/bind/zones/db.192.168.0.0
$ORIGIN .
$TTL 3600       ; 1 hour
0.168.192.in-addr.arpa  IN SOA  heimdal.bifrost.haavard.name. post.haavard.name. (
                                6          ; serial
                                604800     ; refresh (1 week)
                                86400      ; retry (1 day)
                                2419200    ; expire (4 weeks)
                                86400      ; minimum (1 day)
                                )
                        NS      heimdal.bifrost.haavard.name.
EOD
chown bind:bind /etc/bind/zones/*

/etc/bind/named.conf.local

include "/etc/bind/zones.rfc1918";

include "/etc/bind/bind.keys";

include "/etc/bind/dns-dhcp.key";

zone "bifrost.haavard.name" {
  type master;
  file "/etc/bind/zones/db.bifrost.haavard.name";
  allow-update { key dhcp_updater; };
};

zone "0.168.192.in-addr.arpa" {
  type master;
  file "/etc/bind/zones/db.192.168.0";
  allow-update { key dhcp_updater; };
};

/usr/local/bin/update-bind-forwarders

This script is used in the post up section of ifup to update the forwarders for bind.

#!/bin/bash

forwarders=$(egrep -v '127.0.0.1|::1' /etc/resolv.conf | awk '/^nameserver/ {print $2}' | tr '\n' ';')
sed -ri "s/forwarders[[:space:]]+\{[^}]+\};/forwarders { $forwarders };/" /etc/bind/named.conf.options
/etc/init.d/bind9 reload

DHCP server for IPv4

Installing dhcp server

aptitude install isc-dhcp-server

/etc/dhcpd.conf

ddns-update-style interim;
ddns-domainname "bifrost.haavard.name";

include "/etc/bind/dns-dhcp.key";

zone bifrost.haavard.name. {
   primary 127.0.0.1;
   key dhcp_updater;
}

zone 0.168.192.in-addr.arpa. {
   primary 127.0.0.1;
   key dhcp_updater;
}

option domain-name "bifrost.haavard.name haavard.name";
option domain-name-servers 192.168.0.1;

default-lease-time 86400;
max-lease-time 720000;

authoritative;

log-facility local7;

subnet 192.168.0.0 netmask 255.255.255.0 {
  range 192.168.0.50 192.168.0.198;
  option routers 192.168.0.1;
}

If you want to give some hosts static addresses, for instance for port forwarding, add to the config file

host XX {
  hardware ethernet XX:XX:XX:XX:XX:XX;
  fixed-address 192.168.0.X;
}

Router config

We’re going to run a post-script in dhcp which will configure addresses on the network interfaces and update DNS. To avoid hard coding this script, I use a config file which I put in /etc/router.conf. If you do not want to use a local DNS zone, skip the dns_* configuration parameters. NB! The dns_search parameter is required if you want to use RDNSS.

/etc/router.conf

# the internet facing interface
external_nic = eth0
# space seperated list of internal nics
internal_nics = eth1

ipv4_prefix = 192.168.0.0/24

dns_update_server = ::1
dns_update_key = /etc/bind/dns-dhcp.key
dns_ttl = 3600
dns_zone = bifrost.haavard.name
dns_external_name = heimdal-ext
dns_eth1_name = heimdal
dns_search = bifrost.haavard.name haavard.name

radvd

I have choosen to use stateless autoconfig for IPv6. This is handled by the radvd daemon.

Installing radvd

aptitude install radvd

/etc/radvd.conf.tmpl

This is the template used by the dhcp post script to configure radvd. Modify as needed. If you do not want to use RDNSS remove the RDNSS and DNSSL lines.

interface __IFACE__ {
   AdvSendAdvert on;
   prefix __PREFIX__
   { 
     AdvOnLink on;
     AdvAutonomous on;
     AdvRouterAddr on;
   };

   RDNSS __IP__ { };
   DNSSL __SEARCH__ {};
};

Multicast routing daemon

You probably want to run a multicast routing daemon, I’ve choosen to use mrd6.

Installing mrd6

aptitude install mrd6

DHCP post script

This script will assign IP-addresses to the internal nics from the assigned address range, configure radvd as well as populate dns

The scripts in /etc/dhcp/dhclient-enter-hooks.d are not executables, but shell scripts being sourced by dhclient-scripts. That is why we need to create the actual script which is written in perl somewhere else and just call it from here.

Required packages

aptitude install libconfig-file-perl libnet-ip-perl

/etc/dhcp/dhclient-enter-hooks.d/router

/usr/local/bin/dhclient-router 

/usr/local/bin/dhclient-router

#!/usr/bin/perl

use strict;
use Config::File;
use File::Temp;
use Net::IP;

my $config = Config::File::read_config_file('/etc/router.conf');

exit 0 unless $ENV{'interface'} = $config->{'external_nic'};
exit 0 unless $ENV{'reason'} =~ /^(BOUND|REBIND|RENEW)6?$/;

if(exists $ENV{'new_ip_address'} or exists $ENV{'new_ip6_address'}) {
  my $recordtype;
  my $ip;
  if(exists $ENV{'new_ip_address'}) {
    $recordtype = 'A';
    $ip = $ENV{'new_ip_address'};
  } else {
    $recordtype = 'AAAA';
    $ip = $ENV{'new_ip6_address'};
  };
  if(exists $config->{'dns_update_server'}
      and exists $config->{'dns_update_key'}
      and exists $config->{'dns_ttl'}
      and exists $config->{'dns_zone'}
      and exists $config->{'dns_external_name'}) {
    open(my $nsupdate, "| nsupdate -k $config->{'dns_update_key'}");
    print $nsupdate "server $config->{'dns_update_server'}\n";
    print $nsupdate "zone $config->{'dns_zone'}\n";
    print $nsupdate "update delete $config->{'dns_external_name'}.$config->{'dns_zone'}. IN $recordtype\n";
    print $nsupdate "update add $config->{'dns_external_name'}.$config->{'dns_zone'}. $config->{'dns_ttl'} IN $recordtype $ip\n";
    print $nsupdate "send\n";
    print $nsupdate "quit\n";
    close($nsupdate);
  };
};

exit 0 unless exists $ENV{'new_ip6_prefix'};

my $tempconf = File::Temp->new;
$tempconf->unlink_on_destroy(0);
open(my $template, "/etc/radvd.conf.tmpl") or die "Unable to open /etc/radvd.conf.tmpl: $!";
my @template = <$template>;
close($template);

my @internal_nics = split(/\s+/, $config->{'internal_nics'});
my $num_internal_nets = scalar(@internal_nics);

my $prefix = Net::IP->new($ENV{'new_ip6_prefix'});
my @octets = split(/:/, $prefix->ip);
my $start_octet = hex($octets[3]);
my $last_octet = hex((split(/:/, $prefix->last_ip))[3]);

die "Not enough addresses for all internal nics" unless(($last_octet - $start_octet) >= $num_internal_nets);

if(exists $config->{'dns_update_server'}
    and exists $config->{'dns_update_key'}
    and exists $config->{'dns_ttl'}
    and exists $config->{'dns_zone'}) {
  open(my $nsupdate, "| nsupdate -k $config->{'dns_update_key'}");
  print $nsupdate "server $config->{'dns_update_server'}\n";
  print $nsupdate "zone $config->{'dns_zone'}\n";
  print $nsupdate "update delete $config->{'dns_zone'}. IN APL\n";
  my $apl_entry;
  if(exists $config->{'ipv4_prefix'}) {
    $apl_entry = "1:$config->{'ipv4_prefix'} 2:$ENV{'new_ip6_prefix'}";
  } else {
    $apl_entry = "2:$ENV{'new_ip6_prefix'}";
  };
  print $nsupdate "update add $config->{'dns_zone'}. $config->{'dns_ttl'} IN APL $apl_entry\n";
  print $nsupdate "send\n";
  print $nsupdate "quit\n";
  close($nsupdate);
};

my $i = 0;
foreach my $internal_nic (@internal_nics) {
  my $new_ip = Net::IP->new(sprintf("%s:%s:%s:%x::1", $octets[0], $octets[1], $octets[2], $start_octet + $i));
  my $new_prefix = sprintf("%s:%s:%s:%x::/64", $octets[0], $octets[1], $octets[2], $start_octet + $i++);
  my $current_ip = undef;
  open(my $addr, "ip -6 addr show dev $internal_nic scope global |");
  while(<$addr>) {
    if(/inet6 ([0-9a-f:]+)\/64 scope global\s*$/) {
      $current_ip = Net::IP->new($1);
    };
  };

  my $ip = $new_ip->ip;
  my $search = $config->{'dns_search'};
  foreach my $line (@template) {
    my $l = $line;
    $l =~ s/__IFACE__/$internal_nic/;
    $l =~ s/__PREFIX__/$new_prefix/;
    $l =~ s/__IP__/$ip/;
    $l =~ s/__SEARCH__/$search/;
    print $tempconf $l;
  };

  if(defined $current_ip) {
    next if $new_ip->overlaps($current_ip) == $IP_IDENTICAL; # address already set
    my $ip = $current_ip->ip;
    `ip -6 addr del $ip/64 dev $internal_nic`;
  };
  my $ip = $new_ip->ip;
  `ip -6 addr add $ip/64 dev $internal_nic`;

  if(exists $config->{'dns_update_server'}
      and exists $config->{'dns_update_key'}
      and exists $config->{'dns_zone'}
      and exists $config->{'dns_ttl'}
      and exists $config->{'dns_' . $internal_nic . '_name'}) {
    open(my $nsupdate, "| nsupdate -k $config->{'dns_update_key'}");
    print $nsupdate "server $config->{'dns_update_server'}\n";
    print $nsupdate "zone $config->{'dns_zone'}\n";
    my $entry = $config->{'dns_' . $internal_nic . '_name'} . '.' . $config->{'dns_zone'} . '.';
    print $nsupdate "update delete $entry IN AAAA\n";
    print $nsupdate "update add $entry $config->{'dns_ttl'} IN AAAA $ip\n";
    print $nsupdate "send\n";
    print $nsupdate "quit\n";
    close($nsupdate);
  };
};

my $tempconf_filename = $tempconf->filename;
close($tempconf);
unless(system("diff /etc/radvd.conf $tempconf_filename >/dev/null") == 0) {
  rename($tempconf_filename, '/etc/radvd.conf');
  chmod 0644, $tempconf_filename;
  `/etc/init.d/radvd restart >/dev/null` ;
} else {
  unlink($tempconf_filename);
};

Firewall

Especially for IPv6 there are som important firewall rules as discussed in Recommended Simple Security Capabilities in Customer Premises Equipment for Providing Residential IPv6 Internet Service. and for IPv4 you have to setup NAT. The rules here should address these issues. I have added some commented out examples for port forwarding and openings for specific ports, you will have to tailor the firewall configuration to suit your own needs. The IPv4 configuration is for running the 192.168.0.0/24 network on eth1. The iptables rules are for one external nic on eth0 and internal on eth1, you have to modify to fit your topology.

/etc/iptables

*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [2:120]
# Port forward port 22 to 192.168.0.199. Also remember to rule in the FORWARD table
#-A PREROUTING -i eth0 -p tcp -m tcp --dport 22 -j DNAT --to-destination 192.168.0.199
-A POSTROUTING -o eth0 -j MASQUERADE
COMMIT
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
:bad_tcp_packets - [0:0]
-A bad_tcp_packets -p tcp --tcp-flags SYN,ACK SYN,ACK -m conntrack --ctstate NEW -j REJECT --reject-with tcp-reset
-A bad_tcp_packets -p tcp ! --syn -m conntrack --ctstate NEW -j DROP

-A INPUT -p tcp -j bad_tcp_packets
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# allow traffic from internal nic
-A INPUT -s 192.168.0.0/24 -i eth1 -j ACCEPT
# allow dhcp on external nic
-A INPUT -i eth0 -p udp -m udp --sport 67 --dport 68 -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p icmp -j ACCEPT
# allow DNS and NTP
-A INPUT -p udp -m udp --sport 53 -j ACCEPT
-A INPUT -p udp -m udp --sport 123 -j ACCEPT
-A INPUT -m limit --limit 11/minute -j LOG --log-level info --log-prefix "IPv4-INPUT "
-A INPUT -j REJECT

-A FORWARD -p tcp -j bad_tcp_packets
# allow forwarding for internal nic
-A FORWARD -i eth1 -j ACCEPT
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# Port forward port 22 to 192.168.0.199
#-A FORWARD -d 192.168.0.199/32 -p tcp --dport 22 -j ACCEPT
-A FORWARD -m limit --limit 11/minute -j LOG --log-level info --log-prefix "IPv4-FORWARD "
-A FORWARD -j REJECT --reject-with icmp-host-unreachable

-A OUTPUT -p tcp -j bad_tcp_packets
COMMIT

/etc/ip6tables

*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [27:2428]
:bad_tcp_packets - [0:0]
-A bad_tcp_packets -p tcp --tcp-flags SYN,ACK SYN,ACK -m conntrack --ctstate NEW -j REJECT --reject-with tcp-reset
#-A bad_tcp_packets -p tcp ! --syn -m conntrack --ctstate NEW -j LOG --log-prefix "New not syn:"
-A bad_tcp_packets -p tcp ! --syn -m conntrack --ctstate NEW -j DROP

-A INPUT -p tcp -j bad_tcp_packets
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# allow dhcp
-A INPUT -i eth0 -p udp -m udp --dport dhcpv6-client -j ACCEPT
# allow icmp, essential for IPv6!
-A INPUT -p icmpv6 -j ACCEPT
# allow traffic from internal nic
-A INPUT -i eth1 -j ACCEPT
-A INPUT -i lo -j ACCEPT
# allow link local multicast
-A INPUT --dest ff02::/16 -j ACCEPT
# allow DNS and NTP
-A INPUT -p udp -m udp --sport 53 -j ACCEPT
-A INPUT -p udp -m udp --sport 123 -j ACCEPT
-A INPUT -m limit --limit 11/minute -j LOG --log-level info --log-prefix "IPv6-INPUT "
-A INPUT -j REJECT

-A FORWARD -p tcp -j bad_tcp_packets
# multicast, allow global multicast prefix, reject all else
-A FORWARD --source ff00::/8 -j REJECT --reject-with icmp6-adm-prohibited
-A FORWARD --dest ff0e::/16 -j ACCEPT
-A FORWARD --dest ff1e::/16 -j ACCEPT
-A FORWARD --dest ff2e::/16 -j ACCEPT
-A FORWARD --dest ff3e::/16 -j ACCEPT
-A FORWARD --dest ff4e::/16 -j ACCEPT
-A FORWARD --dest ff5e::/16 -j ACCEPT
-A FORWARD --dest ff6e::/16 -j ACCEPT
-A FORWARD --dest ff7e::/16 -j ACCEPT
-A FORWARD --dest ff00::/8 -j REJECT --reject-with icmp6-no-route
# link-local
-A FORWARD --dest fe80::/10 -j DROP
-A FORWARD --source fe80::/10 -j DROP
-A FORWARD --dest 3fa::/10 -j REJECT --reject-with icmp6-no-route
-A FORWARD --source 3fa::/10 -j REJECT --reject-with icmp6-no-route
# site local
-A FORWARD --dest 3fb::/10 -j REJECT --reject-with icmp6-no-route
-A FORWARD --source 3fb::/10 -j REJECT --reject-with icmp6-no-route
# ipv4-mapped
-A FORWARD --dest ::FFFF:0:0/96 -j REJECT --reject-with icmp6-no-route
-A FORWARD --source ::FFFF:0:0/96 -j REJECT --reject-with icmp6-no-route
# documentation prefix
-A FORWARD --dest 2001:db8::/32 -j REJECT --reject-with icmp6-no-route
-A FORWARD --source 2001:db8::/32 -j REJECT --reject-with icmp6-no-route
# orchid
-A FORWARD --dest 2001:10::/28 -j REJECT --reject-with icmp6-no-route
-A FORWARD --source 2001:10::/28 -j REJECT --reject-with icmp6-no-route

# routing header 0, deprecated
-A FORWARD -m rt --rt-type 0  -j REJECT --reject-with icmp6-adm-prohibited

# allow forwarding for internal nic
-A FORWARD -i eth1 -j ACCEPT
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# icmp
-A FORWARD -p icmpv6 -j ACCEPT

# ipsec
-A FORWARD -p udp --dport 500 -j ACCEPT
-A FORWARD -m ipv6header --soft --header esp -j ACCEPT
-A FORWARD -m ipv6header --soft --header auth -j ACCEPT

# Example if you want to allow ssh to your internal machines
#-A FORWARD -p tcp --dport 22 -j ACCEPT

-A FORWARD -m limit --limit 11/minute -j LOG --log-level info --log-prefix "IPv6-FORWARD "
-A FORWARD -j REJECT --reject-with icmp6-addr-unreachable

-A OUTPUT -p tcp -j bad_tcp_packets
# link-local
-A OUTPUT --dest 3fa::/10 -j REJECT --reject-with icmp6-no-route
-A OUTPUT --source 3fa::/10 -j REJECT --reject-with icmp6-no-route
# site local
-A OUTPUT --dest 3fb::/10 -j REJECT --reject-with icmp6-no-route
-A OUTPUT --source 3fb::/10 -j REJECT --reject-with icmp6-no-route
# ipv4-mapped
-A OUTPUT --dest ::FFFF:0:0/96 -j REJECT --reject-with icmp6-no-route
-A OUTPUT --source ::FFFF:0:0/96 -j REJECT --reject-with icmp6-no-route
# documentation prefix
-A OUTPUT --dest 2001:db8::/32 -j REJECT --reject-with icmp6-no-route
-A OUTPUT --source 2001:db8::/32 -j REJECT --reject-with icmp6-no-route
# orchid
-A OUTPUT --dest 2001:10::/28 -j REJECT --reject-with icmp6-no-route
-A OUTPUT --source 2001:10::/28 -j REJECT --reject-with icmp6-no-route

COMMIT

/etc/network/if-pre-up.d/iptables

#!/bin/bash

/sbin/iptables-restore < /etc/iptables
/sbin/ip6tables-restore < /etc/ip6tables

sysctl config

/etc/sysctl.conf

    net.ipv4.ip_forward=1

    net.ipv6.conf.all.forwarding=1

    net.ipv4.tcp_ecn = 1

Network config

We have to manually run dhclient using post-up as there is per now no built in ifup method in Debian to run dhclient with -P which is needed for prefix delegation.

/etc/network/interfaces

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet dhcp
  pre-up /etc/init.d/bind9 start
iface eth0 inet6 auto
  pre-up /etc/init.d/bind9 start
  post-up /sbin/sysctl net.ipv6.conf.eth0.accept_ra=2
  post-up /sbin/dhclient -1 -6 -P -N -pf /run/dhclient.eth0-ipv6.pid -lf /var/lib/dhcp/dhclient.eth0-ipv6.leases eth0
  post-up /usr/local/bin/update-bind-forwarders
  pre-down /sbin/dhclient -v -r -pf /run/dhclient.eth0-ipv6.pid -lf /var/lib/dhcp/dhclient.eth0-ipv6.leases eth0

auto eth1
iface eth1 inet static
  address 192.168.0.1
  netmask 255.255.255.0
  pre-up /etc/init.d/bind9 start
iface eth1 inet6 manual

Using CoDel queue algorithm

To avoid bufferbloat you can use the CoDel active queue management algorithm. This requires Linux kernel 3.5 or higher as well as iproute 20121001-1 or newer.

Installation

Download debloat.sh from https://github.com/dtaht/deBloat and place it in /etc/network/if-up.d/debloat. Remember to install ethtool if it is not already installed.

Connecting to a Checkpoint Endpoint VPN using radius or SecureID with racoon

Introduction

At work we use a Checkpoint Endpoint VPN and although SNX works, it is not ideal and I would much rather have native IPsec. Thus the quest began.

I found this page which helped me get on the right track. I also found Andrew de Quincey’s patch. I then had all the components required to set this up.

Setting it up

Patching ipsec-tools

You need to patch ipsec-tools with the patch from Andrew de Quincey to add support for Checkpoints xauth to ipsec-tools.

Obtaining the internal CA certificate from the Checkpoint VPN

This can be a bit tricky if you have uncooperative network administrators, but you need to get them to run either on the VPN or the mds (depending on the setup)

   fwm exportcert -obj OBJ -cert defaultCert -pem -withroot -file checkpoint-cert.pkcs7

where OBJ is the name of the firewall object. After obtaining the pkcs7 file run

   openssl pkcs7 -in checkpoint-cert.pkcs7 -print_certs

Find the CA certificate in which is normally the one with headers like

   subject=/O=checkpoint.intranet.example.com..p9bkhs
   issuer= /O=checkpoint.intranet.example.com..p9bkhs

and not the one with headers like

   subject=/O=checkpoint.intranet.example.com..p9bkhs/CN=rhl7 VPN Certificate
   issuer= /O=checkpoint.intranet.example.com..p9bkhs

Copy this certificate to a suitable file, for example /etc/racoon/certs/name-of-vpn.pem.

Configuring racoon

Set up /etc/racoon/racoon.conf

path certificate "/etc/racoon/certs";

listen {
  adminsock "/var/run/racoon.sock" "root" "adm" 0660;
}

log info;

remote anonymous {
  exchange_mode main,base;
  nat_traversal on;
  doi ipsec_doi;
  proposal_check obey;

  mode_cfg on;
  script "/etc/racoon/phase1-up.sh" phase1_up;
  script "/etc/racoon/phase1-down.sh" phase1_down;

  ca_type x509 "/etc/racoon/certs/name-of-vpn.pem";

  verify_identifier on;

  my_identifier user_fqdn "USERNAME";

  proposal {
    encryption_algorithm aes;
    hash_algorithm sha256;
    authentication_method hybrid_rsa_client;
    dh_group modp2048;
  }
}

sainfo anonymous {
  encryption_algorithm aes;
  authentication_algorithm hmac_sha1;
  compression_algorithm deflate;
}

where the permissions of adminsock is suitable to allow your normal user to start the vpn, unless you want to do it as root or using sudo. The certificate is the one extracted in the previous step.

For the phase1 scripts, you can use my phase1-up.sh and phase1-down.sh, find some other phase1 scripts or create your own. I have not added support for split tunell in my scripts, so if you need that you need to either modify the scripts or find more suitable scripts.

Unfortunately there is some information you need to get out of your network administrator, namely the settings for encryption_algorithm, hash_algorithm, dh_group and authentication_algorithm.

Starting it up

I reccomend starting up racoon in foreground debug mode to easier find problems

racoon -v -F -f /etc/racoon/racoon.conf

You can then run

/usr/sbin/racoonctl vc -u USERNAME vpn.name.or.ip

and hopefully you should be up and running. To disconnect run

/usr/sbin/racoonctl vd vpn.name.or.ip