diff --git a/.gitmodules b/.gitmodules index 11d0fab461..08c66f5ed3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -60,3 +60,7 @@ [submodule "third_party/uthash"] path = builder/third_party/uthash url = https://github.com/troydhanson/uthash.git +[submodule "builder/third_party/yaml-cpp"] + path = builder/third_party/yaml-cpp + url = https://github.com/jbeder/yaml-cpp.git + branch = master diff --git a/builder/install/10-yaml-cpp.sh b/builder/install/10-yaml-cpp.sh new file mode 100755 index 0000000000..c8d32e739e --- /dev/null +++ b/builder/install/10-yaml-cpp.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e + +cd third_party/yaml-cpp +cp LICENSE "${LICENSE_DIR}/yaml-cpp-${YAMLCPP_VERSION}" + +cmake -B build/ \ + -DYAML_CPP_BUILD_CONTRIB=OFF \ + -DYAML_CPP_BUILD_TOOLS=OFF \ + -DYAML_BUILD_SHARED_LIBS=OFF \ + -DYAML_CPP_INSTALL=ON +cmake --build build --target install ${NPROCS:+-j ${NPROCS}} diff --git a/builder/install/versions.sh b/builder/install/versions.sh index 075f8bd3f0..926ab2bbdf 100644 --- a/builder/install/versions.sh +++ b/builder/install/versions.sh @@ -18,3 +18,4 @@ export VALIJSON_VERSION=0.6 export RE2_VERSION=2022-06-01 export GPERFTOOLS_VERSION=2.13 export UTHASH_VERSION=v1.9.8 +export YAMLCPP_VERSION=0.8.0 diff --git a/builder/third_party/yaml-cpp b/builder/third_party/yaml-cpp new file mode 160000 index 0000000000..f732014112 --- /dev/null +++ b/builder/third_party/yaml-cpp @@ -0,0 +1 @@ +Subproject commit f7320141120f720aecc4c32be25586e7da9eb978 diff --git a/collector/CMakeLists.txt b/collector/CMakeLists.txt index bb35c5dc0b..f614004a43 100644 --- a/collector/CMakeLists.txt +++ b/collector/CMakeLists.txt @@ -2,6 +2,7 @@ project(collector-bin) find_package(Threads) find_package(CURL REQUIRED) +find_package(yaml-cpp REQUIRED) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wall --std=c++17 -pthread -Wno-deprecated-declarations -fno-omit-frame-pointer -rdynamic") @@ -72,6 +73,7 @@ add_definitions(-DASSERT_TO_LOG) add_subdirectory(lib) add_executable(collector collector.cpp) +target_link_libraries(collector_lib yaml-cpp) target_link_libraries(collector collector_lib) target_link_libraries(collector libprometheus-cpp-pull.a) diff --git a/collector/lib/CollectorConfig.cpp b/collector/lib/CollectorConfig.cpp index a33b456782..a3b70eef5b 100644 --- a/collector/lib/CollectorConfig.cpp +++ b/collector/lib/CollectorConfig.cpp @@ -77,6 +77,8 @@ PathEnvVar tls_ca_path("ROX_COLLECTOR_TLS_CA"); PathEnvVar tls_client_cert_path("ROX_COLLECTOR_TLS_CLIENT_CERT"); PathEnvVar tls_client_key_path("ROX_COLLECTOR_TLS_CLIENT_KEY"); +PathEnvVar config_file("ROX_COLLECTOR_CONFIG_PATH", "/etc/stackrox/runtime_config.yaml"); + } // namespace constexpr bool CollectorConfig::kTurnOffScrape; @@ -283,6 +285,7 @@ void CollectorConfig::InitCollectorConfig(CollectorArgs* args) { HandleAfterglowEnvVars(); HandleConnectionStatsEnvVars(); HandleSinspEnvVars(); + HandleConfig(config_file.value()); host_config_ = ProcessHostHeuristics(*this); } @@ -400,6 +403,56 @@ void CollectorConfig::HandleSinspEnvVars() { } } +bool CollectorConfig::YamlConfigToConfig(YAML::Node& yamlConfig) { + if (yamlConfig.IsNull() || !yamlConfig.IsDefined()) { + CLOG(FATAL) << "Unable to read config from config file"; + return false; + } + YAML::Node networkConnectionConfig = yamlConfig["networkConnectionConfig"]; + if (!networkConnectionConfig) { + CLOG(WARNING) << "No networkConnectionConfig in config file"; + return false; + } + + bool enableExternalIps = false; + if (networkConnectionConfig["enableExternalIps"]) { + enableExternalIps = networkConnectionConfig["enableExternalIps"].as(false); + } + + sensor::CollectorConfig runtime_config; + auto* networkConfig = runtime_config.mutable_network_connection_config(); + networkConfig->set_enable_external_ips(enableExternalIps); + + SetRuntimeConfig(runtime_config); + CLOG(INFO) << "Runtime configuration:"; + CLOG(INFO) << GetRuntimeConfigStr(); + + return true; +} + +void CollectorConfig::HandleConfig(const std::filesystem::path& filePath) { + if (!std::filesystem::exists(filePath)) { + CLOG(DEBUG) << "No configuration file found. " << filePath; + return; + } + + try { + YAML::Node yamlConfig = YAML::LoadFile(filePath); + YamlConfigToConfig(yamlConfig); + } catch (const YAML::BadFile& e) { + CLOG(FATAL) << "Failed to open the configuration file: " << filePath + << ". Error: " << e.what(); + } catch (const YAML::ParserException& e) { + CLOG(FATAL) << "Failed to parse the configuration file: " << filePath + << ". Error: " << e.what(); + } catch (const YAML::Exception& e) { + CLOG(FATAL) << "An error occurred while loading the configuration file: " << filePath + << ". Error: " << e.what(); + } catch (const std::exception& e) { + CLOG(FATAL) << "An unexpected error occurred while trying to read: " << filePath << e.what(); + } +} + bool CollectorConfig::TurnOffScrape() const { return turn_off_scrape_; } diff --git a/collector/lib/CollectorConfig.h b/collector/lib/CollectorConfig.h index 0ee34d8db1..0b54dc0e43 100644 --- a/collector/lib/CollectorConfig.h +++ b/collector/lib/CollectorConfig.h @@ -6,6 +6,7 @@ #include #include +#include #include @@ -99,6 +100,14 @@ class CollectorConfig { return enable_external_ips_; } + std::string GetRuntimeConfigStr() { + if (runtime_config_.has_value()) { + const auto& cfg = runtime_config_.value(); + return cfg.DebugString(); + } + return "{}"; + } + bool EnableConnectionStats() const { return enable_connection_stats_; } bool EnableDetailedMetrics() const { return enable_detailed_metrics_; } bool EnableRuntimeConfig() const { return enable_runtime_config_; } @@ -188,6 +197,8 @@ class CollectorConfig { void HandleAfterglowEnvVars(); void HandleConnectionStatsEnvVars(); void HandleSinspEnvVars(); + bool YamlConfigToConfig(YAML::Node& yamlConfig); + void HandleConfig(const std::filesystem::path& filePath); // Protected, used for testing purposes void SetSinspBufferSize(unsigned int buffer_size); diff --git a/collector/test/CollectorConfigTest.cpp b/collector/test/CollectorConfigTest.cpp index bdf5af190e..a7d532a82e 100644 --- a/collector/test/CollectorConfigTest.cpp +++ b/collector/test/CollectorConfigTest.cpp @@ -1,5 +1,7 @@ #include +#include + #include "CollectorArgs.h" #include "CollectorConfig.h" #include "gmock/gmock.h" @@ -32,6 +34,10 @@ class MockCollectorConfig : public CollectorConfig { void MockSetEnableExternalIPs(bool value) { SetEnableExternalIPs(value); } + + bool MockYamlConfigToConfig(YAML::Node& yamlConfig) { + return YamlConfigToConfig(yamlConfig); + } }; // Test that unmodified value is returned, when some dependency values are @@ -145,4 +151,69 @@ TEST(CollectorConfigTest, TestEnableExternalIpsRuntimeConfig) { EXPECT_TRUE(config.EnableExternalIPs()); } +TEST(CollectorConfigTest, TestYamlConfigToConfigMultiple) { + std::vector> tests = { + {R"( + networkConnectionConfig: + enableExternalIps: true + )", + true}, + {R"( + networkConnectionConfig: + enableExternalIps: false + )", + false}, + {R"( + networkConnectionConfig: + )", + false}, + {R"( + networkConnectionConfig: + unknownField: asdf + )", + false}}; + + for (const auto& [yamlStr, expected] : tests) { + YAML::Node yamlNode = YAML::Load(yamlStr); + + MockCollectorConfig config; + + bool result = config.MockYamlConfigToConfig(yamlNode); + auto runtime_config = config.GetRuntimeConfig(); + + EXPECT_TRUE(result); + EXPECT_TRUE(runtime_config.has_value()); + + const auto& cfg = runtime_config.value(); + const auto& network_cfg = cfg.network_connection_config(); + EXPECT_EQ(network_cfg.enable_external_ips(), expected); + EXPECT_EQ(config.EnableExternalIPs(), expected); + } +} + +TEST(CollectorConfigTest, TestYamlConfigToConfigInvalid) { + std::string yamlStr = R"( + unknownField: asdf + )"; + + YAML::Node yamlNode = YAML::Load(yamlStr); + + MockCollectorConfig config; + + bool result = config.MockYamlConfigToConfig(yamlNode); + auto runtime_config = config.GetRuntimeConfig(); + + EXPECT_FALSE(result); + EXPECT_FALSE(runtime_config.has_value()); +} + +TEST(CollectorConfigTest, TestYamlConfigToConfigEmpty) { + std::string yamlStr = R"()"; + YAML::Node yamlNode = YAML::Load(yamlStr); + + MockCollectorConfig config; + + EXPECT_DEATH({ config.MockYamlConfigToConfig(yamlNode); }, ".*"); +} + } // namespace collector diff --git a/docs/references.md b/docs/references.md index fa0fe77372..0e33c16b92 100644 --- a/docs/references.md +++ b/docs/references.md @@ -89,6 +89,8 @@ internal state of Collector. Refer to the [troubleshooting](troubleshooting.md#introspection-endpoints) section for more details. The default is false. +* `ROX_ENABLE_EXTERNAL_IPS`: Enables or disables the external IPs feature. + NOTE: Using environment variables is a preferred way of configuring Collector, so if you're adding a new configuration knob, keep this in mind. @@ -104,6 +106,35 @@ seconds. The default value is 30 seconds. * `logLevel`: Sets logging level. The default is INFO. +### File mount or ConfigMap + +The external IPs feature can be enabled or disabled using a file or ConfigMap. This is an optional +method and does not have to be used. This file overwites the ENABLE_EXTERNAL_IPS feature flag. +When using collector by itself a file can be mounted to it at /etc/stackrox/runtime_config.yaml. The +following is an example of the contents + +``` +networkConnectionConfig: + enableExternalIps: true +``` + +Alternatively, if collector is used as a part of Stackrox, the configuration can be set +using a ConfigMap. The following is an example of such a ConfigMap. + +``` +apiVersion: v1 +kind: ConfigMap +metadata: + name: collector-config + namespace: stackrox +data: + runtime_config.yaml: | + networkConnectionConfig: + enableExternalIps: true +``` + +The file path can be set using the `ROX_COLLECTOR_CONFIG_PATH` environment variable. + ### Other arguments * `--collection-method`: Which technology to use for data gathering. Either