forked from TrueCloudLab/frostfs-testcases
[#271] Migrate eACL tests to APE
Signed-off-by: a.berezin <a.berezin@yadro.com>
This commit is contained in:
parent
6e4c3c33a5
commit
fe17f2236b
20 changed files with 1133 additions and 1946 deletions
17
pytest_tests/helpers/bearer_token.py
Normal file
17
pytest_tests/helpers/bearer_token.py
Normal file
|
@ -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
|
|
@ -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)
|
||||
|
|
0
pytest_tests/testsuites/access/__init__.py
Normal file
0
pytest_tests/testsuites/access/__init__.py
Normal file
123
pytest_tests/testsuites/access/acl/test_acl.py
Normal file
123
pytest_tests/testsuites/access/acl/test_acl.py
Normal file
|
@ -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)
|
0
pytest_tests/testsuites/access/ape/__init__.py
Normal file
0
pytest_tests/testsuites/access/ape/__init__.py
Normal file
226
pytest_tests/testsuites/access/ape/test_ape.py
Normal file
226
pytest_tests/testsuites/access/ape/test_ape.py
Normal file
|
@ -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)
|
358
pytest_tests/testsuites/access/ape/test_ape_filters.py
Normal file
358
pytest_tests/testsuites/access/ape/test_ape_filters.py
Normal file
|
@ -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 "<some_value>"
|
||||
# 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)
|
190
pytest_tests/testsuites/access/ape/test_bearer.py
Normal file
190
pytest_tests/testsuites/access/ape/test_bearer.py
Normal file
|
@ -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]
|
||||
)
|
64
pytest_tests/testsuites/access/conftest.py
Normal file
64
pytest_tests/testsuites/access/conftest.py
Normal file
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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,
|
||||
)
|
|
@ -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,
|
||||
)
|
|
@ -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,
|
||||
)
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue