diff --git a/README.md b/README.md index 4b8719d..1ae2c58 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/ngx_http_modsecurity_common.h b/src/ngx_http_modsecurity_common.h index 7108347..79355d1 100644 --- a/src/ngx_http_modsecurity_common.h +++ b/src/ngx_http_modsecurity_common.h @@ -95,6 +95,8 @@ typedef struct { Rules *rules_set; void *pool; + + ngx_http_complex_value_t *transaction_id; } ngx_http_modsecurity_conf_t; diff --git a/src/ngx_http_modsecurity_module.c b/src/ngx_http_modsecurity_module.c index 0c491cc..d00d813 100644 --- a/src/ngx_http_modsecurity_module.c +++ b/src/ngx_http_modsecurity_module.c @@ -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) @@ -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"); @@ -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"), @@ -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 }; @@ -542,7 +589,7 @@ 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) { @@ -550,11 +597,22 @@ static void *ngx_http_modsecurity_create_conf(ngx_conf_t *cf) 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) { @@ -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"); @@ -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"); diff --git a/tests/modsecurity-transaction-id.t b/tests/modsecurity-transaction-id.t new file mode 100644 index 0000000..f431c10 --- /dev/null +++ b/tests/modsecurity-transaction-id.t @@ -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(<testdir() . '/' . $file; + open my $fh, '<', $path or return "$!"; + my $value = map { $_ =~ /\Q$pattern\E/ } (<$fh>); + close $fh; + return $value; +} + +###############################################################################