Merge pull request #213510 from RaitoBezarius/nginx-proxyprotocol
nixos/nginx: first-class PROXY protocol support
This commit is contained in:
commit
d74e5f4a18
|
@ -17,3 +17,5 @@
|
|||
## Other Notable Changes {#sec-release-23.11-notable-changes}
|
||||
|
||||
- A new option was added to the virtualisation module that enables specifying explicitly named network interfaces in QEMU VMs. The existing `virtualisation.vlans` is still supported for cases where the name of the network interface is irrelevant.
|
||||
|
||||
- `services.nginx` gained a `defaultListen` option at server-level with support for PROXY protocol listeners, also `proxyProtocol` is now exposed in `services.nginx.virtualHosts.<name>.listen` option. It is now possible to run PROXY listeners and non-PROXY listeners at a server-level, see [#213510](https://github.com/NixOS/nixpkgs/pull/213510/) for more details.
|
||||
|
|
|
@ -309,36 +309,54 @@ let
|
|||
onlySSL = vhost.onlySSL || vhost.enableSSL;
|
||||
hasSSL = onlySSL || vhost.addSSL || vhost.forceSSL;
|
||||
|
||||
# First evaluation of defaultListen based on a set of listen lines.
|
||||
mkDefaultListenVhost = listenLines:
|
||||
# If this vhost has SSL or is a SSL rejection host.
|
||||
# We enable a TLS variant for lines without explicit ssl or ssl = true.
|
||||
optionals (hasSSL || vhost.rejectSSL)
|
||||
(map (listen: { port = cfg.defaultSSLListenPort; ssl = true; } // listen)
|
||||
(filter (listen: !(listen ? ssl) || listen.ssl) listenLines))
|
||||
# If this vhost is supposed to serve HTTP
|
||||
# We provide listen lines for those without explicit ssl or ssl = false.
|
||||
++ optionals (!onlySSL)
|
||||
(map (listen: { port = cfg.defaultHTTPListenPort; ssl = false; } // listen)
|
||||
(filter (listen: !(listen ? ssl) || !listen.ssl) listenLines));
|
||||
|
||||
defaultListen =
|
||||
if vhost.listen != [] then vhost.listen
|
||||
else
|
||||
if cfg.defaultListen != [] then mkDefaultListenVhost
|
||||
# Cleanup nulls which will mess up with //.
|
||||
# TODO: is there a better way to achieve this? i.e. mergeButIgnoreNullPlease?
|
||||
(map (listenLine: filterAttrs (_: v: (v != null)) listenLine) cfg.defaultListen)
|
||||
else
|
||||
let addrs = if vhost.listenAddresses != [] then vhost.listenAddresses else cfg.defaultListenAddresses;
|
||||
in optionals (hasSSL || vhost.rejectSSL) (map (addr: { inherit addr; port = cfg.defaultSSLListenPort; ssl = true; }) addrs)
|
||||
++ optionals (!onlySSL) (map (addr: { inherit addr; port = cfg.defaultHTTPListenPort; ssl = false; }) addrs);
|
||||
in mkDefaultListenVhost (map (addr: { inherit addr; }) addrs);
|
||||
|
||||
|
||||
hostListen =
|
||||
if vhost.forceSSL
|
||||
then filter (x: x.ssl) defaultListen
|
||||
else defaultListen;
|
||||
|
||||
listenString = { addr, port, ssl, extraParameters ? [], ... }:
|
||||
listenString = { addr, port, ssl, proxyProtocol ? false, extraParameters ? [], ... }:
|
||||
# UDP listener for QUIC transport protocol.
|
||||
(optionalString (ssl && vhost.quic) ("
|
||||
listen ${addr}:${toString port} quic "
|
||||
+ optionalString vhost.default "default_server "
|
||||
+ optionalString vhost.reuseport "reuseport "
|
||||
+ optionalString (extraParameters != []) (concatStringsSep " " (
|
||||
let inCompatibleParameters = [ "ssl" "proxy_protocol" "http2" ];
|
||||
+ optionalString (extraParameters != []) (concatStringsSep " "
|
||||
(let inCompatibleParameters = [ "ssl" "proxy_protocol" "http2" ];
|
||||
isCompatibleParameter = param: !(any (p: p == param) inCompatibleParameters);
|
||||
in filter isCompatibleParameter extraParameters))
|
||||
+ ";"))
|
||||
+ "
|
||||
|
||||
listen ${addr}:${toString port} "
|
||||
+ optionalString (ssl && vhost.http2) "http2 "
|
||||
+ optionalString ssl "ssl "
|
||||
+ optionalString vhost.default "default_server "
|
||||
+ optionalString vhost.reuseport "reuseport "
|
||||
+ optionalString proxyProtocol "proxy_protocol "
|
||||
+ optionalString (extraParameters != []) (concatStringsSep " " extraParameters)
|
||||
+ ";";
|
||||
|
||||
|
@ -539,6 +557,49 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
defaultListen = mkOption {
|
||||
type = with types; listOf (submodule {
|
||||
options = {
|
||||
addr = mkOption {
|
||||
type = str;
|
||||
description = lib.mdDoc "IP address.";
|
||||
};
|
||||
port = mkOption {
|
||||
type = nullOr port;
|
||||
description = lib.mdDoc "Port number.";
|
||||
default = null;
|
||||
};
|
||||
ssl = mkOption {
|
||||
type = nullOr bool;
|
||||
default = null;
|
||||
description = lib.mdDoc "Enable SSL.";
|
||||
};
|
||||
proxyProtocol = mkOption {
|
||||
type = bool;
|
||||
description = lib.mdDoc "Enable PROXY protocol.";
|
||||
default = false;
|
||||
};
|
||||
extraParameters = mkOption {
|
||||
type = listOf str;
|
||||
description = lib.mdDoc "Extra parameters of this listen directive.";
|
||||
default = [ ];
|
||||
example = [ "backlog=1024" "deferred" ];
|
||||
};
|
||||
};
|
||||
});
|
||||
default = [];
|
||||
example = literalExpression ''[
|
||||
{ addr = "10.0.0.12"; proxyProtocol = true; ssl = true; }
|
||||
{ addr = "0.0.0.0"; }
|
||||
{ addr = "[::0]"; }
|
||||
]'';
|
||||
description = lib.mdDoc ''
|
||||
If vhosts do not specify listen, use these addresses by default.
|
||||
This option takes precedence over {option}`defaultListenAddresses` and
|
||||
other listen-related defaults options.
|
||||
'';
|
||||
};
|
||||
|
||||
defaultListenAddresses = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "0.0.0.0" ] ++ optional enableIPv6 "[::0]";
|
||||
|
@ -546,6 +607,7 @@ in
|
|||
example = literalExpression ''[ "10.0.0.12" "[2002:a00:1::]" ]'';
|
||||
description = lib.mdDoc ''
|
||||
If vhosts do not specify listenAddresses, use these addresses by default.
|
||||
This is akin to writing `defaultListen = [ { addr = "0.0.0.0" } ]`.
|
||||
'';
|
||||
};
|
||||
|
||||
|
@ -1078,6 +1140,32 @@ in
|
|||
which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;`.
|
||||
'';
|
||||
}
|
||||
|
||||
{
|
||||
# The idea is to understand whether there is a virtual host with a listen configuration
|
||||
# that requires ACME configuration but has no HTTP listener which will make deterministically fail
|
||||
# this operation.
|
||||
# Options' priorities are the following at the moment:
|
||||
# listen (vhost) > defaultListen (server) > listenAddresses (vhost) > defaultListenAddresses (server)
|
||||
assertion =
|
||||
let
|
||||
hasAtLeastHttpListener = listenOptions: any (listenLine: if listenLine ? proxyProtocol then !listenLine.proxyProtocol else true) listenOptions;
|
||||
hasAtLeastDefaultHttpListener = if cfg.defaultListen != [] then hasAtLeastHttpListener cfg.defaultListen else (cfg.defaultListenAddresses != []);
|
||||
in
|
||||
all (host:
|
||||
let
|
||||
hasAtLeastVhostHttpListener = if host.listen != [] then hasAtLeastHttpListener host.listen else (host.listenAddresses != []);
|
||||
vhostAuthority = host.listen != [] || (cfg.defaultListen == [] && host.listenAddresses != []);
|
||||
in
|
||||
# Either vhost has precedence and we need a vhost specific http listener
|
||||
# Either vhost set nothing and inherit from server settings
|
||||
host.enableACME -> ((vhostAuthority && hasAtLeastVhostHttpListener) || (!vhostAuthority && hasAtLeastDefaultHttpListener))
|
||||
) (attrValues virtualHosts);
|
||||
message = ''
|
||||
services.nginx.virtualHosts.<name>.enableACME requires a HTTP listener
|
||||
to answer to ACME requests.
|
||||
'';
|
||||
}
|
||||
] ++ map (name: mkCertOwnershipAssertion {
|
||||
inherit (cfg) group user;
|
||||
cert = config.security.acme.certs.${name};
|
||||
|
|
|
@ -27,12 +27,35 @@ with lib;
|
|||
};
|
||||
|
||||
listen = mkOption {
|
||||
type = with types; listOf (submodule { options = {
|
||||
addr = mkOption { type = str; description = lib.mdDoc "IP address."; };
|
||||
port = mkOption { type = port; description = lib.mdDoc "Port number."; default = 80; };
|
||||
ssl = mkOption { type = bool; description = lib.mdDoc "Enable SSL."; default = false; };
|
||||
extraParameters = mkOption { type = listOf str; description = lib.mdDoc "Extra parameters of this listen directive."; default = []; example = [ "backlog=1024" "deferred" ]; };
|
||||
}; });
|
||||
type = with types; listOf (submodule {
|
||||
options = {
|
||||
addr = mkOption {
|
||||
type = str;
|
||||
description = lib.mdDoc "IP address.";
|
||||
};
|
||||
port = mkOption {
|
||||
type = port;
|
||||
description = lib.mdDoc "Port number.";
|
||||
default = 80;
|
||||
};
|
||||
ssl = mkOption {
|
||||
type = bool;
|
||||
description = lib.mdDoc "Enable SSL.";
|
||||
default = false;
|
||||
};
|
||||
proxyProtocol = mkOption {
|
||||
type = bool;
|
||||
description = lib.mdDoc "Enable PROXY protocol.";
|
||||
default = false;
|
||||
};
|
||||
extraParameters = mkOption {
|
||||
type = listOf str;
|
||||
description = lib.mdDoc "Extra parameters of this listen directive.";
|
||||
default = [ ];
|
||||
example = [ "backlog=1024" "deferred" ];
|
||||
};
|
||||
};
|
||||
});
|
||||
default = [];
|
||||
example = [
|
||||
{ addr = "195.154.1.1"; port = 443; ssl = true; }
|
||||
|
@ -45,7 +68,7 @@ with lib;
|
|||
and `onlySSL`.
|
||||
|
||||
If you only want to set the addresses manually and not
|
||||
the ports, take a look at `listenAddresses`
|
||||
the ports, take a look at `listenAddresses`.
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
|
@ -521,6 +521,7 @@ in {
|
|||
nginx-sandbox = handleTestOn ["x86_64-linux"] ./nginx-sandbox.nix {};
|
||||
nginx-sso = handleTest ./nginx-sso.nix {};
|
||||
nginx-variants = handleTest ./nginx-variants.nix {};
|
||||
nginx-proxyprotocol = handleTest ./nginx-proxyprotocol {};
|
||||
nifi = handleTestOn ["x86_64-linux"] ./web-apps/nifi.nix {};
|
||||
nitter = handleTest ./nitter.nix {};
|
||||
nix-ld = handleTest ./nix-ld.nix {};
|
||||
|
|
20
nixos/tests/nginx-proxyprotocol/_.test.nix.cert.pem
Normal file
20
nixos/tests/nginx-proxyprotocol/_.test.nix.cert.pem
Normal file
|
@ -0,0 +1,20 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDLjCCAhagAwIBAgIIP2+4GFxOYMgwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
||||
AxMVbWluaWNhIHJvb3QgY2EgNGU3NTJiMB4XDTIzMDEzMDAzNDExOFoXDTQzMDEz
|
||||
MDAzNDExOFowFTETMBEGA1UEAwwKKi50ZXN0Lm5peDCCASIwDQYJKoZIhvcNAQEB
|
||||
BQADggEPADCCAQoCggEBAMarJSCzelnzTMT5GMoIKA/MXBNk5j277uI2Gq2MCky/
|
||||
DlBpx+tjSsKsz6QLBduKMF8OH5AgjrVAKQAtsVPDseY0Qcyx/5dgJjkdO4on+DFb
|
||||
V0SJ3ZhYPKACrqQ1SaoG+Xup37puw7sVR13J7oNvP6fAYRcjYqCiFC7VMjJNG4dR
|
||||
251jvWWidSc7v5CYw2AxrngtBgHeQuyG9QCJ1DRH8h6ioV7IeonwReN7noYtTWh8
|
||||
NDjGnw9HH2nYMcL91E+DWCxWVmbC9/orvYOT7u0Orho0t1w9BB0/zzcdojwQpMCv
|
||||
HahEmFQmdGbWTuI4caBeaDBJVsSwKlTcxLSS4MAZ0c8CAwEAAaN3MHUwDgYDVR0P
|
||||
AQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMB
|
||||
Af8EAjAAMB8GA1UdIwQYMBaAFGyXySYI3gL88d7GHnGMU6wpiBf2MBUGA1UdEQQO
|
||||
MAyCCioudGVzdC5uaXgwDQYJKoZIhvcNAQELBQADggEBAJ/DpwiLVBgWyozsn++f
|
||||
kR4m0dUjnuCgpHo2EMoMZh+9og+OC0vq6WITXHaJytB3aBMxFOUTim3vwxPyWPXX
|
||||
/vy+q6jJ6QMLx1J3VIWZdmXsT+qLGbVzL/4gNoaRsLPGO06p3yVjhas+OBFx1Fee
|
||||
6kTHb82S/dzBojOJLRRo18CU9yw0FUXOPqN7HF7k2y+Twe6+iwCuCKGSFcvmRjxe
|
||||
bWy11C921bTienW0Rmq6ppFWDaUNYP8kKpMN2ViAvc0tyF6wwk5lyOiqCR+pQHJR
|
||||
H/J4qSeKDchYLKECuzd6SySz8FW/xPKogQ28zba+DBD86hpqiEJOBzxbrcN3cjUn
|
||||
7N4=
|
||||
-----END CERTIFICATE-----
|
27
nixos/tests/nginx-proxyprotocol/_.test.nix.key.pem
Normal file
27
nixos/tests/nginx-proxyprotocol/_.test.nix.key.pem
Normal file
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAxqslILN6WfNMxPkYyggoD8xcE2TmPbvu4jYarYwKTL8OUGnH
|
||||
62NKwqzPpAsF24owXw4fkCCOtUApAC2xU8Ox5jRBzLH/l2AmOR07iif4MVtXRInd
|
||||
mFg8oAKupDVJqgb5e6nfum7DuxVHXcnug28/p8BhFyNioKIULtUyMk0bh1HbnWO9
|
||||
ZaJ1Jzu/kJjDYDGueC0GAd5C7Ib1AInUNEfyHqKhXsh6ifBF43uehi1NaHw0OMaf
|
||||
D0cfadgxwv3UT4NYLFZWZsL3+iu9g5Pu7Q6uGjS3XD0EHT/PNx2iPBCkwK8dqESY
|
||||
VCZ0ZtZO4jhxoF5oMElWxLAqVNzEtJLgwBnRzwIDAQABAoIBAFuNGOH184cqKJGI
|
||||
3RSVJ6kIGtJRKA0A4vfZyPd61nBBhx4lcRyXOCd4LYPCFKP0DZBwWLk5V6pM89gC
|
||||
NnqMbxnPsRbcXBVtGJAvWXW0L5rHJfMOuVBwMRfnxIUljVnONv/264PlcUtwZd/h
|
||||
o4lsJeBvNg7MnrG5nyVp1+T4RZxYm1P86HLp5zyT+fdj4Cr82b9j6QpxGXEfm1jV
|
||||
QA1xr1ZkrV8fgETyaE0TBIKcdt6xNfv1mpI1RE5gaP/YzcCs/mL+G0kMar4l7pO/
|
||||
6OHXTvHz+W3G6Xlha7Wq1ADoqYz2K7VoL/OgSQhIxRNujyWR6lir7eladVrKkCzu
|
||||
uzFi/HECgYEA0vSNCIK3useSypMPHhYUVNbZ4hbK0WgqSAxfJQtL3nC7KviVMAXj
|
||||
IKVR90xuzJB+ih88KCJpH84JH90paMpW0Gq1yEae90bnWa8Nj7ULLS/Zuj0WrelU
|
||||
+DEGbx47IUPOtiLBxooxFKyIVhX3hWRwZ0pokSQzbgb5zYnlM6tqZ3cCgYEA8Rb2
|
||||
wtt0XmqEQedFacs4fobJoVWMcETjpuxYp0m5Kje/4QkptZIbspXGBgNtPBBRGg51
|
||||
AYSu8wYkGEueI77KiFDgY8AAkpOk2MrMVPszjOhUiO1oEfbT6ynOY5RDOuXcY6jo
|
||||
8RpSk46VkfVxt6LVmappqcVFtVWcAjdGfXeSLmkCgYAWP7SgMSkvidzxgJEXmzyJ
|
||||
th9EuSKq81GCR8vBHG/kBf+3iIAzkGtkBgufCXCmIpc1+hVeJkLwF8rekXTMmIqP
|
||||
cLG7bbdWXSQJUW0cuvtyyJkuC0NZFELh6knDbmzOFVi33PKS/gAvLgMzER4J843n
|
||||
VvGwXSEPeazfAKwrxuhyAQKBgQCOm5TPYlyNVNhy20h18d2zCivOoPn3luhKXtd5
|
||||
7OP4kw2PIYpoesqjcnC2MeS1eLlgfli70y5hVqqXLHOYlUzcIWr51iMAkREbo6oG
|
||||
QqkVmoAWlsfOiICGRC5vPM4f0sPwt4NCyt05p0fWFKd1hn5u7Ryfba90OfWUYfny
|
||||
UX5IsQKBgQCswer4Qc3UepkiYxGwSTxgIh4kYlmamU2I00Kar4uFAr9JsCbk98f0
|
||||
kaCUNZjrrvTwgRmdhwcpMDiMW/F4QkNk0I2unHcoAvzNop6c22VhHJU2XJhrQ57h
|
||||
n1iPiw0NLXiA4RQwMUMjtt3nqlpLOTXGtsF8TmpWPcAN2QcTxOutzw==
|
||||
-----END RSA PRIVATE KEY-----
|
20
nixos/tests/nginx-proxyprotocol/ca.cert.pem
Normal file
20
nixos/tests/nginx-proxyprotocol/ca.cert.pem
Normal file
|
@ -0,0 +1,20 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDSzCCAjOgAwIBAgIITnUr3xFw4oEwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
||||
AxMVbWluaWNhIHJvb3QgY2EgNGU3NTJiMCAXDTIzMDEzMDAzNDExOFoYDzIxMjMw
|
||||
MTMwMDM0MTE4WjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSA0ZTc1MmIwggEi
|
||||
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1SrJT9k3zXIXApEyL5UDlw7F6
|
||||
MMOqE5d+8ZwMccHbEKLu0ssNRY+j31tnNYQ/r5iCNeNgUZccKBgzdU0ysyw5n4tw
|
||||
0y+MTD9fCfUXYcc8pJRPRolo6zxYO9W7WJr0nfJZ+p7zFRAjRCmzXdnZjKz0EGcg
|
||||
x9mHwn//3SuLt1ItK1n3aZ6im9NlcVtunDe3lCSL0tRgy7wDGNvWDZMO49jk4AFU
|
||||
BlMqScuiNpUzYgCxNaaGMuH3M0f0YyRAxSs6FWewLtqTIaVql7HL+3PcGAhvlKEZ
|
||||
fvfaf80F9aWI88sbEddTA0s5837zEoDwGpZl3K5sPU/O3MVEHIhAY5ICG0IBAgMB
|
||||
AAGjgYYwgYMwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
|
||||
BgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRsl8kmCN4C/PHe
|
||||
xh5xjFOsKYgX9jAfBgNVHSMEGDAWgBRsl8kmCN4C/PHexh5xjFOsKYgX9jANBgkq
|
||||
hkiG9w0BAQsFAAOCAQEAmvgpU+q+TBbz+9Y2rdiIeTfeDXtMNPf+nKI3zxYztRGC
|
||||
MoKP6jCQaFSQra4BVumFLV38DoqR1pOV1ojkiyO5c/9Iym/1Wmm8LeqgsHNqSgyS
|
||||
C7wvBcb/N9PzIBQFq/RiboDoC7bqK/0zQguCmBtGceH+AVpQyfXM+P78B1EkHozu
|
||||
67igP8GfouPp2s4Vd5P2XGkA6vMgYCtFEnCbtmmo7C8B+ymhD/D9axpMKQ1OaBg9
|
||||
jfqLOlk+Rc2nYZuaDjnUmlTkYjC6EwCNe9weYkSJgQ9QzoGJLIRARsdQdsp3C2fZ
|
||||
l2UZKkDJ2GPrrc+TdaGXZTYi0uMmvQsEKZXtqAzorQ==
|
||||
-----END CERTIFICATE-----
|
27
nixos/tests/nginx-proxyprotocol/ca.key.pem
Normal file
27
nixos/tests/nginx-proxyprotocol/ca.key.pem
Normal file
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAtUqyU/ZN81yFwKRMi+VA5cOxejDDqhOXfvGcDHHB2xCi7tLL
|
||||
DUWPo99bZzWEP6+YgjXjYFGXHCgYM3VNMrMsOZ+LcNMvjEw/Xwn1F2HHPKSUT0aJ
|
||||
aOs8WDvVu1ia9J3yWfqe8xUQI0Qps13Z2Yys9BBnIMfZh8J//90ri7dSLStZ92me
|
||||
opvTZXFbbpw3t5Qki9LUYMu8Axjb1g2TDuPY5OABVAZTKknLojaVM2IAsTWmhjLh
|
||||
9zNH9GMkQMUrOhVnsC7akyGlapexy/tz3BgIb5ShGX732n/NBfWliPPLGxHXUwNL
|
||||
OfN+8xKA8BqWZdyubD1PztzFRByIQGOSAhtCAQIDAQABAoIBAQCLeAWs1kWtvTYg
|
||||
t8UzspC0slItAKrmgt//hvxYDoPmdewC8yPG+AbDOSfmRKOTIxGeyro79UjdHnNP
|
||||
0yQqpvCU/AqYJ7/inR37jXuCG3TdUHfQbSF1F9N6xb1tvYKoQYKaelYiB8g8eUnj
|
||||
dYYM+U5tDNlpvJW6/YTfYFUJzWRo3i8jj5lhbkjcJDvdOhVxMXNXJgJAymu1KysE
|
||||
N1da2l4fzmuoN82wFE9KMyYSn+LOLWBReQQmXHZPP+2LjRIVrWoFoV49k2Ylp9tH
|
||||
yeaFx1Ya/wVx3PRnSW+zebWDcc0bAua9XU3Fi42yRq5iXOyoXHyefDfJoId7+GAO
|
||||
IF2qRw9hAoGBAM1O1l4ceOEDsEBh7HWTvmfwVfkXgT6VHeI6LGEjb88FApXgT+wT
|
||||
1s1IWVVOigLl9OKQbrjqlg9xgzrPDHYRwu5/Oz3X2WaH6wlF+d+okoqls6sCEAeo
|
||||
GfzF3sKOHQyIYjttCXE5G38uhIgVFFFfK97AbUiY8egYBr0zjVXK7xINAoGBAOIN
|
||||
1pDBFBQIoKj64opm/G9lJBLUpWLBFdWXhXS6q2jNsdY1mLMRmu/RBaKSfGz7W1a/
|
||||
a2WBedjcnTWJ/84tBsn4Qj5tLl8xkcXiN/pslWzg724ZnVsbyxM9KvAdXAma3F0g
|
||||
2EsYq8mhvbAEkpE+aoM6jwOJBnMhTRZrNMKN2lbFAoGAHmZWB4lfvLG3H1FgmehO
|
||||
gUVs9X0tff7GdgD3IUsF+zlasKaOLv6hB7R2xdLjTJqQMBwCyQ6zOYYtUD/oMHNg
|
||||
0b+1HesgHbZybuUVorBrQmxWtjOP/BJABtWlrlkso/Zt1S7H/yPdlm9k4GF+qK3W
|
||||
6RzFEcLTzvH/zXQcsV9jFuECgYEAhaX+1KiC0XFkY2OpaoCHAOlAUa3NdjyIRzcF
|
||||
XUU8MINkgCxB8qUXAHCJL1wCGoDluL0FpwbM3m1YuR200tYGLIUNzVDJ2Ng6wk8E
|
||||
H5fxJGU8ydB1Gzescdx5NWt2Tet0G89ecc/NSTHKL3YUnbDUUm/dvA5YdNscc4PA
|
||||
tsIdc60CgYEArvU1MwqGQUTDKUmaM2t3qm70fbwmOViHfyTWpn4aAQR3sK16iJMm
|
||||
V+dka62L/VYs5CIbzXvCioyugUMZGJi/zIwrViRzqJQbNnPADAW4lG88UxXqHHAH
|
||||
q33ivjgd9omGFb37saKOmR44KmjUIDvSIZF4W3EPwAMEyl5mM31Ryns=
|
||||
-----END RSA PRIVATE KEY-----
|
144
nixos/tests/nginx-proxyprotocol/default.nix
Normal file
144
nixos/tests/nginx-proxyprotocol/default.nix
Normal file
|
@ -0,0 +1,144 @@
|
|||
let
|
||||
certs = import ./snakeoil-certs.nix;
|
||||
in
|
||||
import ../make-test-python.nix ({ pkgs, ... }: {
|
||||
name = "nginx-proxyprotocol";
|
||||
|
||||
nodes = {
|
||||
webserver = { pkgs, lib, ... }: {
|
||||
environment.systemPackages = [ pkgs.netcat ];
|
||||
security.pki.certificateFiles = [
|
||||
certs.ca.cert
|
||||
];
|
||||
|
||||
networking.extraHosts = ''
|
||||
127.0.0.5 proxy.test.nix
|
||||
127.0.0.5 noproxy.test.nix
|
||||
127.0.0.3 direct-nossl.test.nix
|
||||
127.0.0.4 unsecure-nossl.test.nix
|
||||
127.0.0.2 direct-noproxy.test.nix
|
||||
127.0.0.1 direct-proxy.test.nix
|
||||
'';
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
defaultListen = [
|
||||
{ addr = "127.0.0.1"; proxyProtocol = true; ssl = true; }
|
||||
{ addr = "127.0.0.2"; }
|
||||
{ addr = "127.0.0.3"; ssl = false; }
|
||||
{ addr = "127.0.0.4"; ssl = false; proxyProtocol = true; }
|
||||
];
|
||||
commonHttpConfig = ''
|
||||
log_format pcombined '(proxy_protocol=$proxy_protocol_addr) - (remote_addr=$remote_addr) - (realip=$realip_remote_addr) - (upstream=) - (remote_user=$remote_user) [$time_local] '
|
||||
'"$request" $status $body_bytes_sent '
|
||||
'"$http_referer" "$http_user_agent"';
|
||||
access_log /var/log/nginx/access.log pcombined;
|
||||
error_log /var/log/nginx/error.log;
|
||||
'';
|
||||
virtualHosts =
|
||||
let
|
||||
commonConfig = {
|
||||
locations."/".return = "200 '$remote_addr'";
|
||||
extraConfig = ''
|
||||
set_real_ip_from 127.0.0.5/32;
|
||||
real_ip_header proxy_protocol;
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
"*.test.nix" = commonConfig // {
|
||||
sslCertificate = certs."*.test.nix".cert;
|
||||
sslCertificateKey = certs."*.test.nix".key;
|
||||
forceSSL = true;
|
||||
};
|
||||
"direct-nossl.test.nix" = commonConfig;
|
||||
"unsecure-nossl.test.nix" = commonConfig // {
|
||||
extraConfig = ''
|
||||
real_ip_header proxy_protocol;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.sniproxy = {
|
||||
enable = true;
|
||||
config = ''
|
||||
error_log {
|
||||
syslog daemon
|
||||
}
|
||||
access_log {
|
||||
syslog daemon
|
||||
}
|
||||
listener 127.0.0.5:443 {
|
||||
protocol tls
|
||||
source 127.0.0.5
|
||||
}
|
||||
table {
|
||||
^proxy\.test\.nix$ 127.0.0.1 proxy_protocol
|
||||
^noproxy\.test\.nix$ 127.0.0.2
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
def check_origin_ip(src_ip: str, dst_url: str, failure: bool = False, proxy_protocol: bool = False, expected_ip: str | None = None):
|
||||
check = webserver.fail if failure else webserver.succeed
|
||||
if expected_ip is None:
|
||||
expected_ip = src_ip
|
||||
|
||||
return check(f"curl {'--haproxy-protocol' if proxy_protocol else '''} --interface {src_ip} --fail -L {dst_url} | grep '{expected_ip}'")
|
||||
|
||||
webserver.wait_for_unit("nginx")
|
||||
webserver.wait_for_unit("sniproxy")
|
||||
# This should be closed by virtue of ssl = true;
|
||||
webserver.wait_for_closed_port(80, "127.0.0.1")
|
||||
# This should be open by virtue of no explicit ssl
|
||||
webserver.wait_for_open_port(80, "127.0.0.2")
|
||||
# This should be open by virtue of ssl = true;
|
||||
webserver.wait_for_open_port(443, "127.0.0.1")
|
||||
# This should be open by virtue of no explicit ssl
|
||||
webserver.wait_for_open_port(443, "127.0.0.2")
|
||||
# This should be open by sniproxy
|
||||
webserver.wait_for_open_port(443, "127.0.0.5")
|
||||
# This should be closed by sniproxy
|
||||
webserver.wait_for_closed_port(80, "127.0.0.5")
|
||||
|
||||
# Sanity checks for the NGINX module
|
||||
# direct-HTTP connection to NGINX without TLS, this checks that ssl = false; works well.
|
||||
check_origin_ip("127.0.0.10", "http://direct-nossl.test.nix/")
|
||||
# webserver.execute("openssl s_client -showcerts -connect direct-noproxy.test.nix:443")
|
||||
# direct-HTTP connection to NGINX with TLS
|
||||
check_origin_ip("127.0.0.10", "http://direct-noproxy.test.nix/")
|
||||
check_origin_ip("127.0.0.10", "https://direct-noproxy.test.nix/")
|
||||
# Well, sniproxy is not listening on 80 and cannot redirect
|
||||
check_origin_ip("127.0.0.10", "http://proxy.test.nix/", failure=True)
|
||||
check_origin_ip("127.0.0.10", "http://noproxy.test.nix/", failure=True)
|
||||
|
||||
# Actual PROXY protocol related tests
|
||||
# Connecting through sniproxy should passthrough the originating IP address.
|
||||
check_origin_ip("127.0.0.10", "https://proxy.test.nix/")
|
||||
# Connecting through sniproxy to a non-PROXY protocol enabled listener should not pass the originating IP address.
|
||||
check_origin_ip("127.0.0.10", "https://noproxy.test.nix/", expected_ip="127.0.0.5")
|
||||
|
||||
# Attack tests against spoofing
|
||||
# Let's try to spoof our IP address by connecting direct-y to the PROXY protocol listener.
|
||||
# FIXME(RaitoBezarius): rewrite it using Python + (Scapy|something else) as this is too much broken unfortunately.
|
||||
# Or wait for upstream curl patch.
|
||||
# def generate_attacker_request(original_ip: str, target_ip: str, dst_url: str):
|
||||
# return f"""PROXY TCP4 {original_ip} {target_ip} 80 80
|
||||
# GET / HTTP/1.1
|
||||
# Host: {dst_url}
|
||||
|
||||
# """
|
||||
# def spoof(original_ip: str, target_ip: str, dst_url: str, tls: bool = False, expect_failure: bool = True):
|
||||
# method = webserver.fail if expect_failure else webserver.succeed
|
||||
# port = 443 if tls else 80
|
||||
# print(webserver.execute(f"cat <<EOF | nc {target_ip} {port}\n{generate_attacker_request(original_ip, target_ip, dst_url)}\nEOF"))
|
||||
# return method(f"cat <<EOF | nc {target_ip} {port} | grep {original_ip}\n{generate_attacker_request(original_ip, target_ip, dst_url)}\nEOF")
|
||||
|
||||
# check_origin_ip("127.0.0.10", "http://unsecure-nossl.test.nix", proxy_protocol=True)
|
||||
# spoof("1.1.1.1", "127.0.0.4", "direct-nossl.test.nix")
|
||||
# spoof("1.1.1.1", "127.0.0.4", "unsecure-nossl.test.nix", expect_failure=False)
|
||||
'';
|
||||
})
|
30
nixos/tests/nginx-proxyprotocol/generate-certs.nix
Normal file
30
nixos/tests/nginx-proxyprotocol/generate-certs.nix
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Minica can provide a CA key and cert, plus a key
|
||||
# and cert for our fake CA server's Web Front End (WFE).
|
||||
{
|
||||
pkgs ? import <nixpkgs> {},
|
||||
minica ? pkgs.minica,
|
||||
runCommandCC ? pkgs.runCommandCC,
|
||||
}:
|
||||
let
|
||||
conf = import ./snakeoil-certs.nix;
|
||||
domain = conf.domain;
|
||||
domainSanitized = pkgs.lib.replaceStrings ["*"] ["_"] domain;
|
||||
in
|
||||
runCommandCC "generate-tests-certs" {
|
||||
buildInputs = [ (minica.overrideAttrs (old: {
|
||||
postPatch = ''
|
||||
sed -i 's_NotAfter: time.Now().AddDate(2, 0, 30),_NotAfter: time.Now().AddDate(20, 0, 0),_' main.go
|
||||
'';
|
||||
})) ];
|
||||
|
||||
} ''
|
||||
minica \
|
||||
--ca-key ca.key.pem \
|
||||
--ca-cert ca.cert.pem \
|
||||
--domains "${domain}"
|
||||
|
||||
mkdir -p $out
|
||||
mv ca.*.pem $out/
|
||||
mv ${domainSanitized}/key.pem $out/${domainSanitized}.key.pem
|
||||
mv ${domainSanitized}/cert.pem $out/${domainSanitized}.cert.pem
|
||||
''
|
14
nixos/tests/nginx-proxyprotocol/snakeoil-certs.nix
Normal file
14
nixos/tests/nginx-proxyprotocol/snakeoil-certs.nix
Normal file
|
@ -0,0 +1,14 @@
|
|||
let
|
||||
domain = "*.test.nix";
|
||||
domainSanitized = "_.test.nix";
|
||||
in {
|
||||
inherit domain;
|
||||
ca = {
|
||||
cert = ./ca.cert.pem;
|
||||
key = ./ca.key.pem;
|
||||
};
|
||||
"${domain}" = {
|
||||
cert = ./. + "/${domainSanitized}.cert.pem";
|
||||
key = ./. + "/${domainSanitized}.key.pem";
|
||||
};
|
||||
}
|
|
@ -178,7 +178,7 @@ stdenv.mkDerivation {
|
|||
passthru = {
|
||||
inherit modules;
|
||||
tests = {
|
||||
inherit (nixosTests) nginx nginx-auth nginx-etag nginx-globalredirect nginx-http3 nginx-pubhtml nginx-sandbox nginx-sso;
|
||||
inherit (nixosTests) nginx nginx-auth nginx-etag nginx-globalredirect nginx-http3 nginx-pubhtml nginx-sandbox nginx-sso nginx-proxyprotocol;
|
||||
variants = lib.recurseIntoAttrs nixosTests.nginx-variants;
|
||||
acme-integration = nixosTests.acme;
|
||||
} // passthru.tests;
|
||||
|
|
Loading…
Reference in a new issue