Skip to content

Commit

Permalink
[#487] Refactor JSON output logic to switch from TypedDict to pydantic
Browse files Browse the repository at this point in the history
Fix some tests

Fix tests

Add tests and JSON schema

Fix requirements

Fix imports

Update documentation
  • Loading branch information
nabla-c0d3 committed Apr 18, 2021
1 parent 3a59a75 commit 3a9df3a
Show file tree
Hide file tree
Showing 63 changed files with 20,022 additions and 1,007 deletions.
64 changes: 43 additions & 21 deletions api_sample.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from pathlib import Path

from sslyze import (
ServerNetworkLocationViaDirectConnection,
ServerConnectivityTester,
Scanner,
ServerScanRequest,
ScanCommand,
SslyzeOutputAsJson,
)
from sslyze.errors import ConnectionToServerFailed

Expand Down Expand Up @@ -36,29 +39,21 @@ def main() -> None:
print(f"\nResults for {server_scan_result.server_info.server_location.hostname}:")

# Scan commands that were run with no errors
try:
ssl2_result = server_scan_result.scan_commands_results[ScanCommand.SSL_2_0_CIPHER_SUITES]
ssl2_result = server_scan_result.scan_commands_results.ssl_2_0_cipher_suites
if ssl2_result:
print("\nAccepted cipher suites for SSL 2.0:")
for accepted_cipher_suite in ssl2_result.accepted_cipher_suites:
print(f"* {accepted_cipher_suite.cipher_suite.name}")
except KeyError:
pass

try:
certinfo_result = server_scan_result.scan_commands_results[ScanCommand.CERTIFICATE_INFO]
certinfo_result = server_scan_result.scan_commands_results.certificate_info
if certinfo_result:
print("\nCertificate info:")
for cert_deployment in certinfo_result.certificate_deployments:
print(f"Leaf certificate: \n{cert_deployment.received_certificate_chain_as_pem[0]}")
except KeyError:
pass

# Scan commands that were run with errors
for scan_command, error in server_scan_result.scan_commands_errors.items():
print(f"\nError when running {scan_command}:\n{error.exception_trace}")


if __name__ == "__main__":
main()
for scan_command_error in server_scan_result.scan_commands_errors:
print(f"\nError when running {scan_command_error.scan_command}:\n{scan_command_error.exception_trace}")


def basic_example_connectivity_testing() -> None:
Expand Down Expand Up @@ -99,13 +94,40 @@ def basic_example() -> None:
print(f"\nResults for {server_scan_result.server_info.server_location.hostname}:")

# SSL 2.0 results
ssl2_result = server_scan_result.scan_commands_results[ScanCommand.SSL_2_0_CIPHER_SUITES]
print("\nAccepted cipher suites for SSL 2.0:")
for accepted_cipher_suite in ssl2_result.accepted_cipher_suites:
print(f"* {accepted_cipher_suite.cipher_suite.name}")
ssl2_result = server_scan_result.scan_commands_results.ssl_2_0_cipher_suites
if ssl2_result:
print("\nAccepted cipher suites for SSL 2.0:")
for accepted_cipher_suite in ssl2_result.accepted_cipher_suites:
print(f"* {accepted_cipher_suite.cipher_suite.name}")

# Certificate info results
certinfo_result = server_scan_result.scan_commands_results[ScanCommand.CERTIFICATE_INFO]
print("\nCertificate info:")
certinfo_result = server_scan_result.scan_commands_results.certificate_info
if certinfo_result:
print("\nCertificate info:")
for cert_deployment in certinfo_result.certificate_deployments:
print(f"Leaf certificate: \n{cert_deployment.received_certificate_chain_as_pem[0]}")


def example_json_result_parsing() -> None:
# SSLyze scan results serialized to JSON were saved to this file using --json_out
results_as_json_file = Path(__file__).parent / "tests" / "cli_tests" / "sslyze_output.json"
results_as_json = results_as_json_file.read_text()

# These results can be parsed
parsed_results = SslyzeOutputAsJson.parse_raw(results_as_json)

# Making it easy to do post-processing and inspection of the results
print("The following servers were scanned:")
for server_scan_result in parsed_results.server_scan_results:
print(f" {server_scan_result.server_info.server_location}")

certinfo_result = server_scan_result.scan_commands_results.certificate_info
if not certinfo_result:
raise RuntimeError("Should never happen")
for cert_deployment in certinfo_result.certificate_deployments:
print(f"Leaf certificate: \n{cert_deployment.received_certificate_chain_as_pem[0]}")
print(f" SHA1 of leaf certificate: {cert_deployment.received_certificate_chain[0].fingerprint_sha1}")
print("")


if __name__ == "__main__":
main()
15 changes: 2 additions & 13 deletions docs/available-scan-commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Certificate Information
Optional arguments
==================

.. autoclass:: CertificateInfoExtraArguments
.. autoclass:: CertificateInfoExtraArgument

Result class
============
Expand Down Expand Up @@ -96,19 +96,9 @@ Result class
============

.. autoclass:: TlsResumptionSupportEnum
.. autoclass:: SessionResumptionSupportExtraArguments
.. autoclass:: SessionResumptionSupportExtraArgument
.. autoclass:: SessionResumptionSupportScanResult

Session Resumption Rate
***********************

**ScanCommand.SESSION_RESUMPTION_RATE**: Measure a server's session resumption rate when attempting 100 resumptions using session IDs.

Result class
============

.. autoclass:: SessionResumptionRateScanResult

CRIME
*****

Expand Down Expand Up @@ -159,7 +149,6 @@ Result class

.. autoclass:: HttpHeadersScanResult
.. autoclass:: StrictTransportSecurityHeader
.. autoclass:: PublicKeyPinsHeader
.. autoclass:: ExpectCtHeader

OpenSSL CCS Injection
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = []
html_static_path = [] # type: ignore


# -- Options for HTMLHelp output ------------------------------------------
Expand Down
15 changes: 13 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ A full description of the supported options is available via the help command::

$ python -m sslyze -h

Runing scans with the Python API
********************************
Running scans with the Python API
*********************************

The Python API gives full access to SSLyze's scanning engine in order to make it easy to implement SSL/TLS scanning as
part of a continuous security testing platform, and detect any misconfiguration across a range of public and/or internal
Expand Down Expand Up @@ -95,6 +95,17 @@ Using the Python API to scan a server is a two-step process, described in more d
testing-connectivity
running-scan-commands

Exporting and processing scan results in JSON
*********************************************

The result of SSLyze scans can be serialized to JSON for further processing. SSLyze also provides a helper class to
parse JSON scan results; it can be used to process the results of SSLyze scans in a separate Python program.

.. toctree::
:maxdepth: 2

json-output

Indices and tables
******************

Expand Down
25 changes: 25 additions & 0 deletions docs/json-output.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Exporting and processing scan results in JSON
#############################################

The result of SSLyze scans can be serialized to JSON for further processing. SSLyze also provides a helper class to
parse JSON scan results; it can be used to process the results of SSLyze scans in a separate Python program.

A schema of the JSON output is available in the code repository at
`./json_output_schema.json <https://github.com/nabla-c0d3/sslyze/blob/release/json_output_schema.json>`_.

JSON output when using the CLI
******************************

When using the CLI, the scan results can be exported to a JSON file using the ``--json_out`` option::

$ python -m sslyze www.google.com www.facebook.com --json_out=result.json

The generated JSON file can then be parsed using the ``SslyzeOutputAsJson.from_file()`` method:

.. literalinclude:: ../api_sample.py
:pyobject: example_json_result_parsing

The resulting Python object then contains the scan results. Type annotations are available for all fields, thereby
making it easier to process the results.

.. autoclass:: SslyzeOutputAsJson
12 changes: 2 additions & 10 deletions docs/running-scan-commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,11 @@ Related Classes
.. autoclass:: ServerScanRequest

.. autoclass:: ServerScanResult
.. autoclass:: ScanCommandResultsDict
.. autoclass:: ScanCommandsResults
:undoc-members:
:members:

.. autoclass:: ScanCommandErrorsDict
.. autoclass:: ScanCommandError
.. autoclass:: ScanCommandErrorReasonEnum
:undoc-members:
:members:


Exporting to JSON
*****************

A ``ServerScanResult`` can be serialized to JSON using SSLyze's special ``JsonEncoder``.

.. autoclass:: JsonEncoder
Loading

0 comments on commit 3a9df3a

Please sign in to comment.