diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000000..1c571dc569 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,9 @@ +# Default options +build --cxxopt='-std=c++17' +build --host_cxxopt='-std=c++17' +build --cxxopt='-O2' +build --experimental_scale_timeouts=10.0 + +# Windows options +build:windows --cxxopt='/std:c++17' +build:windows --host_cxxopt='/std:c++17' \ No newline at end of file diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 0000000000..905c243928 --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +8.3.1 \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..9336e81109 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,17 @@ +{ + "name": "libtraceable-dev", + "image": "ghcr.io/traceableai/native-build-images/ubuntu-20.04:0.1.98", + "mounts": [ + "source=${env:HOME}/.ssh,target=/home/builder/.ssh,type=bind,consistency=cached" + ], + "customizations": { + "vscode": { + "extensions": [ + "bazelbuild.vscode-bazel", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack" + ] + } + }, + "remoteUser": "builder" +} diff --git a/.gitignore b/.gitignore index 4e314e466e..b6359c6514 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,7 @@ examples/multithread/multithread examples/reading_logs_via_rule_message/simple_request examples/reading_logs_with_offset/read examples/using_bodies_in_chunks/simple_request + +# bazel +MODULE.bazel.lock +bazel-* \ No newline at end of file diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000000..3ce935ba9e --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,40 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") + +cc_library( + name = "modsecurity", + srcs = glob( + [ + "src/**/*.h", + "src/**/*.hh", + "src/**/*.cc", + ], + ), + hdrs = glob([ + "headers/**/*.h", + ]), + includes = ["headers", "others"], + strip_include_prefix = "headers", + linkopts = select({ + "@platforms//os:linux": ["-lpthread"], + "@platforms//os:macos": ["-lpthread"], + "//conditions:default": [], + }), + visibility = ["//visibility:public"], + deps = select({ + "@platforms//os:windows": [ + "//others:mbedtls", + "//others:libinjection", + "@libxml2_conan//:libxml2_headers", + "@libpcre2_conan//:libpcre2_headers", + "@poco_conan//:poco_headers", + ], + "//conditions:default": [ + "//others:mbedtls", + "//others:libinjection", + ], + }), + defines = select({ + "@platforms//os:windows": [ "WIN32", "LIBXML_STATIC", "POCO_STATIC", "POCO_NO_AUTOMATIC_LIBS", "PCRE2_STATIC" ], + "//conditions:default": [], + }), +) \ No newline at end of file diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000000..47ac815f70 --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,48 @@ +module ( + name = "modsecurity", + compatibility_level = 1, +) + +bazel_dep(name = "rules_cc", version = "0.2.14") +bazel_dep(name = "platforms", version = "1.0.0") + +new_local_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:local.bzl", "new_local_repository") + +new_local_repository( + name = "libxml2_conan", + path = "C:/Conan/direct_deploy/libxml2", + build_file_content = """ +cc_library( + name = "libxml2_headers", + hdrs = glob(["include/libxml2/**/*.h"]), + strip_include_prefix = "include/libxml2", + visibility = ["//visibility:public"], +) +""", +) + +new_local_repository( + name = "libpcre2_conan", + path = "C:/Conan/direct_deploy/pcre2", + build_file_content = """ +cc_library( + name = "libpcre2_headers", + hdrs = glob(["include/**/*.h"]), + strip_include_prefix = "include", + visibility = ["//visibility:public"], +) +""", +) + +new_local_repository( + name = "poco_conan", + path = "C:/Conan/direct_deploy/poco", + build_file_content = """ +cc_library( + name = "poco_headers", + hdrs = glob(["include/**/*.h"]), + strip_include_prefix = "include", + visibility = ["//visibility:public"], +) +""", +) \ No newline at end of file diff --git a/examples/bazel/BUILD.bazel b/examples/bazel/BUILD.bazel new file mode 100644 index 0000000000..38af6b0040 --- /dev/null +++ b/examples/bazel/BUILD.bazel @@ -0,0 +1,19 @@ +cc_binary ( + name = "main", + srcs = ["main.cc"], + deps = ["//:modsecurity", "@rules_cc//cc/runfiles"], + data = ["basic_rules.conf"], + linkopts = select({ + "@platforms//os:windows": [ + "/LIBPATH:C:/Conan/Libs", + "Ws2_32.lib", + "Iphlpapi.lib", + "pcre2-8-static.lib", + "PocoFoundationmd.lib", + "libxml2_a.lib", + ], + "//conditions:default": [ + "-l:libpcre2-8.a", + ], + }), +) \ No newline at end of file diff --git a/examples/bazel/basic_rules.conf b/examples/bazel/basic_rules.conf new file mode 100644 index 0000000000..0d17471d82 --- /dev/null +++ b/examples/bazel/basic_rules.conf @@ -0,0 +1,10 @@ +SecRule REQUEST_HEADERS:User-Agent ".*" "id:100,phase:1,t:sha1,t:hexEncode,setvar:tx.ua_hash=%{MATCHED_VAR}" + +SecAction "id:2,phase:2,initcol:ip=%{REMOTE_ADDR}_%{tx.ua_hash}" + +SecRule REQUEST_HEADERS:User-Agent "@rx .*" "id:3,phase:2,setvar:ip.auth_attempt=+1" +SecRule REQUEST_HEADERS:Jacob "@rx .*" "id:300,phase:2,setvar:ip.auth_attempt=+1,msg:'Matched value: %{MATCHED_VAR}'" +SecRule ARGS:foo "@rx herewego" "id:4,phase:2,setvar:ip.foo=bar,expirevar:ip.foo=2" +#SecRule ARGS:foo "@rx herewego" "id:4,phase:2,setvar:ip.foo=bar" +SecRule IP "@rx bar" "id:5,phase:2,pass" +SecRule IP:auth_attempt "@rx bar" "id:6,phase:2,pass" \ No newline at end of file diff --git a/examples/bazel/main.cc b/examples/bazel/main.cc new file mode 100644 index 0000000000..59e4ef0717 --- /dev/null +++ b/examples/bazel/main.cc @@ -0,0 +1,106 @@ +#include + +#include +#include +#include +#include +#include "rules_cc/cc/runfiles/runfiles.h" + + +void ModsecurityTest(const char* argv0); + +static const std::string GetTestConfPath(const char* argv0) { + std::string error; + std::unique_ptr runfiles( + rules_cc::cc::runfiles::Runfiles::Create(argv0, BAZEL_CURRENT_REPOSITORY, + &error)); + + if (!runfiles) { + std::cerr << "Failed to create Runfiles: " << error << "\n"; + return ""; + } + std::string repo_part = BAZEL_CURRENT_REPOSITORY; + if (repo_part.empty()) { + repo_part = "_main"; + } + + std::string logical_path = + repo_part + "/examples/bazel/basic_rules.conf"; + std::string physical_path = runfiles->Rlocation(logical_path); + + if (physical_path.empty()) { + return ""; + } + + return physical_path; +} + + +int main(int argc, char* argv[]) +{ + + std::cout << "Hello world!" << std::endl; + std::cout << "Test project for modsec windows is here!\n"; + try + { + ModsecurityTest(argv[0]); + } + catch (const std::exception &e) + { + std::cerr << "An error occurred: " << e.what() << std::endl; + } + return 0; +} + +void ModsecurityTest(const char* argv0) +{ + std::cout << "Starting ModSecurity test..." << std::endl; + std::cout << "About to create ModSecurity instance..." << std::endl; + std::cout.flush(); + auto modsec = std::make_unique(); + std::cout << "ModSecurity instance created." << std::endl; + std::cout << "ModSecurity whoAmI: " << modsec->whoAmI() << std::endl; + + std::cout << "About to create RulesSet..." << std::endl; + std::cout.flush(); + auto rules = std::make_unique(); + std::cout << "RulesSet created." << std::endl; + + std::string confPath = GetTestConfPath(argv0); + std::cout << "Config path: " << confPath << std::endl; + std::cout.flush(); + + if (rules->loadFromUri(confPath.c_str()) < 0) + { + std::cout<< "Failed to load rules from: " << confPath << std::endl; + std::cerr << "Problems loading the rules..." << std::endl; + std::cerr << rules->m_parserError.str() << std::endl; + return; + } + + std::cout<< "Rules loaded successfully from: " << confPath << std::endl; + auto modsecTransaction = std::make_unique(modsec.get(), rules.get(), nullptr); + modsecTransaction->processConnection("127.0.0.1", 12345, "127.0.0.1", 80); + modsecTransaction->processURI( + "https://www.modsecurity.org/test?foo=herewego", + "GET", "1.1"); + + modsecTransaction->addRequestHeader("User-Agent", + "Basic ModSecurity example"); + modsecTransaction->addRequestHeader("Jacob", + "matched-header"); + modsecTransaction->processRequestHeaders(); + modsecTransaction->processRequestBody(); + + modsecTransaction->addResponseHeader("HTTP/1.1", + "200 OK"); + modsecTransaction->processResponseHeaders(200, "HTTP 1.2"); + modsecTransaction->processResponseBody(); + + modsecTransaction->processLogging(); + + for (const auto &x : modsecTransaction->m_rulesMessages) + { + std::cout << std::to_string(x.m_rule.m_ruleId) << " " << x.m_message << std::endl; + } +} \ No newline at end of file diff --git a/others/BUILD.bazel b/others/BUILD.bazel new file mode 100644 index 0000000000..47035dc0f6 --- /dev/null +++ b/others/BUILD.bazel @@ -0,0 +1,35 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") + +cc_library( + name = "mbedtls", + srcs = glob( + [ + "mbedtls/library/**/*.h", + "mbedtls/library/**/*.c", + ], + ), + hdrs = glob([ + "mbedtls/include/**/*.h", + ]), + includes = ["mbedtls/include"], + strip_include_prefix = "mbedtls/include", + visibility = ["//visibility:public"], +) + +cc_library( + name = "libinjection", + srcs = glob( + [ + "libinjection/src/**/*.h", + "libinjection/src/**/*.c", + ], + exclude = [ + "libinjection/src/fuzz/**", + ], + ), + hdrs = glob([ + "libinjection/src/**/*.h", + ]), + visibility = ["//visibility:public"], + defines = ["LIBINJECTION_VERSION=2"], +) \ No newline at end of file diff --git a/src/actions/transformations/remove_whitespace.cc b/src/actions/transformations/remove_whitespace.cc index f7047f4760..78796527b1 100644 --- a/src/actions/transformations/remove_whitespace.cc +++ b/src/actions/transformations/remove_whitespace.cc @@ -24,7 +24,7 @@ bool RemoveWhitespace::transform(std::string &value, const Transaction *trans) c const char nonBreakingSpaces = 0xa0; const char nonBreakingSpaces2 = 0xc2; - auto pred = [](const auto c) { + auto pred = [nonBreakingSpaces, nonBreakingSpaces2](const auto c) { // remove whitespaces and non breaking spaces (NBSP) return std::isspace(static_cast(c)) || c == nonBreakingSpaces