From 7b2ca1617e9bff088369cf2fd5807a09cb4ac6e8 Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Wed, 15 Jul 2015 13:57:02 -0700 Subject: [PATCH] first pass at JSON logging implementation --- apache2/msc_logging.c | 475 ++++++++++++++++++++++++++++++++++++- apache2/msc_logging_json.h | 13 + configure.ac | 18 +- 3 files changed, 494 insertions(+), 12 deletions(-) create mode 100644 apache2/msc_logging_json.h diff --git a/apache2/msc_logging.c b/apache2/msc_logging.c index 3323fac9..61e21ad7 100644 --- a/apache2/msc_logging.c +++ b/apache2/msc_logging.c @@ -25,6 +25,11 @@ #include "apr_version.h" #include +#ifdef WITH_JSON_LOGGING +#include +#include "msc_logging_json.h" +#endif + /** * Write the supplied data to the audit log (if the FD is ready), update * the size counters, update the hash context. @@ -381,6 +386,40 @@ static void sec_auditlog_write_producer_header(modsec_rec *msr) { sec_auditlog_write(msr, ".\n", 2); } +#ifdef WITH_JSON_LOGGING +/** + * Ouput the Producer header into a JSON generator + */ +static void sec_auditlog_write_producer_header_json(modsec_rec *msr, yajl_gen g) { + char **signatures = NULL; + int i; + + // this is written no matter what + yajl_string(g, "producer"); + + /* Try to write verything in one go. */ + if (msr->txcfg->component_signatures->nelts == 0) { + yajl_string(g, MODSEC_MODULE_NAME_FULL); + + return; + } + + // we'll need an array if there are component signatures + yajl_gen_array_open(g); + + /* Start with the ModSecurity signature. */ + yajl_string(g, MODSEC_MODULE_NAME_FULL); + + /* Then loop through the components and output individual signatures. */ + signatures = (char **)msr->txcfg->component_signatures->elts; + for(i = 0; i < msr->txcfg->component_signatures->nelts; i++) { + yajl_string(g, (char *)signatures[i]); + } + + yajl_gen_array_close(g); // array for producers is finished +} +#endif + /* * \brief This function will returns the next chain node * @@ -480,6 +519,66 @@ static int chained_is_matched(modsec_rec *msr, const msre_rule *next_rule) { return 0; } +#ifdef WITH_JSON_LOGGING +/** + * Write detailed information about a rule and its actionset into a JSON generator + */ +static void write_rule_json(modsec_rec *msr, const msre_rule *rule, yajl_gen g) { + int present = 0; + + yajl_gen_map_open(g); + + yajl_string(g, "actionset"); + yajl_gen_map_open(g); + if (rule->actionset->id) { + yajl_kv_string(g, "id", rule->actionset->id); + } + if (rule->actionset->rev) { + yajl_kv_string(g, "rev", rule->actionset->rev); + } + if (rule->actionset->msg) { + yajl_kv_string(g, "msg", rule->actionset->msg); + } + if (rule->actionset->version) { + yajl_kv_string(g, "version", rule->actionset->version); + } + if (rule->actionset->logdata) { + yajl_kv_string(g, "logdata", rule->actionset->logdata); + } + if (rule->actionset->severity != NOT_SET) { + yajl_kv_int(g, "severity", rule->actionset->severity); + } + if (rule->actionset->accuracy != NOT_SET) { + yajl_kv_int(g, "accuracy", rule->actionset->accuracy); + } + if (rule->actionset->maturity != NOT_SET) { + yajl_kv_int(g, "maturity", rule->actionset->maturity); + } + if (rule->actionset->phase != NOT_SET) { + yajl_kv_int(g, "phase", rule->actionset->phase); + } + yajl_kv_bool(g, "is_chained", rule->actionset->is_chained); + yajl_gen_map_close(g); + + yajl_string(g, "operator"); + yajl_gen_map_open(g); + yajl_kv_string(g, "operator", rule->op_name); + yajl_kv_string(g, "operator_param", rule->op_param); + yajl_kv_string(g, "target", rule->p1); + yajl_gen_map_close(g); + + yajl_string(g, "config"); + yajl_gen_map_open(g); + yajl_kv_string(g, "filename", rule->filename); + yajl_kv_int(g, "line_num", rule->line_num); + yajl_gen_map_close(g); + + yajl_kv_bool(g, "is_matched", chained_is_matched(msr, rule)); + + yajl_gen_map_close(g); +} +#endif + /** * Produce an audit log entry. */ @@ -501,9 +600,14 @@ void sec_audit_logger(modsec_rec *msr) { char *buf = NULL, *pat = NULL; msc_parm *mparm = NULL; int arg_min, arg_max, sanitize_matched; +#ifdef WITH_JSON_LOGGING + yajl_gen g; +#endif +#ifndef WITH_JSON_LOGGING /* the boundary is used by both audit log types */ msr->new_auditlog_boundary = create_auditlog_boundary(msr->r); +#endif /* Return silently if we don't have a request line. This * means we will not be logging request timeouts. @@ -591,29 +695,72 @@ void sec_audit_logger(modsec_rec *msr) { } } - /* AUDITLOG_PART_HEADER */ +#ifdef WITH_JSON_LOGGING + /** + * allocate the buffer for the JSON generator + * passing null will force yajl to use malloc/realloc/free + * need to perf test using APR routines + */ + g = yajl_gen_alloc(NULL); + /** + * don't pretty print JSON by default + * this is harder to eyeball but much easier to parse programmatically + */ + yajl_gen_config(g, yajl_gen_beautify, 0); + + yajl_gen_map_open(g); // IT BEGINS +#endif + + + /* AUDITLOG_PART_HEADER */ +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_HEADER); sec_auditlog_write(msr, text, strlen(text)); - /* Format: time transaction_id remote_addr remote_port local_addr local_port */ text = apr_psprintf(msr->mp, "[%s] %s %s %u %s %u", current_logtime(msr->mp), msr->txid, msr->remote_addr, msr->remote_port, msr->local_addr, msr->local_port); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_string(g, "transaction"); + yajl_gen_map_open(g); // transaction top-level key + yajl_kv_string(g, "time", current_logtime(msr->mp)); + yajl_kv_string(g, "transaction_id", msr->txid); + yajl_kv_string(g, "remote_address", msr->remote_addr); + yajl_kv_int(g, "remote_port", (int)msr->remote_port); // msr->remote_port is unsigned, yajl wants signed + yajl_kv_string(g, "local_address", msr->local_addr); + yajl_kv_int(g, "local_port", (int)msr->local_port); + + yajl_gen_map_close(g); // transaction top-level key is finished + + yajl_string(g, "request"); + yajl_gen_map_open(g); // request top-level key +#endif /* AUDITLOG_PART_REQUEST_HEADERS */ if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_REQUEST_HEADERS) != NULL) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_REQUEST_HEADERS); sec_auditlog_write(msr, text, strlen(text)); +#endif sanitize_request_line(msr); - +#ifndef WITH_JSON_LOGGING sec_auditlog_write(msr, msr->request_line, strlen(msr->request_line)); sec_auditlog_write(msr, "\n", 1); +#else + // Request Line doesn't get its own map for now. should it? + yajl_kv_string(g, "request_line", msr->request_line); +#endif + +#ifdef WITH_JSON_LOGGING + yajl_string(g, "headers"); + yajl_gen_map_open(g); // separate map for request headers +#endif arr = apr_table_elts(msr->request_headers); te = (apr_table_entry_t *)arr->elts; @@ -624,7 +771,13 @@ void sec_audit_logger(modsec_rec *msr) { for (i = 0; i < arr->nelts; i++) { sanitized_partial = 0; sanitize_matched = 0; +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "%s: %s\n", te[i].key, te[i].val); +#else + // write the key no matter what + // since sanitization only occurs on the value + yajl_string(g, te[i].key); +#endif if (apr_table_get(msr->request_headers_to_sanitize, te[i].key) != NULL) { buf = apr_psprintf(msr->mp, "%s",text+strlen(te[i].key)+2); @@ -656,13 +809,30 @@ void sec_audit_logger(modsec_rec *msr) { } if(sanitized_partial == 1 && sanitize_matched == 0) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "%s: %s\n", te[i].key, buf); +#else + yajl_string(g, buf); +#endif } else { +#ifndef WITH_JSON_LOGGING memset(text + strlen(te[i].key) + 2, '*', strlen(te[i].val)); +#else + yajl_string(g, "****"); // fix this later +#endif } } +#ifndef WITH_JSON_LOGGING sec_auditlog_write(msr, text, strlen(text)); +#else + // we diverge from the original logic a bit because we always print the key + // at this no point sanitization had occured, so we just print the value + yajl_string(g, te[i].val); +#endif } +#ifdef WITH_JSON_LOGGING + yajl_gen_map_close(g); // request headers map is finished +#endif } /* AUDITLOG_PART_REQUEST_BODY */ @@ -749,9 +919,13 @@ void sec_audit_logger(modsec_rec *msr) { unsigned int chunk_offset = 0; unsigned int sanitize_offset = 0; unsigned int sanitize_length = 0; - +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_REQUEST_BODY); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_string(g, "body"); + yajl_gen_array_open(g); // use an array here because we're writing in chunks +#endif for(;;) { rc = modsecurity_request_body_retrieve(msr, &chunk, -1, &my_error_msg); @@ -810,7 +984,11 @@ void sec_audit_logger(modsec_rec *msr) { /* Write the sanitized chunk to the log * and advance to the next chunk. */ +#ifndef WITH_JSON_LOGGING sec_auditlog_write(msr, chunk->data, chunk->length); +#else + yajl_string(g, chunk->data); +#endif chunk_offset += chunk->length; } @@ -819,6 +997,10 @@ void sec_audit_logger(modsec_rec *msr) { } } +#ifdef WITH_JSON_LOGGING + yajl_gen_array_close(g); // request body chunks array is finished +#endif + if (rc < 0) { msr_log(msr, 1, "Audit log: %s", my_error_msg); } @@ -838,29 +1020,58 @@ void sec_audit_logger(modsec_rec *msr) { if (buffer == NULL) { msr_log(msr, 1, "Audit log: Failed to reconstruct request body."); } else { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_FAKE_REQUEST_BODY); sec_auditlog_write(msr, text, strlen(text)); sec_auditlog_write(msr, buffer, strlen(buffer)); +#else + // this is a key instead 'request', doesn't need an array or map since it's one value + yajl_kv_string(g, "fake_body", buffer); +#endif } } } +#ifdef WITH_JSON_LOGGING + yajl_gen_map_close(g); // request top-level key is finished + + yajl_string(g, "response"); + yajl_gen_map_open(g); // response top-level key +#endif + /* AUDITLOG_PART_A_RESPONSE_HEADERS */ if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_A_RESPONSE_HEADERS) != NULL) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_A_RESPONSE_HEADERS); sec_auditlog_write(msr, text, strlen(text)); +#endif /* There are no response headers (or the status line) in HTTP 0.9 */ if (msr->response_headers_sent) { if (msr->status_line != NULL) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "%s %s\n", msr->response_protocol, msr->status_line); +#else + yajl_kv_string(g, "protocol", msr->response_protocol); + yajl_kv_string(g, "status", msr->status_line); +#endif } else { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "%s %u\n", msr->response_protocol, msr->response_status); +#else + yajl_kv_string(g, "protocol", msr->response_protocol); + yajl_kv_int(g, "status", (int)msr->response_status); +#endif } +#ifndef WITH_JSON_LOGGING sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_string(g, "headers"); + yajl_gen_map_open(g); // separate map for response headers +#endif /* Output headers */ @@ -873,7 +1084,13 @@ void sec_audit_logger(modsec_rec *msr) { for (i = 0; i < arr->nelts; i++) { sanitized_partial = 0; sanitize_matched = 0; +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "%s: %s\n", te[i].key, te[i].val); +#else + // write the key no matter what + // since sanitization only occurs on the value + yajl_string(g, te[i].key); +#endif if (apr_table_get(msr->response_headers_to_sanitize, te[i].key) != NULL) { buf = apr_psprintf(msr->mp, "%s",text+strlen(te[i].key)+2); @@ -905,13 +1122,30 @@ void sec_audit_logger(modsec_rec *msr) { } if(sanitized_partial == 1 && sanitize_matched == 0) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "%s: %s\n", te[i].key, buf); +#else + yajl_string(g, buf); +#endif } else { +#ifndef WITH_JSON_LOGGING memset(text + strlen(te[i].key) + 2, '*', strlen(te[i].val)); +#else + yajl_string(g, "****"); // fix this later +#endif } } +#ifndef WITH_JSON_LOGGING sec_auditlog_write(msr, text, strlen(text)); +#else + // we diverge from the original logic a bit because we always print the key + // at this point no sanitization had occured, so we just print the value + yajl_string(g, te[i].val); +#endif } +#ifdef WITH_JSON_LOGGING + yajl_gen_map_close(g); // response headers map is finised +#endif } } @@ -921,51 +1155,101 @@ void sec_audit_logger(modsec_rec *msr) { if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_RESPONSE_BODY) != NULL) { if (msr->resbody_data != NULL) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_RESPONSE_BODY); sec_auditlog_write(msr, text, strlen(text)); sec_auditlog_write(msr, msr->resbody_data, msr->resbody_length); +#else + yajl_kv_string(g, "body", msr->resbody_data); +#endif wrote_response_body = 1; } } +#ifdef WITH_JSON_LOGGING + yajl_gen_map_close(g); // response top-level key is finished + + yajl_string(g, "data"); + yajl_gen_map_open(g); // data top-level key +#endif + /* AUDITLOG_PART_TRAILER */ if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_TRAILER) != NULL) { apr_time_t now = apr_time_now(); - +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_TRAILER); sec_auditlog_write(msr, text, strlen(text)); +#endif /* Messages */ +#ifdef WITH_JSON_LOGGING + yajl_string(g, "messages"); + yajl_gen_array_open(g); +#endif for(i = 0; i < msr->alerts->nelts; i++) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "Message: %s\n", ((char **)msr->alerts->elts)[i]); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_string(g, ((char **)msr->alerts->elts)[i]); +#endif } +#ifdef WITH_JSON_LOGGING + yajl_gen_array_close(g); +#endif /* Apache error messages */ +#ifdef WITH_JSON_LOGGING + yajl_string(g, "error_messages"); + yajl_gen_array_open(g); +#endif for(i = 0; i < msr->error_messages->nelts; i++) { error_message_t *em = (((error_message_t **)msr->error_messages->elts)[i]); +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "Apache-Error: %s\n", format_error_log_message(msr->mp, em)); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_string(g, format_error_log_message(msr->mp, em)); +#endif } +#ifdef WITH_JSON_LOGGING + yajl_gen_array_close(g); +#endif /* Action */ if (msr->was_intercepted) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "Action: Intercepted (phase %d)\n", msr->intercept_phase); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_string(g, "action"); + yajl_gen_map_open(g); + yajl_kv_bool(g, "intercepted", 1); + yajl_kv_int(g, "phase", msr->intercept_phase); + yajl_gen_map_close(g); +#endif } /* Apache-Handler */ if (msr->r->handler != NULL) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "Apache-Handler: %s\n", msr->r->handler); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_kv_string(g, "handler", msr->r->handler); +#endif } /* Stopwatch; left in for compatibility reasons */ text = apr_psprintf(msr->mp, "Stopwatch: %" APR_TIME_T_FMT " %" APR_TIME_T_FMT " (- - -)\n", msr->request_time, (now - msr->request_time)); +#ifndef WITH_JSON_LOGGING sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_kv_string(g, "stopwatch", text); +#endif /* Stopwatch2 */ { @@ -973,26 +1257,46 @@ void sec_audit_logger(modsec_rec *msr) { text = apr_psprintf(msr->mp, "Stopwatch2: %" APR_TIME_T_FMT " %" APR_TIME_T_FMT "; %s\n", msr->request_time, (now - msr->request_time), perf_all); - +#ifndef WITH_JSON_LOGGING sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_kv_string(g, "stopwatch2", text); +#endif } /* Our response body does not contain chunks */ /* ENH Only write this when the output was chunked. */ /* ENH Add info when request body was decompressed, dechunked too. */ if (wrote_response_body) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "Response-Body-Transformed: Dechunked\n"); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_kv_bool(g, "response_body_dechunked", 1); +#endif } +#ifndef WITH_JSON_LOGGING sec_auditlog_write_producer_header(msr); +#else + sec_auditlog_write_producer_header_json(msr, g); +#endif /* Server */ if (msr->server_software != NULL) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "Server: %s\n", msr->server_software); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_kv_string(g, "server", msr->server_software); +#endif } +#ifdef WITH_JSON_LOGGING + yajl_string(g, "sanitized"); + yajl_gen_map_open(g); // open a separate map for sanitized values +#endif + /* Sanitised arguments */ { const apr_array_header_t *tarr; @@ -1002,16 +1306,31 @@ void sec_audit_logger(modsec_rec *msr) { telts = (const apr_table_entry_t*)tarr->elts; if (tarr->nelts > 0) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "Sanitised-Args: "); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_string(g, "args"); + yajl_gen_array_open(g); +#endif } for(i = 0; i < tarr->nelts; i++) { msc_arg *arg = (msc_arg *)telts[i].val; +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "%s\"%s\"%s", ((i == 0) ? "" : ", "), log_escape(msr->mp, arg->name), ((i == (tarr->nelts - 1)) ? ".\n" : "")); sec_auditlog_write(msr, text, strlen(text)); +#else + // yay arrays actually make it easier here + yajl_string(g, log_escape(msr->mp, arg->name)); +#endif } +#ifdef WITH_JSON_LOGGING + if (tarr->nelts > 0) { + yajl_gen_array_close(g); + } +#endif } /* Sanitised request headers */ @@ -1023,15 +1342,29 @@ void sec_audit_logger(modsec_rec *msr) { telts = (const apr_table_entry_t*)tarr->elts; if (tarr->nelts > 0) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "Sanitised-Request-Headers: "); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_string(g, "request_headers"); + yajl_gen_array_open(g); +#endif } for(i = 0; i < tarr->nelts; i++) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "%s\"%s\"%s", ((i == 0) ? "" : ", "), log_escape(msr->mp, telts[i].key), ((i == (tarr->nelts - 1)) ? ".\n" : "")); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_string(g, log_escape(msr->mp, telts[i].key)); +#endif } +#ifdef WITH_JSON_LOGGING + if (tarr->nelts > 0) { + yajl_gen_array_close(g); + } +#endif } /* Sanitised response headers */ @@ -1043,40 +1376,85 @@ void sec_audit_logger(modsec_rec *msr) { telts = (const apr_table_entry_t*)tarr->elts; if (tarr->nelts > 0) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "Sanitised-Response-Headers: "); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_string(g, "response_headers"); + yajl_gen_array_open(g); +#endif } for(i = 0; i < tarr->nelts; i++) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "%s\"%s\"%s", ((i == 0) ? "" : ", "), log_escape(msr->mp, telts[i].key), ((i == (tarr->nelts - 1)) ? ".\n" : "")); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_string(g, log_escape(msr->mp, telts[i].key)); +#endif } +#ifdef WITH_JSON_LOGGING + if (tarr->nelts > 0) { + yajl_gen_array_close(g); + } +#endif } +#ifdef WITH_JSON_LOGGING + yajl_gen_map_close(g); // sanitized args map is finished +#endif + /* Web application info. */ if ( ((msr->txcfg->webappid != NULL)&&(strcmp(msr->txcfg->webappid, "default") != 0)) || (msr->sessionid != NULL) || (msr->userid != NULL)) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "WebApp-Info: \"%s\" \"%s\" \"%s\"\n", msr->txcfg->webappid == NULL ? "-" : log_escape(msr->mp, msr->txcfg->webappid), msr->sessionid == NULL ? "-" : log_escape(msr->mp, msr->sessionid), msr->userid == NULL ? "-" : log_escape(msr->mp, msr->userid)); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_string(g, "webapp_info"); + yajl_gen_map_open(g); + + if (msr->txcfg->webappid != NULL) { + yajl_kv_string(g, "id", log_escape(msr->mp, msr->txcfg->webappid)); + } + if (msr->sessionid != NULL) { + yajl_kv_string(g, "session", log_escape(msr->mp, msr->sessionid)); + } + if (msr->userid != NULL) { + yajl_kv_string(g, "user_id", log_escape(msr->mp, msr->userid)); + } + + yajl_gen_map_close(g); +#endif } if ( ((msr->txcfg->sensor_id != NULL)&&(strcmp(msr->txcfg->sensor_id, "default") != 0))) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "Sensor-Id: \"%s\"\n", msr->txcfg->sensor_id == NULL ? "-" : log_escape(msr->mp, msr->txcfg->sensor_id)), sec_auditlog_write(msr, text, strlen(text)); +#else + if(msr->txcfg->sensor_id != NULL) { + yajl_kv_string(g, "sensor_id", log_escape(msr->mp, msr->txcfg->sensor_id)); + } +#endif } if (msr->txcfg->is_enabled > 0) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "Engine-Mode: \"%s\"\n", msr->txcfg->is_enabled == 1 ? "DETECTION_ONLY" : "ENABLED"), sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_kv_string(g, "engine_mode", (msr->txcfg->is_enabled == 1 ? "DETECTION_ONLY" : "ENABLED")); +#endif } /* Rule performance time */ @@ -1088,54 +1466,104 @@ void sec_audit_logger(modsec_rec *msr) { telts = (const apr_table_entry_t*)tarr->elts; if (tarr->nelts > 0) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "Rules-Performance-Info: "); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_string(g, "rules_performance_info"); + yajl_gen_map_open(g); // separate map for rule perf info +#endif } for(i = 0; i < tarr->nelts; i++) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "%s\"%s=%s\"%s", ((i == 0) ? "" : ", "), log_escape(msr->mp, telts[i].key), log_escape(msr->mp, telts[i].val), ((i == (tarr->nelts - 1)) ? ".\n" : "")); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_kv_string(g, log_escape(msr->mp, telts[i].key), log_escape(msr->mp, telts[i].val)); +#endif } +#ifdef WITH_JSON_LOGGING + if (tarr->nelts > 0) { + yajl_gen_map_close(g); // map for rule perf info is finished + } +#endif } - } +#ifdef WITH_JSON_LOGGING + yajl_gen_map_close(g); // data top-level key is finished +#endif + /* AUDITLOG_PART_UPLOADS */ if ((strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_UPLOADS) != NULL) && (msr->mpd != NULL)) { multipart_part **parts = NULL; unsigned int total_size = 0; int cfiles = 0; +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_UPLOADS); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_string(g, "uploads"); + yajl_gen_map_open(g); +#endif parts = (multipart_part **)msr->mpd->parts->elts; +#ifdef WITH_JSON_LOGGING + yajl_string(g, "info"); + yajl_gen_array_open(g); // separate array for upload info +#endif for(cfiles = 0; cfiles < msr->mpd->parts->nelts; cfiles++) { if (parts[cfiles]->type == MULTIPART_FILE) { if(parts[cfiles]->filename != NULL) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "%d,%u,\"%s\",\"%s\"\n", cfiles+1, parts[cfiles]->tmp_file_size, log_escape(msr->mp, parts[cfiles]->filename), log_escape(msr->mp, parts[cfiles]->content_type ? parts[cfiles]->content_type : "")); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_kv_int(g, "file_size", parts[cfiles]->tmp_file_size); + yajl_kv_string(g, "file_name", log_escape(msr->mp, parts[cfiles]->filename)); + yajl_kv_string(g, "content_type", parts[cfiles]->content_type ? parts[cfiles]->content_type : ""); +#endif total_size += parts[cfiles]->tmp_file_size; } } } +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "Total,%u\n", total_size); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_gen_array_close(g); // array for upload info is finished + yajl_kv_int(g, "total", total_size); + + yajl_gen_map_close(g); // uploads top-level key is finished +#endif } /* AUDITLOG_PART_MATCHEDRULES */ if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_MATCHEDRULES) != NULL) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_MATCHEDRULES); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_string(g, "matched_rules"); + yajl_gen_array_open(g); // matched_rules top-level key +#endif + /* Matched Rules */ + for(i = 0; i < msr->matched_rules->nelts; i++) { rule = ((msre_rule **)msr->matched_rules->elts)[i]; if ((rule != NULL) && (rule->actionset != NULL) && rule->actionset->is_chained && (rule->chain_starter == NULL)) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "%s\n", rule->unparsed); sec_auditlog_write(msr, text, strlen(text)); +#else + write_rule_json(msr, rule, g); +#endif do { if (rule->ruleset != NULL) { @@ -1146,32 +1574,59 @@ void sec_audit_logger(modsec_rec *msr) { present = chained_is_matched(msr,next_rule); if (present == 0) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "#%s\n",next_rule->unparsed); +#endif } else { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "%s\n",next_rule->unparsed); +#endif i++; } - +#ifndef WITH_JSON_LOGGING sec_auditlog_write(msr, text, strlen(text)); +#else + write_rule_json(msr, next_rule, g); +#endif } } rule = next_rule; } while (rule != NULL && rule->actionset != NULL && rule->actionset->is_chained); +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "\n"); sec_auditlog_write(msr, text, strlen(text)); +#endif } else { if ((rule != NULL) && (rule->actionset != NULL) && !rule->actionset->is_chained && (rule->chain_starter == NULL)) { +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "%s\n\n", rule->unparsed); sec_auditlog_write(msr, text, strlen(text)); +#else + write_rule_json(msr, rule, g); +#endif } } } +#ifdef WITH_JSON_LOGGING + yajl_gen_array_close(g); // matched_rules top-level key is finished +#endif + } - /* AUDITLOG_PART_ENDMARKER */ - +#ifndef WITH_JSON_LOGGING text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_ENDMARKER); sec_auditlog_write(msr, text, strlen(text)); +#else + yajl_gen_map_close(g); // box it up! + + const unsigned char *final_buf; + size_t len; + yajl_gen_get_buf(g, &final_buf, &len); + sec_auditlog_write(msr, final_buf, len); + + yajl_gen_clear(g); + yajl_gen_free(g); +#endif /* Return here if we were writing to a serial log * as it does not need an index file. diff --git a/apache2/msc_logging_json.h b/apache2/msc_logging_json.h new file mode 100644 index 00000000..e49b98c2 --- /dev/null +++ b/apache2/msc_logging_json.h @@ -0,0 +1,13 @@ +#include + +#include + +#define yajl_string(g, s) yajl_gen_string(g, (const unsigned char *)s, strlen(s)) + +#define yajl_kv_null(g, k) yajl_string(g, k); yajl_gen_null(g) + +#define yajl_kv_int(g, k, v) yajl_string(g, k); yajl_gen_integer(g, v) + +#define yajl_kv_string(g, k, v) yajl_string(g, k); yajl_string(g, v) + +#define yajl_kv_bool(g, k, v) yajl_string(g, k); yajl_gen_bool(g, v) diff --git a/configure.ac b/configure.ac index 75178858..bb2e42e3 100644 --- a/configure.ac +++ b/configure.ac @@ -275,6 +275,21 @@ if test "$build_docs" -eq 1; then TOPLEVEL_SUBDIRS="$TOPLEVEL_SUBDIRS docs" fi +# Add JSON audit logging +AC_ARG_ENABLE(json-logging, + AS_HELP_STRING([--enabled-json-logging], + [Enable JSON audit logging.]), +[ + if test "$enableval" != "no"; then + json_logging='-DWITH_JSON_LOGGING' + MODSEC_EXTRA_CFLAGS="$MODSEC_EXTRA_CFLAGS $json_logging" + else + json_logging='' + fi +], +[ + json_logging='' +]) # Add PCRE Studying @@ -659,8 +674,7 @@ else fi fi -MODSEC_EXTRA_CFLAGS="$pcre_study $pcre_match_limit $pcre_match_limit_recursion $pcre_jit $request_early $htaccess_config $lua_cache $debug_conf $debug_cache $debug_acmp $debug_mem $perf_meas $modsec_api $cpu_type" - +MODSEC_EXTRA_CFLAGS="$json_logging $pcre_study $pcre_match_limit $pcre_match_limit_recursion $pcre_jit $request_early $htaccess_config $lua_cache $debug_conf $debug_cache $debug_acmp $debug_mem $perf_meas $modsec_api $cpu_type" APXS_WRAPPER=build/apxs-wrapper APXS_EXTRA_CFLAGS="" for f in $EXTRA_CFLAGS; do