Skip to content

Commit d51fc3d

Browse files
committed
TOR class to allow remote SSH access over deep networks
This class configures an onion service to access the SSH server over Tor. This is useful when you have a box behind NAT or some firewall that is broken or unknown, and you need to get a rescue shell on the host. With this, you give an operator a thumb drive, who only needs to figure out how to boot into GRML, and then after a while you get a shell, pretty much regardless of where the box is. This is not enabled by default, naturally, otherwise the secret key would leak in default GRML builds: this is solely designed to be run in an ad-hoc, one-time fashion. It also generates the SSH keys for the same reason: those are shown in the build logs and can be used to authenticate the remote host (a redundant measure to the onion service name, of course). I also enable `DEFAULT_BOOT_OPTIONS=ssh` in my builds, but that hasn't been done here (although maybe it's possible to enable that in the class? to be investigated). Finally, another shim is required here to inject a valid SSH public key in the image, so you can login over SSH. In my case, I have an extra CLASS that only has this one script which does: gpg --export-ssh-key [email protected] | tee -a $target/root/.ssh/authorized_keys This could also be folded in the TOR class, but I'm not sure how to do variables yet, so that's not yet standardized.
1 parent 7668f12 commit d51fc3d

File tree

4 files changed

+93
-0
lines changed

4 files changed

+93
-0
lines changed

config/package_config/TOR

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
PACKAGES install
2+
3+
tor
4+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
SocksPort 0
2+
Log notice syslog
3+
HiddenServiceDir /var/lib/tor/ssh_onion_service
4+
HiddenServicePort 22 127.0.0.1:22
+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/python3
2+
3+
from os import environ
4+
from pathlib import Path
5+
from subprocess import run, Popen, PIPE
6+
from time import sleep
7+
8+
target = environ.get("target")
9+
assert target, "no $target set in the environment, aborting"
10+
assert isinstance(target, str), "$target environment variable must be a string"
11+
12+
wants_dir = Path(target) / "etc/systemd/system/grml-boot.target.wants/"
13+
print("enabling tor.service in", str(wants_dir))
14+
assert not str(wants_dir).startswith("/etc"), "wants dir starts with /etc"
15+
wants_dir.mkdir(exist_ok=True)
16+
(wants_dir / "tor.service").symlink_to("/lib/systemd/system/tor.service")
17+
18+
print("deploying minimal torrc config for SSH onion service")
19+
run(["fcopy", "-i", "-B", "-v", "/etc/tor/torrc"], check=True)
20+
21+
cmd = [
22+
"chroot",
23+
target,
24+
"runuser",
25+
"-u",
26+
"debian-tor",
27+
"--",
28+
"tor",
29+
"--DisableNetwork",
30+
"1",
31+
"--Runasdaemon",
32+
"0",
33+
"--SocksPort",
34+
"0",
35+
"--HiddenServiceDir",
36+
"/var/lib/tor/ssh_onion_service",
37+
"--HiddenServicePort",
38+
"22 127.0.0.1:22",
39+
]
40+
print("starting tor to create the private keys and hostname with", str(cmd))
41+
process = Popen(
42+
cmd,
43+
stdout=PIPE,
44+
text=True,
45+
)
46+
47+
assert process.stdout, "no output from tor command?"
48+
49+
hostname_path = Path(target) / "var/lib/tor/ssh_onion_service/hostname"
50+
for line in process.stdout:
51+
print(line.strip())
52+
if "DisableNetwork is set." in line:
53+
count = 0
54+
while count < 10 and not hostname_path.exists():
55+
print(f"path {hostname_path} doesn't exist, sleeping")
56+
sleep(1)
57+
count += 1
58+
process.terminate()
59+
60+
with hostname_path.open() as hn:
61+
print("Tor onion service hostnamename:", hn.read().strip())
62+
63+
print("generating SSH keys in", target)
64+
run(["chroot", target, "ssh-keygen", "-A"], check=True)
65+
66+
count = 0
67+
for path in [str(x) for x in (Path(target) / "etc/ssh").iterdir()]:
68+
if not path.endswith("_key.pub"):
69+
continue
70+
print(path)
71+
cmd = [
72+
"chroot",
73+
target,
74+
"ssh-keygen",
75+
"-l",
76+
"-f",
77+
path.removeprefix(target),
78+
]
79+
80+
ret = run(cmd, check=True, stdout=PIPE, stderr=PIPE, text=True)
81+
print(ret.stdout, end='')
82+
print(ret.stderr, end='')
83+
count += 1
84+
assert count, "no SSH keys found in %s" % (Path(target) / "etc/ssh")

etc/grml/grml-live.local

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DEFAULT_BOOTOPTIONS="ssh"

0 commit comments

Comments
 (0)