Pārlūkot izejas kodu

Mask the password when logging a MongoDB dump or restore command (#848).

Dan Helfman 1 gadu atpakaļ
vecāks
revīzija
7e51c41ebf
3 mainītis faili ar 40 papildinājumiem un 1 dzēšanām
  1. 1 0
      NEWS
  2. 20 1
      borgmatic/execute.py
  3. 19 0
      tests/unit/test_execute.py

+ 1 - 0
NEWS

@@ -9,6 +9,7 @@
  * #843: Add documentation link to Loki dashboard for borgmatic:
    https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#loki-hook
  * #847: Fix "--json" error when Borg includes non-JSON warnings in JSON output.
+ * #848: SECURITY: Mask the password when logging a MongoDB dump or restore command.
  * Fix handling of the NO_COLOR environment variable to ignore an empty value.
  * Add documentation about backing up containerized databases by configuring borgmatic to exec into
    a container to run a dump command:

+ 20 - 1
borgmatic/execute.py

@@ -220,6 +220,24 @@ def log_outputs(processes, exclude_stdouts, output_log_level, borg_local_path, b
         }
 
 
+SECRET_COMMAND_FLAG_NAMES = {'--password'}
+
+
+def mask_command_secrets(full_command):
+    '''
+    Given a command as a sequence, mask secret values for flags like "--password" in preparation for
+    logging.
+    '''
+    masked_command = []
+    previous_piece = None
+
+    for piece in full_command:
+        masked_command.append('***' if previous_piece in SECRET_COMMAND_FLAG_NAMES else piece)
+        previous_piece = piece
+
+    return tuple(masked_command)
+
+
 MAX_LOGGED_COMMAND_LENGTH = 1000
 
 
@@ -231,7 +249,8 @@ def log_command(full_command, input_file=None, output_file=None, environment=Non
     logger.debug(
         textwrap.shorten(
             ' '.join(
-                tuple(f'{key}=***' for key in (environment or {}).keys()) + tuple(full_command)
+                tuple(f'{key}=***' for key in (environment or {}).keys())
+                + mask_command_secrets(full_command)
             ),
             width=MAX_LOGGED_COMMAND_LENGTH,
             placeholder=' ...',

+ 19 - 0
tests/unit/test_execute.py

@@ -117,6 +117,24 @@ def test_append_last_lines_with_output_log_level_none_appends_captured_output():
     assert captured_output == ['captured', 'line']
 
 
+def test_mask_command_secrets_masks_password_flag_value():
+    assert module.mask_command_secrets(('cooldb', '--username', 'bob', '--password', 'pass')) == (
+        'cooldb',
+        '--username',
+        'bob',
+        '--password',
+        '***',
+    )
+
+
+def test_mask_command_secrets_passes_through_other_commands():
+    assert module.mask_command_secrets(('cooldb', '--username', 'bob')) == (
+        'cooldb',
+        '--username',
+        'bob',
+    )
+
+
 @pytest.mark.parametrize(
     'full_command,input_file,output_file,environment,expected_result',
     (
@@ -149,6 +167,7 @@ def test_append_last_lines_with_output_log_level_none_appends_captured_output():
 def test_log_command_logs_command_constructed_from_arguments(
     full_command, input_file, output_file, environment, expected_result
 ):
+    flexmock(module).should_receive('mask_command_secrets').replace_with(lambda command: command)
     flexmock(module.logger).should_receive('debug').with_args(expected_result).once()
 
     module.log_command(full_command, input_file, output_file, environment)