Skip to content

Commit

Permalink
TOR class to allow remote SSH access over deep networks
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
anarcat committed Mar 10, 2024
1 parent a036acb commit f8b8773
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 0 deletions.
4 changes: 4 additions & 0 deletions etc/grml/fai/config/files/etc/tor/torrc/TOR
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
SocksPort 0
Log notice syslog
HiddenServiceDir /var/lib/tor/ssh_onion_service
HiddenServicePort 22 127.0.0.1:22
4 changes: 4 additions & 0 deletions etc/grml/fai/config/package_config/TOR
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
PACKAGES install

tor

72 changes: 72 additions & 0 deletions etc/grml/fai/config/scripts/TOR/10-gen_hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/python3

from os import environ
from pathlib import Path
from subprocess import run, Popen, PIPE
from time import sleep

target = environ.get("target")
assert target, "no $target set in the environment, aborting"
assert isinstance(target, str), "$target environment variable must be a string"

wants_dir = Path(target) / "etc/systemd/system/grml-boot.target.wants/"
print("enabling tor.service in", str(wants_dir))
assert not str(wants_dir).startswith("/etc"), f"wants dir starts with /etc, wtf: {wants_dir}"
wants_dir.mkdir(exist_ok=True)
(wants_dir / "tor.service").symlink_to("/lib/systemd/system/tor.service")

print("wants_dir:", str(wants_dir))
run(["ls", "-al", str(wants_dir)])
print("tor.service:", str(Path(target) / "lib/systemd/system/tor.service"))
run(["ls", "-al", str(Path(target) / "lib/systemd/system/tor.service")])

print("deploying minimal torrc config for SSH onion service")
run(["fcopy", "-i", "-B", "-v", "/etc/tor/torrc"], check=True)

cmd = [
"chroot", target,
"runuser", "-u", "debian-tor", "--",
"tor", "--DisableNetwork", "1",
"--Runasdaemon", "0",
"--SocksPort", "0",
"--HiddenServiceDir", "/var/lib/tor/ssh_onion_service",
"--HiddenServicePort", "22 127.0.0.1:22",
]
print("starting tor to create the private keys and hostname with", str(cmd))
process = Popen(
cmd,
stdout=PIPE,
text=True,
)

assert process.stdout, "no output from tor command?"

hostname_path = Path(target) / "var/lib/tor/ssh_onion_service/hostname"
for line in process.stdout:
print(line.strip())
if "DisableNetwork is set." in line:
count = 0
while count < 10 and not hostname_path.exists():
print(f"path {hostname_path} doesn't exist, sleeping")
sleep(1)
count += 1
process.terminate()

with hostname_path.open() as hn:
print("Tor onion service name:", hn.read().strip())

print("generating SSH keys in", target)
run(["chroot", target, "ssh-keygen", "-A"], check=True)

for path in [str(x) for x in (Path(target) / "etc/ssh").iterdir()]:
if path.startswith("ssh_host_") and path.endswith("_key.pub"):
run(
[
"chroot",
target,
"ssh-keygen",
"-l",
"-f",
path.removeprefix(target),
]
)

0 comments on commit f8b8773

Please sign in to comment.