aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--man/en/sosreport.128
-rw-r--r--sos/__init__.py10
-rw-r--r--sos/archive.py63
-rw-r--r--sos/sosreport.py20
-rw-r--r--tests/archive_tests.py3
5 files changed, 113 insertions, 11 deletions
diff --git a/man/en/sosreport.1 b/man/en/sosreport.1
index b0adcd8b..b6051edc 100644
--- a/man/en/sosreport.1
+++ b/man/en/sosreport.1
@@ -22,6 +22,8 @@ sosreport \- Collect and package diagnostic and support data
[--log-size]\fR
[--all-logs]\fR
[-z|--compression-type method]\fR
+ [--encrypt-key KEY]\fR
+ [--encrypt-pass PASS]\fR
[--experimental]\fR
[-h|--help]\fR
@@ -120,6 +122,32 @@ increase the size of reports.
.B \-z, \--compression-type METHOD
Override the default compression type specified by the active policy.
.TP
+.B \--encrypt-key KEY
+Encrypts the resulting archive that sosreport produces using GPG. KEY must be
+an existing key in the user's keyring as GPG does not allow for keyfiles.
+KEY can be any value accepted by gpg's 'recipient' option.
+
+Note that the user running sosreport must match the user owning the keyring
+from which keys will be obtained. In particular this means that if sudo is
+used to run sosreport, the keyring must also be set up using sudo
+(or direct shell access to the account).
+
+Users should be aware that encrypting the final archive will result in sos
+using double the amount of temporary disk space - the encrypted archive must be
+written as a separate, rather than replacement, file within the temp directory
+that sos writes the archive to. However, since the encrypted archive will be
+the same size as the original archive, there is no additional space consumption
+once the temporary directory is removed at the end of execution.
+
+This means that only the encrypted archive is present on disk after sos
+finishes running.
+
+If encryption fails for any reason, the original unencrypted archive is
+preserved instead.
+.TP
+.B \--encrypt-pass PASS
+The same as \--encrypt-key, but use the provided PASS for symmetric encryption
+rather than key-pair encryption.
.TP
.B \--batch
Generate archive without prompting for interactive input.
diff --git a/sos/__init__.py b/sos/__init__.py
index ef4524c6..cd9779bd 100644
--- a/sos/__init__.py
+++ b/sos/__init__.py
@@ -45,10 +45,10 @@ _sos = _default
_arg_names = [
'add_preset', 'alloptions', 'all_logs', 'batch', 'build', 'case_id',
'chroot', 'compression_type', 'config_file', 'desc', 'debug', 'del_preset',
- 'enableplugins', 'experimental', 'label', 'list_plugins', 'list_presets',
- 'list_profiles', 'log_size', 'noplugins', 'noreport', 'note',
- 'onlyplugins', 'plugopts', 'preset', 'profiles', 'quiet', 'sysroot',
- 'threads', 'tmp_dir', 'verbosity', 'verify'
+ 'enableplugins', 'encrypt_key', 'encrypt_pass', 'experimental', 'label',
+ 'list_plugins', 'list_presets', 'list_profiles', 'log_size', 'noplugins',
+ 'noreport', 'note', 'onlyplugins', 'plugopts', 'preset', 'profiles',
+ 'quiet', 'sysroot', 'threads', 'tmp_dir', 'verbosity', 'verify'
]
#: Arguments with non-zero default values
@@ -84,6 +84,8 @@ class SoSOptions(object):
del_preset = ""
desc = ""
enableplugins = []
+ encrypt_key = None
+ encrypt_pass = None
experimental = False
label = ""
list_plugins = False
diff --git a/sos/archive.py b/sos/archive.py
index e153c09a..263e3dd3 100644
--- a/sos/archive.py
+++ b/sos/archive.py
@@ -142,11 +142,12 @@ class FileCacheArchive(Archive):
_archive_root = ""
_archive_name = ""
- def __init__(self, name, tmpdir, policy, threads):
+ def __init__(self, name, tmpdir, policy, threads, enc_opts):
self._name = name
self._tmp_dir = tmpdir
self._policy = policy
self._threads = threads
+ self.enc_opts = enc_opts
self._archive_root = os.path.join(tmpdir, name)
with self._path_lock:
os.makedirs(self._archive_root, 0o700)
@@ -384,12 +385,65 @@ class FileCacheArchive(Archive):
os.stat(self._archive_name).st_size))
self.method = method
try:
- return self._compress()
+ res = self._compress()
except Exception as e:
exp_msg = "An error occurred compressing the archive: "
self.log_error("%s %s" % (exp_msg, e))
return self.name()
+ if self.enc_opts['encrypt']:
+ try:
+ return self._encrypt(res)
+ except Exception as e:
+ exp_msg = "An error occurred encrypting the archive:"
+ self.log_error("%s %s" % (exp_msg, e))
+ return res
+ else:
+ return res
+
+ def _encrypt(self, archive):
+ """Encrypts the compressed archive using GPG.
+
+ If encryption fails for any reason, it should be logged by sos but not
+ cause execution to stop. The assumption is that the unencrypted archive
+ would still be of use to the user, and/or that the end user has another
+ means of securing the archive.
+
+ Returns the name of the encrypted archive, or raises an exception to
+ signal that encryption failed and the unencrypted archive name should
+ be used.
+ """
+ arc_name = archive.replace("sosreport-", "secured-sosreport-")
+ arc_name += ".gpg"
+ enc_cmd = "gpg --batch -o %s " % arc_name
+ env = None
+ if self.enc_opts["key"]:
+ # need to assume a trusted key here to be able to encrypt the
+ # archive non-interactively
+ enc_cmd += "--trust-model always -e -r %s " % self.enc_opts["key"]
+ enc_cmd += archive
+ if self.enc_opts["password"]:
+ # prevent change of gpg options using a long password, but also
+ # prevent the addition of quote characters to the passphrase
+ passwd = "%s" % self.enc_opts["password"].replace('\'"', '')
+ env = {"sos_gpg": passwd}
+ enc_cmd += "-c --passphrase-fd 0 "
+ enc_cmd = "/bin/bash -c \"echo $sos_gpg | %s\"" % enc_cmd
+ enc_cmd += archive
+ r = sos_get_command_output(enc_cmd, timeout=0, env=env)
+ if r["status"] == 0:
+ return arc_name
+ elif r["status"] == 2:
+ if self.enc_opts["key"]:
+ msg = "Specified key not in keyring"
+ else:
+ msg = "Could not read passphrase"
+ else:
+ # TODO: report the actual error from gpg. Currently, we cannot as
+ # sos_get_command_output() does not capture stderr
+ msg = "gpg exited with code %s" % r["status"]
+ raise Exception(msg)
+
# Compatibility version of the tarfile.TarFile class. This exists to allow
# compatibility with PY2 runtimes that lack the 'filter' parameter to the
@@ -468,8 +522,9 @@ class TarFileArchive(FileCacheArchive):
method = None
_with_selinux_context = False
- def __init__(self, name, tmpdir, policy, threads):
- super(TarFileArchive, self).__init__(name, tmpdir, policy, threads)
+ def __init__(self, name, tmpdir, policy, threads, enc_opts):
+ super(TarFileArchive, self).__init__(name, tmpdir, policy, threads,
+ enc_opts)
self._suffix = "tar"
self._archive_name = os.path.join(tmpdir, self.name())
diff --git a/sos/sosreport.py b/sos/sosreport.py
index 60802617..00c3e811 100644
--- a/sos/sosreport.py
+++ b/sos/sosreport.py
@@ -316,6 +316,13 @@ def _parse_args(args):
preset_grp.add_argument("--del-preset", type=str, action="store",
help="Delete the named command line preset")
+ encrypt_grp = parser.add_mutually_exclusive_group()
+ encrypt_grp.add_argument("--encrypt-key",
+ help="Encrypt the final archive using a GPG "
+ "key-pair")
+ encrypt_grp.add_argument("--encrypt-pass",
+ help="Encrypt the final archive using a password")
+
return parser.parse_args(args)
@@ -431,16 +438,25 @@ class SoSReport(object):
return self.tempfile_util.new()
def _set_archive(self):
+ enc_opts = {
+ 'encrypt': True if (self.opts.encrypt_pass or
+ self.opts.encrypt_key) else False,
+ 'key': self.opts.encrypt_key,
+ 'password': self.opts.encrypt_pass
+ }
+
archive_name = os.path.join(self.tmpdir,
self.policy.get_archive_name())
if self.opts.compression_type == 'auto':
auto_archive = self.policy.get_preferred_archive()
self.archive = auto_archive(archive_name, self.tmpdir,
- self.policy, self.opts.threads)
+ self.policy, self.opts.threads,
+ enc_opts)
else:
self.archive = TarFileArchive(archive_name, self.tmpdir,
- self.policy, self.opts.threads)
+ self.policy, self.opts.threads,
+ enc_opts)
self.archive.set_debug(True if self.opts.debug else False)
diff --git a/tests/archive_tests.py b/tests/archive_tests.py
index b4dd8d0f..e5b329b5 100644
--- a/tests/archive_tests.py
+++ b/tests/archive_tests.py
@@ -19,7 +19,8 @@ class TarFileArchiveTest(unittest.TestCase):
def setUp(self):
self.tmpdir = tempfile.mkdtemp()
- self.tf = TarFileArchive('test', self.tmpdir, Policy(), 1)
+ enc = {'encrypt': False}
+ self.tf = TarFileArchive('test', self.tmpdir, Policy(), 1, enc)
def tearDown(self):
shutil.rmtree(self.tmpdir)