From fe17f2236b64d73cdb2c878eb8ac7a6a5a068f9e Mon Sep 17 00:00:00 2001 From: "a.berezin" Date: Wed, 17 Jul 2024 23:56:05 +0300 Subject: [PATCH] [#271] Migrate eACL tests to APE Signed-off-by: a.berezin --- pytest_tests/helpers/bearer_token.py | 17 + pytest_tests/helpers/container_access.py | 112 +--- pytest_tests/testsuites/access/__init__.py | 0 .../testsuites/access/acl/test_acl.py | 123 ++++ .../testsuites/access/ape/__init__.py | 0 .../testsuites/access/ape/test_ape.py | 226 +++++++ .../testsuites/access/ape/test_ape_filters.py | 358 ++++++++++ .../testsuites/access/ape/test_bearer.py | 190 ++++++ pytest_tests/testsuites/access/conftest.py | 64 ++ pytest_tests/testsuites/acl/conftest.py | 90 --- pytest_tests/testsuites/acl/test_acl.py | 151 ----- pytest_tests/testsuites/acl/test_bearer.py | 205 ------ pytest_tests/testsuites/acl/test_eacl.py | 612 ------------------ .../testsuites/acl/test_eacl_filters.py | 588 ----------------- pytest_tests/testsuites/conftest.py | 51 +- .../management/test_node_management.py | 39 +- .../object/test_object_api_bearer.py | 93 ++- .../services/http_gate/test_http_bearer.py | 102 ++- .../testsuites/session_token/conftest.py | 6 +- .../test_static_session_token_container.py | 52 +- 20 files changed, 1133 insertions(+), 1946 deletions(-) create mode 100644 pytest_tests/helpers/bearer_token.py create mode 100644 pytest_tests/testsuites/access/__init__.py create mode 100644 pytest_tests/testsuites/access/acl/test_acl.py create mode 100644 pytest_tests/testsuites/access/ape/__init__.py create mode 100644 pytest_tests/testsuites/access/ape/test_ape.py create mode 100644 pytest_tests/testsuites/access/ape/test_ape_filters.py create mode 100644 pytest_tests/testsuites/access/ape/test_bearer.py create mode 100644 pytest_tests/testsuites/access/conftest.py delete mode 100644 pytest_tests/testsuites/acl/conftest.py delete mode 100644 pytest_tests/testsuites/acl/test_acl.py delete mode 100644 pytest_tests/testsuites/acl/test_bearer.py delete mode 100644 pytest_tests/testsuites/acl/test_eacl.py delete mode 100644 pytest_tests/testsuites/acl/test_eacl_filters.py diff --git a/pytest_tests/helpers/bearer_token.py b/pytest_tests/helpers/bearer_token.py new file mode 100644 index 00000000..b2e2803a --- /dev/null +++ b/pytest_tests/helpers/bearer_token.py @@ -0,0 +1,17 @@ +import os + +from frostfs_testlib.cli.frostfs_cli.cli import FrostfsCli +from frostfs_testlib.storage.dataclasses import ape +from frostfs_testlib.utils import string_utils + + +def create_bearer_token(frostfs_cli: FrostfsCli, directory: str, cid: str, rule: ape.Rule, endpoint: str) -> str: + chain_file = os.path.join(directory, string_utils.unique_name("chain-", ".json")) + bearer_token_file = os.path.join(directory, string_utils.unique_name("bt-", ".json")) + signed_bearer_token_file = os.path.join(directory, string_utils.unique_name("bt-sign-", ".json")) + + frostfs_cli.bearer.generate_ape_override(rule.chain_id, rule=rule.as_string(), cid=cid, output=chain_file) + frostfs_cli.bearer.create(endpoint, bearer_token_file, issued_at=1, expire_at=9999, ape=chain_file) + frostfs_cli.util.sign_bearer_token(bearer_token_file, signed_bearer_token_file) + + return signed_bearer_token_file diff --git a/pytest_tests/helpers/container_access.py b/pytest_tests/helpers/container_access.py index 0c1416e2..f19ab9b7 100644 --- a/pytest_tests/helpers/container_access.py +++ b/pytest_tests/helpers/container_access.py @@ -1,8 +1,9 @@ -from typing import List, Optional +import functools +from typing import Optional from frostfs_testlib.shell import Shell from frostfs_testlib.storage.cluster import Cluster -from frostfs_testlib.storage.dataclasses.acl import EACLOperation +from frostfs_testlib.storage.dataclasses import ape from frostfs_testlib.storage.dataclasses.wallet import WalletInfo from pytest_tests.helpers.object_access import ( @@ -15,8 +16,15 @@ from pytest_tests.helpers.object_access import ( can_search_object, ) +ALL_OBJECT_OPERATIONS = ape.ObjectOperations.get_all() -def check_full_access_to_container( +FULL_ACCESS = {op: True for op in ALL_OBJECT_OPERATIONS} +NO_ACCESS = {op: False for op in ALL_OBJECT_OPERATIONS} +RO_ACCESS = {op: True if op not in [ape.ObjectOperations.PUT, ape.ObjectOperations.DELETE] else False for op in ALL_OBJECT_OPERATIONS} + + +def assert_access_to_container( + access_matrix: dict[ape.ObjectOperations, bool], wallet: WalletInfo, cid: str, oid: str, @@ -27,95 +35,23 @@ def check_full_access_to_container( xhdr: Optional[dict] = None, ): endpoint = cluster.default_rpc_endpoint - assert can_put_object(wallet, cid, file_name, shell, cluster, bearer, xhdr) - assert can_get_head_object(wallet, cid, oid, shell, endpoint, bearer, xhdr) - assert can_get_range_of_object(wallet, cid, oid, shell, endpoint, bearer, xhdr) - assert can_get_range_hash_of_object(wallet, cid, oid, shell, endpoint, bearer, xhdr) - assert can_search_object(wallet, cid, shell, endpoint, oid, bearer, xhdr) - assert can_get_object(wallet, cid, oid, file_name, shell, cluster, bearer, xhdr) - assert can_delete_object(wallet, cid, oid, shell, endpoint, bearer, xhdr) + results: dict = {} - -def check_no_access_to_container( - wallet: WalletInfo, - cid: str, - oid: str, - file_name: str, - shell: Shell, - cluster: Cluster, - bearer: Optional[str] = None, - xhdr: Optional[dict] = None, -): - endpoint = cluster.default_rpc_endpoint - assert not can_put_object(wallet, cid, file_name, shell, cluster, bearer, xhdr) - assert not can_get_head_object(wallet, cid, oid, shell, endpoint, bearer, xhdr) - assert not can_get_range_of_object(wallet, cid, oid, shell, endpoint, bearer, xhdr) - assert not can_get_range_hash_of_object(wallet, cid, oid, shell, endpoint, bearer, xhdr) - assert not can_search_object(wallet, cid, shell, endpoint, oid, bearer, xhdr) - assert not can_get_object(wallet, cid, oid, file_name, shell, cluster, bearer, xhdr) - assert not can_delete_object(wallet, cid, oid, shell, endpoint, bearer, xhdr) - - -def check_custom_access_to_container( - wallet: WalletInfo, - cid: str, - oid: str, - file_name: str, - shell: Shell, - cluster: Cluster, - deny_operations: Optional[List[EACLOperation]] = None, - ignore_operations: Optional[List[EACLOperation]] = None, - bearer: Optional[str] = None, - xhdr: Optional[dict] = None, -): - endpoint = cluster.default_rpc_endpoint - deny_operations = [op.value for op in deny_operations or []] - ignore_operations = [op.value for op in ignore_operations or []] - checks: dict = {} - if EACLOperation.PUT.value not in ignore_operations: - checks[EACLOperation.PUT.value] = can_put_object(wallet, cid, file_name, shell, cluster, bearer, xhdr) - if EACLOperation.HEAD.value not in ignore_operations: - checks[EACLOperation.HEAD.value] = can_get_head_object(wallet, cid, oid, shell, endpoint, bearer, xhdr) - if EACLOperation.GET_RANGE.value not in ignore_operations: - checks[EACLOperation.GET_RANGE.value] = can_get_range_of_object(wallet, cid, oid, shell, endpoint, bearer, xhdr) - if EACLOperation.GET_RANGE_HASH.value not in ignore_operations: - checks[EACLOperation.GET_RANGE_HASH.value] = can_get_range_hash_of_object( - wallet, cid, oid, shell, endpoint, bearer, xhdr - ) - if EACLOperation.SEARCH.value not in ignore_operations: - checks[EACLOperation.SEARCH.value] = can_search_object(wallet, cid, shell, endpoint, oid, bearer, xhdr) - if EACLOperation.GET.value not in ignore_operations: - checks[EACLOperation.GET.value] = can_get_object(wallet, cid, oid, file_name, shell, cluster, bearer, xhdr) - if EACLOperation.DELETE.value not in ignore_operations: - checks[EACLOperation.DELETE.value] = can_delete_object(wallet, cid, oid, shell, endpoint, bearer, xhdr) + results[ape.ObjectOperations.PUT] = can_put_object(wallet, cid, file_name, shell, cluster, bearer, xhdr) + results[ape.ObjectOperations.HEAD] = can_get_head_object(wallet, cid, oid, shell, endpoint, bearer, xhdr) + results[ape.ObjectOperations.GET_RANGE] = can_get_range_of_object(wallet, cid, oid, shell, endpoint, bearer, xhdr) + results[ape.ObjectOperations.GET_RANGE_HASH] = can_get_range_hash_of_object(wallet, cid, oid, shell, endpoint, bearer, xhdr) + results[ape.ObjectOperations.SEARCH] = can_search_object(wallet, cid, shell, endpoint, oid, bearer, xhdr) + results[ape.ObjectOperations.GET] = can_get_object(wallet, cid, oid, file_name, shell, cluster, bearer, xhdr) + results[ape.ObjectOperations.DELETE] = can_delete_object(wallet, cid, oid, shell, endpoint, bearer, xhdr) failed_checks = [ - f"allowed {action} failed" - for action, success in checks.items() - if not success and action not in deny_operations - ] + [f"denied {action} succeeded" for action, success in checks.items() if success and action in deny_operations] + f"allowed {action} failed" for action, success in results.items() if not success and access_matrix[action] != results[action] + ] + [f"denied {action} succeeded" for action, success in results.items() if success and access_matrix[action] != results[action]] assert not failed_checks, ", ".join(failed_checks) -def check_read_only_container( - wallet: WalletInfo, - cid: str, - oid: str, - file_name: str, - shell: Shell, - cluster: Cluster, - bearer: Optional[str] = None, - xhdr: Optional[dict] = None, -): - return check_custom_access_to_container( - wallet, - cid, - oid, - file_name, - deny_operations=[EACLOperation.PUT, EACLOperation.DELETE], - bearer=bearer, - xhdr=xhdr, - shell=shell, - cluster=cluster, - ) +assert_full_access_to_container = functools.partial(assert_access_to_container, FULL_ACCESS) +assert_no_access_to_container = functools.partial(assert_access_to_container, NO_ACCESS) +assert_read_only_container = functools.partial(assert_access_to_container, RO_ACCESS) diff --git a/pytest_tests/testsuites/access/__init__.py b/pytest_tests/testsuites/access/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pytest_tests/testsuites/access/acl/test_acl.py b/pytest_tests/testsuites/access/acl/test_acl.py new file mode 100644 index 00000000..1b0fa45c --- /dev/null +++ b/pytest_tests/testsuites/access/acl/test_acl.py @@ -0,0 +1,123 @@ +import allure +import pytest +from frostfs_testlib import reporter +from frostfs_testlib.resources.wellknown_acl import PRIVATE_ACL_F, PUBLIC_ACL_F, READONLY_ACL_F +from frostfs_testlib.shell import Shell +from frostfs_testlib.steps.cli.container import create_container +from frostfs_testlib.steps.cli.object import put_object_to_random_node +from frostfs_testlib.storage.cluster import Cluster +from frostfs_testlib.storage.dataclasses.wallet import WalletInfo +from frostfs_testlib.testing.cluster_test_base import ClusterTestBase + +from pytest_tests.helpers.container_access import assert_full_access_to_container, assert_no_access_to_container, assert_read_only_container + + +@pytest.mark.sanity +@pytest.mark.smoke +@pytest.mark.acl +class TestACLBasic(ClusterTestBase): + @pytest.fixture(scope="module") + def public_container(self, default_wallet: WalletInfo): + with reporter.step("Create public container"): + cid_public = create_container(default_wallet, self.shell, self.cluster.default_rpc_endpoint, basic_acl=PUBLIC_ACL_F) + + return cid_public + + @pytest.fixture(scope="module") + def private_container(self, default_wallet: WalletInfo): + with reporter.step("Create private container"): + cid_private = create_container(default_wallet, self.shell, self.cluster.default_rpc_endpoint, basic_acl=PRIVATE_ACL_F) + + return cid_private + + @pytest.fixture(scope="module") + def readonly_container(self, default_wallet: WalletInfo): + with reporter.step("Create public readonly container"): + cid_read_only = create_container(default_wallet, self.shell, self.cluster.default_rpc_endpoint, basic_acl=READONLY_ACL_F) + + return cid_read_only + + @allure.title("Operations in public container available to everyone (obj_size={object_size})") + def test_basic_acl_public( + self, + default_wallet: WalletInfo, + other_wallet: WalletInfo, + client_shell: Shell, + public_container: str, + file_path: str, + cluster: Cluster, + ): + """ + Test access to object operations in public container. + """ + + for wallet, role in ((default_wallet, "owner"), (other_wallet, "others")): + with reporter.step("Put objects to container"): + # We create new objects for each wallet because assert_full_access_to_container + # deletes the object + owner_object_oid = put_object_to_random_node( + default_wallet, + file_path, + public_container, + shell=self.shell, + cluster=self.cluster, + attributes={"created": "owner"}, + ) + other_object_oid = put_object_to_random_node( + other_wallet, + file_path, + public_container, + shell=self.shell, + cluster=self.cluster, + attributes={"created": "other"}, + ) + + with reporter.step(f"Check {role} has full access to public container"): + assert_full_access_to_container(wallet, public_container, owner_object_oid, file_path, client_shell, cluster) + assert_full_access_to_container(wallet, public_container, other_object_oid, file_path, client_shell, cluster) + + @allure.title("Operations in private container only available to owner (obj_size={object_size})") + def test_basic_acl_private( + self, + default_wallet: WalletInfo, + other_wallet: WalletInfo, + client_shell: Shell, + private_container: str, + file_path: str, + cluster: Cluster, + ): + """ + Test access to object operations in private container. + """ + + with reporter.step("Put object to container"): + owner_object_oid = put_object_to_random_node(default_wallet, file_path, private_container, client_shell, cluster) + + with reporter.step("Check no one except owner has access to operations with container"): + assert_no_access_to_container(other_wallet, private_container, owner_object_oid, file_path, client_shell, cluster) + + with reporter.step("Check owner has full access to private container"): + assert_full_access_to_container(default_wallet, private_container, owner_object_oid, file_path, self.shell, cluster) + + @allure.title("Read operations in readonly container available to others (obj_size={object_size})") + def test_basic_acl_readonly( + self, + default_wallet: WalletInfo, + other_wallet: WalletInfo, + client_shell: Shell, + readonly_container: str, + file_path: str, + cluster: Cluster, + ): + """ + Test access to object operations in readonly container. + """ + + with reporter.step("Put object to container"): + object_oid = put_object_to_random_node(default_wallet, file_path, readonly_container, client_shell, cluster) + + with reporter.step("Check others has read-only access to operations with container"): + assert_read_only_container(other_wallet, readonly_container, object_oid, file_path, client_shell, cluster) + + with reporter.step("Check owner has full access to public container"): + assert_full_access_to_container(default_wallet, readonly_container, object_oid, file_path, client_shell, cluster) diff --git a/pytest_tests/testsuites/access/ape/__init__.py b/pytest_tests/testsuites/access/ape/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pytest_tests/testsuites/access/ape/test_ape.py b/pytest_tests/testsuites/access/ape/test_ape.py new file mode 100644 index 00000000..e6d08bb2 --- /dev/null +++ b/pytest_tests/testsuites/access/ape/test_ape.py @@ -0,0 +1,226 @@ +import allure +import pytest +from frostfs_testlib import reporter +from frostfs_testlib.cli.frostfs_cli.cli import FrostfsCli +from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL +from frostfs_testlib.steps.cli.container import create_container +from frostfs_testlib.steps.cli.object import put_object_to_random_node +from frostfs_testlib.steps.node_management import drop_object +from frostfs_testlib.storage.dataclasses import ape +from frostfs_testlib.storage.dataclasses.wallet import WalletInfo +from frostfs_testlib.testing.cluster_test_base import ClusterTestBase +from frostfs_testlib.utils import wallet_utils +from frostfs_testlib.utils.failover_utils import wait_object_replication +from frostfs_testlib.utils.file_utils import TestFile + +from pytest_tests.helpers.container_access import ( + ALL_OBJECT_OPERATIONS, + assert_access_to_container, + assert_full_access_to_container, + assert_no_access_to_container, +) + + +@pytest.mark.ape +class TestApeContainer(ClusterTestBase): + @pytest.fixture(scope="function") + def full_placement_container_with_object(self, default_wallet: WalletInfo, file_path: str) -> tuple[str, str, str]: + storage_nodes = self.cluster.storage_nodes + node_count = len(storage_nodes) + with reporter.step("Create public container with full placement rule"): + full_placement_rule = f"REP {node_count} IN X CBF 1 SELECT {node_count} FROM * AS X" + cid = create_container(default_wallet, self.shell, self.cluster.default_rpc_endpoint, full_placement_rule, PUBLIC_ACL) + + with reporter.step("Put object to container"): + oid = put_object_to_random_node(default_wallet, file_path, cid, shell=self.shell, cluster=self.cluster) + wait_object_replication(cid, oid, node_count, shell=self.shell, nodes=storage_nodes) + + yield cid, oid + + @pytest.mark.sanity + @allure.title("Deny operations via APE by role (role={role}, obj_size={object_size})") + @pytest.mark.parametrize("role", [ape.Role.OWNER, ape.Role.OTHERS], indirect=True) + def test_deny_operations_via_ape_by_role( + self, + default_wallet: WalletInfo, + other_wallet: WalletInfo, + frostfs_cli: FrostfsCli, + container_with_objects: tuple[str, list[str], str], + role: ape.Role, + file_path: TestFile, + ): + denied_wallet = other_wallet if role == ape.Role.OTHERS else default_wallet + allowed_wallet = default_wallet if role == ape.Role.OTHERS else other_wallet + allowed_role = ape.Role.OWNER if role == ape.Role.OTHERS else ape.Role.OTHERS + cid, object_oids, file_path = container_with_objects + endpoint = self.cluster.default_rpc_endpoint + + role_condition = ape.Condition.by_role(role.value) + + with reporter.step(f"Deny all operations for {role} via APE"): + deny_rule = ape.Rule(ape.Verb.DENY, ALL_OBJECT_OPERATIONS, role_condition) + frostfs_cli.ape_manager.add(endpoint, deny_rule.chain_id, target_name=cid, target_type="container", rule=deny_rule.as_string()) + + with reporter.step("Wait for one block"): + self.wait_for_blocks() + + with reporter.step(f"Assert {role} have no access to public container"): + assert_no_access_to_container(denied_wallet, cid, object_oids[0], file_path, self.shell, self.cluster) + + with reporter.step(f"Assert {allowed_role} have full access to public container"): + assert_full_access_to_container(allowed_wallet, cid, object_oids.pop(), file_path, self.shell, self.cluster) + + with reporter.step(f"Remove deny rule from APE"): + frostfs_cli.ape_manager.remove(endpoint, deny_rule.chain_id, target_name=cid, target_type="container") + + with reporter.step("Wait for one block"): + self.wait_for_blocks() + + with reporter.step("Assert owner have full access to public container"): + assert_full_access_to_container(default_wallet, cid, object_oids.pop(), file_path, self.shell, self.cluster) + + with reporter.step("Assert others have full access to public container"): + assert_full_access_to_container(other_wallet, cid, object_oids.pop(), file_path, self.shell, self.cluster) + + @allure.title("Deny operations for others via APE excluding single pubkey (obj_size={object_size})") + def test_deny_opeartions_excluding_pubkey( + self, + frostfs_cli: FrostfsCli, + default_wallet: WalletInfo, + other_wallet: WalletInfo, + other_wallet_2: WalletInfo, + container_with_objects: tuple[str, list[str], str], + ): + endpoint = self.cluster.default_rpc_endpoint + cid, object_oids, file_path = container_with_objects + + with reporter.step("Add deny APE rules for others except single wallet"): + rule_conditions = [ + ape.Condition.by_role(ape.Role.OTHERS), + ape.Condition.by_key( + wallet_utils.get_wallet_public_key(other_wallet_2.path, other_wallet_2.password), + match_type=ape.MatchType.NOT_EQUAL, + ), + ] + rule = ape.Rule(ape.Verb.DENY, ALL_OBJECT_OPERATIONS, rule_conditions) + frostfs_cli.ape_manager.add(endpoint, rule.chain_id, target_name=cid, target_type="container", rule=rule.as_string()) + + with reporter.step("Wait for one block"): + self.wait_for_blocks() + + with reporter.step("Assert others have no access to public container"): + assert_no_access_to_container(other_wallet, cid, object_oids[0], file_path, self.shell, self.cluster) + + with reporter.step("Assert owner have full access to public container"): + assert_full_access_to_container(default_wallet, cid, object_oids.pop(), file_path, self.shell, self.cluster) + + with reporter.step("Assert allowed wallet have full access to public container"): + assert_full_access_to_container(other_wallet_2, cid, object_oids.pop(), file_path, self.shell, self.cluster) + + @allure.title("Replication works with APE deny rules on OWNER and OTHERS (obj_size={object_size})") + def test_replication_works_with_deny_rules( + self, + frostfs_cli: FrostfsCli, + full_placement_container_with_object: tuple[str, list[str], str], + ): + endpoint = self.cluster.default_rpc_endpoint + cid, oid = full_placement_container_with_object + storage_nodes = self.cluster.storage_nodes + + with reporter.step("Add deny APE rules for owner and others"): + rule_conditions = [ + ape.Condition.by_role(ape.Role.OWNER), + ape.Condition.by_role(ape.Role.OTHERS), + ] + for rule_condition in rule_conditions: + rule = ape.Rule(ape.Verb.DENY, ALL_OBJECT_OPERATIONS, rule_condition) + frostfs_cli.ape_manager.add(endpoint, rule.chain_id, target_name=cid, target_type="container", rule=rule.as_string()) + + with reporter.step("Wait for one block"): + self.wait_for_blocks() + + with reporter.step("Drop object"): + drop_object(storage_nodes[0], cid, oid) + + with reporter.step("Wait for dropped object to be replicated"): + wait_object_replication(cid, oid, len(storage_nodes), self.shell, storage_nodes) + + @allure.title("Deny operations via APE by role (role=ir, obj_size={object_size})") + def test_deny_operations_via_ape_by_role_ir( + self, frostfs_cli: FrostfsCli, ir_wallet: WalletInfo, container_with_objects: tuple[str, list[str], str] + ): + cid, object_oids, file_path = container_with_objects + endpoint = self.cluster.default_rpc_endpoint + + default_ir_access = { + ape.ObjectOperations.PUT: False, + ape.ObjectOperations.GET: True, + ape.ObjectOperations.HEAD: True, + ape.ObjectOperations.GET_RANGE: False, + ape.ObjectOperations.GET_RANGE_HASH: True, + ape.ObjectOperations.SEARCH: True, + ape.ObjectOperations.DELETE: False, + } + + with reporter.step("Assert IR wallet access in default state"): + assert_access_to_container(default_ir_access, ir_wallet, cid, object_oids[0], file_path, self.shell, self.cluster) + + with reporter.step("Add deny APE rule with deny all operations for IR role"): + rule = ape.Rule(ape.Verb.DENY, ALL_OBJECT_OPERATIONS, [ape.Condition.by_role(ape.Role.IR.value)]) + frostfs_cli.ape_manager.add(endpoint, rule.chain_id, target_name=cid, target_type="container", rule=rule.as_string()) + + with reporter.step("Wait for one block"): + self.wait_for_blocks() + + with reporter.step("Assert IR wallet ignores APE rules"): + assert_access_to_container(default_ir_access, ir_wallet, cid, object_oids[0], file_path, self.shell, self.cluster) + + with reporter.step("Remove APE rule"): + frostfs_cli.ape_manager.remove(endpoint, rule.chain_id, target_name=cid, target_type="container") + + with reporter.step("Wait for one block"): + self.wait_for_blocks() + + with reporter.step("Assert IR wallet access is restored"): + assert_access_to_container(default_ir_access, ir_wallet, cid, object_oids[0], file_path, self.shell, self.cluster) + + @allure.title("Deny operations via APE by role (role=container, obj_size={object_size})") + def test_deny_operations_via_ape_by_role_container( + self, frostfs_cli: FrostfsCli, storage_wallet: WalletInfo, container_with_objects: tuple[str, list[str], str] + ): + + cid, object_oids, file_path = container_with_objects + endpoint = self.cluster.default_rpc_endpoint + + default_container_access = { + ape.ObjectOperations.PUT: True, + ape.ObjectOperations.GET: True, + ape.ObjectOperations.HEAD: True, + ape.ObjectOperations.GET_RANGE: False, + ape.ObjectOperations.GET_RANGE_HASH: True, + ape.ObjectOperations.SEARCH: True, + ape.ObjectOperations.DELETE: False, + } + + with reporter.step("Assert CONTAINER wallet access in default state"): + assert_access_to_container(default_container_access, storage_wallet, cid, object_oids[0], file_path, self.shell, self.cluster) + + rule = ape.Rule(ape.Verb.DENY, ALL_OBJECT_OPERATIONS, ape.Condition.by_role(ape.Role.CONTAINER.value)) + + with reporter.step(f"Add APE rule with deny all operations for CONTAINER and IR roles"): + frostfs_cli.ape_manager.add(endpoint, rule.chain_id, target_name=cid, target_type="container", rule=rule.as_string()) + + with reporter.step("Wait for one block"): + self.wait_for_blocks() + + with reporter.step("Assert CONTAINER wallet ignores APE rule"): + assert_access_to_container(default_container_access, storage_wallet, cid, object_oids[0], file_path, self.shell, self.cluster) + + with reporter.step("Remove APE rules"): + frostfs_cli.ape_manager.remove(endpoint, rule.chain_id, target_name=cid, target_type="container") + + with reporter.step("Wait for one block"): + self.wait_for_blocks() + + with reporter.step("Assert CONTAINER wallet access is restored"): + assert_access_to_container(default_container_access, storage_wallet, cid, object_oids[0], file_path, self.shell, self.cluster) diff --git a/pytest_tests/testsuites/access/ape/test_ape_filters.py b/pytest_tests/testsuites/access/ape/test_ape_filters.py new file mode 100644 index 00000000..0b0555cb --- /dev/null +++ b/pytest_tests/testsuites/access/ape/test_ape_filters.py @@ -0,0 +1,358 @@ +import allure +import pytest +from frostfs_testlib import reporter +from frostfs_testlib.cli.frostfs_cli.cli import FrostfsCli +from frostfs_testlib.resources.error_patterns import OBJECT_ACCESS_DENIED, OBJECT_NOT_FOUND +from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL +from frostfs_testlib.steps.cli.container import create_container +from frostfs_testlib.steps.cli.object import get_object_from_random_node, head_object, put_object_to_random_node +from frostfs_testlib.storage.cluster import Cluster +from frostfs_testlib.storage.dataclasses import ape +from frostfs_testlib.storage.dataclasses.wallet import WalletInfo +from frostfs_testlib.testing.cluster_test_base import ClusterTestBase +from frostfs_testlib.testing.test_control import expect_not_raises +from frostfs_testlib.utils.file_utils import TestFile + +from pytest_tests.helpers.bearer_token import create_bearer_token +from pytest_tests.helpers.container_access import ( + ALL_OBJECT_OPERATIONS, + FULL_ACCESS, + assert_access_to_container, + assert_full_access_to_container, + assert_no_access_to_container, +) + +OBJECT_NO_ACCESS = f"(?:{OBJECT_NOT_FOUND}|{OBJECT_ACCESS_DENIED})" + + +@pytest.mark.ape +class TestApeFilters(ClusterTestBase): + # SPEC: https://github.com/nspcc-dev/neofs-spec/blob/master/01-arch/07-acl.md + HEADER = {"check_key": "check_value"} + OTHER_HEADER = {"check_key": "other_value"} + ATTRIBUTES = { + "key_one": "check_value", + "x_key": "xvalue", + "check_key": "check_value", + } + OTHER_ATTRIBUTES = { + "key_one": "check_value", + "x_key": "other_value", + "check_key": "other_value", + } + + OBJECT_COUNT = 5 + RESOURCE_OPERATIONS = [ + ape.ObjectOperations.GET, + ape.ObjectOperations.HEAD, + ape.ObjectOperations.PUT, + ] + + @pytest.fixture(scope="function") + def public_container_with_objects(self, default_wallet: WalletInfo, file_path: TestFile): + with reporter.step("Create public container"): + cid = create_container(default_wallet, self.shell, self.cluster.default_rpc_endpoint, basic_acl=PUBLIC_ACL) + + objects_with_header, objects_with_other_header, objects_without_header = self._fill_container(default_wallet, file_path, cid) + + return cid, objects_with_header, objects_with_other_header, objects_without_header, file_path + + @pytest.fixture(scope="function") + def container_with_objects(self, default_wallet: WalletInfo, file_path: TestFile, frostfs_cli: FrostfsCli, cluster: Cluster): + with reporter.step("Create private container"): + cid = create_container(default_wallet, self.shell, self.cluster.default_rpc_endpoint, basic_acl="0") + + with reporter.step("Create allow APE rule for container owner"): + role_condition = ape.Condition.by_role(ape.Role.OWNER) + deny_rule = ape.Rule(ape.Verb.ALLOW, ape.ObjectOperations.WILDCARD_ALL, role_condition) + + frostfs_cli.ape_manager.add( + cluster.default_rpc_endpoint, + deny_rule.chain_id, + target_name=cid, + target_type="container", + rule=deny_rule.as_string(), + ) + + with reporter.step("Wait for one block"): + self.wait_for_blocks() + + objects_with_header, objects_with_other_header, objects_without_header = self._fill_container(default_wallet, file_path, cid) + + return cid, objects_with_header, objects_with_other_header, objects_without_header, file_path + + @reporter.step("Add objects to container") + def _fill_container(self, wallet: WalletInfo, test_file: TestFile, cid: str): + objects_with_header = [ + put_object_to_random_node(wallet, test_file, cid, self.shell, self.cluster, attributes={**self.ATTRIBUTES, "key": val}) + for val in range(self.OBJECT_COUNT) + ] + + objects_with_other_header = [ + put_object_to_random_node(wallet, test_file, cid, self.shell, self.cluster, attributes={**self.OTHER_ATTRIBUTES, "key": val}) + for val in range(self.OBJECT_COUNT) + ] + + objects_without_header = [ + put_object_to_random_node(wallet, test_file, cid, self.shell, self.cluster) for _ in range(self.OBJECT_COUNT) + ] + + return objects_with_header, objects_with_other_header, objects_without_header + + @pytest.mark.sanity + @allure.title("Operations with request filter (match_type={match_type}, obj_size={object_size})") + @pytest.mark.parametrize("match_type", [ape.MatchType.EQUAL, ape.MatchType.NOT_EQUAL]) + def test_ape_filters_request( + self, + frostfs_cli: FrostfsCli, + temp_directory: str, + other_wallet: WalletInfo, + public_container_with_objects: tuple[str, list[str], str], + match_type: ape.MatchType, + ): + ( + cid, + objects_with_header, + objects_with_other_header, + objects_without_header, + file_path, + ) = public_container_with_objects + endpoint = self.cluster.default_rpc_endpoint + + with reporter.step("Deny all operations for others via APE with request condition"): + request_condition = ape.Condition('"check_key"', '"check_value"', ape.ConditionType.REQUEST, match_type) + role_condition = ape.Condition.by_role(ape.Role.OTHERS) + deny_rule = ape.Rule(ape.Verb.DENY, ALL_OBJECT_OPERATIONS, [request_condition, role_condition]) + + frostfs_cli.ape_manager.add(endpoint, deny_rule.chain_id, target_name=cid, target_type="container", rule=deny_rule.as_string()) + + with reporter.step("Wait for one block"): + self.wait_for_blocks() + + with reporter.step("Create bearer token with everything allowed for others role"): + role_condition = ape.Condition.by_role(ape.Role.OTHERS) + rule = ape.Rule(ape.Verb.ALLOW, ALL_OBJECT_OPERATIONS, role_condition) + bearer = create_bearer_token(frostfs_cli, temp_directory, cid, rule, endpoint) + + # Filter denies requests where "check_key {match_type} ATTRIBUTE", so when match_type + # is STRING_EQUAL, then requests with "check_key=OTHER_ATTRIBUTE" will be allowed while + # requests with "check_key=ATTRIBUTE" will be denied, and vice versa + allow_headers = self.OTHER_HEADER if match_type == ape.MatchType.EQUAL else self.HEADER + deny_headers = self.HEADER if match_type == ape.MatchType.EQUAL else self.OTHER_HEADER + + # We test on 3 groups of objects with various headers, + # but APE rule should ignore object headers and only work based on request headers + for oids in [objects_with_header, objects_with_other_header, objects_without_header]: + with reporter.step("Check others has full access when sending request without headers"): + assert_full_access_to_container(other_wallet, cid, oids.pop(), file_path, self.shell, self.cluster) + + with reporter.step("Check others has full access when sending request with allowed headers"): + assert_full_access_to_container(other_wallet, cid, oids.pop(), file_path, self.shell, self.cluster, xhdr=allow_headers) + + with reporter.step("Check others has no access when sending request with denied headers"): + assert_no_access_to_container(other_wallet, cid, oids.pop(), file_path, self.shell, self.cluster, xhdr=deny_headers) + + with reporter.step("Check others has full access when sending request with denied headers and using bearer token"): + assert_full_access_to_container(other_wallet, cid, oids.pop(), file_path, self.shell, self.cluster, bearer, deny_headers) + + @allure.title("Operations with deny user headers filter (match_type={match_type}, obj_size={object_size})") + @pytest.mark.parametrize("match_type", [ape.MatchType.EQUAL, ape.MatchType.NOT_EQUAL]) + def test_ape_deny_filters_object( + self, + frostfs_cli: FrostfsCli, + temp_directory: str, + other_wallet: WalletInfo, + public_container_with_objects: tuple[str, list[str], str], + match_type: ape.MatchType, + ): + # TODO: Refactor this to be fixtures, not test logic + ( + cid, + objects_with_header, + objects_with_other_header, + objs_without_header, + file_path, + ) = public_container_with_objects + endpoint = self.cluster.default_rpc_endpoint + + allow_objects = objects_with_other_header if match_type == ape.MatchType.EQUAL else objects_with_header + deny_objects = objects_with_header if match_type == ape.MatchType.EQUAL else objects_with_other_header + + # When there is no attribute on the object, it's the same as "", and "" is not equal to "" + # So it's the same as deny_objects + no_attributes_access = { + ape.MatchType.EQUAL: FULL_ACCESS, + ape.MatchType.NOT_EQUAL: { + ape.ObjectOperations.PUT: False, + ape.ObjectOperations.GET: False, + ape.ObjectOperations.HEAD: False, + ape.ObjectOperations.GET_RANGE: True, + ape.ObjectOperations.GET_RANGE_HASH: True, + ape.ObjectOperations.SEARCH: True, + ape.ObjectOperations.DELETE: False, # Denied by restricted PUT + }, + } + # End of refactor + + with reporter.step("Deny operations for others via APE with resource condition"): + resource_condition = ape.Condition('"check_key"', '"check_value"', ape.ConditionType.RESOURCE, match_type) + role_condition = ape.Condition.by_role(ape.Role.OTHERS) + deny_rule = ape.Rule(ape.Verb.DENY, self.RESOURCE_OPERATIONS, [resource_condition, role_condition]) + + frostfs_cli.ape_manager.add(endpoint, deny_rule.chain_id, target_name=cid, target_type="container", rule=deny_rule.as_string()) + + with reporter.step("Wait for one block"): + self.wait_for_blocks() + + with reporter.step("Create bearer token with everything allowed for others role"): + role_condition = ape.Condition.by_role(ape.Role.OTHERS) + rule = ape.Rule(ape.Verb.ALLOW, ALL_OBJECT_OPERATIONS, role_condition) + bearer = create_bearer_token(frostfs_cli, temp_directory, cid, rule, endpoint) + + with reporter.step("Create bearer token with allowed put for others role"): + role_condition = ape.Condition.by_role(ape.Role.OTHERS) + rule = ape.Rule(ape.Verb.ALLOW, ape.ObjectOperations.PUT, role_condition) + bearer_put = create_bearer_token(frostfs_cli, temp_directory, cid, rule, endpoint) + + # We will attempt requests with various headers, + # but APE rule should ignore request headers and validate only object headers + for xhdr in (self.HEADER, self.OTHER_HEADER, None): + with reporter.step("Check others access to objects without attributes"): + assert_access_to_container( + no_attributes_access[match_type], + other_wallet, + cid, + objs_without_header.pop(), + file_path, + self.shell, + self.cluster, + xhdr=xhdr, + ) + + with reporter.step("Check others have full access to objects without deny attribute"): + assert_full_access_to_container(other_wallet, cid, allow_objects.pop(), file_path, self.shell, self.cluster, xhdr=xhdr) + + with reporter.step("Check others have no access to objects with deny attribute"): + with pytest.raises(Exception, match=OBJECT_NO_ACCESS): + head_object(other_wallet, cid, deny_objects[0], self.shell, endpoint, xhdr=xhdr) + + with pytest.raises(Exception, match=OBJECT_NO_ACCESS): + get_object_from_random_node(other_wallet, cid, deny_objects[0], self.shell, self.cluster, xhdr=xhdr) + + with reporter.step("Check others have access to objects with deny attribute and using bearer token"): + assert_full_access_to_container(other_wallet, cid, deny_objects.pop(), file_path, self.shell, self.cluster, bearer, xhdr) + + allow_attribute = self.OTHER_HEADER if match_type == ape.MatchType.EQUAL else self.HEADER + with reporter.step("Check others can PUT objects without denied attribute"): + put_object_to_random_node(other_wallet, file_path, cid, self.shell, self.cluster, attributes=allow_attribute) + + deny_attribute = self.HEADER if match_type == ape.MatchType.EQUAL else self.OTHER_HEADER + with reporter.step("Check others can not PUT objects with denied attribute"): + with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED): + put_object_to_random_node(other_wallet, file_path, cid, self.shell, self.cluster, attributes=deny_attribute) + + with reporter.step("Check others can PUT objects with denied attribute and using bearer token"): + put_object_to_random_node(other_wallet, file_path, cid, self.shell, self.cluster, bearer_put, attributes=deny_attribute) + + @allure.title("Operations with allow APE rule with resource filters (match_type={match_type}, obj_size={object_size})") + @pytest.mark.parametrize("match_type", [ape.MatchType.EQUAL, ape.MatchType.NOT_EQUAL]) + @pytest.mark.parametrize("object_size", ["simple"], indirect=True) + def test_ape_allow_filters_object( + self, + frostfs_cli: FrostfsCli, + other_wallet: WalletInfo, + container_with_objects: tuple[str, list[str], str], + temp_directory: str, + match_type: ape.MatchType, + ): + # TODO: Refactor this to be fixtures, not test logic! + ( + cid, + objects_with_header, + objects_with_other_header, + objects_without_header, + file_path, + ) = container_with_objects + endpoint = self.cluster.default_rpc_endpoint + + if match_type == ape.MatchType.EQUAL: + allow_objects = objects_with_header + deny_objects = objects_with_other_header + allow_attribute = self.HEADER + deny_attribute = self.OTHER_HEADER + else: + allow_objects = objects_with_other_header + deny_objects = objects_with_header + allow_attribute = self.OTHER_HEADER + deny_attribute = self.HEADER + # End of refactor block + + with reporter.step("Allow operations for others except few operations by resource condition via APE"): + resource_condition = ape.Condition('"check_key"', '"check_value"', ape.ConditionType.RESOURCE, match_type) + role_condition = ape.Condition.by_role(ape.Role.OTHERS) + deny_rule = ape.Rule(ape.Verb.ALLOW, self.RESOURCE_OPERATIONS, [resource_condition, role_condition]) + + frostfs_cli.ape_manager.add(endpoint, deny_rule.chain_id, target_name=cid, target_type="container", rule=deny_rule.as_string()) + + with reporter.step("Wait for one block"): + self.wait_for_blocks() + + with reporter.step("Check others cannot get and put objects without attributes"): + oid = objects_without_header.pop() + with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED): + assert head_object(other_wallet, cid, oid, self.shell, endpoint) + + with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED): + assert get_object_from_random_node(other_wallet, cid, oid, self.shell, self.cluster) + + with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED): + assert put_object_to_random_node(other_wallet, file_path, cid, self.shell, self.cluster) + + with reporter.step("Create bearer token with everything allowed for others role"): + role_condition = ape.Condition.by_role(ape.Role.OTHERS) + rule = ape.Rule(ape.Verb.ALLOW, ALL_OBJECT_OPERATIONS, role_condition) + bearer = create_bearer_token(frostfs_cli, temp_directory, cid, rule, endpoint) + + with reporter.step("Check others can get and put objects without attributes and using bearer token"): + oid = objects_without_header[0] + with expect_not_raises(): + head_object(other_wallet, cid, oid, self.shell, endpoint, bearer) + + with expect_not_raises(): + get_object_from_random_node(other_wallet, cid, oid, self.shell, self.cluster, bearer) + + with expect_not_raises(): + put_object_to_random_node(other_wallet, file_path, cid, self.shell, self.cluster, bearer) + + with reporter.step("Check others can get and put objects with attributes matching the filter"): + oid = allow_objects.pop() + with expect_not_raises(): + head_object(other_wallet, cid, oid, self.shell, endpoint) + + with expect_not_raises(): + get_object_from_random_node(other_wallet, cid, oid, self.shell, self.cluster) + + with expect_not_raises(): + put_object_to_random_node(other_wallet, file_path, cid, self.shell, self.cluster, attributes=allow_attribute) + + with reporter.step("Check others cannot get and put objects without attributes matching the filter"): + oid = deny_objects[0] + with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED): + head_object(other_wallet, cid, oid, self.shell, endpoint) + + with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED): + assert get_object_from_random_node(other_wallet, cid, oid, file_path, self.shell, self.cluster) + + with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED): + assert put_object_to_random_node(other_wallet, file_path, cid, self.shell, self.cluster, attributes=deny_attribute) + + with reporter.step("Check others can get and put objects without attributes matching the filter with bearer token"): + oid = deny_objects.pop() + with expect_not_raises(): + head_object(other_wallet, cid, oid, self.shell, endpoint, bearer) + + with expect_not_raises(): + get_object_from_random_node(other_wallet, cid, oid, self.shell, self.cluster, bearer) + + with expect_not_raises(): + put_object_to_random_node(other_wallet, file_path, cid, self.shell, self.cluster, bearer, attributes=allow_attribute) diff --git a/pytest_tests/testsuites/access/ape/test_bearer.py b/pytest_tests/testsuites/access/ape/test_bearer.py new file mode 100644 index 00000000..e15d06ee --- /dev/null +++ b/pytest_tests/testsuites/access/ape/test_bearer.py @@ -0,0 +1,190 @@ +import allure +import pytest +from frostfs_testlib import reporter +from frostfs_testlib.cli.frostfs_cli.cli import FrostfsCli +from frostfs_testlib.storage.dataclasses import ape +from frostfs_testlib.storage.dataclasses.wallet import WalletInfo +from frostfs_testlib.testing.cluster_test_base import ClusterTestBase + +from pytest_tests.helpers.bearer_token import create_bearer_token +from pytest_tests.helpers.container_access import ( + ALL_OBJECT_OPERATIONS, + assert_access_to_container, + assert_full_access_to_container, + assert_no_access_to_container, +) + + +@pytest.mark.sanity +@pytest.mark.bearer +@pytest.mark.ape +class TestApeBearer(ClusterTestBase): + @allure.title("Operations with BearerToken (role={role}, obj_size={object_size})") + @pytest.mark.parametrize("role", [ape.Role.OWNER, ape.Role.OTHERS], indirect=True) + def test_bearer_token_operations( + self, + container_with_objects: tuple[str, list[str], str], + frostfs_cli: FrostfsCli, + temp_directory: str, + test_wallet: WalletInfo, + role: ape.Role, + ): + cid, objects_oids, file_path = container_with_objects + endpoint = self.cluster.default_rpc_endpoint + + with reporter.step(f"Check {role} has full access to container without bearer token"): + assert_full_access_to_container(test_wallet, cid, objects_oids.pop(), file_path, self.shell, self.cluster) + + with reporter.step(f"Deny all operations for everyone via APE"): + rule = ape.Rule(ape.Verb.DENY, ALL_OBJECT_OPERATIONS) + frostfs_cli.ape_manager.add(endpoint, rule.chain_id, target_name=cid, target_type="container", rule=rule.as_string()) + + with reporter.step("Wait for one block"): + self.wait_for_blocks() + + with reporter.step(f"Create bearer token with all operations allowed"): + bearer = create_bearer_token( + frostfs_cli, + temp_directory, + cid, + rule=ape.Rule(ape.Verb.ALLOW, ALL_OBJECT_OPERATIONS), + endpoint=self.cluster.default_rpc_endpoint, + ) + + with reporter.step(f"Check {role} without token has no access to all operations with container"): + assert_no_access_to_container(test_wallet, cid, objects_oids.pop(), file_path, self.shell, self.cluster) + + with reporter.step(f"Check {role} with token has access to all operations with container"): + assert_full_access_to_container(test_wallet, cid, objects_oids.pop(), file_path, self.shell, self.cluster, bearer) + + with reporter.step(f"Remove deny rule from APE"): + frostfs_cli.ape_manager.remove(endpoint, rule.chain_id, target_name=cid, target_type="container") + + with reporter.step("Wait for one block"): + self.wait_for_blocks() + + with reporter.step(f"Check {role} without token has access to all operations with container"): + assert_full_access_to_container(test_wallet, cid, objects_oids.pop(), file_path, self.shell, self.cluster) + + @allure.title("BearerToken for compound operations (obj_size={object_size})") + def test_bearer_token_compound_operations( + self, + frostfs_cli: FrostfsCli, + temp_directory: str, + default_wallet: WalletInfo, + other_wallet: WalletInfo, + container_with_objects: tuple[str, list[str], str], + ): + """ + Bearer Token COMPLETLY overrides chains set for the specific target. + Thus, any restictions or permissions should be explicitly defined in BT. + """ + + endpoint = self.cluster.default_rpc_endpoint + cid, objects_oids, file_path = container_with_objects + + wallets_map = { + ape.Role.OWNER: default_wallet, + ape.Role.OTHERS: other_wallet, + } + + access_map = { + ape.Role.OWNER: { + ape.ObjectOperations.PUT: True, + ape.ObjectOperations.GET: True, + ape.ObjectOperations.HEAD: True, + ape.ObjectOperations.GET_RANGE: True, + ape.ObjectOperations.GET_RANGE_HASH: True, + ape.ObjectOperations.SEARCH: True, + ape.ObjectOperations.DELETE: False, + }, + ape.Role.OTHERS: { + ape.ObjectOperations.PUT: True, + ape.ObjectOperations.GET: True, + ape.ObjectOperations.HEAD: True, + ape.ObjectOperations.GET_RANGE: False, + ape.ObjectOperations.GET_RANGE_HASH: False, + ape.ObjectOperations.SEARCH: False, + ape.ObjectOperations.DELETE: True, + }, + } + + bt_access_map = { + ape.Role.OWNER: { + ape.ObjectOperations.PUT: True, + ape.ObjectOperations.GET: True, + ape.ObjectOperations.HEAD: True, + ape.ObjectOperations.GET_RANGE: True, + ape.ObjectOperations.GET_RANGE_HASH: True, + ape.ObjectOperations.SEARCH: True, + ape.ObjectOperations.DELETE: True, + }, + ape.Role.OTHERS: { + ape.ObjectOperations.PUT: True, + ape.ObjectOperations.GET: False, + ape.ObjectOperations.HEAD: True, + ape.ObjectOperations.GET_RANGE: False, + ape.ObjectOperations.GET_RANGE_HASH: False, + # Although SEARCH is denied by the APE chain defined in Policy contract, + # Bearer Token COMPLETLY overrides chains set for the specific target. + # Thus, any restictions or permissions should be explicitly defined in BT. + ape.ObjectOperations.SEARCH: True, + ape.ObjectOperations.DELETE: True, + }, + } + + # Operations that we will deny for each role via APE + deny_map = { + ape.Role.OWNER: [ape.ObjectOperations.DELETE], + ape.Role.OTHERS: [ + ape.ObjectOperations.SEARCH, + ape.ObjectOperations.GET_RANGE_HASH, + ape.ObjectOperations.GET_RANGE, + ], + } + + # Operations that we will allow for each role with bearer token + bearer_map = { + ape.Role.OWNER: [ + ape.ObjectOperations.DELETE, + ape.ObjectOperations.PUT, + ape.ObjectOperations.GET_RANGE, + ], + ape.Role.OTHERS: [ + ape.ObjectOperations.GET, + ape.ObjectOperations.GET_RANGE, + ape.ObjectOperations.GET_RANGE_HASH, + ], + } + + conditions_map = { + ape.Role.OWNER: ape.Condition.by_role(ape.Role.OWNER), + ape.Role.OTHERS: ape.Condition.by_role(ape.Role.OTHERS), + } + + verb_map = {ape.Role.OWNER: ape.Verb.ALLOW, ape.Role.OTHERS: ape.Verb.DENY} + + for role, operations in deny_map.items(): + with reporter.step(f"Add APE deny rule for {role}"): + rule = ape.Rule(ape.Verb.DENY, operations, conditions_map[role]) + frostfs_cli.ape_manager.add(endpoint, rule.chain_id, target_name=cid, target_type="container", rule=rule.as_string()) + + with reporter.step("Wait for one block"): + self.wait_for_blocks() + + for role, wallet in wallets_map.items(): + with reporter.step(f"Assert access to container without bearer token for {role}"): + assert_access_to_container(access_map[role], wallet, cid, objects_oids.pop(), file_path, self.shell, self.cluster) + + bearer_tokens = {} + for role in wallets_map.keys(): + with reporter.step(f"Create bearer token for {role}"): + rule = ape.Rule(verb_map[role], bearer_map[role], conditions_map[role]) + bt = create_bearer_token(frostfs_cli, temp_directory, cid, rule, endpoint) + bearer_tokens[role] = bt + + for role, wallet in wallets_map.items(): + with reporter.step(f"Assert access to container with bearer token for {role}"): + assert_access_to_container( + bt_access_map[role], wallet, cid, objects_oids.pop(), file_path, self.shell, self.cluster, bearer_tokens[role] + ) diff --git a/pytest_tests/testsuites/access/conftest.py b/pytest_tests/testsuites/access/conftest.py new file mode 100644 index 00000000..278759d5 --- /dev/null +++ b/pytest_tests/testsuites/access/conftest.py @@ -0,0 +1,64 @@ +import pytest +from frostfs_testlib import reporter +from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL +from frostfs_testlib.shell import Shell +from frostfs_testlib.steps.cli.container import create_container +from frostfs_testlib.steps.cli.object import put_object_to_random_node +from frostfs_testlib.storage.cluster import Cluster +from frostfs_testlib.storage.dataclasses import ape +from frostfs_testlib.storage.dataclasses.wallet import WalletInfo +from frostfs_testlib.testing.parallel import parallel + +OBJECT_COUNT = 5 + + +@pytest.fixture(scope="session") +def ir_wallet(cluster: Cluster) -> WalletInfo: + return WalletInfo.from_node(cluster.ir_nodes[0]) + + +@pytest.fixture(scope="session") +def storage_wallet(cluster: Cluster) -> WalletInfo: + return WalletInfo.from_node(cluster.storage_nodes[0]) + + +@pytest.fixture(scope="session") +def role(request: pytest.FixtureRequest): + return request.param + + +@pytest.fixture(scope="session") +def test_wallet(default_wallet: WalletInfo, other_wallet: WalletInfo, role: ape.Role): + role_to_wallet_map = { + ape.Role.OWNER: default_wallet, + ape.Role.OTHERS: other_wallet, + } + + assert role in role_to_wallet_map, "Missing wallet with role {role}" + + return role_to_wallet_map[role] + + +@pytest.fixture(scope="function") +def container_with_objects(default_wallet: WalletInfo, client_shell: Shell, cluster: Cluster, file_path: str) -> tuple[str, list[str], str]: + + with reporter.step("Create public container"): + cid = create_container( + default_wallet, + shell=client_shell, + endpoint=cluster.default_rpc_endpoint, + basic_acl=PUBLIC_ACL, + ) + + with reporter.step("Add test objects to container"): + put_results = parallel( + [put_object_to_random_node] * OBJECT_COUNT, + wallet=default_wallet, + path=file_path, + cid=cid, + shell=client_shell, + cluster=cluster, + ) + objects_oids = [put_result.result() for put_result in put_results] + + return cid, objects_oids, file_path diff --git a/pytest_tests/testsuites/acl/conftest.py b/pytest_tests/testsuites/acl/conftest.py deleted file mode 100644 index bbacd543..00000000 --- a/pytest_tests/testsuites/acl/conftest.py +++ /dev/null @@ -1,90 +0,0 @@ -import os -from dataclasses import dataclass -from datetime import datetime - -import pytest -from frostfs_testlib import reporter -from frostfs_testlib.credentials.interfaces import CredentialsProvider, User -from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL -from frostfs_testlib.shell import Shell -from frostfs_testlib.steps.cli.container import create_container -from frostfs_testlib.steps.cli.object import put_object_to_random_node -from frostfs_testlib.storage.cluster import Cluster -from frostfs_testlib.storage.dataclasses.acl import EACLRole -from frostfs_testlib.storage.dataclasses.frostfs_services import InnerRing, StorageNode -from frostfs_testlib.storage.dataclasses.wallet import WalletInfo - -OBJECT_COUNT = 5 - - -@dataclass -class Wallets: - wallets: dict[EACLRole, list[WalletInfo]] - - def get_wallet(self, role: EACLRole = EACLRole.USER) -> WalletInfo: - return self.wallets[role][0] - - def get_wallets_list(self, role: EACLRole = EACLRole.USER) -> list[WalletInfo]: - return self.wallets[role] - - -@pytest.fixture(scope="module") -def wallets(default_wallet: WalletInfo, credentials_provider: CredentialsProvider, cluster: Cluster) -> Wallets: - other_wallets: list = [] - for _ in range(2): - user = User(f"user_{hex(int(datetime.now().timestamp() * 1000000))}") - other_wallets.append(credentials_provider.GRPC.provide(user, cluster.cluster_nodes[0])) - - ir_node: InnerRing = cluster.ir_nodes[0] - storage_node: StorageNode = cluster.storage_nodes[0] - - wallets_collection = Wallets( - wallets={ - EACLRole.USER: [default_wallet], - EACLRole.OTHERS: other_wallets, - EACLRole.SYSTEM: [ - WalletInfo.from_node(ir_node), - WalletInfo.from_node(storage_node), - ], - } - ) - - for role, wallets in wallets_collection.wallets.items(): - if role == EACLRole.SYSTEM: - continue - for wallet in wallets: - reporter.attach(wallet.path, os.path.basename(wallet.path)) - - return wallets_collection - - -@pytest.fixture(scope="function") -def eacl_container_with_objects( - wallets: Wallets, client_shell: Shell, cluster: Cluster, file_path: str -) -> tuple[str, list[str], str]: - user_wallet = wallets.get_wallet() - with reporter.step("Create eACL public container"): - cid = create_container( - user_wallet, - basic_acl=PUBLIC_ACL, - shell=client_shell, - endpoint=cluster.default_rpc_endpoint, - ) - - with reporter.step("Add test objects to container"): - objects_oids = [ - put_object_to_random_node( - user_wallet, - file_path, - cid, - attributes={"key1": "val1", "key": val, "key2": "abc"}, - shell=client_shell, - cluster=cluster, - ) - for val in range(OBJECT_COUNT) - ] - - yield cid, objects_oids, file_path - - # with reporter.step('Delete eACL public container'): - # delete_container(user_wallet, cid) diff --git a/pytest_tests/testsuites/acl/test_acl.py b/pytest_tests/testsuites/acl/test_acl.py deleted file mode 100644 index 80348760..00000000 --- a/pytest_tests/testsuites/acl/test_acl.py +++ /dev/null @@ -1,151 +0,0 @@ -import allure -import pytest -from frostfs_testlib import reporter -from frostfs_testlib.resources.wellknown_acl import PRIVATE_ACL_F, PUBLIC_ACL_F, READONLY_ACL_F -from frostfs_testlib.shell import Shell -from frostfs_testlib.steps.cli.container import create_container -from frostfs_testlib.steps.cli.object import put_object_to_random_node -from frostfs_testlib.storage.dataclasses.acl import EACLRole -from frostfs_testlib.testing.cluster_test_base import ClusterTestBase - -from pytest_tests.helpers.container_access import ( - check_full_access_to_container, - check_no_access_to_container, - check_read_only_container, -) -from pytest_tests.testsuites.acl.conftest import Wallets - - -@pytest.mark.sanity -@pytest.mark.smoke -@pytest.mark.acl -@pytest.mark.acl_basic -class TestACLBasic(ClusterTestBase): - @pytest.fixture(scope="function") - def public_container(self, wallets: Wallets): - user_wallet = wallets.get_wallet() - with reporter.step("Create public container"): - cid_public = create_container( - user_wallet, - basic_acl=PUBLIC_ACL_F, - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - - yield cid_public - - # with reporter.step('Delete public container'): - # delete_container(user_wallet, cid_public) - - @pytest.fixture(scope="function") - def private_container(self, wallets: Wallets): - user_wallet = wallets.get_wallet() - with reporter.step("Create private container"): - cid_private = create_container( - user_wallet, - basic_acl=PRIVATE_ACL_F, - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - - yield cid_private - - # with reporter.step('Delete private container'): - # delete_container(user_wallet, cid_private) - - @pytest.fixture(scope="function") - def read_only_container(self, wallets: Wallets): - user_wallet = wallets.get_wallet() - with reporter.step("Create public readonly container"): - cid_read_only = create_container( - user_wallet, - basic_acl=READONLY_ACL_F, - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - - yield cid_read_only - - # with reporter.step('Delete public readonly container'): - # delete_container(user_wallet, cid_read_only) - - @allure.title("Operations with basic ACL on public container (obj_size={object_size})") - def test_basic_acl_public(self, wallets: Wallets, public_container: str, file_path: str): - """ - Test basic ACL set during public container creation. - """ - user_wallet = wallets.get_wallet() - other_wallet = wallets.get_wallet(role=EACLRole.OTHERS) - cid = public_container - for wallet, desc in ((user_wallet, "owner"), (other_wallet, "other users")): - with reporter.step("Add test objects to container"): - # We create new objects for each wallet because check_full_access_to_container - # deletes the object - owner_object_oid = put_object_to_random_node( - user_wallet, - file_path, - cid, - shell=self.shell, - cluster=self.cluster, - attributes={"created": "owner"}, - ) - other_object_oid = put_object_to_random_node( - other_wallet, - file_path, - cid, - shell=self.shell, - cluster=self.cluster, - attributes={"created": "other"}, - ) - with reporter.step(f"Check {desc} has full access to public container"): - check_full_access_to_container( - wallet, - cid, - owner_object_oid, - file_path, - shell=self.shell, - cluster=self.cluster, - ) - check_full_access_to_container( - wallet, - cid, - other_object_oid, - file_path, - shell=self.shell, - cluster=self.cluster, - ) - - @allure.title("Operations with basic ACL on PRIVATE container (obj_size={object_size})") - def test_basic_acl_private(self, wallets: Wallets, private_container: str, file_path: str): - """ - Test basic ACL set during private container creation. - """ - user_wallet = wallets.get_wallet() - other_wallet = wallets.get_wallet(role=EACLRole.OTHERS) - cid = private_container - with reporter.step("Add test objects to container"): - owner_object_oid = put_object_to_random_node(user_wallet, file_path, cid, self.shell, self.cluster) - - with reporter.step("Check no one except owner has access to operations with container"): - check_no_access_to_container(other_wallet, cid, owner_object_oid, file_path, self.shell, self.cluster) - - with reporter.step("Check owner has full access to private container"): - check_full_access_to_container(user_wallet, cid, owner_object_oid, file_path, self.shell, self.cluster) - - @allure.title("Operations with basic ACL on READONLY container (obj_size={object_size})") - def test_basic_acl_readonly(self, wallets: Wallets, client_shell: Shell, read_only_container: str, file_path: str): - """ - Test basic ACL Operations for Read-Only Container. - """ - user_wallet = wallets.get_wallet() - other_wallet = wallets.get_wallet(role=EACLRole.OTHERS) - cid = read_only_container - - with reporter.step("Add test objects to container"): - object_oid = put_object_to_random_node(user_wallet, file_path, cid, client_shell, self.cluster) - - with reporter.step("Check other has read-only access to operations with container"): - check_read_only_container(other_wallet, cid, object_oid, file_path, client_shell, self.cluster) - - with reporter.step("Check owner has full access to public container"): - check_full_access_to_container(user_wallet, cid, object_oid, file_path, client_shell, self.cluster) diff --git a/pytest_tests/testsuites/acl/test_bearer.py b/pytest_tests/testsuites/acl/test_bearer.py deleted file mode 100644 index e0f5f8ad..00000000 --- a/pytest_tests/testsuites/acl/test_bearer.py +++ /dev/null @@ -1,205 +0,0 @@ -import allure -import pytest -from frostfs_testlib import reporter -from frostfs_testlib.steps.acl import create_eacl, form_bearertoken_file, set_eacl, wait_for_cache_expired -from frostfs_testlib.storage.dataclasses.acl import EACLAccess, EACLOperation, EACLRole, EACLRule -from frostfs_testlib.testing.cluster_test_base import ClusterTestBase - -from pytest_tests.helpers.container_access import ( - check_custom_access_to_container, - check_full_access_to_container, - check_no_access_to_container, -) -from pytest_tests.testsuites.acl.conftest import Wallets - - -@pytest.mark.sanity -@pytest.mark.acl -@pytest.mark.acl_bearer -class TestACLBearer(ClusterTestBase): - @allure.title("Operations with BearerToken (role={role.value}, obj_size={object_size})") - @pytest.mark.parametrize("role", [EACLRole.USER, EACLRole.OTHERS]) - def test_bearer_token_operations( - self, - wallets: Wallets, - eacl_container_with_objects: tuple[str, list[str], str], - role: EACLRole, - ): - cid, objects_oids, file_path = eacl_container_with_objects - user_wallet = wallets.get_wallet() - deny_wallet = wallets.get_wallet(role) - endpoint = self.cluster.default_rpc_endpoint - - with reporter.step(f"Check {role.value} has full access to container without bearer token"): - check_full_access_to_container( - deny_wallet, - cid, - objects_oids.pop(), - file_path, - shell=self.shell, - cluster=self.cluster, - ) - - with reporter.step(f"Set deny all operations for {role.value} via eACL"): - eacl = [EACLRule(access=EACLAccess.DENY, role=role, operation=op) for op in EACLOperation] - eacl_file = create_eacl(cid, eacl, shell=self.shell) - set_eacl(user_wallet, cid, eacl_file, shell=self.shell, endpoint=endpoint) - wait_for_cache_expired() - - with reporter.step(f"Create bearer token for {role.value} with all operations allowed"): - bearer = form_bearertoken_file( - user_wallet, - cid, - [EACLRule(operation=op, access=EACLAccess.ALLOW, role=role) for op in EACLOperation], - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - - with reporter.step(f"Check {role.value} without token has no access to all operations with container"): - check_no_access_to_container( - deny_wallet, - cid, - objects_oids.pop(), - file_path, - shell=self.shell, - cluster=self.cluster, - ) - - with reporter.step(f"Check {role.value} with token has access to all operations with container"): - check_full_access_to_container( - deny_wallet, - cid, - objects_oids.pop(), - file_path, - bearer=bearer, - shell=self.shell, - cluster=self.cluster, - ) - - with reporter.step(f"Set allow all operations for {role.value} via eACL"): - eacl = [EACLRule(access=EACLAccess.ALLOW, role=role, operation=op) for op in EACLOperation] - eacl_file = create_eacl(cid, eacl, shell=self.shell) - set_eacl(user_wallet, cid, eacl_file, shell=self.shell, endpoint=endpoint) - wait_for_cache_expired() - - with reporter.step(f"Check {role.value} without token has access to all operations with container"): - check_full_access_to_container( - deny_wallet, - cid, - objects_oids.pop(), - file_path, - shell=self.shell, - cluster=self.cluster, - ) - - @allure.title("BearerToken for compound operations (obj_size={object_size})") - def test_bearer_token_compound_operations(self, wallets: Wallets, eacl_container_with_objects): - endpoint = self.cluster.default_rpc_endpoint - cid, objects_oids, file_path = eacl_container_with_objects - user_wallet = wallets.get_wallet() - other_wallet = wallets.get_wallet(role=EACLRole.OTHERS) - - # Operations that we will deny for each role via eACL - deny_map = { - EACLRole.USER: [EACLOperation.DELETE], - EACLRole.OTHERS: [ - EACLOperation.SEARCH, - EACLOperation.GET_RANGE_HASH, - EACLOperation.GET_RANGE, - ], - } - - # Operations that we will allow for each role with bearer token - bearer_map = { - EACLRole.USER: [ - EACLOperation.DELETE, - EACLOperation.PUT, - EACLOperation.GET_RANGE, - ], - EACLRole.OTHERS: [ - EACLOperation.GET, - EACLOperation.GET_RANGE, - EACLOperation.GET_RANGE_HASH, - ], - } - - deny_map_with_bearer = { - EACLRole.USER: [op for op in deny_map[EACLRole.USER] if op not in bearer_map[EACLRole.USER]], - EACLRole.OTHERS: [op for op in deny_map[EACLRole.OTHERS] if op not in bearer_map[EACLRole.OTHERS]], - } - - eacl_deny = [] - for role, operations in deny_map.items(): - eacl_deny += [EACLRule(access=EACLAccess.DENY, role=role, operation=op) for op in operations] - set_eacl( - user_wallet, - cid, - eacl_table_path=create_eacl(cid, eacl_deny, shell=self.shell), - shell=self.shell, - endpoint=endpoint, - ) - wait_for_cache_expired() - - with reporter.step("Check rule consistency without bearer"): - check_custom_access_to_container( - user_wallet, - cid, - objects_oids.pop(), - file_path, - deny_operations=deny_map[EACLRole.USER], - shell=self.shell, - cluster=self.cluster, - ) - check_custom_access_to_container( - other_wallet, - cid, - objects_oids.pop(), - file_path, - deny_operations=deny_map[EACLRole.OTHERS], - shell=self.shell, - cluster=self.cluster, - ) - - with reporter.step("Check rule consistency using bearer token"): - bearer_user = form_bearertoken_file( - user_wallet, - cid, - [ - EACLRule(operation=op, access=EACLAccess.ALLOW, role=EACLRole.USER) - for op in bearer_map[EACLRole.USER] - ], - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - - bearer_other = form_bearertoken_file( - user_wallet, - cid, - [ - EACLRule(operation=op, access=EACLAccess.ALLOW, role=EACLRole.OTHERS) - for op in bearer_map[EACLRole.OTHERS] - ], - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - - check_custom_access_to_container( - user_wallet, - cid, - objects_oids.pop(), - file_path, - deny_operations=deny_map_with_bearer[EACLRole.USER], - bearer=bearer_user, - shell=self.shell, - cluster=self.cluster, - ) - check_custom_access_to_container( - other_wallet, - cid, - objects_oids.pop(), - file_path, - deny_operations=deny_map_with_bearer[EACLRole.OTHERS], - bearer=bearer_other, - shell=self.shell, - cluster=self.cluster, - ) diff --git a/pytest_tests/testsuites/acl/test_eacl.py b/pytest_tests/testsuites/acl/test_eacl.py deleted file mode 100644 index 6905c3cf..00000000 --- a/pytest_tests/testsuites/acl/test_eacl.py +++ /dev/null @@ -1,612 +0,0 @@ -import allure -import pytest -from frostfs_testlib import reporter -from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL -from frostfs_testlib.steps.acl import create_eacl, set_eacl, wait_for_cache_expired -from frostfs_testlib.steps.cli.container import create_container -from frostfs_testlib.steps.cli.object import put_object_to_random_node -from frostfs_testlib.steps.node_management import drop_object -from frostfs_testlib.storage.dataclasses.acl import EACLAccess, EACLOperation, EACLRole, EACLRule -from frostfs_testlib.testing.cluster_test_base import ClusterTestBase -from frostfs_testlib.utils.failover_utils import wait_object_replication - -from pytest_tests.helpers.container_access import check_full_access_to_container, check_no_access_to_container -from pytest_tests.helpers.object_access import ( - can_delete_object, - can_get_head_object, - can_get_object, - can_get_range_hash_of_object, - can_get_range_of_object, - can_put_object, - can_search_object, -) -from pytest_tests.testsuites.acl.conftest import Wallets - - -@pytest.mark.acl -@pytest.mark.acl_extended -class TestEACLContainer(ClusterTestBase): - @pytest.fixture(scope="function") - def eacl_full_placement_container_with_object(self, wallets: Wallets, file_path: str) -> tuple[str, str, str]: - user_wallet = wallets.get_wallet() - storage_nodes = self.cluster.storage_nodes - node_count = len(storage_nodes) - with reporter.step("Create eACL public container with full placement rule"): - full_placement_rule = f"REP {node_count} IN X CBF 1 SELECT {node_count} FROM * AS X" - cid = create_container( - wallet=user_wallet, - rule=full_placement_rule, - basic_acl=PUBLIC_ACL, - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - - with reporter.step("Add test object to container"): - oid = put_object_to_random_node(user_wallet, file_path, cid, shell=self.shell, cluster=self.cluster) - wait_object_replication( - cid, - oid, - node_count, - shell=self.shell, - nodes=storage_nodes, - ) - - yield cid, oid, file_path - - @pytest.mark.sanity - @allure.title("Deny operations (role={deny_role.value}, obj_size={object_size})") - @pytest.mark.parametrize("deny_role", [EACLRole.USER, EACLRole.OTHERS]) - def test_extended_acl_deny_all_operations( - self, - wallets: Wallets, - eacl_container_with_objects: tuple[str, list[str], str], - deny_role: EACLRole, - ): - user_wallet = wallets.get_wallet() - other_wallet = wallets.get_wallet(EACLRole.OTHERS) - deny_role_wallet = other_wallet if deny_role == EACLRole.OTHERS else user_wallet - not_deny_role_wallet = user_wallet if deny_role == EACLRole.OTHERS else other_wallet - deny_role_str = "all others" if deny_role == EACLRole.OTHERS else "user" - not_deny_role_str = "user" if deny_role == EACLRole.OTHERS else "all others" - cid, object_oids, file_path = eacl_container_with_objects - - with reporter.step(f"Deny all operations for {deny_role_str} via eACL"): - eacl_deny = [EACLRule(access=EACLAccess.DENY, role=deny_role, operation=op) for op in EACLOperation] - set_eacl( - user_wallet, - cid, - create_eacl(cid, eacl_deny, shell=self.shell), - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - wait_for_cache_expired() - - with reporter.step(f"Check only {not_deny_role_str} has full access to container"): - with reporter.step(f"Check {deny_role_str} has not access to any operations with container"): - check_no_access_to_container( - deny_role_wallet, - cid, - object_oids[0], - file_path, - shell=self.shell, - cluster=self.cluster, - ) - - with reporter.step(f"Check {not_deny_role_str} has full access to eACL public container"): - check_full_access_to_container( - not_deny_role_wallet, - cid, - object_oids.pop(), - file_path, - shell=self.shell, - cluster=self.cluster, - ) - - with reporter.step(f"Allow all operations for {deny_role_str} via eACL"): - eacl_deny = [EACLRule(access=EACLAccess.ALLOW, role=deny_role, operation=op) for op in EACLOperation] - set_eacl( - user_wallet, - cid, - create_eacl(cid, eacl_deny, shell=self.shell), - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - wait_for_cache_expired() - - with reporter.step("Check all have full access to eACL public container"): - check_full_access_to_container( - user_wallet, - cid, - object_oids.pop(), - file_path, - shell=self.shell, - cluster=self.cluster, - ) - check_full_access_to_container( - other_wallet, - cid, - object_oids.pop(), - file_path, - shell=self.shell, - cluster=self.cluster, - ) - - @allure.title("Operations for only one other pubkey (obj_size={object_size})") - def test_extended_acl_deny_all_operations_exclude_pubkey( - self, wallets: Wallets, eacl_container_with_objects: tuple[str, list[str], str] - ): - user_wallet = wallets.get_wallet() - other_wallet, other_wallet_allow = wallets.get_wallets_list(EACLRole.OTHERS)[0:2] - cid, object_oids, file_path = eacl_container_with_objects - - with reporter.step("Deny all operations for others except single wallet via eACL"): - eacl = [ - EACLRule( - access=EACLAccess.ALLOW, - role=other_wallet_allow, - operation=op, - ) - for op in EACLOperation - ] - eacl += [EACLRule(access=EACLAccess.DENY, role=EACLRole.OTHERS, operation=op) for op in EACLOperation] - set_eacl( - user_wallet, - cid, - create_eacl(cid, eacl, shell=self.shell), - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - wait_for_cache_expired() - - with reporter.step("Check only owner and allowed other have full access to public container"): - with reporter.step("Check other has not access to operations with container"): - check_no_access_to_container( - other_wallet, - cid, - object_oids[0], - file_path, - shell=self.shell, - cluster=self.cluster, - ) - - with reporter.step("Check owner has full access to public container"): - check_full_access_to_container( - user_wallet, - cid, - object_oids.pop(), - file_path, - shell=self.shell, - cluster=self.cluster, - ) - - with reporter.step("Check allowed other has full access to public container"): - check_full_access_to_container( - other_wallet_allow, - cid, - object_oids.pop(), - file_path, - shell=self.shell, - cluster=self.cluster, - ) - - @allure.title("Replication with eACL deny rules (obj_size={object_size})") - def test_extended_acl_deny_replication( - self, - wallets: Wallets, - eacl_full_placement_container_with_object: tuple[str, list[str], str], - ): - user_wallet = wallets.get_wallet() - cid, oid, file_path = eacl_full_placement_container_with_object - storage_nodes = self.cluster.storage_nodes - storage_node = self.cluster.storage_nodes[0] - - with reporter.step("Deny all operations for user via eACL"): - eacl_deny = [EACLRule(access=EACLAccess.DENY, role=EACLRole.USER, operation=op) for op in EACLOperation] - eacl_deny += [EACLRule(access=EACLAccess.DENY, role=EACLRole.OTHERS, operation=op) for op in EACLOperation] - set_eacl( - user_wallet, - cid, - create_eacl(cid, eacl_deny, shell=self.shell), - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - wait_for_cache_expired() - - with reporter.step("Drop object to check replication"): - drop_object(storage_node, cid=cid, oid=oid) - - storage_wallet_path = storage_node.get_wallet_path() - with reporter.step("Wait for dropped object replicated"): - wait_object_replication( - cid, - oid, - len(storage_nodes), - self.shell, - storage_nodes, - ) - - @allure.title("Operations with extended ACL for SYSTEM (obj_size={object_size})") - def test_extended_actions_system(self, wallets: Wallets, eacl_container_with_objects: tuple[str, list[str], str]): - user_wallet = wallets.get_wallet() - ir_wallet, storage_wallet = wallets.get_wallets_list(role=EACLRole.SYSTEM)[:2] - - cid, object_oids, file_path = eacl_container_with_objects - endpoint = self.cluster.default_rpc_endpoint - - with reporter.step("Check IR and STORAGE rules compliance"): - assert not can_put_object( - ir_wallet, - cid, - file_path, - shell=self.shell, - cluster=self.cluster, - ) - assert can_put_object( - storage_wallet, - cid, - file_path, - shell=self.shell, - cluster=self.cluster, - ) - - assert can_get_object( - ir_wallet, - cid, - object_oids[0], - file_path, - shell=self.shell, - cluster=self.cluster, - ) - assert can_get_object( - storage_wallet, - cid, - object_oids[0], - file_path, - shell=self.shell, - cluster=self.cluster, - ) - - assert can_get_head_object( - ir_wallet, - cid, - object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - assert can_get_head_object( - storage_wallet, - cid, - object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - - assert can_search_object( - ir_wallet, - cid, - shell=self.shell, - endpoint=endpoint, - oid=object_oids[0], - ) - assert can_search_object( - storage_wallet, - cid, - shell=self.shell, - endpoint=endpoint, - oid=object_oids[0], - ) - - with pytest.raises(AssertionError): - assert can_get_range_of_object( - wallet=ir_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - with pytest.raises(AssertionError): - assert can_get_range_of_object( - wallet=storage_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - - assert can_get_range_hash_of_object( - wallet=ir_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - - assert can_get_range_hash_of_object( - wallet=storage_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - - with pytest.raises(AssertionError): - assert can_delete_object( - wallet=ir_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - with pytest.raises(AssertionError): - assert can_delete_object( - wallet=storage_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - - with reporter.step("Deny all operations for SYSTEM via eACL"): - set_eacl( - user_wallet, - cid, - create_eacl( - cid=cid, - rules_list=[ - EACLRule(access=EACLAccess.DENY, role=EACLRole.SYSTEM, operation=op) for op in EACLOperation - ], - shell=self.shell, - ), - shell=self.shell, - endpoint=endpoint, - ) - wait_for_cache_expired() - - with reporter.step("Check IR and STORAGE rules compliance with deny eACL"): - assert not can_put_object( - wallet=ir_wallet, - cid=cid, - file_name=file_path, - shell=self.shell, - cluster=self.cluster, - ) - assert not can_put_object( - wallet=storage_wallet, - cid=cid, - file_name=file_path, - shell=self.shell, - cluster=self.cluster, - ) - - with pytest.raises(AssertionError): - assert can_get_object( - wallet=ir_wallet, - cid=cid, - oid=object_oids[0], - file_name=file_path, - shell=self.shell, - cluster=self.cluster, - ) - with pytest.raises(AssertionError): - assert can_get_object( - wallet=storage_wallet, - cid=cid, - oid=object_oids[0], - file_name=file_path, - shell=self.shell, - cluster=self.cluster, - ) - - with pytest.raises(AssertionError): - assert can_get_head_object( - wallet=ir_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - with pytest.raises(AssertionError): - assert can_get_head_object( - wallet=storage_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - - with pytest.raises(AssertionError): - assert can_search_object( - wallet=ir_wallet, - cid=cid, - shell=self.shell, - endpoint=endpoint, - oid=object_oids[0], - ) - with pytest.raises(AssertionError): - assert can_search_object( - wallet=storage_wallet, - cid=cid, - shell=self.shell, - endpoint=endpoint, - oid=object_oids[0], - ) - - with pytest.raises(AssertionError): - assert can_get_range_of_object( - wallet=ir_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - with pytest.raises(AssertionError): - assert can_get_range_of_object( - wallet=storage_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - - with pytest.raises(AssertionError): - assert can_get_range_hash_of_object( - wallet=ir_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - with pytest.raises(AssertionError): - assert can_get_range_hash_of_object( - wallet=storage_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - - with pytest.raises(AssertionError): - assert can_delete_object( - wallet=ir_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - with pytest.raises(AssertionError): - assert can_delete_object( - wallet=storage_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - - with reporter.step("Allow all operations for SYSTEM via eACL"): - set_eacl( - user_wallet, - cid, - create_eacl( - cid=cid, - rules_list=[ - EACLRule(access=EACLAccess.ALLOW, role=EACLRole.SYSTEM, operation=op) for op in EACLOperation - ], - shell=self.shell, - ), - shell=self.shell, - endpoint=endpoint, - ) - wait_for_cache_expired() - - with reporter.step("Check IR and STORAGE rules compliance with allow eACL"): - assert not can_put_object( - wallet=ir_wallet, - cid=cid, - file_name=file_path, - shell=self.shell, - cluster=self.cluster, - ) - assert can_put_object( - wallet=storage_wallet, - cid=cid, - file_name=file_path, - shell=self.shell, - cluster=self.cluster, - ) - - assert can_get_object( - wallet=ir_wallet, - cid=cid, - oid=object_oids[0], - file_name=file_path, - shell=self.shell, - cluster=self.cluster, - ) - assert can_get_object( - wallet=storage_wallet, - cid=cid, - oid=object_oids[0], - file_name=file_path, - shell=self.shell, - cluster=self.cluster, - ) - - assert can_get_head_object( - wallet=ir_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - assert can_get_head_object( - wallet=storage_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - - assert can_search_object( - wallet=ir_wallet, - cid=cid, - shell=self.shell, - oid=object_oids[0], - endpoint=endpoint, - ) - assert can_search_object( - wallet=storage_wallet, - cid=cid, - shell=self.shell, - oid=object_oids[0], - endpoint=endpoint, - ) - - with pytest.raises(AssertionError): - assert can_get_range_of_object( - wallet=ir_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - with pytest.raises(AssertionError): - assert can_get_range_of_object( - wallet=storage_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - - assert can_get_range_hash_of_object( - wallet=ir_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - - assert can_get_range_hash_of_object( - wallet=storage_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - - with pytest.raises(AssertionError): - assert can_delete_object( - wallet=ir_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) - with pytest.raises(AssertionError): - assert can_delete_object( - wallet=storage_wallet, - cid=cid, - oid=object_oids[0], - shell=self.shell, - endpoint=endpoint, - ) diff --git a/pytest_tests/testsuites/acl/test_eacl_filters.py b/pytest_tests/testsuites/acl/test_eacl_filters.py deleted file mode 100644 index cce219e2..00000000 --- a/pytest_tests/testsuites/acl/test_eacl_filters.py +++ /dev/null @@ -1,588 +0,0 @@ -import allure -import pytest -from frostfs_testlib import reporter -from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL -from frostfs_testlib.steps.acl import create_eacl, form_bearertoken_file, set_eacl, wait_for_cache_expired -from frostfs_testlib.steps.cli.container import create_container, delete_container -from frostfs_testlib.steps.cli.object import put_object_to_random_node -from frostfs_testlib.storage.dataclasses.acl import ( - EACLAccess, - EACLFilter, - EACLFilters, - EACLHeaderType, - EACLMatchType, - EACLOperation, - EACLRole, - EACLRule, -) -from frostfs_testlib.testing.cluster_test_base import ClusterTestBase - -from pytest_tests.helpers.container_access import check_full_access_to_container, check_no_access_to_container -from pytest_tests.helpers.object_access import can_get_head_object, can_get_object, can_put_object -from pytest_tests.testsuites.acl.conftest import Wallets - - -@pytest.mark.acl -@pytest.mark.acl_filters -class TestEACLFilters(ClusterTestBase): - # SPEC: https://github.com/nspcc-dev/neofs-spec/blob/master/01-arch/07-acl.md - ATTRIBUTE = {"check_key": "check_value"} - OTHER_ATTRIBUTE = {"check_key": "other_value"} - SET_HEADERS = { - "key_one": "check_value", - "x_key": "xvalue", - "check_key": "check_value", - } - OTHER_HEADERS = { - "key_one": "check_value", - "x_key": "other_value", - "check_key": "other_value", - } - REQ_EQUAL_FILTER = EACLFilter(key="check_key", value="check_value", header_type=EACLHeaderType.REQUEST) - NOT_REQ_EQUAL_FILTER = EACLFilter( - key="check_key", - value="other_value", - match_type=EACLMatchType.STRING_NOT_EQUAL, - header_type=EACLHeaderType.REQUEST, - ) - OBJ_EQUAL_FILTER = EACLFilter(key="check_key", value="check_value", header_type=EACLHeaderType.OBJECT) - NOT_OBJ_EQUAL_FILTER = EACLFilter( - key="check_key", - value="other_value", - match_type=EACLMatchType.STRING_NOT_EQUAL, - header_type=EACLHeaderType.OBJECT, - ) - OBJECT_COUNT = 5 - OBJECT_ATTRIBUTES_FILTER_SUPPORTED_OPERATIONS = [ - EACLOperation.GET, - EACLOperation.HEAD, - EACLOperation.PUT, - ] - - @pytest.fixture(scope="function") - def eacl_container_with_objects(self, wallets: Wallets, file_path: str): - user_wallet = wallets.get_wallet() - with reporter.step("Create eACL public container"): - cid = create_container( - user_wallet, - basic_acl=PUBLIC_ACL, - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - - with reporter.step("Add test objects to container"): - objects_with_header = [ - put_object_to_random_node( - user_wallet, - file_path, - cid, - shell=self.shell, - cluster=self.cluster, - attributes={**self.SET_HEADERS, "key": val}, - ) - for val in range(self.OBJECT_COUNT) - ] - - objects_with_other_header = [ - put_object_to_random_node( - user_wallet, - file_path, - cid, - shell=self.shell, - cluster=self.cluster, - attributes={**self.OTHER_HEADERS, "key": val}, - ) - for val in range(self.OBJECT_COUNT) - ] - - objects_without_header = [ - put_object_to_random_node( - user_wallet, - file_path, - cid, - shell=self.shell, - cluster=self.cluster, - ) - for _ in range(self.OBJECT_COUNT) - ] - - yield cid, objects_with_header, objects_with_other_header, objects_without_header, file_path - - with reporter.step("Delete eACL public container"): - delete_container( - user_wallet, - cid, - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - - @pytest.mark.sanity - @allure.title("Operations with request filter (match_type={match_type}, obj_size={object_size})") - @pytest.mark.parametrize("match_type", [EACLMatchType.STRING_EQUAL, EACLMatchType.STRING_NOT_EQUAL]) - def test_extended_acl_filters_request( - self, - wallets: Wallets, - eacl_container_with_objects: tuple[str, list[str], str], - match_type: EACLMatchType, - ): - user_wallet = wallets.get_wallet() - other_wallet = wallets.get_wallet(EACLRole.OTHERS) - ( - cid, - objects_with_header, - objects_with_other_header, - objects_without_header, - file_path, - ) = eacl_container_with_objects - - with reporter.step("Deny all operations for other with eACL request filter"): - equal_filter = EACLFilter(**self.REQ_EQUAL_FILTER.__dict__) - equal_filter.match_type = match_type - eacl_deny = [ - EACLRule( - access=EACLAccess.DENY, - role=EACLRole.OTHERS, - filters=EACLFilters([equal_filter]), - operation=op, - ) - for op in EACLOperation - ] - set_eacl( - user_wallet, - cid, - create_eacl(cid, eacl_deny, shell=self.shell), - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - wait_for_cache_expired() - - # Filter denies requests where "check_key {match_type} ATTRIBUTE", so when match_type - # is STRING_EQUAL, then requests with "check_key=OTHER_ATTRIBUTE" will be allowed while - # requests with "check_key=ATTRIBUTE" will be denied, and vice versa - allow_headers = self.OTHER_ATTRIBUTE if match_type == EACLMatchType.STRING_EQUAL else self.ATTRIBUTE - deny_headers = self.ATTRIBUTE if match_type == EACLMatchType.STRING_EQUAL else self.OTHER_ATTRIBUTE - # We test on 3 groups of objects with various headers, - # but eACL rule should ignore object headers and - # work only based on request headers - for oid in ( - objects_with_header, - objects_with_other_header, - objects_without_header, - ): - with reporter.step("Check other has full access when sending request without headers"): - check_full_access_to_container( - other_wallet, - cid, - oid.pop(), - file_path, - shell=self.shell, - cluster=self.cluster, - ) - - with reporter.step("Check other has full access when sending request with allowed headers"): - check_full_access_to_container( - other_wallet, - cid, - oid.pop(), - file_path, - shell=self.shell, - cluster=self.cluster, - xhdr=allow_headers, - ) - - with reporter.step("Check other has no access when sending request with denied headers"): - check_no_access_to_container( - other_wallet, - cid, - oid.pop(), - file_path, - shell=self.shell, - cluster=self.cluster, - xhdr=deny_headers, - ) - - with reporter.step( - "Check other has full access when sending request " "with denied headers and using bearer token" - ): - bearer_other = form_bearertoken_file( - user_wallet, - cid, - [EACLRule(operation=op, access=EACLAccess.ALLOW, role=EACLRole.OTHERS) for op in EACLOperation], - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - check_full_access_to_container( - other_wallet, - cid, - oid.pop(), - file_path, - shell=self.shell, - cluster=self.cluster, - xhdr=deny_headers, - bearer=bearer_other, - ) - - @allure.title("Operations with deny user headers filter (match_type={match_type}, obj_size={object_size})") - @pytest.mark.parametrize("match_type", [EACLMatchType.STRING_EQUAL, EACLMatchType.STRING_NOT_EQUAL]) - def test_extended_acl_deny_filters_object( - self, - wallets: Wallets, - eacl_container_with_objects: tuple[str, list[str], str], - match_type: EACLMatchType, - ): - user_wallet = wallets.get_wallet() - other_wallet = wallets.get_wallet(EACLRole.OTHERS) - ( - cid, - objects_with_header, - objects_with_other_header, - objs_without_header, - file_path, - ) = eacl_container_with_objects - - with reporter.step("Deny all operations for other with object filter"): - equal_filter = EACLFilter(**self.OBJ_EQUAL_FILTER.__dict__) - equal_filter.match_type = match_type - eacl_deny = [ - EACLRule( - access=EACLAccess.DENY, - role=EACLRole.OTHERS, - filters=EACLFilters([equal_filter]), - operation=op, - ) - for op in self.OBJECT_ATTRIBUTES_FILTER_SUPPORTED_OPERATIONS - ] - set_eacl( - user_wallet, - cid, - create_eacl(cid, eacl_deny, shell=self.shell), - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - wait_for_cache_expired() - - allow_objects = objects_with_other_header if match_type == EACLMatchType.STRING_EQUAL else objects_with_header - deny_objects = objects_with_header if match_type == EACLMatchType.STRING_EQUAL else objects_with_other_header - - # We will attempt requests with various headers, - # but eACL rule should ignore request headers and validate - # only object headers - for xhdr in (self.ATTRIBUTE, self.OTHER_ATTRIBUTE, None): - with reporter.step("Check other have full access to objects without attributes"): - check_full_access_to_container( - other_wallet, - cid, - objs_without_header.pop(), - file_path, - shell=self.shell, - cluster=self.cluster, - xhdr=xhdr, - ) - - with reporter.step("Check other have full access to objects without deny attribute"): - check_full_access_to_container( - other_wallet, - cid, - allow_objects.pop(), - file_path, - shell=self.shell, - cluster=self.cluster, - xhdr=xhdr, - ) - - with reporter.step("Check other have no access to objects with deny attribute"): - with pytest.raises(AssertionError): - assert can_get_head_object( - other_wallet, - cid, - deny_objects[0], - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - xhdr=xhdr, - ) - with pytest.raises(AssertionError): - assert can_get_object( - other_wallet, - cid, - deny_objects[0], - file_path, - shell=self.shell, - cluster=self.cluster, - xhdr=xhdr, - ) - - with reporter.step("Check other have access to objects with deny attribute and using bearer token"): - bearer_other = form_bearertoken_file( - user_wallet, - cid, - [ - EACLRule( - operation=op, - access=EACLAccess.ALLOW, - role=EACLRole.OTHERS, - ) - for op in EACLOperation - ], - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - check_full_access_to_container( - other_wallet, - cid, - deny_objects.pop(), - file_path, - shell=self.shell, - cluster=self.cluster, - xhdr=xhdr, - bearer=bearer_other, - ) - - allow_attribute = self.OTHER_ATTRIBUTE if match_type == EACLMatchType.STRING_EQUAL else self.ATTRIBUTE - with reporter.step("Check other can PUT objects without denied attribute"): - assert can_put_object( - other_wallet, - cid, - file_path, - shell=self.shell, - cluster=self.cluster, - attributes=allow_attribute, - ) - assert can_put_object(other_wallet, cid, file_path, shell=self.shell, cluster=self.cluster) - - deny_attribute = self.ATTRIBUTE if match_type == EACLMatchType.STRING_EQUAL else self.OTHER_ATTRIBUTE - with reporter.step("Check other can not PUT objects with denied attribute"): - with pytest.raises(AssertionError): - assert can_put_object( - other_wallet, - cid, - file_path, - shell=self.shell, - cluster=self.cluster, - attributes=deny_attribute, - ) - - with reporter.step("Check other can PUT objects with denied attribute and using bearer token"): - bearer_other_for_put = form_bearertoken_file( - user_wallet, - cid, - [ - EACLRule( - operation=EACLOperation.PUT, - access=EACLAccess.ALLOW, - role=EACLRole.OTHERS, - ) - ], - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - assert can_put_object( - other_wallet, - cid, - file_path, - shell=self.shell, - cluster=self.cluster, - attributes=deny_attribute, - bearer=bearer_other_for_put, - ) - - @allure.title("Operations with allow eACL user headers filters (match_type={match_type}, obj_size={object_size})") - @pytest.mark.parametrize("match_type", [EACLMatchType.STRING_EQUAL, EACLMatchType.STRING_NOT_EQUAL]) - @pytest.mark.parametrize("object_size", ["simple"], indirect=True) - def test_extended_acl_allow_filters_object( - self, - wallets: Wallets, - eacl_container_with_objects: tuple[str, list[str], str], - match_type: EACLMatchType, - ): - user_wallet = wallets.get_wallet() - other_wallet = wallets.get_wallet(EACLRole.OTHERS) - ( - cid, - objects_with_header, - objects_with_other_header, - objects_without_header, - file_path, - ) = eacl_container_with_objects - - with reporter.step("Deny all operations for others except few operations allowed by object filter"): - equal_filter = EACLFilter(**self.OBJ_EQUAL_FILTER.__dict__) - equal_filter.match_type = match_type - eacl = [ - EACLRule( - access=EACLAccess.ALLOW, - role=EACLRole.OTHERS, - filters=EACLFilters([equal_filter]), - operation=op, - ) - for op in self.OBJECT_ATTRIBUTES_FILTER_SUPPORTED_OPERATIONS - ] + [ - EACLRule(access=EACLAccess.DENY, role=EACLRole.OTHERS, operation=op) - for op in self.OBJECT_ATTRIBUTES_FILTER_SUPPORTED_OPERATIONS - ] - set_eacl( - user_wallet, - cid, - create_eacl(cid, eacl, shell=self.shell), - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - wait_for_cache_expired() - - if match_type == EACLMatchType.STRING_EQUAL: - allow_objects = objects_with_header - deny_objects = objects_with_other_header - allow_attribute = self.ATTRIBUTE - deny_attribute = self.OTHER_ATTRIBUTE - else: - allow_objects = objects_with_other_header - deny_objects = objects_with_header - allow_attribute = self.OTHER_ATTRIBUTE - deny_attribute = self.ATTRIBUTE - - with reporter.step("Check other cannot get and put objects without attributes"): - oid = objects_without_header.pop() - with pytest.raises(AssertionError): - assert can_get_head_object( - other_wallet, - cid, - oid, - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - with pytest.raises(AssertionError): - assert can_get_object( - other_wallet, - cid, - oid, - file_path, - shell=self.shell, - cluster=self.cluster, - ) - with pytest.raises(AssertionError): - assert can_put_object(other_wallet, cid, file_path, shell=self.shell, cluster=self.cluster) - - with reporter.step("Check other can get and put objects without attributes and using bearer token"): - bearer_other = form_bearertoken_file( - user_wallet, - cid, - [ - EACLRule( - operation=op, - access=EACLAccess.ALLOW, - role=EACLRole.OTHERS, - ) - for op in EACLOperation - ], - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - assert can_get_head_object( - other_wallet, - cid, - objects_without_header[0], - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - bearer=bearer_other, - ) - assert can_get_object( - other_wallet, - cid, - objects_without_header[0], - file_path, - shell=self.shell, - cluster=self.cluster, - bearer=bearer_other, - ) - assert can_put_object( - other_wallet, - cid, - file_path, - shell=self.shell, - cluster=self.cluster, - bearer=bearer_other, - ) - - with reporter.step("Check other can get objects with attributes matching the filter"): - oid = allow_objects.pop() - assert can_get_head_object( - other_wallet, - cid, - oid, - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - assert can_get_object( - other_wallet, - cid, - oid, - file_path, - shell=self.shell, - cluster=self.cluster, - ) - assert can_put_object( - other_wallet, - cid, - file_path, - shell=self.shell, - cluster=self.cluster, - attributes=allow_attribute, - ) - - with reporter.step("Check other cannot get objects without attributes matching the filter"): - with pytest.raises(AssertionError): - assert can_get_head_object( - other_wallet, - cid, - deny_objects[0], - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - with pytest.raises(AssertionError): - assert can_get_object( - other_wallet, - cid, - deny_objects[0], - file_path, - shell=self.shell, - cluster=self.cluster, - ) - with pytest.raises(AssertionError): - assert can_put_object( - other_wallet, - cid, - file_path, - attributes=deny_attribute, - shell=self.shell, - cluster=self.cluster, - ) - - with reporter.step( - "Check other can get objects without attributes matching the filter " "and using bearer token" - ): - oid = deny_objects.pop() - assert can_get_head_object( - other_wallet, - cid, - oid, - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - bearer=bearer_other, - ) - assert can_get_object( - other_wallet, - cid, - oid, - file_path, - shell=self.shell, - cluster=self.cluster, - bearer=bearer_other, - ) - assert can_put_object( - other_wallet, - cid, - file_path, - shell=self.shell, - cluster=self.cluster, - attributes=deny_attribute, - bearer=bearer_other, - ) diff --git a/pytest_tests/testsuites/conftest.py b/pytest_tests/testsuites/conftest.py index b10bcf53..16f3c29f 100644 --- a/pytest_tests/testsuites/conftest.py +++ b/pytest_tests/testsuites/conftest.py @@ -11,6 +11,7 @@ import pytest import yaml from dateutil import parser from frostfs_testlib import plugins, reporter +from frostfs_testlib.cli import FrostfsCli from frostfs_testlib.credentials.interfaces import CredentialsProvider, User from frostfs_testlib.healthcheck.interfaces import Healthcheck from frostfs_testlib.hosting import Hosting @@ -18,7 +19,7 @@ from frostfs_testlib.reporter import AllureHandler, StepsLogger from frostfs_testlib.resources.common import ASSETS_DIR, COMPLEX_OBJECT_CHUNKS_COUNT, COMPLEX_OBJECT_TAIL_SIZE, SIMPLE_OBJECT_SIZE from frostfs_testlib.s3 import AwsCliClient, Boto3ClientWrapper, S3ClientWrapper, VersioningStatus from frostfs_testlib.shell import LocalShell, Shell -from frostfs_testlib.steps.cli.container import DEFAULT_EC_PLACEMENT_RULE, DEFAULT_PLACEMENT_RULE +from frostfs_testlib.steps.cli.container import DEFAULT_EC_PLACEMENT_RULE, DEFAULT_PLACEMENT_RULE, FROSTFS_CLI_EXEC from frostfs_testlib.steps.cli.object import get_netmap_netinfo from frostfs_testlib.steps.s3 import s3_helper from frostfs_testlib.storage import get_service_registry @@ -31,14 +32,15 @@ from frostfs_testlib.storage.dataclasses.wallet import WalletInfo from frostfs_testlib.testing.cluster_test_base import ClusterTestBase from frostfs_testlib.testing.parallel import parallel from frostfs_testlib.testing.test_control import wait_for_success -from frostfs_testlib.utils import env_utils, version_utils -from frostfs_testlib.utils.file_utils import generate_file +from frostfs_testlib.utils import env_utils, string_utils, version_utils +from frostfs_testlib.utils.file_utils import TestFile, generate_file from pytest_tests.resources.common import HOSTING_CONFIG_FILE, TEST_CYCLES_COUNT logger = logging.getLogger("NeoLogger") SERVICE_ACTIVE_TIME = 20 +WALLTETS_IN_POOL = 2 # Add logs check test even if it's not fit to mark selectors def pytest_configure(config: pytest.Config): @@ -189,7 +191,7 @@ def simple_object_size(max_object_size: int) -> ObjectSize: @pytest.fixture() -def file_path(object_size: ObjectSize) -> str: +def file_path(object_size: ObjectSize) -> TestFile: return generate_file(object_size.value) @@ -222,6 +224,12 @@ def ec_placement_policy() -> PlacementPolicy: return PlacementPolicy("ec", DEFAULT_EC_PLACEMENT_RULE) +@pytest.fixture(scope="session") +@allure.title("Init Frostfs CLI") +def frostfs_cli(client_shell: Shell, default_wallet: WalletInfo) -> FrostfsCli: + return FrostfsCli(client_shell, FROSTFS_CLI_EXEC, default_wallet.config_path) + + # By default we want all tests to be executed with both storage policies. # This can be overriden in choosen tests if needed. @pytest.fixture( @@ -316,10 +324,6 @@ def versioning_status(request: pytest.FixtureRequest) -> VersioningStatus: return VersioningStatus.UNDEFINED -def unique_name(prefix: str) -> str: - return f"{prefix}{hex(int(datetime.now().timestamp() * 1000000))}" - - @allure.title("[Session] Bulk create buckets for tests") @pytest.fixture(scope="session") def buckets_pool(s3_client: S3ClientWrapper, request: pytest.FixtureRequest): @@ -332,11 +336,11 @@ def buckets_pool(s3_client: S3ClientWrapper, request: pytest.FixtureRequest): continue if "bucket" in test.fixturenames: - test_buckets.append(unique_name("bucket-")) + test_buckets.append(string_utils.unique_name("bucket-")) if "two_buckets" in test.fixturenames: - test_buckets.append(unique_name("bucket-")) - test_buckets.append(unique_name("bucket-")) + test_buckets.append(string_utils.unique_name("bucket-")) + test_buckets.append(string_utils.unique_name("bucket-")) if test_buckets: parallel(s3_client.create_bucket, test_buckets) @@ -452,8 +456,7 @@ def readiness_on_node(cluster_node: ClusterNode): @reporter.step("Prepare default user with wallet") @pytest.fixture(scope="session") def default_user(credentials_provider: CredentialsProvider, cluster: Cluster) -> User: - # always unique username - user = User(f"user_{hex(int(datetime.now().timestamp() * 1000000))}") + user = User(string_utils.unique_name("user-")) node = cluster.cluster_nodes[0] credentials_provider.GRPC.provide(user, node) @@ -467,6 +470,28 @@ def default_wallet(default_user: User) -> WalletInfo: return default_user.wallet +@pytest.fixture(scope="session") +def wallets_pool(credentials_provider: CredentialsProvider, cluster: Cluster) -> list[WalletInfo]: + users = [User(string_utils.unique_name("user-")) for _ in range(WALLTETS_IN_POOL)] + parallel(credentials_provider.GRPC.provide, users, cluster_node=cluster.cluster_nodes[0]) + + return [user.wallet for user in users] + + +@pytest.fixture(scope="session") +def other_wallet(wallets_pool: list[WalletInfo]) -> WalletInfo: + if not wallets_pool: + raise RuntimeError("[other_wallet] No wallets in pool. Consider increasing WALLTETS_IN_POOL or review.") + return wallets_pool.pop() + + +@pytest.fixture(scope="session") +def other_wallet_2(wallets_pool: list[WalletInfo]) -> WalletInfo: + if not wallets_pool: + raise RuntimeError("[other_wallet2] No wallets in pool. Consider increasing WALLTETS_IN_POOL or review.") + return wallets_pool.pop() + + @pytest.fixture() @allure.title("Select random node for testing") def node_under_test(cluster: Cluster) -> ClusterNode: diff --git a/pytest_tests/testsuites/management/test_node_management.py b/pytest_tests/testsuites/management/test_node_management.py index 2837d66b..a6854862 100644 --- a/pytest_tests/testsuites/management/test_node_management.py +++ b/pytest_tests/testsuites/management/test_node_management.py @@ -9,7 +9,6 @@ from frostfs_testlib import reporter from frostfs_testlib.cli import FrostfsCli from frostfs_testlib.cli.netmap_parser import NetmapParser from frostfs_testlib.resources.cli import FROSTFS_CLI_EXEC -from frostfs_testlib.resources.common import MORPH_BLOCK_TIME from frostfs_testlib.resources.error_patterns import OBJECT_NOT_FOUND from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL from frostfs_testlib.steps.cli.container import create_container, search_nodes_with_container @@ -41,7 +40,7 @@ from frostfs_testlib.storage.dataclasses.object_size import ObjectSize from frostfs_testlib.storage.dataclasses.storage_object_info import NodeStatus from frostfs_testlib.storage.dataclasses.wallet import WalletInfo from frostfs_testlib.testing.cluster_test_base import ClusterTestBase -from frostfs_testlib.utils import datetime_utils, string_utils +from frostfs_testlib.utils import string_utils from frostfs_testlib.utils.failover_utils import wait_object_replication from frostfs_testlib.utils.file_utils import generate_file @@ -57,9 +56,7 @@ check_nodes: list[StorageNode] = [] class TestNodeManagement(ClusterTestBase): @pytest.fixture @allure.title("Create container and pick the node with data") - def create_container_and_pick_node( - self, default_wallet: WalletInfo, simple_object_size: ObjectSize - ) -> Tuple[str, StorageNode]: + def create_container_and_pick_node(self, default_wallet: WalletInfo, simple_object_size: ObjectSize) -> Tuple[str, StorageNode]: file_path = generate_file(simple_object_size.value) placement_rule = "REP 1 IN X CBF 1 SELECT 1 FROM * AS X" endpoint = self.cluster.default_rpc_endpoint @@ -114,13 +111,13 @@ class TestNodeManagement(ClusterTestBase): # We need to wait for node to establish notifications from morph-chain # Otherwise it will hang up when we will try to set status - sleep(datetime_utils.parse_time(MORPH_BLOCK_TIME)) + self.wait_for_blocks() with reporter.step(f"Move node {node} to online state"): storage_node_set_status(node, status="online", retries=2) check_nodes.remove(node) - sleep(datetime_utils.parse_time(MORPH_BLOCK_TIME)) + self.wait_for_blocks() self.tick_epoch_with_retries(3, wait_block=2) check_node_in_map(node, shell=self.shell, alive_node=alive_node) @@ -141,9 +138,7 @@ class TestNodeManagement(ClusterTestBase): storage_nodes = self.cluster.storage_nodes random_node = random.choice(storage_nodes[1:]) - alive_node = random.choice( - [storage_node for storage_node in storage_nodes if storage_node.id != random_node.id] - ) + alive_node = random.choice([storage_node for storage_node in storage_nodes if storage_node.id != random_node.id]) check_node_in_map(random_node, shell=self.shell, alive_node=alive_node) @@ -283,9 +278,7 @@ class TestNodeManagement(ClusterTestBase): source_file_path = generate_file(simple_object_size.value) storage_nodes = self.cluster.storage_nodes random_node = random.choice(storage_nodes[1:]) - alive_node = random.choice( - [storage_node for storage_node in storage_nodes if storage_node.id != random_node.id] - ) + alive_node = random.choice([storage_node for storage_node in storage_nodes if storage_node.id != random_node.id]) cid = create_container( wallet, @@ -340,14 +333,6 @@ class TestMaintenanceMode(ClusterTestBase): cli = FrostfsCli(shell=shell, frostfs_cli_exec_path=FROSTFS_CLI_EXEC, config_file=wallet_config_path) return cli - @pytest.fixture() - @allure.title("Init Frostfs CLI remote") - def frostfs_cli(self, default_wallet: WalletInfo) -> FrostfsCli: - cli = FrostfsCli( - shell=self.shell, frostfs_cli_exec_path=FROSTFS_CLI_EXEC, config_file=default_wallet.config_path - ) - return cli - @pytest.fixture() def restore_node_status(self, cluster_state_controller: ClusterStateController, default_wallet: WalletInfo): nodes_to_restore = [] @@ -357,21 +342,15 @@ class TestMaintenanceMode(ClusterTestBase): for node_to_restore in nodes_to_restore: cluster_state_controller.set_node_status(node_to_restore, default_wallet, NodeStatus.ONLINE) - def check_node_status( - self, expected_status: NodeStatus, node_under_test: ClusterNode, frostfs_cli: FrostfsCli, rpc_endpoint: str - ): + def check_node_status(self, expected_status: NodeStatus, node_under_test: ClusterNode, frostfs_cli: FrostfsCli, rpc_endpoint: str): netmap = frostfs_cli.netmap.snapshot(rpc_endpoint).stdout all_snapshots = NetmapParser.snapshot_all_nodes(netmap) node_snapshot = [snapshot for snapshot in all_snapshots if node_under_test.host_ip == snapshot.node] if expected_status == NodeStatus.OFFLINE and not node_snapshot: - assert ( - node_under_test.host_ip not in netmap - ), f"{node_under_test} status should be {expected_status}. See netmap:\n{netmap}" + assert node_under_test.host_ip not in netmap, f"{node_under_test} status should be {expected_status}. See netmap:\n{netmap}" return - assert ( - node_snapshot - ), f"{node_under_test} status should be {expected_status}, but was not in netmap. See netmap:\n{netmap}" + assert node_snapshot, f"{node_under_test} status should be {expected_status}, but was not in netmap. See netmap:\n{netmap}" node_snapshot = node_snapshot[0] assert ( expected_status == node_snapshot.node_status diff --git a/pytest_tests/testsuites/object/test_object_api_bearer.py b/pytest_tests/testsuites/object/test_object_api_bearer.py index c6e09cd8..fe0bb85b 100644 --- a/pytest_tests/testsuites/object/test_object_api_bearer.py +++ b/pytest_tests/testsuites/object/test_object_api_bearer.py @@ -1,9 +1,9 @@ import allure import pytest from frostfs_testlib import reporter -from frostfs_testlib.resources.wellknown_acl import EACL_PUBLIC_READ_WRITE +from frostfs_testlib.cli import FrostfsCli +from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL from frostfs_testlib.shell import Shell -from frostfs_testlib.steps.acl import form_bearertoken_file from frostfs_testlib.steps.cli.container import ( REP_2_FOR_3_NODES_PLACEMENT_RULE, SINGLE_PLACEMENT_RULE, @@ -12,68 +12,50 @@ from frostfs_testlib.steps.cli.container import ( create_container, ) from frostfs_testlib.steps.cli.object import delete_object, get_object -from frostfs_testlib.steps.epoch import get_epoch from frostfs_testlib.steps.storage_object import StorageObjectInfo from frostfs_testlib.storage.cluster import Cluster -from frostfs_testlib.storage.dataclasses.acl import EACLAccess, EACLOperation, EACLRole, EACLRule +from frostfs_testlib.storage.dataclasses import ape from frostfs_testlib.storage.dataclasses.object_size import ObjectSize from frostfs_testlib.storage.dataclasses.wallet import WalletInfo from frostfs_testlib.testing.cluster_test_base import ClusterTestBase from frostfs_testlib.testing.test_control import expect_not_raises from pytest import FixtureRequest - -@pytest.fixture(scope="module") -@allure.title("Create bearer token for OTHERS with all operations allowed for all containers") -def bearer_token_file_all_allow(default_wallet: WalletInfo, client_shell: Shell, cluster: Cluster) -> str: - bearer = form_bearertoken_file( - default_wallet, - "", - [EACLRule(operation=op, access=EACLAccess.ALLOW, role=EACLRole.OTHERS) for op in EACLOperation], - shell=client_shell, - endpoint=cluster.default_rpc_endpoint, - ) - - return bearer +from pytest_tests.helpers.bearer_token import create_bearer_token +from pytest_tests.helpers.container_access import assert_full_access_to_container -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") @allure.title("Create user container for bearer token usage") -def user_container( - default_wallet: WalletInfo, client_shell: Shell, cluster: Cluster, request: FixtureRequest -) -> StorageContainer: - container_id = create_container( - default_wallet, - shell=client_shell, - rule=request.param, - basic_acl=EACL_PUBLIC_READ_WRITE, - endpoint=cluster.default_rpc_endpoint, - ) +def user_container(default_wallet: WalletInfo, client_shell: Shell, cluster: Cluster, request: FixtureRequest) -> StorageContainer: + rule = request.param if "param" in request.__dict__ else SINGLE_PLACEMENT_RULE + container_id = create_container(default_wallet, client_shell, cluster.default_rpc_endpoint, rule, PUBLIC_ACL) + # Deliberately using s3gate wallet here to test bearer token - s3gate = cluster.s3_gates[0] - return StorageContainer( - StorageContainerInfo(container_id, WalletInfo.from_node(s3gate)), - client_shell, - cluster, - ) + s3_gate_wallet = WalletInfo.from_node(cluster.s3_gates[0]) + return StorageContainer(StorageContainerInfo(container_id, s3_gate_wallet), client_shell, cluster) + + +@pytest.fixture(scope="session") +@allure.title("Create bearer token with allowed put for container") +def bearer_token(frostfs_cli: FrostfsCli, temp_directory: str, user_container: StorageContainer, cluster: Cluster) -> str: + rule = ape.Rule(ape.Verb.ALLOW, ape.ObjectOperations.WILDCARD_ALL) + return create_bearer_token(frostfs_cli, temp_directory, user_container.get_id(), rule, cluster.default_rpc_endpoint) @pytest.fixture() def storage_objects( user_container: StorageContainer, - bearer_token_file_all_allow: str, + bearer_token: str, object_size: ObjectSize, - client_shell: Shell, cluster: Cluster, ) -> list[StorageObjectInfo]: - epoch = get_epoch(client_shell, cluster) storage_objects: list[StorageObjectInfo] = [] for node in cluster.storage_nodes: storage_objects.append( user_container.generate_object( object_size.value, - epoch + 3, - bearer_token=bearer_token_file_all_allow, + bearer_token=bearer_token, endpoint=node.get_rpc_endpoint(), ) ) @@ -82,6 +64,7 @@ def storage_objects( @pytest.mark.smoke @pytest.mark.bearer +@pytest.mark.ape class TestObjectApiWithBearerToken(ClusterTestBase): @allure.title("Object can be deleted from any node using s3gate wallet with bearer token (obj_size={object_size})") @pytest.mark.parametrize( @@ -92,10 +75,10 @@ class TestObjectApiWithBearerToken(ClusterTestBase): def test_delete_object_with_s3_wallet_bearer( self, storage_objects: list[StorageObjectInfo], - bearer_token_file_all_allow: str, + bearer_token: str, ): s3_gate_wallet = WalletInfo.from_node(self.cluster.s3_gates[0]) - with reporter.step("Try to delete each object from first storage node"): + with reporter.step("Delete each object from first storage node"): for storage_object in storage_objects: with expect_not_raises(): delete_object( @@ -104,7 +87,7 @@ class TestObjectApiWithBearerToken(ClusterTestBase): storage_object.oid, self.shell, endpoint=self.cluster.default_rpc_endpoint, - bearer=bearer_token_file_all_allow, + bearer=bearer_token, ) @allure.title("Object can be fetched from any node using s3gate wallet with bearer token (obj_size={object_size})") @@ -117,16 +100,17 @@ class TestObjectApiWithBearerToken(ClusterTestBase): self, user_container: StorageContainer, object_size: ObjectSize, - bearer_token_file_all_allow: str, + bearer_token: str, ): s3_gate_wallet = WalletInfo.from_node(self.cluster.s3_gates[0]) - with reporter.step("Put one object to container"): - epoch = self.get_epoch() + with reporter.step("Put object to container"): storage_object = user_container.generate_object( - object_size.value, epoch + 3, bearer_token=bearer_token_file_all_allow + object_size.value, + bearer_token=bearer_token, + endpoint=self.cluster.default_rpc_endpoint, ) - with reporter.step("Try to fetch object from each storage node"): + with reporter.step("Get object from each storage node"): for node in self.cluster.storage_nodes: with expect_not_raises(): get_object( @@ -134,6 +118,17 @@ class TestObjectApiWithBearerToken(ClusterTestBase): storage_object.cid, storage_object.oid, self.shell, - endpoint=node.get_rpc_endpoint(), - bearer=bearer_token_file_all_allow, + node.get_rpc_endpoint(), + bearer_token, ) + + @allure.title("Wildcard APE rule contains all permissions (obj_size={object_size})") + def test_ape_wildcard_contains_all_rules( + self, + other_wallet: WalletInfo, + storage_objects: list[StorageObjectInfo], + bearer_token: str, + ): + obj = storage_objects.pop() + with reporter.step(f"Assert all operations available with object"): + assert_full_access_to_container(other_wallet, obj.cid, obj.oid, obj.file_path, self.shell, self.cluster, bearer_token) diff --git a/pytest_tests/testsuites/services/http_gate/test_http_bearer.py b/pytest_tests/testsuites/services/http_gate/test_http_bearer.py index 1a22c1ac..53b2454d 100644 --- a/pytest_tests/testsuites/services/http_gate/test_http_bearer.py +++ b/pytest_tests/testsuites/services/http_gate/test_http_bearer.py @@ -3,22 +3,20 @@ import logging import allure import pytest from frostfs_testlib import reporter +from frostfs_testlib.cli.frostfs_cli.cli import FrostfsCli from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL -from frostfs_testlib.steps.acl import ( - bearer_token_base64_from_file, - create_eacl, - form_bearertoken_file, - set_eacl, - sign_bearer, - wait_for_cache_expired, -) +from frostfs_testlib.steps.acl import bearer_token_base64_from_file from frostfs_testlib.steps.cli.container import create_container from frostfs_testlib.steps.http.http_gate import upload_via_http_gate_curl, verify_object_hash -from frostfs_testlib.storage.dataclasses.acl import EACLAccess, EACLOperation, EACLRole, EACLRule +from frostfs_testlib.storage.cluster import Cluster +from frostfs_testlib.storage.dataclasses import ape from frostfs_testlib.storage.dataclasses.object_size import ObjectSize +from frostfs_testlib.storage.dataclasses.wallet import WalletInfo from frostfs_testlib.testing.cluster_test_base import ClusterTestBase from frostfs_testlib.utils.file_utils import generate_file +from pytest_tests.helpers.bearer_token import create_bearer_token + logger = logging.getLogger("NeoLogger") @@ -27,60 +25,34 @@ logger = logging.getLogger("NeoLogger") class Test_http_bearer(ClusterTestBase): PLACEMENT_RULE = "REP 2 IN X CBF 1 SELECT 2 FROM * AS X" - @pytest.fixture(scope="class", autouse=True) - @allure.title("[Class/Autouse]: Prepare wallet and deposit") - def prepare_wallet(self, default_wallet): - Test_http_bearer.wallet = default_wallet + @pytest.fixture(scope="class") + def user_container(self, frostfs_cli: FrostfsCli, default_wallet: WalletInfo, cluster: Cluster) -> str: + with reporter.step("Create container"): + cid = create_container(default_wallet, self.shell, self.cluster.default_rpc_endpoint, self.PLACEMENT_RULE, PUBLIC_ACL) + + with reporter.step("Deny PUT via APE rule to container"): + role_condition = ape.Condition.by_role(ape.Role.OWNER) + rule = ape.Rule(ape.Verb.DENY, ape.ObjectOperations.PUT, role_condition) + frostfs_cli.ape_manager.add( + cluster.default_rpc_endpoint, rule.chain_id, target_name=cid, target_type="container", rule=rule.as_string() + ) + + with reporter.step("Wait for one block"): + self.wait_for_blocks() + + return cid @pytest.fixture(scope="class") - def user_container(self) -> str: - return create_container( - wallet=self.wallet, - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - rule=self.PLACEMENT_RULE, - basic_acl=PUBLIC_ACL, - ) + def bearer_token(self, frostfs_cli: FrostfsCli, user_container: str, temp_directory: str, cluster: Cluster) -> str: + with reporter.step(f"Create bearer token for {ape.Role.OTHERS} with all operations allowed"): + role_condition = ape.Condition.by_role(ape.Role.OTHERS) + rule = ape.Rule(ape.Verb.ALLOW, ape.ObjectOperations.WILDCARD_ALL, role_condition) + bearer = create_bearer_token(frostfs_cli, temp_directory, user_container, rule, cluster.default_rpc_endpoint) - @pytest.fixture(scope="class") - def eacl_deny_for_others(self, user_container: str) -> None: - with reporter.step(f"Set deny all operations for {EACLRole.OTHERS} via eACL"): - eacl = EACLRule(access=EACLAccess.DENY, role=EACLRole.OTHERS, operation=EACLOperation.PUT) - set_eacl( - self.wallet, - user_container, - create_eacl(user_container, eacl, shell=self.shell), - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - wait_for_cache_expired() + return bearer_token_base64_from_file(bearer) - @pytest.fixture(scope="class") - def bearer_token_no_limit_for_others(self, user_container: str) -> str: - with reporter.step(f"Create bearer token for {EACLRole.OTHERS} with all operations allowed"): - bearer = form_bearertoken_file( - self.wallet, - user_container, - [EACLRule(operation=op, access=EACLAccess.ALLOW, role=EACLRole.OTHERS) for op in EACLOperation], - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - sign=False, - ) - bearer_signed = f"{bearer}_signed" - sign_bearer( - shell=self.shell, - wallet=self.wallet, - eacl_rules_file_from=bearer, - eacl_rules_file_to=bearer_signed, - json=False, - ) - return bearer_token_base64_from_file(bearer_signed) - - @allure.title(f"[NEGATIVE] Put object without bearer token for {EACLRole.OTHERS}") - def test_unable_put_without_bearer_token( - self, simple_object_size: ObjectSize, user_container: str, eacl_deny_for_others - ): - eacl_deny_for_others + @allure.title(f"[NEGATIVE] Put object without bearer token for {ape.Role.OTHERS}") + def test_unable_put_without_bearer_token(self, simple_object_size: ObjectSize, user_container: str): upload_via_http_gate_curl( cid=user_container, filepath=generate_file(simple_object_size.value), @@ -91,15 +63,13 @@ class Test_http_bearer(ClusterTestBase): def test_put_with_bearer_when_eacl_restrict( self, object_size: ObjectSize, + default_wallet: WalletInfo, user_container: str, - eacl_deny_for_others, - bearer_token_no_limit_for_others: str, + bearer_token: str, ): - eacl_deny_for_others - bearer = bearer_token_no_limit_for_others file_path = generate_file(object_size.value) - with reporter.step(f"Put object with bearer token for {EACLRole.OTHERS}, then get and verify hashes"): - headers = [f" -H 'Authorization: Bearer {bearer}'"] + with reporter.step(f"Put object with bearer token for {ape.Role.OTHERS}, then get and verify hashes"): + headers = [f" -H 'Authorization: Bearer {bearer_token}'"] oid = upload_via_http_gate_curl( cid=user_container, filepath=file_path, @@ -109,7 +79,7 @@ class Test_http_bearer(ClusterTestBase): verify_object_hash( oid=oid, file_name=file_path, - wallet=self.wallet, + wallet=default_wallet, cid=user_container, shell=self.shell, nodes=self.cluster.storage_nodes, diff --git a/pytest_tests/testsuites/session_token/conftest.py b/pytest_tests/testsuites/session_token/conftest.py index faf173d0..713442dc 100644 --- a/pytest_tests/testsuites/session_token/conftest.py +++ b/pytest_tests/testsuites/session_token/conftest.py @@ -8,10 +8,8 @@ from frostfs_testlib.storage.dataclasses.wallet import WalletInfo @pytest.fixture(scope="module") -def owner_wallet(credentials_provider: CredentialsProvider, cluster: Cluster) -> WalletInfo: - with reporter.step("Create user wallet which owns containers and objects"): - user = User(f"user_{hex(int(datetime.now().timestamp() * 1000000))}") - return credentials_provider.GRPC.provide(user, cluster.cluster_nodes[0]) +def owner_wallet(default_wallet: WalletInfo) -> WalletInfo: + return default_wallet @pytest.fixture(scope="module") diff --git a/pytest_tests/testsuites/session_token/test_static_session_token_container.py b/pytest_tests/testsuites/session_token/test_static_session_token_container.py index 266a9379..c86f3dd5 100644 --- a/pytest_tests/testsuites/session_token/test_static_session_token_container.py +++ b/pytest_tests/testsuites/session_token/test_static_session_token_container.py @@ -1,17 +1,10 @@ import pytest from frostfs_testlib import reporter -from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL from frostfs_testlib.shell import Shell -from frostfs_testlib.steps.acl import create_eacl, set_eacl, wait_for_cache_expired from frostfs_testlib.steps.cli.container import create_container, delete_container, get_container, list_containers from frostfs_testlib.steps.session_token import ContainerVerb, get_container_signed_token -from frostfs_testlib.storage.dataclasses.acl import EACLAccess, EACLOperation, EACLRole, EACLRule -from frostfs_testlib.storage.dataclasses.object_size import ObjectSize from frostfs_testlib.storage.dataclasses.wallet import WalletInfo from frostfs_testlib.testing.cluster_test_base import ClusterTestBase -from frostfs_testlib.utils.file_utils import generate_file - -from pytest_tests.helpers.object_access import can_put_object @pytest.mark.static_session_container @@ -27,10 +20,7 @@ class TestSessionTokenContainer(ClusterTestBase): """ Returns dict with static session token file paths for all verbs with default lifetime """ - return { - verb: get_container_signed_token(owner_wallet, user_wallet, verb, client_shell, temp_directory) - for verb in ContainerVerb - } + return {verb: get_container_signed_token(owner_wallet, user_wallet, verb, client_shell, temp_directory) for verb in ContainerVerb} def test_static_session_token_container_create( self, @@ -50,9 +40,7 @@ class TestSessionTokenContainer(ClusterTestBase): wait_for_creation=False, ) - container_info: dict[str, str] = get_container( - owner_wallet, cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint - ) + container_info: dict[str, str] = get_container(owner_wallet, cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint) assert container_info["ownerID"] == owner_wallet.get_address() assert cid not in list_containers(user_wallet, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint) @@ -122,39 +110,3 @@ class TestSessionTokenContainer(ClusterTestBase): ) assert cid not in list_containers(owner_wallet, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint) - - @pytest.mark.sanity - def test_static_session_token_container_set_eacl( - self, - owner_wallet: WalletInfo, - user_wallet: WalletInfo, - stranger_wallet: WalletInfo, - static_sessions: dict[ContainerVerb, str], - simple_object_size: ObjectSize, - ): - """ - Validate static session with set eacl operation - """ - with reporter.step("Create container"): - cid = create_container( - owner_wallet, - basic_acl=PUBLIC_ACL, - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - ) - file_path = generate_file(simple_object_size.value) - assert can_put_object(stranger_wallet, cid, file_path, self.shell, self.cluster) - - with reporter.step("Deny all operations for other via eACL"): - eacl_deny = [EACLRule(access=EACLAccess.DENY, role=EACLRole.OTHERS, operation=op) for op in EACLOperation] - set_eacl( - user_wallet, - cid, - create_eacl(cid, eacl_deny, shell=self.shell), - shell=self.shell, - endpoint=self.cluster.default_rpc_endpoint, - session_token=static_sessions[ContainerVerb.SETEACL], - ) - wait_for_cache_expired() - - assert not can_put_object(stranger_wallet, cid, file_path, self.shell, self.cluster)