Add configurable policy files

Configurable policy files are added for services.

Change-Id: I53549fe80798bdd2e6ed3f92f548ed16393a269f
This commit is contained in:
yangshaoxue 2023-10-31 00:19:50 +08:00
parent cdfe0330d3
commit 1e4f72ca2b
5 changed files with 95 additions and 32 deletions

View File

@ -0,0 +1,5 @@
---
features:
- |
Editable policy files are supported. Add policy yaml file name and path options
to the configuration file. Users can edit and modify the policy as needed.

View File

@ -28,6 +28,7 @@ from skyline_apiserver.client.utils import generate_session, get_access, get_sys
from skyline_apiserver.config import CONF from skyline_apiserver.config import CONF
from skyline_apiserver.log import LOG from skyline_apiserver.log import LOG
from skyline_apiserver.policy import ENFORCER, UserContext from skyline_apiserver.policy import ENFORCER, UserContext
from skyline_apiserver.types import constants
router = APIRouter() router = APIRouter()
@ -111,11 +112,27 @@ async def list_policies(
# user_context as is. # user_context as is.
LOG.debug("Keystone is not reachable. No privilege to access system scope.") LOG.debug("Keystone is not reachable. No privilege to access system scope.")
target = _generate_target(profile) target = _generate_target(profile)
results = []
services = constants.SUPPORTED_SERVICE_EPS.keys()
for service in services:
try:
enforcer = ENFORCER[service]
result = [ result = [
{"rule": rule, "allowed": ENFORCER.authorize(rule, target, user_context)} {
for rule in ENFORCER.rules "rule": f"{service}:{rule}",
"allowed": enforcer.authorize(rule, target, user_context),
}
for rule in enforcer.rules
] ]
return schemas.Policies(**{"policies": result}) results.extend(result)
except Exception:
msg = "An error occurred when calling %(service)s enforcer." % {
"service": str(service)
}
LOG.warning(msg)
return schemas.Policies(**{"policies": results})
@router.post( @router.post(
@ -159,10 +176,14 @@ async def check_policies(
target = _generate_target(profile) target = _generate_target(profile)
target.update(policy_rules.target if policy_rules.target else {}) target.update(policy_rules.target if policy_rules.target else {})
try: try:
result = [ result = []
{"rule": rule, "allowed": ENFORCER.authorize(rule, target, user_context)} for policy_rule in policy_rules.rules:
for rule in policy_rules.rules service = policy_rule.split(":", 1)[0]
] rule = policy_rule.split(":", 1)[1]
enforcer = ENFORCER[service]
result.append(
{"rule": policy_rule, "allowed": enforcer.authorize(rule, target, user_context)}
)
except Exception as e: except Exception as e:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,

View File

@ -125,6 +125,21 @@ cafile = Opt(
default="", default="",
) )
policy_file_suffix = Opt(
name="policy_file_suffix",
description="policy file suffix",
schema=StrictStr,
default="policy.yaml",
)
policy_file_path = Opt(
name="policy_file_path",
description="A path to policy file",
schema=StrictStr,
default="/etc/skyline/policy",
)
GROUP_NAME = __name__.split(".")[-1] GROUP_NAME = __name__.split(".")[-1]
ALL_OPTS = ( ALL_OPTS = (
debug, debug,
@ -142,6 +157,8 @@ ALL_OPTS = (
prometheus_enable_basic_auth, prometheus_enable_basic_auth,
prometheus_basic_auth_user, prometheus_basic_auth_user,
prometheus_basic_auth_password, prometheus_basic_auth_password,
policy_file_suffix,
policy_file_path,
) )
__all__ = ("GROUP_NAME", "ALL_OPTS") __all__ = ("GROUP_NAME", "ALL_OPTS")

View File

@ -19,27 +19,20 @@ from oslo_policy import _parser
from .base import Enforcer, UserContext from .base import Enforcer, UserContext
from .manager import get_service_rules from .manager import get_service_rules
ENFORCER = Enforcer() ENFORCER = {}
def setup() -> None: def setup() -> None:
service_rules = get_service_rules() service_rules = get_service_rules()
all_api_rules = []
for service, rules in service_rules.items(): for service, rules in service_rules.items():
api_rules = [] api_rules = []
for rule in rules: for rule in rules:
# Update rule name with prefix service.
rule.name = f"{service}:{rule.name}"
# Update check
rule.check_str = rule.check_str.replace("rule:", f"rule:{service}:")
rule.check = _parser.parse_rule(rule.check_str) rule.check = _parser.parse_rule(rule.check_str)
# Update basic check
rule.basic_check_str = rule.basic_check_str.replace("rule:", f"rule:{service}:")
rule.basic_check = _parser.parse_rule(rule.basic_check_str) rule.basic_check = _parser.parse_rule(rule.basic_check_str)
api_rules.append(rule) api_rules.append(rule)
all_api_rules.extend(api_rules) enforcer = Enforcer(service=service)
enforcer.register_rules(api_rules)
ENFORCER.register_rules(all_api_rules) ENFORCER[service] = enforcer
__all__ = ( __all__ = (

View File

@ -15,14 +15,16 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import MutableMapping from collections.abc import MutableMapping
from pathlib import Path
from typing import Any, Dict, Iterator, List, Union from typing import Any, Dict, Iterator, List, Union
import attr import attr
from immutables import Map from immutables import Map
from keystoneauth1.access.access import AccessInfoV3 from keystoneauth1.access.access import AccessInfoV3
from oslo_policy._checks import _check from oslo_policy import _cache_handler, _checks, policy
from skyline_apiserver.config import CONF from skyline_apiserver.config import CONF
from skyline_apiserver.log import LOG
from .manager.base import APIRule, Rule from .manager.base import APIRule, Rule
@ -94,7 +96,23 @@ class UserContext(MutableMapping):
@attr.s(kw_only=True, repr=True, frozen=False, slots=True, auto_attribs=True) @attr.s(kw_only=True, repr=True, frozen=False, slots=True, auto_attribs=True)
class Enforcer: class Enforcer:
service: str = attr.ib(repr=True, init=True)
rules: Map = attr.ib(factory=Map, repr=True, init=False) rules: Map = attr.ib(factory=Map, repr=True, init=False)
file_rules: Dict[str, Any] = attr.ib(default={}, repr=True, init=True)
_file_cache: Dict[str, Any] = attr.ib(default={}, repr=True, init=True)
def load_rules(self) -> None:
path = Path(CONF.default.policy_file_path).joinpath(
str(self.service + "_" + CONF.default.policy_file_suffix)
)
if path.exists():
reloaded, data = _cache_handler.read_cached_file(
self._file_cache, path, force_reload=False
)
if reloaded or not self.file_rules:
self.file_rules = policy.Rules.load(data)
else:
self.file_rules = {}
def register_rules(self, rules: List[Union[Rule, APIRule]]) -> None: def register_rules(self, rules: List[Union[Rule, APIRule]]) -> None:
rule_map = {} rule_map = {}
@ -107,16 +125,25 @@ class Enforcer:
self.rules = Map(rule_map) self.rules = Map(rule_map)
def authorize(self, rule: str, target: Dict[str, Any], context: UserContext) -> bool: def authorize(self, rule: str, target: Dict[str, Any], context: UserContext) -> bool:
result = False try:
do_check = self.rules.get(rule) self.load_rules()
if do_check is None: except Exception:
raise ValueError(f"Policy {rule} not registered.") LOG.debug(f"Failed to load {self.service} rules.")
result = _check( do_check = self.file_rules.get(rule) or self.rules.get(rule)
if do_check is None:
LOG.debug(f"Policy {rule} not registered.")
return False
try:
result = _checks._check(
rule=do_check, rule=do_check,
target=target, target=target,
creds=context, creds=context,
enforcer=self, enforcer=self,
current_rule=rule, current_rule=rule,
) )
except Exception:
result = False
return result return result