def invert_generic_cluster_host(host: GenericClusterHost): """ Returns a list of GenericClusterHosts with once inverted pod label selectors, once inverted namespace label selectors and once both """ if host == GenericClusterHost({}, {}): raise ValueError( "Cannot invert GenericClusterHost matching all hosts in cluster" ) if host.namespace_labels == {}: return [GenericClusterHost({}, invert_label_selector(host.pod_labels))] inverted_hosts = [ GenericClusterHost( host.namespace_labels, invert_label_selector(host.pod_labels) ), GenericClusterHost( invert_label_selector(host.namespace_labels), host.pod_labels ), GenericClusterHost( invert_label_selector(host.namespace_labels), invert_label_selector(host.pod_labels), ), ] return inverted_hosts
def invert_generic_cluster_host(host: GenericClusterHost): if host == GenericClusterHost({}, {}): raise ValueError( "Cannot invert GenericClusterHost matching all hosts in cluster") elif host.namespace_labels == {}: return [GenericClusterHost({}, invert_labels(host.pod_labels))] else: inverted_hosts = [ GenericClusterHost(host.namespace_labels, invert_labels(host.pod_labels)), GenericClusterHost(invert_labels(host.namespace_labels), host.pod_labels), GenericClusterHost(invert_labels(host.namespace_labels), invert_labels(host.pod_labels)) ] return inverted_hosts
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)
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
def generate_negative_cases_for_incoming_cases(self, isolated_hosts, incoming_test_cases, other_hosts, namespaces): runtimes = {} start_time = time.time() namespace_labels = [ h.namespace_labels for h in other_hosts if isinstance(h, GenericClusterHost) ] namespaces_per_label_strings = { labels_to_string(k): [ n.metadata.name for n in namespaces if n.metadata.labels is not None and k.items() <= n.metadata.labels.items() ] for k in namespace_labels } namespace_label_resolve_time = time.time() runtimes["nsLabelResolve"] = namespace_label_resolve_time - start_time labels_per_namespace = { n.metadata.name: n.metadata.labels for n in namespaces } overlaps_per_host = { host: get_overlapping_hosts(host, namespaces_per_label_strings, labels_per_namespace, isolated_hosts + other_hosts) for host in isolated_hosts } overlap_calc_time = time.time() runtimes[ "overlapCalc"] = overlap_calc_time - namespace_label_resolve_time cases = [] for host in isolated_hosts: host_string = str(host) host_start_time = time.time() runtimes[host_string] = {} # Check for hosts that can target these to construct negative cases from logger.debug(overlaps_per_host[host]) reaching_hosts_with_ports = [ (t.from_host, t.port_string) for t in incoming_test_cases if t.to_host in overlaps_per_host[host] ] logger.debug(reaching_hosts_with_ports) reaching_host_find_time = time.time() runtimes[host_string][ "findReachingHosts"] = reaching_host_find_time - host_start_time if reaching_hosts_with_ports: reaching_hosts, _ = zip(*reaching_hosts_with_ports) ports_per_host = { host: [p for h, p in reaching_hosts_with_ports if h == host] for host in reaching_hosts } match_all_host = GenericClusterHost({}, {}) if match_all_host in reaching_hosts: # All hosts are allowed to reach (on some ports or all) => results from ALLOW all if "*" in ports_per_host[match_all_host]: logger.info("Not generating negative tests for host " + str(host) + " as all connections to it are allowed") else: case = NetworkTestCase( match_all_host, host, rand_port(ports_per_host[match_all_host]), False) cases.append(case) runtimes[host_string]["matchAllCase"] = time.time( ) - reaching_host_find_time else: inverted_hosts = set([ h for l in [invert_host(host) for host in reaching_hosts] for h in l ]) hosts_on_inverted = { h: originalHost for l, originalHost in [(invert_host(host), host) for host in reaching_hosts] for h in l } host_inversion_time = time.time() runtimes[host_string][ "hostInversion"] = host_inversion_time - reaching_host_find_time overlaps_for_inverted_hosts = { h: get_overlapping_hosts(h, namespaces_per_label_strings, labels_per_namespace, reaching_hosts) for h in inverted_hosts } overlap_calc_time = time.time() runtimes[host_string][ "overlapCalc"] = overlap_calc_time - host_inversion_time logger.debug("InvertedHosts: " + str(inverted_hosts)) negative_test_targets = [ h for h in inverted_hosts if len(overlaps_for_inverted_hosts[h]) <= 1 ] logger.debug("NegativeTestTargets: " + str(negative_test_targets)) # now remove the inverted hosts that are reachable for target in negative_test_targets: ports_for_inverted_hosts_original_host = ports_per_host[ hosts_on_inverted[target]] if ports_for_inverted_hosts_original_host: cases.append( NetworkTestCase( target, host, ports_for_inverted_hosts_original_host[0], False)) else: cases.append( NetworkTestCase(target, host, "*", False)) runtimes[host_string]["casesGen"] = time.time( ) - overlap_calc_time else: # No hosts are allowed to reach host -> it should be totally isolated # => results from default deny policy cases.append(NetworkTestCase(host, host, "*", False)) runtimes["all"] = time.time() - start_time
def generate_negative_cases_for_incoming_cases( self, isolated_hosts, incoming_test_cases, other_hosts, namespaces ): """ Generates negative test cases based on desired positive test cases """ runtimes = {} start_time = time.time() # list of all namespace labels set on other hosts namespace_labels = [ h.namespace_labels for h in other_hosts if isinstance(h, GenericClusterHost) ] namespaces_per_label_strings = get_namespace_label_strings( namespace_labels, namespaces ) namespace_label_resolve_time = time.time() runtimes["nsLabelResolve"] = namespace_label_resolve_time - start_time labels_per_namespace = {n.metadata.name: n.metadata.labels for n in namespaces} overlaps_per_host = { host: self.get_overlapping_hosts( host, namespaces_per_label_strings, labels_per_namespace, isolated_hosts + other_hosts, ) for host in isolated_hosts } overlap_calc_time = time.time() runtimes["overlapCalc"] = overlap_calc_time - namespace_label_resolve_time cases = [] for host in isolated_hosts: host_string = str(host) host_start_time = time.time() runtimes[host_string] = {} # Check for hosts that can target these to construct negative cases from self.logger.debug(overlaps_per_host[host]) allowed_hosts_with_ports = [ (test_case.from_host, test_case.port_string) for test_case in incoming_test_cases if test_case.to_host in overlaps_per_host[host] ] self.logger.debug("allowed_hosts_with_ports=%s", allowed_hosts_with_ports) reaching_host_find_time = time.time() runtimes[host_string]["findReachingHosts"] = ( reaching_host_find_time - host_start_time ) if allowed_hosts_with_ports: allowed_hosts, _ = zip(*allowed_hosts_with_ports) ports_per_host = { host: [ port for _host, port in allowed_hosts_with_ports if _host == host ] for host in allowed_hosts } match_all_host = GenericClusterHost({}, {}) if match_all_host in allowed_hosts: # All hosts are allowed to reach (on some ports or all) => results from ALLOW all if "*" in ports_per_host[match_all_host]: self.logger.info( "Not generating negative tests for host %s" "as all connections to it are allowed", host, ) else: cases.append( NetworkTestCase( match_all_host, host, rand_port(ports_per_host[match_all_host]), False, ) ) runtimes[host_string]["matchAllCase"] = ( time.time() - reaching_host_find_time ) else: inverted_hosts = set( [ h for l in [invert_host(host) for host in allowed_hosts] for h in l ] ) hosts_on_inverted = { h: originalHost for l, originalHost in [ (invert_host(host), host) for host in allowed_hosts ] for h in l } host_inversion_time = time.time() runtimes[host_string]["hostInversion"] = ( host_inversion_time - reaching_host_find_time ) overlaps_for_inverted_hosts = { h: self.get_overlapping_hosts( h, namespaces_per_label_strings, labels_per_namespace, allowed_hosts, ) for h in inverted_hosts } overlap_calc_time = time.time() runtimes[host_string]["overlapCalc"] = ( overlap_calc_time - host_inversion_time ) self.logger.debug("InvertedHosts: %s", inverted_hosts) negative_test_targets = [ h for h in inverted_hosts if len(overlaps_for_inverted_hosts[h]) <= 1 ] self.logger.debug("NegativeTestTargets: %s", negative_test_targets) # now remove the inverted hosts that are reachable for target in negative_test_targets: ports_for_inverted_hosts_original_host = ports_per_host[ hosts_on_inverted[target] ] if ports_for_inverted_hosts_original_host: cases.append( NetworkTestCase( target, host, ports_for_inverted_hosts_original_host[0], False, ) ) else: cases.append(NetworkTestCase(target, host, "*", False)) runtimes[host_string]["casesGen"] = time.time() - overlap_calc_time else: # No hosts are allowed to reach host -> it should be totally isolated # => results from default deny policy cases.append(NetworkTestCase(host, host, "*", False)) runtimes["all"] = time.time() - start_time return cases, runtimes
], [ 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(
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", GenericClusterHost({"test": "test"}, {"test": "test"}), id="Simple GenericClusterHost", ), pytest.param( "illuminatio-inverted-test.io/test-123_XYZ=test_456-123.ABC:" + "illuminatio-inverted-test.io/test-123_XYZ=test_456-123.ABC", GenericClusterHost( { "illuminatio-inverted-test.io/test-123_XYZ": "test_456-123.ABC" }, { "illuminatio-inverted-test.io/test-123_XYZ": "test_456-123.ABC" }, ),