diff --git a/CHANGELOG.md b/CHANGELOG.md index e2e0b9a..3564c4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Upcoming changes... +## [1.25.0] - 2025-06-04 +### Added +- Add `grpc-ssl-target` option to CLI to override SSL target name for gRPC connections + ## [1.24.0] - 2025-05-28 ### Added - Add `crypto` subcommand to retrieve cryptographic algorithms for the given components @@ -522,4 +526,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [1.21.0]: https://github.com/scanoss/scanoss.py/compare/v1.20.6...v1.21.0 [1.22.0]: https://github.com/scanoss/scanoss.py/compare/v1.21.0...v1.22.0 [1.23.0]: https://github.com/scanoss/scanoss.py/compare/v1.22.0...v1.23.0 -[1.24.0]: https://github.com/scanoss/scanoss.py/compare/v1.23.0...v1.24.0 \ No newline at end of file +[1.24.0]: https://github.com/scanoss/scanoss.py/compare/v1.23.0...v1.24.0 +[1.25.0]: https://github.com/scanoss/scanoss.py/compare/v1.24.0...v1.25.0 diff --git a/src/scanoss/__init__.py b/src/scanoss/__init__.py index 228a61a..629fead 100644 --- a/src/scanoss/__init__.py +++ b/src/scanoss/__init__.py @@ -22,4 +22,4 @@ THE SOFTWARE. """ -__version__ = '1.24.0' +__version__ = '1.25.0' diff --git a/src/scanoss/cli.py b/src/scanoss/cli.py index ec88177..e85c2c5 100644 --- a/src/scanoss/cli.py +++ b/src/scanoss/cli.py @@ -761,6 +761,12 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915 '"REQUESTS_CA_BUNDLE=/path/to/cacert.pem" and ' '"GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/path/to/cacert.pem" for gRPC', ) + p.add_argument( + '--grpc-ssl-target', + type=str, + help='Override SSL target name for gRPC connections (optional). ' + 'Useful when connecting to localhost with a certificate issued for a different domain.', + ) # Global GRPC options for p in [ @@ -1138,6 +1144,7 @@ def scan(parser, args): # noqa: PLR0912, PLR0915 ignore_cert_errors=args.ignore_cert_errors, proxy=args.proxy, grpc_proxy=args.grpc_proxy, + grpc_ssl_target=args.grpc_ssl_target, pac=pac_file, ca_cert=args.ca_cert, retry=args.retry, @@ -1617,6 +1624,7 @@ def comp_vulns(parser, args): ca_cert=args.ca_cert, proxy=args.proxy, grpc_proxy=args.grpc_proxy, + grpc_ssl_target=args.grpc_ssl_target, pac=pac_file, timeout=args.timeout, req_headers=process_req_headers(args.header), @@ -1652,6 +1660,7 @@ def comp_semgrep(parser, args): ca_cert=args.ca_cert, proxy=args.proxy, grpc_proxy=args.grpc_proxy, + grpc_ssl_target=args.grpc_ssl_target, pac=pac_file, timeout=args.timeout, req_headers=process_req_headers(args.header), @@ -1690,6 +1699,7 @@ def comp_search(parser, args): ca_cert=args.ca_cert, proxy=args.proxy, grpc_proxy=args.grpc_proxy, + grpc_ssl_target=args.grpc_ssl_target, pac=pac_file, timeout=args.timeout, req_headers=process_req_headers(args.header), @@ -1735,6 +1745,7 @@ def comp_versions(parser, args): ca_cert=args.ca_cert, proxy=args.proxy, grpc_proxy=args.grpc_proxy, + grpc_ssl_target=args.grpc_ssl_target, pac=pac_file, timeout=args.timeout, req_headers=process_req_headers(args.header), @@ -1770,6 +1781,7 @@ def comp_provenance(parser, args): ca_cert=args.ca_cert, proxy=args.proxy, grpc_proxy=args.grpc_proxy, + grpc_ssl_target=args.grpc_ssl_target, pac=pac_file, timeout=args.timeout, req_headers=process_req_headers(args.header), diff --git a/src/scanoss/components.py b/src/scanoss/components.py index c68a233..d43c900 100644 --- a/src/scanoss/components.py +++ b/src/scanoss/components.py @@ -50,6 +50,7 @@ def __init__( # noqa: PLR0913, PLR0915 proxy: str = None, grpc_proxy: str = None, ca_cert: str = None, + grpc_ssl_target: str = None, pac: PACFile = None, req_headers: dict = None, ): @@ -77,6 +78,7 @@ def __init__( # noqa: PLR0913, PLR0915 api_key=api_key, ver_details=ver_details, ca_cert=ca_cert, + grpc_ssl_target=grpc_ssl_target, proxy=proxy, pac=pac, grpc_proxy=grpc_proxy, diff --git a/src/scanoss/scanner.py b/src/scanoss/scanner.py index 4b59903..b5423cb 100644 --- a/src/scanoss/scanner.py +++ b/src/scanoss/scanner.py @@ -22,40 +22,40 @@ THE SOFTWARE. """ +import datetime import json import os -from pathlib import Path import sys -import datetime +from pathlib import Path from typing import Any, Dict, List, Optional -import importlib_resources +import importlib_resources from progress.bar import Bar from progress.spinner import Spinner from pypac.parser import PACFile from scanoss.file_filters import FileFilters -from .scanossapi import ScanossApi -from .cyclonedx import CycloneDx -from .spdxlite import SpdxLite +from . import __version__ from .csvoutput import CsvOutput -from .threadedscanning import ThreadedScanning +from .cyclonedx import CycloneDx from .scancodedeps import ScancodeDeps -from .threadeddependencies import ThreadedDependencies, SCOPE -from .scanossgrpc import ScanossGrpc -from .scantype import ScanType -from .scanossbase import ScanossBase from .scanoss_settings import ScanossSettings +from .scanossapi import ScanossApi +from .scanossbase import ScanossBase +from .scanossgrpc import ScanossGrpc from .scanpostprocessor import ScanPostProcessor -from . import __version__ +from .scantype import ScanType +from .spdxlite import SpdxLite +from .threadeddependencies import SCOPE, ThreadedDependencies +from .threadedscanning import ThreadedScanning FAST_WINNOWING = False try: from scanoss_winnowing.winnowing import Winnowing FAST_WINNOWING = True -except ModuleNotFoundError or ImportError: +except ModuleNotFoundError or ImportError: # noqa: PLW0711 FAST_WINNOWING = False from .winnowing import Winnowing @@ -69,7 +69,7 @@ class Scanner(ScanossBase): Handle the scanning of files, snippets and dependencies """ - def __init__( # noqa: PLR0913, PLR0915 + def __init__( # noqa: PLR0913, PLR0915 self, wfp: str = None, scan_output: str = None, @@ -96,6 +96,7 @@ def __init__( # noqa: PLR0913, PLR0915 proxy: str = None, grpc_proxy: str = None, ca_cert: str = None, + grpc_ssl_target: str = None, pac: PACFile = None, retry: int = 5, hpsm: bool = False, @@ -158,7 +159,7 @@ def __init__( # noqa: PLR0913, PLR0915 ca_cert=ca_cert, pac=pac, retry=retry, - req_headers= self.req_headers, + req_headers=self.req_headers, ) sc_deps = ScancodeDeps(debug=debug, quiet=quiet, trace=trace, timeout=sc_timeout, sc_command=sc_command) grpc_api = ScanossGrpc( @@ -169,6 +170,7 @@ def __init__( # noqa: PLR0913, PLR0915 api_key=api_key, ver_details=ver_details, ca_cert=ca_cert, + grpc_ssl_target=grpc_ssl_target, proxy=proxy, pac=pac, grpc_proxy=grpc_proxy, @@ -284,7 +286,7 @@ def is_dependency_scan(self): return True return False - def scan_folder_with_options( + def scan_folder_with_options( # noqa: PLR0913 self, scan_dir: str, deps_file: str = None, @@ -332,7 +334,7 @@ def scan_folder_with_options( success = False return success - def scan_folder(self, scan_dir: str) -> bool: + def scan_folder(self, scan_dir: str) -> bool: # noqa: PLR0912, PLR0915 """ Scan the specified folder producing fingerprints, send to the SCANOSS API and return results @@ -400,7 +402,7 @@ def scan_folder(self, scan_dir: str) -> bool: scan_block += wfp scan_size = len(scan_block.encode('utf-8')) wfp_file_count += 1 - # If the scan request block (group of WFPs) or larger than the POST size or we have reached the file limit, add it to the queue + # If the scan request block (group of WFPs) or larger than the POST size or we have reached the file limit, add it to the queue # noqa: E501 if wfp_file_count > self.post_file_count or scan_size >= self.max_post_size: self.threaded_scan.queue_add(scan_block) queue_size += 1 @@ -509,7 +511,7 @@ def _merge_scan_results( for response in scan_responses: if response is not None: if file_map: - response = self._deobfuscate_filenames(response, file_map) + response = self._deobfuscate_filenames(response, file_map) # noqa: PLW2901 results.update(response) dep_files = dep_responses.get('files', None) if dep_responses else None @@ -532,7 +534,7 @@ def _deobfuscate_filenames(self, response: dict, file_map: dict) -> dict: deobfuscated[key] = value return deobfuscated - def scan_file_with_options( + def scan_file_with_options( # noqa: PLR0913 self, file: str, deps_file: str = None, @@ -603,7 +605,7 @@ def scan_file(self, file: str) -> bool: success = False return success - def scan_files(self, files: []) -> bool: + def scan_files(self, files: []) -> bool: # noqa: PLR0912, PLR0915 """ Scan the specified list of files, producing fingerprints, send to the SCANOSS API and return results Please note that by providing an explicit list you bypass any exclusions that may be defined on the scanner @@ -657,7 +659,7 @@ def scan_files(self, files: []) -> bool: file_count += 1 if self.threaded_scan: wfp_size = len(wfp.encode('utf-8')) - # If the WFP is bigger than the max post size and we already have something stored in the scan block, add it to the queue + # If the WFP is bigger than the max post size and we already have something stored in the scan block, add it to the queue # noqa: E501 if scan_block != '' and (wfp_size + scan_size) >= self.max_post_size: self.threaded_scan.queue_add(scan_block) queue_size += 1 @@ -666,7 +668,7 @@ def scan_files(self, files: []) -> bool: scan_block += wfp scan_size = len(scan_block.encode('utf-8')) wfp_file_count += 1 - # If the scan request block (group of WFPs) or larger than the POST size or we have reached the file limit, add it to the queue + # If the scan request block (group of WFPs) or larger than the POST size or we have reached the file limit, add it to the queue # noqa: E501 if wfp_file_count > self.post_file_count or scan_size >= self.max_post_size: self.threaded_scan.queue_add(scan_block) queue_size += 1 @@ -675,9 +677,7 @@ def scan_files(self, files: []) -> bool: if not scan_started and queue_size > self.nb_threads: # Start scanning if we have something to do scan_started = True if not self.threaded_scan.run(wait=False): - self.print_stderr( - 'Warning: Some errors encounted while scanning. Results might be incomplete.' - ) + self.print_stderr('Warning: Some errors encounted while scanning. Results might be incomplete.') success = False # End for loop @@ -755,7 +755,7 @@ def scan_contents(self, filename: str, contents: bytes) -> bool: success = False return success - def scan_wfp_file(self, file: str = None) -> bool: + def scan_wfp_file(self, file: str = None) -> bool: # noqa: PLR0912, PLR0915 """ Scan the contents of the specified WFP file (in the current process) :param file: Scan the contents of the specified WFP file (in the current process) diff --git a/src/scanoss/scanners/container_scanner.py b/src/scanoss/scanners/container_scanner.py index 43aec68..1e7c61b 100644 --- a/src/scanoss/scanners/container_scanner.py +++ b/src/scanoss/scanners/container_scanner.py @@ -228,6 +228,7 @@ def __init__( url=config.apiurl, api_key=config.key, ca_cert=config.ca_cert, + grpc_ssl_target=config.grpc_ssl_target, proxy=config.proxy, pac=config.pac, grpc_proxy=config.grpc_proxy, diff --git a/src/scanoss/scanners/scanner_config.py b/src/scanoss/scanners/scanner_config.py index ed48342..4a43ab9 100644 --- a/src/scanoss/scanners/scanner_config.py +++ b/src/scanoss/scanners/scanner_config.py @@ -51,6 +51,7 @@ class ScannerConfig: grpc_proxy: Optional[str] = None ca_cert: Optional[str] = None + grpc_ssl_target: Optional[str] = None pac: Optional[PACFile] = None @@ -69,5 +70,6 @@ def create_scanner_config_from_args(args) -> ScannerConfig: proxy=getattr(args, 'proxy', None), grpc_proxy=getattr(args, 'grpc_proxy', None), ca_cert=getattr(args, 'ca_cert', None), + grpc_ssl_target=getattr(args, 'grpc_ssl_target', None), pac=getattr(args, 'pac', None), ) diff --git a/src/scanoss/scanossgrpc.py b/src/scanoss/scanossgrpc.py index 189f4c1..8e415c7 100644 --- a/src/scanoss/scanossgrpc.py +++ b/src/scanoss/scanossgrpc.py @@ -103,6 +103,7 @@ def __init__( # noqa: PLR0913, PLR0915 trace: bool = False, quiet: bool = False, ca_cert: str = None, + grpc_ssl_target: str = None, api_key: str = None, ver_details: str = None, timeout: int = 600, @@ -132,6 +133,7 @@ def __init__( # noqa: PLR0913, PLR0915 self.timeout = timeout self.proxy = proxy self.grpc_proxy = grpc_proxy + self.grpc_ssl_target = grpc_ssl_target self.pac = pac self.req_headers = req_headers self.metadata = [] @@ -171,17 +173,26 @@ def __init__( # noqa: PLR0913, PLR0915 self.provenance_stub = GeoProvenanceStub(grpc.insecure_channel(self.url)) self.scanning_stub = ScanningStub(grpc.insecure_channel(self.url)) else: + channel_options = [] + if self.grpc_ssl_target: + channel_options.append(('grpc.ssl_target_name_override', self.grpc_ssl_target)) + if ca_cert is not None: credentials = grpc.ssl_channel_credentials(cert_data) # secure with specified certificate else: credentials = grpc.ssl_channel_credentials() # secure connection with default certificate - self.comp_search_stub = ComponentsStub(grpc.secure_channel(self.url, credentials)) - self.crypto_stub = CryptographyStub(grpc.secure_channel(self.url, credentials)) - self.dependencies_stub = DependenciesStub(grpc.secure_channel(self.url, credentials)) - self.semgrep_stub = SemgrepStub(grpc.secure_channel(self.url, credentials)) - self.vuln_stub = VulnerabilitiesStub(grpc.secure_channel(self.url, credentials)) - self.provenance_stub = GeoProvenanceStub(grpc.secure_channel(self.url, credentials)) - self.scanning_stub = ScanningStub(grpc.secure_channel(self.url, credentials)) + + self.comp_search_stub = ComponentsStub(grpc.secure_channel(self.url, credentials, options=channel_options)) + self.crypto_stub = CryptographyStub(grpc.secure_channel(self.url, credentials, options=channel_options)) + self.dependencies_stub = DependenciesStub( + grpc.secure_channel(self.url, credentials, options=channel_options) + ) + self.semgrep_stub = SemgrepStub(grpc.secure_channel(self.url, credentials, options=channel_options)) + self.vuln_stub = VulnerabilitiesStub(grpc.secure_channel(self.url, credentials, options=channel_options)) + self.provenance_stub = GeoProvenanceStub( + grpc.secure_channel(self.url, credentials, options=channel_options) + ) + self.scanning_stub = ScanningStub(grpc.secure_channel(self.url, credentials, options=channel_options)) @classmethod def _load_cert(cls, cert_file: str) -> bytes: @@ -694,6 +705,7 @@ class GrpcConfig: timeout: Optional[int] = DEFAULT_TIMEOUT proxy: Optional[str] = None grpc_proxy: Optional[str] = None + grpc_ssl_target: Optional[str] = None pac: Optional[PACFile] = None req_headers: Optional[dict] = None @@ -710,4 +722,5 @@ def create_grpc_config_from_args(args) -> GrpcConfig: timeout=getattr(args, 'timeout', DEFAULT_TIMEOUT), proxy=getattr(args, 'proxy', None), grpc_proxy=getattr(args, 'grpc_proxy', None), + grpc_ssl_target=getattr(args, 'grpc_ssl_target', None), )