Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check only root level banner messages for ASA configs #602

Merged
merged 7 commits into from
Dec 20, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
updating ASAConfigParse banner_start list and updating tests to follo…
…w pattern
carlitos75 committed Dec 19, 2024
commit 41f588635fa1d6ef25a18e4191b160e297bce510
55 changes: 43 additions & 12 deletions netutils/config/parser.py
Original file line number Diff line number Diff line change
@@ -195,7 +195,9 @@ def _remove_parents(self, line: str, current_spaces: int) -> t.Tuple[str, ...]:
previous_parent = self._current_parents[-deindent_level]
previous_indent = self.get_leading_space_count(previous_parent)
except IndexError:
raise IndexError(f"\nValidate the first line does not begin with a space\n{line}\n")
raise IndexError(
f"\nValidate the first line does not begin with a space\n{line}\n"
)
parents = self._current_parents[:-deindent_level] or (self._current_parents[0],)
return parents

@@ -417,7 +419,9 @@ def find_children_w_parents(
]
for cfg_line in self.build_config_relationship():
parents = cfg_line.parents[0] if cfg_line.parents else None
if parents in potential_parents and self._match_type_check(parents, parent_pattern, match_type):
if parents in potential_parents and self._match_type_check(
parents, parent_pattern, match_type
):
config.append(cfg_line.config_line)
return config

@@ -434,7 +438,11 @@ def config_lines_only(self) -> str:
Returns:
The non-space lines from ``config``.
"""
config_lines = [line.rstrip() for line in self.config.splitlines() if line and not line.isspace()]
config_lines = [
line.rstrip()
for line in self.config.splitlines()
if line and not line.isspace()
]
return "\n".join(config_lines)

def build_config_relationship(self) -> t.List[ConfigLine]:
@@ -512,7 +520,9 @@ def _build_multiline_config(self, delimiter: str) -> t.Optional[ConfigLine]:
for line in self.generator_config:
multiline_config.append(line)
if line.lstrip() == delimiter:
multiline_entry = ConfigLine("\n".join(multiline_config), self._current_parents)
multiline_entry = ConfigLine(
"\n".join(multiline_config), self._current_parents
)
self.config_lines.append(multiline_entry)
self._current_parents = self._current_parents[:-1]
return multiline_entry
@@ -522,7 +532,9 @@ def _build_multiline_config(self, delimiter: str) -> t.Optional[ConfigLine]:
class CiscoConfigParser(BaseSpaceConfigParser):
"""Cisco Implementation of ConfigParser Class."""

regex_banner = re.compile(r"^(banner\s+\S+|\s*vacant-message)\s+(?P<banner_delimiter>\^C|.)")
regex_banner = re.compile(
r"^(banner\s+\S+|\s*vacant-message)\s+(?P<banner_delimiter>\^C|.)"
)

def __init__(self, config: str):
"""Create ConfigParser Object.
@@ -584,7 +596,9 @@ def banner_end(self) -> str:
def banner_end(self, banner_start_line: str) -> None:
banner_parsed = self.regex_banner.match(banner_start_line)
if not banner_parsed:
raise ValueError("There was an error parsing your banner, the end of the banner could not be found")
raise ValueError(
"There was an error parsing your banner, the end of the banner could not be found"
)
self._banner_end = banner_parsed.groupdict()["banner_delimiter"]


@@ -853,7 +867,9 @@ def build_config_relationship(self) -> t.List[ConfigLine]:

return self.config_lines

def _build_multiline_single_configuration_line(self, delimiter: str, prev_line: str) -> t.Optional[ConfigLine]:
def _build_multiline_single_configuration_line(
self, delimiter: str, prev_line: str
) -> t.Optional[ConfigLine]:
r"""Concatenate Multiline strings between delimiter when newlines causes string to traverse multiple lines.
Args:
@@ -891,7 +907,9 @@ def _build_multiline_single_configuration_line(self, delimiter: str, prev_line:
for line in self.generator_config:
multiline_config.append(line)
if line.endswith(delimiter):
multiline_entry = ConfigLine("\n".join(multiline_config), self._current_parents)
multiline_entry = ConfigLine(
"\n".join(multiline_config), self._current_parents
)
self.config_lines[-1] = multiline_entry
self._current_parents = self._current_parents[:-1]
return multiline_entry
@@ -913,6 +931,7 @@ def banner_end(self) -> str:
class ASAConfigParser(CiscoConfigParser):
"""Cisco ASA implementation of ConfigParser Class."""

banner_start: t.List[str] = ["banner"]
comment_chars: t.List[str] = ["!", ":"]

def __init__(self, config: str):
@@ -1053,7 +1072,9 @@ def _parse_out_offending(self, config: str) -> str:
# This will grab everything between quotes after the 'set buffer' sub-command.
# Its explicitly looking for "\n to end the captured data. This is to support html
# data that is supported in Fortinet config with double quotes within the html.
pattern = r"(config system replacemsg.*(\".*\")\n)(\s{4}set\sbuffer\s\"[\S\s]*?\"\n)"
pattern = (
r"(config system replacemsg.*(\".*\")\n)(\s{4}set\sbuffer\s\"[\S\s]*?\"\n)"
)
return re.sub(pattern, r"\1 [\2]\n", config)

@property
@@ -1069,7 +1090,10 @@ def config_lines_only(self) -> str:
config_lines = (
line.rstrip()
for line in self.config.splitlines()
if line and not self.is_comment(line) and not line.isspace() and not self.is_end_next(line)
if line
and not self.is_comment(line)
and not line.isspace()
and not self.is_end_next(line)
)
self._config = "\n".join(config_lines)
return self._config
@@ -1469,7 +1493,12 @@ class PaloAltoNetworksConfigParser(BaseSpaceConfigParser):

def is_banner_end(self, line: str) -> bool:
"""Determine if end of banner."""
if line.endswith('"') or line.startswith('";') or line.startswith("set") or line.endswith(self.banner_end):
if (
line.endswith('"')
or line.startswith('";')
or line.startswith("set")
or line.endswith(self.banner_end)
):
return True
return False

@@ -1537,7 +1566,9 @@ def build_config_relationship(self) -> t.List[ConfigLine]: # pylint: disable=to
if line.endswith("{"):
_needs_conversion = True
if _needs_conversion:
converted_config = paloalto_panos_brace_to_set(cfg=self.config, cfg_type="string")
converted_config = paloalto_panos_brace_to_set(
cfg=self.config, cfg_type="string"
)
list_config = converted_config.splitlines()
self.generator_config = (line for line in list_config)

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from netutils.config.parser import ConfigLine

parent: str = "group-policy Grs-POLICY attributes"
data: list[ConfigLine] = [
ConfigLine(config_line=parent, parents=()),
ConfigLine(config_line=" banner value This is an", parents=(parent,)),
ConfigLine(config_line=" banner value example nested banner", parents=(parent,)),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
group-policy Grs-POLICY attributes
banner value This is an
banner value example nested banner
66 changes: 40 additions & 26 deletions tests/unit/test_parser.py
Original file line number Diff line number Diff line change
@@ -6,9 +6,10 @@
import pytest

from netutils.config import compliance
from netutils.config.parser import ConfigLine

MOCK_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "mock", "config", "parser")
MOCK_DIR = os.path.join(
os.path.dirname(os.path.realpath(__file__)), "mock", "config", "parser"
)
TXT_FILE = "_sent.txt"

base_parameters = []
@@ -19,7 +20,9 @@
base_parameters.append([_file, network_os])
for _file in glob.glob(f"{MOCK_DIR}/find_all_children/{network_os}/*{TXT_FILE}"):
find_all_children_parameters.append([_file, network_os])
for _file in glob.glob(f"{MOCK_DIR}/find_children_w_parents/{network_os}/*{TXT_FILE}"):
for _file in glob.glob(
f"{MOCK_DIR}/find_children_w_parents/{network_os}/*{TXT_FILE}"
):
find_children_w_parents_parameters.append([_file, network_os])


@@ -38,23 +41,32 @@ def test_find_all_children(_file, network_os, get_text_data, get_json_data): #
truncate_file = os.path.join(MOCK_DIR, "find_all_children", _file[: -len(TXT_FILE)])

device_cfg = get_text_data(os.path.join(MOCK_DIR, "find_all_children", _file))
received_data = get_text_data(os.path.join(MOCK_DIR, "find_all_children", truncate_file + "_received.txt"))
received_data = get_text_data(
os.path.join(MOCK_DIR, "find_all_children", truncate_file + "_received.txt")
)
kwargs = get_json_data(truncate_file + "_args.json")
os_parser = compliance.parser_map[network_os]
assert "\n".join(os_parser(device_cfg).find_all_children(**kwargs)) == received_data


@pytest.mark.parametrize("_file, network_os", find_children_w_parents_parameters)
def test_find_children_w_parents(
_file, network_os, get_text_data, get_json_data
): # pylint: disable=redefined-outer-name
truncate_file = os.path.join(MOCK_DIR, "find_children_w_parents", _file[: -len(TXT_FILE)])
def test_find_children_w_parents(_file, network_os, get_text_data, get_json_data): # pylint: disable=redefined-outer-name
truncate_file = os.path.join(
MOCK_DIR, "find_children_w_parents", _file[: -len(TXT_FILE)]
)

device_cfg = get_text_data(os.path.join(MOCK_DIR, "find_children_w_parents", _file))
received_data = get_text_data(os.path.join(MOCK_DIR, "find_children_w_parents", truncate_file + "_received.txt"))
received_data = get_text_data(
os.path.join(
MOCK_DIR, "find_children_w_parents", truncate_file + "_received.txt"
)
)
kwargs = get_json_data(truncate_file + "_args.json")
os_parser = compliance.parser_map[network_os]
assert "\n".join(os_parser(device_cfg).find_children_w_parents(**kwargs)) == received_data
assert (
"\n".join(os_parser(device_cfg).find_children_w_parents(**kwargs))
== received_data
)


def test_incorrect_banner_ios():
@@ -79,23 +91,25 @@ def test_duplicate_line():
"snmp-server community <<REPLACED>> RO SNMP_ACL_RO\n"
"snmp-server community <<REPLACED>> RW SNMP_ACL_RW\n"
)
with pytest.raises(IndexError, match=r".*This error is likely from a duplicate line detected.*"):
with pytest.raises(
IndexError, match=r".*This error is likely from a duplicate line detected.*"
):
compliance.parser_map["cisco_ios"](logging).config_lines # pylint: disable=expression-not-assigned


def test_nested_banner():
banner: str = (
"group-policy Grs-POLICY attributes\n" " banner value This is an\n" " banner value example nested banner.\n"
)
# def test_nested_banner():
# banner: str = (
# "group-policy Grs-POLICY attributes\n" " banner value This is an\n" " banner value example nested banner.\n"
# )

# generated_config_lines: list[ConfigLine] = compliance.parser_map["cisco_asa"](
# banner,
# ).config_lines

generated_config_lines: list[ConfigLine] = compliance.parser_map["cisco_asa"](
banner,
).config_lines

parent: str = "group-policy Grs-POLICY attributes"
mock_config_lines: list[ConfigLine] = [
ConfigLine(config_line=parent, parents=()),
ConfigLine(config_line=" banner value This is an", parents=(parent,)),
ConfigLine(config_line=" banner value example nested banner.", parents=(parent,)),
]
assert generated_config_lines == mock_config_lines
# parent: str = "group-policy Grs-POLICY attributes"
# mock_config_lines: list[ConfigLine] = [
# ConfigLine(config_line=parent, parents=()),
# ConfigLine(config_line=" banner value This is an", parents=(parent,)),
# ConfigLine(config_line=" banner value example nested banner.", parents=(parent,)),
# ]
# assert generated_config_lines == mock_config_lines