This is an automated email from the ASF dual-hosted git repository.
martinzink pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new 4ca62e0cb MINIFICPP-2846 minifi-encrypt-config should encrypt
sensitive propert… (#2204)
4ca62e0cb is described below
commit 4ca62e0cb8a9833dfc8f6197df0c8ab1fc8ce92a
Author: Ferenc Gerlits <[email protected]>
AuthorDate: Thu Jul 2 15:09:25 2026 +0200
MINIFICPP-2846 minifi-encrypt-config should encrypt sensitive propert…
(#2204)
The minifi-encrypt-config utility should encrypt the additional properties
files in the
minifi.properties.d directory, too, if they exist. We allow
nifi.sensitive.props.additional.keys
(the list of additional sensitive property keys) to be in any of the
properties files.
---
encrypt-config/ConfigFile.cpp | 39 --------
encrypt-config/ConfigFileEncryptor.h | 29 ------
encrypt-config/EncryptConfig.cpp | 66 +++++++++----
encrypt-config/EncryptConfig.h | 4 +-
encrypt-config/EncryptConfigMain.cpp | 12 ++-
...leEncryptor.cpp => PropertiesFileEncryptor.cpp} | 43 +++++---
.../{ConfigFile.h => PropertiesFileEncryptor.h} | 16 +--
encrypt-config/tests/EncryptConfigTests.cpp | 64 ++++++++++++
...rTests.cpp => PropertiesFileEncryptorTests.cpp} | 51 ++++++----
encrypt-config/tests/resources/conf/bootstrap.conf | 1 +
.../tests/resources/conf/minifi.properties | 102 +++++++++++++++++++
.../conf/minifi.properties.d/20_llm.properties | 18 ++++
.../conf/minifi.properties.d/90_c2.properties | 19 ++++
libminifi/include/properties/Properties.h | 8 ++
libminifi/include/properties/PropertiesFile.h | 1 -
libminifi/src/properties/Properties.cpp | 38 ++++----
libminifi/test/resources/minifi.properties | 108 +++++++++++++++++++++
...th-additional-sensitive-props.minifi.properties | 104 ++++++++++++++++++++
.../test/unit/PropertiesFileTests.cpp | 82 +++++++---------
19 files changed, 606 insertions(+), 199 deletions(-)
diff --git a/encrypt-config/ConfigFile.cpp b/encrypt-config/ConfigFile.cpp
deleted file mode 100644
index d4abed332..000000000
--- a/encrypt-config/ConfigFile.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 "ConfigFile.h"
-
-#include <algorithm>
-#include <fstream>
-#include <utility>
-#include <optional>
-
-#include "utils/StringUtils.h"
-#include "properties/Configuration.h"
-
-namespace org::apache::nifi::minifi::encrypt_config {
-
-std::vector<std::string> ConfigFile::getSensitiveProperties() const {
- auto sensitive_properties =
Configuration::getSensitiveProperties([this](const std::string&
sensitive_props) { return getValue(sensitive_props); });
- const auto not_found = [this](const std::string& property_name) { return
!hasValue(property_name); };
- const auto new_end = std::remove_if(sensitive_properties.begin(),
sensitive_properties.end(), not_found);
- sensitive_properties.erase(new_end, sensitive_properties.end());
-
- return sensitive_properties;
-}
-
-} // namespace org::apache::nifi::minifi::encrypt_config
diff --git a/encrypt-config/ConfigFileEncryptor.h
b/encrypt-config/ConfigFileEncryptor.h
deleted file mode 100644
index 04941a4d2..000000000
--- a/encrypt-config/ConfigFileEncryptor.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.
- */
-#pragma once
-
-#include "ConfigFile.h"
-#include "utils/crypto/EncryptionUtils.h"
-#include "Utils.h"
-
-namespace org::apache::nifi::minifi::encrypt_config {
-
-uint32_t encryptSensitivePropertiesInFile(ConfigFile& config_file, const
utils::crypto::Bytes& encryption_key);
-
-uint32_t encryptSensitivePropertiesInFile(ConfigFile& config_file, const
EncryptionKeys& keys);
-
-} // namespace org::apache::nifi::minifi::encrypt_config
diff --git a/encrypt-config/EncryptConfig.cpp b/encrypt-config/EncryptConfig.cpp
index 5e3bbd378..0bc30cb03 100644
--- a/encrypt-config/EncryptConfig.cpp
+++ b/encrypt-config/EncryptConfig.cpp
@@ -24,16 +24,18 @@
#include <stdexcept>
#include "../core-framework/include/Defaults.h"
-#include "ConfigFile.h"
-#include "ConfigFileEncryptor.h"
#include "FlowConfigEncryptor.h"
-#include "utils/Enum.h"
+#include "PropertiesFileEncryptor.h"
+#include "properties/Properties.h"
+#include "properties/PropertiesFile.h"
#include "utils/file/FileUtils.h"
namespace {
constexpr std::string_view ENCRYPTION_KEY_PROPERTY_NAME =
"nifi.bootstrap.sensitive.key";
constexpr std::string_view SENSITIVE_PROPERTIES_KEY_PROPERTY_NAME =
"nifi.bootstrap.sensitive.properties.key";
+namespace minifi = org::apache::nifi::minifi;
+
std::string readFile(const std::filesystem::path& file_path) {
try {
std::ifstream file_stream{file_path, std::ios::binary};
@@ -43,11 +45,27 @@ std::string readFile(const std::filesystem::path&
file_path) {
throw std::runtime_error("Error while reading file \"" +
file_path.string() + "\"");
}
}
+
+uint32_t encryptSensitiveValuesInMinifiPropertiesFile(const
std::filesystem::path& file_path, const std::vector<std::string>&
sensitive_properties,
+ const minifi::encrypt_config::EncryptionKeys& keys) {
+ minifi::PropertiesFile properties_file{std::ifstream{file_path}};
+ if (properties_file.size() == 0) {
+ std::cout << "Properties file " + file_path.string() + " is missing or
empty!\n";
+ return 0;
+ }
+
+ uint32_t num_properties_encrypted =
minifi::encrypt_config::encryptSensitivePropertiesInFile(properties_file,
sensitive_properties, keys);
+ if (num_properties_encrypted > 0) {
+ properties_file.writeTo(file_path);
+ std::cout << "Encrypted " << num_properties_encrypted << " sensitive " <<
(num_properties_encrypted == 1 ? "property" : "properties") << " in " <<
file_path.string() << '\n';
+ }
+ return num_properties_encrypted;
+}
} // namespace
namespace org::apache::nifi::minifi::encrypt_config {
-EncryptConfig::EncryptConfig(const std::string& minifi_home) :
minifi_home_(minifi_home) {
+EncryptConfig::EncryptConfig(std::filesystem::path minifi_home) :
minifi_home_(std::move(minifi_home)),
prev_current_path_(std::filesystem::current_path()) {
if (sodium_init() < 0) {
// encryption/decryption depends on the libsodium library which needs to
be initialized
throw std::runtime_error{"Could not initialize the libsodium library!"};
@@ -56,8 +74,12 @@ EncryptConfig::EncryptConfig(const std::string& minifi_home)
: minifi_home_(mini
std::filesystem::current_path(minifi_home_);
}
+EncryptConfig::~EncryptConfig() {
+ std::filesystem::current_path(prev_current_path_);
+}
+
bool EncryptConfig::isReEncrypting() const {
- encrypt_config::ConfigFile
bootstrap_file{std::ifstream{bootstrapFilePath()}};
+ PropertiesFile bootstrap_file{std::ifstream{bootstrapFilePath()}};
std::string decryption_key_name =
utils::string::join_pack(ENCRYPTION_KEY_PROPERTY_NAME, ".old");
std::optional<std::string> decryption_key_hex =
bootstrap_file.getValue(decryption_key_name);
@@ -66,7 +88,7 @@ bool EncryptConfig::isReEncrypting() const {
}
std::filesystem::path EncryptConfig::flowConfigPath() const {
- encrypt_config::ConfigFile
properties_file{std::ifstream{propertiesFilePath()}};
+ PropertiesFile properties_file{std::ifstream{propertiesFilePath()}};
std::optional<std::filesystem::path>
config_path{properties_file.getValue(Configure::nifi_flow_configuration_file)};
if (!config_path) {
config_path = utils::file::PathUtils::resolve(minifi_home_,
"conf/config.yml");
@@ -123,7 +145,7 @@ std::filesystem::path EncryptConfig::propertiesFilePath()
const {
}
EncryptionKeys EncryptConfig::getEncryptionKeys(std::string_view
property_name) const {
- encrypt_config::ConfigFile
bootstrap_file{std::ifstream{bootstrapFilePath()}};
+ PropertiesFile bootstrap_file{std::ifstream{bootstrapFilePath()}};
std::string decryption_key_name = utils::string::join_pack(property_name,
".old");
std::optional<std::string> decryption_key_hex =
bootstrap_file.getValue(decryption_key_name);
@@ -168,7 +190,7 @@ std::string EncryptConfig::hexDecodeAndValidateKey(const
std::string& key, const
void EncryptConfig::writeEncryptionKeyToBootstrapFile(const std::string&
encryption_key_name, const utils::crypto::Bytes& encryption_key) const {
std::string key_encoded =
utils::string::to_hex(utils::crypto::bytesToString(encryption_key));
- encrypt_config::ConfigFile
bootstrap_file{std::ifstream{bootstrapFilePath()}};
+ PropertiesFile bootstrap_file{std::ifstream{bootstrapFilePath()}};
if (bootstrap_file.hasValue(encryption_key_name)) {
bootstrap_file.update(encryption_key_name, key_encoded);
@@ -180,22 +202,28 @@ void
EncryptConfig::writeEncryptionKeyToBootstrapFile(const std::string& encrypt
}
void EncryptConfig::encryptSensitiveValuesInMinifiProperties() const {
- EncryptionKeys keys = getEncryptionKeys(ENCRYPTION_KEY_PROPERTY_NAME);
+ const auto base_properties_file = propertiesFilePath();
+ if (!utils::file::exists(base_properties_file)) {
+ std::cout << "The minifi properties file " <<
base_properties_file.string() << " does not exist. Did you specify the minifi
home directory correctly?\n";
+ return;
+ }
- encrypt_config::ConfigFile
properties_file{std::ifstream{propertiesFilePath()}};
- if (properties_file.size() == 0) {
- throw std::runtime_error{"Properties file " +
propertiesFilePath().string() + " not found!"};
+ const std::vector<std::string> sensitive_properties =
getSensitiveProperties(base_properties_file);
+ const EncryptionKeys keys = getEncryptionKeys(ENCRYPTION_KEY_PROPERTY_NAME);
+
+ uint32_t num_properties_encrypted =
encryptSensitiveValuesInMinifiPropertiesFile(base_properties_file,
sensitive_properties, keys);
+
+ const auto extra_properties_files_dir =
properties::extraPropertiesFilesDirName(base_properties_file);
+ const auto logger = core::logging::LoggerFactory<EncryptConfig>::getLogger();
+ const auto extra_properties_file_names =
properties::getExtraPropertiesFileNames(extra_properties_files_dir, logger);
+
+ for (const auto& file_name : extra_properties_file_names) {
+ num_properties_encrypted +=
encryptSensitiveValuesInMinifiPropertiesFile(extra_properties_files_dir /
file_name, sensitive_properties, keys);
}
- uint32_t num_properties_encrypted =
encryptSensitivePropertiesInFile(properties_file, keys);
if (num_properties_encrypted == 0) {
- std::cout << "Could not find any (new) sensitive properties to encrypt in
" << propertiesFilePath() << '\n';
- return;
+ std::cout << "Could not find any (new) sensitive properties to encrypt.\n";
}
-
- properties_file.writeTo(propertiesFilePath());
- std::cout << "Encrypted " << num_properties_encrypted << " sensitive "
- << (num_properties_encrypted == 1 ? "property" : "properties") << " in "
<< propertiesFilePath() << '\n';
}
void EncryptConfig::encryptSensitiveValuesInFlowConfig(
diff --git a/encrypt-config/EncryptConfig.h b/encrypt-config/EncryptConfig.h
index e64290db6..a89615303 100644
--- a/encrypt-config/EncryptConfig.h
+++ b/encrypt-config/EncryptConfig.h
@@ -25,7 +25,8 @@ namespace org::apache::nifi::minifi::encrypt_config {
class EncryptConfig {
public:
- explicit EncryptConfig(const std::string& minifi_home);
+ explicit EncryptConfig(std::filesystem::path minifi_home);
+ ~EncryptConfig();
void encryptSensitiveValuesInMinifiProperties() const;
void encryptSensitiveValuesInFlowConfig(
@@ -44,6 +45,7 @@ class EncryptConfig {
void writeEncryptionKeyToBootstrapFile(const std::string&
encryption_key_name, const utils::crypto::Bytes& encryption_key) const;
const std::filesystem::path minifi_home_;
+ const std::filesystem::path prev_current_path_;
};
} // namespace org::apache::nifi::minifi::encrypt_config
diff --git a/encrypt-config/EncryptConfigMain.cpp
b/encrypt-config/EncryptConfigMain.cpp
index c8e76ccfb..40fc191d0 100644
--- a/encrypt-config/EncryptConfigMain.cpp
+++ b/encrypt-config/EncryptConfigMain.cpp
@@ -21,6 +21,7 @@
#include "EncryptConfig.h"
#include "argparse/argparse.hpp"
#include "minifi-cpp/agent/agent_version.h"
+#include "utils/file/FileUtils.h"
#include "utils/StringUtils.h"
namespace minifi = org::apache::nifi::minifi;
@@ -68,7 +69,16 @@ int main(int argc, char* argv[]) try {
return 1;
}
- EncryptConfig encrypt_config{argument_parser.get("-m")};
+ std::filesystem::path minifi_home{argument_parser.get("-m")};
+ if (!minifi::utils::file::exists(minifi_home)) {
+ std::cerr << "The minifi home directory " << minifi_home.string() << "
does not exist!\n";
+ return 7;
+ }
+ if (!minifi::utils::file::is_directory(minifi_home)) {
+ std::cerr << "The minifi home is set to " << minifi_home.string() << ",
which is not a directory!\n";
+ return 8;
+ }
+ EncryptConfig encrypt_config{std::move(minifi_home)};
std::string operation = argument_parser.get("operation");
const auto re_encrypt = argument_parser.get<bool>("--re-encrypt");
diff --git a/encrypt-config/ConfigFileEncryptor.cpp
b/encrypt-config/PropertiesFileEncryptor.cpp
similarity index 56%
rename from encrypt-config/ConfigFileEncryptor.cpp
rename to encrypt-config/PropertiesFileEncryptor.cpp
index c5eacbbe9..6e454bf9a 100644
--- a/encrypt-config/ConfigFileEncryptor.cpp
+++ b/encrypt-config/PropertiesFileEncryptor.cpp
@@ -15,33 +15,50 @@
* limitations under the License.
*/
-#include "ConfigFileEncryptor.h"
+#include "PropertiesFileEncryptor.h"
#include <iostream>
#include <optional>
#include <string>
+#include "properties/Configuration.h"
+#include "properties/Properties.h"
#include "utils/StringUtils.h"
-namespace org::apache::nifi::minifi::encrypt_config {
-
+namespace {
bool isEncrypted(const std::optional<std::string>& encryption_type) {
return encryption_type && !encryption_type->empty() && *encryption_type !=
"plaintext";
}
+} // namespace
+
+namespace org::apache::nifi::minifi::encrypt_config {
+
+std::vector<std::string> getSensitiveProperties(const std::filesystem::path&
properties_file_path) {
+ auto minifi_properties =
PropertiesImpl{PropertiesImpl::PersistTo::MultipleFiles, "MiNiFi properties"};
+ minifi_properties.loadConfigureFile(properties_file_path);
+
+ auto sensitive_properties =
Configuration::getSensitiveProperties([&minifi_properties](const std::string&
property_name) {
+ return minifi_properties.getString(property_name);
+ });
+ auto not_found = [&minifi_properties](const std::string& property_name) {
return !minifi_properties.getString(property_name).has_value(); };
+ std::erase_if(sensitive_properties, not_found);
+
+ return sensitive_properties;
+}
-uint32_t encryptSensitivePropertiesInFile(ConfigFile& config_file, const
utils::crypto::Bytes & encryption_key) {
- return encryptSensitivePropertiesInFile(config_file, EncryptionKeys{{},
encryption_key});
+uint32_t encryptSensitivePropertiesInFile(PropertiesFile& properties_file,
const std::vector<std::string>& sensitive_properties, const
utils::crypto::Bytes & encryption_key) {
+ return encryptSensitivePropertiesInFile(properties_file,
sensitive_properties, EncryptionKeys{{}, encryption_key});
}
-uint32_t encryptSensitivePropertiesInFile(ConfigFile& config_file, const
EncryptionKeys& keys) {
- int num_properties_encrypted = 0;
+uint32_t encryptSensitivePropertiesInFile(PropertiesFile& properties_file,
const std::vector<std::string>& sensitive_properties, const EncryptionKeys&
keys) {
+ uint32_t num_properties_encrypted = 0;
- for (const auto& property_key : config_file.getSensitiveProperties()) {
- std::optional<std::string> property_value =
config_file.getValue(property_key);
+ for (const auto& property_key : sensitive_properties) {
+ std::optional<std::string> property_value =
properties_file.getValue(property_key);
if (!property_value) { continue; }
std::string encryption_type_key = property_key + ".protected";
- std::optional<std::string> encryption_type =
config_file.getValue(encryption_type_key);
+ std::optional<std::string> encryption_type =
properties_file.getValue(encryption_type_key);
std::string raw_value = *property_value;
if (isEncrypted(encryption_type)) {
@@ -63,12 +80,12 @@ uint32_t encryptSensitivePropertiesInFile(ConfigFile&
config_file, const Encrypt
std::string encrypted_property_value = utils::crypto::encrypt(raw_value,
keys.encryption_key);
- config_file.update(property_key, encrypted_property_value);
+ properties_file.update(property_key, encrypted_property_value);
if (encryption_type) {
- config_file.update(encryption_type_key,
utils::crypto::EncryptionType::name());
+ properties_file.update(encryption_type_key,
utils::crypto::EncryptionType::name());
} else {
- config_file.insertAfter(property_key, encryption_type_key,
utils::crypto::EncryptionType::name());
+ properties_file.insertAfter(property_key, encryption_type_key,
utils::crypto::EncryptionType::name());
}
std::cout << "Encrypted property: " << property_key << '\n';
diff --git a/encrypt-config/ConfigFile.h
b/encrypt-config/PropertiesFileEncryptor.h
similarity index 69%
rename from encrypt-config/ConfigFile.h
rename to encrypt-config/PropertiesFileEncryptor.h
index 56079c9ff..abab6e059 100644
--- a/encrypt-config/ConfigFile.h
+++ b/encrypt-config/PropertiesFileEncryptor.h
@@ -16,22 +16,16 @@
*/
#pragma once
-#include <string>
-#include <vector>
-
-#include "utils/crypto/EncryptionUtils.h"
#include "properties/PropertiesFile.h"
+#include "utils/crypto/EncryptionUtils.h"
+#include "Utils.h"
namespace org::apache::nifi::minifi::encrypt_config {
-class ConfigFile : public PropertiesFile {
- public:
- using PropertiesFile::PropertiesFile;
+std::vector<std::string> getSensitiveProperties(const std::filesystem::path&
properties_file_path);
- [[nodiscard]] std::vector<std::string> getSensitiveProperties() const;
+uint32_t encryptSensitivePropertiesInFile(PropertiesFile& properties_file,
const std::vector<std::string>& sensitive_properties, const
utils::crypto::Bytes& encryption_key);
- private:
- friend bool operator==(const ConfigFile&, const ConfigFile&);
-};
+uint32_t encryptSensitivePropertiesInFile(PropertiesFile& properties_file,
const std::vector<std::string>& sensitive_properties, const EncryptionKeys&
keys);
} // namespace org::apache::nifi::minifi::encrypt_config
diff --git a/encrypt-config/tests/EncryptConfigTests.cpp
b/encrypt-config/tests/EncryptConfigTests.cpp
new file mode 100644
index 000000000..1d5f80be8
--- /dev/null
+++ b/encrypt-config/tests/EncryptConfigTests.cpp
@@ -0,0 +1,64 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 "EncryptConfig.h"
+#include "properties/PropertiesFile.h"
+#include "unit/Catch.h"
+#include "unit/TestBase.h"
+
+namespace {
+constexpr std::string llm_api_key = "llm.api.key";
+}
+
+TEST_CASE("EncryptConfig::encryptSensitiveValuesInMinifiProperties can encrypt
the sensitive properties",
"[encrypt-config][encryptSensitivePropertiesInFile]") {
+ TestController test_controller;
+ const auto minifi_home = test_controller.createTempDirectory();
+ std::filesystem::copy("resources/conf", minifi_home / "conf",
std::filesystem::copy_options::recursive);
+
+ auto original_properties =
minifi::PropertiesImpl{minifi::PropertiesImpl::PersistTo::MultipleFiles,
"minifi.properties"};
+ original_properties.loadConfigureFile(minifi_home / "conf" /
"minifi.properties");
+ const auto original_passphrase =
original_properties.getString(minifi::Configuration::nifi_security_client_pass_phrase);
+ const auto original_password =
original_properties.getString(minifi::Configuration::nifi_rest_api_password);
+ const auto original_api_key = original_properties.getString(llm_api_key);
+ REQUIRE(original_passphrase);
+ REQUIRE(original_password);
+ REQUIRE(original_api_key);
+
+ minifi::encrypt_config::EncryptConfig encrypt_config{minifi_home};
+ encrypt_config.encryptSensitiveValuesInMinifiProperties();
+
+ auto encrypted_properties =
minifi::PropertiesImpl{minifi::PropertiesImpl::PersistTo::MultipleFiles,
"minifi.properties"};
+ encrypted_properties.loadConfigureFile(minifi_home / "conf" /
"minifi.properties");
+ const auto encrypted_passphrase =
encrypted_properties.getString(minifi::Configuration::nifi_security_client_pass_phrase);
+ const auto encrypted_password =
encrypted_properties.getString(minifi::Configuration::nifi_rest_api_password);
+ const auto encrypted_api_key = encrypted_properties.getString(llm_api_key);
+ REQUIRE(encrypted_passphrase);
+ REQUIRE(encrypted_password);
+ REQUIRE(encrypted_api_key);
+ CHECK(encrypted_passphrase != original_passphrase);
+ CHECK(encrypted_password != original_password);
+ CHECK(encrypted_api_key != original_api_key);
+
+ minifi::PropertiesFile bootstrap_file{std::ifstream{minifi_home / "conf" /
"bootstrap.conf"}};
+ const auto encryption_key_hex =
bootstrap_file.getValue("nifi.bootstrap.sensitive.key");
+ REQUIRE(encryption_key_hex);
+ const auto encryption_key = utils::string::from_hex(*encryption_key_hex);
+
+ CHECK(utils::crypto::decrypt(*encrypted_passphrase, encryption_key) ==
*original_passphrase);
+ CHECK(utils::crypto::decrypt(*encrypted_password, encryption_key) ==
*original_password);
+ CHECK(utils::crypto::decrypt(*encrypted_api_key, encryption_key) ==
*original_api_key);
+}
diff --git a/encrypt-config/tests/ConfigFileEncryptorTests.cpp
b/encrypt-config/tests/PropertiesFileEncryptorTests.cpp
similarity index 68%
rename from encrypt-config/tests/ConfigFileEncryptorTests.cpp
rename to encrypt-config/tests/PropertiesFileEncryptorTests.cpp
index 4cdebb0bb..7b08dd990 100644
--- a/encrypt-config/tests/ConfigFileEncryptorTests.cpp
+++ b/encrypt-config/tests/PropertiesFileEncryptorTests.cpp
@@ -15,18 +15,20 @@
* limitations under the License.
*/
+#include <algorithm>
#include <fstream>
#include <optional>
#include <string>
-#include "ConfigFileEncryptor.h"
+#include "PropertiesFileEncryptor.h"
#include "properties/Configuration.h"
+#include "properties/PropertiesFile.h"
#include "utils/RegexUtils.h"
#include "unit/TestBase.h"
#include "unit/Catch.h"
-using org::apache::nifi::minifi::encrypt_config::ConfigFile;
+using org::apache::nifi::minifi::PropertiesFile;
using
org::apache::nifi::minifi::encrypt_config::encryptSensitivePropertiesInFile;
using org::apache::nifi::minifi::Configuration;
namespace utils = org::apache::nifi::minifi::utils;
@@ -36,7 +38,7 @@ size_t base64_length(size_t unencoded_length) {
return (unencoded_length + 2) / 3 * 4;
}
-bool check_encryption(const ConfigFile& test_file, const std::string&
property_name, size_t original_value_length) {
+bool check_encryption(const PropertiesFile& test_file, const std::string&
property_name, size_t original_value_length) {
const auto encrypted_value = test_file.getValue(property_name);
if (!encrypted_value) { return false; }
@@ -52,38 +54,47 @@ bool check_encryption(const ConfigFile& test_file, const
std::string& property_n
} // namespace
namespace org::apache::nifi::minifi {
-
// NOTE(fgerlits): these ==/!= operators are in the test file on purpose, and
should not be part of production code,
// as they take a varying amount of time depending on which character in the
line differs, so they would open up
// our code to timing attacks. If you need == in production code, make sure
to compare all pairs of chars/lines.
bool operator==(const PropertiesFile::Line& left, const PropertiesFile::Line&
right) { return left.getLine() == right.getLine(); }
+bool operator!=(const PropertiesFile::Line& left, const PropertiesFile::Line&
right) { return !(left == right); }
+bool operator==(const PropertiesFile& left, const PropertiesFile& right) {
return std::ranges::equal(left, right); }
+bool operator!=(const PropertiesFile& left, const PropertiesFile& right) {
return !(left == right); }
+} // namespace org::apache::nifi::minifi
-namespace encrypt_config {
-
-bool operator!=(const ConfigFile::Line& left, const ConfigFile::Line& right) {
return !(left == right); }
-bool operator==(const ConfigFile& left, const ConfigFile& right) { return
left.lines_ == right.lines_; }
-bool operator!=(const ConfigFile& left, const ConfigFile& right) { return
!(left == right); }
+TEST_CASE("PropertiesFileEncryptor can find the list of sensitive properties",
"[encrypt-config][getSensitiveProperties]") {
+ SECTION("default properties") {
+ const auto sensitive_properties =
minifi::encrypt_config::getSensitiveProperties(std::filesystem::path("resources")
/ "minifi.properties");
+ CHECK(sensitive_properties ==
std::vector<std::string>{Configuration::nifi_rest_api_password});
+ }
-} // namespace encrypt_config
-} // namespace org::apache::nifi::minifi
+ SECTION("with additional properties") {
+ const auto sensitive_properties =
minifi::encrypt_config::getSensitiveProperties(std::filesystem::path("resources")
/ "with-additional-sensitive-props.minifi.properties");
+ CHECK(sensitive_properties ==
std::vector<std::string>{Configuration::nifi_c2_enable,
Configuration::nifi_flow_configuration_file,
+ Configuration::nifi_rest_api_password,
Configuration::nifi_security_client_pass_phrase});
+ }
+}
-TEST_CASE("ConfigFileEncryptor can encrypt the sensitive properties",
"[encrypt-config][encryptSensitivePropertiesInFile]") {
+TEST_CASE("PropertiesFileEncryptor can encrypt the sensitive properties",
"[encrypt-config][encryptSensitivePropertiesInFile]") {
utils::crypto::Bytes KEY =
utils::string::from_base64("6q9u8LEDy1/CdmSBm8oSqPS/Ds5UOD2nRouP8yUoK10=");
SECTION("default properties") {
- ConfigFile test_file{std::ifstream{"resources/minifi.properties"}};
+ const auto test_file_path = std::filesystem::path("resources") /
"minifi.properties";
+ const auto sensitive_properties =
minifi::encrypt_config::getSensitiveProperties(test_file_path);
+ PropertiesFile test_file{std::ifstream{test_file_path}};
std::string original_password =
test_file.getValue(Configuration::nifi_rest_api_password).value();
- uint32_t initial_num_properties_encrypted =
encryptSensitivePropertiesInFile(test_file, KEY);
+ uint32_t initial_num_properties_encrypted =
encryptSensitivePropertiesInFile(test_file, sensitive_properties, KEY);
REQUIRE(initial_num_properties_encrypted == 1);
REQUIRE(test_file.size() == 109);
REQUIRE(check_encryption(test_file, Configuration::nifi_rest_api_password,
original_password.length()));
SECTION("calling encryptSensitiveValuesInMinifiProperties a second time
does nothing") {
- ConfigFile test_file_copy = test_file;
+ PropertiesFile test_file_copy = test_file;
- uint32_t num_properties_encrypted =
encryptSensitivePropertiesInFile(test_file, KEY);
+ uint32_t num_properties_encrypted =
encryptSensitivePropertiesInFile(test_file, sensitive_properties, KEY);
REQUIRE(num_properties_encrypted == 0);
REQUIRE(test_file == test_file_copy);
@@ -103,7 +114,7 @@ TEST_CASE("ConfigFileEncryptor can encrypt the sensitive
properties", "[encrypt-
test_file.update("nifi.rest.api.password.protected", "plaintext");
}
- uint32_t num_properties_encrypted =
encryptSensitivePropertiesInFile(test_file, KEY);
+ uint32_t num_properties_encrypted =
encryptSensitivePropertiesInFile(test_file, sensitive_properties, KEY);
REQUIRE(num_properties_encrypted == 1);
REQUIRE(check_encryption(test_file,
Configuration::nifi_rest_api_password, original_password.length()));
@@ -111,7 +122,9 @@ TEST_CASE("ConfigFileEncryptor can encrypt the sensitive
properties", "[encrypt-
}
SECTION("with additional properties") {
- ConfigFile
test_file{std::ifstream{"resources/with-additional-sensitive-props.minifi.properties"}};
+ const auto test_file_path = std::filesystem::path("resources") /
"with-additional-sensitive-props.minifi.properties";
+ const auto sensitive_properties =
minifi::encrypt_config::getSensitiveProperties(test_file_path);
+ PropertiesFile test_file{std::ifstream{test_file_path}};
size_t original_file_size = test_file.size();
std::string original_c2_enable =
test_file.getValue(Configuration::nifi_c2_enable).value();
@@ -119,7 +132,7 @@ TEST_CASE("ConfigFileEncryptor can encrypt the sensitive
properties", "[encrypt-
std::string original_password =
test_file.getValue(Configuration::nifi_rest_api_password).value();
std::string original_pass_phrase =
test_file.getValue(Configuration::nifi_security_client_pass_phrase).value();
- uint32_t num_properties_encrypted =
encryptSensitivePropertiesInFile(test_file, KEY);
+ uint32_t num_properties_encrypted =
encryptSensitivePropertiesInFile(test_file, sensitive_properties, KEY);
REQUIRE(num_properties_encrypted == 4);
REQUIRE(test_file.size() == original_file_size + 4);
diff --git a/encrypt-config/tests/resources/conf/bootstrap.conf
b/encrypt-config/tests/resources/conf/bootstrap.conf
new file mode 100644
index 000000000..7718ef273
--- /dev/null
+++ b/encrypt-config/tests/resources/conf/bootstrap.conf
@@ -0,0 +1 @@
+nifi.bootstrap.sensitive.key=b1743bb4f6b63aca6e56e2272a729ef8fe6ee9612fb073c16c60851f11588168
diff --git a/encrypt-config/tests/resources/conf/minifi.properties
b/encrypt-config/tests/resources/conf/minifi.properties
new file mode 100644
index 000000000..acdb9973c
--- /dev/null
+++ b/encrypt-config/tests/resources/conf/minifi.properties
@@ -0,0 +1,102 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+# Core Properties #
+nifi.flow.configuration.file=./conf/config.yml
+nifi.administrative.yield.duration=30 sec
+# If a component has no work to do (is "bored"), how long should we wait
before checking again for work?
+nifi.bored.yield.duration=10 millis
+
+# Provenance Repository #
+nifi.provenance.repository.directory.default=${MINIFI_HOME}/provenance_repository
+nifi.provenance.repository.max.storage.time=1 MIN
+nifi.provenance.repository.max.storage.size=1 MB
+nifi.flowfile.repository.directory.default=${MINIFI_HOME}/flowfile_repository
+nifi.database.content.repository.directory.default=${MINIFI_HOME}/content_repository
+
+nifi.remote.input.secure=true
+nifi.security.need.ClientAuth=
+nifi.security.client.certificate=
+nifi.security.client.private.key=
+nifi.security.client.pass.phrase=correct_horse_battery_staple
+nifi.security.client.ca.certificate=
+
+#nifi.rest.api.user.name=
+#nifi.rest.api.password=
+
+# State storage configuration #
+## The default state storage can be overridden by specifying a controller
service instance
+## that implements CoreComponentStateManagementProvider
+## (e.g. an instance of RocksDbPersistableKeyValueStoreService or
UnorderedMapPersistableKeyValueStoreService)
+#nifi.state.management.provider.local=
+## To make the default state storage persist every state change, set this to
true
+## this comes at a performance penalty, but makes sure no state is lost even
on unclean shutdowns
+#nifi.state.management.provider.local.always.persist=true
+## To change the frequency at which the default state storage is persisted,
modify the following
+#nifi.state.management.provider.local.auto.persistence.interval=1 min
+
+## Enabling C2 Uncomment each of the following options
+## define those with missing options
+nifi.c2.enable=true
+## base URL of the c2 server,
+## very likely the same base url of rest urls
+nifi.c2.flow.base.url=http://localhost:10080/c2-server/api
+nifi.c2.rest.url=http://localhost:10080/c2-server/api/c2-protocol/heartbeat
+nifi.c2.rest.url.ack=http://localhost:10080/c2-server/api/c2-protocol/acknowledge
+nifi.c2.root.classes=DeviceInfoNode,AgentInformation,FlowInformation,AssetInformation
+## Minimize heartbeat payload size by excluding agent manifest from the
heartbeat
+#nifi.c2.full.heartbeat=false
+## heartbeat 4 times a second
+#nifi.c2.agent.heartbeat.period=30 sec
+## define parameters about your agent
+nifi.c2.agent.class=EncryptConfigTester
+nifi.c2.agent.identifier=EncryptConfigTester-001
+
+## define metrics reported
+# nifi.c2.root.class.definitions=metrics
+# nifi.c2.root.class.definitions.metrics.name=metrics
+#
nifi.c2.root.class.definitions.metrics.metrics=runtimemetrics,loadmetrics,processorMetrics
+#
nifi.c2.root.class.definitions.metrics.metrics.runtimemetrics.name=RuntimeMetrics
+#
nifi.c2.root.class.definitions.metrics.metrics.runtimemetrics.classes=DeviceInfoNode,FlowInformation
+# nifi.c2.root.class.definitions.metrics.metrics.loadmetrics.name=LoadMetrics
+#
nifi.c2.root.class.definitions.metrics.metrics.loadmetrics.classes=QueueMetrics,RepositoryMetrics
+#
nifi.c2.root.class.definitions.metrics.metrics.processorMetrics.name=ProcessorMetric
+#
nifi.c2.root.class.definitions.metrics.metrics.processorMetrics.classes=GetFileMetrics
+
+## enable the controller socket provider on port 9998
+## off by default.
+#controller.socket.enable=true
+#controller.socket.host=localhost
+#controller.socket.port=9998
+#controller.socket.local.any.interface=false
+
+# must be comma separated
+nifi.c2.flow.id=
+nifi.c2.flow.url=
+
+# Publish metrics to external consumers
+# nifi.metrics.publisher.agent.identifier=
+# nifi.metrics.publisher.class=PrometheusMetricsPublisher
+# nifi.metrics.publisher.PrometheusMetricsPublisher.port=9936
+#
nifi.metrics.publisher.metrics=QueueMetrics,RepositoryMetrics,GetFileMetrics,DeviceInfoNode,FlowInformation
+
+# Python processor properties
+nifi.python.processor.dir=${MINIFI_HOME}/minifi-python/
+nifi.python.virtualenv.directory=${MINIFI_HOME}/minifi-python-env
+nifi.python.install.packages.automatically=true
+# nifi.python.env.setup.binary=python3
+
+# FIPS
+# nifi.openssl.fips.support.enable=false
diff --git
a/encrypt-config/tests/resources/conf/minifi.properties.d/20_llm.properties
b/encrypt-config/tests/resources/conf/minifi.properties.d/20_llm.properties
new file mode 100644
index 000000000..fcd67072d
--- /dev/null
+++ b/encrypt-config/tests/resources/conf/minifi.properties.d/20_llm.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+llm.server.url=https://some.llm.host.com/api
+llm.user.name=minifi
+llm.api.key=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
diff --git
a/encrypt-config/tests/resources/conf/minifi.properties.d/90_c2.properties
b/encrypt-config/tests/resources/conf/minifi.properties.d/90_c2.properties
new file mode 100644
index 000000000..4ef9e9ce2
--- /dev/null
+++ b/encrypt-config/tests/resources/conf/minifi.properties.d/90_c2.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+nifi.sensitive.props.additional.keys=llm.api.key
+
+nifi.rest.api.user.name=admin
+nifi.rest.api.password=password
diff --git a/libminifi/include/properties/Properties.h
b/libminifi/include/properties/Properties.h
index f4880a557..8c90082db 100644
--- a/libminifi/include/properties/Properties.h
+++ b/libminifi/include/properties/Properties.h
@@ -31,6 +31,14 @@
#include "utils/StringUtils.h"
#include "minifi-cpp/properties/Properties.h"
+namespace org::apache::nifi::minifi::properties {
+
+std::vector<std::filesystem::path> getExtraPropertiesFileNames(const
std::filesystem::path& extra_properties_files_dir, const
std::shared_ptr<core::logging::Logger>& logger);
+
+std::filesystem::path extraPropertiesFilesDirName(const std::filesystem::path&
base_properties_file);
+
+} // namespace org::apache::nifi::minifi::properties
+
namespace org::apache::nifi::minifi {
class PropertiesImpl : public virtual Properties {
diff --git a/libminifi/include/properties/PropertiesFile.h
b/libminifi/include/properties/PropertiesFile.h
index b423b175d..a332cd8c3 100644
--- a/libminifi/include/properties/PropertiesFile.h
+++ b/libminifi/include/properties/PropertiesFile.h
@@ -42,7 +42,6 @@ class PropertiesFile {
static bool isValidKey(const std::string& key);
private:
- friend bool operator==(const Line&, const Line&);
// NOTE(fgerlits): having both line_ and { key_, value } is redundant in
many cases, but
// * we need the original line_ in order to preserve formatting, comments
and blank lines
// * we could get rid of key_ and value_ and parse them each time from
line_, but I think the code is clearer this way
diff --git a/libminifi/src/properties/Properties.cpp
b/libminifi/src/properties/Properties.cpp
index 2f5723a64..384a85d3f 100644
--- a/libminifi/src/properties/Properties.cpp
+++ b/libminifi/src/properties/Properties.cpp
@@ -160,20 +160,6 @@ void fixValidatedProperty(const std::string& property_name,
}
}
-auto getExtraPropertiesFileNames(const std::filesystem::path&
extra_properties_files_dir, const std::shared_ptr<core::logging::Logger>&
logger) {
- std::vector<std::filesystem::path> extra_properties_file_names;
- if (utils::file::exists(extra_properties_files_dir) &&
utils::file::is_directory(extra_properties_files_dir)) {
- utils::file::list_dir(extra_properties_files_dir, [&](const
std::filesystem::path&, const std::filesystem::path& file_name) {
- if (file_name.string().ends_with(".properties")) {
- extra_properties_file_names.push_back(file_name);
- }
- return true;
- }, logger, /* recursive = */ false);
- }
- std::ranges::sort(extra_properties_file_names);
- return extra_properties_file_names;
-}
-
void updateChangedPropertiesInPropertiesFile(minifi::PropertiesFile&
current_content, const auto& properties) {
for (const auto& prop : properties) {
if (!prop.second.need_to_persist_new_value) {
@@ -188,12 +174,30 @@ void
updateChangedPropertiesInPropertiesFile(minifi::PropertiesFile& current_con
}
} // namespace
-std::filesystem::path PropertiesImpl::extraPropertiesFilesDirName() const {
- auto extra_properties_files_dir = base_properties_file_;
+std::vector<std::filesystem::path>
properties::getExtraPropertiesFileNames(const std::filesystem::path&
extra_properties_files_dir, const std::shared_ptr<core::logging::Logger>&
logger) {
+ std::vector<std::filesystem::path> extra_properties_file_names;
+ if (utils::file::exists(extra_properties_files_dir) &&
utils::file::is_directory(extra_properties_files_dir)) {
+ utils::file::list_dir(extra_properties_files_dir, [&](const
std::filesystem::path&, const std::filesystem::path& file_name) {
+ if (file_name.string().ends_with(".properties")) {
+ extra_properties_file_names.push_back(file_name);
+ }
+ return true;
+ }, logger, /* recursive = */ false);
+ }
+ std::ranges::sort(extra_properties_file_names);
+ return extra_properties_file_names;
+}
+
+std::filesystem::path properties::extraPropertiesFilesDirName(const
std::filesystem::path& base_properties_file) {
+ auto extra_properties_files_dir = base_properties_file;
extra_properties_files_dir += ".d";
return extra_properties_files_dir;
}
+std::filesystem::path PropertiesImpl::extraPropertiesFilesDirName() const {
+ return properties::extraPropertiesFilesDirName(base_properties_file_);
+}
+
void PropertiesImpl::loadConfigureFile(const std::filesystem::path&
configuration_file, std::string_view prefix) {
std::lock_guard<std::mutex> lock(mutex_);
if (configuration_file.empty()) {
@@ -217,7 +221,7 @@ void PropertiesImpl::loadConfigureFile(const
std::filesystem::path& configuratio
properties_files_ = { base_properties_file_ };
const auto extra_properties_files_dir = extraPropertiesFilesDirName();
- const auto extra_properties_file_names =
getExtraPropertiesFileNames(extra_properties_files_dir, logger_);
+ const auto extra_properties_file_names =
properties::getExtraPropertiesFileNames(extra_properties_files_dir, logger_);
for (const auto& file_name : extra_properties_file_names) {
properties_files_.push_back(extra_properties_files_dir / file_name);
}
diff --git a/libminifi/test/resources/minifi.properties
b/libminifi/test/resources/minifi.properties
new file mode 100644
index 000000000..107aee455
--- /dev/null
+++ b/libminifi/test/resources/minifi.properties
@@ -0,0 +1,108 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+# Core Properties #
+nifi.flow.configuration.file=./conf/config.yml
+nifi.administrative.yield.duration=30 sec
+# If a component has no work to do (is "bored"), how long should we wait
before checking again for work?
+nifi.bored.yield.duration=10 millis
+
+# Provenance Repository #
+nifi.provenance.repository.directory.default=${MINIFI_HOME}/provenance_repository
+nifi.provenance.repository.max.storage.time=1 MIN
+nifi.provenance.repository.max.storage.size=1 MB
+nifi.flowfile.repository.directory.default=${MINIFI_HOME}/flowfile_repository
+nifi.database.content.repository.directory.default=${MINIFI_HOME}/content_repository
+
+#nifi.remote.input.secure=true
+#nifi.security.need.ClientAuth=
+#nifi.security.client.certificate=
+#nifi.security.client.private.key=
+#nifi.security.client.pass.phrase=
+#nifi.security.client.ca.certificate=
+
+nifi.rest.api.user.name=admin
+nifi.rest.api.password=password
+
+# State storage configuration #
+## The default state storage can be overridden by specifying a controller
service instance
+## that implements CoreComponentStateManagementProvider
+## (e.g. an instance of RocksDbPersistableKeyValueStoreService or
UnorderedMapPersistableKeyValueStoreService)
+#nifi.state.management.provider.local=
+## To make the default state storage persist every state change, set this to
true
+## this comes at a performance penalty, but makes sure no state is lost even
on unclean shutdowns
+#nifi.state.management.provider.local.always.persist=true
+## To change the frequency at which the default state storage is persisted,
modify the following
+#nifi.state.management.provider.local.auto.persistence.interval=1 min
+
+## Enabling C2 Uncomment each of the following options
+## define those with missing options
+nifi.c2.enable=true
+## base URL of the c2 server,
+## very likely the same base url of rest urls
+nifi.c2.flow.base.url=http://localhost:10080/c2-server/api
+nifi.c2.rest.url=http://localhost:10080/c2-server/api/c2-protocol/heartbeat
+nifi.c2.rest.url.ack=http://localhost:10080/c2-server/api/c2-protocol/acknowledge
+nifi.c2.root.classes=DeviceInfoNode,AgentInformation,FlowInformation,AssetInformation
+## Minimize heartbeat payload size by excluding agent manifest from the
heartbeat
+#nifi.c2.full.heartbeat=false
+## heartbeat 4 times a second
+#nifi.c2.agent.heartbeat.period=30 sec
+## define parameters about your agent
+nifi.c2.agent.class=EncryptConfigTester
+nifi.c2.agent.identifier=EncryptConfigTester-001
+
+## define metrics reported
+# nifi.c2.root.class.definitions=metrics
+# nifi.c2.root.class.definitions.metrics.name=metrics
+#
nifi.c2.root.class.definitions.metrics.metrics=runtimemetrics,loadmetrics,processorMetrics
+#
nifi.c2.root.class.definitions.metrics.metrics.runtimemetrics.name=RuntimeMetrics
+#
nifi.c2.root.class.definitions.metrics.metrics.runtimemetrics.classes=DeviceInfoNode,FlowInformation
+# nifi.c2.root.class.definitions.metrics.metrics.loadmetrics.name=LoadMetrics
+#
nifi.c2.root.class.definitions.metrics.metrics.loadmetrics.classes=QueueMetrics,RepositoryMetrics
+#
nifi.c2.root.class.definitions.metrics.metrics.processorMetrics.name=ProcessorMetric
+#
nifi.c2.root.class.definitions.metrics.metrics.processorMetrics.classes=GetFileMetrics
+
+## enable the controller socket provider on port 9998
+## off by default.
+#controller.socket.enable=true
+#controller.socket.host=localhost
+#controller.socket.port=9998
+#controller.socket.local.any.interface=false
+
+## specify the destination of c2 directed assets
+#nifi.asset.directory=${MINIFI_HOME}/asset
+
+## You probably don't need to touch this, but you can if you want to
+# nifi.default.internal.buffer.size=4096
+
+# must be comma separated
+nifi.c2.flow.id=
+nifi.c2.flow.url=
+
+# Publish metrics to external consumers
+# nifi.metrics.publisher.agent.identifier=
+# nifi.metrics.publisher.class=PrometheusMetricsPublisher
+# nifi.metrics.publisher.PrometheusMetricsPublisher.port=9936
+#
nifi.metrics.publisher.metrics=QueueMetrics,RepositoryMetrics,GetFileMetrics,DeviceInfoNode,FlowInformation
+
+# Python processor properties
+nifi.python.processor.dir=${MINIFI_HOME}/minifi-python/
+nifi.python.virtualenv.directory=${MINIFI_HOME}/minifi-python-env
+nifi.python.install.packages.automatically=true
+# nifi.python.env.setup.binary=python3
+
+# FIPS
+# nifi.openssl.fips.support.enable=false
diff --git
a/libminifi/test/resources/with-additional-sensitive-props.minifi.properties
b/libminifi/test/resources/with-additional-sensitive-props.minifi.properties
new file mode 100644
index 000000000..c270aef68
--- /dev/null
+++ b/libminifi/test/resources/with-additional-sensitive-props.minifi.properties
@@ -0,0 +1,104 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+# Core Properties #
+nifi.flow.configuration.file=./conf/config.yml
+nifi.administrative.yield.duration=30 sec
+# If a component has no work to do (is "bored"), how long should we wait
before checking again for work?
+nifi.bored.yield.duration=10 millis
+
+# Provenance Repository #
+nifi.provenance.repository.directory.default=${MINIFI_HOME}/provenance_repository
+nifi.provenance.repository.max.storage.time=1 MIN
+nifi.provenance.repository.max.storage.size=1 MB
+nifi.flowfile.repository.directory.default=${MINIFI_HOME}/flowfile_repository
+nifi.database.content.repository.directory.default=${MINIFI_HOME}/content_repository
+
+nifi.remote.input.secure=true
+nifi.security.need.ClientAuth=
+nifi.security.client.certificate=
+nifi.security.client.private.key=
+nifi.security.client.pass.phrase=correct_horse_battery_staple
+nifi.security.client.ca.certificate=
+
+nifi.sensitive.props.additional.keys=nifi.flow.configuration.file
,nifi.rest.api.password, nifi.c2.enable,
+
+nifi.rest.api.user.name=admin
+nifi.rest.api.password=password
+
+# State storage configuration #
+## The default state storage can be overridden by specifying a controller
service instance
+## that implements CoreComponentStateManagementProvider
+## (e.g. an instance of RocksDbPersistableKeyValueStoreService or
UnorderedMapPersistableKeyValueStoreService)
+#nifi.state.management.provider.local=
+## To make the default state storage persist every state change, set this to
true
+## this comes at a performance penalty, but makes sure no state is lost even
on unclean shutdowns
+#nifi.state.management.provider.local.always.persist=true
+## To change the frequency at which the default state storage is persisted,
modify the following
+#nifi.state.management.provider.local.auto.persistence.interval=1 min
+
+## Enabling C2 Uncomment each of the following options
+## define those with missing options
+nifi.c2.enable=true
+## base URL of the c2 server,
+## very likely the same base url of rest urls
+nifi.c2.flow.base.url=http://localhost:10080/c2-server/api
+nifi.c2.rest.url=http://localhost:10080/c2-server/api/c2-protocol/heartbeat
+nifi.c2.rest.url.ack=http://localhost:10080/c2-server/api/c2-protocol/acknowledge
+nifi.c2.root.classes=DeviceInfoNode,AgentInformation,FlowInformation,AssetInformation
+## Minimize heartbeat payload size by excluding agent manifest from the
heartbeat
+#nifi.c2.full.heartbeat=false
+## heartbeat 4 times a second
+#nifi.c2.agent.heartbeat.period=30 sec
+## define parameters about your agent
+nifi.c2.agent.class=EncryptConfigTester
+nifi.c2.agent.identifier=EncryptConfigTester-001
+
+## define metrics reported
+# nifi.c2.root.class.definitions=metrics
+# nifi.c2.root.class.definitions.metrics.name=metrics
+#
nifi.c2.root.class.definitions.metrics.metrics=runtimemetrics,loadmetrics,processorMetrics
+#
nifi.c2.root.class.definitions.metrics.metrics.runtimemetrics.name=RuntimeMetrics
+#
nifi.c2.root.class.definitions.metrics.metrics.runtimemetrics.classes=DeviceInfoNode,FlowInformation
+# nifi.c2.root.class.definitions.metrics.metrics.loadmetrics.name=LoadMetrics
+#
nifi.c2.root.class.definitions.metrics.metrics.loadmetrics.classes=QueueMetrics,RepositoryMetrics
+#
nifi.c2.root.class.definitions.metrics.metrics.processorMetrics.name=ProcessorMetric
+#
nifi.c2.root.class.definitions.metrics.metrics.processorMetrics.classes=GetFileMetrics
+
+## enable the controller socket provider on port 9998
+## off by default.
+#controller.socket.enable=true
+#controller.socket.host=localhost
+#controller.socket.port=9998
+#controller.socket.local.any.interface=false
+
+# must be comma separated
+nifi.c2.flow.id=
+nifi.c2.flow.url=
+
+# Publish metrics to external consumers
+# nifi.metrics.publisher.agent.identifier=
+# nifi.metrics.publisher.class=PrometheusMetricsPublisher
+# nifi.metrics.publisher.PrometheusMetricsPublisher.port=9936
+#
nifi.metrics.publisher.metrics=QueueMetrics,RepositoryMetrics,GetFileMetrics,DeviceInfoNode,FlowInformation
+
+# Python processor properties
+nifi.python.processor.dir=${MINIFI_HOME}/minifi-python/
+nifi.python.virtualenv.directory=${MINIFI_HOME}/minifi-python-env
+nifi.python.install.packages.automatically=true
+# nifi.python.env.setup.binary=python3
+
+# FIPS
+# nifi.openssl.fips.support.enable=false
diff --git a/encrypt-config/tests/ConfigFileTests.cpp
b/libminifi/test/unit/PropertiesFileTests.cpp
similarity index 65%
rename from encrypt-config/tests/ConfigFileTests.cpp
rename to libminifi/test/unit/PropertiesFileTests.cpp
index 6944ac7e3..2b4de07ab 100644
--- a/encrypt-config/tests/ConfigFileTests.cpp
+++ b/libminifi/test/unit/PropertiesFileTests.cpp
@@ -20,8 +20,8 @@
#include <string>
#include <vector>
-#include "ConfigFile.h"
#include "properties/Configuration.h"
+#include "properties/PropertiesFile.h"
#include "minifi-cpp/utils/gsl.h"
@@ -29,13 +29,13 @@
#include "unit/Catch.h"
#include "utils/file/FileUtils.h"
-using org::apache::nifi::minifi::encrypt_config::ConfigFile;
+using org::apache::nifi::minifi::PropertiesFile;
using org::apache::nifi::minifi::Configuration;
-TEST_CASE("ConfigLine can be constructed from a line",
"[encrypt-config][constructor]") {
+TEST_CASE("PropertiesFile::Line can be constructed from a line",
"[constructor]") {
auto line_is_parsed_correctly = [](const std::string& line, const
std::string& expected_key, const std::string& expected_value) {
- ConfigFile::Line config_line{line};
- return config_line.getKey() == expected_key && config_line.getValue() ==
expected_value;
+ PropertiesFile::Line properties_file_line{line};
+ return properties_file_line.getKey() == expected_key &&
properties_file_line.getValue() == expected_value;
};
REQUIRE(line_is_parsed_correctly("", "", ""));
@@ -54,21 +54,21 @@ TEST_CASE("ConfigLine can be constructed from a line",
"[encrypt-config][constru
REQUIRE(line_is_parsed_correctly("nifi.some.key=value=with=equals=signs=",
"nifi.some.key", "value=with=equals=signs="));
}
-TEST_CASE("ConfigLine can be constructed from a key-value pair",
"[encrypt-config][constructor]") {
+TEST_CASE("PropertiesFile::Line can be constructed from a key-value pair",
"[constructor]") {
auto can_construct_from_kv = [](const std::string& key, const std::string&
value, const std::string& expected_line) {
- ConfigFile::Line config_line{key, value};
- return config_line.getLine() == expected_line;
+ PropertiesFile::Line properties_file_line{key, value};
+ return properties_file_line.getLine() == expected_line;
};
REQUIRE(can_construct_from_kv("nifi.some.key", "", "nifi.some.key="));
REQUIRE(can_construct_from_kv("nifi.some.key", "some_value",
"nifi.some.key=some_value"));
}
-TEST_CASE("ConfigLine can update the value", "[encrypt-config][updateValue]") {
+TEST_CASE("PropertiesFile::Line can update the value", "[updateValue]") {
auto can_update_value = [](const std::string& original_line, const
std::string& new_value, const std::string& expected_line) {
- ConfigFile::Line config_line{original_line};
- config_line.updateValue(new_value);
- return config_line.getLine() == expected_line;
+ PropertiesFile::Line properties_file_line{original_line};
+ properties_file_line.updateValue(new_value);
+ return properties_file_line.getLine() == expected_line;
};
REQUIRE(can_update_value("nifi.some.key=some_value", "new_value",
"nifi.some.key=new_value"));
@@ -83,25 +83,25 @@ TEST_CASE("ConfigLine can update the value",
"[encrypt-config][updateValue]") {
REQUIRE(can_update_value(" nifi.some.key =\tsome_value\r", "some_value", "
nifi.some.key =some_value"));
}
-TEST_CASE("ConfigFile creates an empty object from a nonexistent file",
"[encrypt-config][constructor]") {
- ConfigFile
test_file{std::ifstream{"resources/nonexistent-minifi.properties"}};
+TEST_CASE("PropertiesFile creates an empty object from a nonexistent file",
"[constructor]") {
+ PropertiesFile test_file{std::ifstream{std::filesystem::path{TEST_RESOURCES}
/ "nonexistent-minifi.properties"}};
REQUIRE(test_file.size() == 0);
}
-TEST_CASE("ConfigFile can parse a simple config file",
"[encrypt-config][constructor]") {
- ConfigFile test_file{std::ifstream{"resources/minifi.properties"}};
+TEST_CASE("PropertiesFile can parse a simple config file", "[constructor]") {
+ PropertiesFile test_file{std::ifstream{std::filesystem::path{TEST_RESOURCES}
/ "minifi.properties"}};
REQUIRE(test_file.size() == 108);
}
-TEST_CASE("ConfigFile can test whether a key is present",
"[encrypt-config][hasValue]") {
- ConfigFile test_file{std::ifstream{"resources/minifi.properties"}};
+TEST_CASE("PropertiesFile can test whether a key is present", "[hasValue]") {
+ PropertiesFile test_file{std::ifstream{std::filesystem::path{TEST_RESOURCES}
/ "minifi.properties"}};
REQUIRE(test_file.hasValue(Configuration::nifi_c2_flow_id)); // present but
blank
REQUIRE(!test_file.hasValue(Configuration::nifi_remote_input_secure)); //
commented out
REQUIRE(!test_file.hasValue("nifi.this.property.does.not.exist"));
}
-TEST_CASE("ConfigFile can read empty properties correctly",
"[encrypt-config][constructor]") {
- ConfigFile
test_file{std::ifstream{"resources/with-additional-sensitive-props.minifi.properties"}};
+TEST_CASE("PropertiesFile can read empty properties correctly",
"[constructor]") {
+ PropertiesFile test_file{std::ifstream{std::filesystem::path{TEST_RESOURCES}
/ "with-additional-sensitive-props.minifi.properties"}};
REQUIRE(test_file.size() == 104);
auto empty_property =
test_file.getValue(Configuration::nifi_security_need_ClientAuth);
@@ -113,8 +113,8 @@ TEST_CASE("ConfigFile can read empty properties correctly",
"[encrypt-config][co
REQUIRE(whitespace_property->empty());
}
-TEST_CASE("ConfigFile can find the value for a key",
"[encrypt-config][getValue]") {
- ConfigFile test_file{std::ifstream{"resources/minifi.properties"}};
+TEST_CASE("PropertiesFile can find the value for a key", "[getValue]") {
+ PropertiesFile test_file{std::ifstream{std::filesystem::path{TEST_RESOURCES}
/ "minifi.properties"}};
SECTION("valid key") {
REQUIRE(test_file.getValue(Configuration::nifi_bored_yield_duration) ==
"10 millis");
@@ -125,8 +125,8 @@ TEST_CASE("ConfigFile can find the value for a key",
"[encrypt-config][getValue]
}
}
-TEST_CASE("ConfigFile can update the value for a key",
"[encrypt-config][update]") {
- ConfigFile test_file{std::ifstream{"resources/minifi.properties"}};
+TEST_CASE("PropertiesFile can update the value for a key", "[update]") {
+ PropertiesFile test_file{std::ifstream{std::filesystem::path{TEST_RESOURCES}
/ "minifi.properties"}};
SECTION("valid key") {
test_file.update(Configuration::nifi_bored_yield_duration, "20 millis");
@@ -138,8 +138,8 @@ TEST_CASE("ConfigFile can update the value for a key",
"[encrypt-config][update]
}
}
-TEST_CASE("ConfigFile can add a new setting after an existing setting",
"[encrypt-config][insertAfter]") {
- ConfigFile test_file{std::ifstream{"resources/minifi.properties"}};
+TEST_CASE("PropertiesFile can add a new setting after an existing setting",
"[insertAfter]") {
+ PropertiesFile test_file{std::ifstream{std::filesystem::path{TEST_RESOURCES}
/ "minifi.properties"}};
SECTION("valid key") {
test_file.insertAfter(Configuration::nifi_rest_api_password,
"nifi.rest.api.password.protected", "my-cipher-name");
@@ -152,8 +152,8 @@ TEST_CASE("ConfigFile can add a new setting after an
existing setting", "[encryp
}
}
-TEST_CASE("ConfigFile can add a new setting at the end",
"[encrypt-config][append]") {
- ConfigFile test_file{std::ifstream{"resources/minifi.properties"}};
+TEST_CASE("PropertiesFile can add a new setting at the end", "[append]") {
+ PropertiesFile test_file{std::ifstream{std::filesystem::path{TEST_RESOURCES}
/ "minifi.properties"}};
const std::string KEY = "nifi.bootstrap.sensitive.key";
const std::string VALUE =
"aa411f289c91685ef9d5a9e5a4fad9393ff4c7a78ab978484323488caed7a9ab";
@@ -162,8 +162,8 @@ TEST_CASE("ConfigFile can add a new setting at the end",
"[encrypt-config][appen
REQUIRE(test_file.getValue(KEY) == std::make_optional(VALUE));
}
-TEST_CASE("ConfigFile can write to a new file", "[encrypt-config][writeTo]") {
- ConfigFile test_file{std::ifstream{"resources/minifi.properties"}};
+TEST_CASE("PropertiesFile can write to a new file", "[writeTo]") {
+ PropertiesFile test_file{std::ifstream{std::filesystem::path{TEST_RESOURCES}
/ "minifi.properties"}};
test_file.update(Configuration::nifi_bored_yield_duration, "20 millis");
TestController test_controller;
@@ -173,29 +173,13 @@ TEST_CASE("ConfigFile can write to a new file",
"[encrypt-config][writeTo]") {
test_file.writeTo(file_path);
- ConfigFile test_file_copy{std::ifstream{file_path}};
+ PropertiesFile test_file_copy{std::ifstream{file_path}};
REQUIRE(test_file.size() == test_file_copy.size());
REQUIRE(test_file_copy.getValue(Configuration::nifi_bored_yield_duration) ==
"20 millis");
}
-TEST_CASE("ConfigFile will throw if we try to write to an invalid file name",
"[encrypt-config][writeTo]") {
- ConfigFile test_file{std::ifstream{"resources/minifi.properties"}};
+TEST_CASE("PropertiesFile will throw if we try to write to an invalid file
name", "[writeTo]") {
+ PropertiesFile test_file{std::ifstream{std::filesystem::path{TEST_RESOURCES}
/ "minifi.properties"}};
const char* file_path =
"/tmp/3915913c-b37d-4adc-b6a8-b8e36e44c639/6ede949c-12b3-4a91-8956-71bc6ab6f73e/some.file";
REQUIRE_THROWS(test_file.writeTo(file_path));
}
-
-TEST_CASE("ConfigFile can find the list of sensitive properties",
"[encrypt-config][getSensitiveProperties]") {
- SECTION("default properties") {
- ConfigFile test_file{std::ifstream{"resources/minifi.properties"}};
- std::vector<std::string>
expected_properties{Configuration::nifi_rest_api_password};
- REQUIRE(test_file.getSensitiveProperties() == expected_properties);
- }
-
- SECTION("with additional properties") {
- ConfigFile
test_file{std::ifstream{"resources/with-additional-sensitive-props.minifi.properties"}};
- std::vector<std::string> expected_properties{
- Configuration::nifi_c2_enable,
Configuration::nifi_flow_configuration_file,
- Configuration::nifi_rest_api_password,
Configuration::nifi_security_client_pass_phrase};
- REQUIRE(test_file.getSensitiveProperties() == expected_properties);
- }
-}