Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
Marc 'risson' Schmitt 2023-12-27 11:51:37 +01:00
parent afc968437d
commit 18d7395e7e
No known key found for this signature in database
GPG key ID: 9C3FA22FABF1AA8D
4 changed files with 48 additions and 14 deletions

View file

@ -95,6 +95,9 @@ outposts:
discover: true discover: true
disable_embedded_outpost: false disable_embedded_outpost: false
expressions:
restricted: false
ldap: ldap:
task_timeout_hours: 2 task_timeout_hours: 2
page_size: 50 page_size: 50

View file

@ -6,15 +6,18 @@ from textwrap import indent
from typing import Any, Iterable, Optional from typing import Any, Iterable, Optional
from cachetools import TLRUCache, cached from cachetools import TLRUCache, cached
from django.apps import apps
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from guardian.shortcuts import get_anonymous_user from guardian.shortcuts import get_anonymous_user
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from RestrictedPython import compile_restricted, limited_builtins, safe_builtins, utility_builtins
from sentry_sdk.hub import Hub from sentry_sdk.hub import Hub
from sentry_sdk.tracing import Span from sentry_sdk.tracing import Span
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.models import User from authentik.core.models import User
from authentik.events.models import Event from authentik.events.models import Event
from authentik.lib.config import CONFIG
from authentik.lib.utils.http import get_http_session from authentik.lib.utils.http import get_http_session
from authentik.policies.models import Policy, PolicyBinding from authentik.policies.models import Policy, PolicyBinding
from authentik.policies.process import PolicyProcess from authentik.policies.process import PolicyProcess
@ -55,6 +58,10 @@ class BaseEvaluator:
"resolve_dns": BaseEvaluator.expr_resolve_dns, "resolve_dns": BaseEvaluator.expr_resolve_dns,
"reverse_dns": BaseEvaluator.expr_reverse_dns, "reverse_dns": BaseEvaluator.expr_reverse_dns,
} }
for app in apps.get_app_configs():
# Load models from each app
for model in app.get_models():
self._globals[model.__name__] = model
self._context = {} self._context = {}
@cached(cache=TLRUCache(maxsize=32, ttu=lambda key, value, now: now + 180)) @cached(cache=TLRUCache(maxsize=32, ttu=lambda key, value, now: now + 180))
@ -180,6 +187,18 @@ class BaseEvaluator:
full_expression += f"\nresult = handler({handler_signature})" full_expression += f"\nresult = handler({handler_signature})"
return full_expression return full_expression
def compile(self, expression: str) -> Any:
"""Parse expression. Raises SyntaxError or ValueError if the syntax is incorrect."""
param_keys = self._context.keys()
compiler = (
compile_restricted if CONFIG.get_bool("epxressions.restricted", False) else compile
)
return compiler(
self.wrap_expression(expression, param_keys),
self._filename,
"exec",
)
def evaluate(self, expression_source: str) -> Any: def evaluate(self, expression_source: str) -> Any:
"""Parse and evaluate expression. If the syntax is incorrect, a SyntaxError is raised. """Parse and evaluate expression. If the syntax is incorrect, a SyntaxError is raised.
If any exception is raised during execution, it is raised. If any exception is raised during execution, it is raised.
@ -188,17 +207,18 @@ class BaseEvaluator:
span: Span span: Span
span.description = self._filename span.description = self._filename
span.set_data("expression", expression_source) span.set_data("expression", expression_source)
param_keys = self._context.keys()
try: try:
ast_obj = compile( ast_obj = self.compile(expression_source)
self.wrap_expression(expression_source, param_keys),
self._filename,
"exec",
)
except (SyntaxError, ValueError) as exc: except (SyntaxError, ValueError) as exc:
self.handle_error(exc, expression_source) self.handle_error(exc, expression_source)
raise exc raise exc
try: try:
if CONFIG.get_bool("expressions.restricted", False):
self._globals["__builtins__"] = {
**safe_builtins,
**limited_builtins,
**utility_builtins,
}
_locals = self._context _locals = self._context
# Yes this is an exec, yes it is potentially bad. Since we limit what variables are # Yes this is an exec, yes it is potentially bad. Since we limit what variables are
# available here, and these policies can only be edited by admins, this is a risk # available here, and these policies can only be edited by admins, this is a risk
@ -221,13 +241,8 @@ class BaseEvaluator:
def validate(self, expression: str) -> bool: def validate(self, expression: str) -> bool:
"""Validate expression's syntax, raise ValidationError if Syntax is invalid""" """Validate expression's syntax, raise ValidationError if Syntax is invalid"""
param_keys = self._context.keys()
try: try:
compile( self.compile(expression)
self.wrap_expression(expression, param_keys),
self._filename,
"exec",
)
return True return True
except (ValueError, SyntaxError) as exc: except (ValueError, SyntaxError) as exc:
raise ValidationError(f"Expression Syntax Error: {str(exc)}") from exc raise ValidationError(f"Expression Syntax Error: {str(exc)}") from exc

19
poetry.lock generated
View file

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. # This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand.
[[package]] [[package]]
name = "aiohttp" name = "aiohttp"
@ -3279,6 +3279,21 @@ requests = ">=2.0.0"
[package.extras] [package.extras]
rsa = ["oauthlib[signedtoken] (>=3.0.0)"] rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
[[package]]
name = "restrictedpython"
version = "7.0"
description = "RestrictedPython is a defined subset of the Python language which allows to provide a program input into a trusted environment."
optional = false
python-versions = ">=3.7, <3.13"
files = [
{file = "RestrictedPython-7.0-py3-none-any.whl", hash = "sha256:8bb40a822090bed9c7b814d69345b0796db70cc86715d141efc937862f37c6d2"},
{file = "RestrictedPython-7.0.tar.gz", hash = "sha256:53704afbbc350fdc8fb245441367be671c9f8380869201b2e8452e74fce3db14"},
]
[package.extras]
docs = ["Sphinx", "sphinx-rtd-theme"]
test = ["pytest", "pytest-mock"]
[[package]] [[package]]
name = "rich" name = "rich"
version = "13.7.0" version = "13.7.0"
@ -4437,4 +4452,4 @@ files = [
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "~3.12" python-versions = "~3.12"
content-hash = "d0fe6ae1be389f8a5ca5112aa90555e2ce0a4f336f07a1da9c43dd521e9d9340" content-hash = "0782627c112f4cefa27fa066eb1e4c9b01882b416690f4e1348f4e61dfe02190"

View file

@ -157,6 +157,7 @@ pyjwt = "*"
python = "~3.12" python = "~3.12"
pyyaml = "*" pyyaml = "*"
requests-oauthlib = "*" requests-oauthlib = "*"
restrictedpython = "*"
sentry-sdk = "*" sentry-sdk = "*"
service_identity = "*" service_identity = "*"
structlog = "*" structlog = "*"