نسخهٔ وب مستقل — پلتفرم نیروی کار هوش مصنوعی
مخزن اختصاصی برنامهٔ وب؛ محتوای پوشهٔ web در ریشهٔ مخزن قرار گرفت. تاریخچهٔ پروژهٔ ربات (agy-bot) در این مخزن وجود ندارد و هیچ وابستگی runtime به آن نیست.
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
"""Platform-wide validation rules carried over from the existing infrastructure.
|
||||
|
||||
These are pure functions with no I/O, so both the application layer *and* the
|
||||
adapters can enforce them. Defence in depth: a rule that is only checked in one
|
||||
place is a rule that will eventually be bypassed.
|
||||
|
||||
Reference: ``docs/current-infra-notes.md`` §1.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
DEFAULT_ROOT_DOMAIN = "msa.artacloud.ir"
|
||||
|
||||
#: Ports that are never allocatable (system services + data stores).
|
||||
SYSTEM_RESERVED_PORTS: frozenset[int] = frozenset(
|
||||
{22, 53, 80, 443, 2019, 3000, 5432, 6379, 8008}
|
||||
)
|
||||
|
||||
#: Ports of internal platform services. A tenant preview must never proxy to
|
||||
#: these, otherwise a public hostname would expose another component.
|
||||
SYSTEM_PROTECTED_PORTS: frozenset[int] = (
|
||||
frozenset({3000, 35555, 35556, 35557}) | SYSTEM_RESERVED_PORTS
|
||||
)
|
||||
|
||||
DEFAULT_PORT_RANGE_START = 8080
|
||||
DEFAULT_PORT_RANGE_END = 8999
|
||||
|
||||
LABEL_MIN = 3
|
||||
LABEL_MAX = 40
|
||||
_LABEL_RE = re.compile(r"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$")
|
||||
_PROXY_TARGET_RE = re.compile(r"^(https?://)?(localhost|127\.0\.0\.1):(\d{1,5})$")
|
||||
|
||||
|
||||
class RuleViolation(ValueError):
|
||||
"""Raised when a request violates a platform safety rule."""
|
||||
|
||||
|
||||
def validate_subdomain_label(label: str) -> str:
|
||||
"""Normalise and validate a single DNS label.
|
||||
|
||||
Only ``a-z0-9-``, must start and end alphanumeric, 3-40 characters.
|
||||
"""
|
||||
clean = (label or "").strip().lower()
|
||||
if not (LABEL_MIN <= len(clean) <= LABEL_MAX):
|
||||
raise RuleViolation(
|
||||
f"نام زیردامنه باید بین {LABEL_MIN} تا {LABEL_MAX} کاراکتر باشد."
|
||||
)
|
||||
if not _LABEL_RE.match(clean):
|
||||
raise RuleViolation("نام زیردامنه فقط میتواند شامل حروف کوچک، عدد و خط تیره باشد.")
|
||||
return clean
|
||||
|
||||
|
||||
def build_host(label: str, root_domain: str = DEFAULT_ROOT_DOMAIN) -> str:
|
||||
return f"{validate_subdomain_label(label)}.{root_domain.strip().lower()}"
|
||||
|
||||
|
||||
def validate_managed_host(host: str, root_domain: str = DEFAULT_ROOT_DOMAIN) -> str:
|
||||
"""Validate a fully-qualified host against the managed wildcard domain.
|
||||
|
||||
Rejects anything that is not a single label under the root domain — this is
|
||||
the anti-injection check that keeps a crafted host from breaking out of a
|
||||
Caddy site block or pointing at an arbitrary host.
|
||||
"""
|
||||
clean = (host or "").strip().lower()
|
||||
suffix = f".{root_domain.strip().lower()}"
|
||||
if not clean.endswith(suffix):
|
||||
raise RuleViolation(f"دامنه باید زیرمجموعهٔ «{root_domain}» باشد.")
|
||||
label = clean[: -len(suffix)]
|
||||
if "." in label:
|
||||
raise RuleViolation("فقط یک برچسب (single label) مجاز است.")
|
||||
validate_subdomain_label(label)
|
||||
return clean
|
||||
|
||||
|
||||
def validate_proxy_target(target: str) -> tuple[str, int]:
|
||||
"""Ensure the reverse-proxy target is a loopback address with a numeric port.
|
||||
|
||||
Blocks SSRF: a tenant must not be able to proxy a public hostname to an
|
||||
internal service.
|
||||
"""
|
||||
clean = (target or "").strip()
|
||||
match = _PROXY_TARGET_RE.match(clean)
|
||||
if not match:
|
||||
raise RuleViolation(
|
||||
"مقصد reverse proxy باید localhost یا 127.0.0.1 با پورت عددی باشد."
|
||||
)
|
||||
port = int(match.group(3))
|
||||
if not (1 <= port <= 65535):
|
||||
raise RuleViolation("شمارهٔ پورت نامعتبر است.")
|
||||
return match.group(2), port
|
||||
|
||||
|
||||
def is_port_allocatable(
|
||||
port: int,
|
||||
*,
|
||||
start: int = DEFAULT_PORT_RANGE_START,
|
||||
end: int = DEFAULT_PORT_RANGE_END,
|
||||
) -> bool:
|
||||
if port in SYSTEM_RESERVED_PORTS:
|
||||
return False
|
||||
return start <= port <= end
|
||||
|
||||
|
||||
def assert_port_allocatable(
|
||||
port: int,
|
||||
*,
|
||||
start: int = DEFAULT_PORT_RANGE_START,
|
||||
end: int = DEFAULT_PORT_RANGE_END,
|
||||
) -> int:
|
||||
if not is_port_allocatable(port, start=start, end=end):
|
||||
raise RuleViolation(f"پورت {port} قابل تخصیص نیست.")
|
||||
return port
|
||||
|
||||
|
||||
def assert_not_protected_port(port: int) -> int:
|
||||
"""A preview domain must never point at an internal platform port."""
|
||||
if port in SYSTEM_PROTECTED_PORTS:
|
||||
raise RuleViolation(f"اتصال به پورت محافظتشدهٔ {port} ممنوع است.")
|
||||
return port
|
||||
Reference in New Issue
Block a user