Skip to content

Commit a56ddd6

Browse files
authored
issue_403 handle error messages when service resources are missing in… (#408)
* issue_403 handle error messages when service resources are missing in ingress policy and virtual service
1 parent b448db9 commit a56ddd6

File tree

2 files changed

+61
-36
lines changed

2 files changed

+61
-36
lines changed

nca/Parsers/IngressPolicyYamlParser.py

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,18 @@ def parse_backend(self, backend, is_default=False):
4242
Parses ingress backend and returns the set of pods and ports referenced by it.
4343
:param dict backend: the backend resource
4444
:param bool is_default: whether this is the default backend
45-
:return: a tuple PeerSet and PortSet: the sets of pods and ports referenced by the backend,
46-
or None and all ports when the default backend is None,
47-
or None and None when the non-default backend is None.
45+
:return: a tuple PeerSet and PortSet and a bool flag, as following:
46+
the sets of pods and ports referenced by the backend and True,
47+
or None and all ports and True when the default backend is None ,
48+
or None and None and True when the non-default backend is None,
49+
or None and None and False when the backend is not None but backend service does not exist (for default and
50+
non-default backends)
51+
- for non-default backend when the flag is False then the backend service does not exist, and the path
52+
containing this backend should be ignored (i.e. don't override its peers and ports from the default_backend)
53+
:rtype: (PeerSet, PortSet, bool)
4854
"""
4955
if backend is None:
50-
return (None, PortSet(True)) if is_default else (None, None)
56+
return (None, PortSet(True), True) if is_default else (None, None, True)
5157
allowed_elements = {'resource': [0, dict], 'service': [0, dict]}
5258
self.check_fields_validity(backend, 'backend', allowed_elements)
5359
resource = backend.get('resource')
@@ -57,7 +63,7 @@ def parse_backend(self, backend, is_default=False):
5763
f'in the ingress {"default" if is_default else ""} backend', backend)
5864
if resource:
5965
self.warning('Resource is not yet supported in an ingress backend. Ignoring', backend)
60-
return (None, PortSet(True)) if is_default else (None, None)
66+
return (None, PortSet(True), True) if is_default else (None, None, True)
6167
allowed_service_elements = {'name': [1, str], 'port': [1, dict]}
6268
self.check_fields_validity(service, 'backend service', allowed_service_elements)
6369
service_name = service.get('name')
@@ -73,31 +79,41 @@ def parse_backend(self, backend, is_default=False):
7379
self.validate_value_in_domain(port_number, 'dst_ports', backend, 'Port number')
7480
srv = self.peer_container.get_service_by_name_and_ns(service_name, self.namespace)
7581
if not srv:
76-
self.syntax_error(f'Missing service referenced by the ingress {"default" if is_default else ""} backend',
77-
service)
82+
warning_msg = f'The service referenced by the ingress {"default" if is_default else ""} ' \
83+
f'backend does not exist. '
84+
if is_default:
85+
warning_msg += 'The default backend will be ignored'
86+
else:
87+
warning_msg += 'The rule path containing this backend service will be ignored'
88+
self.warning(warning_msg, service)
89+
return None, None, False
90+
7891
service_port = srv.get_port_by_name(port_name) if port_name else srv.get_port_by_number(port_number)
7992
if not service_port:
8093
self.syntax_error(f'Missing port {port_name if port_name else port_number} in the service', service)
8194

8295
rule_ports = PortSet()
8396
rule_ports.add_port(service_port.target_port) # may be either a number or a named port
84-
return srv.target_pods, rule_ports
97+
return srv.target_pods, rule_ports, True
8598

8699
def parse_ingress_path(self, path):
87100
"""
88101
Parses ingress path resource.
89102
The assumption is that the default backend has been already parsed
90103
:param dict path: the path resource
91-
:return: a tuple (path_string, path_type, peers, ports)
104+
:return: a tuple (path_string, path_type, peers, ports) or None if the path to be ignored
92105
"""
93106
self.check_fields_validity(path, 'ingress rule path',
94107
{'backend': [1, dict], 'path': [0, str], 'pathType': [1, str]},
95108
{'pathType': ['ImplementationSpecific', 'Exact', 'Prefix']})
96109

97110
backend = path.get('backend')
98-
peers, ports = self.parse_backend(backend)
111+
peers, ports, override_default = self.parse_backend(backend)
99112
if not peers:
100-
peers, ports = self.default_backend_peers, self.default_backend_ports
113+
if override_default:
114+
peers, ports = self.default_backend_peers, self.default_backend_ports
115+
else: # backend service does not exist , ignoring this path
116+
return None
101117
path_string = path.get('path')
102118
path_type = path.get('pathType')
103119
# from https://docs.nginx.com/nginx-ingress-controller/configuration/ingress-resources/basic-configuration/
@@ -193,26 +209,30 @@ def parse_rule(self, rule):
193209
hosts_dfa = self.parse_regex_host_value(rule.get("host"), rule)
194210
paths_array = self.get_key_array_and_validate_not_empty(rule.get('http'), 'paths')
195211
allowed_conns = None
212+
default_conns = None
196213
if paths_array is not None:
197214
all_paths_dfa = None
198215
parsed_paths = []
199216
for path in paths_array:
200-
parsed_paths.append(self.parse_ingress_path(path))
201-
parsed_paths_with_dfa = self.segregate_longest_paths_and_make_dfa(parsed_paths)
202-
for (_, paths_dfa, _, peers, ports) in parsed_paths_with_dfa:
203-
# every path is converted to allowed connections
204-
conns = self._make_tcp_like_properties(ports, peers, paths_dfa, hosts_dfa)
205-
if not allowed_conns:
206-
allowed_conns = conns
207-
else:
208-
allowed_conns |= conns
209-
if not all_paths_dfa:
210-
all_paths_dfa = paths_dfa
211-
else:
212-
all_paths_dfa = all_paths_dfa | paths_dfa # pick all captured paths
213-
# for this host, every path not captured by the above paths goes to the default backend or is denied
214-
paths_remainder_dfa = DimensionsManager().get_dimension_domain_by_name('paths') - all_paths_dfa
215-
default_conns = self._make_default_connections(hosts_dfa, paths_remainder_dfa)
217+
path_resources = self.parse_ingress_path(path)
218+
if path_resources is not None:
219+
parsed_paths.append(path_resources)
220+
if parsed_paths:
221+
parsed_paths_with_dfa = self.segregate_longest_paths_and_make_dfa(parsed_paths)
222+
for (_, paths_dfa, _, peers, ports) in parsed_paths_with_dfa:
223+
# every path is converted to allowed connections
224+
conns = self._make_tcp_like_properties(ports, peers, paths_dfa, hosts_dfa)
225+
if not allowed_conns:
226+
allowed_conns = conns
227+
else:
228+
allowed_conns |= conns
229+
if not all_paths_dfa:
230+
all_paths_dfa = paths_dfa
231+
else:
232+
all_paths_dfa = all_paths_dfa | paths_dfa # pick all captured paths
233+
# for this host, every path not captured by the above paths goes to the default backend or is denied
234+
paths_remainder_dfa = DimensionsManager().get_dimension_domain_by_name('paths') - all_paths_dfa
235+
default_conns = self._make_default_connections(hosts_dfa, paths_remainder_dfa)
216236
else: # no paths --> everything for this host goes to the default backend or is denied
217237
default_conns = self._make_default_connections(hosts_dfa)
218238
if allowed_conns and default_conns:
@@ -240,8 +260,8 @@ def parse_policy(self):
240260
'rules': [0, list], 'tls': [0, list]}
241261
self.check_fields_validity(policy_spec, 'Ingress spec', allowed_spec_keys)
242262

243-
self.default_backend_peers, self.default_backend_ports = self.parse_backend(policy_spec.get('defaultBackend'),
244-
True)
263+
self.default_backend_peers, self.default_backend_ports, _ = self.parse_backend(policy_spec.get('defaultBackend'),
264+
True)
245265
# TODO extend to other ingress controllers
246266
res_policy.selected_peers = \
247267
self.peer_container.get_pods_with_service_name_containing_given_string('ingress-nginx')
@@ -251,10 +271,11 @@ def parse_policy(self):
251271
all_hosts_dfa = None
252272
for ingress_rule in policy_spec.get('rules', []):
253273
conns, hosts_dfa = self.parse_rule(ingress_rule)
254-
if not allowed_conns:
255-
allowed_conns = conns
256-
else:
257-
allowed_conns |= conns
274+
if conns:
275+
if not allowed_conns:
276+
allowed_conns = conns
277+
else:
278+
allowed_conns |= conns
258279
if hosts_dfa:
259280
if not all_hosts_dfa:
260281
all_hosts_dfa = hosts_dfa
@@ -269,8 +290,10 @@ def parse_policy(self):
269290
allowed_conns |= default_conns
270291
elif default_conns:
271292
allowed_conns = default_conns
272-
assert allowed_conns
273293

274-
res_policy.add_rules(self._make_allow_rules(allowed_conns))
294+
# allowed_conns = none means that services referenced by this Ingress policy are not found,
295+
# then no connections rules to add (Ingress policy has no effect)
296+
if allowed_conns:
297+
res_policy.add_rules(self._make_allow_rules(allowed_conns))
275298
res_policy.findings = self.warning_msgs
276299
return res_policy

nca/Parsers/IstioTrafficResourcesYamlParser.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,9 @@ def parse_http_route_destinations(self, route, parsed_route, vs):
275275
{'host': [1, str], 'subset': [3, str], 'port': 0})
276276
service = self.parse_service(dest, vs)
277277
if not service:
278-
self.syntax_error(f'missing service referenced in {dest} in the VirtualService {vs.full_name()}', route)
278+
self.warning(f'The service referenced in http destination {dest} in the VirtualService {vs.full_name()}'
279+
f' does not exist. This HTTPRouteDestination will be ignored', route)
280+
continue
279281
target_port = None
280282
port = dest.get('port')
281283
if port:

0 commit comments

Comments
 (0)