diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 74c8c7bbc82..c0c3249c60b 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -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 diff --git a/nixos/modules/services/networking/strongswan-swanctl/module.nix b/nixos/modules/services/networking/strongswan-swanctl/module.nix new file mode 100644 index 00000000000..d770094960b --- /dev/null +++ b/nixos/modules/services/networking/strongswan-swanctl/module.nix @@ -0,0 +1,82 @@ +{ config, lib, pkgs, ... }: + +with lib; +with (import ./param-lib.nix lib); + +let + cfg = config.services.strongswan-swanctl; + 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.extraConfig = mkOption { + type = types.str; + default = ""; + description = '' + Contents of the strongswan.conf file. + ''; + }; + + 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 = cfg.strongswan.extraConfig; + }; + 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"; + }; + }; + }; +} diff --git a/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix b/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix new file mode 100644 index 00000000000..5e74a96664f --- /dev/null +++ b/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix @@ -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 + '' + + StrongSwan default: + ''; + + 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); + }; + +} diff --git a/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix b/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix new file mode 100644 index 00000000000..fb87e81f321 --- /dev/null +++ b/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix @@ -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; + +} diff --git a/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix b/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix new file mode 100644 index 00000000000..ad211f41eef --- /dev/null +++ b/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix @@ -0,0 +1,1168 @@ +# See: https://wiki.strongswan.org/projects/strongswan/wiki/Swanctlconf +# +# When strongSwan is upgraded please update the parameters in this file. You can +# see which parameters should be deleted, changed or added by diffing +# swanctl.opt: +# +# git clone https://github.com/strongswan/strongswan.git +# cd strongswan +# git diff 5.5.3..5.6.0 src/swanctl/swanctl.opt + +lib: with (import ./param-constructors.nix lib); + +let + certParams = { + file = mkOptionalStrParam '' + Absolute path to the certificate to load. Passed as-is to the daemon, so + it must be readable by it. + + Configure either this or , but not both, in one section. + ''; + + handle = mkOptionalHexParam '' + Hex-encoded CKA_ID or handle of the certificate on a token or TPM, + respectively. + + Configure either this or , but not both, in one section. + ''; + + slot = mkOptionalIntParam '' + Optional slot number of the token that stores the certificate. + ''; + + module = mkOptionalStrParam '' + Optional PKCS#11 module name. + ''; + }; +in { + authorities = mkAttrsOfParams ({ + + cacert = mkOptionalStrParam '' + The certificates may use a relative path from the swanctl + x509ca directory or an absolute path. + + Configure one of , + , or + per section. + ''; + + cert_uri_base = mkOptionalStrParam '' + Defines the base URI for the Hash and URL feature supported by + IKEv2. Instead of exchanging complete certificates, IKEv2 allows one to + send an URI that resolves to the DER encoded certificate. The certificate + URIs are built by appending the SHA1 hash of the DER encoded certificates + to this base URI. + ''; + + crl_uris = mkCommaSepListParam [] '' + List of CRL distribution points (ldap, http, or file URI). + ''; + + ocsp_uris = mkCommaSepListParam [] '' + List of OCSP URIs. + ''; + + } // certParams) '' + Section defining complementary attributes of certification authorities, each + in its own subsection with an arbitrary yet unique name + ''; + + connections = mkAttrsOfParams { + + version = mkIntParam 0 '' + IKE major version to use for connection. + + 1 uses IKEv1 aka ISAKMP, + 2 uses IKEv2. + A connection using the default of 0 accepts both IKEv1 and IKEv2 as + responder, and initiates the connection actively with IKEv2. + + ''; + + local_addrs = mkCommaSepListParam [] '' + Local address(es) to use for IKE communication. Takes + single IPv4/IPv6 addresses, DNS names, CIDR subnets or IP address ranges. + + As initiator, the first non-range/non-subnet is used to initiate the + connection from. As responder, the local destination address must match at + least to one of the specified addresses, subnets or ranges. + + If FQDNs are assigned they are resolved every time a configuration lookup + is done. If DNS resolution times out, the lookup is delayed for that time. + ''; + + remote_addrs = mkCommaSepListParam [] '' + Remote address(es) to use for IKE communication. Takes + single IPv4/IPv6 addresses, DNS names, CIDR subnets or IP address ranges. + + As initiator, the first non-range/non-subnet is used to initiate the + connection to. As responder, the initiator source address must match at + least to one of the specified addresses, subnets or ranges. + + If FQDNs are assigned they are resolved every time a configuration lookup + is done. If DNS resolution times out, the lookup is delayed for that time. + To initiate a connection, at least one specific address or DNS name must + be specified. + ''; + + local_port = mkIntParam 500 '' + Local UDP port for IKE communication. By default the port of the socket + backend is used, which is usually 500. If port + 500 is used, automatic IKE port floating to port + 4500 is used to work around NAT issues. + + Using a non-default local IKE port requires support from the socket + backend in use (socket-dynamic). + ''; + + remote_port = mkIntParam 500 '' + Remote UDP port for IKE communication. If the default of port + 500 is used, automatic IKE port floating to port + 4500 is used to work around NAT issues. + ''; + + proposals = mkCommaSepListParam ["default"] '' + A proposal is a set of algorithms. For non-AEAD algorithms, this includes + for IKE an encryption algorithm, an integrity algorithm, a pseudo random + function and a Diffie-Hellman group. For AEAD algorithms, instead of + encryption and integrity algorithms, a combined algorithm is used. + + In IKEv2, multiple algorithms of the same kind can be specified in a + single proposal, from which one gets selected. In IKEv1, only one + algorithm per kind is allowed per proposal, more algorithms get implicitly + stripped. Use multiple proposals to offer different algorithms + combinations in IKEv1. + + Algorithm keywords get separated using dashes. Multiple proposals may be + specified in a list. The special value default forms a + default proposal of supported algorithms considered safe, and is usually a + good choice for interoperability. + ''; + + vips = mkCommaSepListParam [] '' + List of virtual IPs to request in IKEv2 configuration payloads or IKEv1 + Mode Config. The wildcard addresses 0.0.0.0 and + :: request an arbitrary address, specific addresses may + be defined. The responder may return a different address, though, or none + at all. + ''; + + aggressive = mkYesNoParam no '' + Enables Aggressive Mode instead of Main Mode with Identity + Protection. Aggressive Mode is considered less secure, because the ID and + HASH payloads are exchanged unprotected. This allows a passive attacker to + snoop peer identities, and even worse, start dictionary attacks on the + Preshared Key. + ''; + + pull = mkYesNoParam yes '' + If the default of yes is used, Mode Config works in pull mode, where the + initiator actively requests a virtual IP. With no, push mode is used, + where the responder pushes down a virtual IP to the initiating peer. + + Push mode is currently supported for IKEv1, but not in IKEv2. It is used + by a few implementations only, pull mode is recommended. + ''; + + dscp = mkStrParam "000000" '' + Differentiated Services Field Codepoint to set on outgoing IKE packets for + this connection. The value is a six digit binary encoded string specifying + the Codepoint to set, as defined in RFC 2474. + ''; + + encap = mkYesNoParam no '' + To enforce UDP encapsulation of ESP packets, the IKE daemon can fake the + NAT detection payloads. This makes the peer believe that NAT takes place + on the path, forcing it to encapsulate ESP packets in UDP. + + Usually this is not required, but it can help to work around connectivity + issues with too restrictive intermediary firewalls. + ''; + + mobike = mkYesNoParam yes '' + Enables MOBIKE on IKEv2 connections. MOBIKE is enabled by default on IKEv2 + connections, and allows mobility of clients and multi-homing on servers by + migrating active IPsec tunnels. + + Usually keeping MOBIKE enabled is unproblematic, as it is not used if the + peer does not indicate support for it. However, due to the design of + MOBIKE, IKEv2 always floats to port 4500 starting from the second + exchange. Some implementations don't like this behavior, hence it can be + disabled. + ''; + + dpd_delay = mkDurationParam "0s" '' + Interval to check the liveness of a peer actively using IKEv2 + INFORMATIONAL exchanges or IKEv1 R_U_THERE messages. Active DPD checking + is only enforced if no IKE or ESP/AH packet has been received for the + configured DPD delay. + ''; + + dpd_timeout = mkDurationParam "0s" '' + Charon by default uses the normal retransmission mechanism and timeouts to + check the liveness of a peer, as all messages are used for liveness + checking. For compatibility reasons, with IKEv1 a custom interval may be + specified; this option has no effect on connections using IKEv2. + ''; + + fragmentation = mkEnumParam ["yes" "accept" "force" "no"] "yes" '' + Use IKE fragmentation (proprietary IKEv1 extension or RFC 7383 IKEv2 + fragmentation). Acceptable values are yes (the default + since 5.5.1), accept (since versions:5.5.3), + force and no. + + If set to yes, and the peer + supports it, oversized IKE messages will be sent in fragments. + If set to + accept, support for fragmentation is announced to the peer but the daemon + does not send its own messages in fragments. + If set to force (only + supported for IKEv1) the initial IKE message will already be fragmented if + required. + Finally, setting the option to no will disable announcing + support for this feature. + + + Note that fragmented IKE messages sent by a peer are always processed + irrespective of the value of this option (even when set to no). + ''; + + send_certreq = mkYesNoParam yes '' + Send certificate request payloads to offer trusted root CA certificates to + the peer. Certificate requests help the peer to choose an appropriate + certificate/private key for authentication and are enabled by default. + Disabling certificate requests can be useful if too many trusted root CA + certificates are installed, as each certificate request increases the size + of the initial IKE packets. + ''; + + send_cert = mkEnumParam ["always" "never" "ifasked" ] "ifasked" '' + Send certificate payloads when using certificate authentication. + + With the default of ifasked the daemon sends + certificate payloads only if certificate requests have been received. + never disables sending of certificate payloads + altogether, + always causes certificate payloads to be sent + unconditionally whenever certificate authentication is used. + + ''; + + keyingtries = mkIntParam 1 '' + Number of retransmission sequences to perform during initial + connect. Instead of giving up initiation after the first retransmission + sequence with the default value of 1, additional + sequences may be started according to the configured value. A value of + 0 initiates a new sequence until the connection + establishes or fails with a permanent error. + ''; + + unique = mkEnumParam ["no" "never" "keep" "replace"] "no" '' + Connection uniqueness policy to enforce. To avoid multiple connections + from the same user, a uniqueness policy can be enforced. + + + + The value never does never enforce such a policy, even + if a peer included INITIAL_CONTACT notification messages, + + + whereas no replaces existing connections for the same + identity if a new one has the INITIAL_CONTACT notify. + + + keep rejects new connection attempts if the same user + already has an active connection, + + + replace deletes any existing connection if a new one + for the same user gets established. + + + To compare connections for uniqueness, the remote IKE identity is used. If + EAP or XAuth authentication is involved, the EAP-Identity or XAuth + username is used to enforce the uniqueness policy instead. + + On initiators this setting specifies whether an INITIAL_CONTACT notify is + sent during IKE_AUTH if no existing connection is found with the remote + peer (determined by the identities of the first authentication + round). Unless set to never the client will send a notify. + ''; + + reauth_time = mkDurationParam "0s" '' + Time to schedule IKE reauthentication. IKE reauthentication recreates the + IKE/ISAKMP SA from scratch and re-evaluates the credentials. In asymmetric + configurations (with EAP or configuration payloads) it might not be + possible to actively reauthenticate as responder. The IKEv2 + reauthentication lifetime negotiation can instruct the client to perform + reauthentication. + + Reauthentication is disabled by default. Enabling it usually may lead to + small connection interruptions, as strongSwan uses a break-before-make + policy with IKEv2 to avoid any conflicts with associated tunnel resources. + ''; + + rekey_time = mkDurationParam "4h" '' + IKE rekeying refreshes key material using a Diffie-Hellman exchange, but + does not re-check associated credentials. It is supported in IKEv2 only, + IKEv1 performs a reauthentication procedure instead. + + With the default value IKE rekeying is scheduled every 4 hours, minus the + configured rand_time. If a reauth_time is configured, rekey_time defaults + to zero, disabling rekeying; explicitly set both to enforce rekeying and + reauthentication. + ''; + + over_time = mkOptionalDurationParam '' + Hard IKE_SA lifetime if rekey/reauth does not complete, as time. To avoid + having an IKE/ISAKMP kept alive if IKE reauthentication or rekeying fails + perpetually, a maximum hard lifetime may be specified. If the IKE_SA fails + to rekey or reauthenticate within the specified time, the IKE_SA gets + closed. + + In contrast to CHILD_SA rekeying, over_time is relative in time to the + rekey_time and reauth_time values, as it applies to both. + + The default is 10% of the longer of and + . + ''; + + rand_time = mkOptionalDurationParam '' + Time range from which to choose a random value to subtract from + rekey/reauth times. To avoid having both peers initiating the rekey/reauth + procedure simultaneously, a random time gets subtracted from the + rekey/reauth times. + + The default is equal to the configured . + ''; + + pools = mkCommaSepListParam [] '' + List of named IP pools to allocate virtual IP addresses + and other configuration attributes from. Each name references a pool by + name from either the pools section or an external pool. + ''; + + mediation = mkYesNoParam no '' + Whether this connection is a mediation connection, that is, whether this + connection is used to mediate other connections using the IKEv2 Mediation + Extension. Mediation connections create no CHILD_SA. + ''; + + mediated_by = mkOptionalStrParam '' + The name of the connection to mediate this connection through. If given, + the connection will be mediated through the named mediation + connection. The mediation connection must have mediation enabled. + ''; + + mediation_peer = mkOptionalStrParam '' + Identity under which the peer is registered at the mediation server, that + is, the IKE identity the other end of this connection uses as its local + identity on its connection to the mediation server. This is the identity + we request the mediation server to mediate us with. Only relevant on + connections that set mediated_by. If it is not given, the remote IKE + identity of the first authentication round of this connection will be + used. + ''; + + local = mkPrefixedAttrsOfParams { + + round = mkIntParam 0 '' + Optional numeric identifier by which authentication rounds are + sorted. If not specified rounds are ordered by their position in the + config file/vici message. + ''; + + certs = mkCommaSepListParam [] '' + List of certificate candidates to use for + authentication. The certificates may use a relative path from the + swanctl x509 directory or an absolute path. + + The certificate used for authentication is selected based on the + received certificate request payloads. If no appropriate CA can be + located, the first certificate is used. + ''; + + cert = mkPostfixedAttrsOfParams certParams '' + Section for a certificate candidate to use for + authentication. Certificates in certs are transmitted as binary blobs, + these sections offer more flexibility. + ''; + + pubkeys = mkCommaSepListParam [] '' + List of raw public key candidates to use for + authentication. The public keys may use a relative path from the swanctl + pubkey directory or an absolute path. + + Even though multiple local public keys could be defined in principle, + only the first public key in the list is used for authentication. + ''; + + auth = mkStrParam "pubkey" '' + Authentication to perform locally. + + + The default pubkey uses public key authentication + using a private key associated to a usable certificate. + + + psk uses pre-shared key authentication. + + + The IKEv1 specific xauth is used for XAuth or Hybrid + authentication, + + + while the IKEv2 specific eap keyword defines EAP + authentication. + + + For xauth, a specific backend name may be appended, + separated by a dash. The appropriate xauth backend is + selected to perform the XAuth exchange. For traditional XAuth, the + xauth method is usually defined in the second + authentication round following an initial pubkey (or + psk) round. Using xauth in the + first round performs Hybrid Mode client authentication. + + + For eap, a specific EAP method name may be appended, separated by a + dash. An EAP module implementing the appropriate method is selected to + perform the EAP conversation. + + + Since 5.4.0, if both peers support RFC 7427 ("Signature Authentication + in IKEv2") specific hash algorithms to be used during IKEv2 + authentication may be configured. To do so use ike: + followed by a trust chain signature scheme constraint (see description + of the section's + keyword). For example, with ike:pubkey-sha384-sha256 + a public key signature scheme with either SHA-384 or SHA-256 would get + used for authentication, in that order and depending on the hash + algorithms supported by the peer. If no specific hash algorithms are + configured, the default is to prefer an algorithm that matches or + exceeds the strength of the signature key. If no constraints with + ike: prefix are configured any signature scheme + constraint (without ike: prefix) will also apply to + IKEv2 authentication, unless this is disabled in + strongswan.conf. To use RSASSA-PSS signatures use + rsa/pss instead of pubkey or + rsa as in e.g. + ike:rsa/pss-sha256. If pubkey or + rsa constraints are configured RSASSA-PSS signatures + will only be used if enabled in strongswan.conf(5). + + + ''; + + id = mkOptionalStrParam '' + IKE identity to use for authentication round. When using certificate + authentication, the IKE identity must be contained in the certificate, + either as subject or as subjectAltName. + ''; + + eap_id = mkOptionalStrParam '' + Client EAP-Identity to use in EAP-Identity exchange and the EAP method. + ''; + + aaa_id = mkOptionalStrParam '' + Server side EAP-Identity to expect in the EAP method. Some EAP methods, + such as EAP-TLS, use an identity for the server to perform mutual + authentication. This identity may differ from the IKE identity, + especially when EAP authentication is delegated from the IKE responder + to an AAA backend. + + For EAP-(T)TLS, this defines the identity for which the server must + provide a certificate in the TLS exchange. + ''; + + xauth_id = mkOptionalStrParam '' + Client XAuth username used in the XAuth exchange. + ''; + + } '' + Section for a local authentication round. A local authentication round + defines the rules how authentication is performed for the local + peer. Multiple rounds may be defined to use IKEv2 RFC 4739 Multiple + Authentication or IKEv1 XAuth. + + Each round is defined in a section having local as + prefix, and an optional unique suffix. To define a single authentication + round, the suffix may be omitted. + ''; + + remote = mkPrefixedAttrsOfParams { + + round = mkIntParam 0 '' + Optional numeric identifier by which authentication rounds are + sorted. If not specified rounds are ordered by their position in the + config file/vici message. + ''; + + id = mkStrParam "%any" '' + IKE identity to expect for authentication round. When using certificate + authentication, the IKE identity must be contained in the certificate, + either as subject or as subjectAltName. + ''; + + eap_id = mkOptionalStrParam '' + Identity to use as peer identity during EAP authentication. If set to + %any the EAP-Identity method will be used to ask the + client for an EAP identity. + ''; + + groups = mkCommaSepListParam [] '' + Authorization group memberships to require. The peer + must prove membership to at least one of the specified groups. Group + membership can be certified by different means, for example by + appropriate Attribute Certificates or by an AAA backend involved in the + authentication. + ''; + + cert_policy = mkCommaSepListParam [] '' + List of certificate policy OIDs the peer's certificate + must have. OIDs are specified using the numerical dotted representation. + ''; + + certs = mkCommaSepListParam [] '' + List of certificates to accept for authentication. The certificates may + use a relative path from the swanctl x509 directory + or an absolute path. + ''; + + cert = mkPostfixedAttrsOfParams certParams '' + Section for a certificate candidate to use for + authentication. Certificates in certs are transmitted as binary blobs, + these sections offer more flexibility. + ''; + + cacerts = mkCommaSepListParam [] '' + List of CA certificates to accept for + authentication. The certificates may use a relative path from the + swanctl x509ca directory or an absolute path. + ''; + + cacert = mkPostfixedAttrsOfParams certParams '' + Section for a CA certificate to accept for authentication. Certificates + in cacerts are transmitted as binary blobs, these sections offer more + flexibility. + ''; + + pubkeys = mkCommaSepListParam [] '' + List of raw public keys to accept for + authentication. The public keys may use a relative path from the swanctl + pubkey directory or an absolute path. + ''; + + revocation = mkEnumParam ["strict" "ifuri" "relaxed"] "relaxed" '' + Certificate revocation policy for CRL or OCSP revocation. + + + A strict revocation policy fails if no revocation information is + available, i.e. the certificate is not known to be unrevoked. + + + ifuri fails only if a CRL/OCSP URI is available, but certificate + revocation checking fails, i.e. there should be revocation information + available, but it could not be obtained. + + + The default revocation policy relaxed fails only if a certificate is + revoked, i.e. it is explicitly known that it is bad. + + + ''; + + auth = mkStrParam "pubkey" '' + Authentication to expect from remote. See the + section's keyword description about the details of + supported mechanisms. + + Since 5.4.0, to require a trustchain public key strength for the remote + side, specify the key type followed by the minimum strength in bits (for + example ecdsa-384 or + rsa-2048-ecdsa-256). To limit the acceptable set of + hashing algorithms for trustchain validation, append hash algorithms to + pubkey or a key strength definition (for example + pubkey-sha256-sha512, + rsa-2048-sha256-sha384-sha512 or + rsa-2048-sha256-ecdsa-256-sha256-sha384). + Unless disabled in strongswan.conf, or explicit IKEv2 + signature constraints are configured (refer to the description of the + section's keyword for + details), such key types and hash algorithms are also applied as + constraints against IKEv2 signature authentication schemes used by the + remote side. To require RSASSA-PSS signatures use + rsa/pss instead of pubkey or + rsa as in e.g. rsa/pss-sha256. If + pubkey or rsa constraints are + configured RSASSA-PSS signatures will only be accepted if enabled in + strongswan.conf(5). + + To specify trust chain constraints for EAP-(T)TLS, append a colon to the + EAP method, followed by the key type/size and hash algorithm as + discussed above (e.g. eap-tls:ecdsa-384-sha384). + ''; + + } '' + Section for a remote authentication round. A remote authentication round + defines the constraints how the peers must authenticate to use this + connection. Multiple rounds may be defined to use IKEv2 RFC 4739 Multiple + Authentication or IKEv1 XAuth. + + Each round is defined in a section having remote as + prefix, and an optional unique suffix. To define a single authentication + round, the suffix may be omitted. + ''; + + children = mkAttrsOfParams { + ah_proposals = mkCommaSepListParam [] '' + AH proposals to offer for the CHILD_SA. A proposal is a set of + algorithms. For AH, this includes an integrity algorithm and an optional + Diffie-Hellman group. If a DH group is specified, CHILD_SA/Quick Mode + rekeying and initial negotiation uses a separate Diffie-Hellman exchange + using the specified group (refer to esp_proposals for details). + + In IKEv2, multiple algorithms of the same kind can be specified in a + single proposal, from which one gets selected. In IKEv1, only one + algorithm per kind is allowed per proposal, more algorithms get + implicitly stripped. Use multiple proposals to offer different algorithms + combinations in IKEv1. + + Algorithm keywords get separated using dashes. Multiple proposals may be + specified in a list. The special value default forms + a default proposal of supported algorithms considered safe, and is + usually a good choice for interoperability. By default no AH proposals + are included, instead ESP is proposed. + ''; + + esp_proposals = mkCommaSepListParam ["default"] '' + ESP proposals to offer for the CHILD_SA. A proposal is a set of + algorithms. For ESP non-AEAD proposals, this includes an integrity + algorithm, an encryption algorithm, an optional Diffie-Hellman group and + an optional Extended Sequence Number Mode indicator. For AEAD proposals, + a combined mode algorithm is used instead of the separate + encryption/integrity algorithms. + + If a DH group is specified, CHILD_SA/Quick Mode rekeying and initial + negotiation use a separate Diffie-Hellman exchange using the specified + group. However, for IKEv2, the keys of the CHILD_SA created implicitly + with the IKE_SA will always be derived from the IKE_SA's key material. So + any DH group specified here will only apply when the CHILD_SA is later + rekeyed or is created with a separate CREATE_CHILD_SA exchange. A + proposal mismatch might, therefore, not immediately be noticed when the + SA is established, but may later cause rekeying to fail. + + Extended Sequence Number support may be indicated with the + esn and noesn values, both may be + included to indicate support for both modes. If omitted, + noesn is assumed. + + In IKEv2, multiple algorithms of the same kind can be specified in a + single proposal, from which one gets selected. In IKEv1, only one + algorithm per kind is allowed per proposal, more algorithms get + implicitly stripped. Use multiple proposals to offer different algorithms + combinations in IKEv1. + + Algorithm keywords get separated using dashes. Multiple proposals may be + specified as a list. The special value default forms + a default proposal of supported algorithms considered safe, and is + usually a good choice for interoperability. If no algorithms are + specified for AH nor ESP, the default set of algorithms for ESP is + included. + ''; + + sha256_96 = mkYesNoParam no '' + HMAC-SHA-256 is used with 128-bit truncation with IPsec. For + compatibility with implementations that incorrectly use 96-bit truncation + this option may be enabled to configure the shorter truncation length in + the kernel. This is not negotiated, so this only works with peers that + use the incorrect truncation length (or have this option enabled). + ''; + + local_ts = mkCommaSepListParam ["dynamic"] '' + List of local traffic selectors to include in CHILD_SA. Each selector is + a CIDR subnet definition, followed by an optional proto/port + selector. The special value dynamic may be used + instead of a subnet definition, which gets replaced by the tunnel outer + address or the virtual IP, if negotiated. This is the default. + + A protocol/port selector is surrounded by opening and closing square + brackets. Between these brackets, a numeric or getservent(3) protocol + name may be specified. After the optional protocol restriction, an + optional port restriction may be specified, separated by a slash. The + port restriction may be numeric, a getservent(3) service name, or the + special value opaque for RFC 4301 OPAQUE + selectors. Port ranges may be specified as well, none of the kernel + backends currently support port ranges, though. + + When IKEv1 is used only the first selector is interpreted, except if the + Cisco Unity extension plugin is used. This is due to a limitation of the + IKEv1 protocol, which only allows a single pair of selectors per + CHILD_SA. So to tunnel traffic matched by several pairs of selectors when + using IKEv1 several children (CHILD_SAs) have to be defined that cover + the selectors. The IKE daemon uses traffic selector narrowing for IKEv1, + the same way it is standardized and implemented for IKEv2. However, this + may lead to problems with other implementations. To avoid that, configure + identical selectors in such scenarios. + ''; + + remote_ts = mkCommaSepListParam ["dynamic"] '' + List of remote selectors to include in CHILD_SA. See + for a description of the selector syntax. + ''; + + rekey_time = mkDurationParam "1h" '' + Time to schedule CHILD_SA rekeying. CHILD_SA rekeying refreshes key + material, optionally using a Diffie-Hellman exchange if a group is + specified in the proposal. To avoid rekey collisions initiated by both + ends simultaneously, a value in the range of + gets subtracted to form the effective soft lifetime. + + By default CHILD_SA rekeying is scheduled every hour, minus + . + ''; + + life_time = mkOptionalDurationParam '' + Maximum lifetime before CHILD_SA gets closed. Usually this hard lifetime + is never reached, because the CHILD_SA gets rekeyed before. If that fails + for whatever reason, this limit closes the CHILD_SA. The default is 10% + more than the . + ''; + + rand_time = mkOptionalDurationParam '' + Time range from which to choose a random value to subtract from + . The default is the difference between + and . + ''; + + rekey_bytes = mkIntParam 0 '' + Number of bytes processed before initiating CHILD_SA rekeying. CHILD_SA + rekeying refreshes key material, optionally using a Diffie-Hellman + exchange if a group is specified in the proposal. + + To avoid rekey collisions initiated by both ends simultaneously, a value + in the range of gets subtracted to form the + effective soft volume limit. + + Volume based CHILD_SA rekeying is disabled by default. + ''; + + life_bytes = mkOptionalIntParam '' + Maximum bytes processed before CHILD_SA gets closed. Usually this hard + volume limit is never reached, because the CHILD_SA gets rekeyed + before. If that fails for whatever reason, this limit closes the + CHILD_SA. The default is 10% more than . + ''; + + rand_bytes = mkOptionalIntParam '' + Byte range from which to choose a random value to subtract from + . The default is the difference between + and . + ''; + + rekey_packets = mkIntParam 0 '' + Number of packets processed before initiating CHILD_SA rekeying. CHILD_SA + rekeying refreshes key material, optionally using a Diffie-Hellman + exchange if a group is specified in the proposal. + + To avoid rekey collisions initiated by both ends simultaneously, a value + in the range of gets subtracted to form + the effective soft packet count limit. + + Packet count based CHILD_SA rekeying is disabled by default. + ''; + + life_packets = mkOptionalIntParam '' + Maximum number of packets processed before CHILD_SA gets closed. Usually + this hard packets limit is never reached, because the CHILD_SA gets + rekeyed before. If that fails for whatever reason, this limit closes the + CHILD_SA. + + The default is 10% more than . + ''; + + rand_packets = mkOptionalIntParam '' + Packet range from which to choose a random value to subtract from + . The default is the difference between + and . + ''; + + updown = mkOptionalStrParam '' + Updown script to invoke on CHILD_SA up and down events. + ''; + + hostaccess = mkYesNoParam yes '' + Hostaccess variable to pass to updown script. + ''; + + mode = mkEnumParam [ "tunnel" + "transport" + "transport_proxy" + "beet" + "pass" + "drop" + ] "tunnel" '' + IPsec Mode to establish CHILD_SA with. + + + tunnel negotiates the CHILD_SA in IPsec Tunnel Mode, + + + whereas transport uses IPsec Transport Mode. + + + transport_proxy signifying the special Mobile IPv6 + Transport Proxy Mode. + + + beet is the Bound End to End Tunnel mixture mode, + working with fixed inner addresses without the need to include them in + each packet. + + + Both transport and beet modes are + subject to mode negotiation; tunnel mode is + negotiated if the preferred mode is not available. + + + pass and drop are used to install + shunt policies which explicitly bypass the defined traffic from IPsec + processing or drop it, respectively. + + + ''; + + policies = mkYesNoParam yes '' + Whether to install IPsec policies or not. Disabling this can be useful in + some scenarios e.g. MIPv6, where policies are not managed by the IKE + daemon. Since 5.3.3. + ''; + + policies_fwd_out = mkYesNoParam no '' + Whether to install outbound FWD IPsec policies or not. Enabling this is + required in case there is a drop policy that would match and block + forwarded traffic for this CHILD_SA. Since 5.5.1. + ''; + + dpd_action = mkEnumParam ["clear" "trap" "restart"] "clear" '' + Action to perform for this CHILD_SA on DPD timeout. The default clear + closes the CHILD_SA and does not take further action. trap installs a + trap policy, which will catch matching traffic and tries to re-negotiate + the tunnel on-demand. restart immediately tries to re-negotiate the + CHILD_SA under a fresh IKE_SA. + ''; + + ipcomp = mkYesNoParam no '' + Enable IPComp compression before encryption. If enabled, IKE tries to + negotiate IPComp compression to compress ESP payload data prior to + encryption. + ''; + + inactivity = mkDurationParam "0s" '' + Timeout before closing CHILD_SA after inactivity. If no traffic has been + processed in either direction for the configured timeout, the CHILD_SA + gets closed due to inactivity. The default value of 0 disables inactivity + checks. + ''; + + reqid = mkIntParam 0 '' + Fixed reqid to use for this CHILD_SA. This might be helpful in some + scenarios, but works only if each CHILD_SA configuration is instantiated + not more than once. The default of 0 uses dynamic reqids, allocated + incrementally. + ''; + + priority = mkIntParam 0 '' + Optional fixed priority for IPsec policies. This could be useful to + install high-priority drop policies. The default of 0 uses dynamically + calculated priorities based on the size of the traffic selectors. + ''; + + interface = mkOptionalStrParam '' + Optional interface name to restrict outbound IPsec policies. + ''; + + mark_in = mkStrParam "0/0x00000000" '' + Netfilter mark and mask for input traffic. On Linux, Netfilter may + require marks on each packet to match an SA/policy having that option + set. This allows installing duplicate policies and enables Netfilter + rules to select specific SAs/policies for incoming traffic. Note that + inbound marks are only set on policies, by default, unless + is enabled. The special value + %unique sets a unique mark on each CHILD_SA instance, + beyond that the value %unique-dir assigns a different + unique mark for each + + An additional mask may be appended to the mark, separated by + /. The default mask if omitted is + 0xffffffff. + ''; + + mark_in_sa = mkYesNoParam no '' + Whether to set on the inbound SA. By default, + the inbound mark is only set on the inbound policy. The tuple destination + address, protocol and SPI is unique and the mark is not required to find + the correct SA, allowing to mark traffic after decryption instead (where + more specific selectors may be used) to match different policies. Marking + packets before decryption is still possible, even if no mark is set on + the SA. + ''; + + mark_out = mkStrParam "0/0x00000000" '' + Netfilter mark and mask for output traffic. On Linux, Netfilter may + require marks on each packet to match a policy/SA having that option + set. This allows installing duplicate policies and enables Netfilter + rules to select specific policies/SAs for outgoing traffic. The special + value %unique sets a unique mark on each CHILD_SA + instance, beyond that the value %unique-dir assigns a + different unique mark for each CHILD_SA direction (in/out). + + An additional mask may be appended to the mark, separated by + /. The default mask if omitted is + 0xffffffff. + ''; + + tfc_padding = mkParamOfType (with lib.types; either int (enum ["mtu"])) 0 '' + Pads ESP packets with additional data to have a consistent ESP packet + size for improved Traffic Flow Confidentiality. The padding defines the + minimum size of all ESP packets sent. The default value of + 0 disables TFC padding, the special value + mtu adds TFC padding to create a packet size equal to + the Path Maximum Transfer Unit. + ''; + + replay_window = mkIntParam 32 '' + IPsec replay window to configure for this CHILD_SA. Larger values than + the default of 32 are supported using the Netlink + backend only, a value of 0 disables IPsec replay + protection. + ''; + + hw_offload = mkYesNoParam no '' + Enable hardware offload for this CHILD_SA, if supported by the IPsec + implementation. + ''; + + start_action = mkEnumParam ["none" "trap" "start"] "none" '' + Action to perform after loading the configuration. + + + The default of none loads the connection only, which + then can be manually initiated or used as a responder configuration. + + + The value trap installs a trap policy, which triggers + the tunnel as soon as matching traffic has been detected. + + + The value start initiates the connection actively. + + + When unloading or replacing a CHILD_SA configuration having a + different from none, + the inverse action is performed. Configurations with + start get closed, while such with + trap get uninstalled. + ''; + + close_action = mkEnumParam ["none" "trap" "start"] "none" '' + Action to perform after a CHILD_SA gets closed by the peer. + + + The default of none does not take any action, + + + trap installs a trap policy for the CHILD_SA. + + + start tries to re-create the CHILD_SA. + + + + does not provide any guarantee that the + CHILD_SA is kept alive. It acts on explicit close messages only, but not + on negotiation failures. Use trap policies to reliably re-create failed + CHILD_SAs. + ''; + + } '' + CHILD_SA configuration sub-section. Each connection definition may have + one or more sections in its subsection. The + section name defines the name of the CHILD_SA configuration, which must be + unique within the connection (denoted <child> below). + ''; + } '' + Section defining IKE connection configurations, each in its own subsection + with an arbitrary yet unique name + ''; + + secrets = let + mkEapXauthParams = mkPrefixedAttrsOfParams { + secret = mkOptionalStrParam '' + Value of the EAP/XAuth secret. It may either be an ASCII string, a hex + encoded string if it has a 0x prefix or a Base64 encoded string if it + has a 0s prefix in its value. + ''; + + id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") '' + Identity the EAP/XAuth secret belongs to. Multiple unique identities may + be specified, each having an id prefix, if a secret + is shared between multiple users. + ''; + + } '' + EAP secret section for a specific secret. Each EAP secret is defined in a + unique section having the eap prefix. EAP secrets are + used for XAuth authentication as well. + ''; + + in { + + eap = mkEapXauthParams; + xauth = mkEapXauthParams; + + ntlm = mkPrefixedAttrsOfParams { + secret = mkOptionalStrParam '' + Value of the NTLM secret, which is the NT Hash of the actual secret, + that is, MD4(UTF-16LE(secret)). The resulting 16-byte value may either + be given as a hex encoded string with a 0x prefix or as a Base64 encoded + string with a 0s prefix. + ''; + + id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") '' + Identity the NTLM secret belongs to. Multiple unique identities may be + specified, each having an id prefix, if a secret is shared between + multiple users. + ''; + } '' + NTLM secret section for a specific secret. Each NTLM secret is defined in + a unique section having the ntlm prefix. NTLM secrets + may only be used for EAP-MSCHAPv2 authentication. + ''; + + ike = mkPrefixedAttrsOfParams { + secret = mkOptionalStrParam '' + Value of the IKE preshared secret. It may either be an ASCII string, a + hex encoded string if it has a 0x prefix or a Base64 encoded string if + it has a 0s prefix in its value. + ''; + + id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") '' + IKE identity the IKE preshared secret belongs to. Multiple unique + identities may be specified, each having an id + prefix, if a secret is shared between multiple peers. + ''; + } '' + IKE preshared secret section for a specific secret. Each IKE PSK is + defined in a unique section having the ike prefix. + ''; + + private = mkPrefixedAttrsOfParams { + file = mkOptionalStrParam '' + File name in the private folder for which this passphrase should be used. + ''; + + secret = mkOptionalStrParam '' + Value of decryption passphrase for private key. + ''; + } '' + Private key decryption passphrase for a key in the + private folder. + ''; + + rsa = mkPrefixedAttrsOfParams { + file = mkOptionalStrParam '' + File name in the rsa folder for which this passphrase + should be used. + ''; + secret = mkOptionalStrParam '' + Value of decryption passphrase for RSA key. + ''; + } '' + Private key decryption passphrase for a key in the rsa + folder. + ''; + + ecdsa = mkPrefixedAttrsOfParams { + file = mkOptionalStrParam '' + File name in the ecdsa folder for which this + passphrase should be used. + ''; + secret = mkOptionalStrParam '' + Value of decryption passphrase for ECDSA key. + ''; + } '' + Private key decryption passphrase for a key in the + ecdsa folder. + ''; + + pkcs8 = mkPrefixedAttrsOfParams { + file = mkOptionalStrParam '' + File name in the pkcs8 folder for which this + passphrase should be used. + ''; + secret = mkOptionalStrParam '' + Value of decryption passphrase for PKCS#8 key. + ''; + } '' + Private key decryption passphrase for a key in the + pkcs8 folder. + ''; + + pkcs12 = mkPrefixedAttrsOfParams { + file = mkOptionalStrParam '' + File name in the pkcs12 folder for which this + passphrase should be used. + ''; + secret = mkOptionalStrParam '' + Value of decryption passphrase for PKCS#12 container. + ''; + } '' + PKCS#12 decryption passphrase for a container in the + pkcs12 folder. + ''; + + token = mkPrefixedAttrsOfParams { + handle = mkOptionalHexParam '' + Hex-encoded CKA_ID or handle of the private key on the token or TPM, + respectively. + ''; + + slot = mkOptionalIntParam '' + Optional slot number to access the token. + ''; + + module = mkOptionalStrParam '' + Optional PKCS#11 module name to access the token. + ''; + + pin = mkOptionalStrParam '' + Optional PIN required to access the key on the token. If none is + provided the user is prompted during an interactive + --load-creds call. + ''; + } ''Definition for a private key that's stored on a token/smartcard/TPM.''; + + }; + + pools = mkAttrsOfParams { + addrs = mkOptionalStrParam '' + Subnet or range defining addresses allocated in pool. Accepts a single + CIDR subnet defining the pool to allocate addresses from or an address + range (<from>-<to>). Pools must be unique and non-overlapping. + ''; + + dns = mkCommaSepListParam [] "Address or CIDR subnets"; + nbns = mkCommaSepListParam [] "Address or CIDR subnets"; + dhcp = mkCommaSepListParam [] "Address or CIDR subnets"; + netmask = mkCommaSepListParam [] "Address or CIDR subnets"; + server = mkCommaSepListParam [] "Address or CIDR subnets"; + subnet = mkCommaSepListParam [] "Address or CIDR subnets"; + split_include = mkCommaSepListParam [] "Address or CIDR subnets"; + split_exclude = mkCommaSepListParam [] "Address or CIDR subnets"; + } '' + Section defining named pools. Named pools may be referenced by connections + with the pools option to assign virtual IPs and other configuration + attributes. Each pool must have a unique name (denoted <name> below). + ''; +} diff --git a/nixos/release.nix b/nixos/release.nix index 7a12baba5c1..5d3c3de08da 100644 --- a/nixos/release.nix +++ b/nixos/release.nix @@ -384,6 +384,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.systemd = callTest tests/systemd.nix {}; tests.switchTest = callTest tests/switch-test.nix {}; diff --git a/nixos/tests/strongswan-swanctl.nix b/nixos/tests/strongswan-swanctl.nix new file mode 100644 index 00000000000..021743021b4 --- /dev/null +++ b/nixos/tests/strongswan-swanctl.nix @@ -0,0 +1,148 @@ +# 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 + allowESP = "iptables --insert INPUT --protocol ESP --jump ACCEPT"; + + # Shared VPN settings: + vlan0 = "192.168.0.0/24"; + carolIp = "192.168.1.2"; + moonIp = "192.168.1.3"; + 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 = "192.168.0.3"; + }; + }; + + moon = {pkgs, config, nodes, ...} : + let 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 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"); + ''; +})