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
This commit is contained in:
Andrzej Jackowski
2026-05-11 10:00:17 +02:00
parent f3a7e2e3dc
commit 762fd5d455

View File

@@ -8,13 +8,19 @@
#include <boost/test/unit_test.hpp>
#include <boost/program_options.hpp>
#include <seastar/core/sstring.hh>
#include <seastar/json/json_elements.hh>
#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<sstring> 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<audit::audit_rule> observed;
auto observer = cfg.audit_rules.observe([&observed] (const std::vector<audit::audit_rule>& 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);
}