222 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			222 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
{ config, lib, pkgs, ... }:
 | 
						|
 | 
						|
with lib;
 | 
						|
 | 
						|
let
 | 
						|
 | 
						|
  cfg = config.services.stunnel;
 | 
						|
  yesNo = val: if val then "yes" else "no";
 | 
						|
 | 
						|
  verifyChainPathAssert = n: c: {
 | 
						|
    assertion = c.verifyHostname == null || (c.verifyChain || c.verifyPeer);
 | 
						|
    message =  "stunnel: \"${n}\" client configuration - hostname verification " +
 | 
						|
      "is not possible without either verifyChain or verifyPeer enabled";
 | 
						|
  };
 | 
						|
 | 
						|
  serverConfig = {
 | 
						|
    options = {
 | 
						|
      accept = mkOption {
 | 
						|
        type = types.int;
 | 
						|
        description = "On which port stunnel should listen for incoming TLS connections.";
 | 
						|
      };
 | 
						|
 | 
						|
      connect = mkOption {
 | 
						|
        type = types.int;
 | 
						|
        description = "To which port the decrypted connection should be forwarded.";
 | 
						|
      };
 | 
						|
 | 
						|
      cert = mkOption {
 | 
						|
        type = types.path;
 | 
						|
        description = "File containing both the private and public keys.";
 | 
						|
      };
 | 
						|
    };
 | 
						|
  };
 | 
						|
 | 
						|
  clientConfig = {
 | 
						|
    options = {
 | 
						|
      accept = mkOption {
 | 
						|
        type = types.string;
 | 
						|
        description = "IP:Port on which connections should be accepted.";
 | 
						|
      };
 | 
						|
 | 
						|
      connect = mkOption {
 | 
						|
        type = types.string;
 | 
						|
        description = "IP:Port destination to connect to.";
 | 
						|
      };
 | 
						|
 | 
						|
      verifyChain = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        default = true;
 | 
						|
        description = "Check if the provided certificate has a valid certificate chain (against CAPath).";
 | 
						|
      };
 | 
						|
 | 
						|
      verifyPeer = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        default = false;
 | 
						|
        description = "Check if the provided certificate is contained in CAPath.";
 | 
						|
      };
 | 
						|
 | 
						|
      CAPath = mkOption {
 | 
						|
        type = types.path;
 | 
						|
        default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
 | 
						|
        description = "Path to a file containing certificates to validate against.";
 | 
						|
      };
 | 
						|
 | 
						|
      verifyHostname = mkOption {
 | 
						|
        type = with types; nullOr string;
 | 
						|
        default = null;
 | 
						|
        description = "If set, stunnel checks if the provided certificate is valid for the given hostname.";
 | 
						|
      };
 | 
						|
    };
 | 
						|
  };
 | 
						|
 | 
						|
 | 
						|
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 string;
 | 
						|
        default = "nobody";
 | 
						|
        description = "The user under which stunnel runs.";
 | 
						|
      };
 | 
						|
 | 
						|
      group = mkOption {
 | 
						|
        type = with types; nullOr string;
 | 
						|
        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 configuations.";
 | 
						|
        type = with types; attrsOf (submodule serverConfig);
 | 
						|
        example = {
 | 
						|
          fancyWebserver = {
 | 
						|
            enable = true;
 | 
						|
            accept = 443;
 | 
						|
            connect = 8080;
 | 
						|
            cert = "/path/to/pem/file";
 | 
						|
          };
 | 
						|
        };
 | 
						|
        default = { };
 | 
						|
      };
 | 
						|
 | 
						|
      clients = mkOption {
 | 
						|
        description = "Define the client configurations.";
 | 
						|
        type = with types; attrsOf (submodule clientConfig);
 | 
						|
        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)
 | 
						|
    ];
 | 
						|
 | 
						|
    environment.systemPackages = [ pkgs.stunnel ];
 | 
						|
 | 
						|
    environment.etc."stunnel.cfg".text = ''
 | 
						|
      ${ if cfg.user != null then "setuid = ${cfg.user}" else "" }
 | 
						|
      ${ if cfg.group != null then "setgid = ${cfg.group}" else "" }
 | 
						|
 | 
						|
      debug = ${cfg.logLevel}
 | 
						|
 | 
						|
      ${ optionalString cfg.fipsMode "fips = yes" }
 | 
						|
      ${ optionalString cfg.enableInsecureSSLv3 "options = -NO_SSLv3" }
 | 
						|
 | 
						|
      ; ----- SERVER CONFIGURATIONS -----
 | 
						|
      ${ lib.concatStringsSep "\n"
 | 
						|
           (lib.mapAttrsToList
 | 
						|
             (n: v: ''
 | 
						|
               [${n}]
 | 
						|
               accept = ${toString v.accept}
 | 
						|
               connect = ${toString v.connect}
 | 
						|
               cert = ${v.cert}
 | 
						|
 | 
						|
             '')
 | 
						|
           cfg.servers)
 | 
						|
      }
 | 
						|
 | 
						|
      ; ----- CLIENT CONFIGURATIONS -----
 | 
						|
      ${ lib.concatStringsSep "\n"
 | 
						|
           (lib.mapAttrsToList
 | 
						|
             (n: v: ''
 | 
						|
               [${n}]
 | 
						|
               client = yes
 | 
						|
               accept = ${v.accept}
 | 
						|
               connect = ${v.connect}
 | 
						|
               verifyChain = ${yesNo v.verifyChain}
 | 
						|
               verifyPeer = ${yesNo v.verifyPeer}
 | 
						|
               ${optionalString (v.CAPath != null) "CApath = ${v.CAPath}"}
 | 
						|
               ${optionalString (v.verifyHostname != null) "checkHost = ${v.verifyHostname}"}
 | 
						|
               OCSPaia = yes
 | 
						|
 | 
						|
             '')
 | 
						|
           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";
 | 
						|
      };
 | 
						|
    };
 | 
						|
 | 
						|
  };
 | 
						|
 | 
						|
}
 |