Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for generating transaction ID in nginx #126

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,51 @@ server {
}
```

modsecurity_transaction_id
--------------------------
**syntax:** *modsecurity_transaction_id string*

**context:** *http, server, location*

**default:** *no*

Allows to pass transaction ID from nginx instead of generating it in the library.
This can be useful for tracing purposes, e.g. consider this configuration:

```nginx
log_format extended '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" $request_id';

server {
server_name host1;
modsecurity on;
modsecurity_transaction_id "host1-$request_id";
access_log logs/host1-access.log extended;
error_log logs/host1-error.log;
location / {
...
}
}

server {
server_name host2;
modsecurity on;
modsecurity_transaction_id "host2-$request_id";
access_log logs/host2-access.log extended;
error_log logs/host2-error.log;
location / {
...
}
}
```

Using a combination of log_format and modsecurity_transaction_id you will
be able to find correlations between access log and error log entries
using the same unique identificator.

String can contain variables.


# Contributing

Expand Down
2 changes: 2 additions & 0 deletions src/ngx_http_modsecurity_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ typedef struct {
Rules *rules_set;

void *pool;

ngx_http_complex_value_t *transaction_id;
} ngx_http_modsecurity_conf_t;


Expand Down
66 changes: 63 additions & 3 deletions src/ngx_http_modsecurity_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ ngx_http_modsecurity_create_ctx(ngx_http_request_t *r)
ngx_http_modsecurity_conf_t *loc_cf = NULL;
ngx_http_modsecurity_conf_t *cf = NULL;
ngx_pool_cleanup_t *cln = NULL;
ngx_str_t s;

ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_modsecurity_ctx_t));
if (ctx == NULL)
Expand All @@ -250,7 +251,15 @@ ngx_http_modsecurity_create_ctx(ngx_http_request_t *r)

dd("creating transaction with the following rules: '%p' -- ms: '%p'", loc_cf->rules_set, cf->modsec);

ctx->modsec_transaction = msc_new_transaction(cf->modsec, loc_cf->rules_set, r->connection->log);
if (loc_cf->transaction_id) {
if (ngx_http_complex_value(r, loc_cf->transaction_id, &s) != NGX_OK) {
return NGX_CONF_ERROR;
}
ctx->modsec_transaction = msc_new_transaction_with_id(cf->modsec, loc_cf->rules_set, (char *) s.data, r->connection->log);

} else {
ctx->modsec_transaction = msc_new_transaction(cf->modsec, loc_cf->rules_set, r->connection->log);
}

dd("transaction created");

Expand Down Expand Up @@ -352,6 +361,36 @@ char *ngx_conf_set_rules_remote(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
}


char *ngx_conf_set_transaction_id(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
ngx_str_t *value;
ngx_http_complex_value_t cv;
ngx_http_compile_complex_value_t ccv;
ngx_http_modsecurity_conf_t *mcf = conf;

value = cf->args->elts;

ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

ccv.cf = cf;
ccv.value = &value[1];
ccv.complex_value = &cv;
ccv.zero = 1;

if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}

mcf->transaction_id = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
if (mcf->transaction_id == NULL) {
return NGX_CONF_ERROR;
}

*mcf->transaction_id = cv;

return NGX_CONF_OK;
}


static ngx_command_t ngx_http_modsecurity_commands[] = {
{
ngx_string("modsecurity"),
Expand Down Expand Up @@ -385,6 +424,14 @@ static ngx_command_t ngx_http_modsecurity_commands[] = {
offsetof(ngx_http_modsecurity_conf_t, enable),
NULL
},
{
ngx_string("modsecurity_transaction_id"),
NGX_HTTP_LOC_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_MAIN_CONF|NGX_CONF_1MORE,
ngx_conf_set_transaction_id,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command
};

Expand Down Expand Up @@ -542,19 +589,30 @@ static void *ngx_http_modsecurity_create_conf(ngx_conf_t *cf)
{
ngx_pool_cleanup_t *cln = NULL;
ngx_http_modsecurity_conf_t *conf = (ngx_http_modsecurity_conf_t *)
ngx_palloc(cf->pool, sizeof(ngx_http_modsecurity_conf_t));
ngx_pcalloc(cf->pool, sizeof(ngx_http_modsecurity_conf_t));

if (conf == NULL)
{
dd("Failed to allocate space for ModSecurity configuration");
return NGX_CONF_ERROR;
}

/*
* set by ngx_pcalloc():
*
* conf->modsec = NULL;
* conf->enable = 0;
* conf->sanity_checks_enabled = 0;
* conf->rules_set = NULL;
* conf->pool = NULL;
* conf->transaction_id = NULL;
*/

conf->enable = NGX_CONF_UNSET;
conf->sanity_checks_enabled = NGX_CONF_UNSET;
conf->rules_set = msc_create_rules_set();
conf->modsec = NULL;
conf->pool = cf->pool;
conf->transaction_id = NGX_CONF_UNSET_PTR;

cln = ngx_pool_cleanup_add(cf->pool, 0);
if (cln == NULL) {
Expand Down Expand Up @@ -587,6 +645,7 @@ ngx_http_modsecurity_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)

ngx_conf_merge_value(c->enable, p->enable, 0);
ngx_conf_merge_value(c->sanity_checks_enabled, p->sanity_checks_enabled, 0);
ngx_conf_merge_ptr_value(c->transaction_id, p->transaction_id, NULL);

#if defined(MODSECURITY_DDEBUG) && (MODSECURITY_DDEBUG)
dd("PARENT RULES");
Expand Down Expand Up @@ -630,6 +689,7 @@ ngx_http_modsecurity_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)

ngx_conf_merge_value(c->enable, p->enable, 0);
ngx_conf_merge_value(c->sanity_checks_enabled, p->sanity_checks_enabled, 0);
ngx_conf_merge_ptr_value(c->transaction_id, p->transaction_id, NULL);

#if defined(MODSECURITY_DDEBUG) && (MODSECURITY_DDEBUG)
dd("PARENT RULES");
Expand Down
167 changes: 167 additions & 0 deletions tests/modsecurity-transaction-id.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
#!/usr/bin/perl

# (C) Andrei Belov

# Tests for ModSecurity-nginx connector (modsecurity_transaction_id).

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

use warnings;
use strict;

use Test::More;

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

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

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

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

my $t = Test::Nginx->new()->plan(5)->write_file_expand('nginx.conf', <<'EOF');

%%TEST_GLOBALS%%

daemon off;

events {
}

http {
%%TEST_GLOBALS_HTTP%%

modsecurity_transaction_id "tid-HTTP-DEFAULT-$request_id";

server {
listen 127.0.0.1:8080;
server_name server1;

location / {
error_log %%TESTDIR%%/e_s1l1.log info;
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:1,log,deny,status:403"
SecRule ARGS "@streq block403" "id:4,phase:1,status:403,block"
';
}
}

server {
listen 127.0.0.1:8080;
server_name server2;

modsecurity_transaction_id "tid-SERVER-DEFAULT-$request_id";

location / {
error_log %%TESTDIR%%/e_s2l1.log info;
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:1,log,deny,status:403"
SecRule ARGS "@streq block403" "id:4,phase:1,status:403,block"
';
}

location /specific {
error_log %%TESTDIR%%/e_s2l2.log info;
modsecurity on;
modsecurity_transaction_id "tid-LOCATION-SPECIFIC-$request_id";
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:1,log,deny,status:403"
SecRule ARGS "@streq block403" "id:4,phase:1,status:403,block"
';
}

location /debuglog {
modsecurity on;
modsecurity_transaction_id "tid-DEBUG-$request_id";
modsecurity_rules '
SecRuleEngine On
SecDebugLog %%TESTDIR%%/modsec_debug.log
SecDebugLogLevel 4
SecDefaultAction "phase:1,log,deny,status:403"
SecRule ARGS "@streq block403" "id:4,phase:1,status:403,block"
';
}

location /auditlog {
modsecurity on;
modsecurity_transaction_id "tid-AUDIT-$request_id";
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:1,log,deny,status:403"
SecAuditEngine On
SecAuditLogParts A
SecAuditLog %%TESTDIR%%/modsec_audit.log
SecAuditLogType Serial
SecAuditLogStorageDir %%TESTDIR%%/
SecRule ARGS "@streq block403" "id:4,phase:1,status:403,block"
';
}
}
}
EOF

$t->run();

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

# charge limit_req

http(<<EOF);
GET /?what=block403 HTTP/1.0
Host: server1

EOF

isnt(lines($t, 'e_s1l1.log', 'unique_id "tid-HTTP-DEFAULT-'), 0, 'http default');

http(<<EOF);
GET /?what=block403 HTTP/1.0
Host: server2

EOF

isnt(lines($t, 'e_s2l1.log', 'unique_id "tid-SERVER-DEFAULT-'), 0, 'server default');

http(<<EOF);
GET /specific/?what=block403 HTTP/1.0
Host: server2

EOF

isnt(lines($t, 'e_s2l2.log', 'unique_id "tid-LOCATION-SPECIFIC-'), 0, 'location specific');

http(<<EOF);
GET /debuglog/?what=block403 HTTP/1.0
Host: server2

EOF

isnt(lines($t, 'modsec_debug.log', 'tid-DEBUG-'), 0, 'libmodsecurity debug log');

http(<<EOF);
GET /auditlog/?what=block403 HTTP/1.0
Host: server2

EOF

isnt(lines($t, 'modsec_audit.log', 'tid-AUDIT-'), 0, 'libmodsecurity audit log');

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

sub lines {
my ($t, $file, $pattern) = @_;
my $path = $t->testdir() . '/' . $file;
open my $fh, '<', $path or return "$!";
my $value = map { $_ =~ /\Q$pattern\E/ } (<$fh>);
close $fh;
return $value;
}

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