pem_files path is incorrect when using PyInstaller's onefile bundling
TechSupportJosh opened this issue · comments
Describe the bug
When using PyInstaller's onefile bundling method, and adding pem_files
to the bundle, sslyze will attempt to load pem_files
from where the bundled executable is executed from, rather than searching in the temporary directory created in /tmp/_MEI...
.
From what I can tell, this is because sys.executable
when using onefile bundling points to the binary that the user called e.g. /home/user/mytool
. The function responsible for this doesn't utilise sys._MEIPASS
(the directory which points towards the bundle folder, regardless of whether it was one-folder or one-file bundled):
To Reproduce
Steps to reproduce the behavior:
- Install sslyze and pyinstaller
pip install sslyze pyinstaller
- Create a file
test.py
with the following content:
from datetime import datetime
from sslyze import (
ServerScanRequest,
ServerNetworkLocation,
Scanner,
ServerHostnameCouldNotBeResolved,
ServerScanStatusEnum,
ScanCommandAttemptStatusEnum,
)
import sys
print("sys.executable:", sys.executable)
print("sys._MEIPASS", sys._MEIPASS if hasattr(sys, "_MEIPASS") else "N/A")
print()
print("=> Starting the scans")
date_scans_started = datetime.utcnow()
# First create the scan requests for each server that we want to scan
try:
all_scan_requests = [
ServerScanRequest(
server_location=ServerNetworkLocation(hostname="cloudflare.com")
),
ServerScanRequest(server_location=ServerNetworkLocation(hostname="google.com")),
]
except ServerHostnameCouldNotBeResolved:
# Handle bad input ie. invalid hostnames
print("Error resolving the supplied hostnames")
exit()
# Then queue all the scans
scanner = Scanner()
scanner.queue_scans(all_scan_requests)
# And retrieve and process the results for each server
all_server_scan_results = []
for server_scan_result in scanner.get_results():
all_server_scan_results.append(server_scan_result)
print(f"\n\n****Results for {server_scan_result.server_location.hostname}****")
# Were we able to connect to the server and run the scan?
if server_scan_result.scan_status == ServerScanStatusEnum.ERROR_NO_CONNECTIVITY:
# No we weren't
print(
f"\nError: Could not connect to {server_scan_result.server_location.hostname}:"
f" {server_scan_result.connectivity_error_trace}"
)
continue
# Since we were able to run the scan, scan_result is populated
assert server_scan_result.scan_result
# Process the result of the certificate info scan command
certinfo_attempt = server_scan_result.scan_result.certificate_info
if certinfo_attempt.status == ScanCommandAttemptStatusEnum.ERROR:
print(certinfo_attempt.status)
print(certinfo_attempt.error_reason)
print(certinfo_attempt.error_trace)
elif certinfo_attempt.status == ScanCommandAttemptStatusEnum.COMPLETED:
certinfo_result = certinfo_attempt.result
assert certinfo_result
print("Certificate worked!")
- Build with pyinstaller (specifying the path to the sslyze's pem_stores folder, here I installed into a venv, your path may vary)
pyinstaller --add-data "./venv/lib/python3.10/site-packages/sslyze/plugins/certificate_info/trust_stores/pem_files:pem_files" --onefile ./test.py
- Run command and verify that
sys.executable
points to the binary in thedist
folder, rather than the/tmp
folder created by PyInstaller:
$ ./test
sys.executable: /home/josh/sslyze_bug_fix/dist/test
sys._MEIPASS /tmp/_MEIqVkqr7
=> Starting the scans
****Results for google.com****
ScanCommandAttemptStatusEnum.ERROR
ScanCommandErrorReasonEnum.BUG_IN_SSLYZE
[Errno 2] No such file or directory: '/home/josh/sslyze_bug_fix/dist/pem_files/apple.yaml'
****Results for cloudflare.com****
ScanCommandAttemptStatusEnum.ERROR
ScanCommandErrorReasonEnum.BUG_IN_SSLYZE
[Errno 2] No such file or directory: '/home/josh/sslyze_bug_fix/dist/pem_files/apple.yaml'
Expected behavior
If the application detects _MEIPASS
, it should use this directory rather than the sys.executable
parent directory.
Python environment:
- OS: Ubuntu 22.04.2 LTS
- Python version: 3.10
Adding this to the top of _get_script_dir
seems to fix it, but would be nice if this could be included within the library:
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
return Path(sys._MEIPASS)
Hello,
Unfortunately, only cx_freeze on Windows is supported at the moment, and I have no plans to add support for pyInstaller. Good luck tho!