You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by sz...@apache.org on 2022/11/28 13:34:24 UTC
[nifi-minifi-cpp] 01/02: MINIFICPP-1925 Ensure compatibility with the MiNiFi C2 Service
This is an automated email from the ASF dual-hosted git repository.
szaszm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git
commit 8829131ecbdfa54ef28dc205ae468d2f5445a15d
Author: Gabor Gyimesi <ga...@gmail.com>
AuthorDate: Mon Nov 28 11:41:12 2022 +0100
MINIFICPP-1925 Ensure compatibility with the MiNiFi C2 Service
Ensure that config update and heartbeats from MiNiFi C2 server are handled properly in MiNiFi agent
* Read flowid from configuration update parameter
* Make C2 operation strings case-insensitive
* Handle null in requested operations
* Remove Accept header temporarily until MiNiFi C2 handling is fixed
* Add test coverage for MiNiFi C2 compatibility
Closes #1420
Signed-off-by: Marton Szasz <sz...@apache.org>
---
conf/minifi.properties | 1 +
docker/test/integration/features/CMakeLists.txt | 1 +
.../integration/features/minifi_c2_server.feature | 20 +++++++++
.../minifi/core/DockerTestDirectoryBindings.py | 1 +
.../test/integration/minifi/core/FlowContainer.py | 4 ++
docker/test/integration/minifi/core/ImageStore.py | 39 +++++++++++++++++
...FlowContainer.py => MinifiC2ServerContainer.py} | 23 +++++++----
.../integration/minifi/core/MinifiContainer.py | 2 +-
...FlowContainer.py => MinifiWithHttpsC2Config.py} | 17 ++------
.../minifi/core/SingleNodeDockerCluster.py | 8 ++++
.../Minifi_flow_yaml_serializer.py | 34 ++++++++++++++-
.../resources/minifi-c2-server-ssl/Dockerfile | 7 ++++
.../minifi-c2-server-ssl/authorities.yaml | 2 +
.../minifi-c2-server-ssl/authorizations.yaml | 46 +++++++++++++++++++++
.../resources/minifi-c2-server-ssl/c2.properties | 10 +++++
.../certs/minifi-c2-server-keystore.p12 | Bin 0 -> 2544 bytes
.../certs/minifi-c2-server-truststore.p12 | Bin 0 -> 2978 bytes
.../certs/minifi-c2-server.crt | 20 +++++++++
.../certs/minifi-c2-server.key | 28 +++++++++++++
.../minifi-c2-server-ssl/certs/minifi-cpp-flow.crt | 20 +++++++++
.../minifi-c2-server-ssl/certs/minifi-cpp-flow.key | 28 +++++++++++++
.../minifi-c2-server-ssl/certs/root-ca.key | 30 ++++++++++++++
.../minifi-c2-server-ssl/certs/root-ca.pem | 19 +++++++++
.../resources/minifi-c2-server-ssl/config.yml | 43 +++++++++++++++++++
.../resources/minifi-c2-server/Dockerfile | 2 +
.../resources/minifi-c2-server/config.yml | 31 ++++++++++++++
docker/test/integration/steps/steps.py | 29 +++++++++++++
extensions/http-curl/protocols/RESTSender.cpp | 25 +++++++----
extensions/http-curl/tests/HTTPHandlers.h | 2 +-
libminifi/include/FlowController.h | 4 +-
libminifi/include/c2/C2Agent.h | 2 +-
libminifi/include/core/FlowConfiguration.h | 6 +--
libminifi/include/core/state/UpdateController.h | 2 +-
.../include/core/state/nodes/FlowInformation.h | 2 +-
libminifi/src/FlowController.cpp | 8 ++--
libminifi/src/c2/C2Agent.cpp | 15 ++++---
libminifi/src/c2/protocols/RESTProtocol.cpp | 8 +++-
libminifi/src/core/FlowConfiguration.cpp | 12 +++---
.../src/core/state/nodes/SupportedOperations.cpp | 2 +-
libminifi/test/unit/ControllerTests.cpp | 2 +-
40 files changed, 495 insertions(+), 60 deletions(-)
diff --git a/conf/minifi.properties b/conf/minifi.properties
index 135c91b21..8dc746e6c 100644
--- a/conf/minifi.properties
+++ b/conf/minifi.properties
@@ -79,6 +79,7 @@ nifi.content.repository.class.name=DatabaseContentRepository
#nifi.c2.flow.base.url=
#nifi.c2.rest.url=
#nifi.c2.rest.url.ack=
+#nifi.c2.rest.ssl.context.service=
nifi.c2.root.classes=DeviceInfoNode,AgentInformation,FlowInformation
## Minimize heartbeat payload size by excluding agent manifest from the heartbeat
nifi.c2.full.heartbeat=false
diff --git a/docker/test/integration/features/CMakeLists.txt b/docker/test/integration/features/CMakeLists.txt
index 901a3d71e..606376295 100644
--- a/docker/test/integration/features/CMakeLists.txt
+++ b/docker/test/integration/features/CMakeLists.txt
@@ -25,6 +25,7 @@ set(ENABLED_BEHAVE_TESTS "${ENABLED_BEHAVE_TESTS};${CMAKE_CURRENT_SOURCE_DIR}/ro
set(ENABLED_BEHAVE_TESTS "${ENABLED_BEHAVE_TESTS};${CMAKE_CURRENT_SOURCE_DIR}/s2s.feature")
set(ENABLED_BEHAVE_TESTS "${ENABLED_BEHAVE_TESTS};${CMAKE_CURRENT_SOURCE_DIR}/syslog_listener.feature")
set(ENABLED_BEHAVE_TESTS "${ENABLED_BEHAVE_TESTS};${CMAKE_CURRENT_SOURCE_DIR}/network_listener.feature")
+set(ENABLED_BEHAVE_TESTS "${ENABLED_BEHAVE_TESTS};${CMAKE_CURRENT_SOURCE_DIR}/minifi_c2_server.feature")
if (ENABLE_AZURE)
set(ENABLED_BEHAVE_TESTS "${ENABLED_BEHAVE_TESTS};${CMAKE_CURRENT_SOURCE_DIR}/azure_storage.feature")
diff --git a/docker/test/integration/features/minifi_c2_server.feature b/docker/test/integration/features/minifi_c2_server.feature
new file mode 100644
index 000000000..f9bc3f5dd
--- /dev/null
+++ b/docker/test/integration/features/minifi_c2_server.feature
@@ -0,0 +1,20 @@
+Feature: MiNiFi can communicate with Apache NiFi MiNiFi C2 server
+
+ Background:
+ Given the content of "/tmp/output" is monitored
+
+ Scenario: MiNiFi flow config is updated from MiNiFi C2 server
+ Given a GetFile processor with the name "GetFile1" and the "Input Directory" property set to "/tmp/non-existent"
+ And a file with the content "test" is present in "/tmp/input"
+ And a MiNiFi C2 server is set up
+ When all instances start up
+ Then the MiNiFi C2 server logs contain the following message: "acknowledged with a state of FULLY_APPLIED(DONE)" in less than 30 seconds
+ And a flowfile with the content "test" is placed in the monitored directory in less than 10 seconds
+
+ Scenario: MiNiFi flow config is updated from MiNiFi C2 server through SSL
+ Given a file with the content "test" is present in "/tmp/input"
+ And a ssl context service is set up for MiNiFi C2 server
+ And a MiNiFi C2 server is set up with SSL
+ When all instances start up
+ Then the MiNiFi C2 SSL server logs contain the following message: "acknowledged with a state of FULLY_APPLIED(DONE)" in less than 60 seconds
+ And a flowfile with the content "test" is placed in the monitored directory in less than 10 seconds
diff --git a/docker/test/integration/minifi/core/DockerTestDirectoryBindings.py b/docker/test/integration/minifi/core/DockerTestDirectoryBindings.py
index ee79f4e23..be39110d2 100644
--- a/docker/test/integration/minifi/core/DockerTestDirectoryBindings.py
+++ b/docker/test/integration/minifi/core/DockerTestDirectoryBindings.py
@@ -56,6 +56,7 @@ class DockerTestDirectoryBindings:
shutil.copytree(test_dir + "/resources/lua", self.data_directories[self.test_id]["resources_dir"] + "/lua")
shutil.copytree(test_dir + "/resources/elasticsearch/certs", self.data_directories[self.test_id]["resources_dir"] + "/elasticsearch")
shutil.copytree(test_dir + "/resources/opensearch/certs", self.data_directories[self.test_id]["resources_dir"] + "/opensearch")
+ shutil.copytree(test_dir + "/resources/minifi-c2-server-ssl/certs", self.data_directories[self.test_id]["resources_dir"] + "/minifi-c2-server-ssl")
def get_data_directories(self, test_id):
return self.data_directories[test_id]
diff --git a/docker/test/integration/minifi/core/FlowContainer.py b/docker/test/integration/minifi/core/FlowContainer.py
index 748991c6c..0ce1655c5 100644
--- a/docker/test/integration/minifi/core/FlowContainer.py
+++ b/docker/test/integration/minifi/core/FlowContainer.py
@@ -22,9 +22,13 @@ class FlowContainer(Container):
super().__init__(name, engine, vols, network, image_store, command)
self.start_nodes = []
self.config_dir = config_dir
+ self.controllers = []
def get_start_nodes(self):
return self.start_nodes
def add_start_node(self, node):
self.start_nodes.append(node)
+
+ def add_controller(self, controller):
+ self.controllers.append(controller)
diff --git a/docker/test/integration/minifi/core/ImageStore.py b/docker/test/integration/minifi/core/ImageStore.py
index 4076f3031..2700c89d0 100644
--- a/docker/test/integration/minifi/core/ImageStore.py
+++ b/docker/test/integration/minifi/core/ImageStore.py
@@ -46,6 +46,8 @@ class ImageStore:
image = self.__build_minifi_cpp_image()
elif container_engine == "minifi-cpp-with-provenance-repo":
image = self.__build_minifi_cpp_image_with_provenance_repo()
+ elif container_engine == "minifi-cpp-with-https-c2-config":
+ image = self.__build_minifi_cpp_image_with_https_c2_config()
elif container_engine == "http-proxy":
image = self.__build_http_proxy_image()
elif container_engine == "postgresql-server":
@@ -64,6 +66,10 @@ class ImageStore:
image = self.__build_elasticsearch_image()
elif container_engine == "opensearch":
image = self.__build_opensearch_image()
+ elif container_engine == "minifi-c2-server":
+ image = self.__build_minifi_c2_image()
+ elif container_engine == "minifi-c2-server-ssl":
+ image = self.__build_minifi_c2_ssl_image()
else:
raise Exception("There is no associated image for " + container_engine)
@@ -106,6 +112,14 @@ class ImageStore:
RUN echo nifi.metrics.publisher.class=PrometheusMetricsPublisher >> {minifi_root}/conf/minifi.properties
RUN echo nifi.metrics.publisher.PrometheusMetricsPublisher.port=9936 >> {minifi_root}/conf/minifi.properties
RUN echo nifi.metrics.publisher.metrics=RepositoryMetrics,QueueMetrics,GetFileMetrics,GetTCPMetrics,PutFileMetrics,FlowInformation,DeviceInfoNode,AgentStatus >> {minifi_root}/conf/minifi.properties
+ RUN echo nifi.c2.enable=true >> {minifi_root}/conf/minifi.properties
+ RUN echo nifi.c2.rest.url=http://minifi-c2-server:10090/c2/config/heartbeat >> {minifi_root}/conf/minifi.properties
+ RUN echo nifi.c2.rest.url.ack=http://minifi-c2-server:10090/c2/config/acknowledge >> {minifi_root}/conf/minifi.properties
+ RUN echo nifi.c2.flow.base.url=http://minifi-c2-server:10090/c2/config/ >> {minifi_root}/conf/minifi.properties
+ RUN echo nifi.c2.root.classes=DeviceInfoNode,AgentInformation,FlowInformation >> {minifi_root}/conf/minifi.properties
+ RUN echo nifi.c2.full.heartbeat=false >> {minifi_root}/conf/minifi.properties
+ RUN echo nifi.c2.agent.class=minifi-test-class >> {minifi_root}/conf/minifi.properties
+ RUN echo nifi.c2.agent.identifier=minifi-test-id >> {minifi_root}/conf/minifi.properties
USER minificpp
""".format(base_image='apacheminificpp:' + MinifiContainer.MINIFI_VERSION,
minifi_root=MinifiContainer.MINIFI_ROOT))
@@ -129,6 +143,25 @@ class ImageStore:
image = self.__build_image(dockerfile, [properties_context])
return image
+ def __build_minifi_cpp_image_with_https_c2_config(self):
+ dockerfile = dedent("""\
+ FROM {base_image}
+ USER root
+ RUN sed -i -e 's/INFO/DEBUG/g' {minifi_root}/conf/minifi-log.properties
+ RUN echo nifi.c2.enable=true >> {minifi_root}/conf/minifi.properties
+ RUN echo nifi.c2.rest.url=https://minifi-c2-server:10090/c2/config/heartbeat >> {minifi_root}/conf/minifi.properties
+ RUN echo nifi.c2.rest.url.ack=https://minifi-c2-server:10090/c2/config/acknowledge >> {minifi_root}/conf/minifi.properties
+ RUN echo nifi.c2.rest.ssl.context.service=SSLContextService >> {minifi_root}/conf/minifi.properties
+ RUN echo nifi.c2.flow.base.url=https://minifi-c2-server:10090/c2/config/ >> {minifi_root}/conf/minifi.properties
+ RUN echo nifi.c2.full.heartbeat=false >> {minifi_root}/conf/minifi.properties
+ RUN echo nifi.c2.agent.class=minifi-test-class >> {minifi_root}/conf/minifi.properties
+ RUN echo nifi.c2.agent.identifier=minifi-test-id >> {minifi_root}/conf/minifi.properties
+ USER minificpp
+ """.format(base_image='apacheminificpp:' + MinifiContainer.MINIFI_VERSION,
+ minifi_root=MinifiContainer.MINIFI_ROOT))
+
+ return self.__build_image(dockerfile)
+
def __build_http_proxy_image(self):
dockerfile = dedent("""\
FROM {base_image}
@@ -195,6 +228,12 @@ class ImageStore:
def __build_opensearch_image(self):
return self.__build_image_by_path(self.test_dir + "/resources/opensearch", 'opensearch')
+ def __build_minifi_c2_image(self):
+ return self.__build_image_by_path(self.test_dir + "/resources/minifi-c2-server", 'minifi-c2-server')
+
+ def __build_minifi_c2_ssl_image(self):
+ return self.__build_image_by_path(self.test_dir + "/resources/minifi-c2-server-ssl", 'minifi-c2-server')
+
def __build_image(self, dockerfile, context_files=[]):
conf_dockerfile_buffer = BytesIO()
docker_context_buffer = BytesIO()
diff --git a/docker/test/integration/minifi/core/FlowContainer.py b/docker/test/integration/minifi/core/MinifiC2ServerContainer.py
similarity index 59%
copy from docker/test/integration/minifi/core/FlowContainer.py
copy to docker/test/integration/minifi/core/MinifiC2ServerContainer.py
index 748991c6c..88d0991e8 100644
--- a/docker/test/integration/minifi/core/FlowContainer.py
+++ b/docker/test/integration/minifi/core/MinifiC2ServerContainer.py
@@ -17,14 +17,21 @@
from .Container import Container
-class FlowContainer(Container):
- def __init__(self, config_dir, name, engine, vols, network, image_store, command):
+class MinifiC2ServerContainer(Container):
+ def __init__(self, name, vols, network, image_store, command=None, ssl=False):
+ engine = "minifi-c2-server-ssl" if ssl else "minifi-c2-server"
super().__init__(name, engine, vols, network, image_store, command)
- self.start_nodes = []
- self.config_dir = config_dir
- def get_start_nodes(self):
- return self.start_nodes
+ def get_startup_finished_log_entry(self):
+ return "Server Started"
- def add_start_node(self, node):
- self.start_nodes.append(node)
+ def deploy(self):
+ if not self.set_deployed():
+ return
+
+ self.docker_container = self.client.containers.run(
+ self.image_store.get_image(self.get_engine()),
+ detach=True,
+ name=self.name,
+ network=self.network.name,
+ entrypoint=self.command)
diff --git a/docker/test/integration/minifi/core/MinifiContainer.py b/docker/test/integration/minifi/core/MinifiContainer.py
index 31d728005..8bd376ca5 100644
--- a/docker/test/integration/minifi/core/MinifiContainer.py
+++ b/docker/test/integration/minifi/core/MinifiContainer.py
@@ -34,7 +34,7 @@ class MinifiContainer(FlowContainer):
def _create_config(self):
serializer = Minifi_flow_yaml_serializer()
- test_flow_yaml = serializer.serialize(self.start_nodes)
+ test_flow_yaml = serializer.serialize(self.start_nodes, self.controllers)
logging.info('Using generated flow config yml:\n%s', test_flow_yaml)
with open(os.path.join(self.config_dir, "config.yml"), 'wb') as config_file:
config_file.write(test_flow_yaml.encode('utf-8'))
diff --git a/docker/test/integration/minifi/core/FlowContainer.py b/docker/test/integration/minifi/core/MinifiWithHttpsC2Config.py
similarity index 64%
copy from docker/test/integration/minifi/core/FlowContainer.py
copy to docker/test/integration/minifi/core/MinifiWithHttpsC2Config.py
index 748991c6c..79a4d44b8 100644
--- a/docker/test/integration/minifi/core/FlowContainer.py
+++ b/docker/test/integration/minifi/core/MinifiWithHttpsC2Config.py
@@ -13,18 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from .MinifiContainer import MinifiContainer
-from .Container import Container
-
-class FlowContainer(Container):
- def __init__(self, config_dir, name, engine, vols, network, image_store, command):
- super().__init__(name, engine, vols, network, image_store, command)
- self.start_nodes = []
- self.config_dir = config_dir
-
- def get_start_nodes(self):
- return self.start_nodes
-
- def add_start_node(self, node):
- self.start_nodes.append(node)
+class MinifiWithHttpsC2Config(MinifiContainer):
+ def __init__(self, config_dir, name, vols, network, image_store, command=None):
+ super().__init__(config_dir, name, vols, network, image_store, command, engine='minifi-cpp-with-https-c2-config')
diff --git a/docker/test/integration/minifi/core/SingleNodeDockerCluster.py b/docker/test/integration/minifi/core/SingleNodeDockerCluster.py
index 5ee368cfb..aca62ee05 100644
--- a/docker/test/integration/minifi/core/SingleNodeDockerCluster.py
+++ b/docker/test/integration/minifi/core/SingleNodeDockerCluster.py
@@ -40,6 +40,8 @@ from .SyslogTcpClientContainer import SyslogTcpClientContainer
from .MinifiAsPodInKubernetesCluster import MinifiAsPodInKubernetesCluster
from .TcpClientContainer import TcpClientContainer
from .PrometheusContainer import PrometheusContainer
+from .MinifiC2ServerContainer import MinifiC2ServerContainer
+from .MinifiWithHttpsC2Config import MinifiWithHttpsC2Config
class SingleNodeDockerCluster(Cluster):
@@ -101,6 +103,8 @@ class SingleNodeDockerCluster(Cluster):
return self.containers.setdefault(name, TransientMinifiContainer(self.data_directories["minifi_config_dir"], name, self.vols, self.network, self.image_store, command))
elif engine == 'minifi-cpp-with-provenance-repo':
return self.containers.setdefault(name, MinifiWithProvenanceRepoContainer(self.data_directories["minifi_config_dir"], name, self.vols, self.network, self.image_store, command))
+ elif engine == 'minifi-cpp-with-https-c2-config':
+ return self.containers.setdefault(name, MinifiWithHttpsC2Config(self.data_directories["minifi_config_dir"], name, self.vols, self.network, self.image_store, command))
elif engine == 'kafka-broker':
if 'zookeeper' not in self.containers:
self.containers.setdefault('zookeeper', ZookeeperContainer('zookeeper', self.vols, self.network, self.image_store, command))
@@ -133,6 +137,10 @@ class SingleNodeDockerCluster(Cluster):
return self.containers.setdefault(name, TcpClientContainer(name, self.vols, self.network, self.image_store, command))
elif engine == "prometheus":
return self.containers.setdefault(name, PrometheusContainer(name, self.vols, self.network, self.image_store, command))
+ elif engine == "minifi-c2-server":
+ return self.containers.setdefault(name, MinifiC2ServerContainer(name, self.vols, self.network, self.image_store, command))
+ elif engine == "minifi-c2-server-ssl":
+ return self.containers.setdefault(name, MinifiC2ServerContainer(name, self.vols, self.network, self.image_store, command, ssl=True))
else:
raise Exception('invalid flow engine: \'%s\'' % engine)
diff --git a/docker/test/integration/minifi/flow_serialization/Minifi_flow_yaml_serializer.py b/docker/test/integration/minifi/flow_serialization/Minifi_flow_yaml_serializer.py
index d0606bc79..0af92ab7d 100644
--- a/docker/test/integration/minifi/flow_serialization/Minifi_flow_yaml_serializer.py
+++ b/docker/test/integration/minifi/flow_serialization/Minifi_flow_yaml_serializer.py
@@ -23,13 +23,16 @@ from ..core.Funnel import Funnel
class Minifi_flow_yaml_serializer:
- def serialize(self, start_nodes):
+ def serialize(self, start_nodes, controllers):
res = None
visited = None
for node in start_nodes:
res, visited = self.serialize_node(node, res, visited)
+ for controller in controllers:
+ res = self.serialize_controller(controller, res)
+
return yaml.dump(res, default_flow_style=False)
def serialize_node(self, connectable, root=None, visited=None):
@@ -145,3 +148,32 @@ class Minifi_flow_yaml_serializer:
self.serialize_node(conn_procs, res, visited)
return (res, visited)
+
+ def serialize_controller(self, controller, root=None):
+ if root is None:
+ res = {
+ 'Flow Controller': {
+ 'name': 'MiNiFi Flow'
+ },
+ 'Processors': [],
+ 'Funnels': [],
+ 'Connections': [],
+ 'Remote Processing Groups': [],
+ 'Controller Services': []
+ }
+ else:
+ res = root
+
+ if hasattr(controller, 'name'):
+ connectable_name = controller.name
+ else:
+ connectable_name = str(controller.uuid)
+
+ res['Controller Services'].append({
+ 'name': connectable_name,
+ 'id': controller.id,
+ 'class': controller.service_class,
+ 'Properties': controller.properties
+ })
+
+ return res
diff --git a/docker/test/integration/resources/minifi-c2-server-ssl/Dockerfile b/docker/test/integration/resources/minifi-c2-server-ssl/Dockerfile
new file mode 100644
index 000000000..e87447a4a
--- /dev/null
+++ b/docker/test/integration/resources/minifi-c2-server-ssl/Dockerfile
@@ -0,0 +1,7 @@
+FROM apache/nifi-minifi-c2:1.18.0
+RUN mkdir $MINIFI_C2_HOME/certs/
+COPY --chown=c2:c2 certs/* $MINIFI_C2_HOME/certs/
+COPY --chown=c2:c2 config.yml $MINIFI_C2_HOME/files/minifi-test-class/config.text.yml.v1
+COPY --chown=c2:c2 authorities.yaml $MINIFI_C2_HOME/conf/
+COPY --chown=c2:c2 authorizations.yaml $MINIFI_C2_HOME/conf/
+COPY --chown=c2:c2 c2.properties $MINIFI_C2_HOME/conf/
diff --git a/docker/test/integration/resources/minifi-c2-server-ssl/authorities.yaml b/docker/test/integration/resources/minifi-c2-server-ssl/authorities.yaml
new file mode 100644
index 000000000..8043a37e6
--- /dev/null
+++ b/docker/test/integration/resources/minifi-c2-server-ssl/authorities.yaml
@@ -0,0 +1,2 @@
+CN=minifi-cpp-flow:
+ - CLASS_MINIFI_CPP
diff --git a/docker/test/integration/resources/minifi-c2-server-ssl/authorizations.yaml b/docker/test/integration/resources/minifi-c2-server-ssl/authorizations.yaml
new file mode 100644
index 000000000..a4fdeeb1d
--- /dev/null
+++ b/docker/test/integration/resources/minifi-c2-server-ssl/authorizations.yaml
@@ -0,0 +1,46 @@
+Default Action: deny
+Paths:
+ /c2/config:
+ Default Action: deny
+ Actions:
+ - Authorization: CLASS_MINIFI_CPP
+ Action: allow
+ - Authorization: ROLE_SUPERUSER
+ Action: allow
+
+ # Default authorization lets anonymous pull any config. Remove below to change that.
+ - Authorization: ROLE_ANONYMOUS
+ Action: allow
+
+ /c2/config/contentTypes:
+ Default Action: deny
+ Actions:
+ - Authorization: CLASS_MINIFI_CPP
+ Action: allow
+ # Default authorization lets anonymous pull any config. Remove below to change that.
+ - Authorization: ROLE_ANONYMOUS
+ Action: allow
+
+ /c2/config/heartbeat:
+ Default Action: deny
+ Actions:
+ - Authorization: CLASS_MINIFI_CPP
+ Action: allow
+ - Authorization: ROLE_SUPERUSER
+ Action: allow
+
+ # Default authorization lets anonymous pull any config. Remove below to change that.
+ - Authorization: ROLE_ANONYMOUS
+ Action: allow
+
+ /c2/config/acknowledge:
+ Default Action: deny
+ Actions:
+ - Authorization: CLASS_MINIFI_CPP
+ Action: allow
+ - Authorization: ROLE_SUPERUSER
+ Action: allow
+
+ # Default authorization lets anonymous pull any config. Remove below to change that.
+ - Authorization: ROLE_ANONYMOUS
+ Action: allow
diff --git a/docker/test/integration/resources/minifi-c2-server-ssl/c2.properties b/docker/test/integration/resources/minifi-c2-server-ssl/c2.properties
new file mode 100644
index 000000000..3cafab269
--- /dev/null
+++ b/docker/test/integration/resources/minifi-c2-server-ssl/c2.properties
@@ -0,0 +1,10 @@
+minifi.c2.server.port=10090
+
+minifi.c2.server.secure=true
+minifi.c2.server.keystore=/opt/minifi-c2/minifi-c2-1.18.0/certs/minifi-c2-server-keystore.p12
+minifi.c2.server.keystoreType=PKCS12
+minifi.c2.server.keystorePasswd=abcdefgh
+minifi.c2.server.keyPasswd=abcdefgh
+minifi.c2.server.truststore=/opt/minifi-c2/minifi-c2-1.18.0/certs/minifi-c2-server-truststore.p12
+minifi.c2.server.truststoreType=PKCS12
+minifi.c2.server.truststorePasswd=abcdefgh
diff --git a/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-c2-server-keystore.p12 b/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-c2-server-keystore.p12
new file mode 100644
index 000000000..7d280f90f
Binary files /dev/null and b/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-c2-server-keystore.p12 differ
diff --git a/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-c2-server-truststore.p12 b/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-c2-server-truststore.p12
new file mode 100644
index 000000000..d72e6ebd4
Binary files /dev/null and b/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-c2-server-truststore.p12 differ
diff --git a/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-c2-server.crt b/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-c2-server.crt
new file mode 100644
index 000000000..0fcfff9f0
--- /dev/null
+++ b/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-c2-server.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDMTCCAhmgAwIBAgIUasTOqNQ87C9VtUiy+N8uLOQTcRUwDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAwwEbXlDQTAgFw0yMjEwMTQxNTAyNTBaGA8yMDUwMDIyODE1
+MDI1MFowGzEZMBcGA1UEAwwQbWluaWZpLWMyLXNlcnZlcjCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAKsIVaHbxd9HGI/5Dc9Q75vv7/dDLrlEU7S9/k1S
+lJkBBnF32rBNlwNfZdCgSsdtFzsq++88CQjzbbCdi72hwKA9V60oW8HDI8utDdE6
+QY4qTBhBmrxbwGIE/6TNkSpfz940a18Nhzv0mDTvpjLsYqbDAA6vr1/Od/BSqnXq
+/DLtIEEzX1d1gtRqRonyt44Hhmj58gORka8Kggyh84i9k/Ol9IZdCZoxvgDQOwOK
+XaohByQBPChMKpMDQkpJfkIKRGE1K2Ya7BfGqO0Q+lX6N/zSFE9YIZxrhcT6wjcC
+yjOJGou9vVgnu6Idw6iX6Y0pwOrnWPqpQ/sqIxFKWu2UAy0CAwEAAaN3MHUwHwYD
+VR0jBBgwFoAUbor1/5iMMRzmWD4nVDkQ0IkzMawwCQYDVR0TBAIwADALBgNVHQ8E
+BAMCBPAwGwYDVR0RBBQwEoIQbWluaWZpLWMyLXNlcnZlcjAdBgNVHQ4EFgQUqmzn
+O9XoHVK6afH0IDN3oV4Tv2cwDQYJKoZIhvcNAQELBQADggEBAKdtUTXWcUcSr9U7
+CFaU5DFtJbUIvEkbAs2R8+bK/Y280uOEQ4+9plyCcVPF5/ZvHaRDu0orKwaUWEdZ
+eAjYj2isZACXZdWey6b5kE2s8XAGYrM3W8R6gZzSf0rEdjPyA3TwNb3/RgltBKWV
+nzqNiCjo6Cb4dY2MXKOrmlVtUDKEKDa0zl2FYsrJDJ8KS9chROX01r+kcWXiemXP
+bWWfCdhBCJA6gAQfOHz4Cc/fCLwsMi9xO8ddRNBl/jxJ8Sl9UlImSWlp5BSprSO2
+hu+ucHwoVZbC9As1bkEEwODBUCO5L2XKl5O8MuA/NxgT57L7G1UCxBM9sTWb4bEI
+xd6prvQ=
+-----END CERTIFICATE-----
diff --git a/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-c2-server.key b/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-c2-server.key
new file mode 100644
index 000000000..fc18ef48e
--- /dev/null
+++ b/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-c2-server.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCrCFWh28XfRxiP
++Q3PUO+b7+/3Qy65RFO0vf5NUpSZAQZxd9qwTZcDX2XQoErHbRc7KvvvPAkI822w
+nYu9ocCgPVetKFvBwyPLrQ3ROkGOKkwYQZq8W8BiBP+kzZEqX8/eNGtfDYc79Jg0
+76Yy7GKmwwAOr69fznfwUqp16vwy7SBBM19XdYLUakaJ8reOB4Zo+fIDkZGvCoIM
+ofOIvZPzpfSGXQmaMb4A0DsDil2qIQckATwoTCqTA0JKSX5CCkRhNStmGuwXxqjt
+EPpV+jf80hRPWCGca4XE+sI3AsoziRqLvb1YJ7uiHcOol+mNKcDq51j6qUP7KiMR
+SlrtlAMtAgMBAAECggEAReBUWBo52BSseNnopfxrwLqBQHTeyIudZVlAZifolTBh
+iQdOPjydB6A4sUlj8+FineZcYuwUxubpuEBNwO6ui+k0AodcIahP3h14aTSTZvlp
++HkJNo6H5aQkLBleh0D45NBm08FrsHeonewRa3m/fmFqCxYFIS/yOaoUgbO9UTJ7
+7nQZfurke8a3dxk6zQqrRkAp7MdXiVRYkOv3NOBk6efM8DjvYJSCpC1I0h2XRxJM
+9ZvCvGbmDHpGWnFkSU3Sc19H7latDp+Tef6Oi31tXVDTjmn1Dgyp3fXw6y1qrj99
+7V+lBQO0TEXW3wrqhMfTvJG9VHASEBaeP1cmXiLj4wKBgQDeiwRaw6/APc9bE+rz
+wer9MQ4UzfmZ3nZH0uYggZJhikCQqFZLyuGT041TX3YrNkmqvDD2ue29GA0H0LBa
+GSQkmw0CtBzT/hxjBO5pib1GjdsTRaB82VIXOmSrjgMR2gND/AguOnK4R2EgakXB
+Ah25+7HZ91aHfbeyesrjUneiSwKBgQDEvtdA2+p64Ryo8nR+RaVpqcdjJRa9Iywz
+QMUwJg8uM7bBap3xThtt1otB8MhetqhL916bZHBBK9AdfSIoTq7Q8lrlbE+lQDub
+ytlz0qOv0GsIVKf7pvgGy0IyZTo8ExNrk0ZXi83tEDBH90VCkPZAdYMkTmgRG6Lw
+frwuperFZwKBgG/X/BtFp8l9Bv5mFznkpp4TDlmkXyJWrKlSM/f4RsIgwmwxPhWf
+ZBlwQ+G342K6SPG23QDS1smnEb1ww4C0i/aduj82mBpu5oNZUhzWbbrMxmJ8Jrk4
+W0pzPW7+00oggG2ld9ML6uX0cbrhzia/UoNLHMpHxUQZCb54egkfRCLbAoGAXq9J
+gJlVu1VjKZulnK9/794ZawmKa/PlbbUaMRXf8GhK58KbyGnCoZXC5zUt+QcG76hZ
+C4fGzlZ7jfWO3r8fOseoHwmFOw4yocN561fQFujC2fuD7IRqkTp43TACWq8DhZ4X
+GELcE97anYfO+T4yhMsJFgv14WXfgMY9YmXPGrkCgYA8BVpuN4rAUsJ+4ouePWRX
+Z0FVKzUcenMy+0whYmEjkUtd72ZwCMh1sNp7rgH+3w8U79RJ52iiOLRtcAl9cNEK
++w8GUdSRr4JQcvhh024eakQ713bZaNZWBZLtgvzDe4HOAC+eRo7v7coHHCBpJzG1
+ihO8WGGFhArEq0YuGxdKaA==
+-----END PRIVATE KEY-----
diff --git a/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-cpp-flow.crt b/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-cpp-flow.crt
new file mode 100644
index 000000000..7b6ac1056
--- /dev/null
+++ b/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-cpp-flow.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLzCCAhegAwIBAgIUI5l5u5iR4/JsSBS/Vt4UlvwJ0lswDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAwwEbXlDQTAgFw0yMjEwMTQxNTAyMDhaGA8yMDUwMDIyODE1
+MDIwOFowGjEYMBYGA1UEAwwPbWluaWZpLWNwcC1mbG93MIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEAqFEBLXiOkjDQ8qt6VOHKrCfQmh0a9FL5VNlfYUvV
+IyuVFpL1+6wc9s3/7yd+M8MwkhbzXHQbh/Hh1VHIrmmcNWVesmCs8vU7f9vueBtX
+mqKEzTN+/apNRSABznkkJoHRJFwQjXq7iznmBh9AHyvBIU/O330b7aKdNWHJsYaK
+lX7HGXYti9nG7dmCxDbZFIWu73JU4fh6PJd/lbRYngHcZ6XoZtsX26bAvWRGqfym
+XQ+RK7yURb87iXEUmToCVlYQRoKTme7uDcJowej/6yjNuru9/VQjDnSG930cY+Ak
+Ssxs73ivn8dnNOvcDOiNeEwjkUONBPFWe8GYiSJhZ6m89QIDAQABo3YwdDAfBgNV
+HSMEGDAWgBRuivX/mIwxHOZYPidUORDQiTMxrDAJBgNVHRMEAjAAMAsGA1UdDwQE
+AwIE8DAaBgNVHREEEzARgg9taW5pZmktY3BwLWZsb3cwHQYDVR0OBBYEFCxfop38
+h8apLKD1KQT5iqbzbOTKMA0GCSqGSIb3DQEBCwUAA4IBAQBbsjR5cdOUWEpGi81E
+jNZ8Wtcxl/ebnl4RRwI2tACn1WHLEj65w0dm2BHzs8e6a3JnA8NDwTDctCsUHV2m
+DtxriBxtcPNcaGtzh2IMwX26a+sVfLR1pP1HJi77AGyLA3Rhm1hBws//KZ6seEbc
+qKoQR0o99UcfASQP1uqBC4LF9k4gzhvE8n+KlTJ5neEwZ4jpD505OiCsN5ZMLa+3
+mCyTGdrO4EXSl2ElvaSSxzf5+FfGE2LT+oSrdwQYFuB+9/qr+c5nU+M8e2E0QLvN
+UusnNOw8wp8rEHiSE6DDhp0PhfxhiOVz3l1oJQduzr7L5aO6kgSlT3nWn3PqPaJo
+G0Vd
+-----END CERTIFICATE-----
diff --git a/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-cpp-flow.key b/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-cpp-flow.key
new file mode 100644
index 000000000..c73b44e39
--- /dev/null
+++ b/docker/test/integration/resources/minifi-c2-server-ssl/certs/minifi-cpp-flow.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCoUQEteI6SMNDy
+q3pU4cqsJ9CaHRr0UvlU2V9hS9UjK5UWkvX7rBz2zf/vJ34zwzCSFvNcdBuH8eHV
+UciuaZw1ZV6yYKzy9Tt/2+54G1eaooTNM379qk1FIAHOeSQmgdEkXBCNeruLOeYG
+H0AfK8EhT87ffRvtop01YcmxhoqVfscZdi2L2cbt2YLENtkUha7vclTh+Ho8l3+V
+tFieAdxnpehm2xfbpsC9ZEap/KZdD5ErvJRFvzuJcRSZOgJWVhBGgpOZ7u4NwmjB
+6P/rKM26u739VCMOdIb3fRxj4CRKzGzveK+fx2c069wM6I14TCORQ40E8VZ7wZiJ
+ImFnqbz1AgMBAAECggEAKYizrbDOHa0GIpvF+CQviwPYKe98s0W2WQW6z5uS4Lbk
+d0mUgaIbE5wJx84LCmLkHWikbPAJyyYZADbKOp+8+EAnegT5KIrzP73ZvrGgkHwC
+IVDPyXC42JHpYDXsgcQPA9XkD8V1egmzhVc4z3hQlBPJjMSmm6FBAec7ih8VG4Zi
+fxZ3b4fLmezQvqzC+MBRTMqkWU0A4Q3+IIqe5hTUReglTD8yeu9rzyM8xPYksFV1
+qOatWB22IS+fdU/0xzKp3at/yjU8a4DiTnyosNN/2txazQdU1LdSlNxwDqlTDPbi
+swybSc1a6KLO+ZeD4yGB1NLvPCQuIwR4ugw6dQoMkQKBgQC/05rcrGgju4QBcfSe
+SHagt5nypeGUy/yXIlUbWoetETHx/NXaXtWZ7vxN7S0u09HTr7F4+wu00td3etMA
+dOTuqGU/YEHFoZutDpg+oauZIL+MecM1uZeKKTHN3wQ0qmLsYOS172fNUcZ/gcRt
+4JylyK45c10oE0SvXKeC9bWsSwKBgQDgn/Hhhp57NwNWdlOuA6oXkmSN/UFZWE7w
+EntMcviYqGGtBVW9O3D9SqdbEb0kO2NByNgpjsjUTAGCsyYSa50O3q28vQw4z3qK
+EWyg6Ep51eHDUQDo1cdoIRFrFxrZLOMUoVTfT1uqAI/vHxQ089s60W+WUmU5wUic
+9fJoZdnzvwKBgHEwnI2gEecby73Kjywi2BTnoaiDZ0OUxlwrvwpf9fUSU2VV6p5r
+HSEy2p/k1qduB78gSdl4USUG0GtJB16am0eUCAJIeybxwFlyZjV20jmOEFkEtEJs
+W9YDjsbK1MF61NpkJjCQrrCBk15DpTOsuOI+M0flIc/25q2PP6zP7b5XAoGAO/d3
+Q5YEyTAum+6K+HHR/uj+H0n1ID0LFdxZPleTNm39ZYt/ED3GNFixxQY/UGTqYq2T
+x8RuqP6BiLr69v/ztfyMtU5i7Oe29xUfwvVArLYEx3fgnkg0LABn/gb1C/WHygIn
+/lXZStFLm7LYWiqf5Fv1RlRI4dpP4Fdol6ZZQVECgYBWCtKu2CmRb4cYbYo8092E
+72Jh5B9fuBqcUFELjSgUzxgL/L7P6mx6evCCUj4o4Zzy+U45tm2AYpKAXhkQh33h
+xDFs1t0X2CHSTvES65CCEniN/RKoOBu59fEbZu6hV1tzoz9iU6lgKNkndDeeeYip
+hIGI5QK6mK7sNyOnfE+b4g==
+-----END PRIVATE KEY-----
diff --git a/docker/test/integration/resources/minifi-c2-server-ssl/certs/root-ca.key b/docker/test/integration/resources/minifi-c2-server-ssl/certs/root-ca.key
new file mode 100644
index 000000000..fbb74cc67
--- /dev/null
+++ b/docker/test/integration/resources/minifi-c2-server-ssl/certs/root-ca.key
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIgbRh5/zItgICAggA
+MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECKS8TpnvoUisBIIEyBOQzTn7rMTg
+Igkc79mdZZsU0XX8Z9USUAEVRnKylsWxnt0BX+DlsgnX6bP33gHUXdCSllVRlyKu
+vpNfJadLUNXGU0ZWpYmnD+jtd0J2j6qVqQGQgSuZ0UpPe/6cBaiZcM2BCeWNr0J6
+f7/HDc8AgyeHyP3v+ZqfO0c9TC6N9BMVBreKLtejI3jbNMJOPMdT3sIdyaKLJonO
+ozIZES3ZhPR9kKDRAgk4NUEaPF41SBOYnlqIR4zfBVbtzn2CHbejvAt32KHlfJMj
+D53GrdKDZdOGTAal90rHw127F75ScF5dyskst1PBO4VxufYPzmo/KgMoZ0TAPDzh
+DUDI0Glff9jep8mrACp8XmEMD9uqd3a/E75KWZWQ5eyUrBrzr1qIdLsGsD0UO00o
+MEEIpkz2Is6VTJMGcNCUXDHbKUgHagSs5T35OHESC/JqWM02i/GCiubPPeNfDEjJ
+wGCwdilMhNcuJR8D3Rb9xyv/NOxsECuO+0rDgV9kCe3WF4p3qey4Pcf3NC9BwmOZ
+QTVpa59rlLqGSbrB/EgETucvBFGy9ZhCs/A3Wuc2yirtzWiAsky95643scQbrUrD
+P+QtrfaDLi3Gj9xEjGzUSoui7MogDaQtIBVa81tioQ7TKzQl1cPBToAes/8vPQ/5
+Jrfc04wVH8jz1YSlP37ho63K7YNVINe+Rplwmf/6S0vQqqEKH0EhPiyqRflJ5Xy5
+HyM1p5vQujg45TsltL9DG/fxiO7uR9D0D/1dz/kMZszAPFeP3yQRaKMCJqMc0hXE
+SjWOpblm8y+RxPwehY7OTCKJtH9acURLsFAeHDqHKcobyPOCv2D3ekhx7ujuzyyb
+BrdGKMsTu7e+sEFZrNAvqBDr7KJuS3YnRX6y9lNySh+WzSBf+AW6Tzpabz5GBMEz
+UdOaq0MvtsYD3GOJvDBHHJS8AAPkqUvZnsztJSl5soKkbPJdUpZF2B3PH93eREb1
+sjUkTG8V7JrdpGrJvqkuVnzDFT2xVlt4dDByEj7wFkRtjX/WEDPX8HvNI34hPGSN
+5m88kTWQncd0h6+lbxKDz5dFZ06uDt3m44YFjVBZUpKhTNKgKnBFh1yEXN/LczVz
+DtNtTVlY+zK2MFdfZ3GB8aGsTajGmXreoom8BbigH0QXjp4t9cZHMbPHz0BfjQ9O
+eEukx+2LyH4+DovqKGUN9iZrqe5VAin9wsgyGDt2N5qJfZj++jmF9r5m9JNhOKLm
+RcZwjnayRfVXILGqoRI/JOlwfximJReOzAvYiVtEhCdjn3J2QJsNAMOlc5oUpwdM
+8MVzXc5/ccsKVzNbsAfBaG5IbTLP+iH2pbCmBhaZeX7X5eB4Fsx5ZKcDqGXHju8Y
+xJNIlgV9ALU3Oqnv1yOGGb7BStgsGVAD8lUfycXZjCTwiXiOMx4/KUWtnBkiOz4x
+R/9FZEGIupfRY2MqkFVbaMpYH3u1pXTVt+hDzEak6NboXbH2hIdQndAA5plxcU5y
+SD+xEiTCHWKxkH/W8mzzzPYXKpr17MjCwqok0+FiR2tT9NkPj7G2Ob0vKfAcBRBD
+Ou4COgYTK2AORxgzgs0fM8Par+sa5p1wuuUILIvAxkqNqh/e3G70xj5OMtdaUrT+
+pIPFUG0yfz/lqoTxfYdtUw==
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/docker/test/integration/resources/minifi-c2-server-ssl/certs/root-ca.pem b/docker/test/integration/resources/minifi-c2-server-ssl/certs/root-ca.pem
new file mode 100644
index 000000000..948ebd5b2
--- /dev/null
+++ b/docker/test/integration/resources/minifi-c2-server-ssl/certs/root-ca.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIUOzrVvbxYHge08GMeuuJmOPgWOtswDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAwwEbXlDQTAeFw0yMjEwMTQxNDQ0MzlaFw0yNzEwMTMxNDQ0
+MzlaMA8xDTALBgNVBAMMBG15Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCwXjMjfLWgn8YVWkOCarKc40tGCNrRKITgiKZSQvM44hQw9wbYk/yRj67S
+5bZIr5djvnbmJC5XQSj/vAYL92B37qtualPlw/1ziUURSN1MQKUMgbirKAcGFDMJ
+7g6yrCXrq94A/66cQl1fksXfFk8RzSBPMWfzLNTzy96A/2HriYn3J9GFQV3ggEAg
+VcVJhFiyRBBreZEIar+sasDeR5T1lOfn2Ev7QbiWXTr9IqR1mv4dtqKOv9oYld5Y
+AfoQ0P6yis+RLuNcokNIHgUO+ORpy+CUmIOrCbZgVjSetSlZ8SNQkCXwWhKcObaI
+PK15veDo4SyPv2GDEEBbD1Ccqe/nAgMBAAGjUzBRMB0GA1UdDgQWBBRuivX/mIwx
+HOZYPidUORDQiTMxrDAfBgNVHSMEGDAWgBRuivX/mIwxHOZYPidUORDQiTMxrDAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAtnKrcaSoSVgHtFBe8
+HBEbK1tzTXrBeFEEQqNZpRoJ6JSq1oY54UlAlBJdNQJFZOPoeaf9vheevFGFhBDJ
+PwRqE5T6wSp9aohUcnKiBzL8VbTIuUgyWM9gwyg/lipTYK6U5sDROUzfEppBJb0E
+gBp7eBoiEcI4tuih3zncbKKvTNR3b/twAL5yI4MEUNcJsi80xefQt0m39RAwpMIu
+khQNH2YLNOl6hl6wRTylCGB6eWZAtB4qGEDtdCXG9uyCnoUO5MFgZZGFiaqN+avF
+cUU2mOwaoPDbWQ7Q+2m9c1fo/iJ8RThAnFhH5/GQe+OaecW8vHmfCbivj7wkRgTW
+nXzz
+-----END CERTIFICATE-----
diff --git a/docker/test/integration/resources/minifi-c2-server-ssl/config.yml b/docker/test/integration/resources/minifi-c2-server-ssl/config.yml
new file mode 100644
index 000000000..d4c69534c
--- /dev/null
+++ b/docker/test/integration/resources/minifi-c2-server-ssl/config.yml
@@ -0,0 +1,43 @@
+MiNiFi Config Version: 3
+Flow Controller:
+ name: MiNiFi Flow
+Processors:
+- name: Get files from /tmp/input
+ id: 2f2a3b47-f5ba-49f6-82b5-bc1c86b96e27
+ class: org.apache.nifi.minifi.processors.GetFile
+ scheduling strategy: TIMER_DRIVEN
+ scheduling period: 1000 ms
+ Properties:
+ Input Directory: /tmp/input
+- name: Put files to /tmp/output
+ id: e143601d-de4f-44ba-a6ec-d1f97d77ec94
+ class: org.apache.nifi.minifi.processors.PutFile
+ scheduling strategy: EVENT_DRIVEN
+ auto-terminated relationships list:
+ - failure
+ - success
+ Properties:
+ Conflict Resolution Strategy: fail
+ Create Missing Directories: 'true'
+ Directory: /tmp/output
+Connections:
+- name: GetFile/success/PutFile
+ id: 098a56ba-f4bf-4323-a3f3-6f8a5e3586bf
+ source id: 2f2a3b47-f5ba-49f6-82b5-bc1c86b96e27
+ source relationship names:
+ - success
+ destination id: e143601d-de4f-44ba-a6ec-d1f97d77ec94
+Controller Services:
+ - name: SSLContextService
+ id: 2438e3c8-015a-1000-79ca-83af40ec1994
+ class: SSLContextService
+ Properties:
+ Client Certificate:
+ - value: /tmp/minifi-c2-server-ssl/minifi-c2-client.crt
+ Private Key:
+ - value: /tmp/minifi-c2-server-ssl/minifi-c2-client.key
+ Passphrase:
+ - value: abcdefgh
+ CA Certificate:
+ - value: /tmp/minifi-c2-server-ssl/minifi-c2-server.crt
+Remote Process Groups: []
diff --git a/docker/test/integration/resources/minifi-c2-server/Dockerfile b/docker/test/integration/resources/minifi-c2-server/Dockerfile
new file mode 100644
index 000000000..ce13e0312
--- /dev/null
+++ b/docker/test/integration/resources/minifi-c2-server/Dockerfile
@@ -0,0 +1,2 @@
+FROM apache/nifi-minifi-c2:1.18.0
+ADD config.yml $MINIFI_C2_HOME/files/minifi-test-class/config.text.yml.v1
diff --git a/docker/test/integration/resources/minifi-c2-server/config.yml b/docker/test/integration/resources/minifi-c2-server/config.yml
new file mode 100644
index 000000000..ddac4cd77
--- /dev/null
+++ b/docker/test/integration/resources/minifi-c2-server/config.yml
@@ -0,0 +1,31 @@
+MiNiFi Config Version: 3
+Flow Controller:
+ name: MiNiFi Flow
+Processors:
+- name: Get files from /tmp/input
+ id: 2f2a3b47-f5ba-49f6-82b5-bc1c86b96e27
+ class: org.apache.nifi.minifi.processors.GetFile
+ scheduling strategy: TIMER_DRIVEN
+ scheduling period: 1000 ms
+ Properties:
+ Input Directory: /tmp/input
+- name: Put files to /tmp/output
+ id: e143601d-de4f-44ba-a6ec-d1f97d77ec94
+ class: org.apache.nifi.minifi.processors.PutFile
+ scheduling strategy: EVENT_DRIVEN
+ auto-terminated relationships list:
+ - failure
+ - success
+ Properties:
+ Conflict Resolution Strategy: fail
+ Create Missing Directories: 'true'
+ Directory: /tmp/output
+Connections:
+- name: GetFile/success/PutFile
+ id: 098a56ba-f4bf-4323-a3f3-6f8a5e3586bf
+ source id: 2f2a3b47-f5ba-49f6-82b5-bc1c86b96e27
+ source relationship names:
+ - success
+ destination id: e143601d-de4f-44ba-a6ec-d1f97d77ec94
+Controller Services: []
+Remote Process Groups: []
diff --git a/docker/test/integration/steps/steps.py b/docker/test/integration/steps/steps.py
index 723576715..6ae9b5904 100644
--- a/docker/test/integration/steps/steps.py
+++ b/docker/test/integration/steps/steps.py
@@ -963,3 +963,32 @@ def step_impl(context):
@then(u'Opensearch has a document with "{doc_id}" in "{index}" that has "{value}" set in "{field}"')
def step_impl(context, doc_id, index, value, field):
context.test.check_elastic_field_value("opensearch", index_name=index, doc_id=doc_id, field_name=field, field_value=value)
+
+
+# MiNiFi C2 Server
+@given("a ssl context service is set up for MiNiFi C2 server")
+def step_impl(context):
+ ssl_context_service = SSLContextService(cert="/tmp/resources/minifi-c2-server-ssl/minifi-cpp-flow.crt", ca_cert="/tmp/resources/minifi-c2-server-ssl/root-ca.pem", key="/tmp/resources/minifi-c2-server-ssl/minifi-cpp-flow.key", passphrase="abcdefgh")
+ ssl_context_service.name = "SSLContextService"
+ container = context.test.acquire_container("minifi-cpp-flow", "minifi-cpp-with-https-c2-config")
+ container.add_controller(ssl_context_service)
+
+
+@given(u'a MiNiFi C2 server is set up')
+def step_impl(context):
+ context.test.acquire_container("minifi-c2-server", "minifi-c2-server")
+
+
+@then("the MiNiFi C2 server logs contain the following message: \"{log_message}\" in less than {duration}")
+def step_impl(context, log_message, duration):
+ context.test.check_container_log_contents("minifi-c2-server", log_message, timeparse(duration))
+
+
+@then("the MiNiFi C2 SSL server logs contain the following message: \"{log_message}\" in less than {duration}")
+def step_impl(context, log_message, duration):
+ context.test.check_container_log_contents("minifi-c2-server-ssl", log_message, timeparse(duration))
+
+
+@given(u'a MiNiFi C2 server is set up with SSL')
+def step_impl(context):
+ context.test.acquire_container("minifi-c2-server", "minifi-c2-server-ssl")
diff --git a/extensions/http-curl/protocols/RESTSender.cpp b/extensions/http-curl/protocols/RESTSender.cpp
index e1ce3e978..b58bf3233 100644
--- a/extensions/http-curl/protocols/RESTSender.cpp
+++ b/extensions/http-curl/protocols/RESTSender.cpp
@@ -103,11 +103,19 @@ C2Payload RESTSender::sendPayload(const std::string& url, const Direction direct
extensions::curl::HTTPClient client(url, ssl_context_service_);
client.setKeepAliveProbe(extensions::curl::KeepAliveProbeData{2s, 2s});
client.setConnectionTimeout(2s);
- if (direction == Direction::TRANSMIT) {
- client.set_request_method("POST");
- if (!ssl_context_service_ && url.find("https://") == 0) {
- setSecurityContext(client, "POST", url);
+
+ auto setUpHttpRequest = [&](const std::string& http_method) {
+ client.set_request_method(http_method);
+ if (url.find("https://") == 0) {
+ if (!ssl_context_service_) {
+ setSecurityContext(client, http_method, url);
+ } else {
+ client.initialize(http_method, url, ssl_context_service_);
+ }
}
+ };
+ if (direction == Direction::TRANSMIT) {
+ setUpHttpRequest("POST");
if (payload.getOperation() == Operation::TRANSFER) {
// treat nested payloads as files
for (const auto& file : payload.getNestedPayloads()) {
@@ -148,17 +156,16 @@ C2Payload RESTSender::sendPayload(const std::string& url, const Direction direct
} else {
// we do not need to set the upload callback
// since we are not uploading anything on a get
- if (!ssl_context_service_ && url.find("https://") == 0) {
- setSecurityContext(client, "GET", url);
- }
- client.set_request_method("GET");
+ setUpHttpRequest("GET");
}
if (payload.getOperation() == Operation::TRANSFER) {
auto read = std::make_unique<utils::HTTPReadCallback>(std::numeric_limits<size_t>::max());
client.setReadCallback(std::move(read));
} else {
- client.setRequestHeader("Accept", "application/json");
+ // Due to a bug in MiNiFi C2 the Accept header is not handled properly thus we need to exclude it to be compatible
+ // TODO(lordgamez): The header should be re-added when the issue in MiNiFi C2 is fixed: https://issues.apache.org/jira/browse/NIFI-10535
+ // client.setRequestHeader("Accept", "application/json");
client.setContentType("application/json");
}
bool isOkay = client.submit();
diff --git a/extensions/http-curl/tests/HTTPHandlers.h b/extensions/http-curl/tests/HTTPHandlers.h
index c0f8a9ad2..88484d5a8 100644
--- a/extensions/http-curl/tests/HTTPHandlers.h
+++ b/extensions/http-curl/tests/HTTPHandlers.h
@@ -609,7 +609,7 @@ class HeartbeatHandler : public ServerAwareHandler {
for (const auto& operation_node : agent_manifest["supportedOperations"].GetArray()) {
assert(operation_node.HasMember("type"));
operations.insert(operation_node["type"].GetString());
- verifyProperties(operation_node, minifi::c2::Operation::parse(operation_node["type"].GetString()), verify_components, disallowed_properties);
+ verifyProperties(operation_node, minifi::c2::Operation::parse(operation_node["type"].GetString(), {}, false), verify_components, disallowed_properties);
}
assert(operations == minifi::c2::Operation::values());
diff --git a/libminifi/include/FlowController.h b/libminifi/include/FlowController.h
index f1354adbb..97710c0d0 100644
--- a/libminifi/include/FlowController.h
+++ b/libminifi/include/FlowController.h
@@ -110,7 +110,7 @@ class FlowController : public core::controller::ForwardingControllerServiceProvi
int16_t resume() override;
// Unload the current flow YAML, clean the root process group and all its children
int16_t stop() override;
- int16_t applyUpdate(const std::string &source, const std::string &configuration, bool persist) override;
+ int16_t applyUpdate(const std::string &source, const std::string &configuration, bool persist, const std::optional<std::string>& flow_id) override;
int16_t drainRepositories() override {
return -1;
}
@@ -145,7 +145,7 @@ class FlowController : public core::controller::ForwardingControllerServiceProvi
// first it will validate the payload with the current root node config for flowController
// like FlowController id/name is the same and new version is greater than the current version
// after that, it will apply the configuration
- bool applyConfiguration(const std::string &source, const std::string &configurePayload);
+ bool applyConfiguration(const std::string &source, const std::string &configurePayload, const std::optional<std::string>& flow_id = std::nullopt);
// get name
std::string getName() const override {
diff --git a/libminifi/include/c2/C2Agent.h b/libminifi/include/c2/C2Agent.h
index 3a491d1cb..9ad280cbb 100644
--- a/libminifi/include/c2/C2Agent.h
+++ b/libminifi/include/c2/C2Agent.h
@@ -171,8 +171,8 @@ class C2Agent : public state::UpdateController {
void handleAssetUpdate(const C2ContentResponse &resp);
std::optional<std::string> resolveFlowUrl(const std::string& url) const;
-
std::optional<std::string> resolveUrl(const std::string& url) const;
+ static std::optional<std::string> getFlowIdFromConfigUpdate(const C2ContentResponse &resp);
protected:
std::timed_mutex metrics_mutex_;
diff --git a/libminifi/include/core/FlowConfiguration.h b/libminifi/include/core/FlowConfiguration.h
index 1c2be2e79..860351cb6 100644
--- a/libminifi/include/core/FlowConfiguration.h
+++ b/libminifi/include/core/FlowConfiguration.h
@@ -66,9 +66,9 @@ class FlowConfiguration : public CoreComponent {
* Constructor that will be used for configuring
* the flow controller.
*/
- explicit FlowConfiguration(std::shared_ptr<core::Repository> repo, std::shared_ptr<core::Repository> flow_file_repo,
+ explicit FlowConfiguration(const std::shared_ptr<core::Repository>& repo, std::shared_ptr<core::Repository> flow_file_repo,
std::shared_ptr<core::ContentRepository> content_repo, std::shared_ptr<io::StreamFactory> stream_factory,
- std::shared_ptr<Configure> configuration, const std::optional<std::string>& path,
+ const std::shared_ptr<Configure>& configuration, const std::optional<std::string>& path,
std::shared_ptr<utils::file::FileSystem> filesystem = std::make_shared<utils::file::FileSystem>());
~FlowConfiguration() override;
@@ -112,7 +112,7 @@ class FlowConfiguration : public CoreComponent {
return nullptr;
}
- std::unique_ptr<core::ProcessGroup> updateFromPayload(const std::string& url, const std::string& yamlConfigPayload);
+ std::unique_ptr<core::ProcessGroup> updateFromPayload(const std::string& url, const std::string& yamlConfigPayload, const std::optional<std::string>& flow_id = std::nullopt);
virtual std::unique_ptr<core::ProcessGroup> getRootFromPayload(const std::string& /*yamlConfigPayload*/) {
return nullptr;
diff --git a/libminifi/include/core/state/UpdateController.h b/libminifi/include/core/state/UpdateController.h
index 4fc93883f..f87cc7a56 100644
--- a/libminifi/include/core/state/UpdateController.h
+++ b/libminifi/include/core/state/UpdateController.h
@@ -164,7 +164,7 @@ class StateMonitor : public StateController {
* < 0 is an error code
* 0 is success
*/
- virtual int16_t applyUpdate(const std::string & source, const std::string &configuration, bool persist = false) = 0;
+ virtual int16_t applyUpdate(const std::string & source, const std::string &configuration, bool persist = false, const std::optional<std::string>& flow_id = std::nullopt) = 0;
/**
* Apply an update that the agent must decode. This is useful for certain operations
diff --git a/libminifi/include/core/state/nodes/FlowInformation.h b/libminifi/include/core/state/nodes/FlowInformation.h
index e0f95c1a7..e08eb6684 100644
--- a/libminifi/include/core/state/nodes/FlowInformation.h
+++ b/libminifi/include/core/state/nodes/FlowInformation.h
@@ -90,7 +90,7 @@ class FlowVersion : public DeviceInformation {
void setFlowVersion(const std::string &url, const std::string &bucket_id, const std::string &flow_id) {
std::lock_guard<std::mutex> lock(guard);
- identifier = std::make_shared<FlowIdentifier>(url, bucket_id, flow_id);
+ identifier = std::make_shared<FlowIdentifier>(url, bucket_id, flow_id.empty() ? utils::IdGenerator::getIdGenerator()->generate().to_string() : flow_id);
}
std::vector<SerializedResponseNode> serialize() override {
diff --git a/libminifi/src/FlowController.cpp b/libminifi/src/FlowController.cpp
index 6ef93b35b..2f82fe8aa 100644
--- a/libminifi/src/FlowController.cpp
+++ b/libminifi/src/FlowController.cpp
@@ -103,10 +103,10 @@ FlowController::~FlowController() {
logger_->log_trace("Destroying FlowController");
}
-bool FlowController::applyConfiguration(const std::string &source, const std::string &configurePayload) {
+bool FlowController::applyConfiguration(const std::string &source, const std::string &configurePayload, const std::optional<std::string>& flow_id) {
std::unique_ptr<core::ProcessGroup> newRoot;
try {
- newRoot = flow_configuration_->updateFromPayload(source, configurePayload);
+ newRoot = flow_configuration_->updateFromPayload(source, configurePayload, flow_id);
} catch (const std::exception& ex) {
logger_->log_error("Invalid configuration payload, type: %s, what: %s", typeid(ex).name(), ex.what());
return false;
@@ -422,8 +422,8 @@ int16_t FlowController::resume() {
return 0;
}
-int16_t FlowController::applyUpdate(const std::string &source, const std::string &configuration, bool persist) {
- if (applyConfiguration(source, configuration)) {
+int16_t FlowController::applyUpdate(const std::string &source, const std::string &configuration, bool persist, const std::optional<std::string>& flow_id) {
+ if (applyConfiguration(source, configuration, flow_id)) {
if (persist) {
flow_configuration_->persist(configuration);
}
diff --git a/libminifi/src/c2/C2Agent.cpp b/libminifi/src/c2/C2Agent.cpp
index fc61de0b8..7fe53898a 100644
--- a/libminifi/src/c2/C2Agent.cpp
+++ b/libminifi/src/c2/C2Agent.cpp
@@ -430,7 +430,7 @@ C2Payload C2Agent::prepareConfigurationOptions(const C2ContentResponse &resp) co
void C2Agent::handle_clear(const C2ContentResponse &resp) {
ClearOperand operand;
try {
- operand = ClearOperand::parse(resp.name.c_str());
+ operand = ClearOperand::parse(resp.name.c_str(), {}, false);
} catch(const std::runtime_error&) {
logger_->log_debug("Clearing unknown %s", resp.name);
return;
@@ -485,7 +485,7 @@ void C2Agent::handle_clear(const C2ContentResponse &resp) {
void C2Agent::handle_describe(const C2ContentResponse &resp) {
DescribeOperand operand;
try {
- operand = DescribeOperand::parse(resp.name.c_str());
+ operand = DescribeOperand::parse(resp.name.c_str(), {}, false);
} catch(const std::runtime_error&) {
C2Payload response(Operation::ACKNOWLEDGE, resp.ident, true);
enqueue_c2_response(std::move(response));
@@ -587,7 +587,7 @@ void C2Agent::handle_describe(const C2ContentResponse &resp) {
void C2Agent::handle_update(const C2ContentResponse &resp) {
UpdateOperand operand;
try {
- operand = UpdateOperand::parse(resp.name.c_str());
+ operand = UpdateOperand::parse(resp.name.c_str(), {}, false);
} catch(const std::runtime_error&) {
C2Payload response(Operation::ACKNOWLEDGE, state::UpdateState::NOT_APPLIED, resp.ident, true);
enqueue_c2_response(std::move(response));
@@ -696,7 +696,7 @@ C2Payload C2Agent::bundleDebugInfo(std::map<std::string, std::unique_ptr<io::Inp
void C2Agent::handle_transfer(const C2ContentResponse &resp) {
TransferOperand operand;
try {
- operand = TransferOperand::parse(resp.name.c_str());
+ operand = TransferOperand::parse(resp.name.c_str(), {}, false);
} catch(const std::runtime_error&) {
throw C2TransferError("Unknown operand '" + resp.name + "'");
}
@@ -848,6 +848,11 @@ std::optional<std::string> C2Agent::fetchFlow(const std::string& uri) const {
return response.getRawDataAsString();
}
+std::optional<std::string> C2Agent::getFlowIdFromConfigUpdate(const C2ContentResponse &resp) {
+ auto flow_id = resp.operation_arguments.find("flowId");
+ return flow_id == resp.operation_arguments.end() ? std::nullopt : std::make_optional(flow_id->second.to_string());
+}
+
bool C2Agent::handleConfigurationUpdate(const C2ContentResponse &resp) {
auto url = resp.operation_arguments.find("location");
@@ -886,7 +891,7 @@ bool C2Agent::handleConfigurationUpdate(const C2ContentResponse &resp) {
return utils::StringUtils::equalsIgnoreCase(persist->second.to_string(), "true");
}();
- int16_t err = {update_sink_->applyUpdate(file_uri, configuration_str, should_persist)};
+ int16_t err = {update_sink_->applyUpdate(file_uri, configuration_str, should_persist, getFlowIdFromConfigUpdate(resp))};
if (err != 0) {
logger_->log_error("Flow configuration update failed with error code %" PRIi16, err);
C2Payload response(Operation::ACKNOWLEDGE, state::UpdateState::SET_ERROR, resp.ident, true);
diff --git a/libminifi/src/c2/protocols/RESTProtocol.cpp b/libminifi/src/c2/protocols/RESTProtocol.cpp
index 7ef042a09..1e573eb58 100644
--- a/libminifi/src/c2/protocols/RESTProtocol.cpp
+++ b/libminifi/src/c2/protocols/RESTProtocol.cpp
@@ -65,7 +65,9 @@ C2Payload RESTProtocol::parseJsonResponse(const C2Payload &payload, gsl::span<co
std::string identifier;
for (auto key : {"operationid", "operationId", "identifier"}) {
if (root.HasMember(key)) {
- identifier = root[key].GetString();
+ if (!root[key].IsNull()) {
+ identifier = root[key].GetString();
+ }
break;
}
}
@@ -73,7 +75,9 @@ C2Payload RESTProtocol::parseJsonResponse(const C2Payload &payload, gsl::span<co
int size = 0;
for (auto key : {"requested_operations", "requestedOperations"}) {
if (root.HasMember(key)) {
- size = root[key].Size();
+ if (!root[key].IsNull()) {
+ size = root[key].Size();
+ }
break;
}
}
diff --git a/libminifi/src/core/FlowConfiguration.cpp b/libminifi/src/core/FlowConfiguration.cpp
index b35cda810..a34002be9 100644
--- a/libminifi/src/core/FlowConfiguration.cpp
+++ b/libminifi/src/core/FlowConfiguration.cpp
@@ -29,9 +29,9 @@
namespace org::apache::nifi::minifi::core {
FlowConfiguration::FlowConfiguration(
- std::shared_ptr<core::Repository> /*repo*/, std::shared_ptr<core::Repository> flow_file_repo,
+ const std::shared_ptr<core::Repository>& /*repo*/, std::shared_ptr<core::Repository> flow_file_repo,
std::shared_ptr<core::ContentRepository> content_repo, std::shared_ptr<io::StreamFactory> stream_factory,
- std::shared_ptr<Configure> configuration, const std::optional<std::string>& path,
+ const std::shared_ptr<Configure>& configuration, const std::optional<std::string>& path,
std::shared_ptr<utils::file::FileSystem> filesystem)
: CoreComponent(core::getClassName<FlowConfiguration>()),
flow_file_repo_(std::move(flow_file_repo)),
@@ -97,24 +97,24 @@ std::unique_ptr<core::reporting::SiteToSiteProvenanceReportingTask> FlowConfigur
return processor;
}
-std::unique_ptr<core::ProcessGroup> FlowConfiguration::updateFromPayload(const std::string& url, const std::string& yamlConfigPayload) {
+std::unique_ptr<core::ProcessGroup> FlowConfiguration::updateFromPayload(const std::string& url, const std::string& yamlConfigPayload, const std::optional<std::string>& flow_id) {
auto old_services = controller_services_;
auto old_provider = service_provider_;
controller_services_ = std::make_shared<core::controller::ControllerServiceMap>();
service_provider_ = std::make_shared<core::controller::StandardControllerServiceProvider>(controller_services_, nullptr, configuration_);
auto payload = getRootFromPayload(yamlConfigPayload);
if (!url.empty() && payload != nullptr) {
- std::string flow_id;
+ std::string payload_flow_id;
std::string bucket_id;
auto path_split = utils::StringUtils::split(url, "/");
for (auto it = path_split.cbegin(); it != path_split.cend(); ++it) {
if (*it == "flows" && std::next(it) != path_split.cend()) {
- flow_id = *++it;
+ payload_flow_id = *++it;
} else if (*it == "buckets" && std::next(it) != path_split.cend()) {
bucket_id = *++it;
}
}
- flow_version_->setFlowVersion(url, bucket_id, flow_id);
+ flow_version_->setFlowVersion(url, bucket_id, flow_id ? *flow_id : payload_flow_id);
} else {
controller_services_ = old_services;
service_provider_ = old_provider;
diff --git a/libminifi/src/core/state/nodes/SupportedOperations.cpp b/libminifi/src/core/state/nodes/SupportedOperations.cpp
index 453bd4bf9..e3469e1d5 100644
--- a/libminifi/src/core/state/nodes/SupportedOperations.cpp
+++ b/libminifi/src/core/state/nodes/SupportedOperations.cpp
@@ -139,7 +139,7 @@ std::vector<SerializedResponseNode> SupportedOperations::serialize() {
SerializedResponseNode properties;
properties.name = "properties";
- fillProperties(properties, minifi::c2::Operation::parse(operation.c_str()));
+ fillProperties(properties, minifi::c2::Operation::parse(operation.c_str(), {}, false));
child.children.push_back(operation_type);
child.children.push_back(properties);
diff --git a/libminifi/test/unit/ControllerTests.cpp b/libminifi/test/unit/ControllerTests.cpp
index eca69e38b..8b7a2bec1 100644
--- a/libminifi/test/unit/ControllerTests.cpp
+++ b/libminifi/test/unit/ControllerTests.cpp
@@ -158,7 +158,7 @@ class TestUpdateSink : public minifi::state::StateMonitor {
* < 0 is an error code
* 0 is success
*/
- int16_t applyUpdate(const std::string& /*source*/, const std::string& /*configuration*/, bool /*persist*/ = false) override {
+ int16_t applyUpdate(const std::string& /*source*/, const std::string& /*configuration*/, bool /*persist*/ = false, const std::optional<std::string>& /*flow_id*/ = std::nullopt) override {
update_calls++;
return 0;
}