nixos: add the strongswan-swanctl service

The strongswan-swanctl systemd service starts charon-systemd. This implements a IKE daemon
very similar to charon, but it's specifically designed for use with systemd. It uses the
systemd libraries for a native integration.

Instead of using starter and an ipsec.conf based configuration, the daemon is directly
managed by systemd and configured with the swanctl configuration backend.

See: https://wiki.strongswan.org/projects/strongswan/wiki/Charon-systemd

Note that the strongswan.conf and swantctl.conf configuration files are automatically
generated based on NixOS options under services.strongswan-swanctl.strongswan and
services.strongswan-swanctl.swanctl respectively.
This commit is contained in:
Bas van Dijk 2017-08-05 14:01:52 +02:00
parent 04dd1987a3
commit bd24b3addd
12 changed files with 3811 additions and 0 deletions

View File

@ -560,6 +560,7 @@
./services/networking/ssh/lshd.nix
./services/networking/ssh/sshd.nix
./services/networking/strongswan.nix
./services/networking/strongswan-swanctl/module.nix
./services/networking/stunnel.nix
./services/networking/supplicant.nix
./services/networking/supybot.nix

View File

@ -0,0 +1,80 @@
{ config, lib, pkgs, ... }:
with lib;
with (import ./param-lib.nix lib);
let
cfg = config.services.strongswan-swanctl;
# TODO: auto-generate these files using:
# https://github.com/strongswan/strongswan/tree/master/conf
# IDEA: extend the format-options.py script to output these Nix files.
strongswanParams = import ./strongswan-params.nix lib;
swanctlParams = import ./swanctl-params.nix lib;
in {
options.services.strongswan-swanctl = {
enable = mkEnableOption "strongswan-swanctl service";
package = mkOption {
type = types.package;
default = pkgs.strongswan;
defaultText = "pkgs.strongswan";
description = ''
The strongswan derivation to use.
'';
};
strongswan = paramsToOptions strongswanParams;
swanctl = paramsToOptions swanctlParams;
};
config = mkIf cfg.enable {
assertions = [
{ assertion = !config.services.strongswan.enable;
message = "cannot enable both services.strongswan and services.strongswan-swanctl. Choose either one.";
}
];
environment.etc."swanctl/swanctl.conf".text =
paramsToConf cfg.swanctl swanctlParams;
# The swanctl command complains when the following directories don't exist:
# See: https://wiki.strongswan.org/projects/strongswan/wiki/Swanctldirectory
system.activationScripts.strongswan-swanctl-etc = stringAfter ["etc"] ''
mkdir -p '/etc/swanctl/x509' # Trusted X.509 end entity certificates
mkdir -p '/etc/swanctl/x509ca' # Trusted X.509 Certificate Authority certificates
mkdir -p '/etc/swanctl/x509ocsp'
mkdir -p '/etc/swanctl/x509aa' # Trusted X.509 Attribute Authority certificates
mkdir -p '/etc/swanctl/x509ac' # Attribute Certificates
mkdir -p '/etc/swanctl/x509crl' # Certificate Revocation Lists
mkdir -p '/etc/swanctl/pubkey' # Raw public keys
mkdir -p '/etc/swanctl/private' # Private keys in any format
mkdir -p '/etc/swanctl/rsa' # PKCS#1 encoded RSA private keys
mkdir -p '/etc/swanctl/ecdsa' # Plain ECDSA private keys
mkdir -p '/etc/swanctl/bliss'
mkdir -p '/etc/swanctl/pkcs8' # PKCS#8 encoded private keys of any type
mkdir -p '/etc/swanctl/pkcs12' # PKCS#12 containers
'';
systemd.services.strongswan-swanctl = {
description = "strongSwan IPsec IKEv1/IKEv2 daemon using swanctl";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" "keys.target" ];
wants = [ "keys.target" ];
path = with pkgs; [ kmod iproute iptables utillinux ];
environment.STRONGSWAN_CONF = pkgs.writeTextFile {
name = "strongswan.conf";
text = paramsToConf cfg.strongswan strongswanParams;
};
restartTriggers = [ config.environment.etc."swanctl/swanctl.conf".source ];
serviceConfig = {
ExecStart = "${cfg.package}/sbin/charon-systemd";
Type = "notify";
ExecStartPost = "${cfg.package}/sbin/swanctl --load-all --noprompt";
ExecReload = "${cfg.package}/sbin/swanctl --reload";
Restart = "on-abnormal";
};
};
};
}

View File

@ -0,0 +1,162 @@
# In the following context a parameter is an attribute set that
# contains a NixOS option and a render function. It also contains the
# attribute: '_type = "param"' so we can distinguish it from other
# sets.
#
# The render function is used to convert the value of the option to a
# snippet of strongswan.conf. Most parameters simply render their
# value to a string. For example, take the following parameter:
#
# threads = mkIntParam 10 "Threads to use for request handling.";
#
# When a users defines the corresponding option as for example:
#
# services.strongswan-swanctl.strongswan.threads = 32;
#
# It will get rendered to the following snippet in strongswan.conf:
#
# threads = 32
#
# Some parameters however need to be able to change the attribute
# name. For example, take the following parameter:
#
# id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") "...";
#
# A user can define the corresponding option as for example:
#
# id = {
# "foo" = "bar";
# "baz" = "qux";
# };
#
# This will get rendered to the following snippet:
#
# foo-id = bar
# baz-id = qux
#
# For this reason the render function is not simply a function from
# value -> string but a function from a value to an attribute set:
# { "${name}" = string }. This allows parameters to change the attribute
# name like in the previous example.
lib :
with lib;
with (import ./param-lib.nix lib);
rec {
mkParamOfType = type : strongswanDefault : description : {
_type = "param";
option = mkOption {
type = types.nullOr type;
default = null;
description = documentDefault description strongswanDefault;
};
render = single toString;
};
documentDefault = description : strongswanDefault :
if isNull strongswanDefault
then description
else description + ''
</para><para>
StrongSwan default: <literal><![CDATA[${builtins.toJSON strongswanDefault}]]></literal>
'';
single = f: name: value: { "${name}" = f value; };
mkStrParam = mkParamOfType types.str;
mkOptionalStrParam = mkStrParam null;
mkEnumParam = values : mkParamOfType (types.enum values);
mkIntParam = mkParamOfType types.int;
mkOptionalIntParam = mkIntParam null;
# We should have floats in Nix...
mkFloatParam = mkStrParam;
# TODO: Check for hex format:
mkHexParam = mkStrParam;
mkOptionalHexParam = mkOptionalStrParam;
# TODO: Check for duration format:
mkDurationParam = mkStrParam;
mkOptionalDurationParam = mkOptionalStrParam;
mkYesNoParam = strongswanDefault : description : {
_type = "param";
option = mkOption {
type = types.nullOr types.bool;
default = null;
description = documentDefault description strongswanDefault;
};
render = single (b: if b then "yes" else "no");
};
yes = true;
no = false;
mkSpaceSepListParam = mkSepListParam " ";
mkCommaSepListParam = mkSepListParam ",";
mkSepListParam = sep : strongswanDefault : description : {
_type = "param";
option = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
description = documentDefault description strongswanDefault;
};
render = single (value: concatStringsSep sep value);
};
mkAttrsOfParams = params :
mkAttrsOf params (types.submodule {options = paramsToOptions params;});
mkAttrsOfParam = param :
mkAttrsOf param param.option.type;
mkAttrsOf = param : option : description : {
_type = "param";
option = mkOption {
type = types.attrsOf option;
default = {};
inherit description;
};
render = single (attrs:
(paramsToRenderedStrings attrs
(mapAttrs (_n: _v: param) attrs)));
};
mkPrefixedAttrsOfParams = params :
mkPrefixedAttrsOf params (types.submodule {options = paramsToOptions params;});
mkPrefixedAttrsOfParam = param :
mkPrefixedAttrsOf param param.option.type;
mkPrefixedAttrsOf = p : option : description : {
_type = "param";
option = mkOption {
type = types.attrsOf option;
default = {};
inherit description;
};
render = prefix: attrs:
let prefixedAttrs = mapAttrs' (name: nameValuePair "${prefix}-${name}") attrs;
in paramsToRenderedStrings prefixedAttrs
(mapAttrs (_n: _v: p) prefixedAttrs);
};
mkPostfixedAttrsOfParams = params : description : {
_type = "param";
option = mkOption {
type = types.attrsOf (types.submodule {options = paramsToOptions params;});
default = {};
inherit description;
};
render = postfix: attrs:
let postfixedAttrs = mapAttrs' (name: nameValuePair "${name}-${postfix}") attrs;
in paramsToRenderedStrings postfixedAttrs
(mapAttrs (_n: _v: params) postfixedAttrs);
};
}

View File

@ -0,0 +1,82 @@
lib :
with lib;
rec {
paramsToConf = cfg : ps : mkConf 0 (paramsToRenderedStrings cfg ps);
# mkConf takes an indentation level (which usually starts at 0) and a nested
# attribute set of strings and will render that set to a strongswan.conf style
# configuration format. For example:
#
# mkConf 0 {a = "1"; b = { c = { "foo" = "2"; "bar" = "3"; }; d = "4";};} => ''
# a = 1
# b {
# c {
# foo = 2
# bar = 3
# }
# d = 4
# }''
mkConf = indent : ps :
concatMapStringsSep "\n"
(name:
let value = ps."${name}";
indentation = replicate indent " ";
in
indentation + (
if isAttrs value
then "${name} {\n" +
mkConf (indent + 2) value + "\n" +
indentation + "}"
else "${name} = ${value}"
)
)
(attrNames ps);
replicate = n : c : concatStrings (builtins.genList (_x : c) n);
# `paramsToRenderedStrings cfg ps` converts the NixOS configuration `cfg`
# (typically the "config" argument of a NixOS module) and the set of
# parameters `ps` (an attribute set where the values are constructed using the
# parameter constructors in ./param-constructors.nix) to a nested attribute
# set of strings (rendered parameters).
paramsToRenderedStrings = cfg : ps :
filterEmptySets (
(mapParamsRecursive (path: name: param:
let value = attrByPath path null cfg;
in optionalAttrs (!isNull value) (param.render name value)
) ps));
filterEmptySets = set : filterAttrs (n: v: !(isNull v)) (mapAttrs (name: value:
if isAttrs value
then let value' = filterEmptySets value;
in if value' == {}
then null
else value'
else value
) set);
# Recursively map over every parameter in the given attribute set.
mapParamsRecursive = mapAttrsRecursiveCond' (as: (!(as ? "_type" && as._type == "param")));
mapAttrsRecursiveCond' = cond: f: set:
let
recurse = path: set:
let
g =
name: value:
if isAttrs value && cond value
then { "${name}" = recurse (path ++ [name]) value; }
else f (path ++ [name]) name value;
in mapAttrs'' g set;
in recurse [] set;
mapAttrs'' = f: set:
foldl' (a: b: a // b) {} (map (attr: f attr set.${attr}) (attrNames set));
# Extract the options from the given set of parameters.
paramsToOptions = ps :
mapParamsRecursive (_path: name: param: { "${name}" = param.option; }) ps;
}

View File

@ -0,0 +1,568 @@
lib: with (import ./param-constructors.nix lib);
let loglevelParams = import ./strongswan-loglevel-params.nix lib;
in {
accept_unencrypted_mainmode_messages = mkYesNoParam no ''
Accept unencrypted ID and HASH payloads in IKEv1 Main Mode. Some
implementations send the third Main Mode message unencrypted, probably
to find the PSKs for the specified ID for authentication. This is very
similar to Aggressive Mode, and has the same security implications: A
passive attacker can sniff the negotiated Identity, and start brute
forcing the PSK using the HASH payload. It is recommended to keep this
option to no, unless you know exactly what the implications are and
require compatibility to such devices (for example, some SonicWall
boxes).
'';
block_threshold = mkIntParam 5 ''
Maximum number of half-open IKE_SAs for a single peer IP.
'';
cache_crls = mkYesNoParam no ''
Whether Certicate Revocation Lists (CRLs) fetched via HTTP or LDAP
should be saved under a unique file name derived from the public
key of the Certification Authority (CA) to
<literal>/etc/ipsec.d/crls</literal> (stroke) or
<literal>/etc/swanctl/x509crl</literal> (vici), respectively.
'';
cert_cache = mkYesNoParam yes ''
Whether relations in validated certificate chains should be cached in memory.
'';
cisco_unity = mkYesNoParam no ''
Send Cisco Unity vendor ID payload (IKEv1 only), see unity plugin.
'';
close_ike_on_child_failure = mkYesNoParam no ''
Close the IKE_SA if setup of the CHILD_SA along with IKE_AUTH failed.
'';
cookie_threshold = mkIntParam 10 ''
Number of half-open IKE_SAs that activate the cookie mechanism.
'';
crypto_test.bench = mkYesNoParam no ''
Benchmark crypto algorithms and order them by efficiency.
'';
crypto_test.bench_size = mkIntParam 1024 ''
Buffer size used for crypto benchmark.
'';
crypto_test.bench_time = mkIntParam 50 ''
Number of iterations to test each algorithm.
'';
crypto_test.on_add = mkYesNoParam no ''
Test crypto algorithms during registration
(requires test vectors provided by the test-vectors plugin).
'';
crypto_test.on_create = mkYesNoParam no ''
Test crypto algorithms on each crypto primitive instantiation.
'';
crypto_test.required = mkYesNoParam no ''
Strictly require at least one test vector to enable an algorithm.
'';
crypto_test.rng_true = mkYesNoParam no ''
Whether to test RNG with TRUE quality; requires a lot of entropy.
'';
delete_rekeyed = mkYesNoParam no ''
Delete CHILD_SAs right after they got successfully rekeyed (IKEv1 only).
Reduces the number of stale CHILD_SAs in scenarios with a lot of rekeyings.
However, this might cause problems with implementations that continue
to use rekeyed SAs until they expire.
'';
delete_rekeyed_delay = mkIntParam 5 ''
Delay in seconds until inbound IPsec SAs are deleted after rekeyings
(IKEv2 only).
</para><para>
To process delayed packets the inbound part of a CHILD_SA is kept
installed up to the configured number of seconds after it got replaced
during a rekeying. If set to 0 the CHILD_SA will be kept installed until
it expires (if no lifetime is set it will be destroyed immediately).
'';
dh_exponent_ansi_x9_42 = mkYesNoParam yes ''
Use ANSI X9.42 DH exponent size or optimum size matched to
cryptographical strength.
'';
dlopen_use_rtld_now = mkYesNoParam no ''
Use RTLD_NOW with dlopen() when loading plugins and IMV/IMCs to reveal
missing symbols immediately. Useful during development of custom plugins.
'';
dns1 = mkOptionalStrParam ''
DNS server assigned to peer via configuration payload (CP), see attr plugin.
'';
dns2 = mkOptionalStrParam ''
DNS server assigned to peer via configuration payload (CP).
'';
dos_protection = mkYesNoParam yes ''
Enable Denial of Service protection using cookies and aggressiveness checks.
'';
ecp_x_coordinate_only = mkYesNoParam yes ''
Compliance with the errata for RFC 4753.
'';
filelog = mkAttrsOfParams ({
append = mkYesNoParam yes ''
If this option is enabled log entries are appended to the existing file.
'';
flush_line = mkYesNoParam no ''
Enabling this option disables block buffering and enables line
buffering. That is, a flush to disk is enforced for each logged line.
'';
ike_name = mkYesNoParam no ''
Prefix each log entry with the connection name and a unique numerical
identifier for each IKE_SA.
'';
time_format = mkOptionalStrParam ''
Prefix each log entry with a timestamp. The option accepts a format string
as passed to strftime(3).
'';
time_add_ms = mkYesNoParam no ''
Adds the milliseconds within the current second after the timestamp
(separated by a dot, so time_format should end with %S or %T)
'';
} // loglevelParams) ''Section to define file loggers, see LoggerConfiguration.'';
flush_auth_cfg = mkYesNoParam no ''
If enabled objects used during authentication (certificates, identities
etc.) are released to free memory once an IKE_SA is
established. Enabling this might conflict with plugins that later need
access to e.g. the used certificates.
'';
follow_redirects = mkYesNoParam yes ''
Whether to follow IKEv2 redirects (RFC 5685).
'';
fragment_size = mkIntParam 1280 ''
Maximum size (complete IP datagram size in bytes) of a sent IKE fragment
when using proprietary IKEv1 or standardized IKEv2 fragmentation,
defaults to 1280 (use 0 for address family specific default values,
which uses a lower value for IPv4). If specified this limit is used for
both IPv4 and IPv6.
'';
group = mkOptionalStrParam ''
Name of the group the daemon changes to after startup.
'';
half_open_timeout = mkIntParam 30 ''
Timeout in seconds for connecting IKE_SAs, also see IKE_SA_INIT dropping.
'';
hash_and_url = mkYesNoParam no ''
Enable hash and URL support.
'';
host_resolver.max_threads = mkIntParam 3 ''
Maximum number of concurrent resolver threads (they are terminated if unused).
'';
host_resolver.min_threads = mkIntParam 0 ''
Minimum number of resolver threads to keep around.
'';
i_dont_care_about_security_and_use_aggressive_mode_psk = mkYesNoParam no ''
If enabled responders are allowed to use IKEv1 Aggressive Mode with
pre-shared keys, which is discouraged due to security concerns (offline
attacks on the openly transmitted hash of the PSK).
'';
ignore_acquire_ts = mkYesNoParam no ''
If this is disabled the traffic selectors from the kernel's acquire
events, which are derived from the triggering packet, are prepended to
the traffic selectors from the configuration for IKEv2 connection. By
enabling this, such specific traffic selectors will be ignored and only
the ones in the config will be sent. This always happens for IKEv1
connections as the protocol only supports one set of traffic selectors
per CHILD_SA.
'';
ignore_routing_tables = mkSpaceSepListParam [] ''
A space-separated list of routing tables to be excluded from route lookup.
'';
ikesa_limit = mkIntParam 0 ''
Maximum number of IKE_SAs that can be established at the same time
before new connection attempts are blocked.
'';
ikesa_table_segments = mkIntParam 1 ''
Number of exclusively locked segments in the hash table, see IKE_SA
lookup tuning.
'';
ikesa_table_size = mkIntParam 1 ''
Size of the IKE_SA hash table, see IKE_SA lookup tuning.
'';
inactivity_close_ike = mkYesNoParam no ''
Whether to close IKE_SA if the only CHILD_SA closed due to inactivity.
'';
init_limit_half_open = mkIntParam 0 ''
Limit new connections based on the current number of half open IKE_SAs,
see IKE_SA_INIT dropping.
'';
init_limit_job_load = mkIntParam 0 ''
Limit new connections based on the number of jobs currently queued for
processing, see IKE_SA_INIT dropping.
'';
initiator_only = mkYesNoParam no ''
Causes charon daemon to ignore IKE initiation requests.
'';
install_routes = mkYesNoParam yes ''
Install routes into a separate routing table for established IPsec
tunnels. If disabled a more efficient lookup for source and next-hop
addresses is used since 5.5.2.
'';
install_virtual_ip = mkYesNoParam yes ''
Install virtual IP addresses.
'';
install_virtual_ip_on = mkOptionalStrParam ''
The name of the interface on which virtual IP addresses should be
installed. If not specified the addresses will be installed on the
outbound interface.
'';
integrity_test = mkYesNoParam no ''
Check daemon, libstrongswan and plugin integrity at startup.
'';
interfaces_ignore = mkCommaSepListParam [] ''
List of network interfaces that should be ignored, if
<option>interfaces_use</option> is specified this option has no effect.
'';
interfaces_use = mkCommaSepListParam [] ''
List of network interfaces that should be used by
charon. All other interfaces are ignored.
'';
keep_alive = mkIntParam 20 ''
NAT keep alive interval in seconds.
'';
leak_detective.detailed = mkYesNoParam yes ''
Includes source file names and line numbers in leak detective output.
'';
leak_detective.usage_threshold = mkIntParam 10240 ''
Threshold in bytes for leaks to be reported (0 to report all).
'';
leak_detective.usage_threshold_count = mkIntParam 0 ''
Threshold in number of allocations for leaks to be reported (0 to report
all).
'';
load = mkSpaceSepListParam [] ''
Plugins to load in IKEv2 charon daemon, see PluginLoad.
'';
load_modular = mkYesNoParam no ''
If enabled the list of plugins to load is determined by individual load
settings for each plugin, see PluginLoad.
'';
make_before_break = mkYesNoParam no ''
Initiate IKEv2 reauthentication with a make-before-break instead of a
break-before-make scheme. Make-before-break uses overlapping IKE and
CHILD_SA during reauthentication by first recreating all new SAs before
deleting the old ones. This behavior can be beneficial to avoid
connectivity gaps during reauthentication, but requires support for
overlapping SAs by the peer. strongSwan can handle such overlapping SAs
since 5.3.0.
'';
max_ikev1_exchanges = mkIntParam 3 ''
Maximum number of IKEv1 phase 2 exchanges per IKE_SA to keep state about
and track concurrently.
'';
max_packet = mkIntParam 10000 ''
Maximum packet size accepted by charon.
'';
multiple_authentication = mkYesNoParam yes ''
Enable multiple authentication exchanges (RFC 4739).
'';
nbns1 = mkOptionalStrParam ''
WINS server assigned to peer via configuration payload (CP), see attr
plugin.
'';
nbns2 = mkOptionalStrParam ''
WINS server assigned to peer via configuration payload (CP).
'';
port = mkIntParam 500 ''
UDP port used locally. If set to 0 a random port will be allocated.
'';
port_nat_t = mkIntParam 4500 ''
UDP port used locally in case of NAT-T. If set to 0 a random port will
be allocated. Has to be different from charon.port, otherwise a random
port will be allocated.
'';
prefer_best_path = mkYesNoParam no ''
By default, charon keeps SAs on the routing path with addresses it
previously used if that path is still usable. By enabling this option,
it tries more aggressively to update SAs with MOBIKE on routing priority
changes using the cheapest path. This adds more noise, but allows to
dynamically adapt SAs to routing priority changes. This option has no
effect if MOBIKE is not supported or disabled.
'';
prefer_configured_proposals = mkYesNoParam yes ''
Prefer locally configured proposals for IKE/IPsec over supplied ones as
responder (disabling this can avoid keying retries due to
INVALID_KE_PAYLOAD notifies).
'';
prefer_temporary_addrs = mkYesNoParam no ''
By default public IPv6 addresses are preferred over temporary ones
(according to RFC 4941), to make connections more stable. Enable this
option to reverse this.
'';
process_route = mkYesNoParam yes ''
Process RTM_NEWROUTE and RTM_DELROUTE events.
'';
processor.priority_threads = {
critical = mkIntParam 0 ''
Threads reserved for CRITICAL priority class jobs.
'';
high = mkIntParam 0 ''
Threads reserved for HIGH priority class jobs.
'';
medium = mkIntParam 0 ''
Threads reserved for MEDIUM priority class jobs.
'';
low = mkIntParam 0 ''
Threads reserved for LOW priority class jobs.
'';
};
receive_delay = mkIntParam 0 ''
Delay in ms for receiving packets, to simulate larger RTT.
'';
receive_delay_response = mkYesNoParam yes ''
Delay response messages.
'';
receive_delay_request = mkYesNoParam yes ''
Delay request messages.
'';
receive_delay_type = mkIntParam 0 ''
Specific IKEv2 message type to delay, 0 for any.
'';
replay_window = mkIntParam 32 ''
Size of the AH/ESP replay window, in packets.
'';
retransmit_base = mkFloatParam "1.8" ''
Base to use for calculating exponential back off, see Retransmission.
'';
retransmit_jitter = mkIntParam 0 ''
Maximum jitter in percent to apply randomly to calculated retransmission
timeout (0 to disable).
'';
retransmit_limit = mkIntParam 0 ''
Upper limit in seconds for calculated retransmission timeout (0 to
disable).
'';
retransmit_timeout = mkFloatParam "4.0" ''
Timeout in seconds before sending first retransmit.
'';
retransmit_tries = mkIntParam 5 ''
Number of times to retransmit a packet before giving up.
'';
retry_initiate_interval = mkIntParam 0 ''
Interval in seconds to use when retrying to initiate an IKE_SA (e.g. if
DNS resolution failed), 0 to disable retries.
'';
reuse_ikesa = mkYesNoParam yes ''
Initiate CHILD_SA within existing IKE_SAs (always enabled for IKEv1).
'';
routing_table = mkIntParam 220 ''
Numerical routing table to install routes to.
'';
routing_table_prio = mkIntParam 220 ''
Priority of the routing table.
'';
send_delay = mkIntParam 0 ''
Delay in ms for sending packets, to simulate larger RTT.
'';
send_delay_request = mkYesNoParam yes ''
Delay request messages.
'';
send_delay_response = mkYesNoParam yes ''
Delay response messages.
'';
send_delay_type = mkIntParam 0 ''
Specific IKEv2 message type to delay, 0 for any.
'';
send_vendor_id = mkYesNoParam no ''
Send strongSwan vendor ID payload.
'';
signature_authentication = mkYesNoParam yes ''
Whether to enable Signature Authentication as per RFC 7427.
'';
signature_authentication_constraints = mkYesNoParam yes ''
If enabled, signature schemes configured in rightauth, in addition to
getting used as constraints against signature schemes employed in the
certificate chain, are also used as constraints against the signature
scheme used by peers during IKEv2.
'';
spi_min = mkHexParam "0xc0000000" ''
The lower limit for SPIs requested from the kernel for IPsec SAs. Should
not be set lower than 0x00000100 (256), as SPIs between 1 and 255 are
reserved by IANA.
'';
spi_max = mkHexParam "0xcfffffff" ''
The upper limit for SPIs requested from the kernel for IPsec SAs.
'';
start-scripts = mkAttrsOfParam (mkStrParam "" "") ''
Section containing a list of scripts (name = path) that are executed
when the daemon is started.
'';
stop-scripts = mkAttrsOfParam (mkStrParam "" "") ''
Section containing a list of scripts (name = path) that are executed
when the daemon is terminated.
'';
syslog = loglevelParams // {
identifier = mkOptionalStrParam ''
Identifier for use with openlog(3).
</para><para>
Global identifier used for an openlog(3) call, prepended to each log
message by syslog. If not configured, openlog(3) is not called, so
the value will depend on system defaults (often the program name).
'';
ike_name = mkYesNoParam no ''
Prefix each log entry with the connection name and a unique numerical
identifier for each IKE_SA.
'';
};
threads = mkIntParam 16 ''
Number of worker threads in charon. Several of these are reserved for
long running tasks in internal modules and plugins. Therefore, make sure
you don't set this value too low. The number of idle worker threads
listed in ipsec statusall might be used as indicator on the number of
reserved threads (JobPriority has more on this).
'';
user = mkOptionalStrParam ''
Name of the user the daemon changes to after startup.
'';
x509.enforce_critical = mkYesNoParam yes ''
Discard certificates with unsupported or unknown critical extensions.
'';
plugins = import ./strongswan-charon-plugins-params.nix lib;
imcv = {
assessment_result = mkYesNoParam yes ''
Whether IMVs send a standard IETF Assessment Result attribute.
'';
database = mkOptionalStrParam ''
Global IMV policy database URI. If it contains a password, make sure to
adjust the permissions of the config file accordingly.
'';
os_info.default_password_enabled = mkYesNoParam no ''
Manually set whether a default password is enabled.
'';
os_info.name = mkOptionalStrParam ''
Manually set the name of the client OS (e.g. <literal>NixOS</literal>).
'';
os_info.version = mkOptionalStrParam ''
Manually set the version of the client OS (e.g. <literal>17.09</literal>).
'';
policy_script = mkStrParam "ipsec _imv_policy" ''
Script called for each TNC connection to generate IMV policies.
'';
};
tls = {
cipher = mkSpaceSepListParam [] ''
List of TLS encryption ciphers.
'';
key_exchange = mkSpaceSepListParam [] ''
List of TLS key exchange methods.
'';
mac = mkSpaceSepListParam [] ''
List of TLS MAC algorithms.
'';
suites = mkSpaceSepListParam [] ''
List of TLS cipher suites.
'';
};
tnc = {
libtnccs.tnc_config = mkStrParam "/etc/tnc_config" ''
TNC IMC/IMV configuration file.
'';
};
}

View File

@ -0,0 +1,291 @@
lib : with (import ./param-constructors.nix lib); {
debug_level = mkIntParam 1 ''
Debug level for a stand-alone libimcv library.
'';
load = mkSpaceSepListParam ["random" "nonce" "gmp" "pubkey" "x509"] ''
Plugins to load in IMC/IMVs with stand-alone libimcv library.
'';
stderr_quiet = mkYesNoParam no ''
Disable the output to stderr with a stand-alone libimcv library.
'';
swid_gen = {
command = mkStrParam "/usr/local/bin/swid_generator" ''
SWID generator command to be executed.
'';
tag_creator = {
name = mkStrParam "strongSwan Project" ''
Name of the tagCreator entity.
'';
regid = mkStrParam "strongswan.org" ''
regid of the tagCreator entity.
'';
};
};
plugins = {
imc-attestation = {
aik_blob = mkOptionalStrParam ''
AIK encrypted private key blob file.
'';
aik_cert = mkOptionalStrParam ''
AIK certificate file.
'';
aik_handle = mkOptionalStrParam ''
AIK object handle, e.g. 0x81010003.
'';
aik_pubkey = mkOptionalStrParam ''
AIK public key file.
'';
mandatory_dh_groups = mkYesNoParam yes ''
Enforce mandatory Diffie-Hellman groups
'';
nonce_len = mkIntParam 20 ''
DH nonce length.
'';
pcr_info = mkYesNoParam no ''
Whether to send pcr_before and pcr_after info.
'';
use_quote2 = mkYesNoParam yes ''
Use Quote2 AIK signature instead of Quote signature.
'';
use_version_info = mkYesNoParam no ''
Version Info is included in Quote2 signature.
'';
};
imc-hcd.push_info = mkYesNoParam yes ''
Send quadruple info without being prompted.
'';
imc-hcd.subtypes = let
imcHcdSubtypeParams = let
softwareParams = mkAttrsOfParams {
name = mkOptionalStrParam ''
Name of the software installed on the hardcopy device.
'';
patches = mkOptionalStrParam ''
String describing all patches applied to the given software on this
hardcopy device. The individual patches are separated by a newline
character '\\n'.
'';
string_version = mkOptionalStrParam ''
String describing the version of the given software on this hardcopy device.
'';
version = mkOptionalStrParam ''
Hex-encoded version string with a length of 16 octets consisting of
the fields major version number (4 octets), minor version number (4
octets), build number (4 octets), service pack major number (2
octets) and service pack minor number (2 octets).
'';
} ''
Defines a software section having an arbitrary name.
'';
in {
firmware = softwareParams;
resident_application = softwareParams;
user_application = softwareParams;
attributes_natural_language = mkStrParam "en" ''
Variable length natural language tag conforming to RFC 5646 specifies
the language to be used in the health assessment message of a given
subtype.
'';
};
in {
system = imcHcdSubtypeParams // {
certification_state = mkOptionalStrParam ''
Hex-encoded certification state.
'';
configuration_state = mkOptionalStrParam ''
Hex-encoded configuration state.
'';
machine_type_model = mkOptionalStrParam ''
String specifying the machine type and model of the hardcopy device.
'';
pstn_fax_enabled = mkYesNoParam no ''
Specifies if a PSTN facsimile interface is installed and enabled on the
hardcopy device.
'';
time_source = mkOptionalStrParam ''
String specifying the hostname of the network time server used by the
hardcopy device.
'';
user_application_enabled = mkYesNoParam no ''
Specifies if users can dynamically download and execute applications on
the hardcopy device.
'';
user_application_persistence_enabled = mkYesNoParam no ''
Specifies if user dynamically downloaded applications can persist outside
the boundaries of a single job on the hardcopy device.
'';
vendor_name = mkOptionalStrParam ''
String specifying the manufacturer of the hardcopy device.
'';
vendor_smi_code = mkOptionalIntParam ''
Integer specifying the globally unique 24-bit SMI code assigned to the
manufacturer of the hardcopy device.
'';
};
control = imcHcdSubtypeParams;
marker = imcHcdSubtypeParams;
finisher = imcHcdSubtypeParams;
interface = imcHcdSubtypeParams;
scanner = imcHcdSubtypeParams;
};
imc-os = {
device_cert = mkOptionalStrParam ''
Manually set the path to the client device certificate
(e.g. /etc/pts/aikCert.der)
'';
device_id = mkOptionalStrParam ''
Manually set the client device ID in hexadecimal format
(e.g. 1083f03988c9762703b1c1080c2e46f72b99cc31)
'';
device_pubkey = mkOptionalStrParam ''
Manually set the path to the client device public key
(e.g. /etc/pts/aikPub.der)
'';
push_info = mkYesNoParam yes ''
Send operating system info without being prompted.
'';
};
imc-scanner.push_info = mkYesNoParam yes ''
Send open listening ports without being prompted.
'';
imc-swid = {
swid_full = mkYesNoParam no ''
Include file information in the XML-encoded SWID tags.
'';
swid_pretty = mkYesNoParam no ''
Generate XML-encoded SWID tags with pretty indentation.
'';
swid_directory = mkStrParam "\${prefix}/share" ''
Directory where SWID tags are located.
'';
};
imc-swima = {
eid_epoch = mkHexParam "0x11223344" ''
Set 32 bit epoch value for event IDs manually if software collector
database is not available.
'';
swid_database = mkOptionalStrParam ''
URI to software collector database containing event timestamps, software
creation and deletion events and collected software identifiers. If it
contains a password, make sure to adjust the permissions of the config
file accordingly.
'';
swid_directory = mkStrParam "\${prefix}/share" ''
Directory where SWID tags are located.
'';
swid_pretty = mkYesNoParam no ''
Generate XML-encoded SWID tags with pretty indentation.
'';
swid_full = mkYesNoParam no ''
Include file information in the XML-encoded SWID tags.
'';
};
imc-test = {
additional_ids = mkIntParam 0 ''
Number of additional IMC IDs.
'';
command = mkStrParam "none" ''
Command to be sent to the Test IMV.
'';
dummy_size = mkIntParam 0 ''
Size of dummy attribute to be sent to the Test IMV (0 = disabled).
'';
retry = mkYesNoParam no ''
Do a handshake retry.
'';
retry_command = mkOptionalStrParam ''
Command to be sent to the IMV Test in the handshake retry.
'';
};
imv-attestation = {
cadir = mkOptionalStrParam ''
Path to directory with AIK cacerts.
'';
dh_group = mkStrParam "ecp256" ''
Preferred Diffie-Hellman group.
'';
hash_algorithm = mkStrParam "sha256" ''
Preferred measurement hash algorithm.
'';
min_nonce_len = mkIntParam 0 ''
DH minimum nonce length.
'';
remediation_uri = mkOptionalStrParam ''
URI pointing to attestation remediation instructions.
'';
};
imv-os.remediation_uri = mkOptionalStrParam ''
URI pointing to operating system remediation instructions.
'';
imv-scanner.remediation_uri = mkOptionalStrParam ''
URI pointing to scanner remediation instructions.
'';
imv-swima.rest_api = {
uri = mkOptionalStrParam ''
HTTP URI of the SWID REST API.
'';
timeout = mkIntParam 120 ''
Timeout of SWID REST API HTTP POST transaction.
'';
};
imv-test.rounds = mkIntParam 0 ''
Number of IMC-IMV retry rounds.
'';
};
}

View File

@ -0,0 +1,29 @@
lib : with (import ./param-constructors.nix lib);
let mkJournalParam = description :
mkEnumParam [(-1) 0 1 2 3 4] 0 "Logging level for ${description}";
in {
default = mkIntParam 1 ''
Specifies the default loglevel to be used for subsystems for which no
specific loglevel is defined.
'';
app = mkJournalParam "applications other than daemons.";
asn = mkJournalParam "low-level encoding/decoding (ASN.1, X.509 etc.)";
cfg = mkJournalParam "configuration management and plugins.";
chd = mkJournalParam "CHILD_SA/IPsec SA.";
dmn = mkJournalParam "main daemon setup/cleanup/signal handling.";
enc = mkJournalParam "packet encoding/decoding encryption/decryption operations.";
esp = mkJournalParam "libipsec library messages.";
ike = mkJournalParam "IKE_SA/ISAKMP SA.";
imc = mkJournalParam "integrity Measurement Collector.";
imv = mkJournalParam "integrity Measurement Verifier.";
job = mkJournalParam "jobs queuing/processing and thread pool management.";
knl = mkJournalParam "IPsec/Networking kernel interface.";
lib = mkJournalParam "libstrongwan library messages.";
mgr = mkJournalParam "IKE_SA manager, handling synchronization for IKE_SA access.";
net = mkJournalParam "IKE network communication.";
pts = mkJournalParam "platform Trust Service.";
tls = mkJournalParam "libtls library messages.";
tnc = mkJournalParam "trusted Network Connect.";
}

View File

@ -0,0 +1,228 @@
# See: https://wiki.strongswan.org/projects/strongswan/wiki/StrongswanConf
#
# When strongSwan is upgraded please update the parameters in this file. You can
# see which parameters should be deleted, changed or added by diffing
# the strongswan conf directory:
#
# git clone https://github.com/strongswan/strongswan.git
# cd strongswan
# git diff 5.5.3..5.6.0 conf/
lib: with (import ./param-constructors.nix lib);
let charonParams = import ./strongswan-charon-params.nix lib;
in {
aikgen = {
load = mkSpaceSepListParam [] ''
Plugins to load in ipsec aikgen tool.
'';
};
attest = {
database = mkOptionalStrParam ''
File measurement information database URI. If it contains a password,
make sure to adjust the permissions of the config file accordingly.
'';
load = mkSpaceSepListParam [] ''
Plugins to load in ipsec attest tool.
'';
};
charon = charonParams;
charon-nm = {
ca_dir = mkStrParam "<default>" ''
Directory from which to load CA certificates if no certificate is
configured.
'';
};
charon-systemd = charonParams // {
journal = import ./strongswan-loglevel-params.nix lib;
};
imv_policy_manager = {
command_allow = mkOptionalStrParam ''
Shell command to be executed with recommendation allow.
'';
command_block = mkOptionalStrParam ''
Shell command to be executed with all other recommendations.
'';
database = mkOptionalStrParam ''
Database URI for the database that stores the package information. If it
contains a password, make sure to adjust permissions of the config file
accordingly.
'';
load = mkSpaceSepListParam ["sqlite"] ''
Plugins to load in IMV policy manager.
'';
};
libimcv = import ./strongswan-libimcv-params.nix lib;
manager = {
database = mkOptionalStrParam ''
Credential database URI for manager. If it contains a password, make
sure to adjust the permissions of the config file accordingly.
'';
debug = mkYesNoParam no ''
Enable debugging in manager.
'';
load = mkSpaceSepListParam [] ''
Plugins to load in manager.
'';
socket = mkOptionalStrParam ''
FastCGI socket of manager, to run it statically.
'';
threads = mkIntParam 10 ''
Threads to use for request handling.
'';
timeout = mkDurationParam "15m" ''
Session timeout for manager.
'';
};
medcli = {
database = mkOptionalStrParam ''
Mediation client database URI. If it contains a password, make sure to
adjust the permissions of the config file accordingly.
'';
dpd = mkDurationParam "5m" ''
DPD timeout to use in mediation client plugin.
'';
rekey = mkDurationParam "20m" ''
Rekeying time on mediation connections in mediation client plugin.
'';
};
medsrv = {
database = mkOptionalStrParam ''
Mediation server database URI. If it contains a password, make sure to
adjust the permissions of the config file accordingly.
'';
debug = mkYesNoParam no ''
Debugging in mediation server web application.
'';
dpd = mkDurationParam "5m" ''
DPD timeout to use in mediation server plugin.
'';
load = mkSpaceSepListParam [] ''
Plugins to load in mediation server plugin.
'';
password_length = mkIntParam 6 ''
Minimum password length required for mediation server user accounts.
'';
rekey = mkDurationParam "20m" ''
Rekeying time on mediation connections in mediation server plugin.
'';
socket = mkOptionalStrParam ''
Run Mediation server web application statically on socket.
'';
threads = mkIntParam 5 ''
Number of thread for mediation service web application.
'';
timeout = mkDurationParam "15m" ''
Session timeout for mediation service.
'';
};
pacman.database = mkOptionalStrParam ''
Database URI for the database that stores the package information. If it
contains a password, make sure to adjust the permissions of the config
file accordingly.
'';
pki.load = mkSpaceSepListParam [] ''
Plugins to load in ipsec pki tool.
'';
pool = {
database = mkOptionalStrParam ''
Database URI for the database that stores IP pools and configuration
attributes. If it contains a password, make sure to adjust the
permissions of the config file accordingly.
'';
load = mkSpaceSepListParam [] ''
Plugins to load in ipsec pool tool.
'';
};
pt-tls-client.load = mkSpaceSepListParam [] ''
Plugins to load in ipsec pt-tls-client tool.
'';
scepclient.load = mkSpaceSepListParam [] ''
Plugins to load in ipsec scepclient tool.
'';
starter = {
config_file = mkStrParam "\${sysconfdir}/ipsec.conf" ''
Location of the ipsec.conf file.
'';
load_warning = mkYesNoParam yes ''
Show charon.load setting warning, see
https://wiki.strongswan.org/projects/strongswan/wiki/PluginLoad
'';
};
sw-collector = {
database = mkOptionalStrParam ''
URI to software collector database containing event timestamps,
software creation and deletion events and collected software
identifiers. If it contains a password, make sure to adjust the
permissions of the config file accordingly.
'';
first_file = mkStrParam "/var/log/bootstrap.log" ''
Path pointing to file created when the Linux OS was installed.
'';
first_time = mkStrParam "0000-00-00T00:00:00Z" ''
Time in UTC when the Linux OS was installed.
'';
history = mkOptionalStrParam ''
Path pointing to apt history.log file.
'';
rest_api = {
uri = mkOptionalStrParam ''
HTTP URI of the central collector's REST API.
'';
timeout = mkIntParam 120 ''
Timeout of REST API HTTP POST transaction.
'';
};
load = mkSpaceSepListParam [] "Plugins to load in sw-collector tool.";
};
swanctl = {
load = mkSpaceSepListParam [] "Plugins to load in swanctl.";
socket = mkStrParam "unix://\${piddir}/charon.vici" ''
VICI socket to connect to by default.
'';
};
}

File diff suppressed because it is too large Load Diff

View File

@ -352,6 +352,7 @@ in rec {
tests.smokeping = callTest tests/smokeping.nix {};
tests.snapper = callTest tests/snapper.nix {};
tests.statsd = callTest tests/statsd.nix {};
tests.strongswan-swanctl = callTest tests/strongswan-swanctl.nix {};
tests.sudo = callTest tests/sudo.nix {};
tests.switchTest = callTest tests/switch-test.nix {};
tests.taskserver = callTest tests/taskserver.nix {};

View File

@ -0,0 +1,154 @@
# This strongswan-swanctl test is based on:
# https://www.strongswan.org/testing/testresults/swanctl/rw-psk-ipv4/index.html
# https://github.com/strongswan/strongswan/tree/master/testing/tests/swanctl/rw-psk-ipv4
#
# The roadwarrior carol sets up a connection to gateway moon. The authentication
# is based on pre-shared keys and IPv4 addresses. Upon the successful
# establishment of the IPsec tunnels, the specified updown script automatically
# inserts iptables-based firewall rules that let pass the tunneled traffic. In
# order to test both tunnel and firewall, carol pings the client alice behind
# the gateway moon.
#
# alice moon carol
# eth1------vlan_0------eth1 eth2------vlan_1------eth1
# 192.168.0.1 192.168.0.3 192.168.1.3 192.168.1.2
#
# See the NixOS manual for how to run this test:
# https://nixos.org/nixos/manual/index.html#sec-running-nixos-tests-interactively
import ./make-test.nix ({ pkgs, ...} :
let
ifAddr = node: iface: (pkgs.lib.head node.config.networking.interfaces.${iface}.ip4).address;
allowESP = "iptables --insert INPUT --protocol ESP --jump ACCEPT";
# Shared VPN settings:
vlan0 = "192.168.0.0/24";
version = 2;
secret = "0sFpZAZqEN6Ti9sqt4ZP5EWcqx";
esp_proposals = [ "aes128gcm128-x25519" ];
proposals = [ "aes128-sha256-x25519" ];
in {
name = "strongswan-swanctl";
meta.maintainers = with pkgs.stdenv.lib.maintainers; [ basvandijk ];
nodes = {
alice = { nodes, ... } : {
virtualisation.vlans = [ 0 ];
networking = {
dhcpcd.enable = false;
defaultGateway = ifAddr nodes.moon "eth1";
};
};
moon = {pkgs, config, nodes, ...} :
let
carolIp = ifAddr nodes.carol "eth1";
moonIp = ifAddr nodes.moon "eth2";
strongswan = config.services.strongswan-swanctl.package;
in {
virtualisation.vlans = [ 0 1 ];
networking = {
dhcpcd.enable = false;
firewall = {
allowedUDPPorts = [ 4500 500 ];
extraCommands = allowESP;
};
nat = {
enable = true;
internalIPs = [ vlan0 ];
internalInterfaces = [ "eth1" ];
externalIP = moonIp;
externalInterface = "eth2";
};
};
environment.systemPackages = [ strongswan ];
services.strongswan-swanctl = {
enable = true;
swanctl = {
connections = {
"rw" = {
local_addrs = [ moonIp ];
local."main" = {
auth = "psk";
};
remote."main" = {
auth = "psk";
};
children = {
"net" = {
local_ts = [ vlan0 ];
updown = "${strongswan}/libexec/ipsec/_updown iptables";
inherit esp_proposals;
};
};
inherit version;
inherit proposals;
};
};
secrets = {
ike."carol" = {
id."main" = carolIp;
inherit secret;
};
};
};
};
};
carol = {pkgs, config, nodes, ...} :
let
carolIp = ifAddr nodes.carol "eth1";
moonIp = ifAddr nodes.moon "eth2";
strongswan = config.services.strongswan-swanctl.package;
in {
virtualisation.vlans = [ 1 ];
networking = {
dhcpcd.enable = false;
firewall.extraCommands = allowESP;
};
environment.systemPackages = [ strongswan ];
services.strongswan-swanctl = {
enable = true;
swanctl = {
connections = {
"home" = {
local_addrs = [ carolIp ];
remote_addrs = [ moonIp ];
local."main" = {
auth = "psk";
id = carolIp;
};
remote."main" = {
auth = "psk";
id = moonIp;
};
children = {
"home" = {
remote_ts = [ vlan0 ];
start_action = "trap";
updown = "${strongswan}/libexec/ipsec/_updown iptables";
inherit esp_proposals;
};
};
inherit version;
inherit proposals;
};
};
secrets = {
ike."moon" = {
id."main" = moonIp;
inherit secret;
};
};
};
};
};
};
testScript = ''
startAll();
$carol->waitUntilSucceeds("ping -c 1 alice");
'';
})