diff --git a/README.md b/README.md index b4ec00f..9d0b151 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,12 @@ AzureFox is built for that workflow. - Highlight pivot paths through workloads, managed identities, deployment systems, and secret-bearing configuration - Expose escalation opportunities and likely next steps instead of leaving you to sort raw Azure data +## Install + +```bash +pipx install azurefox +``` + ## Operator Workflow Start with the identity you have, then work outward toward movement and consequence: @@ -80,12 +86,6 @@ AzureFox reduces noise by ranking consequence, not just returning Azure objects. - Assess whether a service principal or application relationship creates a pivot or escalation path - Work outward from subscription or tenant visibility to identify cross-resource and cross-tenant movement -## Install - -```bash -pipx install azurefox -``` - ## Run It Start with the current Azure identity and the strongest visible control paths: diff --git a/tests/test_collectors.py b/tests/test_collectors.py index 0fc41d2..a5e490d 100644 --- a/tests/test_collectors.py +++ b/tests/test_collectors.py @@ -33,6 +33,7 @@ collect_rbac, collect_resource_trusts, collect_role_trusts, + collect_snapshots_disks, collect_storage, collect_tokens_credentials, collect_vms, @@ -3673,6 +3674,19 @@ def test_collect_vms(fixture_provider, options) -> None: assert len(output.findings) == 1 +def test_collect_snapshots_disks(fixture_provider, options) -> None: + output = collect_snapshots_disks(fixture_provider, options) + assert len(output.snapshot_disk_assets) == 4 + assert output.issues == [] + assert output.snapshot_disk_assets[0].name == "data-detached-legacy" + assert output.snapshot_disk_assets[0].attachment_state == "detached" + assert output.snapshot_disk_assets[0].public_network_access == "Enabled" + assert output.snapshot_disk_assets[1].asset_kind == "snapshot" + assert output.snapshot_disk_assets[1].source_resource_name == "data-detached-legacy" + assert output.snapshot_disk_assets[2].name == "vm-web-01-os-snap" + assert output.snapshot_disk_assets[3].name == "vm-web-01-os" + + def test_collect_vms_keeps_public_ip_lookup_failures_explicit() -> None: provider = object.__new__(AzureProvider) provider.clients = SimpleNamespace( diff --git a/tests/test_terminal_ux.py b/tests/test_terminal_ux.py index 083bd13..da5ec63 100644 --- a/tests/test_terminal_ux.py +++ b/tests/test_terminal_ux.py @@ -358,6 +358,27 @@ def test_snapshots_disks_takeaway_counts_disk_access_as_broad_export_signal() -> assert "1 show broader sharing or export posture" in " ".join(rendered.split()) +def test_vms_table_mode_surfaces_public_ip_and_identity_cues(tmp_path: Path) -> None: + result = runner.invoke( + app, + ["--outdir", str(tmp_path), "vms"], + env=_fixture_env(), + ) + + assert result.exit_code == 0 + assert ( + "Summarizing reachable compute assets and identity-bearing workloads." in result.stdout + ) + assert "vm-web-01" in result.stdout + assert "52.160.10.20" in result.stdout + assert "10.0.1.4" in result.stdout + assert "Public workload with attached identity" in result.stdout + normalized_output = " ".join(result.stdout.split()) + assert ( + "Takeaway: 1 compute assets visible; 1 have public IP exposure." + ) in normalized_output + + def test_storage_takeaway_keeps_partial_read_posture_explicit() -> None: payload = { "metadata": {"command": "storage"},