view auth_request.t @ 1938:e1059682aeef

Tests: fixed ClientHello with resending Initial QUIC packets. Previously it was rebuilt each time using distinct ClientHello.random resulting in different CRYPTO payload. As such, it led to TLS digest hash and derived secrets mismatch when resending Initial packet. Now ClientHello is built once and reused when resending Initial packets. Additionally, this required to preserve a generated secret value used in shared secret calculation as part of TLS key schedule. Previously it was regenerated when receiving a Retry packet, but this won't work with reused ClientHello as the resulting shared secrets won't match.
author Sergey Kandaurov <pluknet@nginx.com>
date Wed, 30 Aug 2023 02:22:58 +0400
parents 83ec64929612
children
line wrap: on
line source

#!/usr/bin/perl

# (C) Maxim Dounin

# Tests for auth request module.

###############################################################################

use warnings;
use strict;

use Test::More;

use Socket qw/ CRLF /;

BEGIN { use FindBin; chdir($FindBin::Bin); }

use lib 'lib';
use Test::Nginx;

###############################################################################

select STDERR; $| = 1;
select STDOUT; $| = 1;

my $t = Test::Nginx->new()
	->has(qw/http rewrite proxy cache fastcgi auth_basic auth_request/)
	->plan(20);

$t->write_file_expand('nginx.conf', <<'EOF');

%%TEST_GLOBALS%%

daemon off;

events {
}

http {
    %%TEST_GLOBALS_HTTP%%

    proxy_cache_path   %%TESTDIR%%/cache  levels=1:2
                       keys_zone=NAME:1m;

    server {
        listen       127.0.0.1:8080;
        server_name  localhost;

        location / {
            return 444;
        }

        location /open {
            auth_request /auth-open;
        }
        location = /auth-open {
            return 204;
        }

        location /open-static {
            auth_request /auth-open-static;
        }
        location = /auth-open-static {
            # nothing, use static file
        }

        location /unauthorized {
            auth_request /auth-unauthorized;
        }
        location = /auth-unauthorized {
            return 401;
        }

        location /forbidden {
            auth_request /auth-forbidden;
        }
        location = /auth-forbidden {
            return 403;
        }

        location /error {
            auth_request /auth-error;
        }
        location = /auth-error {
            return 404;
        }

        location /off {
            auth_request off;
        }

        location /proxy {
            auth_request /auth-proxy;
        }
        location = /auth-proxy {
            proxy_pass http://127.0.0.1:8080/auth-basic;
            proxy_pass_request_body off;
            proxy_set_header Content-Length "";
        }
        location = /auth-basic {
            auth_basic "restricted";
            auth_basic_user_file %%TESTDIR%%/htpasswd;
        }

        location = /proxy-double {
            proxy_pass http://127.0.0.1:8080/auth-error;
            proxy_intercept_errors on;
            error_page 404 = /proxy-double-fallback;
            client_body_buffer_size 4k;
        }
        location = /proxy-double-fallback {
            auth_request /auth-proxy-double;
            proxy_pass http://127.0.0.1:8080/auth-open;
        }
        location = /auth-proxy-double {
            proxy_pass http://127.0.0.1:8080/auth-open;
            proxy_pass_request_body off;
            proxy_set_header Content-Length "";
        }

        location /proxy-cache {
            auth_request /auth-proxy-cache;
        }
        location = /auth-proxy-cache {
            proxy_pass http://127.0.0.1:8080/auth-basic;
            proxy_pass_request_body off;
            proxy_set_header Content-Length "";
            proxy_cache NAME;
            proxy_cache_valid 1m;
        }

        location /proxy-multi {
            auth_request /auth-proxy-multi;
        }
        location = /auth-proxy-multi {
            proxy_pass http://127.0.0.1:8080/auth-multi;
            proxy_pass_request_body off;
            proxy_set_header Content-Length "";
        }
        location = /auth-multi {
            add_header WWW-Authenticate foo always;
            add_header WWW-Authenticate bar always;
            return 401;
        }

        location /fastcgi {
            auth_request /auth-fastcgi;
        }
        location = /auth-fastcgi {
            fastcgi_pass 127.0.0.1:8081;
            fastcgi_pass_request_body off;
        }
    }
}

EOF

$t->write_file('htpasswd', 'user:{PLAIN}secret' . "\n");
$t->write_file('auth-basic', 'INVISIBLE');
$t->write_file('auth-open-static', 'INVISIBLE');
$t->run();

###############################################################################

like(http_get('/open'), qr/ 404 /, 'auth open');
like(http_get('/unauthorized'), qr/ 401 /, 'auth unauthorized');
like(http_get('/forbidden'), qr/ 403 /, 'auth forbidden');
like(http_get('/error'), qr/ 500 /, 'auth error');
like(http_get('/off'), qr/ 404 /, 'auth off');

like(http_post('/open'), qr/ 404 /, 'auth post open');
like(http_post('/unauthorized'), qr/ 401 /, 'auth post unauthorized');

like(http_get('/open-static'), qr/ 404 /, 'auth open static');
unlike(http_get('/open-static'), qr/INVISIBLE/, 'auth static no content');

like(http_get('/proxy'), qr/ 401 /, 'proxy auth unauthorized');
like(http_get('/proxy'), qr/WWW-Authenticate: Basic realm="restricted"/,
	'proxy auth has www-authenticate');
like(http_get_auth('/proxy'), qr/ 404 /, 'proxy auth pass');
unlike(http_get_auth('/proxy'), qr/INVISIBLE/, 'proxy auth no content');

like(http_post('/proxy'), qr/ 401 /, 'proxy auth post');

like(http_get_auth('/proxy-cache'), qr/ 404 /, 'proxy auth with cache');
like(http_get('/proxy-cache'), qr/ 404 /, 'proxy auth cached');

# Consider the following scenario:
#
# 1. proxy_pass reads request body, then goes to fallback via error_page
# 2. auth request uses proxy_pass, and upstream module closes request body file
#    in ngx_http_upstream_send_response()
# 3. oops: fallback has no body
#
# To prevent this we always allocate fake request body for auth request.
#
# Note that this doesn't happen when using header_only as relevant code
# in ngx_http_upstream_send_response() isn't reached.  It may be reached
# with proxy_cache or proxy_store, but they will shutdown client connection
# in case of header_only and hence do not work for us at all.

like(http_post_big('/proxy-double'), qr/ 204 /, 'proxy auth with body read');

# Multiple WWW-Authenticate headers (ticket #485).

TODO: {
local $TODO = 'not yet' unless $t->has_version('1.23.0');

like(http_get('/proxy-multi-auth'), qr/WWW-Authenticate: foo.*bar/s,
	'multiple www-authenticate headers');

}

SKIP: {
	eval { require FCGI; };
	skip 'FCGI not installed', 2 if $@;
	skip 'win32', 2 if $^O eq 'MSWin32';

	$t->run_daemon(\&fastcgi_daemon);
	$t->waitforsocket('127.0.0.1:' . port(8081));

	like(http_get('/fastcgi'), qr/ 404 /, 'fastcgi auth open');
	unlike(http_get('/fastcgi'), qr/INVISIBLE/, 'fastcgi auth no content');
}

###############################################################################

sub http_get_auth {
	my ($url, %extra) = @_;
	return http(<<EOF, %extra);
GET $url HTTP/1.0
Host: localhost
Authorization: Basic dXNlcjpzZWNyZXQ=

EOF
}

sub http_post {
	my ($url, %extra) = @_;

	my $p = "POST $url HTTP/1.0" . CRLF .
		"Host: localhost" . CRLF .
		"Content-Length: 10" . CRLF .
		CRLF .
		"1234567890";

	return http($p, %extra);
}

sub http_post_big {
	my ($url, %extra) = @_;

	my $p = "POST $url HTTP/1.0" . CRLF .
		"Host: localhost" . CRLF .
		"Content-Length: 10240" . CRLF .
		CRLF .
		("1234567890" x 1024);

	return http($p, %extra);
}

###############################################################################

sub fastcgi_daemon {
	my $socket = FCGI::OpenSocket('127.0.0.1:' . port(8081), 5);
	my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV,
		$socket);

	while ($request->Accept() >= 0) {
		print <<EOF;
Content-Type: text/html

INVISIBLE
EOF
	}

	FCGI::CloseSocket($socket);
}

###############################################################################