[#271] Migrate eACL tests to APE
Some checks reported warnings
DCO check / Commits Check (pull_request) Has been cancelled

Signed-off-by: a.berezin <a.berezin@yadro.com>
This commit is contained in:
Andrey Berezin 2024-07-17 23:56:05 +03:00
parent 6e4c3c33a5
commit fe17f2236b
20 changed files with 1133 additions and 1946 deletions

View 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

View 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.shell import Shell
from frostfs_testlib.storage.cluster import Cluster 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 frostfs_testlib.storage.dataclasses.wallet import WalletInfo
from pytest_tests.helpers.object_access import ( from pytest_tests.helpers.object_access import (
@ -15,8 +16,15 @@ from pytest_tests.helpers.object_access import (
can_search_object, 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, wallet: WalletInfo,
cid: str, cid: str,
oid: str, oid: str,
@ -27,95 +35,23 @@ def check_full_access_to_container(
xhdr: Optional[dict] = None, xhdr: Optional[dict] = None,
): ):
endpoint = cluster.default_rpc_endpoint endpoint = cluster.default_rpc_endpoint
assert can_put_object(wallet, cid, file_name, shell, cluster, bearer, xhdr) results: dict = {}
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[ape.ObjectOperations.PUT] = can_put_object(wallet, cid, file_name, shell, cluster, bearer, xhdr)
def check_no_access_to_container( results[ape.ObjectOperations.HEAD] = can_get_head_object(wallet, cid, oid, shell, endpoint, bearer, xhdr)
wallet: WalletInfo, results[ape.ObjectOperations.GET_RANGE] = can_get_range_of_object(wallet, cid, oid, shell, endpoint, bearer, xhdr)
cid: str, results[ape.ObjectOperations.GET_RANGE_HASH] = can_get_range_hash_of_object(wallet, cid, oid, shell, endpoint, bearer, xhdr)
oid: str, results[ape.ObjectOperations.SEARCH] = can_search_object(wallet, cid, shell, endpoint, oid, bearer, xhdr)
file_name: str, results[ape.ObjectOperations.GET] = can_get_object(wallet, cid, oid, file_name, shell, cluster, bearer, xhdr)
shell: Shell, results[ape.ObjectOperations.DELETE] = can_delete_object(wallet, cid, oid, shell, endpoint, bearer, xhdr)
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)
failed_checks = [ failed_checks = [
f"allowed {action} failed" f"allowed {action} failed" for action, success in results.items() if not success and access_matrix[action] != results[action]
for action, success in checks.items() ] + [f"denied {action} succeeded" for action, success in results.items() if success and access_matrix[action] != results[action]]
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]
assert not failed_checks, ", ".join(failed_checks) assert not failed_checks, ", ".join(failed_checks)
def check_read_only_container( assert_full_access_to_container = functools.partial(assert_access_to_container, FULL_ACCESS)
wallet: WalletInfo, assert_no_access_to_container = functools.partial(assert_access_to_container, NO_ACCESS)
cid: str, assert_read_only_container = functools.partial(assert_access_to_container, RO_ACCESS)
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,
)

View 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)

View 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)

View 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)

View 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]
)

View 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

View file

@ -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)

View file

@ -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)

View file

@ -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,
)

View file

@ -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,
)

View file

@ -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,
)

View file

@ -11,6 +11,7 @@ import pytest
import yaml import yaml
from dateutil import parser from dateutil import parser
from frostfs_testlib import plugins, reporter from frostfs_testlib import plugins, reporter
from frostfs_testlib.cli import FrostfsCli
from frostfs_testlib.credentials.interfaces import CredentialsProvider, User from frostfs_testlib.credentials.interfaces import CredentialsProvider, User
from frostfs_testlib.healthcheck.interfaces import Healthcheck from frostfs_testlib.healthcheck.interfaces import Healthcheck
from frostfs_testlib.hosting import Hosting 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.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.s3 import AwsCliClient, Boto3ClientWrapper, S3ClientWrapper, VersioningStatus
from frostfs_testlib.shell import LocalShell, Shell 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.cli.object import get_netmap_netinfo
from frostfs_testlib.steps.s3 import s3_helper from frostfs_testlib.steps.s3 import s3_helper
from frostfs_testlib.storage import get_service_registry 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.cluster_test_base import ClusterTestBase
from frostfs_testlib.testing.parallel import parallel from frostfs_testlib.testing.parallel import parallel
from frostfs_testlib.testing.test_control import wait_for_success from frostfs_testlib.testing.test_control import wait_for_success
from frostfs_testlib.utils import env_utils, version_utils from frostfs_testlib.utils import env_utils, string_utils, version_utils
from frostfs_testlib.utils.file_utils import generate_file from frostfs_testlib.utils.file_utils import TestFile, generate_file
from pytest_tests.resources.common import HOSTING_CONFIG_FILE, TEST_CYCLES_COUNT from pytest_tests.resources.common import HOSTING_CONFIG_FILE, TEST_CYCLES_COUNT
logger = logging.getLogger("NeoLogger") logger = logging.getLogger("NeoLogger")
SERVICE_ACTIVE_TIME = 20 SERVICE_ACTIVE_TIME = 20
WALLTETS_IN_POOL = 2
# Add logs check test even if it's not fit to mark selectors # Add logs check test even if it's not fit to mark selectors
def pytest_configure(config: pytest.Config): def pytest_configure(config: pytest.Config):
@ -189,7 +191,7 @@ def simple_object_size(max_object_size: int) -> ObjectSize:
@pytest.fixture() @pytest.fixture()
def file_path(object_size: ObjectSize) -> str: def file_path(object_size: ObjectSize) -> TestFile:
return generate_file(object_size.value) return generate_file(object_size.value)
@ -222,6 +224,12 @@ def ec_placement_policy() -> PlacementPolicy:
return PlacementPolicy("ec", DEFAULT_EC_PLACEMENT_RULE) 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. # By default we want all tests to be executed with both storage policies.
# This can be overriden in choosen tests if needed. # This can be overriden in choosen tests if needed.
@pytest.fixture( @pytest.fixture(
@ -316,10 +324,6 @@ def versioning_status(request: pytest.FixtureRequest) -> VersioningStatus:
return VersioningStatus.UNDEFINED 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") @allure.title("[Session] Bulk create buckets for tests")
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def buckets_pool(s3_client: S3ClientWrapper, request: pytest.FixtureRequest): def buckets_pool(s3_client: S3ClientWrapper, request: pytest.FixtureRequest):
@ -332,11 +336,11 @@ def buckets_pool(s3_client: S3ClientWrapper, request: pytest.FixtureRequest):
continue continue
if "bucket" in test.fixturenames: 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: if "two_buckets" in test.fixturenames:
test_buckets.append(unique_name("bucket-")) test_buckets.append(string_utils.unique_name("bucket-"))
test_buckets.append(unique_name("bucket-")) test_buckets.append(string_utils.unique_name("bucket-"))
if test_buckets: if test_buckets:
parallel(s3_client.create_bucket, 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") @reporter.step("Prepare default user with wallet")
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def default_user(credentials_provider: CredentialsProvider, cluster: Cluster) -> User: def default_user(credentials_provider: CredentialsProvider, cluster: Cluster) -> User:
# always unique username user = User(string_utils.unique_name("user-"))
user = User(f"user_{hex(int(datetime.now().timestamp() * 1000000))}")
node = cluster.cluster_nodes[0] node = cluster.cluster_nodes[0]
credentials_provider.GRPC.provide(user, node) credentials_provider.GRPC.provide(user, node)
@ -467,6 +470,28 @@ def default_wallet(default_user: User) -> WalletInfo:
return default_user.wallet 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() @pytest.fixture()
@allure.title("Select random node for testing") @allure.title("Select random node for testing")
def node_under_test(cluster: Cluster) -> ClusterNode: def node_under_test(cluster: Cluster) -> ClusterNode:

View file

@ -9,7 +9,6 @@ from frostfs_testlib import reporter
from frostfs_testlib.cli import FrostfsCli from frostfs_testlib.cli import FrostfsCli
from frostfs_testlib.cli.netmap_parser import NetmapParser from frostfs_testlib.cli.netmap_parser import NetmapParser
from frostfs_testlib.resources.cli import FROSTFS_CLI_EXEC 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.error_patterns import OBJECT_NOT_FOUND
from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL
from frostfs_testlib.steps.cli.container import create_container, search_nodes_with_container 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.storage_object_info import NodeStatus
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase 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.failover_utils import wait_object_replication
from frostfs_testlib.utils.file_utils import generate_file from frostfs_testlib.utils.file_utils import generate_file
@ -57,9 +56,7 @@ check_nodes: list[StorageNode] = []
class TestNodeManagement(ClusterTestBase): class TestNodeManagement(ClusterTestBase):
@pytest.fixture @pytest.fixture
@allure.title("Create container and pick the node with data") @allure.title("Create container and pick the node with data")
def create_container_and_pick_node( def create_container_and_pick_node(self, default_wallet: WalletInfo, simple_object_size: ObjectSize) -> Tuple[str, StorageNode]:
self, default_wallet: WalletInfo, simple_object_size: ObjectSize
) -> Tuple[str, StorageNode]:
file_path = generate_file(simple_object_size.value) file_path = generate_file(simple_object_size.value)
placement_rule = "REP 1 IN X CBF 1 SELECT 1 FROM * AS X" placement_rule = "REP 1 IN X CBF 1 SELECT 1 FROM * AS X"
endpoint = self.cluster.default_rpc_endpoint 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 # We need to wait for node to establish notifications from morph-chain
# Otherwise it will hang up when we will try to set status # 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"): with reporter.step(f"Move node {node} to online state"):
storage_node_set_status(node, status="online", retries=2) storage_node_set_status(node, status="online", retries=2)
check_nodes.remove(node) 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) self.tick_epoch_with_retries(3, wait_block=2)
check_node_in_map(node, shell=self.shell, alive_node=alive_node) 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 storage_nodes = self.cluster.storage_nodes
random_node = random.choice(storage_nodes[1:]) random_node = random.choice(storage_nodes[1:])
alive_node = random.choice( alive_node = random.choice([storage_node for storage_node in storage_nodes if storage_node.id != random_node.id])
[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) 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) source_file_path = generate_file(simple_object_size.value)
storage_nodes = self.cluster.storage_nodes storage_nodes = self.cluster.storage_nodes
random_node = random.choice(storage_nodes[1:]) random_node = random.choice(storage_nodes[1:])
alive_node = random.choice( alive_node = random.choice([storage_node for storage_node in storage_nodes if storage_node.id != random_node.id])
[storage_node for storage_node in storage_nodes if storage_node.id != random_node.id]
)
cid = create_container( cid = create_container(
wallet, wallet,
@ -340,14 +333,6 @@ class TestMaintenanceMode(ClusterTestBase):
cli = FrostfsCli(shell=shell, frostfs_cli_exec_path=FROSTFS_CLI_EXEC, config_file=wallet_config_path) cli = FrostfsCli(shell=shell, frostfs_cli_exec_path=FROSTFS_CLI_EXEC, config_file=wallet_config_path)
return cli 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() @pytest.fixture()
def restore_node_status(self, cluster_state_controller: ClusterStateController, default_wallet: WalletInfo): def restore_node_status(self, cluster_state_controller: ClusterStateController, default_wallet: WalletInfo):
nodes_to_restore = [] nodes_to_restore = []
@ -357,21 +342,15 @@ class TestMaintenanceMode(ClusterTestBase):
for node_to_restore in nodes_to_restore: for node_to_restore in nodes_to_restore:
cluster_state_controller.set_node_status(node_to_restore, default_wallet, NodeStatus.ONLINE) cluster_state_controller.set_node_status(node_to_restore, default_wallet, NodeStatus.ONLINE)
def check_node_status( def check_node_status(self, expected_status: NodeStatus, node_under_test: ClusterNode, frostfs_cli: FrostfsCli, rpc_endpoint: str):
self, expected_status: NodeStatus, node_under_test: ClusterNode, frostfs_cli: FrostfsCli, rpc_endpoint: str
):
netmap = frostfs_cli.netmap.snapshot(rpc_endpoint).stdout netmap = frostfs_cli.netmap.snapshot(rpc_endpoint).stdout
all_snapshots = NetmapParser.snapshot_all_nodes(netmap) all_snapshots = NetmapParser.snapshot_all_nodes(netmap)
node_snapshot = [snapshot for snapshot in all_snapshots if node_under_test.host_ip == snapshot.node] 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: if expected_status == NodeStatus.OFFLINE and not node_snapshot:
assert ( assert node_under_test.host_ip not in netmap, f"{node_under_test} status should be {expected_status}. See netmap:\n{netmap}"
node_under_test.host_ip not in netmap
), f"{node_under_test} status should be {expected_status}. See netmap:\n{netmap}"
return return
assert ( assert node_snapshot, f"{node_under_test} status should be {expected_status}, but was not in netmap. See netmap:\n{netmap}"
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] node_snapshot = node_snapshot[0]
assert ( assert (
expected_status == node_snapshot.node_status expected_status == node_snapshot.node_status

View file

@ -1,9 +1,9 @@
import allure import allure
import pytest import pytest
from frostfs_testlib import reporter 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.shell import Shell
from frostfs_testlib.steps.acl import form_bearertoken_file
from frostfs_testlib.steps.cli.container import ( from frostfs_testlib.steps.cli.container import (
REP_2_FOR_3_NODES_PLACEMENT_RULE, REP_2_FOR_3_NODES_PLACEMENT_RULE,
SINGLE_PLACEMENT_RULE, SINGLE_PLACEMENT_RULE,
@ -12,68 +12,50 @@ from frostfs_testlib.steps.cli.container import (
create_container, create_container,
) )
from frostfs_testlib.steps.cli.object import delete_object, get_object 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.steps.storage_object import StorageObjectInfo
from frostfs_testlib.storage.cluster import Cluster 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.object_size import ObjectSize
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.testing.test_control import expect_not_raises from frostfs_testlib.testing.test_control import expect_not_raises
from pytest import FixtureRequest from pytest import FixtureRequest
from pytest_tests.helpers.bearer_token import create_bearer_token
@pytest.fixture(scope="module") from pytest_tests.helpers.container_access import assert_full_access_to_container
@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
@pytest.fixture(scope="module") @pytest.fixture(scope="session")
@allure.title("Create user container for bearer token usage") @allure.title("Create user container for bearer token usage")
def user_container( def user_container(default_wallet: WalletInfo, client_shell: Shell, cluster: Cluster, request: FixtureRequest) -> StorageContainer:
default_wallet: WalletInfo, client_shell: Shell, cluster: Cluster, request: FixtureRequest rule = request.param if "param" in request.__dict__ else SINGLE_PLACEMENT_RULE
) -> StorageContainer: container_id = create_container(default_wallet, client_shell, cluster.default_rpc_endpoint, rule, PUBLIC_ACL)
container_id = create_container(
default_wallet,
shell=client_shell,
rule=request.param,
basic_acl=EACL_PUBLIC_READ_WRITE,
endpoint=cluster.default_rpc_endpoint,
)
# Deliberately using s3gate wallet here to test bearer token # Deliberately using s3gate wallet here to test bearer token
s3gate = cluster.s3_gates[0] s3_gate_wallet = WalletInfo.from_node(cluster.s3_gates[0])
return StorageContainer( return StorageContainer(StorageContainerInfo(container_id, s3_gate_wallet), client_shell, cluster)
StorageContainerInfo(container_id, WalletInfo.from_node(s3gate)),
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() @pytest.fixture()
def storage_objects( def storage_objects(
user_container: StorageContainer, user_container: StorageContainer,
bearer_token_file_all_allow: str, bearer_token: str,
object_size: ObjectSize, object_size: ObjectSize,
client_shell: Shell,
cluster: Cluster, cluster: Cluster,
) -> list[StorageObjectInfo]: ) -> list[StorageObjectInfo]:
epoch = get_epoch(client_shell, cluster)
storage_objects: list[StorageObjectInfo] = [] storage_objects: list[StorageObjectInfo] = []
for node in cluster.storage_nodes: for node in cluster.storage_nodes:
storage_objects.append( storage_objects.append(
user_container.generate_object( user_container.generate_object(
object_size.value, object_size.value,
epoch + 3, bearer_token=bearer_token,
bearer_token=bearer_token_file_all_allow,
endpoint=node.get_rpc_endpoint(), endpoint=node.get_rpc_endpoint(),
) )
) )
@ -82,6 +64,7 @@ def storage_objects(
@pytest.mark.smoke @pytest.mark.smoke
@pytest.mark.bearer @pytest.mark.bearer
@pytest.mark.ape
class TestObjectApiWithBearerToken(ClusterTestBase): class TestObjectApiWithBearerToken(ClusterTestBase):
@allure.title("Object can be deleted from any node using s3gate wallet with bearer token (obj_size={object_size})") @allure.title("Object can be deleted from any node using s3gate wallet with bearer token (obj_size={object_size})")
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -92,10 +75,10 @@ class TestObjectApiWithBearerToken(ClusterTestBase):
def test_delete_object_with_s3_wallet_bearer( def test_delete_object_with_s3_wallet_bearer(
self, self,
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
bearer_token_file_all_allow: str, bearer_token: str,
): ):
s3_gate_wallet = WalletInfo.from_node(self.cluster.s3_gates[0]) 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: for storage_object in storage_objects:
with expect_not_raises(): with expect_not_raises():
delete_object( delete_object(
@ -104,7 +87,7 @@ class TestObjectApiWithBearerToken(ClusterTestBase):
storage_object.oid, storage_object.oid,
self.shell, self.shell,
endpoint=self.cluster.default_rpc_endpoint, 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})") @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, self,
user_container: StorageContainer, user_container: StorageContainer,
object_size: ObjectSize, object_size: ObjectSize,
bearer_token_file_all_allow: str, bearer_token: str,
): ):
s3_gate_wallet = WalletInfo.from_node(self.cluster.s3_gates[0]) s3_gate_wallet = WalletInfo.from_node(self.cluster.s3_gates[0])
with reporter.step("Put one object to container"): with reporter.step("Put object to container"):
epoch = self.get_epoch()
storage_object = user_container.generate_object( 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: for node in self.cluster.storage_nodes:
with expect_not_raises(): with expect_not_raises():
get_object( get_object(
@ -134,6 +118,17 @@ class TestObjectApiWithBearerToken(ClusterTestBase):
storage_object.cid, storage_object.cid,
storage_object.oid, storage_object.oid,
self.shell, self.shell,
endpoint=node.get_rpc_endpoint(), node.get_rpc_endpoint(),
bearer=bearer_token_file_all_allow, 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)

View file

@ -3,22 +3,20 @@ import logging
import allure import allure
import pytest import pytest
from frostfs_testlib import reporter 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.resources.wellknown_acl import PUBLIC_ACL
from frostfs_testlib.steps.acl import ( from frostfs_testlib.steps.acl import bearer_token_base64_from_file
bearer_token_base64_from_file,
create_eacl,
form_bearertoken_file,
set_eacl,
sign_bearer,
wait_for_cache_expired,
)
from frostfs_testlib.steps.cli.container import create_container 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.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.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.cluster_test_base import ClusterTestBase
from frostfs_testlib.utils.file_utils import generate_file from frostfs_testlib.utils.file_utils import generate_file
from pytest_tests.helpers.bearer_token import create_bearer_token
logger = logging.getLogger("NeoLogger") logger = logging.getLogger("NeoLogger")
@ -27,60 +25,34 @@ logger = logging.getLogger("NeoLogger")
class Test_http_bearer(ClusterTestBase): class Test_http_bearer(ClusterTestBase):
PLACEMENT_RULE = "REP 2 IN X CBF 1 SELECT 2 FROM * AS X" PLACEMENT_RULE = "REP 2 IN X CBF 1 SELECT 2 FROM * AS X"
@pytest.fixture(scope="class", autouse=True) @pytest.fixture(scope="class")
@allure.title("[Class/Autouse]: Prepare wallet and deposit") def user_container(self, frostfs_cli: FrostfsCli, default_wallet: WalletInfo, cluster: Cluster) -> str:
def prepare_wallet(self, default_wallet): with reporter.step("Create container"):
Test_http_bearer.wallet = default_wallet 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") @pytest.fixture(scope="class")
def user_container(self) -> str: def bearer_token(self, frostfs_cli: FrostfsCli, user_container: str, temp_directory: str, cluster: Cluster) -> str:
return create_container( with reporter.step(f"Create bearer token for {ape.Role.OTHERS} with all operations allowed"):
wallet=self.wallet, role_condition = ape.Condition.by_role(ape.Role.OTHERS)
shell=self.shell, rule = ape.Rule(ape.Verb.ALLOW, ape.ObjectOperations.WILDCARD_ALL, role_condition)
endpoint=self.cluster.default_rpc_endpoint, bearer = create_bearer_token(frostfs_cli, temp_directory, user_container, rule, cluster.default_rpc_endpoint)
rule=self.PLACEMENT_RULE,
basic_acl=PUBLIC_ACL,
)
@pytest.fixture(scope="class") return bearer_token_base64_from_file(bearer)
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()
@pytest.fixture(scope="class") @allure.title(f"[NEGATIVE] Put object without bearer token for {ape.Role.OTHERS}")
def bearer_token_no_limit_for_others(self, user_container: str) -> str: def test_unable_put_without_bearer_token(self, simple_object_size: ObjectSize, user_container: 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
upload_via_http_gate_curl( upload_via_http_gate_curl(
cid=user_container, cid=user_container,
filepath=generate_file(simple_object_size.value), filepath=generate_file(simple_object_size.value),
@ -91,15 +63,13 @@ class Test_http_bearer(ClusterTestBase):
def test_put_with_bearer_when_eacl_restrict( def test_put_with_bearer_when_eacl_restrict(
self, self,
object_size: ObjectSize, object_size: ObjectSize,
default_wallet: WalletInfo,
user_container: str, user_container: str,
eacl_deny_for_others, bearer_token: str,
bearer_token_no_limit_for_others: str,
): ):
eacl_deny_for_others
bearer = bearer_token_no_limit_for_others
file_path = generate_file(object_size.value) file_path = generate_file(object_size.value)
with reporter.step(f"Put object with bearer token for {EACLRole.OTHERS}, then get and verify hashes"): with reporter.step(f"Put object with bearer token for {ape.Role.OTHERS}, then get and verify hashes"):
headers = [f" -H 'Authorization: Bearer {bearer}'"] headers = [f" -H 'Authorization: Bearer {bearer_token}'"]
oid = upload_via_http_gate_curl( oid = upload_via_http_gate_curl(
cid=user_container, cid=user_container,
filepath=file_path, filepath=file_path,
@ -109,7 +79,7 @@ class Test_http_bearer(ClusterTestBase):
verify_object_hash( verify_object_hash(
oid=oid, oid=oid,
file_name=file_path, file_name=file_path,
wallet=self.wallet, wallet=default_wallet,
cid=user_container, cid=user_container,
shell=self.shell, shell=self.shell,
nodes=self.cluster.storage_nodes, nodes=self.cluster.storage_nodes,

View file

@ -8,10 +8,8 @@ from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def owner_wallet(credentials_provider: CredentialsProvider, cluster: Cluster) -> WalletInfo: def owner_wallet(default_wallet: WalletInfo) -> WalletInfo:
with reporter.step("Create user wallet which owns containers and objects"): return default_wallet
user = User(f"user_{hex(int(datetime.now().timestamp() * 1000000))}")
return credentials_provider.GRPC.provide(user, cluster.cluster_nodes[0])
@pytest.fixture(scope="module") @pytest.fixture(scope="module")

View file

@ -1,17 +1,10 @@
import pytest import pytest
from frostfs_testlib import reporter from frostfs_testlib import reporter
from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL
from frostfs_testlib.shell import Shell 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.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.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.storage.dataclasses.wallet import WalletInfo
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase 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 @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 Returns dict with static session token file paths for all verbs with default lifetime
""" """
return { return {verb: get_container_signed_token(owner_wallet, user_wallet, verb, client_shell, temp_directory) for verb in ContainerVerb}
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( def test_static_session_token_container_create(
self, self,
@ -50,9 +40,7 @@ class TestSessionTokenContainer(ClusterTestBase):
wait_for_creation=False, wait_for_creation=False,
) )
container_info: dict[str, str] = get_container( container_info: dict[str, str] = get_container(owner_wallet, cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint)
owner_wallet, cid, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
)
assert container_info["ownerID"] == owner_wallet.get_address() 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) 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) 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)