diff --git a/docs/api/swagger.json b/docs/api/swagger.json index 3b4bf90..e5ce3e5 100644 --- a/docs/api/swagger.json +++ b/docs/api/swagger.json @@ -2207,6 +2207,14 @@ "type": "string" }, "description": "Policies rules list" + }, + "target": { + "title": "Target", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Policies targets" } } }, diff --git a/requirements.txt b/requirements.txt index d8e3d51..84c3fce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,10 +23,10 @@ click>=7.1.2,<=8.1.3 # BSD License (3 clause) jinja2>=2.11.3,<=3.1.2 # BSD License (3 clause) h11<0.13,>=0.11 # MIT MarkupSafe>=2.0.1,<=2.1.1 # BSD License (3 clause) -python-keystoneclient>=3.21.0,<=4.5.0 # Apache-2.0 -python-cinderclient>=5.0.2,<=8.3.0 # Apache-2.0 +python-keystoneclient>=3.21.0 # Apache-2.0 +python-cinderclient>=5.0.2 # Apache-2.0 python-glanceclient>=2.17.1 # Apache-2.0 -python-neutronclient>=6.14.1,<=7.8.0 # Apache-2.0 -python-novaclient>=15.1.1,<=18.0.0 # Apache-2.0 -keystoneauth1>=3.17.4,<=4.6.0 # Apache-2.0 +python-neutronclient>=6.14.1 # Apache-2.0 +python-novaclient>=15.1.1 # Apache-2.0 +keystoneauth1>=3.17.4 # Apache-2.0 oslo.policy>=2.3.4 # Apache-2.0 diff --git a/skyline_apiserver/api/v1/policy.py b/skyline_apiserver/api/v1/policy.py index 292ed68..eddec0d 100644 --- a/skyline_apiserver/api/v1/policy.py +++ b/skyline_apiserver/api/v1/policy.py @@ -17,10 +17,12 @@ from __future__ import annotations from typing import Dict from fastapi import APIRouter, Depends, HTTPException, status +from keystoneauth1.exceptions.http import Unauthorized as KeystoneUnauthorized from skyline_apiserver import schemas from skyline_apiserver.api import deps -from skyline_apiserver.client.utils import generate_session, get_access +from skyline_apiserver.client.utils import generate_session, get_access, get_system_scope_access +from skyline_apiserver.log import LOG from skyline_apiserver.policy import ENFORCER, UserContext router = APIRouter() @@ -75,10 +77,24 @@ def _generate_target(profile: schemas.Profile) -> Dict[str, str]: ) async def list_policies( profile: schemas.Profile = Depends(deps.get_profile_update_jwt), -): +) -> schemas.Policies: session = await generate_session(profile) access = await get_access(session) user_context = UserContext(access) + try: + system_scope_access = await get_system_scope_access( + profile.keystone_token, profile.region + ) + user_context["system_scope"] = ( + "all" + if getattr(system_scope_access, "system") + and getattr(system_scope_access, "system", {}).get("all", False) + else user_context["system_scope"] + ) + except KeystoneUnauthorized: + # User is not authorized to access the system scope. So just ignore the + # exception and use the user_context as is. + LOG.debug("Keystone token is invalid. No privilege to access system scope.") target = _generate_target(profile) result = [ {"rule": rule, "allowed": ENFORCER.authorize(rule, target, user_context)} @@ -103,11 +119,26 @@ async def list_policies( async def check_policies( policy_rules: schemas.PoliciesRules, profile: schemas.Profile = Depends(deps.get_profile_update_jwt), -): +) -> schemas.Policies: session = await generate_session(profile) access = await get_access(session) user_context = UserContext(access) + try: + system_scope_access = await get_system_scope_access( + profile.keystone_token, profile.region + ) + user_context["system_scope"] = ( + "all" + if getattr(system_scope_access, "system") + and getattr(system_scope_access, "system", {}).get("all", False) + else user_context["system_scope"] + ) + except KeystoneUnauthorized: + # User is not authorized to access the system scope. So just ignore the + # exception and use the user_context as is. + LOG.debug("Keystone token is invalid. No privilege to access system scope.") 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)} diff --git a/skyline_apiserver/client/utils.py b/skyline_apiserver/client/utils.py index f25a7c9..3a3b74d 100644 --- a/skyline_apiserver/client/utils.py +++ b/skyline_apiserver/client/utils.py @@ -69,6 +69,13 @@ def get_system_session() -> Session: return SESSION +async def get_system_scope_access(keystone_token: str, region: str) -> AccessInfoV3: + auth_url = await get_endpoint(region, "keystone", get_system_session()) + scope_auth = Token(auth_url, keystone_token, system_scope="all") + session = Session(auth=scope_auth, verify=False, timeout=constants.DEFAULT_TIMEOUT) + return await run_in_threadpool(session.auth.get_auth_ref, session) + + async def get_access(session: Session) -> AccessInfoV3: auth = session.auth if auth._needs_reauthenticate(): diff --git a/skyline_apiserver/policy/base.py b/skyline_apiserver/policy/base.py index be9de8e..dc11718 100644 --- a/skyline_apiserver/policy/base.py +++ b/skyline_apiserver/policy/base.py @@ -45,7 +45,12 @@ class UserContext(MutableMapping): self._data.setdefault("domain_name", getattr(access, "domain_name", None)) self._data.setdefault("user_domain_name", getattr(access, "user_domain_name", None)) self._data.setdefault("project_domain_name", getattr(access, "project_domain_name", None)) - self._data.setdefault("system_scope", getattr(access, "system_scope", None)) + self._data.setdefault( + "system_scope", + "all" + if getattr(access, "system") and getattr(access, "system", {}).get("all", False) + else "", + ) self._data.setdefault("role_ids", getattr(access, "role_ids", [])) self._data.setdefault("roles", getattr(access, "role_names", [])) diff --git a/skyline_apiserver/schemas/policy.py b/skyline_apiserver/schemas/policy.py index 3aaa0a4..f971a12 100644 --- a/skyline_apiserver/schemas/policy.py +++ b/skyline_apiserver/schemas/policy.py @@ -14,7 +14,7 @@ from __future__ import annotations -from typing import List +from typing import Dict, List, Optional from pydantic import BaseModel, Field @@ -30,3 +30,4 @@ class Policies(BaseModel): class PoliciesRules(BaseModel): rules: List[str] = Field(..., description="Policies rules list") + target: Optional[Dict[str, str]] = Field(None, description="Policies targets") diff --git a/test-requirements.txt b/test-requirements.txt index 384ed8a..f1187a1 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -18,4 +18,4 @@ mimesis<=4.1.3 # MIT asgi-lifespan<=1.0.1 # MIT types-PyYAML<=5.4.10 # Apache-2.0 oslo.log<=5.0.0 # Apache-2.0 -neutron-lib>=2.15.0,<=2.21.0 # Apache-2.0 +neutron-lib>=2.15.0 # Apache-2.0