@@ -807,7 +807,7 @@ in {
|
|||||||
nginx-modsecurity = runTest ./nginx-modsecurity.nix;
|
nginx-modsecurity = runTest ./nginx-modsecurity.nix;
|
||||||
nginx-moreheaders = runTest ./nginx-moreheaders.nix;
|
nginx-moreheaders = runTest ./nginx-moreheaders.nix;
|
||||||
nginx-njs = handleTest ./nginx-njs.nix {};
|
nginx-njs = handleTest ./nginx-njs.nix {};
|
||||||
nginx-proxyprotocol = handleTest ./nginx-proxyprotocol {};
|
nginx-proxyprotocol = runTest ./nginx-proxyprotocol/default.nix;
|
||||||
nginx-pubhtml = handleTest ./nginx-pubhtml.nix {};
|
nginx-pubhtml = handleTest ./nginx-pubhtml.nix {};
|
||||||
nginx-redirectcode = handleTest ./nginx-redirectcode.nix {};
|
nginx-redirectcode = handleTest ./nginx-redirectcode.nix {};
|
||||||
nginx-sso = handleTest ./nginx-sso.nix {};
|
nginx-sso = handleTest ./nginx-sso.nix {};
|
||||||
|
|||||||
@@ -1,164 +1,162 @@
|
|||||||
let
|
let
|
||||||
certs = import ./snakeoil-certs.nix;
|
certs = import ./snakeoil-certs.nix;
|
||||||
in
|
in
|
||||||
import ../make-test-python.nix (
|
{ pkgs, ... }:
|
||||||
{ pkgs, ... }:
|
{
|
||||||
{
|
name = "nginx-proxyprotocol";
|
||||||
name = "nginx-proxyprotocol";
|
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
|
maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
|
||||||
};
|
};
|
||||||
|
|
||||||
nodes = {
|
nodes = {
|
||||||
webserver =
|
webserver =
|
||||||
{ pkgs, lib, ... }:
|
{ pkgs, lib, ... }:
|
||||||
{
|
{
|
||||||
environment.systemPackages = [ pkgs.netcat ];
|
environment.systemPackages = [ pkgs.netcat ];
|
||||||
security.pki.certificateFiles = [
|
security.pki.certificateFiles = [
|
||||||
certs.ca.cert
|
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 = ''
|
||||||
networking.extraHosts = ''
|
log_format pcombined '(proxy_protocol=$proxy_protocol_addr) - (remote_addr=$remote_addr) - (realip=$realip_remote_addr) - (upstream=) - (remote_user=$remote_user) [$time_local] '
|
||||||
127.0.0.5 proxy.test.nix
|
'"$request" $status $body_bytes_sent '
|
||||||
127.0.0.5 noproxy.test.nix
|
'"$http_referer" "$http_user_agent"';
|
||||||
127.0.0.3 direct-nossl.test.nix
|
access_log /var/log/nginx/access.log pcombined;
|
||||||
127.0.0.4 unsecure-nossl.test.nix
|
error_log /var/log/nginx/error.log;
|
||||||
127.0.0.2 direct-noproxy.test.nix
|
|
||||||
127.0.0.1 direct-proxy.test.nix
|
|
||||||
'';
|
'';
|
||||||
services.nginx = {
|
virtualHosts =
|
||||||
enable = true;
|
let
|
||||||
defaultListen = [
|
commonConfig = {
|
||||||
{
|
locations."/".return = "200 '$remote_addr'";
|
||||||
addr = "127.0.0.1";
|
extraConfig = ''
|
||||||
proxyProtocol = true;
|
set_real_ip_from 127.0.0.5/32;
|
||||||
ssl = true;
|
real_ip_header proxy_protocol;
|
||||||
}
|
'';
|
||||||
{ 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;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
in
|
||||||
|
{
|
||||||
services.sniproxy = {
|
"*.test.nix" = commonConfig // {
|
||||||
enable = true;
|
sslCertificate = certs."*.test.nix".cert;
|
||||||
config = ''
|
sslCertificateKey = certs."*.test.nix".key;
|
||||||
error_log {
|
forceSSL = true;
|
||||||
syslog daemon
|
};
|
||||||
}
|
"direct-nossl.test.nix" = commonConfig;
|
||||||
access_log {
|
"unsecure-nossl.test.nix" = commonConfig // {
|
||||||
syslog daemon
|
extraConfig = ''
|
||||||
}
|
real_ip_header proxy_protocol;
|
||||||
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 = ''
|
services.sniproxy = {
|
||||||
def check_origin_ip(src_ip: str, dst_url: str, failure: bool = False, proxy_protocol: bool = False, expected_ip: str | None = None):
|
enable = true;
|
||||||
check = webserver.fail if failure else webserver.succeed
|
config = ''
|
||||||
if expected_ip is None:
|
error_log {
|
||||||
expected_ip = src_ip
|
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
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return check(f"curl {'--haproxy-protocol' if proxy_protocol else '''} --interface {src_ip} --fail -L {dst_url} | grep '{expected_ip}'")
|
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
|
||||||
|
|
||||||
webserver.wait_for_unit("nginx")
|
return check(f"curl {'--haproxy-protocol' if proxy_protocol else '''} --interface {src_ip} --fail -L {dst_url} | grep '{expected_ip}'")
|
||||||
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
|
webserver.wait_for_unit("nginx")
|
||||||
# direct-HTTP connection to NGINX without TLS, this checks that ssl = false; works well.
|
webserver.wait_for_unit("sniproxy")
|
||||||
check_origin_ip("127.0.0.10", "http://direct-nossl.test.nix/")
|
# This should be closed by virtue of ssl = true;
|
||||||
# webserver.execute("openssl s_client -showcerts -connect direct-noproxy.test.nix:443")
|
webserver.wait_for_closed_port(80, "127.0.0.1")
|
||||||
# direct-HTTP connection to NGINX with TLS
|
# This should be open by virtue of no explicit ssl
|
||||||
check_origin_ip("127.0.0.10", "http://direct-noproxy.test.nix/")
|
webserver.wait_for_open_port(80, "127.0.0.2")
|
||||||
check_origin_ip("127.0.0.10", "https://direct-noproxy.test.nix/")
|
# This should be open by virtue of ssl = true;
|
||||||
# Well, sniproxy is not listening on 80 and cannot redirect
|
webserver.wait_for_open_port(443, "127.0.0.1")
|
||||||
check_origin_ip("127.0.0.10", "http://proxy.test.nix/", failure=True)
|
# This should be open by virtue of no explicit ssl
|
||||||
check_origin_ip("127.0.0.10", "http://noproxy.test.nix/", failure=True)
|
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")
|
||||||
|
|
||||||
# Actual PROXY protocol related tests
|
# Sanity checks for the NGINX module
|
||||||
# Connecting through sniproxy should passthrough the originating IP address.
|
# direct-HTTP connection to NGINX without TLS, this checks that ssl = false; works well.
|
||||||
check_origin_ip("127.0.0.10", "https://proxy.test.nix/")
|
check_origin_ip("127.0.0.10", "http://direct-nossl.test.nix/")
|
||||||
# Connecting through sniproxy to a non-PROXY protocol enabled listener should not pass the originating IP address.
|
# webserver.execute("openssl s_client -showcerts -connect direct-noproxy.test.nix:443")
|
||||||
check_origin_ip("127.0.0.10", "https://noproxy.test.nix/", expected_ip="127.0.0.5")
|
# 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)
|
||||||
|
|
||||||
# Attack tests against spoofing
|
# Actual PROXY protocol related tests
|
||||||
# Let's try to spoof our IP address by connecting direct-y to the PROXY protocol listener.
|
# Connecting through sniproxy should passthrough the originating IP address.
|
||||||
# FIXME(RaitoBezarius): rewrite it using Python + (Scapy|something else) as this is too much broken unfortunately.
|
check_origin_ip("127.0.0.10", "https://proxy.test.nix/")
|
||||||
# Or wait for upstream curl patch.
|
# Connecting through sniproxy to a non-PROXY protocol enabled listener should not pass the originating IP address.
|
||||||
# def generate_attacker_request(original_ip: str, target_ip: str, dst_url: str):
|
check_origin_ip("127.0.0.10", "https://noproxy.test.nix/", expected_ip="127.0.0.5")
|
||||||
# return f"""PROXY TCP4 {original_ip} {target_ip} 80 80
|
|
||||||
# GET / HTTP/1.1
|
|
||||||
# Host: {dst_url}
|
|
||||||
|
|
||||||
# """
|
# Attack tests against spoofing
|
||||||
# def spoof(original_ip: str, target_ip: str, dst_url: str, tls: bool = False, expect_failure: bool = True):
|
# Let's try to spoof our IP address by connecting direct-y to the PROXY protocol listener.
|
||||||
# method = webserver.fail if expect_failure else webserver.succeed
|
# FIXME(RaitoBezarius): rewrite it using Python + (Scapy|something else) as this is too much broken unfortunately.
|
||||||
# port = 443 if tls else 80
|
# Or wait for upstream curl patch.
|
||||||
# print(webserver.execute(f"cat <<EOF | nc {target_ip} {port}\n{generate_attacker_request(original_ip, target_ip, dst_url)}\nEOF"))
|
# def generate_attacker_request(original_ip: str, target_ip: str, dst_url: str):
|
||||||
# return method(f"cat <<EOF | nc {target_ip} {port} | grep {original_ip}\n{generate_attacker_request(original_ip, target_ip, dst_url)}\nEOF")
|
# return f"""PROXY TCP4 {original_ip} {target_ip} 80 80
|
||||||
|
# GET / HTTP/1.1
|
||||||
|
# Host: {dst_url}
|
||||||
|
|
||||||
# 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")
|
# def spoof(original_ip: str, target_ip: str, dst_url: str, tls: bool = False, expect_failure: bool = True):
|
||||||
# spoof("1.1.1.1", "127.0.0.4", "unsecure-nossl.test.nix", expect_failure=False)
|
# 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)
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user