From be927edfe8325f094dc33073062720a5ab7e683b Mon Sep 17 00:00:00 2001 From: Federico Di Pierro Date: Wed, 21 Aug 2024 12:42:22 +0200 Subject: [PATCH] new(userspace/falco,unit_tests): added new tests around schema validation feature. Signed-off-by: Federico Di Pierro --- unit_tests/CMakeLists.txt | 1 + .../falco/test_configuration_schema.cpp | 120 ++++++++++++++++++ unit_tests/falco_test_var.h.in | 1 + userspace/falco/app/actions/load_config.cpp | 4 +- userspace/falco/configuration.h | 3 +- userspace/falco/yaml_helper.h | 13 +- 6 files changed, 133 insertions(+), 9 deletions(-) create mode 100644 unit_tests/falco/test_configuration_schema.cpp diff --git a/unit_tests/CMakeLists.txt b/unit_tests/CMakeLists.txt index a6ab26d3..c3d2f588 100644 --- a/unit_tests/CMakeLists.txt +++ b/unit_tests/CMakeLists.txt @@ -47,6 +47,7 @@ add_executable(falco_unit_tests falco/test_configuration_rule_selection.cpp falco/test_configuration_config_files.cpp falco/test_configuration_env_vars.cpp + falco/test_configuration_schema.cpp falco/app/actions/test_select_event_sources.cpp falco/app/actions/test_load_config.cpp ) diff --git a/unit_tests/falco/test_configuration_schema.cpp b/unit_tests/falco/test_configuration_schema.cpp new file mode 100644 index 00000000..79c5f22f --- /dev/null +++ b/unit_tests/falco/test_configuration_schema.cpp @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2023 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include +#include +#include + +#define EXPECT_VALIDATION_STATUS(res, status) \ + do { \ + for(const auto& pair : res) { \ + auto validation_status = pair.second; \ + EXPECT_TRUE(sinsp_utils::startswith(validation_status, status)); \ + } \ + } \ + while (0) + +// Read Falco config from current repo-path +TEST(Configuration, schema_validate_config) +{ + falco_configuration falco_config; + config_loaded_res res; + + EXPECT_NO_THROW(res = falco_config.init_from_file(TEST_FALCO_CONFIG, {})); + EXPECT_VALIDATION_STATUS(res, yaml_helper::validation_ok); +} + +TEST(Configuration, schema_ok) +{ + falco_configuration falco_config; + config_loaded_res res; + + /* OK YAML */ + std::string config = + "falco_libs:\n" + " thread_table_size: 50\n"; + + EXPECT_NO_THROW(res = falco_config.init_from_content(config, {})); + EXPECT_VALIDATION_STATUS(res, yaml_helper::validation_ok); +} + +TEST(Configuration, schema_wrong_key) +{ + falco_configuration falco_config; + config_loaded_res res; + + /* Miss-typed key YAML */ + std::string config = + "falco_libss:\n" + " thread_table_size: 50\n"; + + EXPECT_NO_THROW(res = falco_config.init_from_content(config, {})); + EXPECT_VALIDATION_STATUS(res, yaml_helper::validation_failed); +} + +TEST(Configuration, schema_wrong_type) +{ + falco_configuration falco_config; + + /* Wrong value type YAML */ + std::string config = + "falco_libs: 512\n"; + + // We expect an exception since `falco_configuration::load_yaml()` + // will fail to parse `falco_libs` node. + ASSERT_ANY_THROW(falco_config.init_from_content(config, {})); +} + +TEST(Configuration, schema_wrong_embedded_key) +{ + falco_configuration falco_config; + config_loaded_res res; + + /* Miss-typed sub-key YAML */ + std::string config = + "falco_libs:\n" + " thread_table_sizeee: 50\n"; + + EXPECT_NO_THROW(res = falco_config.init_from_content(config, {})); + EXPECT_VALIDATION_STATUS(res, yaml_helper::validation_failed); +} + +TEST(Configuration, schema_yaml_helper_validator) +{ + yaml_helper conf; + falco_configuration falco_config; + + /* Broken YAML */ + std::string sample_yaml = + "falco_libs:\n" + " thread_table_size: 50\n"; + + // Ok, we don't ask for any validation + EXPECT_NO_THROW(conf.load_from_string(sample_yaml)); + + // We pass a string variable but not a schema + std::string validation; + EXPECT_NO_THROW(conf.load_from_string(sample_yaml, Json::Value{}, &validation)); + EXPECT_EQ(validation, yaml_helper::validation_none); + + // We pass a schema but not a string storage for the validation; no validation takes place + EXPECT_NO_THROW(conf.load_from_string(sample_yaml, falco_config.m_config_schema, nullptr)); + + // We pass everything + EXPECT_NO_THROW(conf.load_from_string(sample_yaml, falco_config.m_config_schema, &validation)); + EXPECT_EQ(validation, yaml_helper::validation_ok); +} \ No newline at end of file diff --git a/unit_tests/falco_test_var.h.in b/unit_tests/falco_test_var.h.in index 43633e60..1a817e48 100644 --- a/unit_tests/falco_test_var.h.in +++ b/unit_tests/falco_test_var.h.in @@ -2,3 +2,4 @@ #define TEST_ENGINE_KMOD_CONFIG "${CMAKE_SOURCE_DIR}/unit_tests/falco/test_configs/engine_kmod_config.yaml" #define TEST_ENGINE_MODERN_CONFIG "${CMAKE_SOURCE_DIR}/unit_tests/falco/test_configs/engine_modern_config.yaml" +#define TEST_FALCO_CONFIG "${CMAKE_SOURCE_DIR}/falco.yaml" diff --git a/userspace/falco/app/actions/load_config.cpp b/userspace/falco/app/actions/load_config.cpp index 86359f8b..2a00eefd 100644 --- a/userspace/falco/app/actions/load_config.cpp +++ b/userspace/falco/app/actions/load_config.cpp @@ -65,8 +65,8 @@ falco::app::run_result falco::app::actions::load_config(const falco::app::state& { auto config_path = pair.first; auto validation = pair.second; - auto priority = validation == "validated" ? falco_logger::level::INFO : falco_logger::level::WARNING; - falco_logger::log(priority, std::string(" ") + config_path + " | " + validation + "\n"); + auto priority = validation == yaml_helper::validation_ok ? falco_logger::level::INFO : falco_logger::level::WARNING; + falco_logger::log(priority, std::string(" ") + config_path + " | validation: " + validation + "\n"); } } diff --git a/userspace/falco/configuration.h b/userspace/falco/configuration.h index 89b0f1d2..40a5f10c 100644 --- a/userspace/falco/configuration.h +++ b/userspace/falco/configuration.h @@ -197,10 +197,9 @@ public: // Needed by tests yaml_helper m_config; - -private: Json::Value m_config_schema; +private: void merge_config_files(const std::string& config_name, config_loaded_res &res); void load_yaml(const std::string& config_name); void init_logger(); diff --git a/userspace/falco/yaml_helper.h b/userspace/falco/yaml_helper.h index 2d51082b..90c8645b 100644 --- a/userspace/falco/yaml_helper.h +++ b/userspace/falco/yaml_helper.h @@ -85,6 +85,9 @@ class yaml_helper { public: inline static const std::string configs_key = "config_files"; + inline static const std::string validation_ok = "ok"; + inline static const std::string validation_failed = "failed"; + inline static const std::string validation_none = "schema not provided"; /** * Load the YAML document represented by the input string. @@ -102,7 +105,7 @@ public: } else { - *validation = "no schema provided"; + *validation = validation_none; } } } @@ -216,7 +219,7 @@ private: } else { - *validation = "no schema provided"; + *validation = validation_none; } } return root; @@ -239,14 +242,14 @@ private: // report only the top-most error if (validationResults.popError(error)) { - return std::string("validation failed for ") + return std::string(validation_failed + " for ") + std::accumulate(error.context.begin(), error.context.end(), std::string("")) + ": " + error.description; } - return "validation failed"; + return validation_failed; } - return "validated"; + return validation_ok; } /*