220 lines
8.6 KiB
Python
220 lines
8.6 KiB
Python
|
#
|
||
|
# gnome_remote_desktop.py: GRD related installer functionality
|
||
|
#
|
||
|
# Copyright (C) 2024 Red Hat, Inc. All rights reserved.
|
||
|
#
|
||
|
# This program is free software; you can redistribute it and/or modify
|
||
|
# it under the terms of the GNU General Public License as published by
|
||
|
# the Free Software Foundation; either version 2 of the License, or
|
||
|
# (at your option) any later version.
|
||
|
#
|
||
|
# This program is distributed in the hope that it will be useful,
|
||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
# GNU General Public License for more details.
|
||
|
#
|
||
|
# You should have received a copy of the GNU General Public License
|
||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
#
|
||
|
|
||
|
import os
|
||
|
import sys
|
||
|
import time
|
||
|
from pyanaconda import network
|
||
|
from pyanaconda.core import util
|
||
|
from pyanaconda.core.util import execWithCapture, startProgram
|
||
|
import socket
|
||
|
from systemd import journal
|
||
|
|
||
|
from pyanaconda.core.i18n import _
|
||
|
|
||
|
from pyanaconda.anaconda_loggers import get_stdout_logger
|
||
|
stdoutLog = get_stdout_logger()
|
||
|
|
||
|
from pyanaconda.anaconda_loggers import get_module_logger
|
||
|
log = get_module_logger(__name__)
|
||
|
|
||
|
OPENSSL_BINARY_PATH = "/usr/bin/openssl"
|
||
|
|
||
|
GRD_RDP_CERT_DIR = "/root/.local/share/gnome-remote-desktop/"
|
||
|
GRD_RDP_CERT = "/root/.local/share/gnome-remote-desktop/rdp.crt"
|
||
|
GRD_RDP_CERT_KEY = "/root/.local/share/gnome-remote-desktop/rdp.key"
|
||
|
|
||
|
GRD_BINARY_PATH = "/usr/libexec/gnome-remote-desktop-daemon"
|
||
|
GRD_PID = None
|
||
|
GRD_LOG_FILE = "/tmp/gnome-remote-desktop.log"
|
||
|
|
||
|
grd_process = None
|
||
|
|
||
|
# partially based on: https://copr.fedorainfracloud.org/coprs/jadahl/headless-sessions/
|
||
|
|
||
|
|
||
|
def shutdown_server():
|
||
|
"""Try to shutdown running GNOME Remote Desktop instance
|
||
|
|
||
|
Why is this function on the module level and not in the GRDServer class ?
|
||
|
|
||
|
As the server needs to be killed from the exit handler, it would have
|
||
|
to somehow get to the GRD instance. Like this, it can just kill
|
||
|
it by calling a function of the GNOME Remote Desktop module, that
|
||
|
has access to the GRD process.
|
||
|
"""
|
||
|
|
||
|
if grd_process is None:
|
||
|
log.error("Cannot shutdown GNOME Remote Desktop - process handle missing")
|
||
|
else:
|
||
|
try:
|
||
|
grd_process.kill()
|
||
|
log.info("The GNOME Remote Desktop session has been shut down.")
|
||
|
except SystemError as e:
|
||
|
log.error("Shutdown of the GNOME Remote Desktop session failed with exception:\n%s", e)
|
||
|
|
||
|
|
||
|
class GRDServer(object):
|
||
|
|
||
|
def __init__(self, anaconda, root="/", ip=None, name=None,
|
||
|
rdp_username="", rdp_password=""):
|
||
|
self.root = root
|
||
|
self.ip = ip
|
||
|
self.rdp_username = rdp_username
|
||
|
self.name = name
|
||
|
self.rdp_password = rdp_password
|
||
|
self.anaconda = anaconda
|
||
|
self.log = get_stdout_logger()
|
||
|
|
||
|
# check if we the needed dependencies for using the GNOME remote desktop
|
||
|
# & abort the installation if not
|
||
|
|
||
|
# start by checking we have openssl available
|
||
|
if not os.path.exists(OPENSSL_BINARY_PATH):
|
||
|
stdoutLog.critical("No openssl binary found, can't generate certificates "
|
||
|
"for GNOME remote desktop. Aborting.")
|
||
|
util.ipmi_abort(scripts=self.anaconda.ksdata.scripts)
|
||
|
sys.exit(1)
|
||
|
|
||
|
# start by checking we have GNOME remote desktop available
|
||
|
if not os.path.exists(GRD_BINARY_PATH):
|
||
|
# we assume there that the main binary being present implies grdctl is there as well
|
||
|
stdoutLog.critical("GNOME remote desktop tooling is not available. Aborting.")
|
||
|
util.ipmi_abort(scripts=self.anaconda.ksdata.scripts)
|
||
|
sys.exit(1)
|
||
|
|
||
|
def _handle_rdp_certificates(self):
|
||
|
"""Generate SSL certificate and use it for incoming RDP connection."""
|
||
|
|
||
|
# then create folder for the certs
|
||
|
os.makedirs(GRD_RDP_CERT_DIR)
|
||
|
# generate the certs
|
||
|
execWithCapture(OPENSSL_BINARY_PATH,
|
||
|
["req", "-new",
|
||
|
"-newkey", "rsa:4096",
|
||
|
"-days", "720", "-nodes", "-x509",
|
||
|
"-subj", "/C=DE/ST=NONE/L=NONE/O=GNOME/CN=localhost",
|
||
|
"-out", GRD_RDP_CERT,
|
||
|
"-keyout", GRD_RDP_CERT_KEY]
|
||
|
)
|
||
|
# tell GNOME remote desktop to use these certificates
|
||
|
self._run_grdctl(["rdp", "set-tls-cert", GRD_RDP_CERT])
|
||
|
self._run_grdctl(["rdp", "set-tls-key", GRD_RDP_CERT_KEY])
|
||
|
|
||
|
def _set_rdp_username_and_password(self):
|
||
|
"""Set the RDP username and password."""
|
||
|
self._run_grdctl(["rdp", "set-credentials", self.rdp_username, self.rdp_password])
|
||
|
# disable view only mode
|
||
|
self._run_grdctl(["rdp", "disable-view-only"])
|
||
|
# also actually tell GNOME remote desktop that we (obviously) want to use RDP
|
||
|
self._run_grdctl(["rdp", "enable"])
|
||
|
|
||
|
def _find_network_address(self):
|
||
|
"""Find machine IP address, so we can show it to the user."""
|
||
|
|
||
|
# Network may be slow. Try for 5 seconds
|
||
|
tries = 5
|
||
|
while tries:
|
||
|
self.ip = network.get_first_ip_address()
|
||
|
if self.ip:
|
||
|
break
|
||
|
time.sleep(1)
|
||
|
tries -= 1
|
||
|
|
||
|
if not self.ip:
|
||
|
return
|
||
|
|
||
|
# FIXME: resolve this somehow,
|
||
|
# so it does not get stuck for 2 minutes in some VMs
|
||
|
|
||
|
if self.ip.find(':') != -1:
|
||
|
ipstr = "[%s]" % (self.ip,)
|
||
|
else:
|
||
|
ipstr = self.ip
|
||
|
|
||
|
try:
|
||
|
hinfo = socket.gethostbyaddr(self.ip)
|
||
|
if len(hinfo) == 3:
|
||
|
# Consider as coming from a valid DNS record only if single IP is returned
|
||
|
if len(hinfo[2]) == 1:
|
||
|
self.name = hinfo[0]
|
||
|
except socket.herror as e:
|
||
|
log.debug("Exception caught trying to get host name of %s: %s", ipstr, e)
|
||
|
|
||
|
def _run_grdctl(self, argv):
|
||
|
"""Run grdctl in the correct environment.
|
||
|
|
||
|
This is necessary, as grdctl requires $HOME to be pruned
|
||
|
or else the call might not have the desired effect.
|
||
|
"""
|
||
|
# we always run GRD in --headless mode
|
||
|
base_argv = ["--headless"]
|
||
|
# extend the base argv by the caller provided arguments
|
||
|
combined_argv = base_argv + argv
|
||
|
# make sure HOME is set to /root or else settings might not be saved
|
||
|
execWithCapture("grdctl", combined_argv, env_add={"HOME": "/root"})
|
||
|
|
||
|
def _start_grd_process(self):
|
||
|
"""Start the GNOME remote desktop process."""
|
||
|
try:
|
||
|
self.log.info("Starting GNOME remote desktop.")
|
||
|
global grd_process
|
||
|
# forward GRD stdout & stderr to Journal
|
||
|
grd_stdout_stream = journal.stream("gnome-remote-desktop", priority=journal.LOG_INFO)
|
||
|
grd_stderr_stream = journal.stream("gnome-remote-desktop", priority=journal.LOG_ERR)
|
||
|
grd_process = startProgram([GRD_BINARY_PATH, "--headless"],
|
||
|
stdout=grd_stdout_stream,
|
||
|
stderr=grd_stderr_stream,
|
||
|
env_add={"HOME": "/root"})
|
||
|
self.log.info("GNOME remote desktop is now running.")
|
||
|
except OSError:
|
||
|
stdoutLog.critical("Could not start GNOME remote desktop. Aborting.")
|
||
|
util.ipmi_abort(scripts=self.anaconda.ksdata.scripts)
|
||
|
sys.exit(1)
|
||
|
|
||
|
def start_grd_rdp(self):
|
||
|
# check if RDP user name & password are set
|
||
|
if not self.rdp_password or not self.rdp_username:
|
||
|
stdoutLog.critical("RDP user name or password not set. Aborting.")
|
||
|
util.ipmi_abort(scripts=self.anaconda.ksdata.scripts)
|
||
|
sys.exit(1)
|
||
|
|
||
|
self.log.info(_("Starting GNOME remote desktop in RDP mode..."))
|
||
|
|
||
|
# looks like we have some valid credentials, lets generate certificates &
|
||
|
# set the credentials
|
||
|
self._handle_rdp_certificates()
|
||
|
self.log.info(_("GNOME remote desktop RDP: SSL certificates generated & set"))
|
||
|
self._set_rdp_username_and_password()
|
||
|
self.log.info(_("GNOME remote desktop RDP: user name and password set"))
|
||
|
|
||
|
# next try to find our IP address or even the hostname
|
||
|
network.wait_for_connectivity()
|
||
|
try:
|
||
|
self._find_network_address()
|
||
|
self.log.info(_("GNOME remote desktop RDP IP: %s"), self.ip)
|
||
|
self.log.info(_("GNOME remote desktop RDP host name: %s"), self.name)
|
||
|
except (socket.herror, ValueError) as e:
|
||
|
stdoutLog.critical("GNOME remote desktop RDP: Could not find network address: %s", e)
|
||
|
util.ipmi_abort(scripts=self.anaconda.ksdata.scripts)
|
||
|
sys.exit(1)
|
||
|
|
||
|
# Lets start GRD.
|
||
|
self._start_grd_process()
|