Skip to content

Commit

Permalink
Mask Password in Log table when using the CLI (#11468)
Browse files Browse the repository at this point in the history
(cherry picked from commit 4e32546)
  • Loading branch information
kaxil committed Nov 18, 2020
1 parent b4652d6 commit 3cd9e9f
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 2 deletions.
13 changes: 12 additions & 1 deletion airflow/utils/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,20 @@ def _build_metrics(func_name, namespace):
:param namespace: Namespace instance from argparse
:return: dict with metrics
"""
sensitive_fields = {'-p', '--password', '--conn-password'}
full_command = list(sys.argv)
for idx, command in enumerate(full_command): # pylint: disable=too-many-nested-blocks
if command in sensitive_fields:
# For cases when password is passed as "--password xyz" (with space between key and value)
full_command[idx + 1] = "*" * 8
else:
# For cases when password is passed as "--password=xyz" (with '=' between key and value)
for sensitive_field in sensitive_fields:
if command.startswith('{}='.format(sensitive_field)):
full_command[idx] = '{}={}'.format(sensitive_field, "*" * 8)

metrics = {'sub_command': func_name, 'start_datetime': datetime.utcnow(),
'full_command': '{}'.format(list(sys.argv)), 'user': getpass.getuser()}
'full_command': '{}'.format(full_command), 'user': getpass.getuser()}

assert isinstance(namespace, Namespace)
tmp_dic = vars(namespace)
Expand Down
48 changes: 47 additions & 1 deletion tests/utils/test_cli_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@
# specific language governing permissions and limitations
# under the License.
#

import json
import os
import sys
import unittest
from argparse import Namespace
from contextlib import contextmanager
from datetime import datetime

from parameterized import parameterized

from airflow.utils import cli, cli_action_loggers
from tests.compat import mock


class CliUtilTest(unittest.TestCase):
Expand Down Expand Up @@ -71,6 +75,48 @@ def test_success_function(self):
with fail_action_logger_callback():
success_func(Namespace())

@parameterized.expand(
[
(
"airflow create_user -u test2 -l doe -f jon -e jdoe@apache.org -r admin --password test",
"airflow create_user -u test2 -l doe -f jon -e jdoe@apache.org -r admin --password ********"
),
(
"airflow create_user -u test2 -l doe -f jon -e jdoe@apache.org -r admin -p test",
"airflow create_user -u test2 -l doe -f jon -e jdoe@apache.org -r admin -p ********"
),
(
"airflow create_user -u test2 -l doe -f jon -e jdoe@apache.org -r admin --password=test",
"airflow create_user -u test2 -l doe -f jon -e jdoe@apache.org -r admin --password=********"
),
(
"airflow create_user -u test2 -l doe -f jon -e jdoe@apache.org -r admin -p=test",
"airflow create_user -u test2 -l doe -f jon -e jdoe@apache.org -r admin -p=********"
),
(
"airflow connections --add dsfs --conn-login asd --conn-password test --conn-type google",
"airflow connections --add dsfs --conn-login asd --conn-password ******** --conn-type google",
)
]
)
def test_cli_create_user_supplied_password_is_masked(self, given_command, expected_masked_command):
args = given_command.split()

expected_command = expected_masked_command.split()

exec_date = datetime.utcnow()
namespace = Namespace(dag_id='foo', task_id='bar', subcommand='test', execution_date=exec_date)
with mock.patch.object(sys, "argv", args):
metrics = cli._build_metrics(args[1], namespace)

self.assertTrue(metrics.get('start_datetime') <= datetime.utcnow())

log = metrics.get('log')
command = json.loads(log.extra).get('full_command') # type: str
# Replace single quotes to double quotes to avoid json decode error
command = json.loads(command.replace("'", '"'))
self.assertEqual(command, expected_command)


@contextmanager
def fail_action_logger_callback():
Expand Down

0 comments on commit 3cd9e9f

Please sign in to comment.