pcf-toolkit 0.2.5__py3-none-any.whl → 0.2.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pcf_toolkit/proxy/cli.py +79 -1
- pcf_toolkit/proxy/doctor.py +93 -4
- {pcf_toolkit-0.2.5.dist-info → pcf_toolkit-0.2.6.dist-info}/METADATA +1 -1
- {pcf_toolkit-0.2.5.dist-info → pcf_toolkit-0.2.6.dist-info}/RECORD +8 -8
- {pcf_toolkit-0.2.5.dist-info → pcf_toolkit-0.2.6.dist-info}/WHEEL +0 -0
- {pcf_toolkit-0.2.5.dist-info → pcf_toolkit-0.2.6.dist-info}/entry_points.txt +0 -0
- {pcf_toolkit-0.2.5.dist-info → pcf_toolkit-0.2.6.dist-info}/licenses/LICENSE.md +0 -0
- {pcf_toolkit-0.2.5.dist-info → pcf_toolkit-0.2.6.dist-info}/top_level.txt +0 -0
pcf_toolkit/proxy/cli.py
CHANGED
|
@@ -43,7 +43,7 @@ from pcf_toolkit.proxy.config import (
|
|
|
43
43
|
render_dist_path,
|
|
44
44
|
write_default_config,
|
|
45
45
|
)
|
|
46
|
-
from pcf_toolkit.proxy.doctor import CheckResult, run_doctor
|
|
46
|
+
from pcf_toolkit.proxy.doctor import CheckResult, check_mitmproxy_certificate, run_doctor
|
|
47
47
|
from pcf_toolkit.proxy.mitm import ensure_mitmproxy, find_mitmproxy, spawn_mitmproxy
|
|
48
48
|
from pcf_toolkit.proxy.server import spawn_http_server
|
|
49
49
|
from pcf_toolkit.rich_help import RichTyperCommand, RichTyperGroup
|
|
@@ -365,6 +365,9 @@ def start(
|
|
|
365
365
|
creationflags=creationflags,
|
|
366
366
|
)
|
|
367
367
|
|
|
368
|
+
if not detach:
|
|
369
|
+
_maybe_prompt_for_cert_trust()
|
|
370
|
+
|
|
368
371
|
if log_handle is not None:
|
|
369
372
|
log_handle.close()
|
|
370
373
|
|
|
@@ -546,9 +549,84 @@ def _apply_fixes(results: list[CheckResult], config: ProxyConfig) -> None:
|
|
|
546
549
|
except Exception as exc: # noqa: BLE001
|
|
547
550
|
typer.secho(f"Failed to install mitmproxy: {exc}", fg=typer.colors.RED)
|
|
548
551
|
if result.name == "mitmproxy_cert" and result.status == "warn":
|
|
552
|
+
if _is_interactive() and _supports_cert_install():
|
|
553
|
+
if typer.confirm(
|
|
554
|
+
"Install mitmproxy CA into the system trust store? This requires sudo.",
|
|
555
|
+
default=False,
|
|
556
|
+
):
|
|
557
|
+
_install_mitmproxy_cert()
|
|
558
|
+
continue
|
|
549
559
|
typer.secho(result.fix or "", fg=typer.colors.YELLOW)
|
|
550
560
|
|
|
551
561
|
|
|
562
|
+
def _supports_cert_install() -> bool:
|
|
563
|
+
return sys.platform == "darwin" or sys.platform.startswith("linux")
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def _maybe_prompt_for_cert_trust() -> None:
|
|
567
|
+
cert_result = check_mitmproxy_certificate()
|
|
568
|
+
if cert_result.status == "ok":
|
|
569
|
+
return
|
|
570
|
+
|
|
571
|
+
typer.secho(f"[{cert_result.status.upper()}] {cert_result.name}: {cert_result.message}", fg=typer.colors.YELLOW)
|
|
572
|
+
if _is_interactive() and _supports_cert_install():
|
|
573
|
+
if typer.confirm(
|
|
574
|
+
"Install mitmproxy CA into the system trust store? This requires sudo.",
|
|
575
|
+
default=False,
|
|
576
|
+
):
|
|
577
|
+
_install_mitmproxy_cert()
|
|
578
|
+
return
|
|
579
|
+
if cert_result.fix:
|
|
580
|
+
typer.secho(cert_result.fix, fg=typer.colors.YELLOW)
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def _install_mitmproxy_cert() -> None:
|
|
584
|
+
cert_path = Path.home() / ".mitmproxy" / "mitmproxy-ca-cert.pem"
|
|
585
|
+
if not cert_path.exists():
|
|
586
|
+
typer.secho("mitmproxy CA cert not found. Run the proxy once to generate it.", fg=typer.colors.RED)
|
|
587
|
+
return
|
|
588
|
+
|
|
589
|
+
if sys.platform == "darwin":
|
|
590
|
+
_run_privileged(
|
|
591
|
+
[
|
|
592
|
+
"sudo",
|
|
593
|
+
"security",
|
|
594
|
+
"add-trusted-cert",
|
|
595
|
+
"-d",
|
|
596
|
+
"-r",
|
|
597
|
+
"trustRoot",
|
|
598
|
+
"-k",
|
|
599
|
+
"/Library/Keychains/System.keychain",
|
|
600
|
+
str(cert_path),
|
|
601
|
+
],
|
|
602
|
+
"Trusting mitmproxy CA in System Keychain...",
|
|
603
|
+
)
|
|
604
|
+
return
|
|
605
|
+
|
|
606
|
+
if sys.platform.startswith("linux"):
|
|
607
|
+
if shutil.which("update-ca-certificates"):
|
|
608
|
+
target = Path("/usr/local/share/ca-certificates/mitmproxy-ca-cert.crt")
|
|
609
|
+
_run_privileged(["sudo", "cp", str(cert_path), str(target)], "Copying cert into CA store...")
|
|
610
|
+
_run_privileged(["sudo", "update-ca-certificates"], "Updating CA certificates...")
|
|
611
|
+
return
|
|
612
|
+
if shutil.which("update-ca-trust"):
|
|
613
|
+
target = Path("/etc/pki/ca-trust/source/anchors/mitmproxy-ca-cert.pem")
|
|
614
|
+
_run_privileged(["sudo", "cp", str(cert_path), str(target)], "Copying cert into CA trust anchors...")
|
|
615
|
+
_run_privileged(["sudo", "update-ca-trust", "extract"], "Updating CA trust...")
|
|
616
|
+
return
|
|
617
|
+
|
|
618
|
+
typer.secho("Automatic install not supported on this platform.", fg=typer.colors.YELLOW)
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
def _run_privileged(command: list[str], message: str) -> None:
|
|
622
|
+
typer.secho(message, fg=typer.colors.CYAN)
|
|
623
|
+
try:
|
|
624
|
+
subprocess.run(command, check=True)
|
|
625
|
+
typer.secho("Done.", fg=typer.colors.GREEN)
|
|
626
|
+
except subprocess.CalledProcessError as exc:
|
|
627
|
+
typer.secho(f"Command failed: {exc}", fg=typer.colors.RED)
|
|
628
|
+
|
|
629
|
+
|
|
552
630
|
def _print_results(results: list[CheckResult]) -> None:
|
|
553
631
|
"""Prints doctor check results with color coding.
|
|
554
632
|
|
pcf_toolkit/proxy/doctor.py
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
+
import shutil
|
|
6
7
|
import socket
|
|
8
|
+
import subprocess
|
|
7
9
|
import sys
|
|
8
10
|
from dataclasses import dataclass
|
|
9
11
|
from pathlib import Path
|
|
@@ -97,6 +99,11 @@ def run_doctor(
|
|
|
97
99
|
return results
|
|
98
100
|
|
|
99
101
|
|
|
102
|
+
def check_mitmproxy_certificate() -> CheckResult:
|
|
103
|
+
"""Returns a single check result for mitmproxy certificate trust."""
|
|
104
|
+
return _check_certificates()[0]
|
|
105
|
+
|
|
106
|
+
|
|
100
107
|
def _check_ports(config: ProxyConfig) -> list[CheckResult]:
|
|
101
108
|
"""Checks if proxy and HTTP server ports are available.
|
|
102
109
|
|
|
@@ -168,11 +175,30 @@ def _check_certificates() -> list[CheckResult]:
|
|
|
168
175
|
cert_dir = Path.home() / ".mitmproxy"
|
|
169
176
|
cert_file = cert_dir / "mitmproxy-ca-cert.pem"
|
|
170
177
|
if cert_file.exists():
|
|
178
|
+
trusted = _is_cert_trusted(cert_file)
|
|
179
|
+
if trusted is True:
|
|
180
|
+
return [
|
|
181
|
+
CheckResult(
|
|
182
|
+
name="mitmproxy_cert",
|
|
183
|
+
status="ok",
|
|
184
|
+
message="mitmproxy CA cert is trusted.",
|
|
185
|
+
)
|
|
186
|
+
]
|
|
187
|
+
if trusted is False:
|
|
188
|
+
return [
|
|
189
|
+
CheckResult(
|
|
190
|
+
name="mitmproxy_cert",
|
|
191
|
+
status="warn",
|
|
192
|
+
message="mitmproxy CA cert exists but is not trusted.",
|
|
193
|
+
fix=_cert_fix_instructions(cert_dir),
|
|
194
|
+
)
|
|
195
|
+
]
|
|
171
196
|
return [
|
|
172
197
|
CheckResult(
|
|
173
198
|
name="mitmproxy_cert",
|
|
174
|
-
status="
|
|
175
|
-
message=
|
|
199
|
+
status="warn",
|
|
200
|
+
message="mitmproxy CA cert found but trust status is unknown.",
|
|
201
|
+
fix=_cert_fix_instructions(cert_dir),
|
|
176
202
|
)
|
|
177
203
|
]
|
|
178
204
|
return [
|
|
@@ -275,5 +301,68 @@ def _cert_fix_instructions(cert_dir: Path) -> str:
|
|
|
275
301
|
if os.name == "nt":
|
|
276
302
|
return f"Run: certutil -addstore -f Root {cert_path} (elevated)"
|
|
277
303
|
if sys.platform == "darwin":
|
|
278
|
-
return
|
|
279
|
-
|
|
304
|
+
return (
|
|
305
|
+
"Run: sudo security add-trusted-cert -d -r trustRoot "
|
|
306
|
+
f"-k /Library/Keychains/System.keychain {cert_path}"
|
|
307
|
+
)
|
|
308
|
+
if shutil.which("update-ca-certificates"):
|
|
309
|
+
return (
|
|
310
|
+
f"Run: sudo cp {cert_path} /usr/local/share/ca-certificates/mitmproxy-ca-cert.crt "
|
|
311
|
+
"&& sudo update-ca-certificates"
|
|
312
|
+
)
|
|
313
|
+
if shutil.which("update-ca-trust"):
|
|
314
|
+
return (
|
|
315
|
+
f"Run: sudo cp {cert_path} /etc/pki/ca-trust/source/anchors/mitmproxy-ca-cert.pem "
|
|
316
|
+
"&& sudo update-ca-trust extract"
|
|
317
|
+
)
|
|
318
|
+
return f"Trust the cert manually: {cert_path}"
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _is_cert_trusted(cert_path: Path) -> bool | None:
|
|
322
|
+
"""Best-effort check for whether the mitmproxy CA is trusted."""
|
|
323
|
+
if sys.platform == "darwin":
|
|
324
|
+
return _is_cert_trusted_macos(cert_path)
|
|
325
|
+
if sys.platform.startswith("linux"):
|
|
326
|
+
return _is_cert_trusted_linux(cert_path)
|
|
327
|
+
if os.name == "nt":
|
|
328
|
+
return _is_cert_trusted_windows(cert_path)
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _is_cert_trusted_macos(cert_path: Path) -> bool | None:
|
|
333
|
+
try:
|
|
334
|
+
result = subprocess.run(
|
|
335
|
+
[
|
|
336
|
+
"security",
|
|
337
|
+
"find-certificate",
|
|
338
|
+
"-c",
|
|
339
|
+
"mitmproxy",
|
|
340
|
+
"-a",
|
|
341
|
+
"/Library/Keychains/System.keychain",
|
|
342
|
+
],
|
|
343
|
+
capture_output=True,
|
|
344
|
+
text=True,
|
|
345
|
+
check=False,
|
|
346
|
+
)
|
|
347
|
+
except FileNotFoundError:
|
|
348
|
+
return None
|
|
349
|
+
if result.returncode != 0:
|
|
350
|
+
return False
|
|
351
|
+
return bool(result.stdout.strip())
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _is_cert_trusted_linux(cert_path: Path) -> bool:
|
|
355
|
+
candidates = [
|
|
356
|
+
Path("/usr/local/share/ca-certificates/mitmproxy-ca-cert.crt"),
|
|
357
|
+
Path("/etc/ssl/certs/mitmproxy-ca-cert.pem"),
|
|
358
|
+
Path("/etc/ssl/certs/mitmproxy-ca-cert.crt"),
|
|
359
|
+
Path("/etc/pki/ca-trust/source/anchors/mitmproxy-ca-cert.pem"),
|
|
360
|
+
Path("/etc/pki/ca-trust/source/anchors/mitmproxy-ca-cert.crt"),
|
|
361
|
+
]
|
|
362
|
+
return any(path.exists() for path in candidates)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _is_cert_trusted_windows(cert_path: Path) -> bool | None:
|
|
366
|
+
# Implementing a reliable trust-store check on Windows is non-trivial here.
|
|
367
|
+
# Fall back to unknown and let the CLI provide instructions.
|
|
368
|
+
return None
|
|
@@ -16,16 +16,16 @@ pcf_toolkit/data/schema_snapshot.json,sha256=87tr3NDBF9IshguUpxkNl-rNK3jpdHA9-2l
|
|
|
16
16
|
pcf_toolkit/data/spec_raw.json,sha256=U6WtINyuJei3LLEfTOqyee_-tUfr_3UE-hkKE4S5oDQ,112224
|
|
17
17
|
pcf_toolkit/proxy/__init__.py,sha256=ivhtZia35IJinxfZOoL2bcp7PyL0U7KxavqSp4nyDL0,37
|
|
18
18
|
pcf_toolkit/proxy/browser.py,sha256=9a-sPvnM_W0Vg2nA_uYgSRmfaej6acgtl4OekDzI9wk,4561
|
|
19
|
-
pcf_toolkit/proxy/cli.py,sha256=
|
|
19
|
+
pcf_toolkit/proxy/cli.py,sha256=8U15hzZb9bLenJgoI9POaN4J8Ev2AxcdN7WahQFxBks,58181
|
|
20
20
|
pcf_toolkit/proxy/config.py,sha256=-uKI96Fhc8CWKJ2Cws5yOpPzLVDd7ZeRhdvgC_hNDks,9753
|
|
21
|
-
pcf_toolkit/proxy/doctor.py,sha256=
|
|
21
|
+
pcf_toolkit/proxy/doctor.py,sha256=VApvsScXbMmlj2dZY5xRQYyzS0eXtS4iOt41RVaMc44,10891
|
|
22
22
|
pcf_toolkit/proxy/mitm.py,sha256=bKawnEqIsM8wASHfJGr1GQTeipSdHRwRrLNceXm8TTw,5624
|
|
23
23
|
pcf_toolkit/proxy/server.py,sha256=Xt6dgw5MjBDP_GiHxD5iUQTAdULkyTP7e5erVWkNbfk,1152
|
|
24
24
|
pcf_toolkit/proxy/addons/__init__.py,sha256=NI3FmO7tBhNGvy5O6r2cTgqxpmts8NVCQ4Sk2jNmlCM,32
|
|
25
25
|
pcf_toolkit/proxy/addons/redirect_bundle.py,sha256=2neGzVjbUGjw7X94Si1MGcxi2rhdkcpVTNtA9Pua4g8,2072
|
|
26
|
-
pcf_toolkit-0.2.
|
|
27
|
-
pcf_toolkit-0.2.
|
|
28
|
-
pcf_toolkit-0.2.
|
|
29
|
-
pcf_toolkit-0.2.
|
|
30
|
-
pcf_toolkit-0.2.
|
|
31
|
-
pcf_toolkit-0.2.
|
|
26
|
+
pcf_toolkit-0.2.6.dist-info/licenses/LICENSE.md,sha256=KVBxRZbs2pIY2yrA50_E8As1atJXuRw9tRAJ_vreyF4,8639
|
|
27
|
+
pcf_toolkit-0.2.6.dist-info/METADATA,sha256=rVktwGe9uLifJEzM-1-z2NlnDrwiWiJj1VPYxzB63vE,25773
|
|
28
|
+
pcf_toolkit-0.2.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
29
|
+
pcf_toolkit-0.2.6.dist-info/entry_points.txt,sha256=ieMDIFZ1D0i8lWqoUYQAtsbU3dzIt6vxx21jtDMncNo,52
|
|
30
|
+
pcf_toolkit-0.2.6.dist-info/top_level.txt,sha256=0TURCNKgchkb-VxvC0o7jWJWJSp64d2ZVzZnRbj9GG8,12
|
|
31
|
+
pcf_toolkit-0.2.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|