forked from TrueCloudLab/frostfs-testcases
Add ACL and eACL PyTest tests
Signed-off-by: Vladimir Avdeev <v.avdeev@yadro.com>
This commit is contained in:
parent
590a5cfb0e
commit
6d040c6834
36 changed files with 979 additions and 1318 deletions
|
@ -1,63 +1,119 @@
|
|||
#!/usr/bin/python3.9
|
||||
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import uuid
|
||||
from enum import Enum, auto
|
||||
from typing import Optional
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from time import sleep
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
import allure
|
||||
import base58
|
||||
from cli import NeofsCli
|
||||
from cli_helpers import _cmd_run
|
||||
from common import ASSETS_DIR, NEOFS_CLI_EXEC, NEOFS_ENDPOINT, WALLET_CONFIG
|
||||
from data_formatters import get_wallet_public_key
|
||||
from robot.api import logger
|
||||
from robot.api.deco import keyword
|
||||
|
||||
"""
|
||||
Robot Keywords and helper functions for work with NeoFS ACL.
|
||||
"""
|
||||
|
||||
ROBOT_AUTO_KEYWORDS = False
|
||||
logger = logging.getLogger('NeoLogger')
|
||||
EACL_LIFETIME = 100500
|
||||
NEOFS_CONTRACT_CACHE_TIMEOUT = 30
|
||||
|
||||
|
||||
class AutoName(Enum):
|
||||
def _generate_next_value_(name, start, count, last_values):
|
||||
return name
|
||||
class EACLOperation(Enum):
|
||||
PUT = 'put'
|
||||
GET = 'get'
|
||||
HEAD = 'head'
|
||||
GET_RANGE = 'getrange'
|
||||
GET_RANGE_HASH = 'getrangehash'
|
||||
SEARCH = 'search'
|
||||
DELETE = 'delete'
|
||||
|
||||
|
||||
class Role(AutoName):
|
||||
USER = auto()
|
||||
SYSTEM = auto()
|
||||
OTHERS = auto()
|
||||
class EACLAccess(Enum):
|
||||
ALLOW = 'allow'
|
||||
DENY = 'deny'
|
||||
|
||||
|
||||
@keyword('Get eACL')
|
||||
class EACLRole(Enum):
|
||||
OTHERS = 'others'
|
||||
USER = 'user'
|
||||
SYSTEM = 'system'
|
||||
|
||||
|
||||
class EACLHeaderType(Enum):
|
||||
REQUEST = 'req' # Filter request headers
|
||||
OBJECT = 'obj' # Filter object headers
|
||||
SERVICE = 'SERVICE' # Filter service headers. These are not processed by NeoFS nodes and exist for service use only
|
||||
|
||||
|
||||
class EACLMatchType(Enum):
|
||||
STRING_EQUAL = '=' # Return true if strings are equal
|
||||
STRING_NOT_EQUAL = '!=' # Return true if strings are different
|
||||
|
||||
|
||||
@dataclass
|
||||
class EACLFilter:
|
||||
header_type: EACLHeaderType = EACLHeaderType.REQUEST
|
||||
match_type: EACLMatchType = EACLMatchType.STRING_EQUAL
|
||||
key: Optional[str] = None
|
||||
value: Optional[str] = None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {'headerType': self.header_type, 'matchType': self.match_type, 'key': self.key, 'value': self.value}
|
||||
|
||||
|
||||
@dataclass
|
||||
class EACLFilters:
|
||||
filters: Optional[List[EACLFilter]] = None
|
||||
|
||||
def __str__(self):
|
||||
return ','.join(
|
||||
[f'{filter.header_type.value}:{filter.key}{filter.match_type.value}{filter.value}'
|
||||
for filter in self.filters]
|
||||
) if self.filters else []
|
||||
|
||||
|
||||
@dataclass
|
||||
class EACLPubKey:
|
||||
keys: Optional[List[str]] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class EACLRule:
|
||||
operation: Optional[EACLOperation] = None
|
||||
access: Optional[EACLAccess] = None
|
||||
role: Optional[Union[EACLRole, str]] = None
|
||||
filters: Optional[EACLFilters] = None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {'Operation': self.operation, 'Access': self.access, 'Role': self.role,
|
||||
'Filters': self.filters or []}
|
||||
|
||||
def __str__(self):
|
||||
role = self.role.value if isinstance(self.role, EACLRole) else f'pubkey:{get_wallet_public_key(self.role, "")}'
|
||||
return f'{self.access.value} {self.operation.value} {self.filters or ""} {role}'
|
||||
|
||||
|
||||
@allure.title('Get extended ACL')
|
||||
def get_eacl(wallet_path: str, cid: str) -> Optional[str]:
|
||||
cmd = (
|
||||
f'{NEOFS_CLI_EXEC} --rpc-endpoint {NEOFS_ENDPOINT} --wallet {wallet_path} '
|
||||
f'container get-eacl --cid {cid} --config {WALLET_CONFIG}'
|
||||
)
|
||||
cli = NeofsCli(config=WALLET_CONFIG)
|
||||
try:
|
||||
output = _cmd_run(cmd)
|
||||
if re.search(r'extended ACL table is not set for this container', output):
|
||||
return None
|
||||
return output
|
||||
output = cli.container.get_eacl(wallet=wallet_path, rpc_endpoint=NEOFS_ENDPOINT, cid=cid)
|
||||
except RuntimeError as exc:
|
||||
logger.info("Extended ACL table is not set for this container")
|
||||
logger.info(f"Got exception while getting eacl: {exc}")
|
||||
return None
|
||||
if 'extended ACL table is not set for this container' in output:
|
||||
return None
|
||||
return output
|
||||
|
||||
|
||||
@keyword('Set eACL')
|
||||
@allure.title('Set extended ACL')
|
||||
def set_eacl(wallet_path: str, cid: str, eacl_table_path: str) -> None:
|
||||
cmd = (
|
||||
f'{NEOFS_CLI_EXEC} --rpc-endpoint {NEOFS_ENDPOINT} --wallet {wallet_path} '
|
||||
f'container set-eacl --cid {cid} --table {eacl_table_path} --config {WALLET_CONFIG} --await'
|
||||
)
|
||||
_cmd_run(cmd)
|
||||
cli = NeofsCli(config=WALLET_CONFIG, timeout=60)
|
||||
cli.container.set_eacl(wallet=wallet_path, rpc_endpoint=NEOFS_ENDPOINT, cid=cid, table=eacl_table_path,
|
||||
await_mode=True)
|
||||
|
||||
|
||||
def _encode_cid_for_eacl(cid: str) -> str:
|
||||
|
@ -65,13 +121,9 @@ def _encode_cid_for_eacl(cid: str) -> str:
|
|||
return base64.b64encode(cid_base58).decode("utf-8")
|
||||
|
||||
|
||||
@keyword('Create eACL')
|
||||
def create_eacl(cid: str, rules_list: list) -> str:
|
||||
def create_eacl(cid: str, rules_list: List[EACLRule]) -> str:
|
||||
table_file_path = f"{os.getcwd()}/{ASSETS_DIR}/eacl_table_{str(uuid.uuid4())}.json"
|
||||
rules = " ".join(f"--rule '{rule}'" for rule in rules_list)
|
||||
|
||||
cmd = f"{NEOFS_CLI_EXEC} acl extended create --cid {cid} {rules} --out {table_file_path}"
|
||||
_cmd_run(cmd)
|
||||
NeofsCli().acl.extended_create(cid=cid, out=table_file_path, rule=rules_list)
|
||||
|
||||
with open(table_file_path, 'r') as file:
|
||||
table_data = file.read()
|
||||
|
@ -80,11 +132,10 @@ def create_eacl(cid: str, rules_list: list) -> str:
|
|||
return table_file_path
|
||||
|
||||
|
||||
@keyword('Form BearerToken File')
|
||||
def form_bearertoken_file(wif: str, cid: str, eacl_records: list) -> str:
|
||||
def form_bearertoken_file(wif: str, cid: str, eacl_rule_list: List[Union[EACLRule, EACLPubKey]]) -> str:
|
||||
"""
|
||||
This function fetches eACL for given <cid> on behalf of <wif>,
|
||||
then extends it with filters taken from <eacl_records>, signs
|
||||
then extends it with filters taken from <eacl_rules>, signs
|
||||
with bearer token and writes to file
|
||||
"""
|
||||
enc_cid = _encode_cid_for_eacl(cid)
|
||||
|
@ -93,8 +144,7 @@ def form_bearertoken_file(wif: str, cid: str, eacl_records: list) -> str:
|
|||
eacl = get_eacl(wif, cid)
|
||||
json_eacl = dict()
|
||||
if eacl:
|
||||
eacl = eacl.replace('eACL: ', '')
|
||||
eacl = eacl.split('Signature')[0]
|
||||
eacl = eacl.replace('eACL: ', '').split('Signature')[0]
|
||||
json_eacl = json.loads(eacl)
|
||||
logger.info(json_eacl)
|
||||
eacl_result = {
|
||||
|
@ -117,32 +167,28 @@ def form_bearertoken_file(wif: str, cid: str, eacl_records: list) -> str:
|
|||
}
|
||||
}
|
||||
|
||||
if not eacl_records:
|
||||
raise (f"Got empty eacl_records list: {eacl_records}")
|
||||
for record in eacl_records:
|
||||
assert eacl_rules, 'Got empty eacl_records list'
|
||||
for rule in eacl_rule_list:
|
||||
op_data = {
|
||||
"operation": record['Operation'],
|
||||
"action": record['Access'],
|
||||
"filters": [],
|
||||
"operation": rule.operation.value.upper(),
|
||||
"action": rule.access.value.upper(),
|
||||
"filters": rule.filters or [],
|
||||
"targets": []
|
||||
}
|
||||
|
||||
if Role(record['Role']):
|
||||
if isinstance(rule.role, EACLRole):
|
||||
op_data['targets'] = [
|
||||
{
|
||||
"role": record['Role']
|
||||
"role": rule.role.value.upper()
|
||||
}
|
||||
]
|
||||
else:
|
||||
elif isinstance(rule.role, EACLPubKey):
|
||||
op_data['targets'] = [
|
||||
{
|
||||
"keys": [record['Role']]
|
||||
'keys': rule.role.keys
|
||||
}
|
||||
]
|
||||
|
||||
if 'Filters' in record.keys():
|
||||
op_data["filters"].append(record['Filters'])
|
||||
|
||||
eacl_result["body"]["eaclTable"]["records"].append(op_data)
|
||||
|
||||
# Add records from current eACL
|
||||
|
@ -158,7 +204,6 @@ def form_bearertoken_file(wif: str, cid: str, eacl_records: list) -> str:
|
|||
return file_path
|
||||
|
||||
|
||||
@keyword('EACL Rules')
|
||||
def eacl_rules(access: str, verbs: list, user: str) -> list[str]:
|
||||
"""
|
||||
This function creates a list of eACL rules.
|
||||
|
@ -188,3 +233,9 @@ def sign_bearer_token(wallet_path: str, eacl_rules_file: str) -> None:
|
|||
f'--to {eacl_rules_file} --wallet {wallet_path} --config {WALLET_CONFIG} --json'
|
||||
)
|
||||
_cmd_run(cmd)
|
||||
|
||||
|
||||
@allure.title('Wait for eACL cache expired')
|
||||
def wait_for_cache_expired():
|
||||
sleep(NEOFS_CONTRACT_CACHE_TIMEOUT)
|
||||
return
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue