Added some basic regression tests.

This commit is contained in:
brectanus 2008-05-22 18:44:22 +00:00
parent f90ffeb970
commit 813127aa13
4 changed files with 657 additions and 18 deletions

View File

@ -0,0 +1,23 @@
{
type => "config",
comment => "module loaded",
match_log => {
error => [ qr/ModSecurity for Apache.* configured\./, 10 ],
},
},
{
type => "config",
comment => "minimal config",
conf => sub {
# Open the minimal conf file, substituting the
# relative log paths with full paths.
open(C, "<$ENV{DIST_ROOT}/modsecurity.conf-minimal") or die "$!\n";
(my $conf = join('', <C>)) =~ s#Log logs/#Log $ENV{TEST_SERVER_ROOT}/logs/#g;
close C;
return $conf;
},
match_log => {
error => [ qr/ModSecurity for Apache.* configured\./, 10 ],
},
},

View File

@ -0,0 +1,144 @@
### SecArgumentSeparator
{
type => "config",
comment => "SecArgumentSeparator (get-pos)",
conf => q(
SecRuleEngine On
SecArgumentSeparator ";"
SecRule ARGS:a "@streq 1" "phase:1,deny,chain"
SecRule ARGS:b "@streq 2"
),
match_log => {
error => [ qr/Access denied with code 403 \(phase 1\). String match "2" at ARGS:b./, 1 ],
},
match_response => {
status => qr/^403$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?a=1;b=2",
),
},
{
type => "config",
comment => "SecArgumentSeparator (get-neg)",
conf => q(
SecRuleEngine On
SecRule ARGS:a "@streq 1" "phase:1,deny,chain"
SecRule ARGS:b "@streq 2"
),
match_log => {
-error => [ qr/Access denied/, 1 ],
},
match_response => {
status => qr/^200$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?a=1;b=2",
),
},
{
type => "config",
comment => "SecArgumentSeparator (post-pos)",
conf => q(
SecRuleEngine On
SecRequestBodyAccess On
SecArgumentSeparator ";"
SecRule ARGS:a "@streq 1" "phase:2,deny,chain"
SecRule ARGS:b "@streq 2"
),
match_log => {
error => [ qr/Access denied with code 403 \(phase 2\). String match "2" at ARGS:b./, 1 ],
},
match_response => {
status => qr/^403$/,
},
request => new HTTP::Request(
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
[
"Content-Type" => "application/x-www-form-urlencoded",
],
"a=1;b=2",
),
},
{
type => "config",
comment => "SecArgumentSeparator (post-neg)",
conf => q(
SecRuleEngine On
SecRequestBodyAccess On
SecRule ARGS:a "@streq 1" "phase:2,deny"
SecRule ARGS:b "@streq 2" "phase:2,deny"
),
match_log => {
-error => [ qr/Access denied/, 1 ],
},
match_response => {
status => qr/^200$/,
},
request => new HTTP::Request(
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
[
"Content-Type" => "application/x-www-form-urlencoded",
],
"a=1;b=2",
),
},
### SecRequestBodyAccess
{
type => "config",
comment => "SecRequestBodyAccess (pos)",
conf => qq(
SecRuleEngine On
SecAuditEngine On
SecAuditLogParts ABCDEFGHIJKZ
SecDebugLog $ENV{DEBUG_LOG}
SecDebugLogLevel 9
SecAuditLog $ENV{AUDIT_LOG}
SecRequestBodyAccess On
SecRule ARGS:a "\@streq 1" "phase:2,deny,chain"
SecRule ARGS:b "\@streq 2"
),
match_log => {
error => [ qr/Access denied with code 403 \(phase 2\). String match "2" at ARGS:b./, 1 ],
},
match_response => {
status => qr/^403$/,
},
request => new HTTP::Request(
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
[
"Content-Type" => "application/x-www-form-urlencoded",
],
"a=1&b=2",
),
},
{
type => "config",
comment => "SecRequestBodyAccess (neg)",
conf => qq(
SecRuleEngine On
SecAuditEngine On
SecAuditLogParts ABCDEFGHIJKZ
SecDebugLog $ENV{DEBUG_LOG}
SecDebugLogLevel 9
SecAuditLog $ENV{AUDIT_LOG}
SecRequestBodyAccess Off
SecRule ARGS:a "\@streq 1" "phase:2,deny"
SecRule ARGS:b "\@streq 2" "phase:2,deny"
),
match_log => {
-error => [ qr/Access denied/, 1 ],
},
match_response => {
status => qr/^200$/,
},
request => new HTTP::Request(
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
[
"Content-Type" => "application/x-www-form-urlencoded",
],
"a=1&b=2",
),
},

View File

@ -0,0 +1,427 @@
### Pass
{
type => "rule",
comment => "pass action in phase:1",
conf => qq(
SecRuleEngine On
SecAction "phase:1,pass"
SecAction "phase:1,deny"
),
match_log => {
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction/, 1 ],
},
match_response => {
status => qr/^403$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
),
},
{
type => "rule",
comment => "pass action in phase:2",
conf => qq(
SecRuleEngine On
SecAction "phase:2,pass"
SecAction "phase:2,deny"
),
match_log => {
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction/, 1 ],
},
match_response => {
status => qr/^403$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
),
},
{
type => "rule",
comment => "pass action in phase:3",
conf => qq(
SecRuleEngine On
SecAction "phase:3,pass"
SecAction "phase:3,deny"
),
match_log => {
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction/, 1 ],
},
match_response => {
status => qr/^403$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
),
},
{
type => "rule",
comment => "pass action in phase:4",
conf => qq(
SecRuleEngine On
SecAction "phase:4,pass"
SecAction "phase:4,deny"
),
match_log => {
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction/, 1 ],
},
match_response => {
status => qr/^403$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
),
},
### Allow
{
type => "rule",
comment => "allow action in phase:1",
conf => qq(
SecRuleEngine On
SecAction "phase:1,allow"
SecAction "phase:1,deny"
),
match_log => {
error => [ qr/ModSecurity: Access allowed \(phase 1\). Unconditional match in SecAction/, 1 ],
},
match_response => {
status => qr/^200$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
),
},
{
type => "rule",
comment => "allow action in phase:2",
conf => qq(
SecRuleEngine On
SecAction "phase:2,allow"
SecAction "phase:2,deny"
),
match_log => {
error => [ qr/ModSecurity: Access allowed \(phase 2\). Unconditional match in SecAction/, 1 ],
},
match_response => {
status => qr/^200$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
),
},
{
type => "rule",
comment => "allow action in phase:3",
conf => qq(
SecRuleEngine On
SecAction "phase:3,allow"
SecAction "phase:3,deny"
),
match_log => {
error => [ qr/ModSecurity: Access allowed \(phase 3\). Unconditional match in SecAction/, 1 ],
},
match_response => {
status => qr/^200$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
),
},
{
type => "rule",
comment => "allow action in phase:4",
conf => qq(
SecRuleEngine On
SecAction "phase:4,allow"
SecAction "phase:4,deny"
),
match_log => {
error => [ qr/ModSecurity: Access allowed \(phase 4\). Unconditional match in SecAction/, 1 ],
},
match_response => {
status => qr/^200$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
),
},
### Deny
{
type => "rule",
comment => "deny action in phase:1",
conf => qq(
SecRuleEngine On
SecAction "phase:1,deny"
),
match_log => {
error => [ qr/Access denied with code 403 \(phase 1\). Unconditional match in SecAction./, 1 ],
},
match_response => {
status => qr/^403$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
),
},
{
type => "rule",
comment => "deny action in phase:2",
conf => qq(
SecRuleEngine On
SecAction "phase:2,deny"
),
match_log => {
error => [ qr/Access denied with code 403 \(phase 2\). Unconditional match in SecAction./, 1 ],
},
match_response => {
status => qr/^403$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
),
},
{
type => "rule",
comment => "deny action in phase:3",
conf => qq(
SecRuleEngine On
SecAction "phase:3,deny"
),
match_log => {
error => [ qr/Access denied with code 403 \(phase 3\). Unconditional match in SecAction./, 1 ],
},
match_response => {
status => qr/^403$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
),
},
{
type => "rule",
comment => "deny action in phase:4",
conf => qq(
SecRuleEngine On
SecAction "phase:4,deny"
),
match_log => {
error => [ qr/Access denied with code 403 \(phase 4\). Unconditional match in SecAction./, 1 ],
},
match_response => {
status => qr/^403$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
),
},
### Drop
{
type => "rule",
comment => "drop action in phase:1",
conf => qq(
SecRuleEngine On
SecAction "phase:1,drop"
),
match_log => {
error => [ qr/Access denied with connection close \(phase 1\). Unconditional match in SecAction./, 1 ],
},
match_response => {
status => qr/^500$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
),
},
{
type => "rule",
comment => "drop action in phase:2",
conf => qq(
SecRuleEngine On
SecAction "phase:2,drop"
),
match_log => {
error => [ qr/Access denied with connection close \(phase 2\). Unconditional match in SecAction./, 1 ],
},
match_response => {
status => qr/^500$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
),
},
{
type => "rule",
comment => "drop action in phase:3",
conf => qq(
SecRuleEngine On
SecAction "phase:3,drop"
),
match_log => {
error => [ qr/Access denied with connection close \(phase 3\). Unconditional match in SecAction./, 1 ],
},
match_response => {
status => qr/^500$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
),
},
{
type => "rule",
comment => "drop action in phase:4",
conf => qq(
SecRuleEngine On
SecAction "phase:4,drop"
),
match_log => {
error => [ qr/Access denied with connection close \(phase 4\). Unconditional match in SecAction./, 1 ],
},
match_response => {
status => qr/^500$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
),
},
### Redirect
{
type => "rule",
comment => "redirect action in phase:1 (get)",
conf => qq(
SecRuleEngine On
SecRule REQUEST_URI "\@streq /test2.txt" "phase:1,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt'"
),
match_log => {
error => [ qr/ModSecurity: Access denied with redirection to .* using status 302 \(phase 1\)/, 1 ],
},
match_response => {
status => qr/^200$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
),
},
{
type => "rule",
comment => "redirect action in phase:2 (get)",
conf => qq(
SecRuleEngine On
SecRule REQUEST_URI "\@streq /test2.txt" "phase:2,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt'"
),
match_log => {
error => [ qr/ModSecurity: Access denied with redirection to .* using status 302 \(phase 2\)/, 1 ],
},
match_response => {
status => qr/^200$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
),
},
{
type => "rule",
comment => "redirect action in phase:3 (get)",
conf => qq(
SecRuleEngine On
SecRule REQUEST_URI "\@streq /test2.txt" "phase:3,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt'"
),
match_log => {
error => [ qr/ModSecurity: Access denied with redirection to .* using status 302 \(phase 3\)/, 1 ],
},
match_response => {
status => qr/^200$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
),
},
{
type => "rule",
comment => "redirect action in phase:4 (get)",
conf => qq(
SecRuleEngine On
SecRule REQUEST_URI "\@streq /test2.txt" "phase:4,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt'"
),
match_log => {
error => [ qr/ModSecurity: Access denied with redirection to .* using status 302 \(phase 4\)/, 1 ],
},
match_response => {
status => qr/^200$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
),
},
### Proxy
{
type => "rule",
comment => "proxy action in phase:1 (get)",
conf => qq(
SecRuleEngine On
SecRule REQUEST_URI "\@streq /test2.txt" "phase:1,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt'"
),
match_log => {
error => [ qr/ModSecurity: Access denied using proxy to \(phase 1\)/, 1 ],
},
match_response => {
status => qr/^200$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
),
},
{
type => "rule",
comment => "proxy action in phase:2 (get)",
conf => qq(
SecRuleEngine On
SecRule REQUEST_URI "\@streq /test2.txt" "phase:2,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt'"
),
match_log => {
error => [ qr/ModSecurity: Access denied using proxy to \(phase 2\)/, 1 ],
},
match_response => {
status => qr/^200$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
),
},
{
type => "rule",
comment => "proxy action in phase:3 (get)",
conf => qq(
SecRuleEngine On
SecRule REQUEST_URI "\@streq /test2.txt" "phase:3,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt'"
),
match_log => {
error => [ qr/ModSecurity: Access denied with code 500 \(phase 3\) \(Configuration Error: Proxy action requested but it does not work in output phases\)./, 1 ],
},
match_response => {
status => qr/^500$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
),
},
{
type => "rule",
comment => "proxy action in phase:4 (get)",
conf => qq(
SecRuleEngine On
SecRule REQUEST_URI "\@streq /test2.txt" "phase:4,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt'"
),
match_log => {
error => [ qr/ModSecurity: Access denied with code 500 \(phase 4\) \(Configuration Error: Proxy action requested but it does not work in output phases\)./, 1 ],
},
match_response => {
status => qr/^500$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
),
},

View File

@ -1,4 +1,3 @@
#!/usr/bin/perl
#!@PERL@
#
# Run regression tests.
@ -71,7 +70,7 @@ EOT
usage() if ($opt{h});
### Check startup script
$opt{a} = "apachectl" unless (defined $opt{a});
$opt{a} = "httpd" unless (defined $opt{a});
usage("Invalid Apache startup script: $opt{a}\n") unless (-e $opt{a});
### Defaults
@ -181,7 +180,7 @@ sub runfile {
}
if ($httpd_up) {
# Perform the request
# Perform the request and check response
if (exists $t{request}) {
my $resp = do_request($t{request});
if (!$resp) {
@ -189,21 +188,45 @@ sub runfile {
dbg("RESPONSE: ", $resp);
$rc = 1;
}
elsif (exists $t{match_response}{status}) {
unless ($resp->code =~ m/$t{match_response}{status}/) {
msg("incorrect status code " . $resp->code . ": $t{match_response}{status}");
else {
for my $key (keys %{ $t{match_response} || {}}) {
my($neg,$mtype) = ($key =~ m/^(-?)(.*)$/);
my $m = $t{match_response}{$key};
my $match = match_response($mtype, $resp, $m);
if ($neg and defined $match) {
$rc = 1;
msg("response $mtype matched: $m");
dbg("$LOG{$mtype}{buf}");
last;
}
elsif (!$neg and !defined $match) {
$rc = 1;
msg("response $mtype no match: $m");
dbg("$LOG{$mtype}{buf}");
last;
}
}
}
}
# Search for all log matches
if ($rc == 0 and exists $t{match_log} and defined $t{match_log}) {
for my $mtype (keys %{ $t{match_log} || {}}) {
my $m = $t{match_log}{$mtype};
unless (defined log_read_match($mtype, @{$m || []})) {
for my $key (keys %{ $t{match_log} || {}}) {
my($neg,$mtype) = ($key =~ m/^(-?)(.*)$/);
my $m = $t{match_log}{$key};
my $match = match_log($mtype, @{$m || []});
if ($neg and defined $match) {
$rc = 1;
msg("$mtype log match failed: $m->[0]");
msg("$mtype log matched: $m->[0]");
dbg("$LOG{$mtype}{buf}");
last;
}
elsif (!$neg and !defined $match) {
$rc = 1;
msg("$mtype log no match: $m->[0]");
dbg("$LOG{$mtype}{buf}");
last;
}
}
@ -251,12 +274,30 @@ sub do_request {
return;
}
sub log_read_match {
sub match_response {
my($name, $resp, $re) = @_;
msg("Warning: Empty regular expression.") if (!defined $re or $re eq "");
if ($name eq "status") {
return $@ if ($resp->code =~ m/$re/m);
}
elsif ($name eq "content") {
return $@ if ($resp->content =~ m/$re/m);
}
return;
}
sub match_log {
my($name, $re, $timeout) = @_;
my $t0 = gettimeofday();
my($fh,$rbuf) = ($LOG{$name}{fd}, \$LOG{$name}{buf});
my $n = length($$rbuf);
msg("Warning: Empty regular expression.") if (!defined $re or $re eq "");
$timeout = 0 unless (defined $timeout);
do {
@ -267,7 +308,7 @@ sub log_read_match {
sleep 0.1;
} while (gettimeofday - $t0 < $timeout);
return undef;
return;
}
sub escape {
@ -288,7 +329,11 @@ sub dbg {
}
sub msg {
print STDOUT "@_\n" if (@_);
return unless(@_);
my $out = join "", map {
(ref $_ ne "" ? Dumper($_) : $_)
} @_;
print STDOUT "$out\n";
}
sub quit {
@ -350,7 +395,7 @@ sub httpd_start {
}
# Look for startup msg
unless (defined log_read_match("error", qr/resuming normal operations/, 10)) {
unless (defined match_log("error", qr/resuming normal operations/, 10)) {
quit(1, "Httpd server failed to start.");
}
@ -396,7 +441,7 @@ sub httpd_stop {
}
# Look for startup msg
unless (defined log_read_match("error", qr/caught SIG[A-Z]+, shutting down/, 10)) {
unless (defined match_log("error", qr/caught SIG[A-Z]+, shutting down/, 10)) {
quit(1, "Httpd server failed to shutdown.");
}
@ -442,7 +487,7 @@ sub httpd_reload {
}
# Look for startup msg
unless (defined log_read_match("error", qr/resuming normal operations/, 10)) {
unless (defined match_log("error", qr/resuming normal operations/, 10)) {
quit(1, "Httpd server failed to reload.");
}