« -L » relaie UN service ; « -D » ouvre un relais SOCKS, par lequel le navigateur atteint n'importe quelle destination depuis la machine distante — une interface qui n'écoute que sur sa boucle locale, un hôte de son réseau sans route depuis ici. Le port par défaut est 1080, modifiable, et contrôlé libre avant d'ouvrir. La saisie de l'adresse est celle de sshfs, désormais partagée : l'alias de ~/.ssh/config part tel quel à ssh, faute de quoi son ProxyJump se perdrait et une VM imbriquée deviendrait injoignable. Le réglage de Firefox s'affiche AVANT le lancement, la commande ne rendant la main qu'au Ctrl+C. Vérifié : 9 tests neufs, et la numérotation du menu que deux autres gardent. --- EN --- "-L" relays ONE service; "-D" opens a SOCKS relay, through which the browser reaches any destination from the remote machine — an interface listening only on its loopback, a host of its network with no route from here. The default port is 1080, changeable, and checked free before opening. The address prompt is sshfs's, now shared: the ~/.ssh/config alias goes to ssh as is, failing which its ProxyJump would be lost and a nested VM become unreachable. The Firefox settings print BEFORE the launch, the command only returning on Ctrl+C. Checked: 9 new tests, and the menu numbering two others guard. Assisted-by: Claude Opus 5
129 lines
4.4 KiB
Python
129 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
"""Le proxy SOCKS ouvre-t-il le bon tunnel, et le dit-il assez ?
|
|
|
|
« -D » ne relaie pas UN service comme « -L » : il ouvre un relais SOCKS, par
|
|
lequel le navigateur atteint n'importe quelle destination depuis la machine
|
|
distante. Trois choses décident si la commande sert à quelque chose, et ces
|
|
tests les gardent :
|
|
|
|
- l'ALIAS de ~/.ssh/config est passé tel quel à ssh. Le remplacer par
|
|
« user@hôte » perdrait son ProxyJump, et une VM imbriquée sans route directe
|
|
deviendrait injoignable ;
|
|
- le port choisi se retrouve dans la commande ET dans le mode d'emploi du
|
|
navigateur, sans quoi l'utilisateur règle Firefox sur un port qui n'écoute
|
|
pas ;
|
|
- le mode d'emploi passe AVANT le lancement : la commande ne rend la main
|
|
qu'au Ctrl+C, et c'est pendant qu'elle tourne qu'on règle le navigateur.
|
|
"""
|
|
|
|
import builtins
|
|
import contextlib
|
|
import io
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
RACINE = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(RACINE))
|
|
|
|
from script.todo.todo import TODO # noqa: E402
|
|
|
|
|
|
class ExecuteFactice:
|
|
def __init__(self):
|
|
self.commandes = []
|
|
|
|
def exec_command_live(self, cmd, **kwargs):
|
|
self.commandes.append(cmd)
|
|
|
|
|
|
class BancProxy(unittest.TestCase):
|
|
def joue(
|
|
self,
|
|
reponses,
|
|
cible=("vm-essai", "u", "10.0.0.1", "vm-essai", True),
|
|
):
|
|
"""Déroule la commande sur des réponses écrites d'avance."""
|
|
todo = TODO.__new__(TODO)
|
|
todo.execute = ExecuteFactice()
|
|
todo._ask_ssh_target = lambda: cible
|
|
suite = iter(reponses)
|
|
ancien = builtins.input
|
|
builtins.input = lambda *a, **k: next(suite, "")
|
|
sortie = io.StringIO()
|
|
try:
|
|
with contextlib.redirect_stdout(sortie):
|
|
todo._deploy_socks_proxy()
|
|
finally:
|
|
builtins.input = ancien
|
|
return todo.execute.commandes, sortie.getvalue()
|
|
|
|
|
|
class TestLaCommande(BancProxy):
|
|
def test_le_port_par_defaut_est_1080(self):
|
|
commandes, _ = self.joue([""])
|
|
self.assertEqual(["ssh -D 1080 -N -C vm-essai"], commandes)
|
|
|
|
def test_le_port_se_change(self):
|
|
commandes, _ = self.joue(["9050"])
|
|
self.assertEqual(["ssh -D 9050 -N -C vm-essai"], commandes)
|
|
|
|
def test_un_port_qui_n_est_pas_un_nombre_retombe_sur_1080(self):
|
|
commandes, _ = self.joue(["mille-quatre-vingts"])
|
|
self.assertEqual(["ssh -D 1080 -N -C vm-essai"], commandes)
|
|
|
|
def test_l_alias_ssh_est_passe_tel_quel(self):
|
|
"""Le remplacer par user@hôte perdrait son ProxyJump."""
|
|
commandes, _ = self.joue(
|
|
[""], cible=("bond", "u", "10.0.0.2", "bond", True)
|
|
)
|
|
self.assertIn(" bond", commandes[0])
|
|
self.assertNotIn("@", commandes[0])
|
|
|
|
def test_renoncer_a_l_adresse_ne_lance_rien(self):
|
|
todo = TODO.__new__(TODO)
|
|
todo.execute = ExecuteFactice()
|
|
todo._ask_ssh_target = lambda: None
|
|
with contextlib.redirect_stdout(io.StringIO()):
|
|
todo._deploy_socks_proxy()
|
|
self.assertEqual([], todo.execute.commandes)
|
|
|
|
|
|
class TestLeModeDEmploi(BancProxy):
|
|
def test_il_nomme_le_port_choisi(self):
|
|
_, sortie = self.joue(["9050"])
|
|
self.assertIn("127.0.0.1", sortie)
|
|
self.assertIn("9050", sortie)
|
|
self.assertNotIn("1080", sortie)
|
|
|
|
def test_il_precede_le_lancement(self):
|
|
"""La commande ne rend la main qu'au Ctrl+C."""
|
|
_, sortie = self.joue([""])
|
|
self.assertLess(sortie.index("127.0.0.1"), sortie.index("Ctrl+C"))
|
|
|
|
def test_il_parle_du_dns_distant(self):
|
|
_, sortie = self.joue([""])
|
|
self.assertIn("SOCKS v5", sortie)
|
|
self.assertIn("DNS", sortie.upper())
|
|
|
|
|
|
class TestLeMenu(unittest.TestCase):
|
|
def test_l_entree_ferme_la_section_locale(self):
|
|
"""Quatrième, et la suite glisse : le VPN passe de 9 à 10."""
|
|
source = (RACINE / "script/todo/todo.py").read_text(encoding="utf-8")
|
|
debut = source.index("def prompt_execute_deploy(self)")
|
|
menu = source[debut : source.index("def prompt_execute_deploy_ssh")]
|
|
self.assertIn(
|
|
'elif status == "4":\n self._deploy_socks_proxy()',
|
|
menu,
|
|
)
|
|
self.assertIn(
|
|
'elif status == "10":\n self.prompt_execute_vpn()',
|
|
menu,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|