# # 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")