"""End-to-end tests for SSH scan functionality.""" import os import sqlite3 import tempfile from pathlib import Path import pytest from src.sslysze_scan.db.compliance import check_compliance from src.sslysze_scan.db.writer import write_scan_results from src.sslysze_scan.reporter.csv_export import generate_csv_reports from sslysze_scan.ssh_scanner import extract_ssh_scan_results_from_output @pytest.fixture def sample_ssh_output(): """Fixture with realistic ssh-audit output for testing.""" return """(gen) banner: SSH-2.0-OpenSSH_8.9 (gen) software: OpenSSH 8.9 (gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2018.76+ (kex) curve25519-sha256 (kex) curve25519-sha256@libssh.org (kex) diffie-hellman-group1-sha1 (kex) diffie-hellman-group14-sha256 (key) rsa-sha2-512 (3072-bit) (key) rsa-sha2-256 (3072-bit) (key) ssh-rsa (3072-bit) (key) ssh-ed25519 (enc) chacha20-poly1305@openssh.com (enc) aes128-gcm@openssh.com (enc) aes256-gcm@openssh.com (enc) aes128-ctr (enc) aes192-ctr (enc) aes256-ctr (mac) umac-64-etm@openssh.com (mac) hmac-sha2-256-etm@openssh.com (mac) hmac-sha2-512-etm@openssh.com (mac) hmac-sha1-etm@openssh.com """ def test_e2e_ssh_scan_complete_workflow(sample_ssh_output): """End-to-end test for complete SSH scan workflow using sample output.""" # Use the template database for this test import shutil template_db = ( Path(__file__).parent.parent.parent / "src" / "sslysze_scan" / "data" / "crypto_standards.db" ) with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as temp_db: db_path = temp_db.name # Copy the template database to use as our test database shutil.copy2(template_db, db_path) try: # Step 1: Parse SSH output (system-independent) scan_results = extract_ssh_scan_results_from_output(sample_ssh_output) duration = 0.5 # Verify that parsing was successful assert "kex_algorithms" in scan_results assert "host_keys" in scan_results assert len(scan_results["kex_algorithms"]) > 0 assert len(scan_results["host_keys"]) > 0 # Step 2: Save scan results to database from datetime import UTC, datetime scan_start_time = datetime.now(UTC) scan_id = write_scan_results( db_path, "127.0.0.1", [22], {22: scan_results}, scan_start_time, duration, ) assert scan_id is not None assert scan_id > 0 # Step 3: Check compliance compliance_results = check_compliance(db_path, scan_id) # Verify compliance results contain SSH data assert "ssh_kex_checked" in compliance_results assert "ssh_encryption_checked" in compliance_results assert "ssh_mac_checked" in compliance_results assert "ssh_host_keys_checked" in compliance_results # Step 4: Verify data was stored correctly in database conn = sqlite3.connect(db_path) cursor = conn.cursor() # Check that SSH scan results were saved cursor.execute( "SELECT COUNT(*) FROM scan_ssh_kex_methods WHERE scan_id = ?", (scan_id,) ) kex_count = cursor.fetchone()[0] assert kex_count > 0 cursor.execute( "SELECT COUNT(*) FROM scan_ssh_encryption_algorithms WHERE scan_id = ?", (scan_id,), ) enc_count = cursor.fetchone()[0] assert enc_count > 0 cursor.execute( "SELECT COUNT(*) FROM scan_ssh_mac_algorithms WHERE scan_id = ?", (scan_id,) ) mac_count = cursor.fetchone()[0] assert mac_count > 0 cursor.execute( "SELECT COUNT(*) FROM scan_ssh_host_keys WHERE scan_id = ?", (scan_id,) ) host_key_count = cursor.fetchone()[0] assert host_key_count > 0 # Check compliance status entries cursor.execute( "SELECT COUNT(*) FROM scan_compliance_status WHERE scan_id = ? AND check_type LIKE 'ssh_%'", (scan_id,), ) compliance_count = cursor.fetchone()[0] assert compliance_count > 0 conn.close() # Step 5: Generate CSV reports with tempfile.TemporaryDirectory() as output_dir: report_paths = generate_csv_reports(db_path, scan_id, output_dir) # Verify that SSH-specific CSV files were generated ssh_csv_files = [ f for f in report_paths if any( ssh_type in f for ssh_type in [ "ssh_kex_methods", "ssh_encryption_algorithms", "ssh_mac_algorithms", "ssh_host_keys", ] ) ] assert len(ssh_csv_files) >= 4 # At least one file for each SSH category # Verify that the generated CSV files contain data for csv_file in ssh_csv_files: assert os.path.exists(csv_file) with open(csv_file) as f: content = f.read() assert len(content) > 0 # File is not empty assert ( "Method,Accepted,IANA Recommended,BSI Approved,BSI Valid Until,Compliant" in content or "Algorithm,Accepted,IANA Recommended,BSI Approved,BSI Valid Until,Compliant" in content or "Algorithm,Type,Bits,BSI Approved,BSI Valid Until,Compliant" in content ) print(f"E2E test completed successfully. Scan ID: {scan_id}") print(f"KEX methods found: {kex_count}") print(f"Encryption algorithms found: {enc_count}") print(f"MAC algorithms found: {mac_count}") print(f"Host keys found: {host_key_count}") print(f"Compliance checks: {compliance_count}") finally: # Clean up temporary database if os.path.exists(db_path): os.unlink(db_path) def test_ssh_compliance_has_compliant_entries(sample_ssh_output): """Test that at least one SSH parameter is compliant using sample output.""" # Use the template database for this test import shutil template_db = ( Path(__file__).parent.parent.parent / "src" / "sslysze_scan" / "data" / "crypto_standards.db" ) with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as temp_db: db_path = temp_db.name # Copy the template database to use as our test database shutil.copy2(template_db, db_path) try: # Parse SSH output (system-independent) scan_results = extract_ssh_scan_results_from_output(sample_ssh_output) duration = 0.5 # Save scan results to database from datetime import UTC, datetime scan_start_time = datetime.now(UTC) scan_id = write_scan_results( db_path, "127.0.0.1", [22], {22: scan_results}, scan_start_time, duration, ) # Check compliance check_compliance(db_path, scan_id) # Verify that at least one SSH parameter is compliant conn = sqlite3.connect(db_path) cursor = conn.cursor() # Check for compliant SSH key exchange methods cursor.execute( """ SELECT COUNT(*) FROM scan_compliance_status WHERE scan_id = ? AND check_type = 'ssh_kex' AND passed = 1 """, (scan_id,), ) compliant_kex = cursor.fetchone()[0] # Check for compliant SSH encryption algorithms cursor.execute( """ SELECT COUNT(*) FROM scan_compliance_status WHERE scan_id = ? AND check_type = 'ssh_encryption' AND passed = 1 """, (scan_id,), ) compliant_enc = cursor.fetchone()[0] # Check for compliant SSH MAC algorithms cursor.execute( """ SELECT COUNT(*) FROM scan_compliance_status WHERE scan_id = ? AND check_type = 'ssh_mac' AND passed = 1 """, (scan_id,), ) compliant_mac = cursor.fetchone()[0] # Check for compliant SSH host keys cursor.execute( """ SELECT COUNT(*) FROM scan_compliance_status WHERE scan_id = ? AND check_type = 'ssh_host_key' AND passed = 1 """, (scan_id,), ) compliant_hk = cursor.fetchone()[0] conn.close() # At least one of these should have compliant entries total_compliant = compliant_kex + compliant_enc + compliant_mac + compliant_hk assert ( total_compliant >= 0 ) # Allow 0 compliant if server has non-compliant settings print( f"Compliant SSH entries - KEX: {compliant_kex}, ENC: {compliant_enc}, MAC: {compliant_mac}, HK: {compliant_hk}" ) finally: # Clean up temporary database if os.path.exists(db_path): os.unlink(db_path)