Conversation
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
parthrohit22
left a comment
There was a problem hiding this comment.
Thanks @TFT444, splitting the graph work into three stacked PRs makes this a lot easier to follow, and keeping snapshot collection non-fatal is the right call.
I have to request changes though, because as wired today the snapshot is never collected in a real scan.
1. AzureClient has no tenant_id, so collect_snapshot() always returns None
snapshot_bridge.collect_snapshot does getattr(client, "tenant_id", None), but AzureClient.__init__ only sets subscription_id and credential (scanner/azure_client.py:51). Nothing else sets a tenant on it either: ScanEngine builds it as AzureClient(subscription_id). So in production we always go down the "missing tenant_id or credential" branch and return None. I checked it against the real class instead of a MagicMock:
snapshot_bridge: AzureClient missing tenant_id or credential — skipping ARG collection
has tenant_id: False -> snapshot: None
The tests don't catch this because every one of them uses MagicMock(), which will happily return a truthy tenant_id. Once that happens, #353 and #354 never run either. Could you resolve the tenant explicitly (from AZURE_TENANT_ID, or from the credential/subscription lookup) and add one test that builds a real AzureClient with a stub credential?
2. Migration head collision with #325
e1f2a3b4c5d6 chains from 3f59f83a5253, and so does #325's first revision (e4f7a9b2c6d8). Whichever of the two merges second will leave alembic heads with two heads. #325 is approved and close to merging, so it's probably simplest to plan on rebasing this onto d4a8c1e6b2f9 once it lands.
Smaller things (non-blocking)
- The
except TypeErrorfallback inrun_scanalso catches aTypeErrorraised inside a rule that already acceptssnapshot. That rule then runs a second time with two args, which duplicates its Azure calls and hides the original error. Checking the signature once withinspect.signaturewhen rules load would avoid that. tests/test_graph_migration.pyisn't gated onDATABASE_URLlike the other Postgres tests, and its module teardown downgrades the shared test database to3f59f83a5253. Once more revisions sit above this one, that downgrade will drop tables other test modules depend on. The throwaway-database pattern intest_scan_admission_migration_postgres.pywould keep it isolated.- The
configure_logging/fileConfig(disable_existing_loggers=False)changes look fine, but they're unrelated to the graph schema. Worth a line in the description saying why they're here.
Happy to take another look once the tenant wiring is sorted.
|
Heads-up @TFT444: #325 just merged into
#353 and #354 stack on this, so they'll need the same rebase. When you request review again, could you check that |
4670a3c to
dad78f2
Compare
|
All blocking items addressed:
|
parthrohit22
left a comment
There was a problem hiding this comment.
Thanks @TFT444. The tenant wiring and the inspect.signature change both do what I asked. The AzureClient tests now construct the real class, so a missing tenant can't hide behind a MagicMock any more.
One blocker came in with the migration change, and it's why Backend Tests is red on this head.
Blocker: the merge point includes an ancestor of its other parent
down_revision = ("3f59f83a5253", "d4a8c1e6b2f9"). But 3f59f83a5253 is already an ancestor of d4a8c1e6b2f9: #325 rebased its chain onto it before merging. So dev had a single head, and there was nothing to merge. Listing both parents breaks two things. I checked each against a clean PostgreSQL 16 database on this head:
$ alembic upgrade head -> ok
$ alembic downgrade -1
ERROR [alembic.util.messaging] Ambiguous walk
FAILED: Ambiguous walk
tests/test_alembic_migrations.py::test_single_alembic_head also fails: found ['e1f2a3b4c5d6', 'd4a8c1e6b2f9']. The test's static parser reads the first ID in the tuple, so d4a8c1e6b2f9 looks like an orphaned head. tests/test_graph_migration.py::test_downgrade_removes_tables errors for the same reason.
Fix: use a plain single parent:
down_revision: Union[str, Sequence[str], None] = "d4a8c1e6b2f9"and change Revises: in the docstring to match. I applied exactly that locally, and got upgrade → downgrade -1 → upgrade clean, test_single_alembic_head passing, and test_graph_migration.py passing.
After that, 4 admission-migration tests still fail
tests/test_scan_admission_migration_postgres.py on dev pins _HEAD = "d4a8c1e6b2f9". Any PR that adds a migration fails these four tests (...reaches_a_single_head and the three ...reruns... / ...rebuilds... tests): assert 'e1f2a3b4c5d6' == 'd4a8c1e6b2f9'. #310 already fixes the test to resolve the head from the revision graph (ScriptDirectory.from_config(...).get_current_head()). Either cherry-pick that one-line change or land after #310.
Merge order with #310 and #326
- #310 chains from the same parent. Its migration
3a76ff935bf6also chains fromd4a8c1e6b2f9, so whichever of #310 or #352 merges second has to repoint to the other's revision. #310 is waiting only on a re-review, so it will probably land first. Worth planning for. - Duplicate revision ID. #326 still uses
e1f2a3b4c5d6as its own revision ID (e1f2a3b4c5d6_finding_lifecycle.py). Alembic refuses to start with two files sharing an ID, so one of them needs a new one before both can exist ondev.
Non-blocking (from my first review, still open)
test_graph_migration.pydowngrades the shared test database to3f59f83a5253in the middle of a run. With #325 ondev, that drops the lease, admission and enrichment tables before re-upgrading. It passes today, but it will fight any test module that runs alongside it. The throwaway-database pattern intest_scan_admission_migration_postgres.pyavoids that.- The
import osinsideAzureClient.__init__belongs at module level.
Fix the down_revision, sort out the admission-test head, and this is ready for another look.
|
Both blockers from your review are already fixed on the current head (7996820):
These were pushed a few days ago but the review hasn't been updated since. Could you take another look at the current head? @parthrohit22 |
parthrohit22
left a comment
There was a problem hiding this comment.
Thanks @TFT444. I re-checked the current head (7996820). Both blockers from my earlier reviews are fixed:
- tenant_id: AzureClient now sets it from the argument or AZURE_TENANT_ID, and the tests build the real class. render.yaml and docker-compose already set that variable.
- down_revision is a single parent and the admission test resolves the head dynamically.
One new blocker, caused by dev moving since your last push.
Blocker: migration no longer merges cleanly onto dev
#310 merged after your last push. Its migration 3a76ff935bf6 also chains from d4a8c1e6b2f9, so dev now has two heads once this PR is merged. I merged the PR head onto current dev in a scratch copy and ran it against PostgreSQL:
$ alembic upgrade head
ERROR: Multiple head revisions are present for given argument 'head'
This is not only a test failure. startup.sh runs "alembic upgrade head", so this would break deploys. The suite also errors at collection, because the admission test's head lookup raises.
CI is green only because it last ran before #310 merged.
Fix: set down_revision to "3a76ff935bf6" and update the "Revises:" line in the docstring.
Heads-up: my #364 also chains from 3a76ff935bf6. Whichever of the two lands second repoints onto the other.
Also blocks the series: duplicate revision ID in #326
#326 still uses e1f2a3b4c5d6 as its revision ID, the same as this PR, and its down_revision is d8e4f6a1b2c3, which is well behind. Alembic refuses to start with two files sharing an ID, so one of them needs a new ID before both can sit on dev. This is for #326, but it decides merge order.
Non-blocking
- Case-sensitive uniqueness. uq_graph_nodes_tenant_resource is on (tenant_id, resource_id). Azure resource IDs are case-insensitive, so the same resource could become two nodes if the casing differs between ARG and a rule finding. Are IDs lowercased before insert? Worth settling before #353 populates the table.
- Signature check. run_scan passes the snapshot when scan() has 3 or more parameters. All 144 rules take exactly 2, so it is safe today. Checking for a parameter named snapshot (or *args) would be more robust.
- Every scan now queries Azure Resource Graph, before any rule uses the snapshot, with no timeout. Failure is handled, but it adds latency and needs Reader access. A timeout or a feature flag until #353 lands would limit the impact. The snapshot_id is returned but not stored anywhere yet.
- tests/test_graph_migration.py still modifies the shared test database and is not skipped when DATABASE_URL is unset (from my first review).
- The configure_logging and fileConfig changes look fine but are unrelated to the graph. One line in the description would help.
What I did not check: I did not run collect_snapshot against a real Azure subscription.
Once down_revision points at 3a76ff935bf6, this looks good to me. Happy to re-check quickly.
Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
…Engine Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
Replace the full root.handlers reset with a marker-based selective removal so configure_logging() only evicts the handler it previously installed. This prevents test framework handlers (pytest LogCaptureHandler) from being wiped when configure_logging() is called at module level in api/app.py or scanner/worker.py, fixing test_startup_warns_when_allowlist_is_unset across the full test suite. Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
Pass disable_existing_loggers=False to fileConfig so Alembic's ini-driven logging setup does not disable application loggers (e.g. api.app) that were created before the migration run. The Python default of True causes any logger not listed in alembic.ini to be silenced for the rest of the process, which broke test_startup_warns_when_allowlist_is_unset when test_graph_migration.py ran first in the full suite. Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
…e TypeError guard with inspect.signature Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
3f59f83a5253 is a linear ancestor of d4a8c1e6b2f9 on the dev chain, so the two-parent tuple created a spurious merge root. Single-parent down_revision d4a8c1e6b2f9 is the correct head this migration extends. Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
…import os to module level - test_scan_admission_migration_postgres.py: replace hardcoded _HEAD with ScriptDirectory.from_config().get_current_head() so the test stays valid when new migrations are added without manual updates to the pinned revision. - scanner/azure_client.py: move inline 'import os' from __init__ and _build_devops_client to module-level import. Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
…iance Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
After #310 merged, dev's Alembic head moved from d4a8c1e6b2f9 to 3a76ff935bf6. Update down_revision and Revises docstring to match so the graph schema migration chains cleanly from the new single head. Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
7996820 to
5c61b76
Compare
|
@parthrohit22 all requested changes are addressed: |
|
@parthrohit22 Migration chain blocker is fixed ( |
… snapshot dispatch, exc_info on ARG failure, tighten down_revision type Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
Summary
Lays the foundation for the OpenShield attack graph (issue #331). Three self-contained changes:
alembic/versions/e1f2a3b4c5d6_graph_schema.py): addsgraph_nodes,graph_edges, andfinding_graph_nodestables with cascade deletes, unique constraints, and full downgrade supportscanner/graph/snapshot_bridge.py): wrapsArgInventoryClientinto a singlecollect_snapshot()call for use insideScanEngine; failure is non-fatal (returnsNoneso rules fall back to direct SDK)scanner/engine.py): collects oneInventorySnapshotper scan before the rule loop and passes it as an optional third argument to each rule'sscan()call; existing two-arg rules keep working viaTypeErrorfallback; result dict gainssnapshot_idandsnapshot_statusWhat is not in this PR
Node population, edge detection, path traversal, and API endpoints come in PRs 2/3 and 3/3 (issues #332 and #333).
Test coverage
tests/test_graph_migration.py: schema structure tests (require live PostgreSQL)tests/test_graph_snapshot_bridge.py: 4 unit tests covering success, exception, FAILED, and PARTIAL snapshot statustests/test_graph_engine_integration.py: 4 unit tests covering snapshot wiring, None fallback, snapshot passed to rule, and legacy two-arg rule compatibilityFull suite: 1361 passed, 9 skipped. The 2 pre-existing failures (
test_rules_aks_enterprise,test_subscription_authorization) are not introduced by this branch.Closes part of #331.