Add SSH scan support with BSI TR-02102-4 compliance
- SSH scanning via ssh-audit (KEX, encryption, MAC, host keys) - BSI TR-02102-4 and IANA compliance validation for SSH - CSV/Markdown/reST reports for SSH results - Unified compliance schema and database views - Code optimization: modular query/writer architecture
This commit is contained in:
297
tests/reporter/test_csv_export.py
Normal file
297
tests/reporter/test_csv_export.py
Normal file
@@ -0,0 +1,297 @@
|
||||
"""Tests for CSV export functionality."""
|
||||
|
||||
import csv
|
||||
from pathlib import Path
|
||||
|
||||
from sslysze_scan.reporter.csv_export import generate_csv_reports
|
||||
|
||||
|
||||
class TestCsvExport:
|
||||
"""Tests for CSV file generation."""
|
||||
|
||||
def test_export_summary(self, test_db_path: str, tmp_path: Path) -> None:
|
||||
"""Test summary CSV export with aggregated statistics."""
|
||||
output_dir = tmp_path / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
files = generate_csv_reports(test_db_path, 1, str(output_dir))
|
||||
|
||||
summary_file = output_dir / "summary.csv"
|
||||
assert summary_file.exists()
|
||||
assert str(summary_file) in files
|
||||
|
||||
with open(summary_file, newline="", encoding="utf-8") as f:
|
||||
reader = csv.reader(f)
|
||||
rows = list(reader)
|
||||
|
||||
assert rows[0] == ["Metric", "Value"]
|
||||
assert len(rows) >= 7
|
||||
|
||||
metrics = {row[0]: row[1] for row in rows[1:]}
|
||||
assert "Scanned Ports" in metrics
|
||||
assert "Ports with TLS Support" in metrics
|
||||
assert "Cipher Suites Checked" in metrics
|
||||
|
||||
def test_export_cipher_suites_port_443(
|
||||
self, test_db_path: str, tmp_path: Path
|
||||
) -> None:
|
||||
"""Test cipher suites export for port 443."""
|
||||
output_dir = tmp_path / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
files = generate_csv_reports(test_db_path, 1, str(output_dir))
|
||||
|
||||
accepted_files = [
|
||||
f for f in files if "443_cipher_suites" in f and "accepted" in f
|
||||
]
|
||||
assert len(accepted_files) > 0
|
||||
|
||||
accepted_file = Path(accepted_files[0])
|
||||
assert accepted_file.exists()
|
||||
|
||||
with open(accepted_file, newline="", encoding="utf-8") as f:
|
||||
reader = csv.reader(f)
|
||||
rows = list(reader)
|
||||
|
||||
assert rows[0] == ["Cipher Suite", "IANA", "BSI", "Valid Until", "Compliant"]
|
||||
assert len(rows) > 1
|
||||
|
||||
for row in rows[1:]:
|
||||
assert len(row) == 5
|
||||
assert row[4] in ["Yes", "No", "-"]
|
||||
|
||||
def test_export_supported_groups_port_636(
|
||||
self, test_db_path: str, tmp_path: Path
|
||||
) -> None:
|
||||
"""Test supported groups export for port 636."""
|
||||
output_dir = tmp_path / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
files = generate_csv_reports(test_db_path, 1, str(output_dir))
|
||||
|
||||
groups_files = [f for f in files if "636_supported_groups.csv" in f]
|
||||
|
||||
if groups_files:
|
||||
groups_file = Path(groups_files[0])
|
||||
assert groups_file.exists()
|
||||
|
||||
with open(groups_file, newline="", encoding="utf-8") as f:
|
||||
reader = csv.reader(f)
|
||||
rows = list(reader)
|
||||
|
||||
assert rows[0] == ["Group", "IANA", "BSI", "Valid Until", "Compliant"]
|
||||
|
||||
for row in rows[1:]:
|
||||
assert len(row) == 5
|
||||
assert row[4] in ["Yes", "No", "-"]
|
||||
|
||||
def test_export_missing_groups_port_443(
|
||||
self, test_db_path: str, tmp_path: Path
|
||||
) -> None:
|
||||
"""Test missing groups export for port 443."""
|
||||
output_dir = tmp_path / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
files = generate_csv_reports(test_db_path, 1, str(output_dir))
|
||||
|
||||
bsi_files = [f for f in files if "443_missing_groups_bsi.csv" in f]
|
||||
|
||||
if bsi_files:
|
||||
bsi_file = Path(bsi_files[0])
|
||||
assert bsi_file.exists()
|
||||
|
||||
with open(bsi_file, newline="", encoding="utf-8") as f:
|
||||
reader = csv.reader(f)
|
||||
rows = list(reader)
|
||||
|
||||
assert rows[0] == ["Group", "TLS Versions", "Valid Until"]
|
||||
|
||||
for row in rows[1:]:
|
||||
assert len(row) == 3
|
||||
|
||||
def test_export_certificates_port_636(
|
||||
self, test_db_path: str, tmp_path: Path
|
||||
) -> None:
|
||||
"""Test certificates export for port 636."""
|
||||
output_dir = tmp_path / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
files = generate_csv_reports(test_db_path, 1, str(output_dir))
|
||||
|
||||
cert_files = [f for f in files if "636_certificates.csv" in f]
|
||||
|
||||
if cert_files:
|
||||
cert_file = Path(cert_files[0])
|
||||
assert cert_file.exists()
|
||||
|
||||
with open(cert_file, newline="", encoding="utf-8") as f:
|
||||
reader = csv.reader(f)
|
||||
rows = list(reader)
|
||||
|
||||
expected_headers = [
|
||||
"Position",
|
||||
"Subject",
|
||||
"Issuer",
|
||||
"Valid From",
|
||||
"Valid Until",
|
||||
"Key Type",
|
||||
"Key Size",
|
||||
"Compliant",
|
||||
]
|
||||
assert rows[0] == expected_headers
|
||||
|
||||
for row in rows[1:]:
|
||||
assert len(row) == 8
|
||||
assert row[7] in ["Yes", "No", "-"]
|
||||
|
||||
def test_export_vulnerabilities_port_443(
|
||||
self, test_db_path: str, tmp_path: Path
|
||||
) -> None:
|
||||
"""Test vulnerabilities export for port 443."""
|
||||
output_dir = tmp_path / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
files = generate_csv_reports(test_db_path, 1, str(output_dir))
|
||||
|
||||
vuln_files = [f for f in files if "443_vulnerabilities.csv" in f]
|
||||
|
||||
if vuln_files:
|
||||
vuln_file = Path(vuln_files[0])
|
||||
assert vuln_file.exists()
|
||||
|
||||
with open(vuln_file, newline="", encoding="utf-8") as f:
|
||||
reader = csv.reader(f)
|
||||
rows = list(reader)
|
||||
|
||||
assert rows[0] == ["Type", "Vulnerable", "Details"]
|
||||
|
||||
for row in rows[1:]:
|
||||
assert len(row) == 3
|
||||
assert row[1] in ["Yes", "No", "-"]
|
||||
|
||||
def test_export_protocol_features_port_636(
|
||||
self, test_db_path: str, tmp_path: Path
|
||||
) -> None:
|
||||
"""Test protocol features export for port 636."""
|
||||
output_dir = tmp_path / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
files = generate_csv_reports(test_db_path, 1, str(output_dir))
|
||||
|
||||
protocol_files = [f for f in files if "636_protocol_features.csv" in f]
|
||||
|
||||
if protocol_files:
|
||||
protocol_file = Path(protocol_files[0])
|
||||
assert protocol_file.exists()
|
||||
|
||||
with open(protocol_file, newline="", encoding="utf-8") as f:
|
||||
reader = csv.reader(f)
|
||||
rows = list(reader)
|
||||
|
||||
assert rows[0] == ["Feature", "Supported", "Details"]
|
||||
|
||||
for row in rows[1:]:
|
||||
assert len(row) == 3
|
||||
assert row[1] in ["Yes", "No", "-"]
|
||||
|
||||
def test_export_session_features_port_443(
|
||||
self, test_db_path: str, tmp_path: Path
|
||||
) -> None:
|
||||
"""Test session features export for port 443."""
|
||||
output_dir = tmp_path / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
files = generate_csv_reports(test_db_path, 1, str(output_dir))
|
||||
|
||||
session_files = [f for f in files if "443_session_features.csv" in f]
|
||||
|
||||
if session_files:
|
||||
session_file = Path(session_files[0])
|
||||
assert session_file.exists()
|
||||
|
||||
with open(session_file, newline="", encoding="utf-8") as f:
|
||||
reader = csv.reader(f)
|
||||
rows = list(reader)
|
||||
|
||||
expected_headers = [
|
||||
"Feature",
|
||||
"Client Initiated",
|
||||
"Secure",
|
||||
"Session ID",
|
||||
"TLS Ticket",
|
||||
"Details",
|
||||
]
|
||||
assert rows[0] == expected_headers
|
||||
|
||||
for row in rows[1:]:
|
||||
assert len(row) == 6
|
||||
for i in range(1, 5):
|
||||
assert row[i] in ["Yes", "No", "-"]
|
||||
|
||||
def test_export_http_headers_port_636(
|
||||
self, test_db_path: str, tmp_path: Path
|
||||
) -> None:
|
||||
"""Test HTTP headers export for port 636."""
|
||||
output_dir = tmp_path / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
files = generate_csv_reports(test_db_path, 1, str(output_dir))
|
||||
|
||||
header_files = [f for f in files if "636_http_headers.csv" in f]
|
||||
|
||||
if header_files:
|
||||
header_file = Path(header_files[0])
|
||||
assert header_file.exists()
|
||||
|
||||
with open(header_file, newline="", encoding="utf-8") as f:
|
||||
reader = csv.reader(f)
|
||||
rows = list(reader)
|
||||
|
||||
assert rows[0] == ["Header", "Present", "Value"]
|
||||
|
||||
for row in rows[1:]:
|
||||
assert len(row) == 3
|
||||
assert row[1] in ["Yes", "No", "-"]
|
||||
|
||||
def test_export_compliance_status_port_443(
|
||||
self, test_db_path: str, tmp_path: Path
|
||||
) -> None:
|
||||
"""Test compliance status export for port 443."""
|
||||
output_dir = tmp_path / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
files = generate_csv_reports(test_db_path, 1, str(output_dir))
|
||||
|
||||
compliance_files = [f for f in files if "443_compliance_status.csv" in f]
|
||||
|
||||
if compliance_files:
|
||||
compliance_file = Path(compliance_files[0])
|
||||
assert compliance_file.exists()
|
||||
|
||||
with open(compliance_file, newline="", encoding="utf-8") as f:
|
||||
reader = csv.reader(f)
|
||||
rows = list(reader)
|
||||
|
||||
assert rows[0] == ["Category", "Checked", "Compliant", "Percentage"]
|
||||
|
||||
for row in rows[1:]:
|
||||
assert len(row) == 4
|
||||
assert "%" in row[3]
|
||||
|
||||
def test_generate_csv_reports_all_files(
|
||||
self, test_db_path: str, tmp_path: Path
|
||||
) -> None:
|
||||
"""Test that generate_csv_reports creates expected files."""
|
||||
output_dir = tmp_path / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
files = generate_csv_reports(test_db_path, 1, str(output_dir))
|
||||
|
||||
assert len(files) > 0
|
||||
assert any("summary.csv" in f for f in files)
|
||||
assert any("443_" in f for f in files)
|
||||
assert any("636_" in f for f in files)
|
||||
|
||||
for file_path in files:
|
||||
assert Path(file_path).exists()
|
||||
assert Path(file_path).suffix == ".csv"
|
||||
Reference in New Issue
Block a user