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.log import LOG
from skyline_apiserver.policy import ENFORCER, UserContext
from skyline_apiserver.types import constants
router = APIRouter()
@ -111,11 +112,27 @@ async def list_policies(
# user_context as is.
LOG.debug("Keystone is not reachable. No privilege to access system scope.")
target = _generate_target(profile)
result = [
{"rule": rule, "allowed": ENFORCER.authorize(rule, target, user_context)}
for rule in ENFORCER.rules
]
return schemas.Policies(**{"policies": result})
results = []
services = constants.SUPPORTED_SERVICE_EPS.keys()
for service in services:
try:
enforcer = ENFORCER[service]
result = [
{
"rule": f"{service}:{rule}",
"allowed": enforcer.authorize(rule, target, user_context),
}
for rule in enforcer.rules
]
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(
@ -159,10 +176,14 @@ async def check_policies(
target = _generate_target(profile)
target.update(policy_rules.target if policy_rules.target else {})
try:
result = [
{"rule": rule, "allowed": ENFORCER.authorize(rule, target, user_context)}
for rule in policy_rules.rules
]
result = []
for policy_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:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,

View File

@ -125,6 +125,21 @@ cafile = Opt(
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]
ALL_OPTS = (
debug,
@ -142,6 +157,8 @@ ALL_OPTS = (
prometheus_enable_basic_auth,
prometheus_basic_auth_user,
prometheus_basic_auth_password,
policy_file_suffix,
policy_file_path,
)
__all__ = ("GROUP_NAME", "ALL_OPTS")

View File

@ -19,27 +19,20 @@ from oslo_policy import _parser
from .base import Enforcer, UserContext
from .manager import get_service_rules
ENFORCER = Enforcer()
ENFORCER = {}
def setup() -> None:
service_rules = get_service_rules()
all_api_rules = []
for service, rules in service_rules.items():
api_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)
# 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)
api_rules.append(rule)
all_api_rules.extend(api_rules)
ENFORCER.register_rules(all_api_rules)
enforcer = Enforcer(service=service)
enforcer.register_rules(api_rules)
ENFORCER[service] = enforcer
__all__ = (

View File

@ -15,14 +15,16 @@
from __future__ import annotations
from collections.abc import MutableMapping
from pathlib import Path
from typing import Any, Dict, Iterator, List, Union
import attr
from immutables import Map
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.log import LOG
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)
class Enforcer:
service: str = attr.ib(repr=True, init=True)
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:
rule_map = {}
@ -107,16 +125,25 @@ class Enforcer:
self.rules = Map(rule_map)
def authorize(self, rule: str, target: Dict[str, Any], context: UserContext) -> bool:
result = False
do_check = self.rules.get(rule)
if do_check is None:
raise ValueError(f"Policy {rule} not registered.")
try:
self.load_rules()
except Exception:
LOG.debug(f"Failed to load {self.service} rules.")
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,
target=target,
creds=context,
enforcer=self,
current_rule=rule,
)
except Exception:
result = False
result = _check(
rule=do_check,
target=target,
creds=context,
enforcer=self,
current_rule=rule,
)
return result