@@ -696,51 +696,135 @@ def exec(self):
696
696
else :
697
697
conns , _ , _ , _ = self .config .allowed_connections (peer1 , peer2 )
698
698
if conns :
699
- # TODO: consider separate connectivity maps for config that involves istio -
700
- # one that handles non-TCP connections, and one for TCP
701
- # TODO: consider avoid "hiding" egress allowed connections, even though they are
702
- # not covered by authorization policies
703
- if self .config .policies_container .layers .does_contain_single_layer (NetworkLayerName .Istio ) and \
704
- self .output_config .connectivityFilterIstioEdges :
705
- should_filter , modified_conns = self .filter_istio_edge (peer2 , conns )
706
- if not should_filter :
707
- connections [modified_conns ].append ((peer1 , peer2 ))
708
- # collect both peers, even if one of them is not in the subset
709
- peers .add (peer1 )
710
- peers .add (peer2 )
711
- else :
712
- connections [conns ].append ((peer1 , peer2 ))
713
- # collect both peers, even if one of them is not in the subset
714
- peers .add (peer1 )
715
- peers .add (peer2 )
699
+ connections [conns ].append ((peer1 , peer2 ))
700
+ # collect both peers, even if one of them is not in the subset
701
+ peers .add (peer1 )
702
+ peers .add (peer2 )
703
+
704
+ # if Istio is a layer in the network config - produce 2 maps, for TCP and for non-TCP
705
+ # because Istio policies can only capture TCP connectivity
706
+ if self .config .policies_container .layers .does_contain_layer (NetworkLayerName .Istio ):
707
+ output_res = self .get_connectivity_output_split_by_tcp (connections , peers , peers_to_compare )
708
+ else :
709
+ output_res = self .get_connectivity_output_full (connections , peers , peers_to_compare )
716
710
717
711
res = QueryAnswer (True )
718
- if self .output_config .outputFormat == 'dot' :
719
- conn_graph = ConnectivityGraph (peers , self .config .get_allowed_labels (), self .output_config )
720
- conn_graph .add_edges (connections )
721
- res .output_explanation = [ComputedExplanation (str_explanation = conn_graph .get_connectivity_dot_format_str ())]
712
+ if self .output_config .outputFormat in ['json' , 'yaml' ]:
713
+ res .output_explanation = [ComputedExplanation (dict_explanation = output_res )]
722
714
else :
723
- conn_graph = ConnectivityGraph (peers_to_compare , self .config .get_allowed_labels (), self .output_config )
724
- conn_graph .add_edges (connections )
725
- fw_rules = conn_graph .get_minimized_firewall_rules ()
726
- formatted_rules = fw_rules .get_fw_rules_in_required_format ()
727
- if self .output_config .outputFormat in ['json' , 'yaml' ]:
728
- res .output_explanation = [ComputedExplanation (dict_explanation = formatted_rules )]
729
- else :
730
- res .output_explanation = [ComputedExplanation (str_explanation = formatted_rules )]
715
+ res .output_explanation = [ComputedExplanation (str_explanation = output_res )]
731
716
return res
732
717
718
+ def get_connectivity_output_full (self , connections , peers , peers_to_compare ):
719
+ """
720
+ get the connectivity map output considering all connections in the output
721
+ :param dict connections: the connections' dict (map from connection-set to peer pairs)
722
+ :param PeerSet peers: the peers to consider for dot output
723
+ :param PeerSet peers_to_compare: the peers to consider for fw-rules output
724
+ :rtype Union[str,dict]
725
+ """
726
+ if self .output_config .outputFormat == 'dot' :
727
+ dot_full = self .dot_format_from_connections_dict (connections , peers )
728
+ return dot_full
729
+ # handle formats other than dot
730
+ formatted_rules = self .fw_rules_from_connections_dict (connections , peers_to_compare )
731
+ return formatted_rules
732
+
733
+ def get_connectivity_output_split_by_tcp (self , connections , peers , peers_to_compare ):
734
+ """
735
+ get the connectivity map output as two parts: TCP and non-TCP
736
+ :param dict connections: the connections' dict (map from connection-set to peer pairs)
737
+ :param PeerSet peers: the peers to consider for dot output
738
+ :param PeerSet peers_to_compare: the peers to consider for fw-rules output
739
+ :rtype Union[str,dict]
740
+ """
741
+ connectivity_tcp_str = 'TCP'
742
+ connectivity_non_tcp_str = 'non-TCP'
743
+ connections_tcp , connections_non_tcp = self .convert_connections_to_split_by_tcp (connections )
744
+ if self .output_config .outputFormat == 'dot' :
745
+ dot_tcp = self .dot_format_from_connections_dict (connections_tcp , peers , connectivity_tcp_str )
746
+ dot_non_tcp = self .dot_format_from_connections_dict (connections_non_tcp , peers , connectivity_non_tcp_str )
747
+ # concatenate the two graphs into one dot file
748
+ res_str = dot_tcp + dot_non_tcp
749
+ return res_str
750
+ # handle formats other than dot
751
+ formatted_rules_tcp = self .fw_rules_from_connections_dict (connections_tcp , peers_to_compare ,
752
+ connectivity_tcp_str )
753
+ formatted_rules_non_tcp = self .fw_rules_from_connections_dict (connections_non_tcp , peers_to_compare ,
754
+ connectivity_non_tcp_str )
755
+ if self .output_config .outputFormat in ['json' , 'yaml' ]:
756
+ # get a dict object containing the two maps on different keys (TCP_rules and non-TCP_rules)
757
+ rules = formatted_rules_tcp
758
+ rules .update (formatted_rules_non_tcp )
759
+ return rules
760
+ # remaining formats: txt / csv / md : concatenate the two strings of the conn-maps
761
+ if self .output_config .outputFormat == 'txt' :
762
+ res_str = f'{ formatted_rules_tcp } \n { formatted_rules_non_tcp } '
763
+ else :
764
+ res_str = formatted_rules_tcp + formatted_rules_non_tcp
765
+ return res_str
766
+
767
+ def dot_format_from_connections_dict (self , connections , peers , connectivity_restriction = None ):
768
+ """
769
+ :param dict connections: the connections' dict (map from connection-set to peer pairs)
770
+ :param PeerSet peers: the peers to consider for dot output
771
+ :param Union[str,None] connectivity_restriction: specify if connectivity is restricted to
772
+ TCP / non-TCP , or not
773
+ :rtype str
774
+ :return the connectivity map in dot-format, considering connectivity_restriction if required
775
+ """
776
+ conn_graph = ConnectivityGraph (peers , self .config .get_allowed_labels (), self .output_config )
777
+ conn_graph .add_edges (connections )
778
+ return conn_graph .get_connectivity_dot_format_str (connectivity_restriction )
779
+
780
+ def fw_rules_from_connections_dict (self , connections , peers_to_compare , connectivity_restriction = None ):
781
+ """
782
+ :param dict connections: the connections' dict (map from connection-set to peer pairs)
783
+ :param PeerSet peers_to_compare: the peers to consider for fw-rules output
784
+ :param Union[str,None] connectivity_restriction: specify if connectivity is restricted to
785
+ TCP / non-TCP , or not
786
+ :return the connectivity map in fw-rules, considering connectivity_restriction if required
787
+ :rtype: Union[str, dict]
788
+ """
789
+ conn_graph = ConnectivityGraph (peers_to_compare , self .config .get_allowed_labels (), self .output_config )
790
+ conn_graph .add_edges (connections )
791
+ fw_rules = conn_graph .get_minimized_firewall_rules ()
792
+ formatted_rules = fw_rules .get_fw_rules_in_required_format (connectivity_restriction = connectivity_restriction )
793
+ return formatted_rules
794
+
795
+ def convert_connections_to_split_by_tcp (self , connections ):
796
+ """
797
+ given the connections' dict , convert it to two connection maps, one for TCP only, and the other
798
+ for non-TCP only.
799
+ :param dict connections: the connections' dict (map from connection-set to peer pairs)
800
+ :return: a tuple of the two connection maps : first for TCP, second for non-TCP
801
+ :rtype: tuple(dict, dict)
802
+ """
803
+ connections_tcp = defaultdict (list )
804
+ connections_non_tcp = defaultdict (list )
805
+ for conn , peers_list in connections .items ():
806
+ tcp_conns , non_tcp_conns = self .split_to_tcp_and_non_tcp_conns (conn )
807
+ connections_tcp [tcp_conns ] += peers_list
808
+ connections_non_tcp [non_tcp_conns ] += peers_list
809
+
810
+ return connections_tcp , connections_non_tcp
811
+
733
812
@staticmethod
734
- def filter_istio_edge (peer2 , conns ):
735
- # currently only supporting authorization policies, that do not capture egress rules
736
- if isinstance (peer2 , IpBlock ):
737
- return True , None
738
- # remove allowed connections for non TCP protocols
739
- # https://istio.io/latest/docs/ops/configuration/traffic-management/protocol-selection/
740
- # Non-TCP based protocols, such as UDP, are not proxied. These protocols will continue to function as normal,
741
- # without any interception by the Istio proxy
742
- conns_new = conns - ConnectionSet .get_non_tcp_connections ()
743
- return False , conns_new
813
+ def split_to_tcp_and_non_tcp_conns (conns ):
814
+ """
815
+ split a ConnectionSet object to two objects: one within TCP only, the other within non-TCP protocols
816
+ :param ConnectionSet conns: a ConnectionSet object
817
+ :return: a tuple of the two ConnectionSet objects: first for TCP, second for non-TCP
818
+ :rtype: tuple(ConnectionSet, ConnectionSet)
819
+ """
820
+ tcp_conns = conns - ConnectionSet .get_non_tcp_connections ()
821
+ non_tcp_conns = conns - tcp_conns
822
+ if non_tcp_conns == ConnectionSet .get_non_tcp_connections ():
823
+ non_tcp_conns = ConnectionSet (True ) # all connections in terms of non-TCP
824
+ if tcp_conns == ConnectionSet .get_all_tcp_connections ():
825
+ tcp_conns = ConnectionSet (True ) # all connections in terms of TCP
826
+
827
+ return tcp_conns , non_tcp_conns
744
828
745
829
746
830
class TwoNetworkConfigsQuery (BaseNetworkQuery ):
@@ -853,7 +937,8 @@ def exec(self, cmd_line_flag=False, layer_name=None):
853
937
if different_conns_list :
854
938
return self ._query_answer_with_relevant_explanation (sorted (different_conns_list ))
855
939
856
- return QueryAnswer (True , self .name1 + ' and ' + self .name2 + ' are semantically equivalent.' , numerical_result = 0 )
940
+ return QueryAnswer (True , self .name1 + ' and ' + self .name2 + ' are semantically equivalent.' ,
941
+ numerical_result = 0 )
857
942
858
943
def _query_answer_with_relevant_explanation (self , explanation_list ):
859
944
output_result = self .name1 + ' and ' + self .name2 + ' are not semantically equivalent.'
0 commit comments