Add ACL and eACL PyTest tests

Signed-off-by: Vladimir Avdeev <v.avdeev@yadro.com>
This commit is contained in:
Vladimir Avdeev 2022-08-25 13:57:55 +03:00 committed by Vladimir Avdeev
parent 590a5cfb0e
commit 6d040c6834
36 changed files with 979 additions and 1318 deletions

View file

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