aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--man/en/sos-report.114
-rw-r--r--sos/collector/__init__.py7
-rw-r--r--sos/policies/distros/__init__.py105
-rw-r--r--sos/report/__init__.py4
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()