def _rewrite_ports_for_host(self, port_list, services_for_host): self.logger.debug("Rewriting portList %s", port_list) if not services_for_host: # assign random port, a service with matching port will be created return { p: "%s%s" % (("-" if "-" in p else ""), str(rand_port())) for p in port_list } rewritten_ports = {} wild_card_ports = {p for p in port_list if "*" in p} numbered_ports = {p for p in port_list if "*" not in p} service_ports = [ p for svc in services_for_host for p in svc.spec.ports ] self.logger.debug("Svc ports: %s", service_ports) for wildcard_port in wild_card_ports: prefix = "-" if "-" in wildcard_port else "" # choose any port for wildcard rewritten_ports[wildcard_port] = "%s%s" % ( prefix, str(service_ports[0].port), ) for port in numbered_ports: prefix = "-" if "-" in port else "" port_int = int(port.replace("-", "")) service_ports_for_port = [ p for p in service_ports if p.target_port == port_int ] # TODO this was a hotfix for recipe 11, where ports 53 were allowed but not for any target, # resulting in test to 53 being written despite no service matching them existing. # That error should be handled in test generation, an exception here would be fine if service_ports_for_port: rewritten_ports[port] = "%s%s" % ( prefix, str(service_ports_for_port[0].port), ) else: # TODO change to exception, handle it higher up rewritten_ports[port] = "err" return rewritten_ports
def test_randPort_noExceptPorts_returnsInt(): assert isinstance(rand_port(), int)
def test_randPort_allButOnePortsExcepted_returnsRemainingPort(): generated = rand_port(except_ports=list(range(65535))) assert generated == 65535
def test_randPort_allPortsExcept_raisesException(): with pytest.raises(Exception): rand_port(except_ports=list(range(65535 + 1)))
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