diff --git a/tests/standalone_plugins/test_deprecate_vts.py b/tests/standalone_plugins/test_deprecate_vts.py index 76b31ca2..af89373b 100644 --- a/tests/standalone_plugins/test_deprecate_vts.py +++ b/tests/standalone_plugins/test_deprecate_vts.py @@ -21,7 +21,7 @@ def test_parse_args(self): testfile = "testfile.nasl" output_path = "attic/" input_path = "nasl/common" - oid_mapping_file = "oid_file.py" + reason = "notus" args = parse_args( [ @@ -29,17 +29,33 @@ def test_parse_args(self): str(testfile), "--output-path", output_path, - "--oid-mapping-path", - oid_mapping_file, "--input-path", input_path, + "--deprecation-reason", + reason, ] ) self.assertEqual(args.file, Path(testfile)) self.assertEqual(args.output_path, output_path) - self.assertEqual(args.oid_mapping_path, oid_mapping_file) + self.assertEqual(args.deprecation_reason, reason) self.assertEqual(args.input_path, input_path) + def test_parse_args_invali_reason(self): + output_path = "attic/" + input_path = "nasl/common" + reason = "foo" + with self.assertRaises(SystemExit): + parse_args( + [ + "--output-path", + output_path, + "--input-path", + input_path, + "--deprecation-reason", + reason, + ] + ) + NASL_CONTENT = ( '...if(description)\n{\n script_oid("1.3.6.1.4.1.25623.1.0.910673");' @@ -77,13 +93,16 @@ def test_deprecate(self): content=NASL_CONTENT, ) ] - deprecate(out_dir, to_deprecate) + deprecate(out_dir, to_deprecate, "notus") result = testfile2.read_text(encoding="utf8") self.assertNotIn(result, "script_mandatory_keys") self.assertNotIn(result, "script_dependencies") self.assertNotIn(result, 'include("revisions-lib.inc");') - assert "This VT has been deprecated." in result + assert ( + "Note: This VT has been deprecated and replaced by " + "a Notus scanner based one." + ) in result def test_deprecate_kb_item(self): with TemporaryDirectory() as out_dir, TemporaryDirectory() as in_dir: @@ -97,15 +116,17 @@ def test_deprecate_kb_item(self): content=NASL_CONTENT_KB, ) ] - deprecate(out_dir, to_deprecate) + deprecate(out_dir, to_deprecate, "notus") self.assertLogs( - "Unable to deprecate testfile1.nasl. There are still KB keys remaining." + "Unable to deprecate testfile1.nasl. There are still KB keys " + "remaining." ) def test_get_summary(self): result = get_summary(NASL_CONTENT) expected = ( - "The remote host is missing an update for the 'gd'\n package(s) announced " + "The remote host is missing an update for the 'gd'\n package(s) " + "announced " "via the RHSA-2020:5443-01 advisory." ) self.assertEqual(result, expected) @@ -114,7 +135,7 @@ def test_finalize_content(self): result = finalize_content(NASL_CONTENT) expected = ( '...if(description)\n{\n script_oid("1.3.6.1.4.1.25623.1.0.910673");\n ' - 'script_version("2024-03-12T14:15:13+0000");\n script_name("RedHat: Security Advisory for gd (RHSA-2020:5443-01)");\n script_family("Red Hat Local Security Checks");\n script_dependencies("gather-package-list.nasl");\n script_mandatory_keys("ssh/login/rhel", "ssh/login/rpms", re:"ssh/login/release=RHENT_7");\n\n script_xref(name:"RHSA", value:"2020:5443-01");\n script_xref(name:"URL", value:"https://www.redhat.com/archives/rhsa-announce/2020-December/msg00044.html");\n\n script_tag(name:"summary", value:"The remote host is missing an update for the \'gd\'\n package(s) announced via the RHSA-2020:5443-01 advisory.");\n\n script_tag(name: \'deprecated\', value: TRUE);\n\nexit(0);\n}\n\nexit(66);\n' + 'script_version("2024-03-12T14:15:13+0000");\n script_name("RedHat: Security Advisory for gd (RHSA-2020:5443-01)");\n script_family("Red Hat Local Security Checks");\n script_dependencies("gather-package-list.nasl");\n script_mandatory_keys("ssh/login/rhel", "ssh/login/rpms", re:"ssh/login/release=RHENT_7");\n\n script_xref(name:"RHSA", value:"2020:5443-01");\n script_xref(name:"URL", value:"https://www.redhat.com/archives/rhsa-announce/2020-December/msg00044.html");\n\n script_tag(name:"summary", value:"The remote host is missing an update for the \'gd\'\n package(s) announced via the RHSA-2020:5443-01 advisory.");\n\n script_tag(name:"deprecated", value:TRUE);\n\nexit(0);\n}\n\nexit(66);\n' ) self.assertEqual(result, expected) @@ -124,7 +145,7 @@ def test_update_summary_no_oid_match(self): full_path=Path("dir/testfile.nasl"), content=NASL_CONTENT, ) - result = update_summary(file) + result = update_summary(file, "notus") self.assertIn("This VT has been deprecated", result) def test_get_files_dir(self): @@ -143,6 +164,34 @@ def test_get_files_dir(self): self.assertEqual(result, expected) + def test_get_files_dir_filtered_in(self): + with TemporaryDirectory() as in_dir: + testfile1 = in_dir / "gb_rhsa_2021_8383_8383.nasl" + testfile1.write_text(NASL_CONTENT, encoding="utf8") + testfile2 = in_dir / "gb_rhsa_2020_8383_8383.nasl" + testfile2.write_text(NASL_CONTENT, encoding="utf8") + + result = get_files(dir_path=in_dir, filename_prefix="gb_rhsa_2021") + expected = [ + DeprecatedFile( + name="gb_rhsa_2021_8383_8383.nasl", + full_path=in_dir / "gb_rhsa_2021_8383_8383.nasl", + content=NASL_CONTENT, + ) + ] + + self.assertEqual(result, expected) + + def test_get_files_dir_filtered_out(self): + with TemporaryDirectory() as in_dir: + testfile1 = in_dir / "gb_rhsa_2021_8383_8383.nasl" + testfile1.write_text(NASL_CONTENT, encoding="utf8") + + result = get_files(dir_path=in_dir, filename_prefix="gb_rhsa_2020") + expected = [] + + self.assertEqual(result, expected) + def test_get_files_single(self): with TemporaryDirectory() as in_dir: testfile1 = in_dir / "gb_rhsa_2021_8383_8383.nasl" @@ -158,3 +207,22 @@ def test_get_files_single(self): ] self.assertEqual(result, expected) + + def test_get_files_single_filtered_in(self): + with TemporaryDirectory() as in_dir: + testfile1 = in_dir / "gb_rhsa_2021_8383_8383.nasl" + testfile2 = in_dir / "gb_rhsa_2022_8484.nasl" + + testfile1.write_text(NASL_CONTENT, encoding="utf8") + testfile2.write_text(NASL_CONTENT, encoding="utf8") + + result = get_files(file=testfile1, filename_prefix="gb_rhsa_2021") + expected = [ + DeprecatedFile( + name="gb_rhsa_2021_8383_8383.nasl", + full_path=in_dir / "gb_rhsa_2021_8383_8383.nasl", + content=NASL_CONTENT, + ) + ] + + self.assertEqual(result, expected) diff --git a/troubadix/standalone_plugins/deprecate_vts.py b/troubadix/standalone_plugins/deprecate_vts.py index 87c74572..8f932d7b 100644 --- a/troubadix/standalone_plugins/deprecate_vts.py +++ b/troubadix/standalone_plugins/deprecate_vts.py @@ -3,7 +3,6 @@ import os import re -import sys from argparse import ArgumentParser, Namespace from dataclasses import dataclass from pathlib import Path @@ -29,35 +28,35 @@ class DeprecatedFile: content: str -# CHANGE AS NEEDED -FILENAME_REGEX = re.compile(r"gb_") - KB_ITEMS = re.compile(r"set_kb_item\(.+\);") -def update_summary(file: DeprecatedFile, oid_mapping_path: Path = None) -> str: +def update_summary(file: DeprecatedFile, deprecation_reason: str) -> str: """Update the summary of the nasl script by adding the information that the script has been deprecated, and if possible, the oid of the new notus script replacing it. Args: file: DeprecatedFile object containing the content of the VT - oid_mapping_path (optional): The path to the file that contains a - mapping of old oids to new oids (see notus-generator transition - layer) + deprecation_reason: The reason this VT is being deprecated, + from a list of options. Returns: The updated content of the file """ - if oid_mapping_path: - oid = match_oid(file.content, oid_mapping_path) - deprecate_text = f"This VT has been replaced by the new VT: {oid}. " - else: - deprecate_text = "This VT has been deprecated." + deprecate_text = "Note: This VT has been deprecated " + if deprecation_reason == "notus": + deprecate_text += "and replaced by a Notus scanner based one." + if deprecation_reason == "merged": + deprecate_text = "because it has been merged into a different VT." + if deprecation_reason == "defunct": + deprecate_text = "and is therefore no longer functional." + if deprecation_reason == "duplicate": + deprecate_text = "as a duplicate." old_summary = get_summary(file.content) if old_summary: - new_summary = deprecate_text + old_summary + new_summary = old_summary + "\n" + deprecate_text file.content = file.content.replace(old_summary, new_summary) else: print(f"No summary in: {file.name}") @@ -65,61 +64,58 @@ def update_summary(file: DeprecatedFile, oid_mapping_path: Path = None) -> str: return file.content -def match_oid(content: str, oid_mapping_path: Path) -> str: - """Find the new Notus oid that has been mapped to the old - OID, so we can add this to the deprecation note. - """ - pattern = get_special_script_tag_pattern(SpecialScriptTag.OID) - match = re.search(pattern, content) - old_oid = match.group(1) - - # needs improvement - # pylint: disable=import-error, import-outside-toplevel - sys.path.append(oid_mapping_path) - from redhat import mapping - - reverse_mapping = dict((v, k) for k, v in mapping.items()) - new_oid = reverse_mapping.get(old_oid) - return new_oid - - def finalize_content(content: str) -> str: """Update the content field of the nasl script by adding the deprecated tag and removing the extra content.""" content_to_keep = content.split("exit(0);")[0] return content_to_keep + ( - "script_tag(name: 'deprecated', value: TRUE);" + 'script_tag(name:"deprecated", value:TRUE);' "\n\nexit(0);\n}\n\nexit(66);\n" ) -def get_files(dir_path: Path = None, file: Path = None) -> list[DeprecatedFile]: +def get_files( + dir_path: Path = None, file: Path = None, filename_prefix=None +) -> list[DeprecatedFile]: """Create a list of DeprecatedFile objects Args: dir_path (optional): The path to the directory with the files to be deprecated file (optional): The path to the single file to be deprecated. + filename_prefix(optional): A filename prefix, such as 'gb_rhsa_2021', + can be used to only deprecate certain VTs Returns: List of DeprecatedFile objects """ to_deprecate = [] - if file and re.match(FILENAME_REGEX, file.name): - to_deprecate.append( - DeprecatedFile( - file.name, - file.absolute(), - file.open("r", encoding="latin-1").read(), + filename_filter = re.compile(rf"{filename_prefix}") + if filename_prefix: + if file and re.match(filename_filter, file.name): + to_deprecate.append( + DeprecatedFile( + file.name, + file.absolute(), + file.open("r", encoding="latin-1").read(), + ) ) - ) + else: + valid_files = [ + file + for file in dir_path.glob("**/*") + if re.match(filename_filter, file.name) + ] + for file in valid_files: + to_deprecate.append( + DeprecatedFile( + file.name, + file.absolute(), + file.open("r", encoding="latin-1").read(), + ) + ) else: - valid_files = [ - file - for file in dir_path.glob("**/*") - if re.match(FILENAME_REGEX, file.name) - ] - for file in valid_files: + if file: to_deprecate.append( DeprecatedFile( file.name, @@ -127,6 +123,16 @@ def get_files(dir_path: Path = None, file: Path = None) -> list[DeprecatedFile]: file.open("r", encoding="latin-1").read(), ) ) + else: + for file in dir_path.glob("**/*"): + to_deprecate.append( + DeprecatedFile( + file.name, + file.absolute(), + file.open("r", encoding="latin-1").read(), + ) + ) + return to_deprecate @@ -142,7 +148,7 @@ def get_summary(content: str) -> Optional[str]: def deprecate( output_path: Path, to_deprecate: list[DeprecatedFile], - oid_mapping_path: Path = None, + deprecation_reason: str, ) -> None: """Deprecate the selected VTs by removing unnecessary keys, updating the summary, and adding the deprecated tag. @@ -151,9 +157,8 @@ def deprecate( output_path: the directory where the deprecated VTs should be written to, i.e. "attic" to_deprecate: the list of files to be deprecated - oid_mapping_path (optional) : the path to the file where the old - oids have been mapped to the new oids (see "transition_layer" - in notus-generator). + deprecation_reason: The reason this VT is being deprecated, + from a list of options. """ output_path.mkdir(parents=True, exist_ok=True) for file in to_deprecate: @@ -164,7 +169,7 @@ def deprecate( f"remaining." ) continue - file.content = update_summary(file, oid_mapping_path) + file.content = update_summary(file, deprecation_reason) file.content = finalize_content(file.content) # Drop any unnecessary script tags like script_dependencies(), @@ -218,7 +223,7 @@ def parse_args(args: Iterable[str] = None) -> Namespace: nargs="?", default=None, type=file_type, - help="single file to deprecate", + help="Single file to deprecate", ) parser.add_argument( "-i", @@ -227,16 +232,29 @@ def parse_args(args: Iterable[str] = None) -> Namespace: nargs="?", default=None, type=str, - help="Path to the existing nasl scripts", + help="Path to the existing nasl script directory", ) parser.add_argument( - "-m", - "--oid-mapping-path", - metavar="", + "-p", + "--filename-prefix", + metavar="", nargs="?", default=None, type=str, - help="Path to the oid mapping file", + help="The prefix of the files you would like to deprecate," + "for example 'gb_rhsa_2021' to filter on the year", + ) + parser.add_argument( + "-d", + "--deprecation-reason", + metavar="", + choices=["notus", "merged", "duplicate", "defunct"], + type=str, + help="The reason the VT is being deprecated. Options are 'notus':" + "The VT has been replaced by a new Notus VT. 'Merged': the VT has" + "been merged with another still active VT, 'duplicate': The VT has" + "a still active duplicate, 'defunct': The VT is no longer " + "functional.", ) return parser.parse_args(args) @@ -245,8 +263,9 @@ def main(): args = parse_args() output_path = Path(args.output_path) input_path = Path(args.input_path) if args.input_path else None - oid_mapping_path = args.oid_mapping_path if args.oid_mapping_path else None single_file = Path(args.file) if args.file else None + deprecation_reason = args.deprecation_reason + filename_prefix = args.filename_prefix if args.filename_prefix else None if not input_path and not single_file: raise PathException( @@ -256,9 +275,9 @@ def main(): if not input_path.is_dir(): raise PathException("Input path is not a directory.") - to_deprecate = get_files(input_path, single_file) + to_deprecate = get_files(input_path, single_file, filename_prefix) - deprecate(output_path, to_deprecate, oid_mapping_path) + deprecate(output_path, to_deprecate, deprecation_reason) if __name__ == "__main__":