예제 #1
0
def test_ClusterHost_matches_podGivenAndFromHostIsClusterHostsWithSupersetLabelsOfPod_returnsFalse(
):
    test_host = ClusterHost("default", {"app": "web", "load": "high"})
    meta = k8s.client.V1ObjectMeta(namespace=test_host.namespace,
                                   labels={"app": test_host.pod_labels["app"]})
    non_matching_pod = k8s.client.V1Pod(metadata=meta)
    assert test_host.matches(non_matching_pod) is False
예제 #2
0
def test_NetworkTestCase_in_sameFieldsVariousHosts_returnsTrue():
    port = 80
    test_host1 = ClusterHost("a", {"a": "b"})
    test_host1_copy = ClusterHost("a", {"a": "b"})
    test_host2 = ExternalHost("192.168.0.1")
    test_host2_copy = ExternalHost("192.168.0.1")
    case_list = [NetworkTestCase(test_host1, test_host2, port, True)]
    copy_case = NetworkTestCase(test_host1_copy, test_host2_copy, port, True)
    assert copy_case in case_list
예제 #3
0
def test_NetworkTestCase_eq_sameFieldsVariousHosts_returnsTrue():
    port = 80
    test_host1 = ClusterHost("a", {"a": "b"})
    test_host1_copy = ClusterHost("a", {"a": "b"})
    test_host2 = ExternalHost("192.168.0.1")
    test_host2_copy = ExternalHost("192.168.0.1")
    case1 = NetworkTestCase(test_host1, test_host2, port, True)
    case2 = NetworkTestCase(test_host1_copy, test_host2_copy, port, True)
    assert case1 == case2
예제 #4
0
def test__generate_test_cases__deny_all__returns_single_negative_case():
    namespaces = [_generate_namespace("default")]
    networkpolicies = [_generate_deny_all_network_policy("default")]
    expected = [
        NetworkTestCase(
            ClusterHost("default", {}), ClusterHost("default", {}), "*", False
        )
    ]
    cases, _ = gen.generate_test_cases(networkpolicies, namespaces)
    assert len(cases) == 1
    assert cases == expected
예제 #5
0
def invert_cluster_host(host: ClusterHost):
    if host.pod_labels == {}:
        return [ClusterHost(INVERTED_ATTRIBUTE_PREFIX + host.namespace, {})]

    inverted_hosts = [
        ClusterHost(INVERTED_ATTRIBUTE_PREFIX + host.namespace,
                    host.pod_labels),
        ClusterHost(INVERTED_ATTRIBUTE_PREFIX + host.namespace,
                    invert_labels(host.pod_labels)),
        ClusterHost(host.namespace, invert_labels(host.pod_labels))
    ]
    return inverted_hosts
예제 #6
0
def test_fromYaml_simpleSampleYaml_returnsExpectedCase():
    testYaml = "localhost:\n  namespc:label=val: ['-80']\n"
    expectedHost = ClusterHost("namespc", {"label": "val"})
    expected = NetworkTestCase(LocalHost(), expectedHost, 80, False)
    actual = from_yaml(testYaml)
    assert len(actual) == 1
    assert actual[0] == expected
예제 #7
0
def _get_other_host_from(connection_targets, rule_namespace):
    namespace_labels = "namespaceLabels"
    pod_labels = "podLabels"
    namespace = "namespace"
    if namespace_labels in connection_targets and pod_labels in connection_targets:
        return GenericClusterHost(connection_targets[namespace_labels],
                                  connection_targets[pod_labels])
    if namespace in connection_targets and pod_labels in connection_targets:
        return ClusterHost(connection_targets[namespace],
                           connection_targets[pod_labels])
    if namespace_labels in connection_targets:  # and no podLabels included
        return GenericClusterHost(connection_targets[namespace_labels], {})
    if pod_labels in connection_targets:
        return ClusterHost(rule_namespace, connection_targets[pod_labels])
    if connection_targets == {}:
        return GenericClusterHost({}, {})
    raise ValueError("Unknown combination of field in connection %s" %
                     connection_targets)
예제 #8
0
def test__generate_test_cases__allow_all__returns_single_positive_case():
    namespaces = [_generate_namespace("default")]
    networkpolicies = [_generate_allow_all_network_policy("default")]
    expected = [
        NetworkTestCase(
            GenericClusterHost({}, {}), ClusterHost("default", {}), "*", True
        )
    ]
    cases, _ = gen.generate_test_cases(networkpolicies, namespaces)
    assert len(cases) == 1
    assert cases == expected
예제 #9
0
def invert_cluster_host(host: ClusterHost):
    """
    Returns a list of ClusterHosts with
    once inverted pod label selectors,
    once inverted namespace label selectors
    and once both
    """
    if host.pod_labels == {}:
        return [ClusterHost("%s%s" % (INVERTED_ATTRIBUTE_PREFIX, host.namespace), {})]

    inverted_hosts = [
        ClusterHost(
            "%s%s" % (INVERTED_ATTRIBUTE_PREFIX, host.namespace), host.pod_labels
        ),
        ClusterHost(
            "%s%s" % (INVERTED_ATTRIBUTE_PREFIX, host.namespace),
            invert_label_selector(host.pod_labels),
        ),
        ClusterHost(host.namespace, invert_label_selector(host.pod_labels)),
    ]
    return inverted_hosts
예제 #10
0
 def generate_test_cases(
     self,
     network_policies: List[k8s.client.V1NetworkPolicy],
     namespaces: List[k8s.client.V1Namespace],
 ):
     """
     Generates positive and negative test cases, also returns measured runtimes
     """
     runtimes = {}
     start_time = time.time()
     isolated_hosts = []
     other_hosts = []
     outgoing_test_cases = []
     incoming_test_cases = []
     self.logger.debug("Generating test cases for %s", network_policies)
     rules = [Rule.from_network_policy(netPol) for netPol in network_policies]
     net_pol_parsing_time = time.time()
     runtimes["parse"] = net_pol_parsing_time - start_time
     self.logger.debug("Rule: %s", rules)
     for rule in rules:
         rule_host = ClusterHost(
             rule.concerns["namespace"], rule.concerns["podLabels"]
         )
         if rule_host not in isolated_hosts:
             isolated_hosts.append(rule_host)
         if rule.allowed:  # means it is NOT default deny rule
             for connection in rule.allowed:
                 for port in connection.ports:
                     on_port = port
                     other_host = _get_other_host_from(
                         connection.targets, rule.concerns["namespace"]
                     )
                     other_hosts.append(other_host)
                     if connection.direction == "to":
                         case = NetworkTestCase(rule_host, other_host, on_port, True)
                         outgoing_test_cases.append(case)
                     elif connection.direction == "from":
                         case = NetworkTestCase(other_host, rule_host, on_port, True)
                         incoming_test_cases.append(case)
                     else:
                         raise ValueError(
                             "Direction '%s' unknown!" % connection.direction
                         )
예제 #11
0
def test__generate_test_cases__allow_some_pods__returns_negative_and_positive_case():
    allowed_namespace = "default"
    forbiden_namespace = INVERTED_ATTRIBUTE_PREFIX + allowed_namespace
    allowed_labels = {"test": "test"}
    forbidden_labels = {INVERTED_ATTRIBUTE_PREFIX + "test": "test"}
    namespaces = [
        _generate_namespace(allowed_namespace)
        ]
    networkpolicies = [
        _generate_allow_labelled_pods_network_policy(allowed_namespace, labels=allowed_labels)
        ]
    expected = [
        NetworkTestCase(
            ClusterHost(allowed_namespace, allowed_labels),
            ClusterHost(allowed_namespace, {}),
            "*",
            True
            ),
        NetworkTestCase(
            ClusterHost(allowed_namespace, forbidden_labels),
            ClusterHost(allowed_namespace, {}),
            "*",
            False
            ),
        NetworkTestCase(
            ClusterHost(forbiden_namespace, allowed_labels),
            ClusterHost(allowed_namespace, {}),
            "*",
            False
            ),
        NetworkTestCase(
            ClusterHost(forbiden_namespace, forbidden_labels),
            ClusterHost(allowed_namespace, {}),
            "*",
            False
            )
        ]
    cases, _ = gen.generate_test_cases(networkpolicies, namespaces)
    assert len(cases) == 4
    assert sorted(cases) == sorted(expected)
예제 #12
0
 def _find_or_create_cluster_resources_for_cases(self, cases_dict,
                                                 api: k8s.client.CoreV1Api):
     resolved_cases = {}
     from_host_mappings = {}
     to_host_mappings = {}
     port_mappings = {}
     for from_host_string, target_dict in cases_dict.items():
         from_host = Host.from_identifier(from_host_string)
         self.logger.debug("Searching pod for host %s", from_host)
         if not isinstance(from_host, (ClusterHost, GenericClusterHost)):
             raise ValueError(
                 "Only ClusterHost and GenericClusterHost fromHosts are supported by this Orchestrator"
             )
         namespaces_for_host = self._find_or_create_namespace_for_host(
             from_host, api)
         from_host = ClusterHost(namespaces_for_host[0].metadata.name,
                                 from_host.pod_labels)
         self.logger.debug("Updated fromHost with found namespace: %s",
                           from_host)
         pods_for_host = [
             pod for pod in self._current_pods if from_host.matches(pod)
         ]
         # create pod if none for fromHost is in cluster (and add it to podsForHost)
         if not pods_for_host:
             self.logger.debug("Creating dummy pod for host %s", from_host)
             additional_labels = {
                 ROLE_LABEL: "from_host_dummy",
                 CLEANUP_LABEL: CLEANUP_ALWAYS,
             }
             # TODO replace 'dummy' with a more suitable name to prevent potential conflicts
             container = k8s.client.V1Container(
                 image=self.oci_images["target"], name="dummy")
             dummy = create_pod_manifest(from_host, additional_labels,
                                         f"{PROJECT_PREFIX}-dummy-",
                                         container)
             resp = api.create_namespaced_pod(dummy.metadata.namespace,
                                              dummy)
             if isinstance(resp, k8s.client.V1Pod):
                 self.logger.debug("Dummy pod %s created succesfully",
                                   resp.metadata.name)
                 pods_for_host = [resp]
                 self._current_pods.append(resp)
             else:
                 self.logger.error("Failed to create dummy pod! Resp: %s",
                                   resp)
         else:
             self.logger.debug("Pods matching %s already exist: ",
                               from_host, pods_for_host)
         # resolve target names for fromHost and add them to resolved cases dict
         pod_identifier = "%s:%s" % (
             pods_for_host[0].metadata.namespace,
             pods_for_host[0].metadata.name,
         )
         self.logger.debug("Mapped pod_identifier: %s", pod_identifier)
         from_host_mappings[from_host_string] = pod_identifier
         (
             names_per_host,
             port_names_per_host,
         ) = self._get_target_names_creating_them_if_missing(
             target_dict, api)
         to_host_mappings[from_host_string] = names_per_host
         port_mappings[from_host_string] = port_names_per_host
         resolved_cases[pod_identifier] = {
             names_per_host[t]:
             [port_names_per_host[t][p] for p in target_dict[t]]
             for t in target_dict
         }
     return resolved_cases, from_host_mappings, to_host_mappings, port_mappings
예제 #13
0
import logging
from typing import List
from unittest.mock import MagicMock

import kubernetes as k8s
from illuminatio.host import ClusterHost
from illuminatio.test_orchestrator import NetworkTestOrchestrator

testHost1 = ClusterHost("default", {"app": "test"})
testHost2 = ClusterHost("default", {"app": "other"})


def createOrchestrator(cases):
    orch = NetworkTestOrchestrator(cases,
                                   logging.getLogger("orchestrator_test"))
    return orch


def test__refreshClusterResourcess_emptyListApiObjectsReturned_extractsEmptyList(
):
    # setup an api mock that returns an empty pod list
    api_mock = k8s.client.CoreV1Api()
    empty_pod_list = k8s.client.V1PodList(items=[])
    api_mock.list_pod_for_all_namespaces = MagicMock(
        return_value=empty_pod_list)
    api_mock.list_service_for_all_namespaces = MagicMock(
        return_value=k8s.client.V1ServiceList(items=[]))
    api_mock.list_namespace = MagicMock(
        return_value=k8s.client.V1NamespaceList(items=[]))
    # test that this results in an empty list
    orch = createOrchestrator([])
예제 #14
0
import kubernetes as k8s
from illuminatio.host import ClusterHost, ExternalHost, LocalHost
from illuminatio.test_case import NetworkTestCase, merge_in_dict, to_yaml, from_yaml

test_host1 = ClusterHost("default", {"app": "test"})
test_host2 = ClusterHost("default", {"app": "test", "label2": "value"})


def test_portString_shouldConnectTrue_outputsPortOnly():
    port = 80
    test_case = NetworkTestCase(LocalHost(), LocalHost(), port, True)
    assert test_case.port_string == str(port)


def test_portString_shouldConnectFalse_outputsPortWithMinusPrefix():
    port = 80
    test_case = NetworkTestCase(LocalHost(), LocalHost(), port, False)
    assert test_case.port_string == "-" + str(port)


# Below equality tests


def test_NetworkTestCase_eq_differentFromHost_returnsFalse():
    port = 80
    case1 = NetworkTestCase(LocalHost(), LocalHost(), port, True)
    case2 = NetworkTestCase(test_host1, LocalHost(), port, True)
    assert case1 != case2


def test_NetworkTestCase_eq_differentToHost_returnsFalse():
예제 #15
0
 def _get_target_names_creating_them_if_missing(self, target_dict,
                                                api: k8s.client.CoreV1Api):
     service_names_per_host = {}
     port_dict_per_host = {}
     for host_string in target_dict.keys():
         host = Host.from_identifier(host_string)
         if isinstance(host, GenericClusterHost):
             self.logger.debug(
                 "Found GenericClusterHost %s,"
                 "Rewriting it to a ClusterHost in default namespace now.",
                 host,
             )
             host = ClusterHost("default", host.pod_labels)
         if not isinstance(host, ClusterHost):
             raise ValueError(
                 "Only ClusterHost targets are supported by this Orchestrator."
                 " Host: %s, hostString: %s" % (host, host_string))
         self.logger.debug("Searching service for host %s", host)
         services_for_host = [
             svc for svc in self._current_services if host.matches(svc)
         ]
         self.logger.debug(
             "Found services %s for host %s ",
             [svc.metadata for svc in services_for_host],
             host,
         )
         rewritten_ports = self._rewrite_ports_for_host(
             target_dict[host_string], services_for_host)
         self.logger.debug("Rewritten ports: %s", rewritten_ports)
         port_dict_per_host[host_string] = rewritten_ports
         if not services_for_host:
             gen_name = "%s-test-target-pod-" % PROJECT_PREFIX
             target_container = k8s.client.V1Container(
                 image=self.oci_images["target"], name="runner")
             pod_labels_tuple = (ROLE_LABEL, "test_target_pod")
             target_pod = create_pod_manifest(
                 host=host,
                 additional_labels={
                     pod_labels_tuple[0]: pod_labels_tuple[1],
                     CLEANUP_LABEL: CLEANUP_ALWAYS,
                 },
                 generate_name=gen_name,
                 container=target_container,
             )
             target_ports = [
                 int(port.replace("-", ""))
                 for port in port_dict_per_host[host_string].values()
             ]
             svc = create_service_manifest(
                 host,
                 {pod_labels_tuple[0]: pod_labels_tuple[1]},
                 {
                     ROLE_LABEL: "test_target_svc",
                     CLEANUP_LABEL: CLEANUP_ALWAYS
                 },
                 target_ports,
             )
             target_pod_namespace = host.namespace
             resp = api.create_namespaced_pod(
                 namespace=target_pod_namespace, body=target_pod)
             if isinstance(resp, k8s.client.V1Pod):
                 self.logger.debug("Target pod %s created succesfully",
                                   resp.metadata.name)
                 self._current_pods.append(resp)
             else:
                 self.logger.error("Failed to create pod! Resp: %s", resp)
             resp = api.create_namespaced_service(namespace=host.namespace,
                                                  body=svc)
             if isinstance(resp, k8s.client.V1Service):
                 service_names_per_host[host_string] = resp.spec.cluster_ip
                 self.logger.debug("Target svc %s created succesfully",
                                   resp.metadata.name)
                 self._current_services.append(resp)
             else:
                 self.logger.error("Failed to create target svc! Resp: %s",
                                   resp)
         else:
             service_names_per_host[host_string] = services_for_host[
                 0].spec.cluster_ip
     return service_names_per_host, port_dict_per_host
예제 #16
0
def test_toYaml_oneTestCase_returnsExpectedYaml():
    testHost = ClusterHost("namespc", {"label": "val"})
    case = NetworkTestCase(LocalHost(), testHost, 80, False)
    expected = "localhost:\n  namespc:label=val: ['-80']\n"
    assert to_yaml([case]) == expected
예제 #17
0
     ExternalHost("123.123.123.123"),
     id="ExternalHost with IPv4",
 ),
 pytest.param(
     "fe80::1ff:fe23:4567:890a",
     ExternalHost("fe80::1ff:fe23:4567:890a"),
     id="ExternalHost with IPv6",
 ),
 pytest.param(
     "default:nginx-23429-asdf",
     ConcreteClusterHost("default", "nginx-23429-asdf"),
     id="Simple ConcreteClusterHost",
 ),
 pytest.param(
     "default:test=test",
     ClusterHost("default", {"test": "test"}),
     id="Simple ClusterHost",
 ),
 pytest.param(
     "illuminatio-inverted-default:illuminatio-inverted-test.io/test-123_XYZ=test_456-123.ABC",
     ClusterHost(
         "illuminatio-inverted-default",
         {
             "illuminatio-inverted-test.io/test-123_XYZ":
             "test_456-123.ABC"
         },
     ),
     id="ClusterHost containing all allowed label characters",
 ),
 pytest.param(
     "test=test:test=test",
예제 #18
0
     [
         k8s.client.V1NetworkPolicy(
             metadata=k8s.client.V1ObjectMeta(name="allow-all",
                                              namespace="default"),
             spec=k8s.client.V1NetworkPolicySpec(
                 pod_selector=k8s.client.V1LabelSelector(
                     match_labels=None),
                 ingress=[
                     k8s.client.V1NetworkPolicyIngressRule(_from=None)
                 ],
             ),
         )
     ],
     [
         NetworkTestCase(GenericClusterHost({}, {}),
                         ClusterHost("default", {}), "*", True)
     ],
     id="Allow all traffic in namespace",
 ),
 pytest.param(
     [
         k8s.client.V1Namespace(metadata=k8s.client.V1ObjectMeta(
             name="default"))
     ],
     [
         k8s.client.V1NetworkPolicy(
             metadata=k8s.client.V1ObjectMeta(name="deny-all",
                                              namespace="default"),
             spec=k8s.client.V1NetworkPolicySpec(
                 pod_selector=k8s.client.V1LabelSelector(
                     match_labels=None),
예제 #19
0
 def _find_or_create_cluster_resources_for_cases(self, cases_dict,
                                                 api: k8s.client.CoreV1Api):
     resolved_cases = {}
     from_host_mappings = {}
     to_host_mappings = {}
     port_mappings = {}
     for from_host_string, target_dict in cases_dict.items():
         from_host = Host.from_identifier(from_host_string)
         logger.debug("Searching pod for host " + str(from_host))
         if not (isinstance(from_host, ClusterHost)
                 or isinstance(from_host, GenericClusterHost)):
             raise ValueError(
                 "Only ClusterHost and GenericClusterHost fromHosts are supported by this Orchestrator"
             )
         namespaces_for_host = self._find_or_create_namespace_for_host(
             from_host, api)
         from_host = ClusterHost(namespaces_for_host[0].metadata.name,
                                 from_host.pod_labels)
         logger.debug("Updated fromHost with found namespace: " +
                      str(from_host))
         pods_for_host = [
             pod for pod in self._current_pods if from_host.matches(pod)
         ]
         # create pod if none for fromHost is in cluster (and add it to podsForHost)
         if not pods_for_host:
             logger.debug("Creating dummy pod for host " + str(from_host))
             additional_labels = {
                 ROLE_LABEL: "from_host_dummy",
                 CLEANUP_LABEL: CLEANUP_ALWAYS
             }
             container = k8s.client.V1Container(image="nginx:stable",
                                                name="dummy")
             dummy = init_pod(from_host, additional_labels,
                              PROJECT_PREFIX + "-dummy-", container)
             resp = api.create_namespaced_pod(dummy.metadata.namespace,
                                              dummy)
             if isinstance(resp, k8s.client.V1Pod):
                 logger.debug("Dummy pod " + resp.metadata.name +
                              " created succesfully")
                 pods_for_host = [resp]
                 self._current_pods.append(resp)
             else:
                 logger.error("Failed to create dummy pod! Resp: " +
                              str(resp))
         else:
             logger.debug("Pods matching " + str(from_host) +
                          " already exist: " + str(pods_for_host))
         # resolve target names for fromHost and add them to resolved cases dict
         pod_identifier = pods_for_host[
             0].metadata.namespace + ":" + pods_for_host[0].metadata.name
         logger.debug("Mapped pod_identifier: " + str(pod_identifier))
         from_host_mappings[from_host_string] = pod_identifier
         names_per_host, port_names_per_host = self._get_target_names_creating_them_if_missing(
             target_dict, api)
         to_host_mappings[from_host_string] = names_per_host
         port_mappings[from_host_string] = port_names_per_host
         resolved_cases[pod_identifier] = {
             names_per_host[t]:
             [port_names_per_host[t][p] for p in target_dict[t]]
             for t in target_dict
         }
     return resolved_cases, from_host_mappings, to_host_mappings, port_mappings
예제 #20
0
 def _get_target_names_creating_them_if_missing(self, target_dict,
                                                api: k8s.client.CoreV1Api):
     svc_names_per_host = {}
     port_dict_per_host = {}
     for host_string in target_dict.keys():
         host = Host.from_identifier(host_string)
         if isinstance(host, GenericClusterHost):
             logger.debug(
                 "Found GenericClusterHost " + str(host) +
                 ". Rewriting it to a ClusterHost in default namespace now."
             )
             host = ClusterHost("default", host.pod_labels)
         if not isinstance(host, ClusterHost):
             raise ValueError(
                 "Only ClusterHost targets are supported by this Orchestrator. Host: "
                 + str(host) + ", hostString: " + host_string)
         logger.debug("Searching service for host " + str(host))
         services_for_host = [
             svc for svc in self._current_services if host.matches(svc)
         ]
         logger.debug("Found services {} for host {} ".format(
             [svc.metadata for svc in services_for_host], host))
         rewritten_ports = self._rewrite_ports_for_host(
             target_dict[host_string], services_for_host)
         logger.debug("Rewritten ports: " + str(rewritten_ports))
         port_dict_per_host[host_string] = rewritten_ports
         if not services_for_host:
             gen_name = PROJECT_PREFIX + "-test-target-pod-"
             target_container = k8s.client.V1Container(
                 image=self.target_image, name="runner")
             pod_labels_tuple = (ROLE_LABEL, "test_target_pod")
             target_pod = init_pod(host=host,
                                   additional_labels={
                                       pod_labels_tuple[0]:
                                       pod_labels_tuple[1],
                                       CLEANUP_LABEL: CLEANUP_ALWAYS
                                   },
                                   generate_name=gen_name,
                                   container=target_container)
             target_ports = [
                 int(port.replace("-", ""))
                 for port in port_dict_per_host[host_string].values()
             ]
             # ToDo we should use the cluser ip instead of the DNS names
             # so we don't need the lookups
             svc_name = "svc-" + convert_to_resource_name(
                 host.to_identifier())
             svc = init_svc(host,
                            {pod_labels_tuple[0]: pod_labels_tuple[1]}, {
                                 ROLE_LABEL: "test_target_svc",
                                 CLEANUP_LABEL: CLEANUP_ALWAYS
                             }, svc_name, target_ports)
             target_pod_namespace = host.namespace
             svc_names_per_host[
                 host_string] = target_pod_namespace + ":" + svc_name
             resp = api.create_namespaced_pod(
                 namespace=target_pod_namespace, body=target_pod)
             if isinstance(resp, k8s.client.V1Pod):
                 logger.debug("Target pod " + resp.metadata.name +
                              " created succesfully")
                 self._current_pods.append(resp)
             else:
                 logger.error("Failed to create pod! Resp: " + str(resp))
             resp = api.create_namespaced_service(namespace=host.namespace,
                                                  body=svc)
             if isinstance(resp, k8s.client.V1Service):
                 logger.debug("Target svc " + resp.metadata.name +
                              " created succesfully")
                 self._current_services.append(resp)
             else:
                 logger.error("Failed to create target svc! Resp: " +
                              str(resp))
         else:
             svc_names_per_host[host_string] = services_for_host[
                 0].metadata.namespace + ":" + services_for_host[
                     0].metadata.name
     return svc_names_per_host, port_dict_per_host