Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 73 additions & 52 deletions capabilities/network-ops/tools/impacket.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,45 +61,62 @@ def _extract_real_path_from_wrapper(wrapper_path: Path) -> Path | None:


def _ensure_impacket_installed() -> None:
"""Install impacket into the running Python if it's not importable.
"""Ensure impacket is importable by subprocesses using sys.executable.

The runtime may not process ``dependencies.python`` from
``capability.yaml``, so we do a best-effort pip install at import
time as a fallback.
The in-process ``import impacket`` may succeed (via inherited
PYTHONPATH or global site-packages) while subprocesses spawned with
``sys.executable`` fail because they run in an isolated venv. We
check the *subprocess* environment specifically, since that's what
``_build_script_command`` uses to run impacket scripts.

Set ``DREADNODE_SKIP_AUTO_INSTALL=1`` to disable (useful in tests/CI).
"""
import subprocess

if os.environ.get("DREADNODE_SKIP_AUTO_INSTALL", "").strip() in ("1", "true", "yes"):
return

# Check if sys.executable can import impacket as a subprocess —
# this matches how _build_script_command actually runs scripts.
try:
import impacket as _ # noqa: F401
except ImportError:
import subprocess
subprocess.run(
[sys.executable, "-c", "import impacket"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=True,
timeout=10,
)
return # sys.executable can import impacket — nothing to do
except (subprocess.CalledProcessError, subprocess.TimeoutExpired, OSError):
pass

logger.warning("impacket not importable — attempting pip install")
for extra_args in ([], ["--break-system-packages"]):
try:
subprocess.run(
[
sys.executable,
"-m",
"pip",
"install",
"--quiet",
"impacket>=0.12.0",
*extra_args,
],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=True,
timeout=120,
)
logger.info("impacket installed successfully")
return
except (subprocess.CalledProcessError, subprocess.TimeoutExpired, OSError):
continue
logger.error("Failed to install impacket — impacket tools will not work")
logger.warning(
f"impacket not importable by {sys.executable} — attempting pip install"
)
for extra_args in ([], ["--break-system-packages"]):
try:
subprocess.run(
[
sys.executable,
"-m",
"pip",
"install",
"--quiet",
"impacket>=0.12.0",
*extra_args,
],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=True,
timeout=120,
)
logger.info("impacket installed successfully")
return
except (subprocess.CalledProcessError, subprocess.TimeoutExpired, OSError):
continue
logger.error(
f"Failed to install impacket into {sys.executable} — impacket tools will not work"
)


_ensure_impacket_installed()
Expand All @@ -112,13 +129,34 @@ def _get_impacket_script_path() -> Path:
Returns a directory containing real Python impacket scripts (not
shell wrappers) so they can be invoked via sys.executable.

Tries multiple common locations:
1. Global PATH: resolve the found script's directory, following
Tries multiple common locations, preferring site-packages (which
matches sys.executable) over PATH (which may contain pip entry
point wrappers installed for a different Python version):
1. pip-installed: site-packages/impacket/examples/ (same Python as sys.executable)
2. apt-installed: /usr/share/doc/python3-impacket/examples/
3. Global PATH: resolve the found script's directory, following
shell wrappers to the real scripts if needed
2. pip-installed: site-packages/impacket/examples/
3. apt-installed: /usr/share/doc/python3-impacket/examples/
"""
# Check if scripts are on PATH (pipx / manual install)
# Prefer site-packages — these scripts are guaranteed to work with
# sys.executable since they live in the same Python installation.
# PATH entries (e.g. ~/.local/bin/) are pip entry point wrappers
# that may be installed for a different Python version.
try:
import impacket

impacket_pkg_path = Path(impacket.__file__).parent
examples_path = impacket_pkg_path / "examples"
if examples_path.exists() and (examples_path / "secretsdump.py").exists():
return examples_path
except ImportError:
pass

# Fall back to apt installation path
apt_path = Path("/usr/share/doc/python3-impacket/examples/")
if apt_path.exists() and (apt_path / "secretsdump.py").exists():
return apt_path

# Last resort: check PATH (pipx / manual install)
found = shutil.which("secretsdump.py")
if found is not None:
found_path = Path(found).resolve()
Expand All @@ -138,23 +176,6 @@ def _get_impacket_script_path() -> Path:
f"Impacket script at {found_path} is a shell wrapper, skipping PATH discovery"
)

# Try to find impacket in site-packages (pip install)
try:
import impacket

impacket_pkg_path = Path(impacket.__file__).parent
examples_path = impacket_pkg_path / "examples"
# Check that actual script files exist, not just the directory
if examples_path.exists() and (examples_path / "secretsdump.py").exists():
return examples_path
except ImportError:
pass

# Fall back to apt installation path
apt_path = Path("/usr/share/doc/python3-impacket/examples/")
if apt_path.exists() and (apt_path / "secretsdump.py").exists():
return apt_path

# Default fallback
return Path("/usr/share/doc/python3-impacket/examples/")

Expand Down
Loading