193 lines
5.5 KiB
Nix
193 lines
5.5 KiB
Nix
|
{ config, lib, pkgs, ... }:
|
||
|
|
||
|
with lib;
|
||
|
|
||
|
let
|
||
|
|
||
|
cfg = config.services.stunnel;
|
||
|
yesNo = val: if val then "yes" else "no";
|
||
|
|
||
|
verifyRequiredField = type: field: n: c: {
|
||
|
assertion = hasAttr field c;
|
||
|
message = "stunnel: \"${n}\" ${type} configuration - Field ${field} is required.";
|
||
|
};
|
||
|
|
||
|
verifyChainPathAssert = n: c: {
|
||
|
assertion = (c.verifyHostname or null) == null || (c.verifyChain || c.verifyPeer);
|
||
|
message = "stunnel: \"${n}\" client configuration - hostname verification " +
|
||
|
"is not possible without either verifyChain or verifyPeer enabled";
|
||
|
};
|
||
|
|
||
|
removeNulls = mapAttrs (_: filterAttrs (_: v: v != null));
|
||
|
mkValueString = v:
|
||
|
if v == true then "yes"
|
||
|
else if v == false then "no"
|
||
|
else generators.mkValueStringDefault {} v;
|
||
|
generateConfig = c:
|
||
|
generators.toINI {
|
||
|
mkSectionName = id;
|
||
|
mkKeyValue = k: v: "${k} = ${mkValueString v}";
|
||
|
} (removeNulls c);
|
||
|
|
||
|
in
|
||
|
|
||
|
{
|
||
|
|
||
|
###### interface
|
||
|
|
||
|
options = {
|
||
|
|
||
|
services.stunnel = {
|
||
|
|
||
|
enable = mkOption {
|
||
|
type = types.bool;
|
||
|
default = false;
|
||
|
description = "Whether to enable the stunnel TLS tunneling service.";
|
||
|
};
|
||
|
|
||
|
user = mkOption {
|
||
|
type = with types; nullOr str;
|
||
|
default = "nobody";
|
||
|
description = "The user under which stunnel runs.";
|
||
|
};
|
||
|
|
||
|
group = mkOption {
|
||
|
type = with types; nullOr str;
|
||
|
default = "nogroup";
|
||
|
description = "The group under which stunnel runs.";
|
||
|
};
|
||
|
|
||
|
logLevel = mkOption {
|
||
|
type = types.enum [ "emerg" "alert" "crit" "err" "warning" "notice" "info" "debug" ];
|
||
|
default = "info";
|
||
|
description = "Verbosity of stunnel output.";
|
||
|
};
|
||
|
|
||
|
fipsMode = mkOption {
|
||
|
type = types.bool;
|
||
|
default = false;
|
||
|
description = "Enable FIPS 140-2 mode required for compliance.";
|
||
|
};
|
||
|
|
||
|
enableInsecureSSLv3 = mkOption {
|
||
|
type = types.bool;
|
||
|
default = false;
|
||
|
description = "Enable support for the insecure SSLv3 protocol.";
|
||
|
};
|
||
|
|
||
|
|
||
|
servers = mkOption {
|
||
|
description = ''
|
||
|
Define the server configurations.
|
||
|
|
||
|
See "SERVICE-LEVEL OPTIONS" in {manpage}`stunnel(8)`.
|
||
|
'';
|
||
|
type = with types; attrsOf (attrsOf (nullOr (oneOf [bool int str])));
|
||
|
example = {
|
||
|
fancyWebserver = {
|
||
|
accept = 443;
|
||
|
connect = 8080;
|
||
|
cert = "/path/to/pem/file";
|
||
|
};
|
||
|
};
|
||
|
default = { };
|
||
|
};
|
||
|
|
||
|
clients = mkOption {
|
||
|
description = ''
|
||
|
Define the client configurations.
|
||
|
|
||
|
By default, verifyChain and OCSPaia are enabled and a CAFile is provided from pkgs.cacert.
|
||
|
|
||
|
See "SERVICE-LEVEL OPTIONS" in {manpage}`stunnel(8)`.
|
||
|
'';
|
||
|
type = with types; attrsOf (attrsOf (nullOr (oneOf [bool int str])));
|
||
|
|
||
|
apply = let
|
||
|
applyDefaults = c:
|
||
|
{
|
||
|
CAFile = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
||
|
OCSPaia = true;
|
||
|
verifyChain = true;
|
||
|
} // c;
|
||
|
setCheckHostFromVerifyHostname = c:
|
||
|
# To preserve backward-compatibility with the old NixOS stunnel module
|
||
|
# definition, allow "verifyHostname" as an alias for "checkHost".
|
||
|
c // {
|
||
|
checkHost = c.checkHost or c.verifyHostname or null;
|
||
|
verifyHostname = null; # Not a real stunnel configuration setting
|
||
|
};
|
||
|
forceClient = c: c // { client = true; };
|
||
|
in mapAttrs (_: c: forceClient (setCheckHostFromVerifyHostname (applyDefaults c)));
|
||
|
|
||
|
example = {
|
||
|
foobar = {
|
||
|
accept = "0.0.0.0:8080";
|
||
|
connect = "nixos.org:443";
|
||
|
verifyChain = false;
|
||
|
};
|
||
|
};
|
||
|
default = { };
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
|
||
|
|
||
|
###### implementation
|
||
|
|
||
|
config = mkIf cfg.enable {
|
||
|
|
||
|
assertions = concatLists [
|
||
|
(singleton {
|
||
|
assertion = (length (attrValues cfg.servers) != 0) || ((length (attrValues cfg.clients)) != 0);
|
||
|
message = "stunnel: At least one server- or client-configuration has to be present.";
|
||
|
})
|
||
|
|
||
|
(mapAttrsToList verifyChainPathAssert cfg.clients)
|
||
|
(mapAttrsToList (verifyRequiredField "client" "accept") cfg.clients)
|
||
|
(mapAttrsToList (verifyRequiredField "client" "connect") cfg.clients)
|
||
|
(mapAttrsToList (verifyRequiredField "server" "accept") cfg.servers)
|
||
|
(mapAttrsToList (verifyRequiredField "server" "cert") cfg.servers)
|
||
|
(mapAttrsToList (verifyRequiredField "server" "connect") cfg.servers)
|
||
|
];
|
||
|
|
||
|
environment.systemPackages = [ pkgs.stunnel ];
|
||
|
|
||
|
environment.etc."stunnel.cfg".text = ''
|
||
|
${ optionalString (cfg.user != null) "setuid = ${cfg.user}" }
|
||
|
${ optionalString (cfg.group != null) "setgid = ${cfg.group}" }
|
||
|
|
||
|
debug = ${cfg.logLevel}
|
||
|
|
||
|
${ optionalString cfg.fipsMode "fips = yes" }
|
||
|
${ optionalString cfg.enableInsecureSSLv3 "options = -NO_SSLv3" }
|
||
|
|
||
|
; ----- SERVER CONFIGURATIONS -----
|
||
|
${ generateConfig cfg.servers }
|
||
|
|
||
|
; ----- CLIENT CONFIGURATIONS -----
|
||
|
${ generateConfig cfg.clients }
|
||
|
'';
|
||
|
|
||
|
systemd.services.stunnel = {
|
||
|
description = "stunnel TLS tunneling service";
|
||
|
after = [ "network.target" ];
|
||
|
wants = [ "network.target" ];
|
||
|
wantedBy = [ "multi-user.target" ];
|
||
|
restartTriggers = [ config.environment.etc."stunnel.cfg".source ];
|
||
|
serviceConfig = {
|
||
|
ExecStart = "${pkgs.stunnel}/bin/stunnel ${config.environment.etc."stunnel.cfg".source}";
|
||
|
Type = "forking";
|
||
|
};
|
||
|
};
|
||
|
|
||
|
meta.maintainers = with maintainers; [
|
||
|
# Server side
|
||
|
lschuermann
|
||
|
# Client side
|
||
|
das_j
|
||
|
];
|
||
|
};
|
||
|
|
||
|
}
|