parent
b571728081
commit
08eb63b5ac
@ -0,0 +1,422 @@ |
||||
{ config, lib, pkgs, ... }: |
||||
with lib; |
||||
let |
||||
pkg = pkgs._3proxy; |
||||
cfg = config.services._3proxy; |
||||
optionalList = list: if list == [ ] then "*" else concatMapStringsSep "," toString list; |
||||
in { |
||||
options.services._3proxy = { |
||||
enable = mkEnableOption "3proxy"; |
||||
confFile = mkOption { |
||||
type = types.path; |
||||
example = "/var/lib/3proxy/3proxy.conf"; |
||||
description = '' |
||||
Ignore all other 3proxy options and load configuration from this file. |
||||
''; |
||||
}; |
||||
usersFile = mkOption { |
||||
type = types.nullOr types.path; |
||||
default = null; |
||||
example = "/var/lib/3proxy/3proxy.passwd"; |
||||
description = '' |
||||
Load users and passwords from this file. |
||||
|
||||
Example users file with plain-text passwords: |
||||
|
||||
<para> |
||||
test1:CL:password1 |
||||
test2:CL:password2 |
||||
</para> |
||||
|
||||
Example users file with md5-crypted passwords: |
||||
|
||||
<para> |
||||
"test1:CR:$1$tFkisVd2$1GA8JXkRmTXdLDytM/i3a1" |
||||
"test2:CR:$1$rkpibm5J$Aq1.9VtYAn0JrqZ8M.1ME." |
||||
</para> |
||||
|
||||
You can generate md5-crypted passwords via https://unix4lyfe.org/crypt/ |
||||
Consult <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/How-To-(incomplete)#USERS">documentation</link> for more information. |
||||
''; |
||||
}; |
||||
services = mkOption { |
||||
type = types.listOf (types.submodule { |
||||
options = { |
||||
type = mkOption { |
||||
type = types.enum [ |
||||
"proxy" |
||||
"socks" |
||||
"pop3p" |
||||
"ftppr" |
||||
"admin" |
||||
"dnspr" |
||||
"tcppm" |
||||
"udppm" |
||||
]; |
||||
example = "proxy"; |
||||
description = '' |
||||
Service type. The following values are valid: |
||||
|
||||
<itemizedlist> |
||||
<listitem><para> |
||||
<literal>"proxy"</literal>: HTTP/HTTPS proxy (default port 3128). |
||||
</para></listitem> |
||||
<listitem><para> |
||||
<literal>"socks"</literal>: SOCKS 4/4.5/5 proxy (default port 1080). |
||||
</para></listitem> |
||||
<listitem><para> |
||||
<literal>"pop3p"</literal>: POP3 proxy (default port 110). |
||||
</para></listitem> |
||||
<listitem><para> |
||||
<literal>"ftppr"</literal>: FTP proxy (default port 21). |
||||
</para></listitem> |
||||
<listitem><para> |
||||
<literal>"admin"</literal>: Web interface (default port 80). |
||||
</para></listitem> |
||||
<listitem><para> |
||||
<literal>"dnspr"</literal>: Caching DNS proxy (default port 53). |
||||
</para></listitem> |
||||
<listitem><para> |
||||
<literal>"tcppm"</literal>: TCP portmapper. |
||||
</para></listitem> |
||||
<listitem><para> |
||||
<literal>"udppm"</literal>: UDP portmapper. |
||||
</para></listitem> |
||||
</itemizedlist> |
||||
''; |
||||
}; |
||||
bindAddress = mkOption { |
||||
type = types.str; |
||||
default = "[::]"; |
||||
example = "127.0.0.1"; |
||||
description = '' |
||||
Address used for service. |
||||
''; |
||||
}; |
||||
bindPort = mkOption { |
||||
type = types.nullOr types.int; |
||||
default = null; |
||||
example = 3128; |
||||
description = '' |
||||
Override default port used for service. |
||||
''; |
||||
}; |
||||
maxConnections = mkOption { |
||||
type = types.int; |
||||
default = 100; |
||||
example = 1000; |
||||
description = '' |
||||
Maximum number of simulationeous connections to this service. |
||||
''; |
||||
}; |
||||
auth = mkOption { |
||||
type = types.listOf (types.enum [ "none" "iponly" "strong" ]); |
||||
example = [ "iponly" "strong" ]; |
||||
description = '' |
||||
Authentication type. The following values are valid: |
||||
|
||||
<itemizedlist> |
||||
<listitem><para> |
||||
<literal>"none"</literal>: disables both authentication and authorization. You can not use ACLs. |
||||
</para></listitem> |
||||
<listitem><para> |
||||
<literal>"iponly"</literal>: specifies no authentication. ACLs authorization is used. |
||||
</para></listitem> |
||||
<listitem><para> |
||||
<literal>"strong"</literal>: authentication by username/password. If user is not registered his access is denied regardless of ACLs. |
||||
</para></listitem> |
||||
</itemizedlist> |
||||
|
||||
Double authentication is possible, e.g. |
||||
<para> |
||||
{ |
||||
auth = [ "iponly" "strong" ]; |
||||
acl = [ |
||||
{ |
||||
rule = "allow"; |
||||
targets = [ "192.168.0.0/16" ]; |
||||
} |
||||
{ |
||||
rule = "allow" |
||||
users = [ "user1" "user2" ]; |
||||
} |
||||
]; |
||||
} |
||||
</para> |
||||
In this example strong username authentication is not required to access 192.168.0.0/16. |
||||
''; |
||||
}; |
||||
acl = mkOption { |
||||
type = types.listOf (types.submodule { |
||||
options = { |
||||
rule = mkOption { |
||||
type = types.enum [ "allow" "deny" ]; |
||||
example = "allow"; |
||||
description = '' |
||||
ACL rule. The following values are valid: |
||||
|
||||
<itemizedlist> |
||||
<listitem><para> |
||||
<literal>"allow"</literal>: connections allowed. |
||||
</para></listitem> |
||||
<listitem><para> |
||||
<literal>"deny"</literal>: connections not allowed. |
||||
</para></listitem> |
||||
</itemizedlist> |
||||
''; |
||||
}; |
||||
users = mkOption { |
||||
type = types.listOf types.str; |
||||
default = [ ]; |
||||
example = [ "user1" "user2" "user3" ]; |
||||
description = '' |
||||
List of users, use empty list for any. |
||||
''; |
||||
}; |
||||
sources = mkOption { |
||||
type = types.listOf types.str; |
||||
default = [ ]; |
||||
example = [ "127.0.0.1" "192.168.1.0/24" ]; |
||||
description = '' |
||||
List of source IP range, use empty list for any. |
||||
''; |
||||
}; |
||||
targets = mkOption { |
||||
type = types.listOf types.str; |
||||
default = [ ]; |
||||
example = [ "127.0.0.1" "192.168.1.0/24" ]; |
||||
description = '' |
||||
List of target IP ranges, use empty list for any. |
||||
May also contain host names instead of addresses. |
||||
It's possible to use wildmask in the begginning and in the the end of hostname, e.g. *badsite.com or *badcontent*. |
||||
Hostname is only checked if hostname presents in request. |
||||
''; |
||||
}; |
||||
targetPorts = mkOption { |
||||
type = types.listOf types.int; |
||||
default = [ ]; |
||||
example = [ 80 443 ]; |
||||
description = '' |
||||
List of target ports, use empty list for any. |
||||
''; |
||||
}; |
||||
}; |
||||
}); |
||||
default = [ ]; |
||||
example = literalExample '' |
||||
[ |
||||
{ |
||||
rule = "allow"; |
||||
users = [ "user1" ]; |
||||
} |
||||
{ |
||||
rule = "allow"; |
||||
sources = [ "192.168.1.0/24" ]; |
||||
} |
||||
{ |
||||
rule = "deny"; |
||||
} |
||||
] |
||||
''; |
||||
description = '' |
||||
Use this option to limit user access to resources. |
||||
''; |
||||
}; |
||||
extraArguments = mkOption { |
||||
type = types.nullOr types.str; |
||||
default = null; |
||||
example = "-46"; |
||||
description = '' |
||||
Extra arguments for service. |
||||
Consult "Options" section in <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg">documentation</link> for available arguments. |
||||
''; |
||||
}; |
||||
extraConfig = mkOption { |
||||
type = types.nullOr types.lines; |
||||
default = null; |
||||
description = '' |
||||
Extra configuration for service. Use this to configure things like bandwidth limiter or ACL-based redirection. |
||||
Consult <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg">documentation</link> for available options. |
||||
''; |
||||
}; |
||||
}; |
||||
}); |
||||
default = [ ]; |
||||
example = literalExample '' |
||||
[ |
||||
{ |
||||
type = "proxy"; |
||||
bindAddress = "192.168.1.24"; |
||||
bindPort = 3128; |
||||
auth = [ "none" ]; |
||||
} |
||||
{ |
||||
type = "proxy"; |
||||
bindAddress = "10.10.1.20"; |
||||
bindPort = 3128; |
||||
auth = [ "iponly" ]; |
||||
} |
||||
{ |
||||
type = "socks"; |
||||
bindAddress = "172.17.0.1"; |
||||
bindPort = 1080; |
||||
auth = [ "strong" ]; |
||||
} |
||||
] |
||||
''; |
||||
description = '' |
||||
Use this option to define 3proxy services. |
||||
''; |
||||
}; |
||||
denyPrivate = mkOption { |
||||
type = types.bool; |
||||
default = true; |
||||
description = '' |
||||
Whether to deny access to private IP ranges including loopback. |
||||
''; |
||||
}; |
||||
privateRanges = mkOption { |
||||
type = types.listOf types.str; |
||||
default = [ |
||||
"0.0.0.0/8" |
||||
"127.0.0.0/8" |
||||
"10.0.0.0/8" |
||||
"100.64.0.0/10" |
||||
"172.16.0.0/12" |
||||
"192.168.0.0/16" |
||||
"::" |
||||
"::1" |
||||
"fc00::/7" |
||||
]; |
||||
example = [ |
||||
"0.0.0.0/8" |
||||
"127.0.0.0/8" |
||||
"10.0.0.0/8" |
||||
"100.64.0.0/10" |
||||
"172.16.0.0/12" |
||||
"192.168.0.0/16" |
||||
"::" |
||||
"::1" |
||||
"fc00::/7" |
||||
]; |
||||
description = '' |
||||
What IP ranges to deny access when denyPrivate is set tu true. |
||||
''; |
||||
}; |
||||
resolution = mkOption { |
||||
type = types.submodule { |
||||
options = { |
||||
nserver = mkOption { |
||||
type = types.listOf types.str; |
||||
default = [ ]; |
||||
example = [ "127.0.0.53" "192.168.1.3:5353/tcp" ]; |
||||
description = '' |
||||
List of nameservers to use. |
||||
|
||||
Up to 5 nservers may be specified. If no nserver is configured, |
||||
default system name resolution functions are used. |
||||
''; |
||||
}; |
||||
nscache = mkOption { |
||||
type = types.int; |
||||
default = 65535; |
||||
example = 65535; |
||||
description = "Set name cache size for IPv4."; |
||||
}; |
||||
nscache6 = mkOption { |
||||
type = types.int; |
||||
default = 65535; |
||||
example = 65535; |
||||
description = "Set name cache size for IPv6."; |
||||
}; |
||||
nsrecord = mkOption { |
||||
type = types.attrsOf types.str; |
||||
default = { }; |
||||
example = { |
||||
"files.local" = "192.168.1.12"; |
||||
"site.local" = "192.168.1.43"; |
||||
}; |
||||
description = "Adds static nsrecords."; |
||||
}; |
||||
}; |
||||
}; |
||||
default = { }; |
||||
description = '' |
||||
Use this option to configure name resolution and DNS caching. |
||||
''; |
||||
}; |
||||
extraConfig = mkOption { |
||||
type = types.nullOr types.lines; |
||||
default = null; |
||||
description = '' |
||||
Extra configuration, appended to the 3proxy configuration file. |
||||
Consult <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg">documentation</link> for available options. |
||||
''; |
||||
}; |
||||
}; |
||||
|
||||
config = mkIf cfg.enable { |
||||
services._3proxy.confFile = mkDefault (pkgs.writeText "3proxy.conf" '' |
||||
# log to stdout |
||||
log |
||||
|
||||
${concatMapStringsSep "\n" (x: "nserver " + x) cfg.resolution.nserver} |
||||
|
||||
nscache ${toString cfg.resolution.nscache} |
||||
nscache6 ${toString cfg.resolution.nscache6} |
||||
|
||||
${concatMapStringsSep "\n" (x: "nsrecord " + x) |
||||
(mapAttrsToList (name: value: "${name} ${value}") |
||||
cfg.resolution.nsrecord)} |
||||
|
||||
${optionalString (cfg.usersFile != null) |
||||
''users $"${cfg.usersFile}"'' |
||||
} |
||||
|
||||
${concatMapStringsSep "\n" (service: '' |
||||
auth ${concatStringsSep " " service.auth} |
||||
|
||||
${optionalString (cfg.denyPrivate) |
||||
"deny * * ${optionalList cfg.privateRanges}"} |
||||
|
||||
${concatMapStringsSep "\n" (acl: |
||||
"${acl.rule} ${ |
||||
concatMapStringsSep " " optionalList [ |
||||
acl.users |
||||
acl.sources |
||||
acl.targets |
||||
acl.targetPorts |
||||
] |
||||
}") service.acl} |
||||
|
||||
maxconn ${toString service.maxConnections} |
||||
|
||||
${optionalString (service.extraConfig != null) service.extraConfig} |
||||
|
||||
${service.type} -i${toString service.bindAddress} ${ |
||||
optionalString (service.bindPort != null) |
||||
"-p${toString service.bindPort}" |
||||
} ${ |
||||
optionalString (service.extraArguments != null) service.extraArguments |
||||
} |
||||
|
||||
flush |
||||
'') cfg.services} |
||||
${optionalString (cfg.extraConfig != null) cfg.extraConfig} |
||||
''); |
||||
systemd.services."3proxy" = { |
||||
description = "Tiny free proxy server"; |
||||
documentation = [ "https://github.com/z3APA3A/3proxy/wiki" ]; |
||||
after = [ "network.target" ]; |
||||
wantedBy = [ "multi-user.target" ]; |
||||
serviceConfig = { |
||||
DynamicUser = true; |
||||
StateDirectory = "3proxy"; |
||||
ExecStart = "${pkg}/bin/3proxy ${cfg.confFile}"; |
||||
Restart = "on-failure"; |
||||
}; |
||||
}; |
||||
}; |
||||
|
||||
meta.maintainers = with maintainers; [ misuzu ]; |
||||
} |
@ -0,0 +1,162 @@ |
||||
import ./make-test.nix ({ pkgs, ...} : { |
||||
name = "3proxy"; |
||||
meta = with pkgs.stdenv.lib.maintainers; { |
||||
maintainers = [ misuzu ]; |
||||
}; |
||||
|
||||
nodes = { |
||||
peer0 = { lib, ... }: { |
||||
networking.useDHCP = false; |
||||
networking.interfaces.eth1 = { |
||||
ipv4.addresses = [ |
||||
{ |
||||
address = "192.168.0.1"; |
||||
prefixLength = 24; |
||||
} |
||||
{ |
||||
address = "216.58.211.111"; |
||||
prefixLength = 24; |
||||
} |
||||
]; |
||||
}; |
||||
}; |
||||
|
||||
peer1 = { lib, ... }: { |
||||
networking.useDHCP = false; |
||||
networking.interfaces.eth1 = { |
||||
ipv4.addresses = [ |
||||
{ |
||||
address = "192.168.0.2"; |
||||
prefixLength = 24; |
||||
} |
||||
{ |
||||
address = "216.58.211.112"; |
||||
prefixLength = 24; |
||||
} |
||||
]; |
||||
}; |
||||
# test that binding to [::] is working when ipv6 is disabled |
||||
networking.enableIPv6 = false; |
||||
services._3proxy = { |
||||
enable = true; |
||||
services = [ |
||||
{ |
||||
type = "admin"; |
||||
bindPort = 9999; |
||||
auth = [ "none" ]; |
||||
} |
||||
{ |
||||
type = "proxy"; |
||||
bindPort = 3128; |
||||
auth = [ "none" ]; |
||||
} |
||||
]; |
||||
}; |
||||
networking.firewall.allowedTCPPorts = [ 3128 9999 ]; |
||||
}; |
||||
|
||||
peer2 = { lib, ... }: { |
||||
networking.useDHCP = false; |
||||
networking.interfaces.eth1 = { |
||||
ipv4.addresses = [ |
||||
{ |
||||
address = "192.168.0.3"; |
||||
prefixLength = 24; |
||||
} |
||||
{ |
||||
address = "216.58.211.113"; |
||||
prefixLength = 24; |
||||
} |
||||
]; |
||||
}; |
||||
services._3proxy = { |
||||
enable = true; |
||||
services = [ |
||||
{ |
||||
type = "admin"; |
||||
bindPort = 9999; |
||||
auth = [ "none" ]; |
||||
} |
||||
{ |
||||
type = "proxy"; |
||||
bindPort = 3128; |
||||
auth = [ "iponly" ]; |
||||
acl = [ |
||||
{ |
||||
rule = "allow"; |
||||
} |
||||
]; |
||||
} |
||||
]; |
||||
}; |
||||
networking.firewall.allowedTCPPorts = [ 3128 9999 ]; |
||||
}; |
||||
|
||||
peer3 = { lib, ... }: { |
||||
networking.useDHCP = false; |
||||
networking.interfaces.eth1 = { |
||||
ipv4.addresses = [ |
||||
{ |
||||
address = "192.168.0.4"; |
||||
prefixLength = 24; |
||||
} |
||||
{ |
||||
address = "216.58.211.114"; |
||||
prefixLength = 24; |
||||
} |
||||
]; |
||||
}; |
||||
services._3proxy = { |
||||
enable = true; |
||||
usersFile = pkgs.writeText "3proxy.passwd" '' |
||||
admin:CR:$1$.GUV4Wvk$WnEVQtaqutD9.beO5ar1W/ |
||||
''; |
||||
services = [ |
||||
{ |
||||
type = "admin"; |
||||
bindPort = 9999; |
||||
auth = [ "none" ]; |
||||
} |
||||
{ |
||||
type = "proxy"; |
||||
bindPort = 3128; |
||||
auth = [ "strong" ]; |
||||
acl = [ |
||||
{ |
||||
rule = "allow"; |
||||
} |
||||
]; |
||||
} |
||||
]; |
||||
}; |
||||
networking.firewall.allowedTCPPorts = [ 3128 9999 ]; |
||||
}; |
||||
}; |
||||
|
||||
testScript = '' |
||||
startAll; |
||||
|
||||
$peer1->waitForUnit("3proxy.service"); |
||||
|
||||
# test none auth |
||||
$peer0->succeed("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.2:3128 -S -O /dev/null http://216.58.211.112:9999"); |
||||
$peer0->succeed("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.2:3128 -S -O /dev/null http://192.168.0.2:9999"); |
||||
$peer0->succeed("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.2:3128 -S -O /dev/null http://127.0.0.1:9999"); |
||||
|
||||
$peer2->waitForUnit("3proxy.service"); |
||||
|
||||
# test iponly auth |
||||
$peer0->succeed("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.3:3128 -S -O /dev/null http://216.58.211.113:9999"); |
||||
$peer0->fail("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.3:3128 -S -O /dev/null http://192.168.0.3:9999"); |
||||
$peer0->fail("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.3:3128 -S -O /dev/null http://127.0.0.1:9999"); |
||||
|
||||
$peer3->waitForUnit("3proxy.service"); |
||||
|
||||
# test strong auth |
||||
$peer0->succeed("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://admin:bigsecret\@192.168.0.4:3128 -S -O /dev/null http://216.58.211.114:9999"); |
||||
$peer0->fail("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://admin:bigsecret\@192.168.0.4:3128 -S -O /dev/null http://192.168.0.4:9999"); |
||||
$peer0->fail("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.4:3128 -S -O /dev/null http://216.58.211.114:9999"); |
||||
$peer0->fail("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.4:3128 -S -O /dev/null http://192.168.0.4:9999"); |
||||
$peer0->fail("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.4:3128 -S -O /dev/null http://127.0.0.1:9999"); |
||||
''; |
||||
}) |
Loading…
Reference in new issue