diff --git a/nixos/modules/services/networking/privoxy.nix b/nixos/modules/services/networking/privoxy.nix
index 7caae3282032..8ab9a6171fc7 100644
--- a/nixos/modules/services/networking/privoxy.nix
+++ b/nixos/modules/services/networking/privoxy.nix
@@ -4,26 +4,46 @@ with lib;
let
- inherit (pkgs) privoxy;
-
cfg = config.services.privoxy;
- confFile = pkgs.writeText "privoxy.conf" (''
- user-manual ${privoxy}/share/doc/privoxy/user-manual
- confdir ${privoxy}/etc/
- listen-address ${cfg.listenAddress}
- enable-edit-actions ${if (cfg.enableEditActions == true) then "1" else "0"}
- ${concatMapStrings (f: "actionsfile ${f}\n") cfg.actionsFiles}
- ${concatMapStrings (f: "filterfile ${f}\n") cfg.filterFiles}
- '' + optionalString cfg.enableTor ''
- forward-socks5t / 127.0.0.1:9063 .
- toggle 1
- enable-remote-toggle 0
- enable-edit-actions 0
- enable-remote-http-toggle 0
- '' + ''
- ${cfg.extraConfig}
- '');
+ serialise = name: val:
+ if isList val then concatMapStrings (serialise name) val
+ else if isBool val then serialise name (if val then "1" else "0")
+ else "${name} ${toString val}\n";
+
+ configType = with types;
+ let atom = oneOf [ int bool string path ];
+ in attrsOf (either atom (listOf atom))
+ // { description = ''
+ privoxy configuration type. The format consists of an attribute
+ set of settings. Each setting can be either a value (integer, string,
+ boolean or path) or a list of such values.
+ '';
+ };
+
+ ageType = types.str // {
+ check = x:
+ isString x &&
+ (builtins.match "([0-9]+([smhdw]|min|ms|us)*)+" x != null);
+ description = "tmpfiles.d(5) age format";
+ };
+
+ configFile = pkgs.writeText "privoxy.conf"
+ (concatStrings (
+ # Relative paths in some options are relative to confdir. Privoxy seems
+ # to parse the options in order of appearance, so this must come first.
+ # Nix however doesn't preserve the order in attrsets, so we have to
+ # hardcode confdir here.
+ [ "confdir ${pkgs.privoxy}/etc\n" ]
+ ++ mapAttrsToList serialise cfg.settings
+ ));
+
+ inspectAction = pkgs.writeText "inspect-all-https.action"
+ ''
+ # Enable HTTPS inspection for all requests
+ {+https-inspection}
+ /
+ '';
in
@@ -31,70 +51,130 @@ in
###### interface
- options = {
+ options.services.privoxy = {
- services.privoxy = {
+ enable = mkEnableOption "Privoxy, non-caching filtering proxy";
- enable = mkOption {
- type = types.bool;
- default = false;
- description = ''
- Whether to enable the Privoxy non-caching filtering proxy.
- '';
- };
-
- listenAddress = mkOption {
- type = types.str;
- default = "127.0.0.1:8118";
- description = ''
- Address the proxy server is listening to.
- '';
- };
-
- actionsFiles = mkOption {
- type = types.listOf types.str;
- example = [ "match-all.action" "default.action" "/etc/privoxy/user.action" ];
- default = [ "match-all.action" "default.action" ];
- description = ''
- List of paths to Privoxy action files.
- These paths may either be absolute or relative to the privoxy configuration directory.
- '';
- };
-
- filterFiles = mkOption {
- type = types.listOf types.str;
- example = [ "default.filter" "/etc/privoxy/user.filter" ];
- default = [ "default.filter" ];
- description = ''
- List of paths to Privoxy filter files.
- These paths may either be absolute or relative to the privoxy configuration directory.
- '';
- };
-
- enableEditActions = mkOption {
- type = types.bool;
- default = false;
- description = ''
- Whether or not the web-based actions file editor may be used.
- '';
- };
-
- enableTor = mkOption {
- type = types.bool;
- default = false;
- description = ''
- Whether to configure Privoxy to use Tor's faster SOCKS port,
- suitable for HTTP.
- '';
- };
-
- extraConfig = mkOption {
- type = types.lines;
- default = "" ;
- description = ''
- Extra configuration. Contents will be added verbatim to the configuration file.
- '';
+ enableTor = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to configure Privoxy to use Tor's faster SOCKS port,
+ suitable for HTTP.
+ '';
+ };
+
+ inspectHttps = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to configure Privoxy to inspect HTTPS requests, meaning all
+ encrypted traffic will be filtered as well. This works by decrypting
+ and re-encrypting the requests using a per-domain generated certificate.
+
+ To issue per-domain certificates, Privoxy must be provided with a CA
+ certificate, using the ca-cert-file,
+ ca-key-file settings.
+
+
+ The CA certificate must also be added to the system trust roots,
+ otherwise browsers will reject all Privoxy certificates as invalid.
+ You can do so by using the option
+ .
+
+ '';
+ };
+
+ certsLifetime = mkOption {
+ type = ageType;
+ default = "10d";
+ example = "12h";
+ description = ''
+ If inspectHttps is enabled, the time generated HTTPS
+ certificates will be stored in a temporary directory for reuse. Once
+ the lifetime has expired the directory will cleared and the certificate
+ will have to be generated again, on-demand.
+
+ Depending on the traffic, you may want to reduce the lifetime to limit
+ the disk usage, since Privoxy itself never deletes the certificates.
+
+ The format is that of the tmpfiles.d(5)
+ Age parameter.
+ '';
+ };
+
+ userActions = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Actions to be included in a user.action file. This
+ will have a higher priority and can be used to override all other
+ actions.
+ '';
+ };
+
+ userFilters = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Filters to be included in a user.filter file. This
+ will have a higher priority and can be used to override all other
+ filters definitions.
+ '';
+ };
+
+ settings = mkOption {
+ type = types.submodule {
+ freeformType = configType;
+
+ options.listen-address = mkOption {
+ type = types.str;
+ default = "127.0.0.1:8118";
+ description = "Pair of address:port the proxy server is listening to.";
+ };
+
+ options.enable-edit-actions = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether the web-based actions file editor may be used.";
+ };
+
+ options.actionsfile = mkOption {
+ type = types.listOf types.str;
+ # This must come after all other entries, in order to override the
+ # other actions/filters installed by Privoxy or the user.
+ apply = x: x ++ optional (cfg.userActions != "")
+ (toString (pkgs.writeText "user.actions" cfg.userActions));
+ default = [ "match-all.action" "default.action" ];
+ description = ''
+ List of paths to Privoxy action files. These paths may either be
+ absolute or relative to the privoxy configuration directory.
+ '';
+ };
+
+ options.filterfile = mkOption {
+ type = types.listOf types.str;
+ default = [ "default.filter" ];
+ apply = x: x ++ optional (cfg.userFilters != "")
+ (toString (pkgs.writeText "user.filter" cfg.userFilters));
+ description = ''
+ List of paths to Privoxy filter files. These paths may either be
+ absolute or relative to the privoxy configuration directory.
+ '';
+ };
};
+ default = {};
+ example = literalExample ''
+ { listen-address = "[::]:8118"; # listen on IPv6 only
+ forward-socks5 = ".onion localhost:9050 ."; # forward .onion requests to Tor
+ }
+ '';
+ description = ''
+ This option is mapped to the main Privoxy configuration file.
+ Check out the Privoxy user manual at
+
+ for available settings and documentation.
+ '';
};
};
@@ -104,23 +184,34 @@ in
config = mkIf cfg.enable {
users.users.privoxy = {
+ description = "Privoxy daemon user";
isSystemUser = true;
- home = "/var/empty";
group = "privoxy";
};
users.groups.privoxy = {};
+ systemd.tmpfiles.rules = with cfg.settings; [
+ "d ${certificate-directory} 0770 privoxy privoxy ${cfg.certsLifetime}"
+ ];
+
systemd.services.privoxy = {
description = "Filtering web proxy";
after = [ "network.target" "nss-lookup.target" ];
wantedBy = [ "multi-user.target" ];
- serviceConfig.ExecStart = "${privoxy}/bin/privoxy --no-daemon --user privoxy ${confFile}";
-
- serviceConfig.PrivateDevices = true;
- serviceConfig.PrivateTmp = true;
- serviceConfig.ProtectHome = true;
- serviceConfig.ProtectSystem = "full";
+ serviceConfig = {
+ User = "privoxy";
+ Group = "privoxy";
+ ExecStart = "${pkgs.privoxy}/bin/privoxy --no-daemon ${configFile}";
+ PrivateDevices = true;
+ PrivateTmp = true;
+ ProtectHome = true;
+ ProtectSystem = "full";
+ };
+ unitConfig = mkIf cfg.inspectHttps {
+ ConditionPathExists = with cfg.settings;
+ [ ca-cert-file ca-key-file ];
+ };
};
services.tor.settings.SOCKSPort = mkIf cfg.enableTor [
@@ -128,8 +219,46 @@ in
{ addr = "127.0.0.1"; port = 9063; IsolateDestAddr = false; }
];
+ services.privoxy.settings = {
+ user-manual = "${pkgs.privoxy}/share/doc/privoxy/user-manual";
+ filterfile = [ "default.filter" ];
+ actionsfile =
+ [ "match-all.action"
+ "default.action"
+ ] ++ optional cfg.inspectHttps (toString inspectAction);
+ } // (optionalAttrs cfg.enableTor {
+ forward-socks5 = "127.0.0.1:9063 .";
+ toggle = true;
+ enable-remote-toggle = false;
+ enable-edit-actions = false;
+ enable-remote-http-toggle = false;
+ }) // (optionalAttrs cfg.inspectHttps {
+ # This allows setting absolute key/crt paths
+ ca-directory = "/var/empty";
+ certificate-directory = "/run/privoxy/certs";
+ trusted-cas-file = "/etc/ssl/certs/ca-certificates.crt";
+ });
+
};
+ imports =
+ let
+ top = x: [ "services" "privoxy" x ];
+ setting = x: [ "services" "privoxy" "settings" x ];
+ in
+ [ (mkRenamedOptionModule (top "enableEditActions") (setting "enable-edit-actions"))
+ (mkRenamedOptionModule (top "listenAddress") (setting "listen-address"))
+ (mkRenamedOptionModule (top "actionsFiles") (setting "actionsfile"))
+ (mkRenamedOptionModule (top "filterFiles") (setting "filterfile"))
+ (mkRemovedOptionModule (top "extraConfig")
+ ''
+ Use services.privoxy.settings instead.
+ This is part of the general move to use structured settings instead of raw
+ text for config as introduced by RFC0042:
+ https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md
+ '')
+ ];
+
meta.maintainers = with lib.maintainers; [ rnhmjoj ];
}