Merge pull request #123254 from rnhmjoj/ipsec

libreswan: 3.2 -> 4.4
This commit is contained in:
Michele Guerini Rocco 2021-05-19 13:36:04 +02:00 committed by GitHub
commit 376eabdac3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 330 additions and 103 deletions

View file

@ -100,6 +100,19 @@
Now nginx uses the zlib-ng library by default.
</para>
</listitem>
<listitem>
<para>
<link xlink:href="https://libreswan.org/">Libreswan</link> has been updated
to version 4.4. The package now includes example configurations and manual
pages by default. The NixOS module has been changed to use the upstream
systemd units and write the configuration in the <literal>/etc/ipsec.d/
</literal> directory. In addition, two new options have been added to
specify connection policies
(<xref linkend="opt-services.libreswan.policies"/>)
and disable send/receive redirects
(<xref linkend="opt-services.libreswan.disableRedirects"/>).
</para>
</listitem>
</itemizedlist>
</section>

View file

@ -9,21 +9,22 @@ let
libexec = "${pkgs.libreswan}/libexec/ipsec";
ipsec = "${pkgs.libreswan}/sbin/ipsec";
trim = chars: str: let
nonchars = filter (x : !(elem x.value chars))
(imap0 (i: v: {ind = i; value = v;}) (stringToCharacters str));
in
if length nonchars == 0 then ""
else substring (head nonchars).ind (add 1 (sub (last nonchars).ind (head nonchars).ind)) str;
trim = chars: str:
let
nonchars = filter (x : !(elem x.value chars))
(imap0 (i: v: {ind = i; value = v;}) (stringToCharacters str));
in
if length nonchars == 0 then ""
else substring (head nonchars).ind (add 1 (sub (last nonchars).ind (head nonchars).ind)) str;
indent = str: concatStrings (concatMap (s: [" " (trim [" " "\t"] s) "\n"]) (splitString "\n" str));
configText = indent (toString cfg.configSetup);
connectionText = concatStrings (mapAttrsToList (n: v:
''
conn ${n}
${indent v}
'') cfg.connections);
configFile = pkgs.writeText "ipsec.conf"
configFile = pkgs.writeText "ipsec-nixos.conf"
''
config setup
${configText}
@ -31,6 +32,11 @@ let
${connectionText}
'';
policyFiles = mapAttrs' (name: text:
{ name = "ipsec.d/policies/${name}";
value.source = pkgs.writeText "ipsec-policy-${name}" text;
}) cfg.policies;
in
{
@ -41,41 +47,71 @@ in
services.libreswan = {
enable = mkEnableOption "libreswan ipsec service";
enable = mkEnableOption "Libreswan IPsec service";
configSetup = mkOption {
type = types.lines;
default = ''
protostack=netkey
nat_traversal=yes
virtual_private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12,%v4:25.0.0.0/8,%v4:100.64.0.0/10,%v6:fd00::/8,%v6:fe80::/10
'';
example = ''
secretsfile=/root/ipsec.secrets
protostack=netkey
nat_traversal=yes
virtual_private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12,%v4:25.0.0.0/8,%v4:100.64.0.0/10,%v6:fd00::/8,%v6:fe80::/10
'';
description = "Options to go in the 'config setup' section of the libreswan ipsec configuration";
description = "Options to go in the 'config setup' section of the Libreswan IPsec configuration";
};
connections = mkOption {
type = types.attrsOf types.lines;
default = {};
example = {
myconnection = ''
auto=add
left=%defaultroute
leftid=@user
example = literalExample ''
{ myconnection = '''
auto=add
left=%defaultroute
leftid=@user
right=my.vpn.com
right=my.vpn.com
ikev2=no
ikelifetime=8h
'';
};
description = "A set of connections to define for the libreswan ipsec service";
ikev2=no
ikelifetime=8h
''';
}
'';
description = "A set of connections to define for the Libreswan IPsec service";
};
policies = mkOption {
type = types.attrsOf types.lines;
default = {};
example = literalExample ''
{ private-or-clear = '''
# Attempt opportunistic IPsec for the entire Internet
0.0.0.0/0
::/0
''';
}
'';
description = ''
A set of policies to apply to the IPsec connections.
<note><para>
The policy name must match the one of connection it needs to apply to.
</para></note>
'';
};
disableRedirects = mkOption {
type = types.bool;
default = true;
description = ''
Whether to disable send and accept redirects for all nework interfaces.
See the Libreswan <link xlink:href="https://libreswan.org/wiki/FAQ#Why_is_it_recommended_to_disable_send_redirects_in_.2Fproc.2Fsys.2Fnet_.3F">
FAQ</link> page for why this is recommended.
'';
};
};
};
@ -85,43 +121,38 @@ in
config = mkIf cfg.enable {
# Install package, systemd units, etc.
environment.systemPackages = [ pkgs.libreswan pkgs.iproute2 ];
systemd.packages = [ pkgs.libreswan ];
systemd.tmpfiles.packages = [ pkgs.libreswan ];
# Install configuration files
environment.etc = {
"ipsec.secrets".source = "${pkgs.libreswan}/etc/ipsec.secrets";
"ipsec.conf".source = "${pkgs.libreswan}/etc/ipsec.conf";
"ipsec.d/01-nixos.conf".source = configFile;
} // policyFiles;
# Create NSS database directory
systemd.tmpfiles.rules = [ "d /var/lib/ipsec/nss 755 root root -" ];
systemd.services.ipsec = {
description = "Internet Key Exchange (IKE) Protocol Daemon for IPsec";
path = [
"${pkgs.libreswan}"
"${pkgs.iproute2}"
"${pkgs.procps}"
"${pkgs.nssTools}"
"${pkgs.iptables}"
"${pkgs.nettools}"
];
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
Restart = "always";
EnvironmentFile = "-${pkgs.libreswan}/etc/sysconfig/pluto";
ExecStartPre = [
"${libexec}/addconn --config ${configFile} --checkconfig"
"${libexec}/_stackmanager start"
"${ipsec} --checknss"
"${ipsec} --checknflog"
];
ExecStart = "${libexec}/pluto --config ${configFile} --nofork \$PLUTO_OPTIONS";
ExecStop = "${libexec}/whack --shutdown";
ExecStopPost = [
"${pkgs.iproute2}/bin/ip xfrm policy flush"
"${pkgs.iproute2}/bin/ip xfrm state flush"
"${ipsec} --stopnflog"
];
ExecReload = "${libexec}/whack --listen";
};
restartTriggers = [ configFile ] ++ mapAttrsToList (n: v: v.source) policyFiles;
path = with pkgs; [
libreswan
iproute2
procps
nssTools
iptables
nettools
];
preStart = optionalString cfg.disableRedirects ''
# Disable send/receive redirects
echo 0 | tee /proc/sys/net/ipv4/conf/*/send_redirects
echo 0 | tee /proc/sys/net/ipv{4,6}/conf/*/accept_redirects
'';
};
};

View file

@ -217,6 +217,7 @@ in
latestKernel.login = handleTest ./login.nix { latestKernel = true; };
leaps = handleTest ./leaps.nix {};
lidarr = handleTest ./lidarr.nix {};
libreswan = handleTest ./libreswan.nix {};
lightdm = handleTest ./lightdm.nix {};
limesurvey = handleTest ./limesurvey.nix {};
locate = handleTest ./locate.nix {};

134
nixos/tests/libreswan.nix Normal file
View file

@ -0,0 +1,134 @@
# This test sets up a host-to-host IPsec VPN between Alice and Bob, each on its
# own network and with Eve as the only route between each other. We check that
# Eve can eavesdrop the plaintext traffic between Alice and Bob, but once they
# enable the secure tunnel Eve's spying becomes ineffective.
import ./make-test-python.nix ({ lib, pkgs, ... }:
let
# IPsec tunnel between Alice and Bob
tunnelConfig = {
services.libreswan.enable = true;
services.libreswan.connections.tunnel =
''
leftid=@alice
left=fd::a
rightid=@bob
right=fd::b
authby=secret
auto=add
'';
environment.etc."ipsec.d/tunnel.secrets" =
{ text = ''@alice @bob : PSK "j1JbIi9WY07rxwcNQ6nbyThKCf9DGxWOyokXIQcAQUnafsNTUJxfsxwk9WYK8fHj"'';
mode = "600";
};
};
# Common network setup
baseNetwork = {
# shared hosts file
extraHosts = lib.mkVMOverride ''
fd::a alice
fd::b bob
fd::e eve
'';
# remove all automatic addresses
useDHCP = false;
interfaces.eth1.ipv4.addresses = lib.mkVMOverride [];
interfaces.eth2.ipv4.addresses = lib.mkVMOverride [];
# open a port for testing
firewall.allowedUDPPorts = [ 1234 ];
};
# Adds an address and route from a to b via Eve
addRoute = a: b: {
interfaces.eth1.ipv6.addresses =
[ { address = a; prefixLength = 64; } ];
interfaces.eth1.ipv6.routes =
[ { address = b; prefixLength = 128; via = "fd::e"; } ];
};
in
{
name = "libreswan";
meta = with lib.maintainers; {
maintainers = [ rnhmjoj ];
};
# Our protagonist
nodes.alice = { ... }: {
virtualisation.vlans = [ 1 ];
networking = baseNetwork // addRoute "fd::a" "fd::b";
} // tunnelConfig;
# Her best friend
nodes.bob = { ... }: {
virtualisation.vlans = [ 2 ];
networking = baseNetwork // addRoute "fd::b" "fd::a";
} // tunnelConfig;
# The malicious network operator
nodes.eve = { ... }: {
virtualisation.vlans = [ 1 2 ];
networking = lib.mkMerge
[ baseNetwork
{ interfaces.br0.ipv6.addresses =
[ { address = "fd::e"; prefixLength = 64; } ];
bridges.br0.interfaces = [ "eth1" "eth2" ];
}
];
environment.systemPackages = [ pkgs.tcpdump ];
boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
};
testScript =
''
def alice_to_bob(msg: str):
"""
Sends a message as Alice to Bob
"""
bob.execute("nc -lu ::0 1234 >/tmp/msg &")
alice.sleep(1)
alice.succeed(f"echo '{msg}' | nc -uw 0 bob 1234")
bob.succeed(f"grep '{msg}' /tmp/msg")
def eavesdrop():
"""
Starts eavesdropping on Alice and Bob
"""
match = "src host alice and dst host bob"
eve.execute(f"tcpdump -i br0 -c 1 -Avv {match} >/tmp/log &")
start_all()
with subtest("Network is up"):
alice.wait_until_succeeds("ping -c1 bob")
with subtest("Eve can eavesdrop cleartext traffic"):
eavesdrop()
alice_to_bob("I secretly love turnip")
eve.sleep(1)
eve.succeed("grep turnip /tmp/log")
with subtest("Libreswan is ready"):
alice.wait_for_unit("ipsec")
bob.wait_for_unit("ipsec")
alice.succeed("ipsec verify 1>&2")
with subtest("Alice and Bob can start the tunnel"):
alice.execute("ipsec auto --start tunnel &")
bob.succeed("ipsec auto --start tunnel")
# apparently this is needed to "wake" the tunnel
bob.execute("ping -c1 alice")
with subtest("Eve no longer can eavesdrop"):
eavesdrop()
alice_to_bob("Just kidding, I actually like rhubarb")
eve.sleep(1)
eve.fail("grep rhubarb /tmp/log")
'';
})

View file

@ -1,71 +1,114 @@
{ lib, stdenv, fetchurl, makeWrapper,
pkg-config, systemd, gmp, unbound, bison, flex, pam, libevent, libcap_ng, curl, nspr,
bash, iproute2, iptables, procps, coreutils, gnused, gawk, nss, which, python3,
docs ? false, xmlto, libselinux, ldns
}:
{ lib
, stdenv
, fetchurl
, fetchpatch
, nixosTests
, pkg-config
, systemd
, gmp
, unbound
, bison
, flex
, pam
, libevent
, libcap_ng
, curl
, nspr
, bash
, iproute2
, iptables
, procps
, coreutils
, gnused
, gawk
, nss
, which
, python3
, libselinux
, ldns
, xmlto
, docbook_xml_dtd_412
, docbook_xsl
, findXMLCatalogs
}:
let
# Tools needed by ipsec scripts
binPath = lib.makeBinPath [
bash iproute2 iptables procps coreutils gnused gawk nss.tools which python3
iproute2 iptables procps
coreutils gnused gawk
nss.tools which
];
in
assert docs -> xmlto != null;
assert stdenv.isLinux -> libselinux != null;
stdenv.mkDerivation rec {
pname = "libreswan";
version = "3.32";
version = "4.4";
src = fetchurl {
url = "https://download.libreswan.org/${pname}-${version}.tar.gz";
sha256 = "0bj3g6qwd3ir3gk6hdl9npy3k44shf56vcgjahn30qpmx3z5fsr3";
sha256 = "0xj974yc0y1r7235zl4jhvxqz3bpb8js2fy9ic820zq9swh0lgsz";
};
strictDeps = true;
# These flags were added to compile v3.18. Try to lift them when updating.
NIX_CFLAGS_COMPILE = toString [ "-Wno-error=redundant-decls" "-Wno-error=format-nonliteral"
# these flags were added to build with gcc7
"-Wno-error=implicit-fallthrough"
"-Wno-error=format-truncation"
"-Wno-error=pointer-compare"
"-Wno-error=stringop-truncation"
# The following flag allows libreswan v3.32 to work with NSS 3.22, see
# https://github.com/libreswan/libreswan/issues/334.
# This flag should not be needed for libreswan v3.33 (which is not yet released).
"-DNSS_PKCS11_2_0_COMPAT=1"
];
nativeBuildInputs = [
bison
flex
makeWrapper
pkg-config
xmlto
docbook_xml_dtd_412
docbook_xsl
findXMLCatalogs
];
buildInputs = [ bash iproute2 iptables systemd coreutils gnused gawk gmp unbound pam libevent
libcap_ng curl nspr nss python3 ldns ]
++ lib.optional docs xmlto
++ lib.optional stdenv.isLinux libselinux;
buildInputs = [
systemd coreutils
gnused gawk gmp unbound pam libevent
libcap_ng curl nspr nss ldns
# needed to patch shebangs
python3 bash
] ++ lib.optional stdenv.isLinux libselinux;
patches = [
# Fix compilation on aarch64, remove on next update
(fetchpatch {
url = "https://github.com/libreswan/libreswan/commit/ea50d36d2886e44317ba5ba841de1d1bf91aee6c.patch";
sha256 = "1jp89rm9jp55zmiyimyhg7yadj0fwwxaw7i5gyclrs38w3y1aacj";
})
];
prePatch = ''
# Correct bash path
sed -i -e 's|/bin/bash|/usr/bin/env bash|' mk/config.mk
# Correct iproute2 path
sed -e 's|"/sbin/ip"|"${iproute2}/bin/ip"|' \
-e 's|"/sbin/iptables"|"${iptables}/bin/iptables"|' \
-i initsystems/systemd/ipsec.service.in \
programs/verify/verify.in
# Fix systemd unit directory, and prevent the makefile from trying to reload the
# systemd daemon or create tmpfiles
sed -i -e 's|UNITDIR=.*$|UNITDIR=$\{out}/etc/systemd/system/|g' \
-e 's|TMPFILESDIR=.*$|TMPFILESDIR=$\{out}/tmpfiles.d/|g' \
-e 's|systemctl|true|g' \
-e 's|systemd-tmpfiles|true|g' \
initsystems/systemd/Makefile
# Prevent the makefile from trying to
# reload the systemd daemon or create tmpfiles
sed -e 's|systemctl|true|g' \
-e 's|systemd-tmpfiles|true|g' \
-i initsystems/systemd/Makefile
# Fix the ipsec program from crushing the PATH
sed -i -e 's|\(PATH=".*"\):.*$|\1:$PATH|' programs/ipsec/ipsec.in
sed -e 's|\(PATH=".*"\):.*$|\1:$PATH|' -i programs/ipsec/ipsec.in
# Fix python script to use the correct python
sed -i -e 's|#!/usr/bin/python|#!/usr/bin/env python|' -e 's/^\(\W*\)installstartcheck()/\1sscmd = "ss"\n\0/' programs/verify/verify.in
sed -e 's/^\(\W*\)installstartcheck()/\1sscmd = "ss"\n\0/' \
-i programs/verify/verify.in
# Replace wget with curl to save a dependency
curlArgs='-s --remote-name-all --output-dir'
sed -e "s|wget -q -P|${curl}/bin/curl $curlArgs|g" \
-i programs/letsencrypt/letsencrypt.in
# Patch the Makefile:
# 1. correct the pam.d directory install path
# 2. do not create the /var/lib/ directory
sed -e 's|$(DESTDIR)/etc/pam.d|$(out)/etc/pam.d|' \
-e '/test ! -d $(NSSDIR)/,+3d' \
-i configs/Makefile
'';
# Set appropriate paths for build
@ -73,10 +116,10 @@ stdenv.mkDerivation rec {
makeFlags = [
"INITSYSTEM=systemd"
(if docs then "all" else "base")
"UNITDIR=$(out)/etc/systemd/system/"
"TMPFILESDIR=$(out)/lib/tmpfiles.d/"
];
installTargets = [ (if docs then "install" else "install-base") ];
# Hack to make install work
installFlags = [
"FINALVARDIR=\${out}/var"
@ -84,18 +127,23 @@ stdenv.mkDerivation rec {
];
postInstall = ''
for i in $out/bin/* $out/libexec/ipsec/*; do
wrapProgram "$i" --prefix PATH ':' "$out/bin:${binPath}"
done
# Install examples directory (needed for letsencrypt)
cp -r docs/examples $out/share/doc/libreswan/examples
'';
enableParallelBuilding = true;
postFixup = ''
# Add a PATH to the main "ipsec" script
sed -e '0,/^$/{s||export PATH=${binPath}:$PATH|}' \
-i $out/bin/ipsec
'';
passthru.tests.libreswan = nixosTests.libreswan;
meta = with lib; {
homepage = "https://libreswan.org";
description = "A free software implementation of the VPN protocol based on IPSec and the Internet Key Exchange";
platforms = platforms.linux ++ platforms.freebsd;
license = licenses.gpl2;
maintainers = [ maintainers.afranchuk ];
license = with licenses; [ gpl2Plus mpl20 ] ;
maintainers = with maintainers; [ afranchuk rnhmjoj ];
};
}