diff options
-rw-r--r-- | man/en/sos-report.1 | 14 | ||||
-rw-r--r-- | sos/collector/__init__.py | 7 | ||||
-rw-r--r-- | sos/policies/distros/__init__.py | 105 | ||||
-rw-r--r-- | sos/report/__init__.py | 4 |
4 files changed, 125 insertions, 5 deletions
diff --git a/man/en/sos-report.1 b/man/en/sos-report.1 index c38753d4..799defaf 100644 --- a/man/en/sos-report.1 +++ b/man/en/sos-report.1 @@ -36,6 +36,7 @@ sosreport \- Collect and package diagnostic and support data [--upload] [--upload-url url] [--upload-user user]\fR [--upload-directory dir] [--upload-pass pass]\fR [--upload-no-ssl-verify] [--upload-method]\fR + [--upload-protocol protocol]\fR [--experimental]\fR [-h|--help]\fR @@ -385,6 +386,19 @@ untrusted by the local system. Default behavior is to perform SSL verification against all upload locations. .TP +.B \--upload-protocol PROTO +Manually specify the protocol to use for uploading to the target \fBupload-url\fR. + +Normally this is determined via the upload address, assuming that the protocol is part +of the address provided, e.g. 'https://example.com'. By using this option, sos will skip +the protocol check and use the method defined for the specified PROTO. + +For RHEL systems, setting this option to \fBsftp\fR will skip the initial attempt to +upload to the Red Hat Customer Portal, and only attempt an upload to Red Hat's SFTP server, +which is typically used as a fallback target. + +Valid values for PROTO are: 'auto' (default), 'https', 'ftp', 'sftp'. +.TP .B \--experimental Enable plugins marked as experimental. Experimental plugins may not have been tested for this port or may still be under active development. diff --git a/sos/collector/__init__.py b/sos/collector/__init__.py index 5d1c599a..1c742cf5 100644 --- a/sos/collector/__init__.py +++ b/sos/collector/__init__.py @@ -102,7 +102,8 @@ class SoSCollector(SoSComponent): 'upload_user': None, 'upload_pass': None, 'upload_method': 'auto', - 'upload_no_ssl_verify': False + 'upload_no_ssl_verify': False, + 'upload_protocol': 'auto' } def __init__(self, parser, parsed_args, cmdline_args): @@ -373,6 +374,10 @@ class SoSCollector(SoSComponent): action='store_true', help="Disable SSL verification for upload url" ) + collect_grp.add_argument("--upload-protocol", default='auto', + choices=['auto', 'https', 'ftp', 'sftp'], + help="Manually specify the upload protocol") + # Group the cleaner options together cleaner_grp = parser.add_argument_group( 'Cleaner/Masking Options', diff --git a/sos/policies/distros/__init__.py b/sos/policies/distros/__init__.py index 4268688c..9fe31513 100644 --- a/sos/policies/distros/__init__.py +++ b/sos/policies/distros/__init__.py @@ -20,7 +20,7 @@ from sos.policies.init_systems.systemd import SystemdInit from sos.policies.runtimes.podman import PodmanContainerRuntime from sos.policies.runtimes.docker import DockerContainerRuntime -from sos.utilities import shell_out +from sos.utilities import shell_out, is_executable try: @@ -295,7 +295,9 @@ class LinuxPolicy(Policy): 'sftp': self.upload_sftp, 'https': self.upload_https } - if '://' not in self.upload_url: + if self.commons['cmdlineopts'].upload_protocol in prots.keys(): + return prots[self.commons['cmdlineopts'].upload_protocol] + elif '://' not in self.upload_url: raise Exception("Must provide protocol in upload URL") prot, url = self.upload_url.split('://') if prot not in prots.keys(): @@ -361,7 +363,7 @@ class LinuxPolicy(Policy): self.upload_password or self._upload_password) - def upload_sftp(self): + def upload_sftp(self, user=None, password=None): """Attempts to upload the archive to an SFTP location. Due to the lack of well maintained, secure, and generally widespread @@ -371,7 +373,102 @@ class LinuxPolicy(Policy): Do not override this method with one that uses python-paramiko, as the upstream sos team will reject any PR that includes that dependency. """ - raise NotImplementedError("SFTP support is not yet implemented") + # if we somehow don't have sftp available locally, fail early + if not is_executable('sftp'): + raise Exception('SFTP is not locally supported') + + # soft dependency on python3-pexpect, which we need to use to control + # sftp login since as of this writing we don't have a viable solution + # via ssh python bindings commonly available among downstreams + try: + import pexpect + except ImportError: + raise Exception('SFTP upload requires python3-pexpect, which is ' + 'not currently installed') + + sftp_connected = False + + if not user: + user = self.get_upload_user() + if not password: + password = self.get_upload_password() + + # need to strip the protocol prefix here + sftp_url = self.get_upload_url().replace('sftp://', '') + sftp_cmd = "sftp -oStrictHostKeyChecking=no %s@%s" % (user, sftp_url) + ret = pexpect.spawn(sftp_cmd, encoding='utf-8') + + sftp_expects = [ + u'sftp>', + u'password:', + u'Connection refused', + pexpect.TIMEOUT, + pexpect.EOF + ] + + idx = ret.expect(sftp_expects, timeout=15) + + if idx == 0: + sftp_connected = True + elif idx == 1: + ret.sendline(password) + pass_expects = [ + u'sftp>', + u'Permission denied', + pexpect.TIMEOUT, + pexpect.EOF + ] + sftp_connected = ret.expect(pass_expects, timeout=10) == 0 + if not sftp_connected: + ret.close() + raise Exception("Incorrect username or password for %s" + % self.get_upload_url_string()) + elif idx == 2: + raise Exception("Connection refused by %s. Incorrect port?" + % self.get_upload_url_string()) + elif idx == 3: + raise Exception("Timeout hit trying to connect to %s" + % self.get_upload_url_string()) + elif idx == 4: + raise Exception("Unexpected error trying to connect to sftp: %s" + % ret.before) + + if not sftp_connected: + ret.close() + raise Exception("Unable to connect via SFTP to %s" + % self.get_upload_url_string()) + + put_cmd = 'put %s %s' % (self.upload_archive_name, + self._get_sftp_upload_name()) + ret.sendline(put_cmd) + + put_expects = [ + u'100%', + pexpect.TIMEOUT, + pexpect.EOF + ] + + put_success = ret.expect(put_expects, timeout=180) + + if put_success == 0: + ret.sendline('bye') + return True + elif put_success == 1: + raise Exception("Timeout expired while uploading") + elif put_success == 2: + raise Exception("Unknown error during upload: %s" % ret.before) + else: + raise Exception("Unexpected response from server: %s" % ret.before) + + def _get_sftp_upload_name(self): + """If a specific file name pattern is required by the SFTP server, + override this method in the relevant Policy. Otherwise the archive's + name on disk will be used + + :returns: Filename as it will exist on the SFTP server + :rtype: ``str`` + """ + return self.upload_archive_name.split('/')[-1] def _upload_https_put(self, archive, verify=True): """If upload_https() needs to use requests.put(), use this method. diff --git a/sos/report/__init__.py b/sos/report/__init__.py index df99186d..d4345409 100644 --- a/sos/report/__init__.py +++ b/sos/report/__init__.py @@ -120,6 +120,7 @@ class SoSReport(SoSComponent): 'upload_pass': None, 'upload_method': 'auto', 'upload_no_ssl_verify': False, + 'upload_protocol': 'auto', 'add_preset': '', 'del_preset': '' } @@ -307,6 +308,9 @@ class SoSReport(SoSComponent): report_grp.add_argument("--upload-no-ssl-verify", default=False, action='store_true', help="Disable SSL verification for upload url") + report_grp.add_argument("--upload-protocol", default='auto', + choices=['auto', 'https', 'ftp', 'sftp'], + help="Manually specify the upload protocol") # Group to make add/del preset exclusive preset_grp = report_grp.add_mutually_exclusive_group() |