Skip to content

Commit 115e367

Browse files
docs: TLDR de tenant-isolation comments en docstrings
1 parent 9949629 commit 115e367

5 files changed

Lines changed: 20 additions & 84 deletions

File tree

operations-manager/python/opi/core/git_monitor.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,7 @@ async def check_and_create_namespaces(project_data: dict[str, Any]) -> bool:
3939
"""
4040
kubectl = create_kubectl_connector()
4141

42-
# Tenant-isolation guard: this path reads the project file directly from
43-
# git, bypassing ProjectManager.get_deployments. Without the same pin a
44-
# tenant who commits a project file with another tenant's namespace would
45-
# make OPI create and label that victim namespace. Pin (and fail closed)
46-
# here too, using the shared helper so both paths stay in lockstep.
42+
# Bypasses ProjectManager.get_deployments, so pin here too.
4743
enforce_namespace_pin(project_data)
4844

4945
project_name = project_data.get("name")
@@ -153,10 +149,7 @@ async def file_change_handler(file_path: str, content: dict) -> None:
153149
try:
154150
namespace_success = await check_and_create_namespaces(content)
155151
except ValueError as exc:
156-
# Tenant-isolation guard rejected a project file that declared
157-
# a namespace not equal to its project name. Fail closed: do
158-
# not create or label anything, and do not crash the polling
159-
# loop - just log the rejected cross-tenant attempt.
152+
# Mismatched namespace; skip this project, keep polling loop alive.
160153
logger.error(f"Rejected project '{project_name}' from git monitor: {exc}")
161154
return
162155

operations-manager/python/opi/core/simple_background.py

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -290,22 +290,14 @@ async def process_project_yaml_background(
290290
deployment_name: str | None = None,
291291
is_new_project: bool = False,
292292
) -> None:
293-
"""Background task that creates a project from pre-built YAML content.
293+
"""Background task that creates a project from pre-built wizard YAML.
294294
295-
Unlike ``process_project_background`` which takes a
296-
``SelfServiceProjectRequest`` and generates YAML, this function
297-
receives the final YAML string directly (produced by the wizard
298-
with editables and generators).
299-
300-
The remaining pipeline is identical: git commit, ProjectManager
301-
deployment, ArgoCD monitoring, and progress tracking.
295+
Same pipeline as ``process_project_background``.
302296
303297
Args:
304-
is_new_project: True when called from the create flow (wizard
305-
"new project"). When True a project file with this name must
306-
not already exist - overwriting it would let one tenant take
307-
over another tenant's project. The update/edit flows pass
308-
False because they intentionally rewrite an existing file.
298+
is_new_project: True for the create flow only. When True the file
299+
must not already exist (or one tenant overwrites another).
300+
Update/edit flows pass False.
309301
"""
310302
try:
311303
logger.info(f"Background task {task_id} starting for project: {project_name} (from wizard YAML)")
@@ -341,12 +333,7 @@ async def process_project_yaml_background(
341333
project_file_path = f"projects/{project_name}.yaml"
342334
commit_message = f"Create project {project_name}"
343335

344-
# Tenant-isolation guard: on the create flow a project file with
345-
# this name must not already exist. Without this check a tenant
346-
# could submit the wizard with another tenant's project name and
347-
# silently overwrite their project file, flipping ownership on
348-
# the next reload. The update/edit flows pass is_new_project=False
349-
# because they legitimately rewrite an existing file.
336+
# Create flow only: existing file would let one tenant overwrite another.
350337
if is_new_project and await git_connector_for_project_files.file_exists(project_file_path):
351338
error_msg = (
352339
f"Project '{project_name}' bestaat al. "

operations-manager/python/opi/core/task_handlers_project.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,7 @@ async def handle_create_project(payload: dict, progress: Any) -> dict:
104104
project_file_path = f"projects/{project_name}.yaml"
105105
commit_message = f"Create project {project_name}"
106106

107-
# Tenant-isolation guard: this is the create path, so a project file
108-
# with this name must not already exist. Without this check a tenant
109-
# could pick another tenant's project name and silently overwrite
110-
# their project file, taking over ownership on reload. Mirrors the
111-
# guard in simple_background.process_project_background.
107+
# Create flow: bail if file exists (would overwrite another tenant's project).
112108
if await git_connector_for_project_files.file_exists(project_file_path):
113109
error_msg = (
114110
f"Project '{project_name}' bestaat al. "

operations-manager/python/opi/handlers/project_file_handler.py

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2138,36 +2138,12 @@ async def extract_deployment_helm_chart_values(
21382138
return {}
21392139

21402140
def extract_deployment_namespace(self, project_data: dict[str, Any], deployment_name: str) -> str | None:
2141-
"""
2142-
Extract the namespace for a deployment, pinned to the project name.
2143-
2144-
The project file is attacker-controlled. A deployment declaring a
2145-
namespace that doesn't match the project name would direct OPI to
2146-
operate on a victim namespace (cross-tenant isolation breach). This
2147-
function enforces the same invariant as
2148-
``project_manager.enforce_namespace_pin`` -- declared namespace must
2149-
equal project name, missing namespace defaults to the project name --
2150-
but locally and without mutating ``project_data``. It is called from
2151-
backup/restore endpoints and tasks that read project YAML directly
2152-
rather than via ``ProjectManager.get_deployments`` (which is the
2153-
canonical pinned reader).
2154-
2155-
Note: To get the actual Kubernetes namespace, use
2156-
get_prefixed_namespace(cluster, namespace) from
2157-
opi.core.cluster_config.
2158-
2159-
Args:
2160-
project_data: The parsed project data
2161-
deployment_name: Name of the deployment
2141+
"""Return the deployment namespace pinned to the project name.
21622142
2163-
Returns:
2164-
The project-name-pinned namespace if the deployment exists,
2165-
None if no deployment with that name is found.
2166-
2167-
Raises:
2168-
ValueError: If the deployment exists and declares a namespace
2169-
that does not match the project name. Same message shape as
2170-
``enforce_namespace_pin``.
2143+
Same invariant as ``project_manager.enforce_namespace_pin`` but
2144+
local and non-mutating. Used by callers that don't go through
2145+
``ProjectManager.get_deployments`` (backup/restore endpoints, tasks).
2146+
Raises ValueError on mismatch.
21712147
"""
21722148
project_name = project_data.get("name")
21732149
for deployment in project_data.get("deployments", []):

operations-manager/python/opi/manager/project_manager.py

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -129,28 +129,12 @@
129129

130130

131131
def enforce_namespace_pin(project_data: dict[str, Any]) -> None:
132-
"""Pin every deployment namespace to the project name.
133-
134-
The project file is attacker-controlled. An explicit ``namespace``
135-
pointing at another project would make OPI label and operate on a
136-
victim namespace and generate ArgoCD resources targeting it
137-
(cross-tenant isolation breach). Default the namespace when absent
138-
(the legitimate common case) and reject any explicit value that
139-
differs. We fail closed instead of silently rewriting, so a mismatch
140-
surfaces the actual intent.
141-
142-
This mutates ``project_data`` in place so every code path that reads
143-
``deployment["namespace"]`` afterwards sees the pinned value. It must
144-
be called by every entry point that turns a project file into
145-
namespace or ArgoCD actions - not only ``ProjectManager.get_deployments``
146-
but also the git-monitor path, which reads the project file directly.
147-
148-
Args:
149-
project_data: The parsed project file contents.
150-
151-
Raises:
152-
ValueError: If a deployment specifies a namespace that does not
153-
match the project name.
132+
"""Pin every deployment namespace to the project name (mutates in place).
133+
134+
Defaults a missing namespace to the project name; raises ValueError on
135+
mismatch. Must be called by every entry that turns a project file into
136+
namespace/ArgoCD actions, otherwise a tenant can label another
137+
project's namespace.
154138
"""
155139
project_name = project_data.get("name")
156140
if not project_name:

0 commit comments

Comments
 (0)