Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
184 commits
Select commit Hold shift + click to select a range
6e71207
WIP mt-upstream: 22 new + 18 clean-applied MT files + facade-route ra…
node5-sublime Jul 13, 2026
ba36221
mt-upstream: append [multitenancy] conf stanza
node5-sublime Jul 13, 2026
b7e15c8
mt-upstream: reconcile 4 churned view files onto master (facade-route…
node5-sublime Jul 13, 2026
a622bb6
mt-upstream: ruff-clean the MT test files (E401/E702/F401/F841)
node5-sublime Jul 13, 2026
a278471
mt-upstream: make dashboard _ut import-optional (mt_absent) + add vie…
node5-sublime Jul 13, 2026
386f5d2
mt: fix fail-open cross-tenant leaks from adversarial review
node5-sublime Jul 13, 2026
f30b5cc
mt: close reopened #1/#10 + rtid wrong-object authz (re-verify round 2)
node5-sublime Jul 13, 2026
201f6ea
mt: fail-close unsupported modes + gate direct-VNC + document boundary
node5-sublime Jul 13, 2026
b7aee49
mt: close final re-verification findings (rtid inline pivot, all dire…
node5-sublime Jul 13, 2026
6f1a6b7
mt: close final-review non-blocking items (TLP-before-regate, toggle …
node5-sublime Jul 13, 2026
b1ed79d
mt: correct stale 503 message + comments after the round-5 rollback
node5-sublime Jul 13, 2026
14d88b5
mt: address Gemini review — batch N+1 visibility + fix cache poisonin…
node5-sublime Jul 13, 2026
6116094
mt: address Codex + Copilot review — PCAP tenancy, OIDC claims, finis…
node5-sublime Jul 14, 2026
3d58790
mt: address Copilot re-review — parse task_id + atomic visibility sync
node5-sublime Jul 14, 2026
1d18e29
mt: session-auth the report visibility PATCH endpoint (Codex)
node5-sublime Jul 14, 2026
6bf4ac6
mt: set_task_visibility — sync mongo before SQL commit, revert in-pla…
node5-sublime Jul 14, 2026
e148cc4
mt: address Copilot re-review of 6bf4ac69 (visibility 400, csrf escap…
node5-sublime Jul 14, 2026
f4db397
mt: address Codex re-review — direct-VNC replay gate + inactive-tenan…
node5-sublime Jul 14, 2026
93c69ed
mt: address Copilot re-review — coerce task ids in analysis authz dec…
node5-sublime Jul 14, 2026
9cce8e5
mt: submission_scope must not mint a tenant-visibility job without a …
node5-sublime Jul 14, 2026
38ef08e
mt: gate live-VM access on manage rights, not read visibility (Codex …
node5-sublime Jul 14, 2026
a52a881
mt: align submit-form visibility options with API + doc/docstring acc…
node5-sublime Jul 14, 2026
c36a4b1
mt: materialize compare mongo cursor + gate resubmit-by-hash original…
node5-sublime Jul 14, 2026
309da2d
mt: revert mongo stamp if the SQL commit fails after a successful syn…
node5-sublime Jul 14, 2026
6f9de56
mt: scope perform_search tags/options SQL prequery by viewer before t…
node5-sublime Jul 14, 2026
32d930e
mt: keep tenant-visibility coherent for tenantless submitters/tasks (…
node5-sublime Jul 14, 2026
35cccb1
mt: direction-aware two-store visibility ordering + type/import clean…
node5-sublime Jul 14, 2026
5435fd5
mt: don't commit on the visibility mongo-fail path + harden _g() grou…
node5-sublime Jul 14, 2026
82ad865
mt: gate the visibility toggle on enabled multitenancy (Codex P2)
node5-sublime Jul 14, 2026
7f7d661
mt: fail-closed on corrupt/non-numeric analysis ids (Copilot)
node5-sublime Jul 14, 2026
a8a726f
mt: submission_scope ignores visibility when multitenancy is off (Cod…
node5-sublime Jul 14, 2026
6f22387
mt: serialize concurrent visibility toggles + document the mongo back…
node5-sublime Jul 14, 2026
e167e85
mt: pin the visibility advisory lock to a dedicated connection (review)
node5-sublime Jul 14, 2026
c2f1c2a
mt: isolate the visibility lock connection + fail closed on acquire f…
node5-sublime Jul 14, 2026
335f11d
mt: lock engine inherits app connect_args + built with the app engine…
node5-sublime Jul 15, 2026
b13c907
mt(tests): make web MT fixtures a named plugin, not web/conftest.py (…
node5-sublime Jul 15, 2026
1312b0b
mt(apiv2): defer missing-task 404 to the caller when MT is disabled
node5-sublime Jul 15, 2026
8e76e56
mt(perf): batch per-row visibility resolution (N+1 -> one query)
node5-sublime Jul 15, 2026
e6d1a95
mt(apiv2): restore upstream missing-task response (fix MT-off 500 reg…
node5-sublime Jul 15, 2026
8b731a0
mt: make multitenancy a true no-op when disabled (default-off == upst…
node5-sublime Jul 15, 2026
9bf18c4
mt(apiv2): fix task_x_hours datetime.datetime.now() crash (default-of…
node5-sublime Jul 15, 2026
91f4fbf
mt(reporting): serialize report visibility stamp with the toggle advi…
node5-sublime Jul 15, 2026
b9df044
mt(reporting): fail-closed visibility stamp + hardened reconcile
node5-sublime Jul 15, 2026
a5a5f32
mt(central): resolve tenancy from the central RDS on the worker stamp…
node5-sublime Jul 15, 2026
32e3f7e
mt(reporting): repair orphaned/unowned mongo docs (toggle sync + back…
node5-sublime Jul 15, 2026
871418e
mt(central): key central-RDS tenancy on the rewritten (ui-*) id only …
node5-sublime Jul 15, 2026
0fb6434
mt(central): serialize the reconcile with the central toggle on the c…
node5-sublime Jul 16, 2026
aadca22
mt(central): NullPool the central engine + validate writer-primary fo…
node5-sublime Jul 16, 2026
469b142
mt(central): per-reconcile writer-primary probe + genuinely fail-clos…
node5-sublime Jul 16, 2026
234a7d4
mt(apiv2): strip shared sample source_url across tenants when MT on
node5-sublime Jul 16, 2026
dd9d1c7
mt(central): bind writer-primary verdict + tenancy read to the lock c…
node5-sublime Jul 16, 2026
830c764
mt: multitenancy_config() fails CLOSED on an unreadable config, not open
node5-sublime Jul 16, 2026
1e1be75
mt(central): report() reads its OWN authorized task unscoped (no owne…
node5-sublime Jul 16, 2026
c726e83
mt: normalize default_visibility + fail closed on local_admins_manage…
node5-sublime Jul 16, 2026
173dd04
mt: fail closed to PRIVATE for default_visibility (sentinel + unrecog…
node5-sublime Jul 17, 2026
81bc56f
mt(central): resolve central artifacts by the RDS-authorized job_id o…
node5-sublime Jul 17, 2026
3fa6fdb
mt(central): authorize the resolved job_id per-call (don't trust forg…
node5-sublime Jul 17, 2026
576d8a9
mt(central): _rds_job_id resolves int() before the DB try (no fake RD…
node5-sublime Jul 17, 2026
d4cbcea
central: web/API tolerates an unreachable rooter (management node adv…
node5-sublime Jul 17, 2026
75d7cde
central: extend rooter-tolerance to init_routing + gate on explicit o…
node5-sublime Jul 17, 2026
e3a9dbe
central: gate rooter-tolerance on the flag alone, not socket reachabi…
node5-sublime Jul 17, 2026
78747b1
mt(central): constrain the tenant_id:null auth arm to the requesting …
node5-sublime Jul 17, 2026
4c8243f
mt(migration): pass existing_type on the visibility alter_column (MyS…
node5-sublime Jul 17, 2026
fb091b0
mt: declare tenant_id index on the Task model (fresh-install schema p…
node5-sublime Jul 17, 2026
260a524
mt: fail-closed legacy backfill (private) + superuser-only tenancy ad…
node5-sublime Jul 18, 2026
66c4bd3
mt(central): propagate the _rds_job_id resilience fix to central_job_…
node5-sublime Jul 18, 2026
0f7fa42
mt(central): scope apiv2 report-family reads to the viewer (cross-sto…
node5-sublime Jul 18, 2026
fcbde50
mt(central): scope the web report-UI analysis reads to the viewer (cr…
node5-sublime Jul 18, 2026
720bf92
mt(central): scope compare + rolling-suri + zipall reads; consolidate…
node5-sublime Jul 19, 2026
033d54f
mt(central): scope the task-delete path (analysis + calls) to the cal…
node5-sublime Jul 19, 2026
5f16ecf
mt(central): scope the visibility WRITE paths to the task's own job_i…
node5-sublime Jul 19, 2026
83d45fa
mt(central): derive guac live-VM job_id from the authorized task_id, …
node5-sublime Jul 19, 2026
f3494f9
mt: import-optional tenancy facade fails CLOSED when MT is enabled bu…
node5-sublime Jul 19, 2026
80c0c90
mt(central): close the remaining write-side collisions + list-enrichm…
node5-sublime Jul 20, 2026
300fcb5
mt(central): viewer_scope fails CLOSED when MT enabled but dashboard.…
node5-sublime Jul 20, 2026
c1887d2
mt(central): reconcile stamp can never re-own an already-stamped fore…
node5-sublime Jul 20, 2026
224eb40
mt(central): fix set_task_visibility non-bridged no-op + fail-open sc…
node5-sublime Jul 20, 2026
e0ff338
mt(central): strip a client-supplied job_id from submitted custom (ro…
node5-sublime Jul 20, 2026
5edc8e3
mt(central): set_task_visibility write - constrain foreign-collision …
node5-sublime Jul 20, 2026
2bde2c0
mt(central): reconcile fallback filter tenant-guarded; document delet…
node5-sublime Jul 20, 2026
fc3d230
mt(central): viewer_can_view_sample fails closed when MT enabled
node5-sublime Jul 20, 2026
40cfeaf
mt(central): bulkzip drops memory to the policy-gated procmemory surface
node5-sublime Jul 20, 2026
6d0ef71
mt: do not bake shared-samples source_url into report.info under mult…
node5-sublime Jul 20, 2026
560c113
mt(central): tenant-guard ban_user/ban_all_user_tasks; stage EVTX art…
node5-sublime Jul 20, 2026
65c8714
mt(central): revert the custom job_id ingest strip - it corrupts the …
node5-sublime Jul 20, 2026
776ca87
mt(central): can_ban_user - close MT-off priv-esc + guard the target'…
node5-sublime Jul 20, 2026
e18906e
mt(central): set_task_visibility - match own direct-submit doc + surf…
node5-sublime Jul 20, 2026
9fc5269
mt(central): bulkzip validate-before-stage + 4xx; document [centralst…
node5-sublime Jul 20, 2026
4fd776e
mt(central): anchor resolve_job_id to the bridge filter (in-tree cont…
node5-sublime Jul 20, 2026
35e66cc
mt(central): set_task_visibility - tenant-gate the local- arm + probe…
node5-sublime Jul 20, 2026
8c86a79
mt(central): apiv2 - gate procmemory staging; validate pcap/keys/etw …
node5-sublime Jul 20, 2026
ae3e3d8
mt(central): unify + fully anchor the job_id parser (raw-prefix parit…
node5-sublime Jul 20, 2026
13806a5
mt(central): central_delete_analysis - derive the delete key from the…
node5-sublime Jul 20, 2026
5227358
mt(central): unify visibility-toggle + delete on one derived unique-k…
node5-sublime Jul 20, 2026
682e6b1
mt(central): procmemory stages per-process dumps only; hoist tasks_re…
node5-sublime Jul 20, 2026
3530e1f
mt(central): reject path-unsafe job_id at the parser (read-seam trave…
node5-sublime Jul 20, 2026
23e9e6c
mt(central): comments() keys its Mongo write on the derived own-doc f…
node5-sublime Jul 20, 2026
c2c5f27
apiv2: guard tasks_screenshot listing; validate tasks_report format +…
node5-sublime Jul 20, 2026
a3df16c
mt(central): own-doc filter = {ui-<tid> OR info.id} AND unstamped-or-…
node5-sublime Jul 20, 2026
5f66802
mt(central): central delete/comment use the read-aligned own-doc filt…
node5-sublime Jul 20, 2026
5138cb8
mt(central): guard _store_and_container against path-unsafe job_id; l…
node5-sublime Jul 20, 2026
11e3ce0
apiv2: normalize report_format case; guard tasks_report zip dir; scop…
node5-sublime Jul 20, 2026
d865bf3
mt(central): only warn on probe-shaped rejected job_id; free-text cus…
node5-sublime Jul 20, 2026
67d980d
apiv2: guard report.json siblings against missing dir; unconditional …
node5-sublime Jul 20, 2026
9156827
mt(central): comments() writes to the exact doc it read (resolve _id)…
node5-sublime Jul 20, 2026
96d009a
mt(central): own-doc filter excludes foreign UNSTAMPED info.id collis…
node5-sublime Jul 20, 2026
e91e836
mt(central): _is_safe_job_id is type-safe (isinstance) so a non-str i…
node5-sublime Jul 20, 2026
e365319
apiv2/central: delete Mongo AFTER SQL+folder delete (rollback-safe); …
node5-sublime Jul 20, 2026
f2dd4cf
mt(central): bridge-required mode -> ui-only own-doc filter (fully cl…
node5-sublime Jul 20, 2026
d0c02f6
mt(central): read own-unstamped arm keys on the unique jid when the b…
node5-sublime Jul 20, 2026
129333f
mt(central): artifact seam denies non-bridged resolution for a tenant…
node5-sublime Jul 20, 2026
fefc330
docs(central): document the bridge-required tenant-isolation boundary…
node5-sublime Jul 20, 2026
ae2c1b6
apiv2: commit the SQL delete before the irreversible Mongo delete (re…
node5-sublime Jul 20, 2026
4eb4044
mt(central): comments() atomic $push (no lost update, tenant guard re…
node5-sublime Jul 20, 2026
1a14286
mt(central): set_task_visibility import-fallback is ui-only fail-clos…
node5-sublime Jul 20, 2026
bbdb27b
mt(central): key the bare-token WARNING on traversal markers, not whi…
node5-sublime Jul 20, 2026
a076a7d
mt(central): refuse to persist a non-bridged analysis doc under multi…
node5-sublime Jul 20, 2026
d90ecca
mt(central): refuse to PERSIST an unkeyed/non-bridged analysis doc un…
node5-sublime Jul 20, 2026
028b6a3
mt(central): read seam denies a non-bridged resolution under MT too (…
node5-sublime Jul 20, 2026
25193bf
mt(central): doc/comment accuracy + test hygiene for the bridge-aware…
node5-sublime Jul 20, 2026
eb7cc00
mt(central): comments() uses DocumentDB-safe $set + logs 0-match; rem…
node5-sublime Jul 20, 2026
dd20b8c
mt(central): set_task_visibility import-failure fallback uses the {ui…
node5-sublime Jul 20, 2026
a0b06e0
apiv2: per-task try/except in the batch delete loops so a partial-app…
node5-sublime Jul 20, 2026
6c300f2
mt(central): fix central_delete docstring (remove() now passes tenant…
node5-sublime Jul 20, 2026
9d94d20
mt(central): set_task_visibility import-fallback mirrors the job_id-q…
node5-sublime Jul 20, 2026
d6b59bb
apiv2: rollback the staged SQL delete on a mid-delete failure; tasks_…
node5-sublime Jul 20, 2026
e104701
mt(central): comments() atomic DocumentDB-safe $push (fixes lost-upda…
node5-sublime Jul 20, 2026
2bc33be
test(central): restore the centralstore over-firing guard (bridge-not…
node5-sublime Jul 20, 2026
e80a365
mt(central): fix comments render (|dictsortreversed:"Timestamp", not …
node5-sublime Jul 20, 2026
9a2aa15
apiv2: commit SQL delete BEFORE the irreversible folder/Mongo deletes…
node5-sublime Jul 20, 2026
10d8e35
mt(central): document the import-failure fallback's residual + delibe…
node5-sublime Jul 20, 2026
f930a33
apiv2: separate the folder-delete and report-delete guards so a folde…
node5-sublime Jul 20, 2026
e56a056
mt(central): remove() guards central_delete separately (no 500 / lost…
node5-sublime Jul 20, 2026
3db31fe
cleaners: delete_data folder-delete moved outside the SQL begin() blo…
node5-sublime Jul 20, 2026
46634ad
mt(central): correct set_task_visibility except-arm comment (two bran…
node5-sublime Jul 20, 2026
1b742aa
apiv2: distinguish post-commit report-orphan from retryable pre-commi…
node5-sublime Jul 20, 2026
3b38da2
mt(central): remove() composes the folder + report failure messages i…
node5-sublime Jul 20, 2026
5a82727
cleaners: correct delete_data comment (report-first ordering + swallo…
node5-sublime Jul 20, 2026
121c8c0
apiv2: delete_mongo empty-string falls through to default; correct de…
node5-sublime Jul 20, 2026
7841e24
mt(central): remove() composes the folder/report failure message from…
node5-sublime Jul 20, 2026
e19adcc
apiv2: document the web_reporting gate on tasks_delete s_deleted; rej…
node5-sublime Jul 20, 2026
6f445ee
mt(central): remove() report-failure comment carries the non-central-…
node5-sublime Jul 20, 2026
6c90791
mt: gate tasks_delete_many with the [taskdelete] kill-switch + fix de…
node5-sublime Jul 21, 2026
f3e6c48
mt: treat empty delete_mongo as retain (upstream back-compat) instead…
node5-sublime Jul 21, 2026
1e6ca3d
mt: revert tasks_delete_many [taskdelete] gate (broke dist cleanup) +…
node5-sublime Jul 21, 2026
b63bc8f
mt: bulk-delete reads request.data + skip-and-reports malformed ids (…
node5-sublime Jul 21, 2026
5b41a05
mt: bulk-delete reads whole body from request.data + collision-safe i…
node5-sublime Jul 21, 2026
0cdc392
mt: bound bulk-delete id magnitude (32-bit) + drop stray django_db ma…
node5-sublime Jul 21, 2026
c90c439
mt: length-gate bulk-delete id before int() (CPython 4300-digit cap -…
node5-sublime Jul 21, 2026
63ac5bc
mt: harden the delete endpoints for production (freeze, strict ids, v…
node5-sublime Jul 21, 2026
b28f456
mt: delete-id parsing edge cases (zero-pad, repeated keys, remove() b…
node5-sublime Jul 21, 2026
31110e7
mt: dedicated can_delete authz for task deletion (public = submitter …
node5-sublime Jul 21, 2026
00b56cc
fix(web): harden task delete/search id-parsing (partial-delete + 500 …
node5-sublime Jul 21, 2026
1dffbbf
fix(mt): close can_delete toggle-bypass + gate audit delete surface +…
node5-sublime Jul 21, 2026
f213465
test(mt): close coverage gaps in the delete/visibility/audit hardenin…
node5-sublime Jul 21, 2026
20eb7fa
fix(mt): fail-closed audit-session delete gate + gate the last delete…
node5-sublime Jul 22, 2026
9d87917
fix(mt): compare-and-swap the visibility transition under the lock (c…
node5-sublime Jul 22, 2026
6674328
perf(mt): resolve the viewer once in pending() (kill the per-row view…
node5-sublime Jul 22, 2026
2664921
fix(dist): un-break worker cleanup — drop the delete_many freeze + fi…
node5-sublime Jul 22, 2026
c6c1452
fix(dist): bound the cleaner delete POST + guarantee pidfile removal …
node5-sublime Jul 22, 2026
62e6306
fix(dist): bound the cleaner re-queue + make the pidfile lock signal-…
node5-sublime Jul 22, 2026
70aedb7
fix(dist): flock the cleaner single-instance lock (ends the stale/rec…
node5-sublime Jul 22, 2026
d082245
fix(dist): don't unlink the flock lockfile (closes the inode-vs-path …
node5-sublime Jul 22, 2026
b00d295
fix(mt): pin info.id on the bridge-required unstamped read arm (close…
node5-sublime Jul 22, 2026
c84ae02
fix(dist): move the cleaner lock out of world-writable /tmp + return …
node5-sublime Jul 22, 2026
673af86
fix(dist): differentiate cleaner-lock EACCES hint when the lockfile i…
node5-sublime Jul 22, 2026
9a1d533
feat(central-guac): staff-gated tasks/machine endpoint for live-VM lo…
node5-sublime Jul 23, 2026
cccd059
fix(analysis): drop underscore-leading template vars that 500 the rep…
node5-sublime Jul 23, 2026
186f745
fix(central-guac): match tasks/machine gate to apiv2 auth posture (Al…
node5-sublime Jul 23, 2026
6aced0e
fix(central-guac): gate tasks/machine on is_local_admin, not is_staff…
node5-sublime Jul 23, 2026
83c5e39
test(analysis): walk the analysis/ template subtree in the '_'-var pa…
node5-sublime Jul 23, 2026
9c89221
ci: run the web/ Django suites (importlib) so MT-isolation tests gate…
node5-sublime Jul 23, 2026
4ad140f
fix(central-guac): unconditional is_local_admin gate on tasks/machine…
node5-sublime Jul 23, 2026
26599bc
feat(central-guac): shared-secret auth path for tasks/machine (intera…
node5-sublime Jul 23, 2026
f155252
fix(guac): define WEB_AUTHENTICATION in guac_settings so /guac/ doesn…
node5-sublime Jul 23, 2026
71cf99b
fix(central-guac): review #16 follow-ups — shared-secret docs, IPv6 n…
node5-sublime Jul 23, 2026
49a8e99
fix(guac): guac-web must share the central auth/session store so requ…
node5-sublime Jul 23, 2026
7ce833a
fix(guac): self-contained error/wait console pages (no main-web heade…
node5-sublime Jul 23, 2026
be119cb
fix(guac): drop invalid multi-line {# #} comment from error/wait temp…
node5-sublime Jul 24, 2026
bea99b9
fix(guac): resolve the interactive VNC port on the worker (apiv2), no…
node5-sublime Jul 24, 2026
e472498
fix(guac): resolve websocket user backend-agnostically (OIDC interact…
node5-sublime Jul 24, 2026
a450f46
fix(suricata): fail fast on suricata socket EOF/crash instead of hang…
node5-sublime Jul 26, 2026
9cedfcb
fix(test): drop unused pytest import in test_suricatasc_recv (ruff F401)
node5-sublime Jul 26, 2026
300a395
Merge pull request #3112 from wmetcalf/feat/mt-upstream
kevoreilly Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ jobs:
- name: Run unit tests
run: poetry run python -m pytest --import-mode=append

# The web/ Django suites (tenant-isolation, apiv2, guac, submission, ...) live
# outside testpaths=["tests","agent"], so the arg-less run above never collects
# them. They require importlib (append raises ImportPathMismatchError under
# pythonpath=web); DJANGO_SETTINGS_MODULE + pythonpath come from pyproject's
# [tool.pytest.ini_options] and still apply with an explicit path arg.
- name: Run web unit tests
run: poetry run python -m pytest web/ --import-mode=importlib

- name: Run Linux analyzer unit tests
run: poetry --project . --directory "analyzer/linux" run python -m pytest -v

Expand Down
10 changes: 10 additions & 0 deletions conf/default/api.conf.default
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ url = http://example.tld
token_auth_enabled = no
mcp = no

# Central-mode interactive live-VM (Guacamole) machine-to-machine auth. The central node
# (lib/cuckoo/common/central_guac) presents this shared secret as "Authorization: Token <v>"
# to the worker's apiv2 tasks/machine endpoint; that endpoint authorizes its (non-tenant-scoped,
# VM-label-ONLY) read via a constant-time compare against this value, so interactive attach works
# even when token_auth_enabled=no (DRF AllowAny leaves the request anonymous). EMPTY (the default)
# = DISABLED: the endpoint then authorizes only via the viewer_for().is_local_admin gate. This is
# a bearer secret -- keep apiv2 SG-locked to the control plane and the conf file non-world-readable;
# rotate on suspicion. Leak scope is a pool VM label, never tenant/analysis data.
control_plane_token =

[list_exitnodes]
enabled = no
mcp = no
Expand Down
55 changes: 54 additions & 1 deletion conf/default/cuckoo.conf.default
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,13 @@ unused_files_in_mongodb = no
# Central control-plane mode (off = current single-node behavior; analyses stay on the local
# storage/analyses tree). When on, analysis artifacts are pushed to a shared object store and
# interactive Guac routing resolves job->worker via a directory backend.
#
# TENANT ISOLATION: when central mode AND multitenancy are BOTH enabled, tenant-isolated access
# requires the submit-bridge -- only a bridged task (job_id "ui-<central_id>", globally unique and
# reconcile-mapped to a tenant) is a tenant-scoped own doc. A direct worker submission (keyed
# "local-<id>", worker-local info.id, no tenancy by construction) is SINGLE-TENANT only: it is not
# addressable through the tenant-scoped own-doc read/write/delete paths. See
# lib.cuckoo.common.central_mode.central_bridge_required.
enabled = no
# Artifact storage backend when central mode is ON: "s3" (any S3-compatible store — AWS S3, MinIO,
# Ceph RGW — via boto3, which is an optional/lazy import: pip install boto3 to use it) or "local"
Expand All @@ -276,10 +283,56 @@ broker_api_token =
broker_table =
# Interactive-Guac worker access: how the central node reaches the worker that hosts a live task's
# VM. Defaults match the standard CAPE deb layout; override for other topologies.
# File holding the worker apiv2 Token (blank/absent => requests go unauthenticated).
# File holding the token the central node presents to the worker's apiv2 for the interactive
# live-VM lookup (apiv2 tasks/machine). The worker authorizes that call by matching this value
# (constant-time) against its own [api] control_plane_token, so this MUST equal the worker's
# control_plane_token (in the AWS deploy: the same Secrets Manager value on both tiers). That
# shared-secret path works on the shipped token_auth_enabled=no worker, where DRF leaves the
# request anonymous. (On a token_auth_enabled=yes worker the endpoint ALSO accepts an
# is_local_admin principal's token — but do NOT flip local_admins_manage_all_tenants=yes just
# for this: that widens cross-tenant break-glass fleet-wide. Use an IdP-linked superuser
# instead if you go that route.) Blank/absent => requests go unauthenticated.
worker_api_token_file = /etc/cape/api-token
# The worker's apiv2/web port.
worker_api_port = 8000
# The central node's libvirt-over-SSH identity onto workers (must be authorized on the workers).
worker_ssh_user = cape
worker_ssh_keyfile = /home/cape/.ssh/id_ed25519
# SQLAlchemy URL of the CENTRAL control-plane RDS, used by WORKERS only to resolve a central task's
# tenancy (tenant_id/user_id/visibility) when stamping the shared DocumentDB analysis doc under
# multitenancy. A worker's own [database] is its LOCAL per-worker task DB (different id space) and
# centralstore rewrites info.id to the central task id, so the stamp must be resolved here. Blank
# (default) => central-mode analyses stamp fail-closed (private/unowned) rather than leak.
# IMPORTANT: point this at the WRITER/PRIMARY endpoint (the same Postgres the central node uses as
# its [database]), NOT a read replica/standby. Read-only credentials on the primary are fine, but a
# reader endpoint breaks the reconcile's cross-node advisory-lock serialization (pg_advisory_lock on
# a standby doesn't exclude the primary) and the under-lock re-read would be replication-lag-stale;
# the worker detects pg_is_in_recovery() and warns + runs unserialized-but-fail-closed in that case.
# Set on workers in a central + multitenancy deployment. Single-node and non-MT installs ignore this.
central_database_url =
# Management/UI node opt-in. A central UI node advertises the fleet's route options on the submission
# form but runs NO rooter (only workers route traffic). When yes, init_rooter/init_routing tolerate an
# unreachable rooter (warn + skip route verification/NAT while still populating the VPN list for the
# form) instead of raising CuckooStartupError at web import. Default no, so single-node AND workers
# keep failing fast on a missing rooter. Set yes ONLY on the central UI node, never on a worker.
tolerate_missing_rooter = no

[multitenancy]
# Multi-tenant identity & job-visibility (off = current single-tenant behavior).
# IMPORTANT: enabling this on an EXISTING populated install stamps NEW analyses only.
# Pre-existing mongo reports have no tenant/visibility stamp and stay invisible to
# every tenant (fail-closed) on the search/statistics/compare surfaces until you run
# the one-shot backfill: `python utils/db_migration/mongo_backfill_tenant.py`
# (stamps tenant_id/user_id/visibility from each report's Postgres task). See
# docs/MULTITENANCY-SUPPORT.md.
enabled = no
# shared = tenant-less collaborative pool (submit default public);
# locked = per-tenant isolation (submit default tenant).
mode = shared
# Blank = per-mode default (shared->public, locked->tenant). Override with one
# of: public | tenant | private.
default_visibility =
# Operator break-glass: local Django superusers manage ALL tenants (cross-tenant
# read + management) when yes. Set no to force admin access through the IdP.
# Server-side only — a tenant/user cannot grant themselves this.
local_admins_manage_all_tenants = yes
9 changes: 9 additions & 0 deletions conf/default/reporting.conf.default
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,12 @@ enabled = no
# MUST be done in conf/gcp.conf. This section only controls the enabled state.
enabled = no

# Central mode: ship the analysis tree to shared object storage (S3/compatible or a
# shared mount) and stamp the central job_id onto the report. This module is a
# PREREQUISITE for central mode -- with it disabled, [central_mode] is non-functional
# (no artifacts uploaded, info.job_id never stamped, info.id never rewritten to the
# central task id, and the visibility reconcile has no job_id to key on). Storage
# targets are configured under [central_mode] in conf/cuckoo.conf. Leave disabled on a
# standard single-node install.
[centralstore]
enabled = no
108 changes: 108 additions & 0 deletions docs/MULTITENANCY-SUPPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Multitenancy — Supported Configurations & Boundaries

Multitenancy (the `[multitenancy]` section of `cuckoo.conf`) scopes every read
surface so a user of one tenant cannot see or act on another tenant's tasks,
samples, reports, artifacts, statistics, search results, or live-VM (Guacamole)
sessions. This document states exactly which deployment modes that guarantee
covers today, and what is intentionally **fail-closed** (safe but limited) until
support is added.

## Enabling on an existing (populated) install — run the backfill

Turning `enabled = yes` stamps tenant/visibility onto **new** analyses only. Reports
already in MongoDB have no `info.tenant_id` / `info.user_id` / `info.visibility`
stamp, so the scoped search / statistics / compare surfaces treat them as
**fail-closed / invisible** to every tenant (no leak, but the history disappears
from those views) until they are stamped. Run the one-shot backfill once, after
flipping the flag:

```
python utils/db_migration/mongo_backfill_tenant.py
```

It reads each selected `analysis` doc's Postgres task and writes
`info.tenant_id` / `info.user_id` / `info.visibility` (orphans whose task was pruned
fail closed to `private`), and creates the `tenant_scope_idx` index. It touches only
(a) un-stamped docs (missing `info.visibility`, first-enable) and (b) crash-orphans in
the exact reporter fail-closed shape (`visibility=private` + null `tenant_id` AND
`user_id`) — never a stamped permissive doc — so it stays idempotent and safe to re-run.
In a **central** deployment run it on the CENTRAL node **while quiesced**: it only
restamps docs whose id space matches the node (broker `ui-*` ids ⇔ central RDS;
worker-local ids ⇔ single-node), and it is not lock-serialized against a live toggle.
The Alembic migration backfills the **SQL** columns only — the mongo stamp is this
separate step. A fresh install needs no backfill (every report is stamped at
creation).

## Supported (isolation enforced end-to-end)

- **Report store: MongoDB.** MT scoping of the aggregate/search/statistics/
compare surfaces reads the tenant stamp (`info.tenant_id` / `info.user_id` /
`info.visibility`) written into the mongo analysis document. **MongoDB is
required for multitenancy.**
- **Single-node CAPE** (one host running web + processing + analysis).
- **Central control plane + broker workers** (the "central mode" path): the
central UI serves artifacts staged from workers, keyed by the broker `job_id`.
Tenant stamping works across this path **only when workers can resolve the
submitter's tenancy from the central control-plane DB**: a worker's own
`[database]` is its LOCAL per-worker task DB (a different id space), and
`centralstore` rewrites `info.id` to the CENTRAL task id, so the worker resolves
and stamps tenancy against the central RDS via `[central_mode] central_database_url`.
If that URL is unset, central-mode analyses stamp **fail-closed** (private / unowned
— invisible to everyone but break-glass), never leaked. Point `central_database_url`
at the **writer/primary** endpoint (the same Postgres the central node uses as its
`[database]`), NOT a read replica: the worker's post-write reconcile takes its
per-task advisory lock there to serialize with the central node's visibility toggle
(advisory locks are cluster-wide, so same-key locks on the same primary mutually
exclude). If it points at a standby, `pg_advisory_lock` wouldn't exclude the primary
and the re-read would be replication-lag stale — the worker detects
`pg_is_in_recovery()`, warns, and runs unserialized-but-fail-closed, leaving a narrow
reprocess-during-toggle re-widen window (still no persistent leak: the fail-closed
insert + backfill keep it safe).
- **Guacamole interactive sessions** for task-backed analyses: minting a live-VM
session (and the WebSocket tunnel re-check) is gated by `can_manage_task`
(owner / tenant-admin / break-glass), NOT mere read visibility — live keyboard/
mouse/framebuffer control is a task action, so a read-only viewer of a public/
tenant task cannot tunnel into another user's or tenant's VM.

## Not yet supported (fail-closed — safe, but limited)

These modes do **not** carry tenant context correctly. Rather than leak, MT
**fails closed** on them (data is stamped private / invisible, or the surface is
admin-only), so enabling MT on these modes is safe but the affected analyses
will simply not be visible. Adding real support is tracked as future work.

- **Elasticsearch report store.** The visibility toggle syncs the tenant stamp
only to MongoDB; an ES-backed install would not update the ES stamp, and the ES
statistics aggregates cannot be per-record gated. **Run MT with MongoDB.** (An
ES bool-filter analogue of the scope predicate exists but is unexercised.)
- **Legacy distributed (`utils/dist.py`).** The main→worker submission does not
forward tenant/user/visibility, so a distributed worker cannot stamp the shared
mongo document correctly. When a worker processes a distributed task
(`options.main_task_id` set), the report is stamped **private/invisible**
(fail-closed) instead of world-visible. Use the broker/central path for
distributed multitenant analysis. (Our central path keys by `job_id` and never
sets `main_task_id`, so it is unaffected.)

## Behavior notes

- **Statistics API shape (shared mode).** With MT enabled in `shared` mode, the
`apiv2` statistics endpoint returns **per-scope** results
(`data['public']`, `data['tenant']`, `data['mine']`) instead of the legacy flat
`data['signatures']`. This is the correct scoped behavior; it is a breaking
change for API clients that assumed the flat shape on an MT-shared install.
Multitenancy-disabled installs and break-glass local-admins still receive the
flat dict.
- **Direct VNC / VM operator console.** The task-less direct-console endpoints
(`task_id=0`: raw host:port VNC, plus VM console/start/shutdown/route/snapshot
by name) have no tenant scoping and mint sessions the per-task tunnel gate does
not cover, so **all** of them are restricted to break-glass admins
(`viewer_for(user).is_local_admin`) in addition to the existing config gate —
never a tenant user. (On an MT-disabled / no-auth install every principal is a
local-admin, so the operator console stays usable.)
- **Threat-hunt facets.** `hunt()` scopes its aggregation by the viewer's entitled
scopes (`viewer_scope` `$match`); its facet `task_ids` rely on that stamp-based
`$match` with no per-id SQL backstop (a `$facet` count can't be post-filtered
per task). This is safe because the report tenant stamp is written fail-closed
on every path, so a doc can't carry a spoofed cross-tenant stamp.
- **Modes:** `shared` (public pool + own tenant + own tasks) and `locked`
(tenant-isolated). An unknown/typo `mode` fails closed to `locked`.
Loading
Loading