Add bearer token tests for s3 wallet api calls
Signed-off-by: Andrey Berezin <a.berezin@yadro.com>
This commit is contained in:
parent
522fc9dccd
commit
6567aa72a9
8 changed files with 211 additions and 17 deletions
|
@ -202,6 +202,7 @@ class Cluster:
|
||||||
|
|
||||||
default_rpc_endpoint: str
|
default_rpc_endpoint: str
|
||||||
default_s3_gate_endpoint: str
|
default_s3_gate_endpoint: str
|
||||||
|
default_http_gate_endpoint: str
|
||||||
|
|
||||||
def __init__(self, hosting: Hosting) -> None:
|
def __init__(self, hosting: Hosting) -> None:
|
||||||
self._hosting = hosting
|
self._hosting = hosting
|
||||||
|
|
|
@ -5,7 +5,7 @@ import allure
|
||||||
from cluster import Cluster
|
from cluster import Cluster
|
||||||
from file_helper import generate_file, get_file_hash
|
from file_helper import generate_file, get_file_hash
|
||||||
from neofs_testlib.shell import Shell
|
from neofs_testlib.shell import Shell
|
||||||
from neofs_verbs import put_object_to_random_node
|
from neofs_verbs import put_object, put_object_to_random_node
|
||||||
from storage_object import StorageObjectInfo
|
from storage_object import StorageObjectInfo
|
||||||
from wallet import WalletFile
|
from wallet import WalletFile
|
||||||
|
|
||||||
|
@ -33,24 +33,47 @@ class StorageContainer:
|
||||||
def get_wallet_path(self) -> str:
|
def get_wallet_path(self) -> str:
|
||||||
return self.storage_container_info.wallet_file.path
|
return self.storage_container_info.wallet_file.path
|
||||||
|
|
||||||
|
def get_wallet_config_path(self) -> str:
|
||||||
|
return self.storage_container_info.wallet_file.config_path
|
||||||
|
|
||||||
@allure.step("Generate new object and put in container")
|
@allure.step("Generate new object and put in container")
|
||||||
def generate_object(self, size: int, expire_at: Optional[int] = None) -> StorageObjectInfo:
|
def generate_object(
|
||||||
|
self,
|
||||||
|
size: int,
|
||||||
|
expire_at: Optional[int] = None,
|
||||||
|
bearer_token: Optional[str] = None,
|
||||||
|
endpoint: Optional[str] = None,
|
||||||
|
) -> StorageObjectInfo:
|
||||||
with allure.step(f"Generate object with size {size}"):
|
with allure.step(f"Generate object with size {size}"):
|
||||||
file_path = generate_file(size)
|
file_path = generate_file(size)
|
||||||
file_hash = get_file_hash(file_path)
|
file_hash = get_file_hash(file_path)
|
||||||
|
|
||||||
container_id = self.get_id()
|
container_id = self.get_id()
|
||||||
wallet_path = self.get_wallet_path()
|
wallet_path = self.get_wallet_path()
|
||||||
|
wallet_config = self.get_wallet_config_path()
|
||||||
with allure.step(f"Put object with size {size} to container {container_id}"):
|
with allure.step(f"Put object with size {size} to container {container_id}"):
|
||||||
object_id = put_object_to_random_node(
|
if endpoint:
|
||||||
wallet=wallet_path,
|
object_id = put_object(
|
||||||
path=file_path,
|
wallet=wallet_path,
|
||||||
cid=container_id,
|
path=file_path,
|
||||||
expire_at=expire_at,
|
cid=container_id,
|
||||||
shell=self.shell,
|
expire_at=expire_at,
|
||||||
cluster=self.cluster,
|
shell=self.shell,
|
||||||
)
|
endpoint=endpoint,
|
||||||
|
bearer=bearer_token,
|
||||||
|
wallet_config=wallet_config,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
object_id = put_object_to_random_node(
|
||||||
|
wallet=wallet_path,
|
||||||
|
path=file_path,
|
||||||
|
cid=container_id,
|
||||||
|
expire_at=expire_at,
|
||||||
|
shell=self.shell,
|
||||||
|
cluster=self.cluster,
|
||||||
|
bearer=bearer_token,
|
||||||
|
wallet_config=wallet_config,
|
||||||
|
)
|
||||||
|
|
||||||
storage_object = StorageObjectInfo(
|
storage_object = StorageObjectInfo(
|
||||||
container_id,
|
container_id,
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from cluster import Cluster
|
from cluster import Cluster, NodeBase
|
||||||
from common import FREE_STORAGE, WALLET_PASS
|
from common import FREE_STORAGE, WALLET_CONFIG, WALLET_PASS
|
||||||
from neofs_testlib.shell import Shell
|
from neofs_testlib.shell import Shell
|
||||||
from neofs_testlib.utils.wallet import get_last_address_from_wallet, init_wallet
|
from neofs_testlib.utils.wallet import get_last_address_from_wallet, init_wallet
|
||||||
from python_keywords.payment_neogo import deposit_gas, transfer_gas
|
from python_keywords.payment_neogo import deposit_gas, transfer_gas
|
||||||
|
@ -13,7 +12,14 @@ from python_keywords.payment_neogo import deposit_gas, transfer_gas
|
||||||
@dataclass
|
@dataclass
|
||||||
class WalletFile:
|
class WalletFile:
|
||||||
path: str
|
path: str
|
||||||
password: str
|
password: str = WALLET_PASS
|
||||||
|
config_path: str = WALLET_CONFIG
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_node(node: NodeBase):
|
||||||
|
return WalletFile(
|
||||||
|
node.get_wallet_path(), node.get_wallet_password(), node.get_wallet_config_path()
|
||||||
|
)
|
||||||
|
|
||||||
def get_address(self) -> str:
|
def get_address(self) -> str:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -29,6 +29,7 @@ markers =
|
||||||
node_mgmt: neofs control commands
|
node_mgmt: neofs control commands
|
||||||
session_token: tests for operations with session token
|
session_token: tests for operations with session token
|
||||||
static_session: tests for operations with static session token
|
static_session: tests for operations with static session token
|
||||||
|
bearer: tests for bearer tokens
|
||||||
acl: All tests for ACL
|
acl: All tests for ACL
|
||||||
acl_basic: tests for basic ACL
|
acl_basic: tests for basic ACL
|
||||||
acl_bearer: tests for ACL with bearer
|
acl_bearer: tests for ACL with bearer
|
||||||
|
|
159
pytest_tests/testsuites/object/test_object_api_bearer.py
Normal file
159
pytest_tests/testsuites/object/test_object_api_bearer.py
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
import allure
|
||||||
|
import pytest
|
||||||
|
from cluster import Cluster
|
||||||
|
from common import COMPLEX_OBJ_SIZE, SIMPLE_OBJ_SIZE
|
||||||
|
from container import REP_2_FOR_3_NODES_PLACEMENT_RULE, SINGLE_PLACEMENT_RULE, create_container
|
||||||
|
from epoch import get_epoch
|
||||||
|
from neofs_testlib.shell import Shell
|
||||||
|
from neofs_verbs import delete_object, get_object
|
||||||
|
from pytest import FixtureRequest
|
||||||
|
from python_keywords.acl import EACLAccess, EACLOperation, EACLRole, EACLRule, form_bearertoken_file
|
||||||
|
from wellknown_acl import EACL_PUBLIC_READ_WRITE
|
||||||
|
|
||||||
|
from helpers.container import StorageContainer, StorageContainerInfo
|
||||||
|
from helpers.test_control import expect_not_raises
|
||||||
|
from helpers.wallet import WalletFile
|
||||||
|
from steps.cluster_test_base import ClusterTestBase
|
||||||
|
from steps.storage_object import StorageObjectInfo
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
@allure.title("Create bearer token for OTHERS with all operations allowed for all containers")
|
||||||
|
def bearer_token_file_all_allow(default_wallet: str, 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")
|
||||||
|
@allure.title("Create user container for bearer token usage")
|
||||||
|
def user_container(
|
||||||
|
default_wallet: str, client_shell: Shell, cluster: Cluster, request: FixtureRequest
|
||||||
|
) -> StorageContainer:
|
||||||
|
container_id = create_container(
|
||||||
|
default_wallet,
|
||||||
|
shell=client_shell,
|
||||||
|
rule=request.param,
|
||||||
|
basic_acl=EACL_PUBLIC_READ_WRITE,
|
||||||
|
endpoint=cluster.default_rpc_endpoint,
|
||||||
|
)
|
||||||
|
# Deliberately using s3gate wallet here to test bearer token
|
||||||
|
s3gate = cluster.s3gates[0]
|
||||||
|
return StorageContainer(
|
||||||
|
StorageContainerInfo(container_id, WalletFile.from_node(s3gate)),
|
||||||
|
client_shell,
|
||||||
|
cluster,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def storage_objects(
|
||||||
|
user_container: StorageContainer,
|
||||||
|
bearer_token_file_all_allow: str,
|
||||||
|
request: FixtureRequest,
|
||||||
|
client_shell: Shell,
|
||||||
|
cluster: Cluster,
|
||||||
|
) -> list[StorageObjectInfo]:
|
||||||
|
epoch = get_epoch(client_shell, cluster)
|
||||||
|
storage_objects: list[StorageObjectInfo] = []
|
||||||
|
for node in cluster.storage_nodes:
|
||||||
|
storage_objects.append(
|
||||||
|
user_container.generate_object(
|
||||||
|
request.param,
|
||||||
|
epoch + 3,
|
||||||
|
bearer_token=bearer_token_file_all_allow,
|
||||||
|
endpoint=node.get_rpc_endpoint(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return storage_objects
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.smoke
|
||||||
|
@pytest.mark.bearer
|
||||||
|
class TestObjectApiWithBearerToken(ClusterTestBase):
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"user_container",
|
||||||
|
[SINGLE_PLACEMENT_RULE],
|
||||||
|
ids=["single replica for all nodes placement rule"],
|
||||||
|
indirect=True,
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"storage_objects",
|
||||||
|
[SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE],
|
||||||
|
ids=["simple object", "complex object"],
|
||||||
|
indirect=True,
|
||||||
|
)
|
||||||
|
def test_delete_object_with_s3_wallet_bearer(
|
||||||
|
self,
|
||||||
|
storage_objects: list[StorageObjectInfo],
|
||||||
|
bearer_token_file_all_allow: str,
|
||||||
|
request: FixtureRequest,
|
||||||
|
):
|
||||||
|
allure.dynamic.title(
|
||||||
|
f"Check that objects can be deleted from any node using s3gate wallet with bearer token for {request.node.callspec.id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
s3_gate_wallet = self.cluster.s3gates[0]
|
||||||
|
with allure.step("Try to delete each object from first storage node"):
|
||||||
|
for storage_object in storage_objects:
|
||||||
|
with expect_not_raises():
|
||||||
|
delete_object(
|
||||||
|
s3_gate_wallet.get_wallet_path(),
|
||||||
|
storage_object.cid,
|
||||||
|
storage_object.oid,
|
||||||
|
self.shell,
|
||||||
|
endpoint=self.cluster.default_rpc_endpoint,
|
||||||
|
bearer=bearer_token_file_all_allow,
|
||||||
|
wallet_config=s3_gate_wallet.get_wallet_config_path(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"user_container",
|
||||||
|
[REP_2_FOR_3_NODES_PLACEMENT_RULE],
|
||||||
|
ids=["2 replicas for 3 nodes placement rule"],
|
||||||
|
indirect=True,
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"file_size",
|
||||||
|
[SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE],
|
||||||
|
ids=["simple object", "complex object"],
|
||||||
|
)
|
||||||
|
def test_get_object_with_s3_wallet_bearer_from_all_nodes(
|
||||||
|
self,
|
||||||
|
user_container: StorageContainer,
|
||||||
|
file_size: int,
|
||||||
|
bearer_token_file_all_allow: str,
|
||||||
|
request: FixtureRequest,
|
||||||
|
):
|
||||||
|
allure.dynamic.title(
|
||||||
|
f"Check that objects can be deleted from any node using s3gate wallet with bearer token for {request.node.callspec.id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
s3_gate_wallet = self.cluster.s3gates[0]
|
||||||
|
with allure.step("Put one object to container"):
|
||||||
|
epoch = self.get_epoch()
|
||||||
|
storage_object = user_container.generate_object(
|
||||||
|
file_size, epoch + 3, bearer_token=bearer_token_file_all_allow
|
||||||
|
)
|
||||||
|
|
||||||
|
with allure.step("Try to fetch object from each storage node"):
|
||||||
|
for node in self.cluster.storage_nodes:
|
||||||
|
with expect_not_raises():
|
||||||
|
get_object(
|
||||||
|
s3_gate_wallet.get_wallet_path(),
|
||||||
|
storage_object.cid,
|
||||||
|
storage_object.oid,
|
||||||
|
self.shell,
|
||||||
|
endpoint=node.get_rpc_endpoint(),
|
||||||
|
bearer=bearer_token_file_all_allow,
|
||||||
|
wallet_config=s3_gate_wallet.get_wallet_config_path(),
|
||||||
|
)
|
|
@ -178,7 +178,7 @@ def form_bearertoken_file(
|
||||||
then extends it with filters taken from <eacl_rules>, signs
|
then extends it with filters taken from <eacl_rules>, signs
|
||||||
with bearer token and writes to file
|
with bearer token and writes to file
|
||||||
"""
|
"""
|
||||||
enc_cid = _encode_cid_for_eacl(cid)
|
enc_cid = _encode_cid_for_eacl(cid) if cid else None
|
||||||
file_path = os.path.join(os.getcwd(), ASSETS_DIR, str(uuid.uuid4()))
|
file_path = os.path.join(os.getcwd(), ASSETS_DIR, str(uuid.uuid4()))
|
||||||
|
|
||||||
eacl = get_eacl(wif, cid, shell, endpoint)
|
eacl = get_eacl(wif, cid, shell, endpoint)
|
||||||
|
@ -189,7 +189,7 @@ def form_bearertoken_file(
|
||||||
logger.info(json_eacl)
|
logger.info(json_eacl)
|
||||||
eacl_result = {
|
eacl_result = {
|
||||||
"body": {
|
"body": {
|
||||||
"eaclTable": {"containerID": {"value": enc_cid}, "records": []},
|
"eaclTable": {"containerID": {"value": enc_cid} if cid else enc_cid, "records": []},
|
||||||
"lifetime": {"exp": EACL_LIFETIME, "nbf": "1", "iat": "0"},
|
"lifetime": {"exp": EACL_LIFETIME, "nbf": "1", "iat": "0"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ from neofs_testlib.shell import Shell
|
||||||
logger = logging.getLogger("NeoLogger")
|
logger = logging.getLogger("NeoLogger")
|
||||||
|
|
||||||
DEFAULT_PLACEMENT_RULE = "REP 2 IN X CBF 1 SELECT 4 FROM * AS X"
|
DEFAULT_PLACEMENT_RULE = "REP 2 IN X CBF 1 SELECT 4 FROM * AS X"
|
||||||
|
SINGLE_PLACEMENT_RULE = "REP 1 IN X CBF 1 SELECT 4 FROM * AS X"
|
||||||
|
REP_2_FOR_3_NODES_PLACEMENT_RULE = "REP 2 IN X CBF 1 SELECT 3 FROM * AS X"
|
||||||
|
|
||||||
|
|
||||||
@allure.step("Create Container")
|
@allure.step("Create Container")
|
||||||
|
|
|
@ -7,3 +7,5 @@ READONLY_ACL_F = "1FBF8CFF"
|
||||||
PUBLIC_ACL = "0FBFBFFF"
|
PUBLIC_ACL = "0FBFBFFF"
|
||||||
INACCESSIBLE_ACL = "40000000"
|
INACCESSIBLE_ACL = "40000000"
|
||||||
STICKYBIT_PUB_ACL = "3FFFFFFF"
|
STICKYBIT_PUB_ACL = "3FFFFFFF"
|
||||||
|
|
||||||
|
EACL_PUBLIC_READ_WRITE = "eacl-public-read-write"
|
||||||
|
|
Loading…
Reference in a new issue