Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#
# 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.
#

import jks
import os
from OpenSSL import crypto
from cryptography.hazmat.primitives.serialization import pkcs12, BestAvailableEncryption, load_pem_private_key
from cryptography import x509
from pathlib import Path

from minifi_test_framework.containers.container import Container
from minifi_test_framework.core.helpers import wait_for_condition
from minifi_test_framework.core.minifi_test_context import MinifiTestContext
from minifi_test_framework.core.ssl_utils import make_server_cert
from minifi_test_framework.containers.file import File
from minifi_test_framework.containers.host_file import HostFile


class MinifiC2Server(Container):
def __init__(self, test_context: MinifiTestContext, ssl: bool = False):
super().__init__("apache/nifi-minifi-c2:1.27.0", f"minifi-c2-server-{test_context.scenario_id}", test_context.network)
if ssl:
c2_cert, c2_key = make_server_cert(f"minifi-c2-server-{test_context.scenario_id}", test_context.root_ca_cert, test_context.root_ca_key)
pke = jks.PrivateKeyEntry.new('c2-server-cert', [crypto.dump_certificate(crypto.FILETYPE_ASN1, c2_cert)], crypto.dump_privatekey(crypto.FILETYPE_ASN1, c2_key), 'rsa_raw')
server_keystore = jks.KeyStore.new('jks', [pke])
server_keystore_content = server_keystore.saves('abcdefgh')
self.files.append(File("/opt/minifi-c2/minifi-c2-current/certs/minifi-c2-server-keystore.jks", server_keystore_content, permissions=0o644))

private_key_pem = crypto.dump_privatekey(crypto.FILETYPE_PEM, test_context.root_ca_key)
private_key = load_pem_private_key(private_key_pem, password=None)
certificate_pem = crypto.dump_certificate(crypto.FILETYPE_PEM, test_context.root_ca_cert)
certificate = x509.load_pem_x509_certificate(certificate_pem)
pkcs12_data = pkcs12.serialize_key_and_certificates(
name=None,
key=private_key,
cert=certificate,
cas=None,
encryption_algorithm=BestAvailableEncryption(b'abcdefgh')
)
self.files.append(File("/opt/minifi-c2/minifi-c2-current/certs/minifi-c2-server-truststore.p12", pkcs12_data, permissions=0o644))

authorities_file_content = """
CN=minifi-primary-{scenario_id}:
- CLASS_MINIFI_CPP
""".format(scenario_id=test_context.scenario_id)
self.files.append(File("/opt/minifi-c2/minifi-c2-current/conf/authorities.yaml", authorities_file_content, permissions=0o644))

resource_dir = Path(__file__).resolve().parent / "resources" / "minifi-c2-server"
self.host_files.append(HostFile("/opt/minifi-c2/minifi-c2-current/files/minifi-test-class/config.text.yml.v1", os.path.join(resource_dir, "config.yml")))
if ssl:
self.host_files.append(HostFile("/opt/minifi-c2/minifi-c2-current/conf/authorizations.yaml", os.path.join(resource_dir, "authorizations.yaml")))
self.host_files.append(HostFile("/opt/minifi-c2/minifi-c2-current/conf/c2.properties", os.path.join(resource_dir, "c2.properties")))

def deploy(self):
super().deploy()
finished_str = "Server Started"
return wait_for_condition(
condition=lambda: finished_str in self.get_logs(),
timeout_seconds=60,
bail_condition=lambda: self.exited,
context=None
)
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def __init__(self, container_name: str, test_context: MinifiTestContext):
self.flow_definition = MinifiFlowDefinition()
self.properties: dict[str, str] = {}
self.log_properties: dict[str, str] = {}
self.scenario_id = test_context.scenario_id

minifi_client_cert, minifi_client_key = make_cert_without_extended_usage(common_name=self.container_name, ca_cert=test_context.root_ca_cert, ca_key=test_context.root_ca_key)
self.files.append(File("/usr/local/share/certs/ca-root-nss.crt", crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=test_context.root_ca_cert)))
Expand Down Expand Up @@ -96,6 +97,35 @@ def set_log_property(self, key: str, value: str):
def enable_openssl_fips_mode(self):
self.properties["nifi.openssl.fips.support.enable"] = "true"

def enable_c2(self):
self.properties["nifi.c2.enable"] = "true"
self.properties["nifi.c2.rest.url"] = f"http://minifi-c2-server-{self.scenario_id}:10090/c2/config/heartbeat"
self.properties["nifi.c2.rest.url.ack"] = f"http://minifi-c2-server-{self.scenario_id}:10090/c2/config/acknowledge"
self.properties["nifi.c2.flow.base.url"] = f"http://minifi-c2-server-{self.scenario_id}:10090/c2/config/"
self.properties["nifi.c2.root.classes"] = "DeviceInfoNode,AgentInformation,FlowInformation,AssetInformation"
self.properties["nifi.c2.full.heartbeat"] = "false"
self.properties["nifi.c2.agent.class"] = "minifi-test-class"
self.properties["nifi.c2.agent.identifier"] = "minifi-test-id"

def enable_c2_with_ssl(self):
self.properties["nifi.c2.enable"] = "true"
self.properties["nifi.c2.rest.url"] = f"https://minifi-c2-server-{self.scenario_id}:10090/c2/config/heartbeat"
self.properties["nifi.c2.rest.url.ack"] = f"https://minifi-c2-server-{self.scenario_id}:10090/c2/config/acknowledge"
self.properties["nifi.c2.flow.base.url"] = f"https://minifi-c2-server-{self.scenario_id}:10090/c2/config/"
self.properties["nifi.c2.root.classes"] = "DeviceInfoNode,AgentInformation,FlowInformation,AssetInformation"
self.properties["nifi.c2.full.heartbeat"] = "false"
self.properties["nifi.c2.agent.class"] = "minifi-test-class"
self.properties["nifi.c2.agent.identifier"] = "minifi-test-id"

def fetch_flow_config_from_flow_url(self):
self.properties["nifi.c2.flow.url"] = f"http://minifi-c2-server-{self.scenario_id}:10090/c2/config?class=minifi-test-class"

def set_up_ssl_proprties(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo:

Suggested change
def set_up_ssl_proprties(self):
def set_up_ssl_properties(self):

self.properties["nifi.remote.input.secure"] = "true"
self.properties["nifi.security.client.certificate"] = "/tmp/resources/minifi_client.crt"
self.properties["nifi.security.client.private.key"] = "/tmp/resources/minifi_client.key"
self.properties["nifi.security.client.ca.certificate"] = "/tmp/resources/root_ca.crt"

def _fill_default_properties(self):
if self.is_fhs:
self.properties["nifi.flow.configuration.file"] = "/etc/nifi-minifi-cpp/config.yml"
Expand Down
20 changes: 14 additions & 6 deletions behave_framework/src/minifi_test_framework/steps/checking_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,15 +196,15 @@ def step_impl(context: MinifiTestContext, content: str, directory: str, duration


@then('after a wait of {duration}, at least {lower_bound:d} and at most {upper_bound:d} files are produced and placed in the "{directory}" directory')
def step_impl(context, lower_bound, upper_bound, duration, directory):
def step_impl(context: MinifiTestContext, lower_bound: int, upper_bound: int, duration: str, directory: str):
duration_seconds = humanfriendly.parse_timespan(duration)
assert check_condition_after_wait(condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].get_number_of_files(directory) >= lower_bound
and context.containers[DEFAULT_MINIFI_CONTAINER_NAME].get_number_of_files(directory) <= upper_bound,
context=context, wait_time=duration_seconds)


@then('exactly these files are in the "{directory}" directory in less than {duration}: "{contents}"')
def step_impl(context, directory, duration, contents):
def step_impl(context: MinifiTestContext, directory: str, duration: str, contents: str):
if not contents:
context.execute_steps(f'then no files are placed in the "{directory}" directory in {duration} of running time')
return
Expand All @@ -221,23 +221,23 @@ def step_impl(context, directory, duration):


@then("at least one empty file is placed in the \"{directory}\" directory in less than {duration}")
def step_impl(context, directory, duration):
def step_impl(context: MinifiTestContext, directory: str, duration: str):
timeout_in_seconds = humanfriendly.parse_timespan(duration)
assert wait_for_condition(
condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].directory_contains_empty_file(directory),
timeout_seconds=timeout_in_seconds, bail_condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].exited, context=context)


@then("in the \"{container_name}\" container at least one empty file is placed in the \"{directory}\" directory in less than {duration}")
def step_impl(context, container_name, directory, duration):
def step_impl(context: MinifiTestContext, container_name: str, directory: str, duration: str):
timeout_in_seconds = humanfriendly.parse_timespan(duration)
assert wait_for_condition(
condition=lambda: context.containers[container_name].directory_contains_empty_file(directory),
timeout_seconds=timeout_in_seconds, bail_condition=lambda: context.containers[container_name].exited, context=context)


@then("in the \"{container_name}\" container at least one file with minimum size of \"{size}\" is placed in the \"{directory}\" directory in less than {duration}")
def step_impl(context, container_name: str, directory: str, size: str, duration: str):
def step_impl(context: MinifiTestContext, container_name: str, directory: str, size: str, duration: str):
timeout_in_seconds = humanfriendly.parse_timespan(duration)
size_in_bytes = humanfriendly.parse_size(size)
assert wait_for_condition(
Expand All @@ -246,6 +246,14 @@ def step_impl(context, container_name: str, directory: str, size: str, duration:


@then("at least one file with minimum size of \"{size}\" is placed in the \"{directory}\" directory in less than {duration}")
def step_impl(context, directory: str, size: str, duration: str):
def step_impl(context: MinifiTestContext, directory: str, size: str, duration: str):
context.execute_steps(
f'Then in the "{DEFAULT_MINIFI_CONTAINER_NAME}" container at least one file with minimum size of "{size}" is placed in the "{directory}" directory in less than {duration}')


@then("the MiNiFi C2 server logs contain the following message: \"{log_message}\" in less than {duration}")
def step_impl(context: MinifiTestContext, log_message: str, duration: str):
duration_seconds = humanfriendly.parse_timespan(duration)
assert wait_for_condition(condition=lambda: log_message in context.containers["minifi-c2-server"].get_logs(),
timeout_seconds=duration_seconds, bail_condition=lambda: context.containers["minifi-c2-server"].exited,
context=context)
39 changes: 36 additions & 3 deletions behave_framework/src/minifi_test_framework/steps/core_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from minifi_test_framework.containers.directory import Directory
from minifi_test_framework.containers.file import File
from minifi_test_framework.core.minifi_test_context import DEFAULT_MINIFI_CONTAINER_NAME, MinifiTestContext
from minifi_test_framework.containers.minifi_c2_server_container import MinifiC2Server


@when("both instances start up")
Expand Down Expand Up @@ -100,15 +101,47 @@ def step_impl(context: MinifiTestContext):


@given("OpenSSL FIPS mode is enabled in MiNiFi")
def step_impl(context):
def step_impl(context: MinifiTestContext):
context.get_or_create_default_minifi_container().enable_openssl_fips_mode()


@step("the http proxy server is set up")
def step_impl(context):
def step_impl(context: MinifiTestContext):
context.containers["http-proxy"] = HttpProxy(context)


@step("a NiFi container is set up")
def step_impl(context):
def step_impl(context: MinifiTestContext):
context.containers["nifi"] = NifiContainer(context)


@given("C2 is enabled in MiNiFi")
def step_impl(context: MinifiTestContext):
context.get_or_create_default_minifi_container().enable_c2()


@given("flow configuration path is set up in flow url property")
def step_impl(context: MinifiTestContext):
context.get_or_create_default_minifi_container().fetch_flow_config_from_flow_url()


@given("ssl properties are set up for MiNiFi C2 server")
def step_impl(context: MinifiTestContext):
context.get_or_create_default_minifi_container().enable_c2_with_ssl()
context.get_or_create_default_minifi_container().set_up_ssl_proprties()


@given("a MiNiFi C2 server is set up")
def step_impl(context: MinifiTestContext):
context.containers["minifi-c2-server"] = MinifiC2Server(context)


@given("a MiNiFi C2 server is set up with SSL")
def step_impl(context: MinifiTestContext):
context.containers["minifi-c2-server"] = MinifiC2Server(context, ssl=True)


@given("a MiNiFi C2 server is started")
def step_impl(context: MinifiTestContext):
context.containers["minifi-c2-server"] = MinifiC2Server(context)
assert context.containers["minifi-c2-server"].deploy()
28 changes: 0 additions & 28 deletions docker/test/integration/cluster/ContainerStore.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
from .containers.SyslogTcpClientContainer import SyslogTcpClientContainer
from .containers.MinifiAsPodInKubernetesCluster import MinifiAsPodInKubernetesCluster
from .containers.PrometheusContainer import PrometheusContainer
from .containers.MinifiC2ServerContainer import MinifiC2ServerContainer
from .FeatureContext import FeatureContext


Expand Down Expand Up @@ -172,24 +171,6 @@ def acquire_container(self, context, container_name: str, engine='minifi-cpp', c
image_store=self.image_store,
command=command,
ssl=True))
elif engine == "minifi-c2-server":
return self.containers.setdefault(container_name,
MinifiC2ServerContainer(feature_context=feature_context,
name=container_name,
vols=self.vols,
network=self.network,
image_store=self.image_store,
command=command,
ssl=False))
elif engine == "minifi-c2-server-ssl":
return self.containers.setdefault(container_name,
MinifiC2ServerContainer(feature_context=feature_context,
name=container_name,
vols=self.vols,
network=self.network,
image_store=self.image_store,
command=command,
ssl=True))
else:
raise Exception('invalid flow engine: \'%s\'' % engine)

Expand Down Expand Up @@ -231,15 +212,6 @@ def restart_container(self, container_name):
def enable_provenance_repository_in_minifi(self):
self.minifi_options.enable_provenance = True

def enable_c2_in_minifi(self):
self.minifi_options.enable_c2 = True

def enable_c2_with_ssl_in_minifi(self):
self.minifi_options.enable_c2_with_ssl = True

def fetch_flow_config_from_c2_url_in_minifi(self):
self.minifi_options.use_flow_config_from_url = True

def set_ssl_context_properties_in_minifi(self):
self.minifi_options.set_ssl_context_properties = True

Expand Down
Loading
Loading