libddwaf-src 2.1.0

Source distribution and build integration for DataDog/libddwaf
Documentation
// Unless explicitly stated otherwise all files in this repository are
// dual-licensed under the Apache-2.0 License or BSD-3-Clause License.
//
// This product includes software developed at Datadog
// (https://www.datadoghq.com/). Copyright 2024 Datadog, Inc.

#include <exception>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>

#include "configuration/actions_parser.hpp"
#include "configuration/common/common.hpp"
#include "configuration/common/configuration.hpp"
#include "configuration/common/configuration_collector.hpp"
#include "configuration/common/parser_exception.hpp"
#include "configuration/common/raw_configuration.hpp"
#include "configuration/configuration_manager.hpp"
#include "configuration/data_parser.hpp"
#include "configuration/exclusion_parser.hpp"
#include "configuration/processor_override_parser.hpp"
#include "configuration/processor_parser.hpp"
#include "configuration/rule_override_parser.hpp"
#include "configuration/rule_parser.hpp"
#include "configuration/scanner_parser.hpp"
#include "fmt/format.h"
#include "log.hpp"
#include "obfuscator.hpp"
#include "ruleset_info.hpp"

namespace ddwaf {

bool configuration_manager::load(
    raw_configuration::map &root, configuration_collector &collector, ruleset_info &info)
{
    auto metadata = at<raw_configuration::map>(root, "metadata", {});
    auto rules_version = at<std::string_view>(metadata, "rules_version", {});
    if (!rules_version.empty()) {
        info.set_ruleset_version(rules_version);
    }

    auto schema_version = parse_schema_version(root);
    if (schema_version != 2) {
        throw unsupported_schema_version(schema_version);
    }

    auto it = root.find("actions");
    if (it != root.end()) {
        DDWAF_DEBUG("Parsing actions");
        auto &section = info.add_section("actions");
        try {
            auto actions = static_cast<raw_configuration::vector>(it->second);
            if (!actions.empty()) {
                parse_actions(actions, collector, section);
            }
        } catch (const std::exception &e) {
            DDWAF_WARN("Failed to parse actions: {}", e.what());
            section.set_error(e.what());
        }
    }

    it = root.find("rules");
    if (it != root.end()) {
        DDWAF_DEBUG("Parsing base rules");
        auto &section = info.add_section("rules");
        try {
            auto rules = static_cast<raw_configuration::vector>(it->second);
            if (!rules.empty()) {
                parse_base_rules(rules, collector, section);
            }
        } catch (const std::exception &e) {
            DDWAF_WARN("Failed to parse rules: {}", e.what());
            section.set_error(e.what());
        }
    }

    it = root.find("rules_compat");
    if (it != root.end()) {
        DDWAF_DEBUG("Parsing base backwards-incompatible rules");
        auto &section = info.add_section("rules_compat");
        try {
            auto rules = static_cast<raw_configuration::vector>(it->second);
            if (!rules.empty()) {
                parse_base_rules(rules, collector, section);
            }
        } catch (const std::exception &e) {
            DDWAF_WARN("Failed to parse backwards-incompatible rules: {}", e.what());
            section.set_error(e.what());
        }
    }

    it = root.find("custom_rules");
    if (it != root.end()) {
        DDWAF_DEBUG("Parsing custom rules");
        auto &section = info.add_section("custom_rules");
        try {
            auto rules = static_cast<raw_configuration::vector>(it->second);
            if (!rules.empty()) {
                parse_user_rules(rules, collector, section);
            }
        } catch (const std::exception &e) {
            DDWAF_WARN("Failed to parse custom rules: {}", e.what());
            section.set_error(e.what());
        }
    }

    it = root.find("rules_data");
    if (it != root.end()) {
        DDWAF_DEBUG("Parsing rule data");
        auto &section = info.add_section("rules_data");
        try {
            auto rules_data = static_cast<raw_configuration::vector>(it->second);
            if (!rules_data.empty()) {
                parse_rule_data(rules_data, collector, section);
            }
        } catch (const std::exception &e) {
            DDWAF_WARN("Failed to parse rule data: {}", e.what());
            section.set_error(e.what());
        }
    }

    it = root.find("rules_override");
    if (it != root.end()) {
        DDWAF_DEBUG("Parsing rule overrides");
        auto &section = info.add_section("rules_override");
        try {
            auto overrides = static_cast<raw_configuration::vector>(it->second);
            if (!overrides.empty()) {
                parse_rule_overrides(overrides, collector, section);
            }
        } catch (const std::exception &e) {
            DDWAF_WARN("Failed to parse rule overrides: {}", e.what());
            section.set_error(e.what());
        }
    }

    it = root.find("processor_overrides");
    if (it != root.end()) {
        DDWAF_DEBUG("Parsing processor overrides");
        auto &section = info.add_section("processor_overrides");
        try {
            auto overrides = static_cast<raw_configuration::vector>(it->second);
            if (!overrides.empty()) {
                parse_processor_overrides(overrides, collector, section);
            }
        } catch (const std::exception &e) {
            DDWAF_WARN("Failed to parse processor overrides: {}", e.what());
            section.set_error(e.what());
        }
    }

    it = root.find("exclusions");
    if (it != root.end()) {
        DDWAF_DEBUG("Parsing exclusions");
        auto &section = info.add_section("exclusions");
        try {
            auto exclusions = static_cast<raw_configuration::vector>(it->second);
            if (!exclusions.empty()) {
                parse_filters(exclusions, collector, section);
            }
        } catch (const std::exception &e) {
            DDWAF_WARN("Failed to parse exclusions: {}", e.what());
            section.set_error(e.what());
        }
    }

    it = root.find("exclusion_data");
    if (it != root.end()) {
        DDWAF_DEBUG("Parsing exclusion data");
        auto &section = info.add_section("exclusion_data");
        try {
            auto exclusions_data = static_cast<raw_configuration::vector>(it->second);
            if (!exclusions_data.empty()) {
                parse_exclusion_data(exclusions_data, collector, section);
            }
        } catch (const std::exception &e) {
            DDWAF_WARN("Failed to parse exclusion data: {}", e.what());
            section.set_error(e.what());
        }
    }

    it = root.find("processors");
    if (it != root.end()) {
        DDWAF_DEBUG("Parsing processors");
        auto &section = info.add_section("processors");
        try {
            auto processors = static_cast<raw_configuration::vector>(it->second);
            if (!processors.empty()) {
                parse_processors(processors, collector, section);
            }
        } catch (const std::exception &e) {
            DDWAF_WARN("Failed to parse processors: {}", e.what());
            section.set_error(e.what());
        }
    }

    it = root.find("scanners");
    if (it != root.end()) {
        DDWAF_DEBUG("Parsing scanners");
        auto &section = info.add_section("scanners");
        try {
            auto scanners = static_cast<raw_configuration::vector>(it->second);
            if (!scanners.empty()) {
                parse_scanners(scanners, collector, section);
            }
        } catch (const std::exception &e) {
            DDWAF_WARN("Failed to parse scanners: {}", e.what());
            section.set_error(e.what());
        }
    }

    it = root.find("obfuscator");
    if (it != root.end()) {
        DDWAF_DEBUG("Parsing obfuscator");
        auto &section = info.add_section("scanners");
        try {
            if (collector.contains_obfuscator()) {
                throw parsing_error("duplicate obfuscator configuration");
            }

            auto obfuscator = static_cast<raw_configuration::map>(it->second);
            collector.set_obfuscator(
                std::make_unique<match_obfuscator>(at<std::string_view>(obfuscator, "key_regex",
                                                       match_obfuscator::default_key_regex_str),
                    at<std::string_view>(
                        obfuscator, "value_regex", match_obfuscator::default_value_regex_str)));
        } catch (const std::exception &e) {
            DDWAF_WARN("Failed to parse obfuscator: {}", e.what());
            section.set_error(e.what());
        }
    }
    return info.state() != ruleset_info_state::invalid;
}

void configuration_manager::remove_config(const configuration_change_spec &cfg)
{
    for (const auto &id : cfg.base_rules) { global_config_.base_rules.erase(id); }
    for (const auto &id : cfg.user_rules) { global_config_.user_rules.erase(id); }
    for (const auto &id : cfg.rule_filters) { global_config_.rule_filters.erase(id); }
    for (const auto &id : cfg.input_filters) { global_config_.input_filters.erase(id); }
    for (const auto &id : cfg.rule_overrides_by_id) {
        global_config_.rule_overrides_by_id.erase(id);
    }
    for (const auto &id : cfg.rule_overrides_by_tags) {
        global_config_.rule_overrides_by_tags.erase(id);
    }
    for (const auto &id : cfg.processors) { global_config_.processors.erase(id); }
    for (const auto &id : cfg.processor_overrides) { global_config_.processor_overrides.erase(id); }
    for (const auto &id : cfg.scanners) { global_config_.scanners.erase(id); }
    for (const auto &id : cfg.actions) { global_config_.actions.erase(id); }
    for (const auto &[data_id, id] : cfg.rule_data) {
        auto it = global_config_.rule_data.find(data_id);
        if (it != global_config_.rule_data.end()) {
            // Should always be true...
            it->second.values.erase(id);
        }
    }
    for (const auto &[data_id, id] : cfg.exclusion_data) {
        auto it = global_config_.exclusion_data.find(data_id);
        if (it != global_config_.exclusion_data.end()) {
            // Should always be true...
            it->second.values.erase(id);
        }
    }

    if (contains(cfg.content, change_set::obfuscator)) {
        global_config_.obfuscator.reset();
    }
}

bool configuration_manager::add_or_update(
    const std::string &path, raw_configuration &root, ruleset_info &info)
{
    try {
        raw_configuration::map root_map = static_cast<raw_configuration::map>(root);

        auto it = configs_.find(path);
        if (it != configs_.end()) {
            // Track the change, i.e. removed stuff
            changes_ = changes_ | it->second.content;

            remove_config(it->second);
        } else {
            auto [new_it, res] = configs_.emplace(path, configuration_change_spec{});
            if (!res) {
                return false;
            }
            it = new_it;
        }

        configuration_change_spec new_config;
        configuration_collector collector{new_config, global_config_};

        if (!load(root_map, collector, info)) {
            configs_.erase(it);
            return false;
        }

        changes_ |= new_config.content;
        it->second = std::move(new_config);

        return true;
    } catch (const bad_cast &e) {
        DDWAF_WARN(
            "Invalid configuration type, expected '{}', obtained '{}'", e.expected(), e.obtained());
        info.set_error(fmt::format("invalid configuration type, expected '{}', obtained '{}'",
            e.expected(), e.obtained()));
    } catch (const unsupported_schema_version &e) {
        DDWAF_WARN("Unsupported schema version: '{}'", e.version());
        info.set_error(e.what());
    }

    return false;
}

bool configuration_manager::remove(const std::string &path)
{
    auto it = configs_.find(path);
    if (it == configs_.end()) {
        return false;
    }

    changes_ |= it->second.content;

    remove_config(it->second);

    configs_.erase(it);

    return true;
}

std::pair<const configuration_spec &, change_set> configuration_manager::consolidate()
{
    // Copy and reset the current changes
    auto current_change = changes_;
    changes_ = change_set::none;

    return {global_config_, current_change};
}

} // namespace ddwaf