Get WaitingWidget to properly check for and install the image

This commit is contained in:
Micah Lee 2021-11-22 14:37:53 -08:00
parent 83759d1a33
commit a54e19fe11
No known key found for this signature in database
GPG key ID: 403C2657CD994F73
4 changed files with 29 additions and 494 deletions

View file

@ -4,7 +4,7 @@ import inspect
import appdirs
import platform
import subprocess
import pipes
import shutil
import json
import gzip
import colorama
@ -35,8 +35,12 @@ class GlobalCommon(object):
# App data folder
self.appdata_path = appdirs.user_config_dir("dangerzone")
# Container name
# Container
self.container_name = "dangerzone.rocks/dangerzone"
if platform.system() == "Linux":
self.container_runtime = shutil.which("podman")
else:
self.container_runtime = shutil.which("docker")
# Languages supported by tesseract
self.ocr_languages = {
@ -467,12 +471,10 @@ class GlobalCommon(object):
# Load the container into podman
print("Installing Dangerzone container...")
p = subprocess.Popen(["podman", "load"], stdin=subprocess.PIPE)
p = subprocess.Popen([self.container_runtime, "load"], stdin=subprocess.PIPE)
chunk_size = 1024
compressed_container_path = self.get_resource_path(
"container/dangerzone.tar.gz"
)
compressed_container_path = self.get_resource_path("dangerzone-converter.tar.gz")
with gzip.open(compressed_container_path) as f:
while True:
chunk = f.read(chunk_size)
@ -493,18 +495,30 @@ class GlobalCommon(object):
"""
See if the podman container is installed. Linux only.
"""
print("Checking if container is already installed")
# Get the image id
with open(self.get_resource_path("image-id.txt")) as f:
image_id = f.read().strip()
expected_image_id = f.read().strip()
# See if this image is already installed
installed = False
if platform.system() == "Linux":
# Podman
images = json.loads(
subprocess.check_output(["podman", "image", "list", "--format", "json"])
subprocess.check_output([self.container_runtime, "image", "list", "--format", "json"])
)
for image in images:
if image["Id"] == image_id:
if image["Id"] == expected_image_id:
installed = True
break
else:
# Docker
found_image_id = subprocess.check_output([self.container_runtime, "image", "list", "--format", "{{.ID}}", self.container_name], text=True)
if found_image_id.strip() == expected_image_id:
installed = True
else:
print(f"Image {found_image_id} is installed, not {expected_image_id}")
return installed

View file

@ -95,9 +95,7 @@ class InstallContainerThread(QtCore.QThread):
self.global_common = global_common
def run(self):
if not self.global_common.is_container_installed():
self.global_common.install_container()
self.finished.emit()
@ -170,6 +168,7 @@ class WaitingWidget(QtWidgets.QWidget):
state = "install_container"
# Update the state
print(f"Dangerzone state: {state}")
self.state_change(state)
def state_change(self, state):

View file

@ -1,467 +0,0 @@
import os
import subprocess
import uuid
import pipes
import tempfile
import socket
import random
import getpass
import json
import psutil
import time
import platform
import shutil
from PySide2 import QtCore
class Vm(QtCore.QObject):
STATE_OFF = 0
STATE_STARTING = 1
STATE_ON = 2
STATE_FAIL = 3
vm_state_change = QtCore.Signal(int)
def __init__(self, global_common, allow_vm_login):
super(Vm, self).__init__()
self.global_common = global_common
self.allow_vm_login = allow_vm_login
# VM starts off
self.state = self.STATE_OFF
# Ports for ssh services
self.sshd_port = None
self.sshd_tunnel_port = None
# Folder to hold temporary files related to the VM
self.state_dir = tempfile.TemporaryDirectory()
self.ssh_host_key_path = os.path.join(self.state_dir.name, "host_ed25519")
self.ssh_host_pubkey_path = os.path.join(
self.state_dir.name, "host_ed25519.pub"
)
self.ssh_client_key_path = os.path.join(self.state_dir.name, "client_ed25519")
self.ssh_client_pubkey_path = os.path.join(
self.state_dir.name, "client_ed25519.pub"
)
self.sshd_pid_path = os.path.join(self.state_dir.name, "sshd.pid")
self.sshd_log_path = os.path.join(self.state_dir.name, "sshd.log")
self.vm_info_path = os.path.join(self.state_dir.name, "info.json")
self.vm_disk_img_path = os.path.join(self.state_dir.name, "disk.img")
self.vm_iso_path = self.global_common.get_resource_path("vm/dangerzone.iso")
if platform.system() == "Darwin":
self.ssh_keygen_path = shutil.which("ssh-keygen")
self.sshd_path = shutil.which("sshd")
# Processes
self.vpnkit_p = None
self.hyperkit_p = None
self.devnull = open(os.devnull, "w")
# Relevant paths
self.vpnkit_path = self.global_common.get_resource_path("bin/vpnkit")
self.hyperkit_path = self.global_common.get_resource_path("bin/hyperkit")
self.vm_kernel_path = self.global_common.get_resource_path("vm/kernel")
self.vm_initramfs_path = self.global_common.get_resource_path(
"vm/initramfs.img"
)
# Temporary files related to the VM
self.vpnkit_sock_path = os.path.join(self.state_dir.name, "vpnkit.eth.sock")
self.hyperkit_pid_path = os.path.join(self.state_dir.name, "hyperkit.pid")
# UDID for VM
self.vm_uuid = str(uuid.uuid4())
self.vm_cmdline = (
"earlyprintk=serial console=ttyS0 modules=loop,squashfs,sd-mod"
)
if platform.system() == "Windows":
self.vboxmanage_path = (
"C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe"
)
self.ssh_keygen_path = os.path.join(
self.global_common.get_resource_path("bin"), "ssh-keygen.exe"
)
self.sshd_path = os.path.join(
self.global_common.get_resource_path("bin"), "sshd.exe"
)
# Threads
self.wait_t = None
def __del__(self):
self.stop()
def start(self):
print("Starting VM\n")
# Delete keys if they already exist
for filename in [
self.ssh_host_key_path,
self.ssh_host_pubkey_path,
self.ssh_client_key_path,
self.ssh_client_pubkey_path,
]:
if os.path.exists(filename):
os.remove(filename)
# Find an open port
self.sshd_port = self.find_open_port()
self.sshd_tunnel_port = self.find_open_port()
# Generate new keys
subprocess.run(
[
self.ssh_keygen_path,
"-t",
"ed25519",
"-C",
"dangerzone-host",
"-N",
"",
"-f",
self.ssh_host_key_path,
],
stdout=self.devnull,
stderr=self.devnull,
)
subprocess.run(
[
self.ssh_keygen_path,
"-t",
"ed25519",
"-C",
"dangerzone-client",
"-N",
"",
"-f",
self.ssh_client_key_path,
],
stdout=self.devnull,
stderr=self.devnull,
)
with open(self.ssh_client_key_path) as f:
self.ssh_client_key = f.read()
with open(self.ssh_client_pubkey_path) as f:
self.ssh_client_pubkey = f.read()
# Start an sshd service on this port
args = [
self.sshd_path,
"-4",
"-E",
self.sshd_log_path,
"-o",
f"PidFile={self.sshd_pid_path}",
"-o",
f"HostKey={self.ssh_host_key_path}",
"-o",
f"ListenAddress=127.0.0.1:{self.sshd_port}",
"-o",
f"AllowUsers={getpass.getuser()}",
"-o",
"PasswordAuthentication=no",
"-o",
"PubkeyAuthentication=yes",
"-o",
"Compression=yes",
"-o",
"UseDNS=no",
"-o",
f"AuthorizedKeysFile={self.ssh_client_pubkey_path}",
"-o",
"ForceCommand=/sbin/nologin",
]
args_str = " ".join(pipes.quote(s) for s in args)
print("> " + args_str)
subprocess.run(args, stdout=self.devnull, stderr=self.devnull)
if platform.system() == "Darwin":
self.start_macos()
if platform.system() == "Windows":
self.start_windows()
def start_macos(self):
self.state = self.STATE_STARTING
self.vm_state_change.emit(self.state)
# Create a JSON object to pass into the VM
# This is a 512kb file that starts with a JSON object, followed by null bytes
guest_vm_info = {
"id_ed25519": self.ssh_client_key,
"id_ed25519.pub": self.ssh_client_pubkey,
"user": getpass.getuser(),
"ip": "192.168.65.2",
"port": self.sshd_port,
"tunnel_port": self.sshd_tunnel_port,
}
with open(self.vm_disk_img_path, "wb") as f:
guest_vm_info_bytes = json.dumps(guest_vm_info).encode()
f.write(guest_vm_info_bytes)
f.write(b"\x00" * (512 * 1024 - len(guest_vm_info_bytes)))
# Create a JSON object for the container process to read
vm_info = {
"client_key_path": self.ssh_client_key_path,
"tunnel_port": self.sshd_tunnel_port,
}
with open(self.vm_info_path, "w") as f:
f.write(json.dumps(vm_info))
# Run VPNKit
args = [
self.vpnkit_path,
"--ethernet",
self.vpnkit_sock_path,
"--gateway-ip",
"192.168.65.1",
"--host-ip",
"192.168.65.2",
"--lowest-ip",
"192.168.65.3",
"--highest-ip",
"192.168.65.254",
]
args_str = " ".join(pipes.quote(s) for s in args)
print("> " + args_str)
self.vpnkit_p = subprocess.Popen(args, stdout=self.devnull, stderr=self.devnull)
# Run Hyperkit
args = [
self.hyperkit_path,
"-F",
self.hyperkit_pid_path,
"-A",
"-u",
"-m",
"4G",
"-c",
"2",
"-s",
"0:0,hostbridge",
"-s",
"31,lpc",
"-l",
"com1,stdio",
"-s",
f"1:0,ahci-cd,{self.vm_iso_path}",
"-s",
f"2:0,virtio-vpnkit,path={self.vpnkit_sock_path}",
"-s",
f"3:0,virtio-blk,{self.vm_disk_img_path}",
"-U",
self.vm_uuid,
"-f",
f'kexec,{self.vm_kernel_path},{self.vm_initramfs_path},"{self.vm_cmdline}"',
]
args_str = " ".join(pipes.quote(s) for s in args)
print("> " + args_str)
if self.allow_vm_login:
# Start the VM with the ability to login
self.hyperkit_p = subprocess.Popen(args)
else:
# Start the VM without ability to login
self.hyperkit_p = subprocess.Popen(
args, stdout=self.devnull, stderr=self.devnull, stdin=self.devnull
)
# Wait for SSH thread
self.wait_t = WaitForSsh(self.sshd_tunnel_port)
self.wait_t.connected.connect(self.vm_connected)
self.wait_t.timeout.connect(self.vm_timeout)
self.wait_t.start()
def start_windows(self):
vm_name = "dangezone-podman"
basefolder_path = os.path.join(
self.global_common.appdata_path, "virtualbox-basefolder"
)
# See if we already have a VM
exists = False
for line in subprocess.check_output([self.vboxmanage_path, "list", "vms"]):
name = line.split()[0].lstrip('"').rstrip('"')
if name == vm_name:
exists = True
break
# Create the VM
if not exists:
subprocess.run(
[
self.vboxmanage_path,
"createvm",
"--name",
vm_name,
"--basefolder",
basefolder_path,
"--ostype",
"Linux_x64",
"--register",
]
)
# Configure the VM
subprocess.run(
[
self.vboxmanage_path,
"modifyvm",
vm_name,
"--memory",
"4096",
"--nic1",
"nat",
"--cableconnected1",
"on",
]
)
subprocess.run(
[
self.vboxmanage_path,
"storagectl",
vm_name,
"--name",
"DangerzoneBoot",
"--add",
"ide",
"--bootable",
"on",
]
)
subprocess.run(
[
self.vboxmanage_path,
"storageattach",
vm_name,
"--storagectl",
"DangerzoneBoot",
"--port",
"1",
"--device",
"1",
"--type",
"dvddrive",
"--medium",
self.vm_iso_path,
]
)
# Start the VM
subprocess.run([self.vboxmanage_path, "startvm", "--type", "headless"])
def vm_connected(self):
self.state = self.STATE_ON
self.vm_state_change.emit(self.state)
def vm_timeout(self):
self.state = self.STATE_FAIL
self.vm_state_change.emit(self.state)
def stop(self):
self.kill_sshd()
if platform.system() == "Darwin":
self.stop_macos()
if platform.system() == "Windows":
self.stop_windows()
def stop_macos(self):
# Kill existing processes
if self.vpnkit_p is not None:
self.vpnkit_p.terminate()
self.vpnkit_p = None
if self.hyperkit_p is not None:
self.hyperkit_p.terminate()
self.hyperkit_p = None
# Just to be extra sure
self.kill_hyperkit()
def stop_windows(self):
vm_name = "dangezone-podman"
subprocess.run(
[
self.vboxmanage_path,
"controlvm",
vm_name,
"poweroff",
]
)
def find_open_port(self):
with socket.socket() as tmpsock:
while True:
try:
tmpsock.bind(("127.0.0.1", random.randint(1024, 65535)))
break
except OSError:
pass
_, port = tmpsock.getsockname()
return port
def kill_sshd(self):
if os.path.exists(self.sshd_pid_path):
with open(self.sshd_pid_path) as f:
sshd_pid = int(f.read())
if psutil.pid_exists(sshd_pid):
try:
proc = psutil.Process(sshd_pid)
proc.kill()
except Exception:
pass
def kill_hyperkit(self):
if os.path.exists(self.hyperkit_pid_path):
with open(self.hyperkit_pid_path) as f:
hyperkit_pid = int(f.read())
if psutil.pid_exists(hyperkit_pid):
try:
proc = psutil.Process(hyperkit_pid)
proc.kill()
except Exception:
pass
class WaitForSsh(QtCore.QThread):
connected = QtCore.Signal()
timeout = QtCore.Signal()
def __init__(self, ssh_port):
super(WaitForSsh, self).__init__()
self.ssh_port = ssh_port
def run(self):
# Wait for the SSH port to be open
success = False
timeout_seconds = 45
start_ts = time.time()
while True:
sock = socket.socket()
try:
sock.connect(("127.0.0.1", int(self.ssh_port)))
sock.close()
success = True
print("\nVM is ready to use")
break
except Exception:
so_far = int(time.time() - start_ts)
print(f"\rWaiting for SSH port to be available ({so_far}s)", end="")
pass
time.sleep(1)
if time.time() - start_ts >= timeout_seconds:
self.timeout.emit()
break
if success:
self.connected.emit()

View file

@ -1,11 +0,0 @@
REM Build ISO
cd install\vm-builder
vagrant up
vagrant ssh -- dos2unix /vagrant/windows.sh
vagrant ssh -- /vagrant/windows.sh
vagrant halt
cd ..\..
REM Copy the ISO to resources
mkdir share\vm
copy install\vm-builder\vm\dangerzone.iso share\vm