From 76951a4bda1222fb5bea05a5af37c54c003646ca Mon Sep 17 00:00:00 2001 From: Nuwan Goonasekera <2070605+nuwang@users.noreply.github.com> Date: Sun, 28 Nov 2021 06:56:35 +0530 Subject: [PATCH] Make sure filesources can access user level and app level vaults --- lib/galaxy/files/__init__.py | 17 +++- test/integration/file_sources_conf_vault.yml | 8 ++ test/integration/test_vault_file_source.py | 90 ++++++++++++++++++++ test/integration/vault_conf.yml | 13 +++ 4 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 test/integration/file_sources_conf_vault.yml create mode 100644 test/integration/test_vault_file_source.py create mode 100644 test/integration/vault_conf.yml diff --git a/lib/galaxy/files/__init__.py b/lib/galaxy/files/__init__.py index b45e0721a07..ff6e7df1254 100644 --- a/lib/galaxy/files/__init__.py +++ b/lib/galaxy/files/__init__.py @@ -264,10 +264,17 @@ class ProvidesUserFileSourcesUserContext: return self.trans.user_is_admin @property - def vault(self): + def user_vault(self): + """User vault namespace""" user_vault = self.trans.user_vault return user_vault or defaultdict(lambda: None) + @property + def app_vault(self): + """App vault namespace""" + vault = self.trans.app.vault + return vault or defaultdict(lambda: None) + class DictFileSourcesUserContext: @@ -303,5 +310,9 @@ class DictFileSourcesUserContext: return self._kwd.get("is_admin") @property - def vault(self): - return self._kwd.get("vault") + def user_vault(self): + return self._kwd.get("user_vault") + + @property + def app_vault(self): + return self._kwd.get("app_vault") diff --git a/test/integration/file_sources_conf_vault.yml b/test/integration/file_sources_conf_vault.yml new file mode 100644 index 00000000000..83a250d549f --- /dev/null +++ b/test/integration/file_sources_conf_vault.yml @@ -0,0 +1,8 @@ +- type: posix + id: test_user_vault + root: ${user.user_vault.read_secret('posix/root_path')} + label: a user level test path +- type: posix + id: test_app_vault + root: ${user.app_vault.read_secret('posix/root_path')} + label: an app level test path \ No newline at end of file diff --git a/test/integration/test_vault_file_source.py b/test/integration/test_vault_file_source.py new file mode 100644 index 00000000000..4b0ebcbc324 --- /dev/null +++ b/test/integration/test_vault_file_source.py @@ -0,0 +1,90 @@ +import os +import tempfile + +from galaxy.security.vault import UserVaultWrapper +from galaxy_test.base import api_asserts +from galaxy_test.driver import integration_util + + +SCRIPT_DIRECTORY = os.path.abspath(os.path.dirname(__file__)) +FILE_SOURCES_VAULT_CONF = os.path.join(SCRIPT_DIRECTORY, "file_sources_conf_vault.yml") +VAULT_CONF = os.path.join(SCRIPT_DIRECTORY, "vault_conf.yml") + + +class VaultFileSourceIntegrationTestCase(integration_util.IntegrationTestCase): + USER_1_APP_VAULT_ENTRY = "randomvaultuser1@universe.com" + USER_2_APP_VAULT_ENTRY = "randomvaultuser2@universe.com" + + @classmethod + def handle_galaxy_config_kwds(cls, config): + config["file_sources_config_file"] = FILE_SOURCES_VAULT_CONF + config["vault_config_file"] = VAULT_CONF + config["user_library_import_symlink_allowlist"] = os.path.realpath(tempfile.mkdtemp()) + + def test_vault_secret_per_user_in_file_source(self): + """ + This file source performs a user vault lookup. The secret stored for the first user is a + valid path and should succeed, while the second user's stored secret should fail. + """ + with self._different_user(email=self.USER_1_APP_VAULT_ENTRY): + app = self._app + user = app.model.context.query(app.model.User).filter( + app.model.User.email == self.USER_1_APP_VAULT_ENTRY).first() + user_vault = UserVaultWrapper(self._app.vault, user) + # use a valid symlink path so the posix list succeeds + user_vault.write_secret('posix/root_path', app.config.user_library_import_symlink_allowlist[0]) + + data = {"target": "gxfiles://test_user_vault"} + list_response = self.galaxy_interactor.get("remote_files", data) + api_asserts.assert_status_code_is_ok(list_response) + remote_files = list_response.json() + print(remote_files) + + with self._different_user(email=self.USER_2_APP_VAULT_ENTRY): + app = self._app + user = app.model.context.query(app.model.User).filter( + app.model.User.email == self.USER_2_APP_VAULT_ENTRY).first() + user_vault = UserVaultWrapper(self._app.vault, user) + # use an invalid symlink path so the posix list fails + user_vault.write_secret('posix/root_path', '/invalid/root') + + data = {"target": "gxfiles://test_user_vault"} + list_response = self.galaxy_interactor.get("remote_files", data) + api_asserts.assert_status_code_is(list_response, 404) + + def test_vault_secret_per_app_in_file_source(self): + """ + This file source performs an app level vault lookup. Although the secret stored for the first user is a + valid path and the second user's stored secret is invalid, we are performing an app level lookup + which should succeed for both users. + """ + # write app level secret + app = self._app + # use a valid symlink path so the posix list succeeds + app.vault.write_secret('posix/root_path', app.config.user_library_import_symlink_allowlist[0]) + + with self._different_user(email=self.USER_1_APP_VAULT_ENTRY): + user = app.model.context.query(app.model.User).filter( + app.model.User.email == self.USER_1_APP_VAULT_ENTRY).first() + user_vault = UserVaultWrapper(self._app.vault, user) + # use a valid symlink path so the posix list succeeds + user_vault.write_secret('posix/root_path', app.config.user_library_import_symlink_allowlist[0]) + + data = {"target": "gxfiles://test_app_vault"} + list_response = self.galaxy_interactor.get("remote_files", data) + api_asserts.assert_status_code_is_ok(list_response) + remote_files = list_response.json() + print(remote_files) + + with self._different_user(email=self.USER_2_APP_VAULT_ENTRY): + user = app.model.context.query(app.model.User).filter( + app.model.User.email == self.USER_2_APP_VAULT_ENTRY).first() + user_vault = UserVaultWrapper(self._app.vault, user) + # use an invalid symlink path so the posix list would fail if used + user_vault.write_secret('posix/root_path', '/invalid/root') + + data = {"target": "gxfiles://test_app_vault"} + list_response = self.galaxy_interactor.get("remote_files", data) + api_asserts.assert_status_code_is_ok(list_response) + remote_files = list_response.json() + print(remote_files) diff --git a/test/integration/vault_conf.yml b/test/integration/vault_conf.yml new file mode 100644 index 00000000000..9064f7a4734 --- /dev/null +++ b/test/integration/vault_conf.yml @@ -0,0 +1,13 @@ +type: database +# Encryption keys must be valid fernet keys +# To generate a valid key: +# >>> from cryptography.fernet import Fernet +# >>> Fernet.generate_key() +# b'pZDP8_baVs3oWT4597HJWCysm49j-XELONQ-EdoU0DE=' +# +# Use the ascii string value as a key +# For more details, see: https://cryptography.io/en/latest/fernet/# +encryption_keys: + - 5RrT94ji178vQwha7TAmEix7DojtsLlxVz8Ef17KWgg= + - iNdXd7tRjLnSqRHxuhqQ98GTLU8HUbd5_Xx38iF8nZ0= + - IK83IXhE4_7W7xCFEtD9op0BAs11pJqYN236Spppp7g=