248 lines
10 KiB
Python
248 lines
10 KiB
Python
|
#
|
||
|
# Copyright (C) 2020 Red Hat, Inc.
|
||
|
#
|
||
|
# This copyrighted material is made available to anyone wishing to use,
|
||
|
# modify, copy, or redistribute it subject to the terms and conditions of
|
||
|
# the GNU General Public License v.2, or (at your option) any later version.
|
||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||
|
# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
|
||
|
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||
|
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
|
||
|
# source code or documentation are not subject to the GNU General Public
|
||
|
# License and may only be used or replicated with the express permission of
|
||
|
# Red Hat, Inc.
|
||
|
#
|
||
|
import os
|
||
|
import glob
|
||
|
import shutil
|
||
|
|
||
|
from dasbus.typing import get_variant, Str
|
||
|
|
||
|
from pyanaconda.core import util
|
||
|
from pyanaconda.core.constants import RHSM_SYSPURPOSE_FILE_PATH
|
||
|
from pyanaconda.core.path import make_directories, join_paths
|
||
|
from pyanaconda.core.subscription import check_system_purpose_set
|
||
|
|
||
|
from pyanaconda.modules.common.task import Task
|
||
|
from pyanaconda.modules.common.errors.installation import InsightsConnectError, \
|
||
|
InsightsClientMissingError, SubscriptionTokenTransferError
|
||
|
|
||
|
from pyanaconda.anaconda_loggers import get_module_logger
|
||
|
log = get_module_logger(__name__)
|
||
|
|
||
|
|
||
|
class ConnectToInsightsTask(Task):
|
||
|
"""Connect the target system to Red Hat Insights."""
|
||
|
|
||
|
INSIGHTS_TOOL_PATH = "/usr/bin/insights-client"
|
||
|
|
||
|
def __init__(self, sysroot, subscription_attached, connect_to_insights):
|
||
|
"""Create a new task.
|
||
|
|
||
|
:param str sysroot: target system root path
|
||
|
:param bool subscription_attached: if True then the system has been subscribed,
|
||
|
False otherwise
|
||
|
:param bool connect_to_insights: if True then connect the system to Insights,
|
||
|
if False do nothing
|
||
|
"""
|
||
|
super().__init__()
|
||
|
self._sysroot = sysroot
|
||
|
self._subscription_attached = subscription_attached
|
||
|
self._connect_to_insights = connect_to_insights
|
||
|
|
||
|
@property
|
||
|
def name(self):
|
||
|
return "Connect the target system to Red Hat Insights"
|
||
|
|
||
|
def run(self):
|
||
|
"""Connect the target system to Red Hat Insights."""
|
||
|
# check if we should connect to Red Hat Insights
|
||
|
if not self._connect_to_insights:
|
||
|
log.debug("insights-connect-task: Insights not requested, skipping")
|
||
|
return
|
||
|
elif not self._subscription_attached:
|
||
|
log.debug("insights-connect-task: "
|
||
|
"Insights requested but target system is not subscribed, skipping")
|
||
|
return
|
||
|
|
||
|
insights_path = join_paths(self._sysroot, self.INSIGHTS_TOOL_PATH)
|
||
|
# check the insights client utility is available
|
||
|
if not os.path.isfile(insights_path):
|
||
|
raise InsightsClientMissingError(
|
||
|
"The insight-client tool ({}) is not available.".format(self.INSIGHTS_TOOL_PATH)
|
||
|
)
|
||
|
|
||
|
# tell the insights client to connect to insights
|
||
|
log.debug("insights-connect-task: connecting to insights")
|
||
|
rc = util.execWithRedirect(self.INSIGHTS_TOOL_PATH, ["--register"], root=self._sysroot)
|
||
|
if rc:
|
||
|
raise InsightsConnectError("Failed to connect to Red Hat Insights.")
|
||
|
|
||
|
|
||
|
class RestoreRHSMDefaultsTask(Task):
|
||
|
"""Restore RHSM defaults we changed for install time purposes.
|
||
|
|
||
|
At the moment this means setting the RHSM log level back to INFO
|
||
|
from DEBUG and making sure SSL certificate validation is enabled
|
||
|
(as we might turn it off for the installation run if requested by
|
||
|
the user).
|
||
|
"""
|
||
|
|
||
|
def __init__(self, rhsm_config_proxy):
|
||
|
"""Create a new task.
|
||
|
:param rhsm_config_proxy: DBus proxy for the RHSM Config object
|
||
|
"""
|
||
|
super().__init__()
|
||
|
self._rhsm_config_proxy = rhsm_config_proxy
|
||
|
|
||
|
@property
|
||
|
def name(self):
|
||
|
return "Restoring subscription manager defaults"
|
||
|
|
||
|
def run(self):
|
||
|
"""Restore RHSM defaults we changed.
|
||
|
|
||
|
We previously set the RHSM log level to DEBUG, which is also
|
||
|
reflected in rhsm.conf. This would mean RHSM would continue to
|
||
|
log in debug mode also on the system once rhsm.conf has been
|
||
|
copied over to the target system.
|
||
|
|
||
|
The same thing needs to be done for the server.insecure key
|
||
|
that we migh set to "1" previously on user request.
|
||
|
|
||
|
So set the log level back to INFO before we copy the config file
|
||
|
and make sure server.insecure is equal to "0".
|
||
|
"""
|
||
|
log.debug("subscription: setting RHSM log level back to INFO")
|
||
|
log.debug("subscription: making sure RHSM SSL certificate validation is enabled")
|
||
|
config_dict = {
|
||
|
"logging.default_log_level": get_variant(Str, "INFO"),
|
||
|
"server.insecure": get_variant(Str, "0")
|
||
|
}
|
||
|
|
||
|
# set all the values at once atomically
|
||
|
self._rhsm_config_proxy.SetAll(config_dict, "")
|
||
|
|
||
|
|
||
|
class TransferSubscriptionTokensTask(Task):
|
||
|
"""Transfer subscription tokens to the target system."""
|
||
|
|
||
|
RHSM_REPO_FILE_PATH = "/etc/yum.repos.d/redhat.repo"
|
||
|
RHSM_CONFIG_FILE_PATH = "/etc/rhsm/rhsm.conf"
|
||
|
RHSM_ENTITLEMENT_KEYS_PATH = "/etc/pki/entitlement"
|
||
|
RHSM_CONSUMER_KEY_PATH = "/etc/pki/consumer/key.pem"
|
||
|
RHSM_CONSUMER_CERT_PATH = "/etc/pki/consumer/cert.pem"
|
||
|
|
||
|
def __init__(self, sysroot, transfer_subscription_tokens):
|
||
|
"""Create a new task.
|
||
|
|
||
|
:param str sysroot: target system root path
|
||
|
:param bool transfer_subscription_tokens: if True attempt to transfer subscription
|
||
|
tokens to target system (we always transfer
|
||
|
system purpose data unconditionally)
|
||
|
"""
|
||
|
super().__init__()
|
||
|
self._sysroot = sysroot
|
||
|
self._transfer_subscription_tokens = transfer_subscription_tokens
|
||
|
|
||
|
@property
|
||
|
def name(self):
|
||
|
return "Transfer subscription tokens to target system"
|
||
|
|
||
|
def _copy_pem_files(self, input_folder, output_folder, not_empty=True):
|
||
|
"""Copy all pem files from input_folder to output_folder.
|
||
|
|
||
|
Files with the pem extension are generally encryption keys and certificates.
|
||
|
If output_folder does not exist, it & any parts of its path will
|
||
|
be created.
|
||
|
|
||
|
:param str input_folder: input folder for the pem files
|
||
|
:param str output_folder: output folder where to copy the pem files
|
||
|
:return: False if the input directory does not exists or is empty,
|
||
|
True after all pem files have be successfully copied
|
||
|
:rtype: bool
|
||
|
"""
|
||
|
# check the input folder exists
|
||
|
if not os.path.isdir(input_folder):
|
||
|
return False
|
||
|
# optionally check the input folder is not empty
|
||
|
if not_empty and not os.listdir(input_folder):
|
||
|
return False
|
||
|
# make sure the output folder exist
|
||
|
make_directories(output_folder)
|
||
|
# transfer all the pem files in the input folder
|
||
|
for pem_file_path in glob.glob(os.path.join(input_folder, "*.pem")):
|
||
|
shutil.copy(pem_file_path, output_folder)
|
||
|
# if we got this far the pem copy operation was a success
|
||
|
return True
|
||
|
|
||
|
def _copy_file(self, file_path, target_file_path):
|
||
|
if not os.path.isfile(file_path):
|
||
|
return False
|
||
|
# make sure the output folder exists
|
||
|
make_directories(os.path.dirname(target_file_path))
|
||
|
shutil.copy(file_path, target_file_path)
|
||
|
return True
|
||
|
|
||
|
def _transfer_file(self, target_path, target_name):
|
||
|
"""Transfer a file with nice logs and raise an exception if it does not exist."""
|
||
|
log.debug("subscription: transferring %s", target_name)
|
||
|
target_repo_file_path = join_paths(self._sysroot, target_path)
|
||
|
if not self._copy_file(target_path, target_repo_file_path):
|
||
|
msg = "{} ({}) is missing".format(target_name, self.RHSM_REPO_FILE_PATH)
|
||
|
raise SubscriptionTokenTransferError(msg)
|
||
|
|
||
|
def _transfer_system_purpose(self):
|
||
|
"""Transfer the system purpose file if present.
|
||
|
|
||
|
A couple notes:
|
||
|
- this might be needed even if the system has not been subscribed
|
||
|
during the installation and is therefore always attempted
|
||
|
- this means the syspurpose tool has been called in the installation
|
||
|
environment & we need to transfer the results to the target system
|
||
|
"""
|
||
|
if check_system_purpose_set(sysroot="/"):
|
||
|
log.debug("subscription: transferring syspurpose file")
|
||
|
target_syspurpose_file_path = self._sysroot + RHSM_SYSPURPOSE_FILE_PATH
|
||
|
self._copy_file(RHSM_SYSPURPOSE_FILE_PATH, target_syspurpose_file_path)
|
||
|
|
||
|
def _transfer_entitlement_keys(self):
|
||
|
"""Transfer the entitlement keys."""
|
||
|
log.debug("subscription: transferring entitlement keys")
|
||
|
target_entitlement_keys_path = self._sysroot + self.RHSM_ENTITLEMENT_KEYS_PATH
|
||
|
if not self._copy_pem_files(self.RHSM_ENTITLEMENT_KEYS_PATH, target_entitlement_keys_path):
|
||
|
msg = "RHSM entitlement keys (from {}) are missing.".format(
|
||
|
self.RHSM_ENTITLEMENT_KEYS_PATH)
|
||
|
raise SubscriptionTokenTransferError(msg)
|
||
|
|
||
|
def run(self):
|
||
|
"""Transfer the subscription tokens to the target system.
|
||
|
|
||
|
Otherwise the target system would have to be registered and subscribed again
|
||
|
due to missing subscription tokens.
|
||
|
"""
|
||
|
self._transfer_system_purpose()
|
||
|
|
||
|
# the other subscription tokens are only relevant if the system has been subscribed
|
||
|
if not self._transfer_subscription_tokens:
|
||
|
log.debug("subscription: transfer of subscription tokens not requested")
|
||
|
return
|
||
|
|
||
|
# transfer entitlement keys
|
||
|
self._transfer_entitlement_keys()
|
||
|
|
||
|
# transfer the consumer key
|
||
|
self._transfer_file(self.RHSM_CONSUMER_KEY_PATH, "RHSM consumer key")
|
||
|
|
||
|
# transfer the consumer cert
|
||
|
self._transfer_file(self.RHSM_CONSUMER_CERT_PATH, "RHSM consumer cert")
|
||
|
|
||
|
# transfer the redhat.repo file
|
||
|
self._transfer_file(self.RHSM_REPO_FILE_PATH, "RHSM repo file")
|
||
|
|
||
|
# transfer the RHSM config file
|
||
|
self._transfer_file(self.RHSM_CONFIG_FILE_PATH, "RHSM config file")
|