Skip to content
Open
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,18 @@ make
## Usage

For comprehensive help, use `dooked --help`

### Regex alerts

Use `--regex-alerts <file>` to log custom alerts when a DNS/HTTP probe result matches a regex. The config can be a JSON array or an object with a `checks` array:

```json
{
"checks": [
{"field": "rdata", "regex": "(?i)github", "alert": "github infrastructure"},
{"field": "http_code", "regex": "^30[12]$", "alert": "redirect detected"}
]
}
```

Supported fields: `domain`/`name`, `http_code`, `content_length`, `dns_type`/`type`, `rdata`/`info`.
2 changes: 2 additions & 0 deletions dooked/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ set(SRC_FILES
./source/utils/string_utils.cpp
./source/utils/random_utils.cpp
./source/utils/ucstring.cpp
./source/regex_alerts.cpp
./source/cli_preprocessor.cpp
./source/main.cpp
)
Expand All @@ -93,6 +94,7 @@ set(HEADERS_FILES
./include/utils/random_utils.hpp
./include/utils/string_utils.hpp
./include/utils/ucstring.hpp
./include/regex_alerts.hpp
./include/cli_preprocessor.hpp
)

Expand Down
2 changes: 2 additions & 0 deletions dooked/include/cli_preprocessor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ struct cli_args_t {
std::string resolver_filename{};
std::string output_filename{};
std::string input_filename{};
std::string regex_alert_filename{};

int file_type{};
int post_http_request{};
Expand All @@ -33,6 +34,7 @@ struct runtime_args_t {
std::optional<std::vector<json_data_t>> previous_data{};
std::unique_ptr<std::ofstream> output_file{};
std::string output_filename{};
std::string regex_alert_filename{};
http_process_e http_request_time_{};
int thread_count{};
int content_length{-1};
Expand Down
21 changes: 21 additions & 0 deletions dooked/include/regex_alerts.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once

#include "utils/containers.hpp"
#include "utils/probe_result.hpp"
#include <nlohmann/json.hpp>
#include <string>
#include <vector>

namespace dooked {

struct regex_alert_rule_t {
std::string field{};
std::string pattern{};
std::string alert{};
};

std::vector<regex_alert_rule_t> load_regex_alert_rules(std::string const &filename);
void run_regex_alerts(std::vector<regex_alert_rule_t> const &rules,
map_container_t<probe_result_t> const &result_map);

} // namespace dooked
3 changes: 3 additions & 0 deletions dooked/source/cli_preprocessor.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "cli_preprocessor.hpp"
#include "dns/dns_resolver.hpp"
#include "regex_alerts.hpp"
#include "http/resolver.hpp"
#include "utils/exceptions.hpp"
#include "utils/random_utils.hpp"
Expand Down Expand Up @@ -354,6 +355,7 @@ void start_name_checking(runtime_args_t &&rt_args) {
spdlog::info("Writing JSON output");
}
write_json_result(result_map, rt_args);
run_regex_alerts(load_regex_alert_rules(rt_args.regex_alert_filename), result_map);

// compare old with new result -- only if we had previous record
if (rt_args.previous_data) {
Expand Down Expand Up @@ -477,6 +479,7 @@ void run_program(cli_args_t const &cli_args) {
static_cast<http_process_e>(cli_args.post_http_request);
rt_args.thread_count = cli_args.thread_count;
rt_args.content_length = cli_args.content_length;
rt_args.regex_alert_filename = cli_args.regex_alert_filename;
return start_name_checking(std::move(rt_args));
}

Expand Down
2 changes: 2 additions & 0 deletions dooked/source/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ int main(int argc, char **argv) {
app.add_option(
"-c,--content-length", cli_args.content_length,
"show content lengths that changed more than --content-length");
app.add_option("--regex-alerts", cli_args.regex_alert_filename,
"JSON config containing runtime regex alert checks");
app.add_flag("-d,--include-date", cli_args.include_date,
"append present datetime(-ddMMyyyy_hhmmss) in output name");
app.add_flag(
Expand Down
92 changes: 92 additions & 0 deletions dooked/source/regex_alerts.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#include "regex_alerts.hpp"
#include "utils/io_utils.hpp"
#include <fstream>
#include <regex>
#include <spdlog/spdlog.h>

namespace dooked {

namespace {
std::string dns_type_to_string(probe_result_t const &record) {
return dns_record_type_to_str(record.type);
}

std::vector<std::string> values_for_field(
std::string const &field, std::string const &domain,
http_dns_response_t<probe_result_t> const &response) {
std::vector<std::string> values{};
if (field == "domain" || field == "name") {
values.push_back(domain);
} else if (field == "http_code") {
values.push_back(std::to_string(response.http_result_.http_status_));
} else if (field == "content_length") {
values.push_back(std::to_string(response.http_result_.content_length_));
} else if (field == "dns_type" || field == "type") {
for (auto const &record : response.dns_result_list_) {
values.push_back(dns_type_to_string(record));
}
} else if (field == "rdata" || field == "info") {
for (auto const &record : response.dns_result_list_) {
values.push_back(record.rdata);
}
}
return values;
}
} // namespace

std::vector<regex_alert_rule_t>
load_regex_alert_rules(std::string const &filename) {
if (filename.empty()) {
return {};
}

std::ifstream file{filename};
if (!file) {
spdlog::error("Unable to open regex alert config `{}`", filename);
return {};
}

try {
auto const parsed = json::parse(file);
auto const rule_list = parsed.contains("checks") ? parsed.at("checks") : parsed;
std::vector<regex_alert_rule_t> rules{};
for (auto const &rule_json : rule_list) {
regex_alert_rule_t rule{};
rule.field = rule_json.at("field").get<std::string>();
rule.pattern = rule_json.at("regex").get<std::string>();
rule.alert = rule_json.at("alert").get<std::string>();
std::regex{rule.pattern};
rules.push_back(std::move(rule));
}
return rules;
} catch (std::exception const &e) {
spdlog::error("Invalid regex alert config `{}`: {}", filename, e.what());
return {};
}
}

void run_regex_alerts(std::vector<regex_alert_rule_t> const &rules,
map_container_t<probe_result_t> const &result_map) {
if (rules.empty()) {
return;
}

std::vector<std::pair<regex_alert_rule_t, std::regex>> compiled_rules{};
compiled_rules.reserve(rules.size());
for (auto const &rule : rules) {
compiled_rules.emplace_back(rule, std::regex{rule.pattern});
}

for (auto const &[domain, response] : result_map.cresult()) {
for (auto const &[rule, matcher] : compiled_rules) {
for (auto const &value : values_for_field(rule.field, domain, response)) {
if (std::regex_search(value, matcher)) {
spdlog::info("[REGEX ALERT][{}][{}] {} matched `{}`",
domain, rule.field, rule.alert, value);
}
}
}
}
}

} // namespace dooked