Skip to content

Commit b448db9

Browse files
shireenf-ibmadisos
andauthored
issue_403 - AllCaptured tasks (#407)
* AllCaptured tasks: 1. improve message when no policies are considered. 2. adding information about ingress captured with istio Signed-off-by: shireenf-ibm <[email protected]> Signed-off-by: shireenf-ibm <[email protected]> * Update nca/NetworkConfig/NetworkConfigQuery.py Co-authored-by: Adi Sosnovich <[email protected]> Signed-off-by: shireenf-ibm <[email protected]> Co-authored-by: Adi Sosnovich <[email protected]>
1 parent db30639 commit b448db9

File tree

6 files changed

+129
-68
lines changed

6 files changed

+129
-68
lines changed

docs/SchemeFileFormat.md

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -44,29 +44,29 @@ Possible entries (sources) in the list under `networkPolicyList` or `resourceLis
4444
### <a name="queryobject"></a>Query object
4545
Each query object instructs the tool to run a specific check on one or more sets of policies.
4646

47-
| Field | Description | Value |
48-
|-------|-------------|-------|
49-
|name |Query name|string|
50-
|emptiness|Checks all NetworkConfigs for empty selectors/rules|list of [config set](#configsets) names|
51-
|redundancy|Checks each set of NetworkConfigs for redundant policies and for redundant rules within each policy|list of [config set](#configsets) names|
52-
|equivalence|Checks semantic equivalence between each pair of NetworkConfigs sets|list of [config set](#configsets) names|
53-
|strongEquivalence|Like equivalence, but comparisons are policy-wise|list of [config set](#configsets) names|
54-
|semanticDiff|Checks semantic diff between each pair of NetworkConfigs sets|list of [config set](#configsets) names|
55-
|forbids|Checks whether the first set denies all connections **explicitly** allowed by the other sets|list of [config set](#configsets) names|
56-
|permits|Checks whether the first set allows all connections **explicitly** allowed by the other sets|list of [config set](#configsets) names|
57-
|interferes|Checks whether any set interferes with the first set|list of [config set](#configsets) names|
58-
|pairwiseInterferes|Checks whether any two sets in the list interfere each other|list of [config set](#configsets) names|
59-
|containment|Checks whether any set is semantically contained in the first set (does not allow additional connections)|list of [config set](#configsets) names|
60-
|twoWayContainment|Checks what are the relations - equivalence, contains, contained, disjoint, neither - between the first set and each of the other sets|list of [config set](#configsets) names|
61-
|disjointness|Reports pairs of policies with overlapping sets of captured pods|list of [config set](#configsets) names|
62-
|vacuity|Checks whether the set of policies changes cluster default behavior|list of [config set](#configsets) names|
63-
|sanity|Checks all NetworkConfigs for sanity check - includes emptiness, vacuity and redundancies|list of [config set](#configsets) names|
64-
|allCaptured|Checks that all pods are captured by at least one NetworkPolicy|list of [config set](#configsets) names|
65-
|connectivityMap|Reports a summary of the allowed connections in the cluster|list of [config set](#configsets) names|
66-
|expected|The expected sum of returned results by all sub-queries in this query (a warning is issued on mismatch)|integer|
67-
|expectedOutput|The file path of the expected output of this query (for connectivityMap or semanticDiff queries) |string|
68-
|expectedNotExecuted|The number of input configs/config pairs that the query is not expected to be run on. Reasons for not executing the configs are listed [here](CmdLineQueriesResults.md#a-query-will-not-be-executed-when) |integer|
69-
|outputConfiguration| A dict object with the required output configuration|[outputConfig](#outputconfig) object|
47+
| Field | Description | Value |
48+
|-------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------|
49+
|name | Query name |string|
50+
|emptiness| Checks all NetworkConfigs for empty selectors/rules |list of [config set](#configsets) names|
51+
|redundancy| Checks each set of NetworkConfigs for redundant policies and for redundant rules within each policy |list of [config set](#configsets) names|
52+
|equivalence| Checks semantic equivalence between each pair of NetworkConfigs sets |list of [config set](#configsets) names|
53+
|strongEquivalence| Like equivalence, but comparisons are policy-wise |list of [config set](#configsets) names|
54+
|semanticDiff| Checks semantic diff between each pair of NetworkConfigs sets |list of [config set](#configsets) names|
55+
|forbids| Checks whether the first set denies all connections **explicitly** allowed by the other sets |list of [config set](#configsets) names|
56+
|permits| Checks whether the first set allows all connections **explicitly** allowed by the other sets |list of [config set](#configsets) names|
57+
|interferes| Checks whether any set interferes with the first set |list of [config set](#configsets) names|
58+
|pairwiseInterferes| Checks whether any two sets in the list interfere each other |list of [config set](#configsets) names|
59+
|containment| Checks whether any set is semantically contained in the first set (does not allow additional connections) |list of [config set](#configsets) names|
60+
|twoWayContainment| Checks what are the relations - equivalence, contains, contained, disjoint, neither - between the first set and each of the other sets |list of [config set](#configsets) names|
61+
|disjointness| Reports pairs of policies with overlapping sets of captured pods |list of [config set](#configsets) names|
62+
|vacuity| Checks whether the set of policies changes cluster default behavior |list of [config set](#configsets) names|
63+
|sanity| Checks all NetworkConfigs for sanity check - includes emptiness, vacuity and redundancies |list of [config set](#configsets) names|
64+
|allCaptured| Checks that all pods are captured by at least one NetworkPolicy in each existing k8s/calico/istio network layer |list of [config set](#configsets) names|
65+
|connectivityMap| Reports a summary of the allowed connections in the cluster |list of [config set](#configsets) names|
66+
|expected| The expected sum of returned results by all sub-queries in this query (a warning is issued on mismatch) |integer|
67+
|expectedOutput| The file path of the expected output of this query (for connectivityMap or semanticDiff queries) |string|
68+
|expectedNotExecuted| The number of input configs/config pairs that the query is not expected to be run on. Reasons for not executing the configs are listed [here](CmdLineQueriesResults.md#a-query-will-not-be-executed-when) |integer|
69+
|outputConfiguration| A dict object with the required output configuration |[outputConfig](#outputconfig) object|
7070

7171
#### <a name="configsets"></a>Config sets
7272
Each entry in the list of config sets should be either

nca/NetworkConfig/NetworkConfigQuery.py

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from nca.Resources.CalicoNetworkPolicy import CalicoNetworkPolicy
1515
from nca.Resources.IngressPolicy import IngressPolicy
1616
from nca.Utils.OutputConfiguration import OutputConfiguration
17-
from .QueryOutputHandler import QueryAnswer, DictOutputHandler, StringOutputHandler, \
17+
from .QueryOutputHandler import QueryAnswer, DictOutputHandler, StringOutputHandler, \
1818
PoliciesAndRulesExplanations, PodsListsExplanations, ConnectionsDiffExplanation, IntersectPodsExplanation, \
1919
PoliciesWithCommonPods, PeersAndConnections, ComputedExplanation
2020
from .NetworkLayer import NetworkLayerName
@@ -1453,7 +1453,7 @@ def exec(self, cmd_line_flag):
14531453
class AllCapturedQuery(NetworkConfigQuery):
14541454
"""
14551455
Check that all pods are captured
1456-
Applies only for k8s/calico policies
1456+
Applies for k8s/calico/istio policies (checks only ingress direction for istio)
14571457
"""
14581458

14591459
def _get_pod_name(self, pod):
@@ -1463,44 +1463,81 @@ def _get_pod_name(self, pod):
14631463
"""
14641464
return pod.workload_name if self.output_config.outputEndpoints == 'deployments' else str(pod)
14651465

1466-
def _get_uncaptured_resources_explanation(self, uncaptured_pods):
1466+
def _get_uncaptured_xgress_pods(self, layer_name, is_ingress=True):
14671467
"""
1468-
get numerical result + set of names of ingress/egress uncaptured pods
1469-
:param PeerSet uncaptured_pods: the set of uncaptured
1470-
:return: (int,set[str]): (the number of uncaptured resources , uncaptured pods names)
1468+
returns the uncaptured ingress/egress pods set and its length for the given layer
1469+
:param NetworkLayerName layer_name: the layer to check uncaptured pods in
1470+
:param bool is_ingress: indicates if to check pods affected by ingress/egress
1471+
:return: - number of uncaptured pod
1472+
- set of the uncaptured pods
1473+
:rtype: (int,set[str])
14711474
"""
1472-
if not uncaptured_pods:
1473-
return 0, ''
1474-
uncaptured_resources = set(self._get_pod_name(pod) for pod in uncaptured_pods) # no duplicate resources in set
1475+
existing_pods = self.config.peer_container.get_all_peers_group()
1476+
uncaptured_xgress_pods = existing_pods - self.config.get_affected_pods(is_ingress, layer_name)
1477+
if not uncaptured_xgress_pods:
1478+
return 0, set()
1479+
uncaptured_resources = set(self._get_pod_name(pod) for pod in uncaptured_xgress_pods) # no duplicate resources in set
14751480
return len(uncaptured_resources), uncaptured_resources
14761481

1482+
def _compute_uncaptured_pods_by_layer(self, layer_name, ingress_only=False):
1483+
"""
1484+
computes and returns the result of allcaptured query on the given layer if it includes policies
1485+
:param NetworkLayerName layer_name: the layer to check uncaptured pods in
1486+
:param bool ingress_only: a flag to indicate if to check captured pods only for ingress affected policies
1487+
:return: 1- if there are uncaptured pods then return an explanation containing them, else None
1488+
2- the number of uncaptured pods on the layer
1489+
:rtype: (PodsListsExplanations, int)
1490+
"""
1491+
if layer_name not in self.config.policies_container.layers:
1492+
return None, 0 # not relevant to compute for non-existed layer
1493+
if ingress_only:
1494+
print(f'Warning: AllCaptured query is not considering uncaptured pods in {layer_name.name} egress direction')
1495+
1496+
res_ingress, uncaptured_ingress_pods_set = self._get_uncaptured_xgress_pods(layer_name, is_ingress=True)
1497+
res_egress = 0
1498+
uncaptured_egress_pods_set = set()
1499+
if not ingress_only:
1500+
res_egress, uncaptured_egress_pods_set = self._get_uncaptured_xgress_pods(layer_name, is_ingress=False)
1501+
1502+
layer_res = res_ingress + res_egress
1503+
if layer_res == 0: # no uncaptured pods in this layer, no explanation would be written
1504+
return None, 0
1505+
1506+
explanation_str = f'workload resources that are not captured by any {layer_name.name} policy that affects '
1507+
layer_explanation = PodsListsExplanations(explanation_description=explanation_str,
1508+
pods_list=list(sorted(uncaptured_ingress_pods_set)),
1509+
egress_pods_list=list(sorted(uncaptured_egress_pods_set)),
1510+
add_xgress_suffix=True)
1511+
return layer_explanation, layer_res
1512+
14771513
def exec(self):
14781514
self.output_config.fullExplanation = True # assign true for this query - it is always ok to compare its results
14791515
existing_pods = self.config.peer_container.get_all_peers_group()
14801516
if not self.config:
14811517
return QueryAnswer(bool_result=False,
1482-
output_result='Flat network in ' + self.config.name,
1518+
output_result=f'There are no network policies in {self.config.name}. '
1519+
f'All workload resources are non captured',
14831520
numerical_result=len(existing_pods))
14841521

1485-
if NetworkLayerName.K8s_Calico not in self.config.policies_container.layers:
1522+
if self.config.policies_container.layers.does_contain_single_layer(NetworkLayerName.Ingress):
14861523
return QueryAnswer(bool_result=False,
1487-
output_result='AllCapturedQuery applies only for k8s/calico network policies',
1524+
output_result='AllCapturedQuery cannot be applied using Ingress resources only',
14881525
query_not_executed=True)
14891526

1490-
uncaptured_ingress_pods = existing_pods - self.config.get_affected_pods(True, NetworkLayerName.K8s_Calico)
1491-
uncaptured_egress_pods = existing_pods - self.config.get_affected_pods(False, NetworkLayerName.K8s_Calico)
1492-
if not uncaptured_ingress_pods and not uncaptured_egress_pods:
1493-
output_str = f'All pods are captured by at least one policy of k8s/calico in {self.config.name}'
1527+
k8s_calico_pods_list_explanation, k8s_calico_res = self._compute_uncaptured_pods_by_layer(NetworkLayerName.K8s_Calico)
1528+
istio_pods_list_explanation, istio_res = self._compute_uncaptured_pods_by_layer(NetworkLayerName.Istio, True)
1529+
1530+
if k8s_calico_res == 0 and istio_res == 0:
1531+
output_str = f'All pods are captured by at least one policy in {self.config.name}'
14941532
return QueryAnswer(bool_result=True, output_result=output_str, numerical_result=0)
14951533

1496-
res_ingress, uncaptured_ingress_pods_set = self._get_uncaptured_resources_explanation(uncaptured_ingress_pods)
1497-
res_egress, uncaptured_egress_pods_set = self._get_uncaptured_resources_explanation(uncaptured_egress_pods)
1498-
res = res_ingress + res_egress
1499-
output_str = f'There are workload resources not captured by any k8s/calico policy in {self.config.name}'
1500-
explanation_str = 'workload resources that are not captured by any policy that affects '
1501-
final_explanation = PodsListsExplanations(explanation_description=explanation_str,
1502-
pods_list=list(sorted(uncaptured_ingress_pods_set)),
1503-
egress_pods_list=list(sorted(uncaptured_egress_pods_set)),
1504-
add_xgress_suffix=True)
1505-
return QueryAnswer(bool_result=False, output_result=output_str, output_explanation=[final_explanation],
1534+
final_explanation = []
1535+
if k8s_calico_pods_list_explanation:
1536+
final_explanation.append(k8s_calico_pods_list_explanation)
1537+
if istio_pods_list_explanation:
1538+
final_explanation.append(istio_pods_list_explanation)
1539+
1540+
output_str = f'There are workload resources not captured by any policy in {self.config.name}'
1541+
res = k8s_calico_res + istio_res
1542+
return QueryAnswer(bool_result=False, output_result=output_str, output_explanation=final_explanation,
15061543
numerical_result=res)

tests/calico_testcases/example_policies/testcase26-multi-layer-policies/testcase26-multi-layer-allcaptured-scheme.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,28 +56,28 @@ queries:
5656
allCaptured:
5757
- testcase26-config-1-k8s-istio-ingress
5858
expectedOutput: ../../expected_output/multi-layer-all-captured-1.txt
59-
expected: 15 # result of non-captured pods in layer k8s-calico
59+
expected: 22 # result of 15 non-captured pods in layer k8s-calico and 7 non-captured pods in layer istio
6060

6161
- name: allcaptured-1-yaml
6262
allCaptured:
6363
- testcase26-config-1-k8s-istio-ingress
6464
outputConfiguration:
6565
outputFormat: yaml
6666
expectedOutput: ../../expected_output/multi-layer-all-captured-1.yaml
67-
expected: 15 # result of non-captured pods in layer k8s-calico
67+
expected: 22 # result of 15 non-captured pods in layer k8s-calico and 7 non-captured pods in layer istio
6868

6969
- name: allcaptured-1-json
7070
allCaptured:
7171
- testcase26-config-1-k8s-istio-ingress
7272
outputConfiguration:
7373
outputFormat: json
7474
expectedOutput: ../../expected_output/multi-layer-all-captured-1.json
75-
expected: 15 # result of non-captured pods in layer k8s-calico
75+
expected: 22 # result of 15 non-captured pods in layer k8s-calico and 7 non-captured pods in layer istio
7676

7777
- name: allcaptured-2
7878
allCaptured:
7979
- testcase26-config-1-k8s-istio-ingress-default-deny
80-
expected: 0 # all is captured on k8-calico layer
80+
expected: 7 # 7 non-captured pods in layer istio, all is captured on k8-calico layer
8181

8282
- name: allcaptured-3
8383
allCaptured:
@@ -92,4 +92,4 @@ queries:
9292
- name: allcaptured-5
9393
allCaptured:
9494
- testcase26-config-1-istio-ingress
95-
expectedNotExecuted: 1
95+
expected: 0

0 commit comments

Comments
 (0)