policy(minor): fix deadlock issue
This commit is contained in:
parent
e59c4ec1c7
commit
4c6f562805
|
@ -1,14 +1,14 @@
|
||||||
"""passbook policy engine"""
|
"""passbook policy engine"""
|
||||||
from multiprocessing import Pipe
|
from multiprocessing import Pipe
|
||||||
from multiprocessing.connection import Connection
|
from multiprocessing.connection import Connection
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple, Dict
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import Policy, User
|
from passbook.core.models import Policy, User
|
||||||
from passbook.policy.struct import PolicyRequest
|
from passbook.policy.struct import PolicyRequest, PolicyResult
|
||||||
from passbook.policy.process import PolicyProcess
|
from passbook.policy.process import PolicyProcess
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
@ -16,6 +16,18 @@ LOGGER = get_logger()
|
||||||
def _cache_key(policy, user):
|
def _cache_key(policy, user):
|
||||||
return f"policy_{policy.pk}#{user.pk}"
|
return f"policy_{policy.pk}#{user.pk}"
|
||||||
|
|
||||||
|
class PolicyProcessInfo:
|
||||||
|
|
||||||
|
process: PolicyProcess
|
||||||
|
connection: Connection
|
||||||
|
result: PolicyResult = None
|
||||||
|
policy: Policy
|
||||||
|
|
||||||
|
def __init__(self, process: PolicyProcess, connection: Connection, policy: Policy):
|
||||||
|
self.process = process
|
||||||
|
self.connection = connection
|
||||||
|
self.policy = policy
|
||||||
|
|
||||||
class PolicyEngine:
|
class PolicyEngine:
|
||||||
"""Orchestrate policy checking, launch tasks and return result"""
|
"""Orchestrate policy checking, launch tasks and return result"""
|
||||||
|
|
||||||
|
@ -23,12 +35,13 @@ class PolicyEngine:
|
||||||
__request: HttpRequest
|
__request: HttpRequest
|
||||||
__user: User
|
__user: User
|
||||||
|
|
||||||
__proc_list: List[Tuple[Connection, PolicyProcess]] = []
|
__processes: List[PolicyProcessInfo] = []
|
||||||
|
|
||||||
def __init__(self, policies, user: User = None, request: HttpRequest = None):
|
def __init__(self, policies, user: User = None, request: HttpRequest = None):
|
||||||
self.policies = policies
|
self.policies = policies
|
||||||
self.__request = request
|
self.__request = request
|
||||||
self.__user = user
|
self.__user = user
|
||||||
|
self.__processes = []
|
||||||
|
|
||||||
def for_user(self, user: User) -> 'PolicyEngine':
|
def for_user(self, user: User) -> 'PolicyEngine':
|
||||||
"""Check policies for user"""
|
"""Check policies for user"""
|
||||||
|
@ -57,36 +70,35 @@ class PolicyEngine:
|
||||||
for policy in self._select_subclasses():
|
for policy in self._select_subclasses():
|
||||||
cached_policy = cache.get(_cache_key(policy, self.__user), None)
|
cached_policy = cache.get(_cache_key(policy, self.__user), None)
|
||||||
if cached_policy:
|
if cached_policy:
|
||||||
LOGGER.debug("Taking result from cache", policy=policy.pk.hex)
|
LOGGER.debug("Taking result from cache", policy=policy)
|
||||||
cached_policies.append(cached_policy)
|
cached_policies.append(cached_policy)
|
||||||
else:
|
else:
|
||||||
LOGGER.debug("Evaluating policy", policy=policy.pk.hex)
|
LOGGER.debug("Evaluating policy", policy=policy)
|
||||||
our_end, task_end = Pipe(False)
|
our_end, task_end = Pipe(False)
|
||||||
task = PolicyProcess()
|
task = PolicyProcess(policy, request, task_end)
|
||||||
task.ret = task_end
|
LOGGER.debug("Starting Process", for_policy=policy)
|
||||||
task.request = request
|
|
||||||
task.policy = policy
|
|
||||||
LOGGER.debug("Starting Process", class_name=task.__class__.__name__)
|
|
||||||
task.start()
|
task.start()
|
||||||
self.__proc_list.append((our_end, task))
|
self.__processes.append(PolicyProcessInfo(process=task,
|
||||||
|
connection=our_end, policy=policy))
|
||||||
# If all policies are cached, we have an empty list here.
|
# If all policies are cached, we have an empty list here.
|
||||||
if self.__proc_list:
|
for proc_info in self.__processes:
|
||||||
for _, running_proc in self.__proc_list:
|
proc_info.process.join(proc_info.policy.timeout)
|
||||||
running_proc.join()
|
# Only call .recv() if no result is saved, otherwise we just deadlock here
|
||||||
|
if not proc_info.result:
|
||||||
|
proc_info.result = proc_info.connection.recv()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def result(self) -> Tuple[bool, List[str]]:
|
def result(self) -> Tuple[bool, List[str]]:
|
||||||
"""Get policy-checking result"""
|
"""Get policy-checking result"""
|
||||||
messages: List[str] = []
|
messages: List[str] = []
|
||||||
for our_end, _ in self.__proc_list:
|
for proc_info in self.__processes:
|
||||||
policy_result = our_end.recv()
|
|
||||||
# passing = (policy_action == Policy.ACTION_ALLOW and policy_result) or \
|
# passing = (policy_action == Policy.ACTION_ALLOW and policy_result) or \
|
||||||
# (policy_action == Policy.ACTION_DENY and not policy_result)
|
# (policy_action == Policy.ACTION_DENY and not policy_result)
|
||||||
LOGGER.debug('Result=%r => %r', policy_result, policy_result.passing)
|
LOGGER.debug("Result", passing=proc_info.result.passing)
|
||||||
if policy_result.messages:
|
if proc_info.result.messages:
|
||||||
messages += policy_result.messages
|
messages += proc_info.result.messages
|
||||||
if not policy_result.passing:
|
if not proc_info.result.passing:
|
||||||
return False, messages
|
return False, messages
|
||||||
return True, messages
|
return True, messages
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,16 @@ def _cache_key(policy, user):
|
||||||
class PolicyProcess(Process):
|
class PolicyProcess(Process):
|
||||||
"""Evaluate a single policy within a seprate process"""
|
"""Evaluate a single policy within a seprate process"""
|
||||||
|
|
||||||
ret: Connection
|
connection: Connection
|
||||||
policy: Policy
|
policy: Policy
|
||||||
request: PolicyRequest
|
request: PolicyRequest
|
||||||
|
|
||||||
|
def __init__(self, policy: Policy, request: PolicyRequest, connection: Connection):
|
||||||
|
super().__init__()
|
||||||
|
self.policy = policy
|
||||||
|
self.request = request
|
||||||
|
self.connection = connection
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Task wrapper to run policy checking"""
|
"""Task wrapper to run policy checking"""
|
||||||
LOGGER.debug("Running policy", policy=self.policy,
|
LOGGER.debug("Running policy", policy=self.policy,
|
||||||
|
@ -38,5 +44,4 @@ class PolicyProcess(Process):
|
||||||
# cache_key = _cache_key(self.policy, self.request.user)
|
# cache_key = _cache_key(self.policy, self.request.user)
|
||||||
# cache.set(cache_key, (self.policy.action, policy_result, message))
|
# cache.set(cache_key, (self.policy.action, policy_result, message))
|
||||||
# LOGGER.debug("Cached entry as %s", cache_key)
|
# LOGGER.debug("Cached entry as %s", cache_key)
|
||||||
self.ret.send(policy_result)
|
self.connection.send(policy_result)
|
||||||
self.ret.close()
|
|
||||||
|
|
Reference in New Issue