From 762fd5d455454ae4a76e822e476358efd4ee7aa2 Mon Sep 17 00:00:00 2001 From: Andrzej Jackowski Date: Mon, 11 May 2026 10:00:17 +0200 Subject: [PATCH] test: audit: cover audit_rules configuration Audit rules enter through three paths (YAML, CQL, CLI), each with its own parsing and tracking -- cover all entry points before routing can depend on them. Test loading from YAML, live update via CQL and server API, CLI parsing, invalid value rejection at each path, and observer notification on live update. Refs SCYLLADB-1430 --- test/boost/audit_rule_test.cc | 117 ++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/test/boost/audit_rule_test.cc b/test/boost/audit_rule_test.cc index 46e6d28b8f..5f6cd9675c 100644 --- a/test/boost/audit_rule_test.cc +++ b/test/boost/audit_rule_test.cc @@ -8,13 +8,19 @@ #include +#include + #include +#include #include "audit/audit.hh" #include "audit/audit_rule.hh" +#include "db/config.hh" using namespace seastar; +namespace bpo = boost::program_options; + namespace { audit::audit_rule make_rule(std::vector sinks, @@ -97,3 +103,114 @@ BOOST_AUTO_TEST_CASE(test_json_round_trip) { BOOST_CHECK_EQUAL(audit::audit_rules_to_json_string({}), "[]"); } + +BOOST_AUTO_TEST_CASE(test_config_audit_rules_yaml) { + db::config cfg; + BOOST_CHECK(cfg.audit_rules().empty()); + + cfg.read_from_yaml( + "audit_rules:\n" + " - sinks: [table]\n" + " categories: [DML, DDL]\n" + " qualified_table_names: [ks.t1]\n" + " roles: ['admin\\*', 'domain\\\\user', 'user[0-9]', '!(guest)']\n"); + + BOOST_CHECK(cfg.audit_rules.source() == utils::config_file::config_source::SettingsFile); + BOOST_REQUIRE_EQUAL(cfg.audit_rules().size(), 1u); + BOOST_CHECK(cfg.audit_rules()[0].categories.contains(audit::statement_category::DML)); + BOOST_CHECK(cfg.audit_rules()[0].categories.contains(audit::statement_category::DDL)); + BOOST_CHECK_EQUAL(cfg.audit_rules()[0].roles[0], "admin\\*"); + + auto parsed = audit::parse_audit_rules_from_json(cfg.audit_rules.value_as_json()._res); + BOOST_REQUIRE_EQUAL(parsed.size(), 1u); + BOOST_CHECK(parsed[0] == cfg.audit_rules()[0]); +} + +BOOST_AUTO_TEST_CASE(test_config_audit_rules_cql) { + db::config cfg; + + std::vector observed; + auto observer = cfg.audit_rules.observe([&observed] (const std::vector& rules) { + observed = rules; + }); + BOOST_CHECK(cfg.audit_rules.set_value( + R"([{"sinks":["syslog"],"categories":["AUTH"],"qualified_table_names":[],"roles":["*"]}])", + utils::config_file::config_source::CQL)); + BOOST_CHECK(cfg.audit_rules.source() == utils::config_file::config_source::CQL); + BOOST_REQUIRE_EQUAL(observed.size(), 1u); + BOOST_CHECK_EQUAL(observed[0].sinks[0], "syslog"); + BOOST_CHECK(observed[0].categories.contains(audit::statement_category::AUTH)); +} + +BOOST_AUTO_TEST_CASE(test_config_audit_rules_cli) { + db::config cfg; + + auto desc = cfg.get_options_description(); + bpo::variables_map vm; + const char* argv[] = {"test", "--audit-rules", R"([{"sinks":["table"],"categories":["DML"],"qualified_table_names":["ks.t1"],"roles":["*"]}])"}; + bpo::store(bpo::parse_command_line(3, argv, desc), vm); + bpo::notify(vm); + BOOST_REQUIRE_EQUAL(cfg.audit_rules().size(), 1u); + BOOST_CHECK_EQUAL(cfg.audit_rules()[0].qualified_table_names[0], "ks.t1"); +} + +BOOST_AUTO_TEST_CASE(test_config_audit_rules_rejects_invalid_values) { + db::config cfg; + BOOST_CHECK_THROW( + cfg.read_from_yaml( + "audit_rules:\n" + " - sinks: [kafka]\n" + " categories: [DML]\n" + " qualified_table_names: []\n" + " roles: []\n"), + std::exception); + + // Each required field missing in turn. + BOOST_CHECK_THROW(cfg.read_from_yaml( + "audit_rules:\n" + " - categories: [DML]\n" + " qualified_table_names: []\n" + " roles: []\n"), std::exception); + BOOST_CHECK_THROW(cfg.read_from_yaml( + "audit_rules:\n" + " - sinks: [table]\n" + " qualified_table_names: []\n" + " roles: []\n"), std::exception); + BOOST_CHECK_THROW(cfg.read_from_yaml( + "audit_rules:\n" + " - sinks: [table]\n" + " categories: [DML]\n" + " roles: []\n"), std::exception); + BOOST_CHECK_THROW(cfg.read_from_yaml( + "audit_rules:\n" + " - sinks: [table]\n" + " categories: [DML]\n" + " qualified_table_names: []\n"), std::exception); + // Scalar value where array is expected. + BOOST_CHECK_THROW(cfg.read_from_yaml( + "audit_rules:\n" + " - sinks: table\n" + " categories: [DML]\n" + " qualified_table_names: []\n" + " roles: []\n"), std::exception); + BOOST_CHECK_THROW(cfg.read_from_yaml( + "audit_rules:\n" + " - sinks: [table]\n" + " categories: DML\n" + " qualified_table_names: []\n" + " roles: []\n"), std::exception); + BOOST_CHECK_THROW(cfg.read_from_yaml( + "audit_rules:\n" + " - sinks: [table]\n" + " categories: [DML]\n" + " qualified_table_names: ks.t1\n" + " roles: []\n"), std::exception); + BOOST_CHECK_THROW(cfg.read_from_yaml( + "audit_rules:\n" + " - sinks: [table]\n" + " categories: [DML]\n" + " qualified_table_names: []\n" + " roles: '*'\n"), std::exception); + + BOOST_CHECK_THROW(cfg.audit_rules.set_value(sstring("{not json}"), utils::config_file::config_source::CQL), std::exception); +}