Linux_scripts/rhui-check/rhui-check.py (457 lines of code) (raw):

#!/bin/env bash import argparse import logging import os import re import subprocess import time import sys #import urllib.request eus = 0 ###################################################### # logger the output of the script into /var/log/rhuicheck.log file ###################################################### class CustomFormatter(logging.Formatter): black = "\x1b[30;1m" grey = "\x1b[30;0m" red = "\x1b[31;20m" green = "\x1b[32;20m" yellow = "\x1b[33;20m" bright_red = "\x1b[91;1m" bold_red = "\x1b[31;1m" bold_green = "\x1b[32;1m" bold_yellow = "\x1b[33;1m" reset = "\x1b[0m" # format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)" format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" FORMATS = { logging.DEBUG: black + format + reset, logging.INFO: bold_green + format + reset, logging.WARNING: bold_yellow + format + reset, logging.ERROR: bright_red + format + reset, logging.CRITICAL: bold_red + format + reset } def format(self, record): log_fmt = self.FORMATS.get(record.levelno) formatter = logging.Formatter(log_fmt) return formatter.format(record) if os.geteuid() != 0: logger.critical('This script needs to execute with root privileges') logger.critical('You could leverage the sudo tool to gain administrative privileges') exit(1) def start_logging(debug_level = False): """This function sets up the logging configuration for the script and writes the log to /var/log/rhuicheck.log""" logger = logging.getLogger(__name__) console_handler = logging.StreamHandler() color_formatter = CustomFormatter() console_handler.setFormatter(color_formatter) logger.addHandler(console_handler) console_handler.setLevel(logging.INFO) if debug_level: console_handler.setLevel(logging.DEBUG) try: log_filename = '/var/log/rhuicheck.log' file_handler = logging.FileHandler(filename=log_filename) plain_formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") file_handler.setFormatter(plain_formatter) except: logger.critical("Unable to create log file in /var/log/rhuicheck.log, make sure the script is running with root privileges, the filesystem has enough space and it's not in Read-Only mode") exit(1) else: file_handler.setLevel(logging.DEBUG) logger.addHandler(file_handler) logger.setLevel(logging.DEBUG) return logger parser = argparse.ArgumentParser() parser.add_argument( '--debug','-d', action='store_true', help='Use DEBUG level') args = parser.parse_args() logger = start_logging(args.debug) try: import requests except ImportError: logger.critical("'requests' python module not found, but it's required for this test script, review your python installation.") exit(1) rhui3 = ['13.91.47.76', '40.85.190.91', '52.187.75.218'] rhui4 = ['52.136.197.163', '20.225.226.182', '52.142.4.99', '20.248.180.252', '20.24.186.80'] rhuius = ['13.72.186.193', '13.72.14.155', '52.224.249.194'] system_proxy = dict() bad_hosts = list() pattern = dict() pattern['clientcert'] = r'^/[/a-zA-Z0-9_\-]+\.(crt)$' pattern['clientkey'] = r'^/[/a-zA-Z0-9_\-]+\.(pem)$' pattern['repofile'] = r'^/[/a-zA-Z0-9_\-\.]+\.(repo)$' try: import configparser except ImportError: import ConfigParser as configparser class localParser(configparser.ConfigParser): def as_dict(self): d = dict(self.sections) for k in d: d[k] = dict(self._defaults, **d[k]) d[k].pop('__name__', None) return d def get_host(url): urlregex = '[^:]*://([^/]*)/.*' host_match = re.match(urlregex, url) return host_match.group(1) def validate_ca_certificates(): """ Used to verify whether the default certificate database has been modified or not """ logger.debug('Entering validate_ca-certificates()') reinstall_ca_bundle_link = 'https://learn.microsoft.com/troubleshoot/azure/virtual-machines/linux/troubleshoot-linux-rhui-certificate-issues?tabs=rhel7-eus%2Crhel7-noneus%2Crhel7-rhel-sap-apps%2Crhel8-rhel-sap-apps%2Crhel9-rhel-sap-apps#solution-4-update-or-reinstall-the-ca-certificates-package' try: result = subprocess.call('/usr/bin/rpm -V ca-certificates', shell=True) except: logger.error('Unable to check server side certificates installed in the server') logger.error('Use {} to reinstall the ca-certificates'.format(reinstall_ca_bundle_link)) exit(1) if result: logger.error('The ca-certificate package is invalid, you can reinstall it. Follow {} to reinstall it manually'.format(reinstall_ca_bundle_link)) exit(1) else: return True def connect_to_host(url, selection, mysection): try: uname = os.uname() except: logger.critical('Unable to identify OS version.') exit(1) try: basearch = uname.machine except AttributeError: basearch = uname[-1] try: baserelease = uname.release except AttributeError: baserelease = uname[2] if eus: fd = open('/etc/yum/vars/releasever') releasever = fd.readline().strip() else: releasever = re.sub(r'^.*el([0-9][0-9]*).*',r'\1',baserelease) if releasever == '7': releasever = '7Server' url = url+"/repodata/repomd.xml" url = url.replace('$releasever',releasever) url = url.replace('$basearch',basearch) logger.debug('baseurl for repo {} is {}'.format(mysection, url)) headers = {'content-type': 'application/json'} s = requests.Session() local_proxy = get_proxies(selection, mysection) cert = () try: cert=(selection.get(mysection, 'sslclientcert'), selection.get(mysection, 'sslclientkey')) except: logger.warning('Client certificate and/or client key attribute not found for {}, testing connectivity w/o certificates'.format(mysection)) cert=() try: r = s.get(url, cert=cert, headers=headers, timeout=5, proxies=local_proxy) except requests.exceptions.Timeout: logger.warning('TIMEOUT: Unable to reach RHUI URI {}'.format(url)) return False except requests.exceptions.SSLError: validate_ca_certificates() logger.warning('PROBLEM: MITM proxy misconfiguration. Proxy cannot intercept certs for {}'.format(url)) return 1 except requests.exceptions.ProxyError: logger.warning('PROBLEM: Unable to use the proxy gateway when connecting to RHUI server {}'.format(url)) return False except requests.exceptions.ConnectionError as e: logger.warning('PROBLEM: Unable to establish connectivity to RHUI server {}'.format(url)) logger.error('{}'.format(e)) return False except OSError: validate_ca_certificates() raise() except Exception as e: logger.warning('PROBLEM: Unknown error, unable to connect to the RHUI server {}'.format(url)) raise(e) return False else: if r.status_code == 200: logger.debug('The RC for this {} link is {}'.format(url, r.status_code)) return True elif r.status_code == 404: logger.error("Unable to find the contents for repo {}, make sure to use the correct version lock if you're using EUS repositories".format(mysection)) logger.error("For more detailed information and valid levels consult: https://access.redhat.com/support/policy/updates/errata#RHEL8_and_9_Life_Cycle") return False else: logger.warning('The RC for this {} link is {}'.format(url, r.status_code)) return False def rpm_names(): """ Identifies the RHUI repositories installed in the server and returns a list of RHUI rpms installed in the server. """ logger.debug('Entering repo_name()') result = subprocess.Popen("rpm -qa 'rhui-*'", shell=True, stdout=subprocess.PIPE) rpm_names = result.stdout.readlines() rpm_names = [ rpm.decode('utf-8').strip() for rpm in rpm_names ] if rpm_names: for rpm in rpm_names: logger.debug('Server has this RHUI pkg: {}'.format(rpm)) return(rpm_names) else: logger.critical('Could not find a specific RHUI package installed, please refer to the documentation and install the apropriate one. ') logger.critical('Consider using the following document to install RHUI support https://learn.microsoft.com/troubleshoot/azure/virtual-machines/troubleshoot-linux-rhui-certificate-issues#cause-3-rhui-package-is-missing') exit(1) def get_pkg_info(package_name): ''' Identifies rhui package name(s)''' logger.debug('Entering get_pkg_info()') logger.debug('Entering pkg_info function') try: result = subprocess.Popen(['rpm', '-q', '--list', package_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE) info = result.stdout.read().decode('utf-8').strip().split('\n') hash_info = {} for key in pattern.keys(): logger.debug('checking key {}'.format(key)) for data in info: logger.debug('checking key {} and data {}'.format(key, data)) if re.match(pattern[key], data): hash_info[key] = data break except: logger.critical('Failed to grab RHUI RPM details, rebuild RPM database.') exit(1) else: return hash_info def verify_pkg_info(package_name, rpm_info): ''' verifies basic elements of the RHUI package are present on the server ''' errors = 0 for keyname in pattern.keys(): if keyname not in rpm_info.keys(): logger.critical('{} file definition not found in RPM metadata, {} rpm needs to be reinstalled.'.format(keyname, package_name)) errors += 1 else: if not os.path.exists(rpm_info[keyname]): logger.critical('{} file not found in server, {} rpm needs to be reinstalled.'.format(keyname, package_name)) errors += 1 if errors: data_link = "https://learn.microsoft.com/troubleshoot/azure/virtual-machines/troubleshoot-linux-rhui-certificate-issues#cause-2-rhui-certificate-is-missing" logger.critical('follow {} for information to install the RHUI package'.format(data_link)) exit(1) return True def default_policy(): """"Returns a boolean whether the default encryption policies are set to default via the /etc/crypto-policies/config file, if it can't test it, the result will be set to true.""" try: uname = os.uname() except: logger.critical('Unable to identify OS version.') exit(1) try: baserelease = uname.release except AttributeError: baserelease = uname[2] policy_releasever = re.sub(r'^.*el([0-9][0-9]*).*',r'\1',baserelease) # return true for EL7 if policy_releasever == '7': return True try: policy = subprocess.check_output('/bin/update-crypto-policies --show', shell=True) policy = policy.decode('utf-8').strip() if policy != 'DEFAULT': return False except: return True return True def expiration_time(cert_path): """ Checks whether client certificate stored at cert_path has expired or not. """ logger.debug('Entering expiration_time()') logger.debug('Checking certificate expiration time.') try: result = subprocess.check_call('openssl x509 -in {} -checkend 0 > /dev/null 2>&1 '.format(cert_path),shell=True) except subprocess.CalledProcessError: logger.critical('Client RHUI Certificate has expired, please update the RHUI rpm.') logger.critical('Refer to: https://learn.microsoft.com/troubleshoot/azure/virtual-machines/troubleshoot-linux-rhui-certificate-issues#cause-1-rhui-client-certificate-is-expired') exit(1) if not default_policy(): logger.critical('Client crypto policies not set to DEFAULT.') logger.critical('Refer to: https://learn.microsoft.com/troubleshoot/azure/virtual-machines/linux/troubleshoot-linux-rhui-certificate-issues?tabs=rhel7-eus%2Crhel7-noneus%2Crhel7-rhel-sap-apps%2Crhel8-rhel-sap-apps%2Crhel9-rhel-sap-apps#cause-5-verification-error-in-rhel-version-8-or-9-ca-certificate-key-too-weak') exit(1) def read_yum_dnf_conf(): """Read /etc/yum.conf or /etc/dnf/dnf.conf searching for proxy information""" logger.debug('Entering read_yum_dnf_conf()') try: yumdnfdotconf = localParser(allow_no_value=True, strict=False) except TypeError: yumdnfdotconf = localParser(allow_no_value=True) try: file = '/etc/yum.conf' with open(file) as stream: yumdnfdotconf.read_string('[default]\n' + stream.read()) except AttributeError: yumdnfdotconf.add_section('[default]') yumdnfdotconf.read(file) except Exception as e: logger.error('Problems reading /etc/yum.conf, on RHEL8+ it is a symbolic link to /etc/dnf/dnf.conf.') raise return yumdnfdotconf def get_proxies(parser_object, mysection): ''' gets the proxy from a configparser section object pointd by the proxy variable if defined in the configuration file ''' proxy_info = dict() # proxy_regex = '(^[^:]*)(:(//)(([^:]*)(:([^@]*)){0,1}@){0,1}.*)?' proxy_regex = '(^[^:]*):(//)(([^:]*)(:([^@]*)){0,1}@){0,1}.*' for key in ['proxy', 'proxy_user', 'proxy_password']: try: value = parser_object.get(mysection, key) except configparser.NoOptionError: continue except Exception as e: logger.error('Problems handling the parser object.') raise else: proxy_info[key] = value try: myproxy = proxy_info['proxy'] if myproxy: ''' Get the scheme used in a proxy for example http from http://proxy.com/. Have to remove the last : as it is not part of the scheme. ''' proxy_match = re.match(proxy_regex, myproxy) if proxy_match: scheme = proxy_match.group(1) # proxy_info['scheme'] = scheme proxy_info['scheme'] = 'https' else: logger.critical('Invalid proxy configuration, please make sure you are using a valid proxy in your settings.') exit(1) else: return system_proxy except KeyError: return system_proxy if proxy_match.group(4) and ('proxy_user' in proxy_info.keys()): logger.warning('proxy definition already has a username defined and proxy_user is also defined, there might be conflicts using the proxy, repair.') if proxy_match.group(6) and ('proxy_password' in proxy_info.keys()): logger.warning('proxy definition already has a username and password defined and proxy_password is also defined, there might be conflicts using the proxy, repair.') if ('proxy_password' in proxy_info.keys() and proxy_info['proxy_password']) and ('proxy_user' not in proxy_info.keys()): logger.warning('proxy_password defined, but there is no proxy user, this could be causing problems.') logger.warning('ignoring proxy_password') if ('proxy_user' in proxy_info.keys() and proxy_info['proxy_user']) and not proxy_match.group(4): ####### need to insert proxy user and passwod in proxy link proxy_prefix = myproxy[:proxy_match.end(2)] proxy_suffix = myproxy[proxy_match.end(2):] if ('proxy_password' in proxy_info.keys()) and proxy_info['proxy_password'] and not proxy_match.group(6): myproxy = '{}{}:{}@{}'.format(proxy_prefix, proxy_info['proxy_user'], proxy_info['proxy_password'], proxy_suffix) else: myproxy = '{}{}@{}'.format(proxy_prefix, proxy_info['proxy_user'], proxy_suffix) logger.critical('Found proxy information in the config files, make sure connectivity works through the proxy.') return {proxy_info['scheme']: myproxy} def check_rhui_repo_file(path): """ Handling the consistency of the Red Hat repositories path: Indicates where the rhui repo is stored. returns: A RHUI repository configuration stored in a configparser structure, each repository is a section. """ logger.debug('Entering check_rhui_repo_file()') logger.debug('RHUI repo file is {}'.format(path)) try: reposconfig = localParser() try: with open(path) as stream: reposconfig.read_string('[default]\n' + stream.read()) except AttributeError: reposconfig.add_section('[default]') reposconfig.read(path) logger.debug('{}'.format(str(reposconfig.sections()))) return reposconfig except configparser.ParsingError: logger.critical('{} does not follow standard REPO config format, reinstall the RHUI rpm and try again.'.format(path)) exit(1) def check_repos(reposconfig): """ Checks whether the rhui-microsoft-azure-* repository exists and tests whether it's enabled or not.""" global eus logger.debug('Entering microsoft_repo()') rhuirepo = '^(rhui-)?microsoft.*' eusrepo = '.*-(eus|e4s)-.*' microsoft_reponame = '' enabled_repos = list() for repo_name in reposconfig.sections(): if re.match('\[*default\]*', repo_name): continue try: enabled = int(reposconfig.get(repo_name, 'enabled').strip()) except configparser.NoOptionError: enabled = 1 if re.match(rhuirepo, repo_name): microsoft_reponame = repo_name if enabled: logger.info('Using Microsoft RHUI repository {}'.format(repo_name)) else: logger.critical('Microsoft RHUI repository not enabled, please enable it with the following command.') logger.critical('yum-config-manager --enable {}'.format(repo_name)) exit(1) if enabled: # only check enabled repositories enabled_repos.append(repo_name) else: continue if re.match(eusrepo, repo_name): eus = 1 if not microsoft_reponame: reinstall_link = 'https://learn.microsoft.com/troubleshoot/azure/virtual-machines/linux/troubleshoot-linux-rhui-certificate-issues?source=recommendations&tabs=rhel7-eus%2Crhel7-noneus%2Crhel7-rhel-sap-apps%2Crhel8-rhel-sap-apps%2Crhel9-rhel-sap-apps#solution-2-reinstall-the-eus-non-eus-or-sap-rhui-package' logger.critical('Microsoft RHUI repository not found, reinstall the RHUI package following{}'.format(reinstall_link)) exit(1) if eus: if not os.path.exists('/etc/yum/vars/releasever'): logger.critical('Server is using EUS repostories but /etc/yum/vars/releasever file not found, please correct and test again.') logger.critical('Refer to: https://learn.microsoft.com/azure/virtual-machines/workloads/redhat/redhat-rhui?tabs=rhel7#rhel-eus-and-version-locking-rhel-vms, to select the appropriate RHUI repo') exit(1) if not eus: if os.path.exists('/etc/yum/vars/releasever'): logger.critical('Server is using non-EUS repos and /etc/yum/vars/releasever file found, correct and try again') logger.critical('Refer to: https://learn.microsoft.com/azure/virtual-machines/workloads/redhat/redhat-rhui?tabs=rhel7#rhel-eus-and-version-locking-rhel-vms, to select the appropriate RHUI repo') exit(1) return enabled_repos def ip_address_check(host): ''' Checks whether the parameter is within the RHUI4 infrastructure ''' try: import socket except ImportError: logger.critical("'socket' python module not found, but it is required for this test script, review your python installation.") exit(1) try: rhui_ip_address = socket.gethostbyname(host) if rhui_ip_address in rhui4: logger.debug('RHUI host {} points to RHUI4 infrastructure.'.format(host)) return True elif rhui_ip_address in rhui3 + rhuius: reinstall_link = 'https://learn.microsoft.com/troubleshoot/azure/virtual-machines/linux/troubleshoot-linux-rhui-certificate-issues?tabs=rhel7-eus%2Crhel7-noneus%2Crhel7-rhel-sap-apps%2Crhel8-rhel-sap-apps%2Crhel9-rhel-sap-apps#solution-2-reinstall-the-eus-non-eus-or-sap-rhui-package' logger.error('RHUI server {} points to decommissioned infrastructure, reinstall the RHUI package'.format(host)) logger.error('for more detailed information, use: {}'.format(reinstall_link)) bad_hosts.append(host) warnings = warnings + 1 return False else: logger.critical('RHUI server {} points to an invalid destination, validate /etc/hosts file for any invalid static RHUI IPs or reinstall the RHUI package.'.format(host)) logger.warning('Please make sure your server is able to resolve {} to one of the ip addresses'.format(host)) rhui_link = 'https://learn.microsoft.com/azure/virtual-machines/workloads/redhat/redhat-rhui?tabs=rhel7#the-ips-for-the-rhui-content-delivery-servers' logger.warning('listed in this document {}'.format(rhui_link)) return False except Exception as e: logger.warning('Unable to resolve IP address for host {}.'.format(host)) logger.warning('Please make sure your server is able to resolve {} to one of the IP addresses.'.format(host)) rhui_link = 'https://learn.microsoft.com/azure/virtual-machines/workloads/redhat/redhat-rhui?tabs=rhel7#the-ips-for-the-rhui-content-delivery-servers' logger.warning('listed in this document {}'.format(rhui_link )) logger.warning(e) return False def connect_to_repos(reposconfig, check_repos): """Downloads repomd.xml from each enabled repository.""" logger.debug('Entering connect_to_repos()') rhuirepo = '^rhui-microsoft.*' warnings = 0 for repo_name in check_repos: if re.match('\[*default\]*', repo_name): continue try: baseurl_info = reposconfig.get(repo_name, 'baseurl').strip().split('\n') except configparser.NoOptionError: reinstall_link = 'https://learn.microsoft.com/troubleshoot/azure/virtual-machines/linux/troubleshoot-linux-rhui-certificate-issues?source=recommendations&tabs=rhel7-eus%2Crhel7-noneus%2Crhel7-rhel-sap-apps%2Crhel8-rhel-sap-apps%2Crhel9-rhel-sap-apps#solution-2-reinstall-the-eus-non-eus-or-sap-rhui-package' logger.critical('The baseurl is a critical component of the repository stanza, and it is not found for repo {}'.format(repo_name)) logger.critical('Follow this link to reinstall the Microsoft RHUI repo {}'.format(reinstall_link)) exit(1) successes = 0 for url in baseurl_info: url_host = get_host(url) if not ip_address_check(url_host): bad_hosts.append(url_host) continue if connect_to_host(url, reposconfig, repo_name): successes += 1 if successes == 0: error_link = 'https://learn.microsoft.com/azure/virtual-machines/workloads/redhat/redhat-rhui?tabs=rhel9#the-ips-for-the-rhui-content-delivery-servers' logger.critical('PROBLEM: Unable to successfully download repository metadata from the any of the configured RHUI server(s).') logger.critical(' Ensure the server is able to resolve to a valid IP address, the communication is allowed to the IP addresses listed in the public document {}'.format(error_link)) logger.critical(' and if you are using EUS repositories, make sure you have a valid EUS version value in /etc/dnf/vars/releasever file.') sys.exit(1) logging.getLogger("requests").setLevel(logging.WARNING) logging.getLogger("urllib3").setLevel(logging.WARNING) yum_dnf_conf = read_yum_dnf_conf() system_proxy = get_proxies(yum_dnf_conf,'main') for package_name in rpm_names(): data = get_pkg_info(package_name) if verify_pkg_info(package_name, data): expiration_time(data['clientcert']) reposconfig = check_rhui_repo_file(data['repofile']) check_repos = check_repos(reposconfig) connect_to_repos(reposconfig, check_repos) logger.info('All communication tests to the RHUI infrastructure have passed, if problems persist, remove third party repositories and test again.') logger.info('The RHUI repository configuration file is {}, move any other configuration file to a temporary location and test again.'.format(data['repofile']))