mirror of
https://github.com/ilyakooo0/nixpkgs.git
synced 2024-11-14 15:36:47 +03:00
nixos/nginx: first-class PROXY protocol support
PROXY protocol is a convenient way to carry information about the originating address/port of a TCP connection across multiple layers of proxies/NAT, etc. Currently, it is possible to make use of it in NGINX's NixOS module, but is painful when we want to enable it "globally". Technically, this is achieved by reworking the defaultListen options and the objective is to have a coherent way to specify default listeners in the current API design. See `mkDefaultListenVhost` and `defaultListen` for the details. It adds a safeguard against running a NGINX with no HTTP listeners (e.g. only PROXY listeners) while asking for ACME certificates over HTTP-01. An interesting usecase of PROXY protocol is to enable seamless IPv4 to IPv6 proxy with origin IPv4 address for IPv6-only NGINX servers, it is demonstrated how to achieve this in the tests, using sniproxy. Finally, the tests covers: - NGINX `defaultListen` mechanisms are not broken by these changes; - NGINX PROXY protocol listeners are working in a final usecase (sniproxy); - uses snakeoil TLS certs from ACME setup with wildcard certificates; In the future, it is desirable to spoof-attack NGINX in this scenario to ascertain that `set_real_ip_from` and all the layers are working as intended and preventing any user from setting their origin IP address to any arbitrary, opening up the NixOS module to bad™ vulnerabilities. For now, it is quite hard to achieve while being minimalistic about the tests dependencies.
This commit is contained in:
parent
bbdb8416a0
commit
69bb0f94de
@ -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 New Issue
Block a user