diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 403a0beca5..a79c9fe41c 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -7,6 +7,7 @@ * Introduced `snowflake.cli.api.console.cli_console` object with helper methods for intermediate output. ## Fixes and improvements +* Restricted permissions of automatically created files # v2.0.0 diff --git a/src/snowflake/cli/app/__init__.py b/src/snowflake/cli/app/__init__.py index ca00d12d11..be574caea6 100644 --- a/src/snowflake/cli/app/__init__.py +++ b/src/snowflake/cli/app/__init__.py @@ -1,4 +1,8 @@ import logging +import os # Suppress logging from Snowflake connector logging.getLogger("snowflake").setLevel(logging.ERROR) + +# Restrict permissions of all created files +os.umask(0o077) diff --git a/tests/test_config.py b/tests/test_config.py index f96a190ff2..cbd16dcf73 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -9,6 +9,7 @@ from snowflake.cli.api.exceptions import MissingConfiguration from tests.testing_utils.fixtures import * +from tests.testing_utils.files_and_dirs import assert_file_permissions_are_strict def test_empty_config_file_is_created_if_not_present(): @@ -19,6 +20,8 @@ def test_empty_config_file_is_created_if_not_present(): config_init(config_file) assert config_file.exists() is True + assert_file_permissions_are_strict(config_file) + @mock.patch.dict(os.environ, {}, clear=True) def test_get_connection_from_file(test_snowcli_config): diff --git a/tests/test_logs.py b/tests/test_logs.py index 9b8a9670c7..0b1e42f99a 100644 --- a/tests/test_logs.py +++ b/tests/test_logs.py @@ -10,6 +10,7 @@ from snowflake.cli.api.exceptions import InvalidLogsConfiguration from tests.conftest import clean_logging_handlers +from tests.testing_utils.files_and_dirs import assert_file_permissions_are_strict @pytest.fixture @@ -199,3 +200,9 @@ def test_incorrect_log_level_in_config(setup_config_and_logs): e.message == "Invalid 'level' value set in [logs] section: funny_level." " 'level' should be one of: DEBUG / INFO / WARNING / ERROR / CRITICAL" ) + + +def test_log_files_permissions(setup_config_and_logs): + with setup_config_and_logs(save_logs=True) as logs_path: + print_log_messages() + assert_file_permissions_are_strict(_get_logs_file(logs_path)) diff --git a/tests/testing_utils/files_and_dirs.py b/tests/testing_utils/files_and_dirs.py index 1999ee741b..90c28d9c55 100644 --- a/tests/testing_utils/files_and_dirs.py +++ b/tests/testing_utils/files_and_dirs.py @@ -4,6 +4,8 @@ from pathlib import Path from typing import Dict, Generator, List, Union +import stat + def create_temp_file(suffix: str, dir: str, contents: List[str]) -> str: with tempfile.NamedTemporaryFile(suffix=suffix, dir=dir, delete=False) as tmp: @@ -23,6 +25,19 @@ def _write_to_file(path: str, contents: List[str]) -> None: new_file.write(line + "\n") +def assert_file_permissions_are_strict(file_path: Path) -> None: + ACCESSIBLE_BY_OTHERS = ( + # https://docs.python.org/3/library/stat.html + stat.S_IRGRP # readable by group + | stat.S_IROTH # readable by others + | stat.S_IWGRP # writeable by group + | stat.S_IWOTH # writeable by others + | stat.S_IXGRP # executable by group + | stat.S_IXOTH # executable by group + ) + assert (file_path.stat().st_mode & ACCESSIBLE_BY_OTHERS) == 0 + + @contextmanager def temp_local_dir(files: Dict[str, Union[str, bytes]]) -> Generator[Path, None, None]: """