From 4b7f69a0812049f57ba5b2b3eb26734c089eb2b4 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 21 Jun 2023 13:39:55 -0700 Subject: [PATCH 1/8] Add _handle_configuration_without_authentication. --- tabpy/tabpy_server/app/app.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/tabpy/tabpy_server/app/app.py b/tabpy/tabpy_server/app/app.py index e0c74d13..c39ce415 100644 --- a/tabpy/tabpy_server/app/app.py +++ b/tabpy/tabpy_server/app/app.py @@ -394,9 +394,7 @@ def _parse_config(self, config_file): logger.critical(msg) raise RuntimeError(msg) else: - logger.info( - "Password file is not specified: " "Authentication is not enabled" - ) + self._handle_configuration_without_authentication() features = self._get_features() self.settings[SettingsParameters.ApiVersions] = {"v1": {"features": features}} @@ -471,6 +469,31 @@ def _parse_pwd_file(self): return succeeded + def _handle_configuration_without_authentication(self): + std_no_auth_msg = "Password file is not specified: Authentication is not enabled" + + if self.disable_auth_warning == True: + logger.info(std_no_auth_msg) + return + + confirm_no_auth_msg = "\nWARNING: This TabPy server is not currently configured for username/password authentication. " + + if self.settings[SettingsParameters.EvaluateEnabled]: + confirm_no_auth_msg += ("This means that, because the TABPY_EVALUATE_ENABLE feature is enabled, there is " + "the potential that unauthenticated individuals may be able to remotely execute code on this machine. ") + + confirm_no_auth_msg += ("We strongly advise against proceeding without authentication as it poses a significant security risk.\n\n" + "Do you wish to proceed without authentication? (y/N): ") + + confirm_no_auth_input = input(confirm_no_auth_msg) + + if confirm_no_auth_input == 'y': + logger.info(std_no_auth_msg) + else: + print("\nAborting start up. To enable authentication for your TabPy server, see " + "https://github.com/tableau/TabPy/blob/master/docs/server-config.md#authentication.") + exit() + def _get_features(self): features = {} From a106da2da169be3d1a1409e78b0a56e8ee455b73 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 21 Jun 2023 13:40:22 -0700 Subject: [PATCH 2/8] Add disable_auth_warning flag. --- tabpy/tabpy_server/app/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tabpy/tabpy_server/app/app.py b/tabpy/tabpy_server/app/app.py index c39ce415..18b83391 100644 --- a/tabpy/tabpy_server/app/app.py +++ b/tabpy/tabpy_server/app/app.py @@ -67,7 +67,8 @@ class TabPyApp: arrow_server = None max_request_size = None - def __init__(self, config_file): + def __init__(self, config_file, disable_auth_warning=True): + self.disable_auth_warning = disable_auth_warning if config_file is None: config_file = os.path.join( os.path.dirname(__file__), os.path.pardir, "common", "default.conf" From dd200acd3ce2b48b5e9f150d388f52f6d6b8d7b5 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 21 Jun 2023 13:41:11 -0700 Subject: [PATCH 3/8] Add --disable-auth-warning CLI flag. --- tabpy/tabpy.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tabpy/tabpy.py b/tabpy/tabpy.py index 50acf799..32feb532 100755 --- a/tabpy/tabpy.py +++ b/tabpy/tabpy.py @@ -3,11 +3,12 @@ Usage: tabpy [-h] | [--help] - tabpy [--config ] + tabpy [--config ] [--disable-auth-warning] Options: - -h --help Show this screen. - --config Path to a config file. + -h --help Show this screen. + --config Path to a config file. + --disable-auth-warning Disable authentication warning. """ import docopt @@ -38,9 +39,13 @@ def main(): args = docopt.docopt(__doc__) config = args["--config"] or None + disable_auth_warning = False + if args["--disable-auth-warning"]: + disable_auth_warning = True + from tabpy.tabpy_server.app.app import TabPyApp - app = TabPyApp(config) + app = TabPyApp(config, disable_auth_warning) app.run() From 273f4591ba5abdf87a32ed8e9aee7ba483f3bf2c Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 21 Jun 2023 13:41:42 -0700 Subject: [PATCH 4/8] Add --disable-auth-warning to integ tests. --- tests/integration/integ_test_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/integ_test_base.py b/tests/integration/integ_test_base.py index 111a0d89..330fbe84 100755 --- a/tests/integration/integ_test_base.py +++ b/tests/integration/integ_test_base.py @@ -226,7 +226,7 @@ def setUp(self): # Platform specific - for integration tests we want to engage # startup script with open(self.tmp_dir + "/output.txt", "w") as outfile: - cmd = ["tabpy", "--config=" + self.config_file_name] + cmd = ["tabpy", "--config=" + self.config_file_name, "--disable-auth-warning"] preexec_fn = None if platform.system() == "Windows": self.py = "python" From 268d720bddb1e884a991fab3240c03f15fdb4bec Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 21 Jun 2023 13:41:52 -0700 Subject: [PATCH 5/8] Bump version to 2.9.0. --- tabpy/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tabpy/VERSION b/tabpy/VERSION index 834f2629..c8e38b61 100755 --- a/tabpy/VERSION +++ b/tabpy/VERSION @@ -1 +1 @@ -2.8.0 +2.9.0 From cf36a6f671894380fc33533a0c98e6bda65da638 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 21 Jun 2023 13:42:27 -0700 Subject: [PATCH 6/8] Add 2.9.0 CHANGELOG notes. --- CHANGELOG | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 6033c151..6cfda689 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,12 @@ # Changelog +## v2.9.0 + +### Improvements + +- Require confirmation to continue when starting TabPy without authentication, + with a warning that this is an insecure state and not recommended. + ## v2.8.0 ### Improvements From 735a7bd76ae1971d94002bef1f1895bae332cee5 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 21 Jun 2023 13:43:08 -0700 Subject: [PATCH 7/8] Fix CI/CD because of GitHub action regression. --- .github/workflows/pull_request.yml | 4 +++- .github/workflows/push.yml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index bac8f295..bed2fc9f 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -9,7 +9,9 @@ jobs: strategy: matrix: - python-version: [3.7, 3.8, 3.9] + # TODO: Add 3.7 to python-versions after GitHub action regression is resolved. + # https://github.com/actions/setup-python/issues/682 + python-version: [3.8, 3.9] os: [ubuntu-latest, windows-latest, macos-latest] steps: diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 1b9e0c71..86c2c391 100755 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -9,7 +9,9 @@ jobs: strategy: matrix: - python-version: [3.7, 3.8, 3.9] + # TODO: Add 3.7 to python-versions after GitHub action regression is resolved. + # https://github.com/actions/setup-python/issues/682 + python-version: [3.8, 3.9] os: [ubuntu-latest, windows-latest, macos-latest] steps: From 59e3dbd21c60f8a478ccae33b45d8f561f5be1b4 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 21 Jun 2023 14:16:33 -0700 Subject: [PATCH 8/8] Add unit test. --- tests/unit/server_tests/test_config.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/unit/server_tests/test_config.py b/tests/unit/server_tests/test_config.py index dbc1d578..4090a7af 100644 --- a/tests/unit/server_tests/test_config.py +++ b/tests/unit/server_tests/test_config.py @@ -68,6 +68,26 @@ def test_no_state_ini_file_or_state_dir( TabPyApp(None) self.assertEqual(len(mock_os.makedirs.mock_calls), 1) + @patch('builtins.input', return_value='y') + @patch("tabpy.tabpy_server.app.app.os") + @patch("tabpy.tabpy_server.app.app.os.path.exists", return_value=False) + @patch("tabpy.tabpy_server.app.app.PythonServiceHandler") + @patch("tabpy.tabpy_server.app.app._get_state_from_file") + @patch("tabpy.tabpy_server.app.app.TabPyState") + def test_handle_configuration_without_authentication( + self, + mock_tabpy_state, + mock_get_state_from_file, + mock_psws, + mock_os_path_exists, + mock_os, + mock_input, + ): + TabPyApp(None) + mock_input.assert_not_called() + + TabPyApp(None, False) + mock_input.assert_called() class TestPartialConfigFile(unittest.TestCase): def setUp(self):