1#!/usr/bin/env python3
2import json
3import os
4import re
5import subprocess
6import sys
7
8import vms
9
10
11def main():
12 if len(sys.argv) != 2:
13 raise ValueError("Usage: vm-ssh.py VM")
14 name = sys.argv[1]
15 vms.ensure_name(name)
16 if vms.virsh("domstate", name).strip() != "running":
17 raise ValueError("Start this VM before connecting through SSH.")
18 directory = vms.DISKS / name
19 with os.fdopen(vms.open_regular(directory / "access.json")) as incoming:
20 username = json.load(incoming)["username"]
21 if not re.fullmatch(r"[a-z_][a-z0-9_-]{0,31}", username):
22 raise ValueError("This VM has no supported SSH account.")
23 forwards = re.findall(
24 r"^\s*TCP\[HOST_FORWARD\]\s+\d+\s+(\S+)\s+(\d+)\s+\S+\s+22\s",
25 vms.virsh("qemu-monitor-command", name, "--hmp", "info usernet"),
26 re.MULTILINE,
27 )
28 if len(forwards) != 1 or forwards[0][0] != "127.0.0.1":
29 raise ValueError("This VM has no private SSH connection.")
30 port = int(forwards[0][1])
31 if not 1 <= port <= 65535:
32 raise ValueError("This VM has no private SSH connection.")
33 for file in ["ssh_key", "ssh_known_hosts"]:
34 os.close(vms.open_regular(directory / file))
35 args = [
36 "ssh", "-F", "/dev/null", "-i", str(directory / "ssh_key"), "-p", str(port),
37 "-o", "IdentitiesOnly=yes", "-o", "BatchMode=yes",
38 "-o", "PreferredAuthentications=publickey", "-o", "ForwardAgent=no",
39 "-o", "ClearAllForwardings=yes", "-o", "StrictHostKeyChecking=yes",
40 "-o", "HostKeyAlias=" + name,
41 "-o", "UserKnownHostsFile=" + str(directory / "ssh_known_hosts"),
42 "-o", "GlobalKnownHostsFile=/dev/null", "-o", "ConnectTimeout=10",
43 "-o", "HostKeyAlgorithms=+ssh-rsa", "-o", "PubkeyAcceptedAlgorithms=+ssh-rsa",
44 "-o", "KexAlgorithms=+diffie-hellman-group14-sha1",
45 ]
46 command = os.environ.get("SSH_ORIGINAL_COMMAND")
47 if not command:
48 args.append("-tt")
49 args.append(username + "@127.0.0.1")
50 if command:
51 args.append(command)
52 os.execvp(args[0], args)
53
54
55if __name__ == "__main__":
56 try:
57 main()
58 except (ValueError, OSError, KeyError) as error:
59 print(str(error), file=sys.stderr)
60 sys.exit(1)
61 except subprocess.CalledProcessError:
62 print("The VM's private SSH connection could not be read.", file=sys.stderr)
63 sys.exit(1)