This is an automated email from the ASF dual-hosted git repository.
fgerlits 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 7b54eb7db MINIFICPP-2787 Do not encrypt empty properties and
parameters (#2217)
7b54eb7db is described below
commit 7b54eb7db84cb020434937ff429945ec9e529bbe
Author: Ferenc Gerlits <[email protected]>
AuthorDate: Mon Jul 27 17:35:54 2026 +0200
MINIFICPP-2787 Do not encrypt empty properties and parameters (#2217)
This also fixes a bug: if a sensitive value was set as
sensitive_property:
instead of
sensitive_property: ""
in a YAML flow configuration, then the value was null instead of an empty
string, and it got expanded to the string "null", and then encrypted.
---
.../PropertyEncryptionUtils.cpp | 2 +-
libminifi/src/core/yaml/YamlFlowSerializer.cpp | 8 ++---
libminifi/test/libtest/unit/Catch.h | 17 ++++++++-
libminifi/test/unit/ExpectedTest.cpp | 9 +++++
libminifi/test/unit/JsonFlowSerializerTests.cpp | 40 ++++++++++++++++++++++
.../test/unit/PropertyEncryptionUtilsTests.cpp | 12 +++----
libminifi/test/unit/YamlFlowSerializerTests.cpp | 39 ++++++++++++++++++++-
7 files changed, 112 insertions(+), 15 deletions(-)
diff --git
a/core-framework/src/utils/crypto/property_encryption/PropertyEncryptionUtils.cpp
b/core-framework/src/utils/crypto/property_encryption/PropertyEncryptionUtils.cpp
index 7f9cbdb74..f88ab1416 100644
---
a/core-framework/src/utils/crypto/property_encryption/PropertyEncryptionUtils.cpp
+++
b/core-framework/src/utils/crypto/property_encryption/PropertyEncryptionUtils.cpp
@@ -39,7 +39,7 @@ std::string decrypt(std::string_view input, const
utils::crypto::EncryptionProvi
}
std::string encrypt(std::string_view input, const
utils::crypto::EncryptionProvider& encryption_provider) {
- if (isEncrypted(input)) {
+ if (input.empty() || isEncrypted(input)) {
return std::string{input};
}
return utils::string::join_pack("enc{", encryption_provider.encrypt(input),
"}");
diff --git a/libminifi/src/core/yaml/YamlFlowSerializer.cpp
b/libminifi/src/core/yaml/YamlFlowSerializer.cpp
index eff5f49aa..eba2d016c 100644
--- a/libminifi/src/core/yaml/YamlFlowSerializer.cpp
+++ b/libminifi/src/core/yaml/YamlFlowSerializer.cpp
@@ -65,7 +65,7 @@ void
YamlFlowSerializer::addProviderCreatedParameterContexts(YAML::Node flow_def
int64_t index = -1;
for (int64_t i = 0; i <
gsl::narrow<int64_t>(parameter_contexts_node.size()); ++i) {
- if (parameter_contexts_node[i][schema.name[0]].as<std::string>() ==
parameter_context->getName()) {
+ if (parameter_contexts_node[i][schema.name[0]].Scalar() ==
parameter_context->getName()) {
index = i;
break;
}
@@ -85,7 +85,7 @@ void
YamlFlowSerializer::encryptSensitiveProperties(YAML::Node property_yamls,
std::unordered_set<std::string> processed_property_names;
for (auto kv : property_yamls) {
- auto name = kv.first.as<std::string>();
+ auto name = kv.first.Scalar();
if (!properties.contains(name)) {
logger_->log_warn("Property {} found in flow definition does not
exist!", name);
continue;
@@ -94,12 +94,12 @@ void
YamlFlowSerializer::encryptSensitiveProperties(YAML::Node property_yamls,
if (kv.second.IsSequence()) {
for (auto property_item : kv.second) {
const auto override_value = overrides.get(name);
- auto value = override_value ? *override_value :
property_item["value"].as<std::string>();
+ auto value = override_value ? *override_value :
property_item["value"].Scalar();
property_item["value"] =
utils::crypto::property_encryption::encrypt(value, encryption_provider);
}
} else {
const auto override_value = overrides.get(name);
- auto value = override_value ? *override_value :
kv.second.as<std::string>();
+ auto value = override_value ? *override_value : kv.second.Scalar();
property_yamls[name] =
utils::crypto::property_encryption::encrypt(value, encryption_provider);
}
processed_property_names.insert(name);
diff --git a/libminifi/test/libtest/unit/Catch.h
b/libminifi/test/libtest/unit/Catch.h
index 3a6ff3bce..742acdb81 100644
--- a/libminifi/test/libtest/unit/Catch.h
+++ b/libminifi/test/libtest/unit/Catch.h
@@ -17,9 +17,11 @@
#pragma once
+#include <chrono>
+#include <expected>
#include <optional>
#include <string>
-#include <chrono>
+
#include "fmt/format.h"
#include "catch2/catch_test_macros.hpp"
#include "catch2/catch_tostring.hpp"
@@ -56,6 +58,19 @@ struct StringMaker<std::chrono::file_clock::duration> {
return fmt::format("{} {} s", duration.count(),
Catch::ratio_string<std::chrono::file_clock::duration::period>::symbol());
}
};
+
+template <typename T, typename E>
+struct StringMaker<std::expected<T, E>> {
+ static std::string convert(const std::expected<T, E>& expected) {
+ if (!expected) {
+ return fmt::format("unexpected({})",
Catch::Detail::stringify(expected.error()));
+ }
+ if constexpr (!std::is_void_v<T>) {
+ return fmt::format("expected({})", Catch::Detail::stringify(*expected));
+ }
+ return "expected(void)";
+ }
+};
} // namespace Catch
namespace org::apache::nifi::minifi::test {
diff --git a/libminifi/test/unit/ExpectedTest.cpp
b/libminifi/test/unit/ExpectedTest.cpp
index 11bc0e9ec..b68e95ed4 100644
--- a/libminifi/test/unit/ExpectedTest.cpp
+++ b/libminifi/test/unit/ExpectedTest.cpp
@@ -578,3 +578,12 @@ TEST_CASE("This fails to compile with std::expected on GCC
15.1 due to https://g
CHECK(a == b);
}
+
+TEST_CASE("Test Catch2 stringification of expected") {
+ CHECK(Catch::Detail::stringify(std::expected<int, std::string>{123}) ==
"expected(123)");
+ CHECK(Catch::Detail::stringify(std::expected<std::string,
std::string>{"hello"}) == R"(expected("hello"))");
+ CHECK(Catch::Detail::stringify(std::expected<std::string,
std::error_code>{"hello"}) == R"(expected("hello"))");
+ CHECK(Catch::Detail::stringify(std::expected<std::string,
std::unique_ptr<int>>{"hello"}) == R"(expected("hello"))");
+ CHECK(Catch::Detail::stringify(std::expected<void, std::string>{}) ==
"expected(void)");
+ CHECK(Catch::Detail::stringify(std::expected<std::string,
std::string>{std::unexpect, "doesn't compute"}) == R"(unexpected("doesn't
compute"))");
+}
diff --git a/libminifi/test/unit/JsonFlowSerializerTests.cpp
b/libminifi/test/unit/JsonFlowSerializerTests.cpp
index 62fad1d3d..88097b040 100644
--- a/libminifi/test/unit/JsonFlowSerializerTests.cpp
+++ b/libminifi/test/unit/JsonFlowSerializerTests.cpp
@@ -749,6 +749,46 @@ TEST_CASE("The encrypted flow configuration cannot be
decrypted with an incorrec
REQUIRE_THROWS_AS(json_configuration_after.getRootFromPayload(config_json_encrypted),
minifi::utils::crypto::EncryptionError);
}
+TEST_CASE("Empty sensitive properties and parameters are not encrypted and
round-trip as empty") {
+ ConfigurationTestController test_controller;
+ auto configuration_context = test_controller.getContext();
+ configuration_context.sensitive_values_encryptor = encryption_provider;
+
+ const auto schema = core::flow::FlowSchema::getNiFiFlowJson();
+ std::string config_json_with_empty_passphrase_and_parameter =
minifi::utils::string::join_pack(config_json_with_nifi_schema_part_1,
config_json_with_nifi_schema_part_2);
+
minifi::utils::string::replaceAll(config_json_with_empty_passphrase_and_parameter,
"very_secure_passphrase", "");
+
minifi::utils::string::replaceAll(config_json_with_empty_passphrase_and_parameter,
"param_value_1", "");
+
+ core::flow::AdaptiveConfiguration
json_configuration_before{configuration_context};
+ const auto process_group_before =
json_configuration_before.getRootFromPayload(config_json_with_empty_passphrase_and_parameter);
+ REQUIRE(process_group_before);
+
+ rapidjson::Document doc;
+ rapidjson::ParseResult res =
doc.Parse(config_json_with_empty_passphrase_and_parameter.data(),
config_json_with_empty_passphrase_and_parameter.size());
+ REQUIRE(res);
+ const auto flow_serializer = core::json::JsonFlowSerializer{std::move(doc)};
+
+ const auto processor_id =
minifi::utils::Identifier::parse("469617f1-3898-4bbf-91fe-27d8f4dd2a75").value();
+ const OverridesMap overrides{{processor_id,
core::flow::Overrides{}.add("invokehttp-proxy-password", "")}};
+ const std::string serialized =
flow_serializer.serialize(*process_group_before, schema, encryption_provider,
overrides, {});
+ CHECK_FALSE(serialized.contains("enc{"));
+
+ core::flow::AdaptiveConfiguration
json_configuration_after{configuration_context};
+ const auto process_group_after =
json_configuration_after.getRootFromPayload(serialized);
+ REQUIRE(process_group_after);
+
+ const auto* processor_after =
process_group_after->findProcessorById(processor_id);
+ REQUIRE(processor_after);
+ CHECK(processor_after->getProperty("invokehttp-proxy-password") == "");
+
+ const auto* const controller_service_node_after =
process_group_after->findControllerService("b9801278-7b5d-4314-aed6-713fd4b5f933");
+ REQUIRE(controller_service_node_after);
+
CHECK(controller_service_node_after->getControllerServiceImplementation()->getProperty("Passphrase")
== "");
+
+ const auto& param_contexts = json_configuration_after.getParameterContexts();
+
CHECK(param_contexts.at("my-context")->getParameter("secret_parameter").value().value.empty());
+}
+
TEST_CASE("Parameter provider generated parameter context is serialized
correctly") {
ConfigurationTestController test_controller;
auto configuration_context = test_controller.getContext();
diff --git a/libminifi/test/unit/PropertyEncryptionUtilsTests.cpp
b/libminifi/test/unit/PropertyEncryptionUtilsTests.cpp
index 93885b672..155cbd60c 100644
--- a/libminifi/test/unit/PropertyEncryptionUtilsTests.cpp
+++ b/libminifi/test/unit/PropertyEncryptionUtilsTests.cpp
@@ -31,11 +31,6 @@ const crypto::Bytes secret_key = crypto::generateKey();
const crypto::EncryptionProvider property_encryptor{secret_key};
TEST_CASE("A property value can be encrypted") {
- std::string encrypted_blank = property_encryption::encrypt("",
property_encryptor);
- CHECK(encrypted_blank.starts_with("enc{"));
- CHECK(encrypted_blank.ends_with("}"));
- CHECK(encrypted_blank.size() == 63);
-
std::string encrypted_foo = property_encryption::encrypt("foo",
property_encryptor);
CHECK(encrypted_foo.starts_with("enc{"));
CHECK(encrypted_foo.ends_with("}"));
@@ -48,6 +43,10 @@ TEST_CASE("A property value can be encrypted") {
CHECK(encrypted_long_text.size() == 283);
}
+TEST_CASE("An empty property value is not encrypted") {
+ CHECK(property_encryption::encrypt("", property_encryptor).empty());
+}
+
TEST_CASE("Encrypting the same value a second time doesn't change its value") {
std::string encrypted_foo = property_encryption::encrypt("foo",
property_encryptor);
std::string re_encrypted_foo = property_encryption::encrypt(encrypted_foo,
property_encryptor);
@@ -58,9 +57,6 @@ TEST_CASE("Encrypting the same value a second time doesn't
change its value") {
}
TEST_CASE("We can decrypt an encrypted value") {
- std::string encrypted_blank = property_encryption::encrypt("",
property_encryptor);
- CHECK(property_encryption::decrypt(encrypted_blank,
property_encryptor).empty());
-
std::string encrypted_foo = property_encryption::encrypt("foo",
property_encryptor);
CHECK(property_encryption::decrypt(encrypted_foo, property_encryptor) ==
"foo");
diff --git a/libminifi/test/unit/YamlFlowSerializerTests.cpp
b/libminifi/test/unit/YamlFlowSerializerTests.cpp
index 445a3cef4..c76c2ac2a 100644
--- a/libminifi/test/unit/YamlFlowSerializerTests.cpp
+++ b/libminifi/test/unit/YamlFlowSerializerTests.cpp
@@ -26,7 +26,6 @@
#include "utils/crypto/EncryptionProvider.h"
#include "utils/crypto/property_encryption/PropertyEncryptionUtils.h"
#include "utils/StringUtils.h"
-#include "core/Resource.h"
#include "utils/Environment.h"
namespace org::apache::nifi::minifi::test {
@@ -340,6 +339,44 @@ TEST_CASE("The encrypted flow configuration can be
decrypted with the correct ke
CHECK(param_contexts.at("my-context")->getParameter("secret_parameter")->value
== "param_value_1");
}
+TEST_CASE("Empty sensitive properties and parameters are not encrypted and
round-trip as empty") {
+ ConfigurationTestController test_controller;
+ auto configuration_context = test_controller.getContext();
+ configuration_context.sensitive_values_encryptor = encryption_provider;
+
+ std::string config_yaml_with_empty_passphrase_and_parameter{config_yaml};
+
minifi::utils::string::replaceAll(config_yaml_with_empty_passphrase_and_parameter,
"very_secure_passphrase", "");
+
minifi::utils::string::replaceAll(config_yaml_with_empty_passphrase_and_parameter,
"param_value_1", "");
+
+ core::flow::AdaptiveConfiguration
yaml_configuration_before{configuration_context};
+ const auto process_group_before =
yaml_configuration_before.getRootFromPayload(config_yaml_with_empty_passphrase_and_parameter);
+ REQUIRE(process_group_before);
+
+ const auto schema = core::flow::FlowSchema::getDefault();
+ YAML::Node root_yaml_node =
YAML::Load(config_yaml_with_empty_passphrase_and_parameter);
+ const auto flow_serializer = core::yaml::YamlFlowSerializer{root_yaml_node};
+
+ const auto processor_id =
minifi::utils::Identifier::parse("469617f1-3898-4bbf-91fe-27d8f4dd2a75").value();
+ const OverridesMap overrides{{processor_id,
core::flow::Overrides{}.add("invokehttp-proxy-password", "")}};
+ const std::string serialized =
flow_serializer.serialize(*process_group_before, schema, encryption_provider,
overrides, {});
+ CHECK_FALSE(serialized.contains("enc{"));
+
+ core::flow::AdaptiveConfiguration
yaml_configuration_after{configuration_context};
+ const auto process_group_after =
yaml_configuration_after.getRootFromPayload(serialized);
+ REQUIRE(process_group_after);
+
+ const auto* processor_after =
process_group_after->findProcessorById(processor_id);
+ REQUIRE(processor_after);
+ CHECK(processor_after->getProperty("invokehttp-proxy-password") == "");
+
+ const auto* const controller_service_node_after =
process_group_after->findControllerService("b9801278-7b5d-4314-aed6-713fd4b5f933");
+ REQUIRE(controller_service_node_after);
+
CHECK(controller_service_node_after->getControllerServiceImplementation()->getProperty("Passphrase")
== "");
+
+ const auto& param_contexts = yaml_configuration_after.getParameterContexts();
+
CHECK(param_contexts.at("my-context")->getParameter("secret_parameter").value().value.empty());
+}
+
TEST_CASE("The encrypted flow configuration cannot be decrypted with an
incorrect key") {
ConfigurationTestController test_controller;
auto configuration_context = test_controller.getContext();