-
Notifications
You must be signed in to change notification settings - Fork 521
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1955 from blacklanternsecurity/fix-multiprocess-bug
Fix Multiprocessing Shenanigans
- Loading branch information
Showing
12 changed files
with
389 additions
and
176 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import os | ||
import atexit | ||
from contextlib import suppress | ||
|
||
|
||
class SharedInterpreterState: | ||
""" | ||
A class to track the primary BBOT process. | ||
Used to prevent spawning multiple unwanted processes with multiprocessing. | ||
""" | ||
|
||
def __init__(self): | ||
self.main_process_var_name = "_BBOT_MAIN_PID" | ||
self.scan_process_var_name = "_BBOT_SCAN_PID" | ||
atexit.register(self.cleanup) | ||
|
||
@property | ||
def is_main_process(self): | ||
is_main_process = self.main_pid == os.getpid() | ||
return is_main_process | ||
|
||
@property | ||
def is_scan_process(self): | ||
is_scan_process = os.getpid() == self.scan_pid | ||
return is_scan_process | ||
|
||
@property | ||
def main_pid(self): | ||
main_pid = int(os.environ.get(self.main_process_var_name, 0)) | ||
if main_pid == 0: | ||
main_pid = os.getpid() | ||
# if main PID is not set, set it to the current PID | ||
os.environ[self.main_process_var_name] = str(main_pid) | ||
return main_pid | ||
|
||
@property | ||
def scan_pid(self): | ||
scan_pid = int(os.environ.get(self.scan_process_var_name, 0)) | ||
if scan_pid == 0: | ||
scan_pid = os.getpid() | ||
# if scan PID is not set, set it to the current PID | ||
os.environ[self.scan_process_var_name] = str(scan_pid) | ||
return scan_pid | ||
|
||
def update_scan_pid(self): | ||
os.environ[self.scan_process_var_name] = str(os.getpid()) | ||
|
||
def cleanup(self): | ||
with suppress(Exception): | ||
if self.is_main_process: | ||
with suppress(KeyError): | ||
del os.environ[self.main_process_var_name] | ||
with suppress(KeyError): | ||
del os.environ[self.scan_process_var_name] | ||
|
||
|
||
SHARED_INTERPRETER_STATE = SharedInterpreterState() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
from typing import List | ||
from bbot import Scanner | ||
from fastapi import FastAPI, Query | ||
|
||
app = FastAPI() | ||
|
||
|
||
@app.get("/start") | ||
async def start(targets: List[str] = Query(...)): | ||
scanner = Scanner(*targets, modules=["httpx"]) | ||
events = [e async for e in scanner.async_start()] | ||
return [e.json() for e in events] | ||
|
||
|
||
@app.get("/ping") | ||
async def ping(): | ||
return {"status": "ok"} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import time | ||
import httpx | ||
import multiprocessing | ||
from pathlib import Path | ||
from subprocess import Popen | ||
from contextlib import suppress | ||
|
||
cwd = Path(__file__).parent.parent.parent | ||
|
||
|
||
def run_bbot_multiprocess(queue): | ||
from bbot import Scanner | ||
|
||
scan = Scanner("http://127.0.0.1:8888", "blacklanternsecurity.com", modules=["httpx"]) | ||
events = [e.json() for e in scan.start()] | ||
queue.put(events) | ||
|
||
|
||
def test_bbot_multiprocess(bbot_httpserver): | ||
|
||
bbot_httpserver.expect_request("/").respond_with_data("[email protected]") | ||
|
||
queue = multiprocessing.Queue() | ||
events_process = multiprocessing.Process(target=run_bbot_multiprocess, args=(queue,)) | ||
events_process.start() | ||
events_process.join() | ||
events = queue.get() | ||
assert len(events) >= 3 | ||
scan_events = [e for e in events if e["type"] == "SCAN"] | ||
assert len(scan_events) == 2 | ||
assert any([e["data"] == "[email protected]" for e in events]) | ||
|
||
|
||
def test_bbot_fastapi(bbot_httpserver): | ||
|
||
bbot_httpserver.expect_request("/").respond_with_data("[email protected]") | ||
fastapi_process = start_fastapi_server() | ||
|
||
try: | ||
|
||
# wait for the server to start with a timeout of 60 seconds | ||
start_time = time.time() | ||
while True: | ||
try: | ||
response = httpx.get("http://127.0.0.1:8978/ping") | ||
response.raise_for_status() | ||
break | ||
except httpx.HTTPError: | ||
if time.time() - start_time > 60: | ||
raise TimeoutError("Server did not start within 60 seconds.") | ||
time.sleep(0.1) | ||
continue | ||
|
||
# run a scan | ||
response = httpx.get( | ||
"http://127.0.0.1:8978/start", | ||
params={"targets": ["http://127.0.0.1:8888", "blacklanternsecurity.com"]}, | ||
timeout=100, | ||
) | ||
events = response.json() | ||
assert len(events) >= 3 | ||
scan_events = [e for e in events if e["type"] == "SCAN"] | ||
assert len(scan_events) == 2 | ||
assert any([e["data"] == "[email protected]" for e in events]) | ||
|
||
finally: | ||
with suppress(Exception): | ||
fastapi_process.terminate() | ||
|
||
|
||
def start_fastapi_server(): | ||
import os | ||
import sys | ||
|
||
env = os.environ.copy() | ||
with suppress(KeyError): | ||
del env["BBOT_TESTING"] | ||
python_executable = str(sys.executable) | ||
process = Popen( | ||
[python_executable, "-m", "uvicorn", "bbot.test.fastapi_test:app", "--port", "8978"], cwd=cwd, env=env | ||
) | ||
return process |
Oops, something went wrong.