Add configurable policy files
Configurable policy files are added for services. Change-Id: I53549fe80798bdd2e6ed3f92f548ed16393a269f
This commit is contained in:
parent
cdfe0330d3
commit
1e4f72ca2b
@ -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.
|
@ -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,
|
||||||
|
@ -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")
|
||||||
|
@ -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__ = (
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user