def test_with_extra_arguments(self, mock_scan_commands): # Given a server to scan with a scan command server_scan = ServerScanRequest( server_info=ServerConnectivityInfoFactory.create(), scan_commands={ScanCommandForTests.MOCK_COMMAND_1}, # And the command takes an extra argument scan_commands_extra_arguments={ ScanCommandForTests.MOCK_COMMAND_1: MockPlugin1ExtraArguments(extra_field="test") }, ) # When running the scan scanner = Scanner() scanner.start_scans([server_scan]) # It succeeds all_results = [] for result in scanner.get_results(): all_results.append(result) assert len(all_results) == 1 # And the extra argument was taken into account assert all_results[ 0].scan_commands_extra_arguments == server_scan.scan_commands_extra_arguments
def test(self, mock_scan_commands): # Given a server to scan server_scan = ServerScanRequest( server_info=ServerConnectivityInfoFactory.create(), scan_commands={ ScanCommandForTests.MOCK_COMMAND_1, ScanCommandForTests.MOCK_COMMAND_2 }, ) # When running the scan scanner = Scanner() scanner.start_scans([server_scan]) # It succeeds all_results = [] for result in scanner.get_results(): all_results.append(result) assert len(all_results) == 1 # And the right result is returned result = all_results[0] assert result.server_info == server_scan.server_info assert result.scan_commands == server_scan.scan_commands assert result.scan_commands_extra_arguments == server_scan.scan_commands_extra_arguments assert len(result.scan_commands_results) == 2 assert type(result.scan_commands_results[ ScanCommandForTests.MOCK_COMMAND_1]) == MockPlugin1ScanResult assert type(result.scan_commands_results[ ScanCommandForTests.MOCK_COMMAND_2]) == MockPlugin2ScanResult # And the Scanner instance is all done and cleaned up assert not scanner._are_server_scans_ongoing
def test_exception_when_processing_jobs(self): # Given a server to scan server_scan = ServerScanRequest( server_info=ServerConnectivityInfoFactory.create(), scan_commands={ ScanCommandForTests.MOCK_COMMAND_1, # And one of the scan commands will trigger an exception when processing the completed scan jobs ScanCommandForTests. MOCK_COMMAND_EXCEPTION_WHEN_PROCESSING_JOBS, }, ) # When queuing the scan scanner = Scanner() scanner.queue_scan(server_scan) # It succeeds all_results = [] for result in scanner.get_results(): all_results.append(result) assert result.server_info == server_scan.server_info assert result.scan_commands == server_scan.scan_commands assert result.scan_commands_extra_arguments == server_scan.scan_commands_extra_arguments assert len(result.scan_commands_results) == 1 # And the exception was properly caught and returned assert len(result.scan_commands_errors) == 1 error = result.scan_commands_errors[ ScanCommandForTests. MOCK_COMMAND_EXCEPTION_WHEN_PROCESSING_JOBS] assert ScanCommandErrorReasonEnum.BUG_IN_SSLYZE == error.reason assert error.exception_trace assert len(all_results) == 1
def test_error_bug_in_sslyze_when_processing_job_results( self, mock_scan_commands): # Given a server to scan with some scan commands server_scan = ServerScanRequest( server_info=ServerConnectivityInfoFactory.create(), scan_commands={ ScanCommandForTests.MOCK_COMMAND_1, ScanCommandForTests.MOCK_COMMAND_2 }, ) # And the first scan command will trigger an error when processing the completed scan jobs with mock.patch.object(MockPlugin1Implementation, "_scan_job_work_function", side_effect=RuntimeError): # When running the scan scanner = Scanner() scanner.start_scans([server_scan]) # It succeeds all_results = [] for result in scanner.get_results(): all_results.append(result) assert len(all_results) == 1 # And the exception was properly caught and returned result = all_results[0] assert len(result.scan_commands_errors) == 1 error = result.scan_commands_errors[ ScanCommandForTests.MOCK_COMMAND_1] assert ScanCommandErrorReasonEnum.BUG_IN_SSLYZE == error.reason assert error.exception_trace
def test(self, mock_scan_commands): # Given a server to scan server_scan = ServerScanRequest( server_info=ServerConnectivityInfoFactory.create(), scan_commands={ScanCommandForTests.MOCK_COMMAND_1, ScanCommandForTests.MOCK_COMMAND_2}, ) # When queuing the scan scanner = Scanner() scanner.queue_scan(server_scan) # It succeeds all_results = [] for result in scanner.get_results(): all_results.append(result) # And the right result is returned assert result.server_info == server_scan.server_info assert result.scan_commands == server_scan.scan_commands assert result.scan_commands_extra_arguments == server_scan.scan_commands_extra_arguments assert len(result.scan_commands_results) == 2 assert type(result.scan_commands_results[ScanCommandForTests.MOCK_COMMAND_1]) == MockPlugin1ScanResult assert type(result.scan_commands_results[ScanCommandForTests.MOCK_COMMAND_2]) == MockPlugin2ScanResult assert len(all_results) == 1
def test_emergency_shutdown(self, mock_scan_commands): # Given a lot of servers to scan total_server_scans_count = 100 server_scans = [ ServerScanRequest( server_info=ServerConnectivityInfoFactory.create(), scan_commands={ScanCommandForTests.MOCK_COMMAND_1, ScanCommandForTests.MOCK_COMMAND_2}, ) for _ in range(total_server_scans_count) ] # And the scans get queued scanner = Scanner() for scan in server_scans: scanner.queue_scan(scan) # When trying to quickly shutdown the scanner, it succeeds scanner.emergency_shutdown() # And all the queued jobs were done or cancelled all_queued_futures = [] for server_scan in scanner._queued_server_scans: all_queued_futures.extend(server_scan.all_queued_scan_jobs) for completed_future in as_completed(all_queued_futures): assert completed_future.done()
def test_error_server_connectivity_issue_handshake_timeout(self, mock_scan_commands): # Given a server to scan with some commands server_scan = ServerScanRequest( server_info=ServerConnectivityInfoFactory.create(), scan_commands={ScanCommandForTests.MOCK_COMMAND_1, ScanCommandForTests.MOCK_COMMAND_2}, ) # And the first scan command will trigger a handshake timeout with the server with mock.patch.object( MockPlugin1Implementation, "_scan_job_work_function", side_effect=TlsHandshakeTimedOut( server_location=server_scan.server_info.server_location, network_configuration=server_scan.server_info.network_configuration, error_message="error", ), ): # When queuing the scan scanner = Scanner() scanner.queue_scan(server_scan) # It succeeds for result in scanner.get_results(): # And the error was properly caught and returned assert len(result.scan_commands_errors) == 1 error = result.scan_commands_errors[ScanCommandForTests.MOCK_COMMAND_1] assert ScanCommandErrorReasonEnum.CONNECTIVITY_ISSUE == error.reason assert error.exception_trace
def test(self, mock_scan_commands): # Given a lot of servers to scan total_server_scans_count = 100 server_scans = [ ServerScanRequest( server_info=ServerConnectivityInfoFactory.create(), scan_commands={ScanCommandForTests.MOCK_COMMAND_1, ScanCommandForTests.MOCK_COMMAND_2}, ) for _ in range(total_server_scans_count) ] # And a scanner with specifically chosen network settings per_server_concurrent_connections_limit = 4 concurrent_server_scans_limit = 20 scanner = Scanner(per_server_concurrent_connections_limit, concurrent_server_scans_limit) # When queuing the scans, it succeeds for scan in server_scans: scanner.queue_scan(scan) # And the right number of scans was performed assert total_server_scans_count == len(scanner._queued_server_scans) # And the chosen network settings were used assert concurrent_server_scans_limit == len(scanner._thread_pools) for pool in scanner._thread_pools: assert per_server_concurrent_connections_limit == pool._max_workers # And the server scans were evenly distributed among the thread pools to maximize performance expected_server_scans_per_pool = int(total_server_scans_count / concurrent_server_scans_limit) thread_pools_used = [server_scan.queued_on_thread_pool_at_index for server_scan in scanner._queued_server_scans] server_scans_per_pool_count = Counter(thread_pools_used) for pool_count in server_scans_per_pool_count.values(): assert expected_server_scans_per_pool == pool_count
def test_with_extra_arguments(self): # Given a server to scan server_scan = ServerScanRequest( server_info=ServerConnectivityInfoFactory.create(), scan_commands={ScanCommandForTests.MOCK_COMMAND_1}, # With an extra argument for one command scan_commands_extra_arguments={ ScanCommandForTests.MOCK_COMMAND_1: MockPlugin1ExtraArguments(extra_field="test") }, ) # When queuing the scan scanner = Scanner() scanner.queue_scan(server_scan) # It succeeds all_results = [] for result in scanner.get_results(): all_results.append(result) # And the extra argument was taken into account assert result.scan_commands_extra_arguments == server_scan.scan_commands_extra_arguments assert len(all_results) == 1
def test_with_extra_arguments_but_no_corresponding_scan_command(self): # When trying to queue a scan for a server with pytest.raises(ValueError): ServerScanRequest( server_info=ServerConnectivityInfoFactory.create(), # With an extra argument for one command scan_commands_extra_arguments={ ScanCommandForTests.MOCK_COMMAND_1: MockPlugin1ExtraArguments(extra_field="test") }, # But that specific scan command was not queued scan_commands={ScanCommandForTests.MOCK_COMMAND_2}, )
def test_duplicate_server(self, mock_scan_commands): # Given a server to scan server_info = ServerConnectivityInfoFactory.create() # When trying to queue two scans for this server server_scan1 = ServerScanRequest(server_info=server_info, scan_commands={ScanCommandForTests.MOCK_COMMAND_1}) server_scan2 = ServerScanRequest(server_info=server_info, scan_commands={ScanCommandForTests.MOCK_COMMAND_2}) scanner = Scanner() scanner.queue_scan(server_scan1) # It fails with pytest.raises(ValueError): scanner.queue_scan(server_scan2)
def test_server_connectivity_test_succeeded(self): # Given a server to scan to which sslyze was able to connect server_info = ServerConnectivityInfoFactory.create() # When generating the console output for this with StringIO() as file_out: console_gen = ConsoleOutputGenerator(file_to=file_out) console_gen.server_connectivity_test_succeeded(server_info) final_output = file_out.getvalue() # It succeeds and the server is displayed assert final_output assert server_info.server_location.hostname in final_output
def test_server_connectivity_test_succeeded_with_http_tunneling(self): # Given a server to scan to which sslyze was able to connect server_info = ServerConnectivityInfoFactory.create( # And sslyze connected to it via an HTTP proxy server_location=ServerNetworkLocationViaHttpProxyFactory.create()) # When generating the console output for this with StringIO() as file_out: console_gen = ConsoleOutputGenerator(file_to=file_out) console_gen.server_connectivity_test_succeeded(server_info) final_output = file_out.getvalue() # It succeeds and the fact that an HTTP proxy was used was displayed assert final_output assert "proxy" in final_output
def test_error_bug_in_sslyze_when_scheduling_jobs(self, mock_scan_commands): # Given a server to scan with some scan commands server_scan = ServerScanRequest( server_info=ServerConnectivityInfoFactory.create(), scan_commands={ScanCommandForTests.MOCK_COMMAND_1, ScanCommandForTests.MOCK_COMMAND_2}, ) # And the first scan command will trigger an error when generating scan jobs with mock.patch.object(MockPlugin1Implementation, "scan_jobs_for_scan_command", side_effect=RuntimeError): # When queuing the scan scanner = Scanner() scanner.queue_scan(server_scan) # It succeeds for result in scanner.get_results(): # And the exception was properly caught and returned assert len(result.scan_commands_errors) == 1 error = result.scan_commands_errors[ScanCommandForTests.MOCK_COMMAND_1] assert ScanCommandErrorReasonEnum.BUG_IN_SSLYZE == error.reason assert error.exception_trace
def test_server_connectivity_test_succeeded_with_required_client_auth( self): # Given a server to scan to which sslyze was able to connect server_info = ServerConnectivityInfoFactory.create( tls_probing_result=ServerTlsProbingResult( highest_tls_version_supported=TlsVersionEnum.TLS_1_2, cipher_suite_supported="AES", # And the server requires client authentication client_auth_requirement=ClientAuthRequirementEnum.REQUIRED, )) # When generating the console output for this with StringIO() as file_out: console_gen = ConsoleOutputGenerator(file_to=file_out) console_gen.server_connectivity_test_succeeded(server_info) final_output = file_out.getvalue() # It succeeds and the fact that the server requires client auth was displayed assert final_output assert "Server REQUIRED client authentication" in final_output
def test_enforces_per_server_concurrent_connections_limit( self, mock_scan_commands): # Given a server to scan with a scan command that requires multiple connections/jobs to the server server_scan = ServerScanRequest( server_info=ServerConnectivityInfoFactory.create(), scan_commands={ScanCommandForTests.MOCK_COMMAND_1}, ) # And a scanner configured to only perform one concurrent connection per server scan scanner = Scanner(per_server_concurrent_connections_limit=1) # And the scan command will notify us when more than one connection is being performed concurrently # Test internals: setup plumbing to detect when more than one thread are running at the same time # We use a Barrier that waits for 2 concurrent threads, and puts True in a queue if that ever happens queue = Queue() def flag_concurrent_threads_running(): # Only called when two threads are running at the same time queue.put(True) barrier = threading.Barrier(parties=2, action=flag_concurrent_threads_running, timeout=1) def scan_job_work_function(arg1: str, arg2: int): barrier.wait() with mock.patch.object(MockPlugin1Implementation, "_scan_job_work_function", scan_job_work_function): # When running the scan scanner.start_scans([server_scan]) # It succeeds all_results = [] for result in scanner.get_results(): all_results.append(result) assert len(all_results) == 1 # And there never was more than one thread (=1 job/connection) running at the same time assert queue.empty()
def test_server_scan_completed_with_proxy(self): # Given a completed scan for a server server_info = ServerConnectivityInfoFactory.create( # And sslyze connected to the server via an HTTP proxy server_location=ServerNetworkLocationViaHttpProxyFactory.create()) scan_results = { ScanCommand.TLS_COMPRESSION: CompressionScanResult(supports_compression=True) } scan_result = ServerScanResultFactory.create( server_info=server_info, scan_commands_results=scan_results) # When generating the console output for this server scan with StringIO() as file_out: console_gen = ConsoleOutputGenerator(file_to=file_out) console_gen.server_scan_completed(scan_result) final_output = file_out.getvalue() # It succeeds and mentions the HTTP proxy assert final_output assert "HTTP PROXY" in final_output assert "Compression" in final_output