From 6f1bbb81ae5e85ab65e38284a0dafaccab8e82b3 Mon Sep 17 00:00:00 2001 From: Colby Farley Date: Mon, 6 Apr 2026 21:58:02 -0500 Subject: [PATCH 1/2] feat: roll forward role-trusts hints and command output examples --- command-output/README.md | 29 +++ command-output/acr/output.json | 98 ++++++++++ command-output/acr/table.txt | 89 +++++++++ command-output/aks/output.json | 87 +++++++++ command-output/aks/table.txt | 68 +++++++ command-output/api-mgmt/output.json | 64 ++++++ command-output/api-mgmt/table.txt | 40 ++++ command-output/app-services/output.json | 64 ++++++ command-output/app-services/table.txt | 32 +++ .../application-gateway/output.json | 126 ++++++++++++ command-output/application-gateway/table.txt | 51 +++++ command-output/arm-deployments/output.json | 127 ++++++++++++ command-output/arm-deployments/table.txt | 32 +++ command-output/auth-policies/output.json | 102 ++++++++++ command-output/auth-policies/table.txt | 25 +++ command-output/automation/output.json | 71 +++++++ command-output/automation/table.txt | 40 ++++ .../chains/credential-path/output.json | 173 +++++++++++++++++ .../chains/credential-path/table.txt | 19 ++ command-output/cross-tenant/output.json | 61 ++++++ command-output/cross-tenant/table.txt | 28 +++ command-output/databases/output.json | 98 ++++++++++ command-output/databases/table.txt | 67 +++++++ command-output/devops/output.json | 176 +++++++++++++++++ command-output/devops/table.txt | 133 +++++++++++++ command-output/dns/output.json | 77 ++++++++ command-output/dns/table.txt | 18 ++ command-output/endpoints/output.json | 57 ++++++ command-output/endpoints/table.txt | 23 +++ command-output/env-vars/output.json | 95 +++++++++ command-output/env-vars/table.txt | 52 +++++ command-output/functions/output.json | 48 +++++ command-output/functions/table.txt | 31 +++ command-output/inventory/output.json | 24 +++ command-output/inventory/table.txt | 9 + command-output/keyvault/output.json | 99 ++++++++++ command-output/keyvault/table.txt | 23 +++ command-output/lighthouse/output.json | 131 +++++++++++++ command-output/lighthouse/table.txt | 16 ++ command-output/managed-identities/output.json | 51 +++++ command-output/managed-identities/table.txt | 14 ++ command-output/network-effective/output.json | 41 ++++ command-output/network-effective/table.txt | 11 ++ command-output/network-ports/output.json | 62 ++++++ command-output/network-ports/table.txt | 19 ++ command-output/nics/output.json | 51 +++++ command-output/nics/table.txt | 10 + command-output/permissions/output.json | 53 +++++ command-output/permissions/table.txt | 13 ++ command-output/principals/output.json | 61 ++++++ command-output/principals/table.txt | 10 + command-output/privesc/output.json | 49 +++++ command-output/privesc/table.txt | 12 ++ command-output/rbac/output.json | 50 +++++ command-output/rbac/table.txt | 10 + command-output/resource-trusts/output.json | 99 ++++++++++ command-output/resource-trusts/table.txt | 26 +++ command-output/role-trusts/output.json | 69 +++++++ command-output/role-trusts/table.txt | 18 ++ command-output/snapshots-disks/output.json | 100 ++++++++++ command-output/snapshots-disks/table.txt | 37 ++++ command-output/storage/output.json | 80 ++++++++ command-output/storage/table.txt | 22 +++ command-output/tokens-credentials/output.json | 183 ++++++++++++++++++ command-output/tokens-credentials/table.txt | 57 ++++++ command-output/vms/output.json | 45 +++++ command-output/vms/table.txt | 13 ++ command-output/vmss/output.json | 85 ++++++++ command-output/vmss/table.txt | 61 ++++++ command-output/whoami/output.json | 30 +++ command-output/whoami/table.txt | 9 + command-output/workloads/output.json | 92 +++++++++ command-output/workloads/table.txt | 32 +++ scripts/generate_command_output_examples.py | 95 +++++++++ src/azurefox/collectors/commands.py | 57 +++++- src/azurefox/help.py | 3 +- src/azurefox/models/common.py | 2 + src/azurefox/output/style.py | 3 +- src/azurefox/render/table.py | 21 +- src/azurefox/role_trust_hints.py | 153 +++++++++++++++ tests/golden/role-trusts.json | 16 +- tests/test_collectors.py | 62 ++++++ tests/test_command_output_examples.py | 25 +++ tests/test_help.py | 2 + tests/test_models.py | 2 + tests/test_terminal_ux.py | 13 +- 86 files changed, 4620 insertions(+), 12 deletions(-) create mode 100644 command-output/README.md create mode 100644 command-output/acr/output.json create mode 100644 command-output/acr/table.txt create mode 100644 command-output/aks/output.json create mode 100644 command-output/aks/table.txt create mode 100644 command-output/api-mgmt/output.json create mode 100644 command-output/api-mgmt/table.txt create mode 100644 command-output/app-services/output.json create mode 100644 command-output/app-services/table.txt create mode 100644 command-output/application-gateway/output.json create mode 100644 command-output/application-gateway/table.txt create mode 100644 command-output/arm-deployments/output.json create mode 100644 command-output/arm-deployments/table.txt create mode 100644 command-output/auth-policies/output.json create mode 100644 command-output/auth-policies/table.txt create mode 100644 command-output/automation/output.json create mode 100644 command-output/automation/table.txt create mode 100644 command-output/chains/credential-path/output.json create mode 100644 command-output/chains/credential-path/table.txt create mode 100644 command-output/cross-tenant/output.json create mode 100644 command-output/cross-tenant/table.txt create mode 100644 command-output/databases/output.json create mode 100644 command-output/databases/table.txt create mode 100644 command-output/devops/output.json create mode 100644 command-output/devops/table.txt create mode 100644 command-output/dns/output.json create mode 100644 command-output/dns/table.txt create mode 100644 command-output/endpoints/output.json create mode 100644 command-output/endpoints/table.txt create mode 100644 command-output/env-vars/output.json create mode 100644 command-output/env-vars/table.txt create mode 100644 command-output/functions/output.json create mode 100644 command-output/functions/table.txt create mode 100644 command-output/inventory/output.json create mode 100644 command-output/inventory/table.txt create mode 100644 command-output/keyvault/output.json create mode 100644 command-output/keyvault/table.txt create mode 100644 command-output/lighthouse/output.json create mode 100644 command-output/lighthouse/table.txt create mode 100644 command-output/managed-identities/output.json create mode 100644 command-output/managed-identities/table.txt create mode 100644 command-output/network-effective/output.json create mode 100644 command-output/network-effective/table.txt create mode 100644 command-output/network-ports/output.json create mode 100644 command-output/network-ports/table.txt create mode 100644 command-output/nics/output.json create mode 100644 command-output/nics/table.txt create mode 100644 command-output/permissions/output.json create mode 100644 command-output/permissions/table.txt create mode 100644 command-output/principals/output.json create mode 100644 command-output/principals/table.txt create mode 100644 command-output/privesc/output.json create mode 100644 command-output/privesc/table.txt create mode 100644 command-output/rbac/output.json create mode 100644 command-output/rbac/table.txt create mode 100644 command-output/resource-trusts/output.json create mode 100644 command-output/resource-trusts/table.txt create mode 100644 command-output/role-trusts/output.json create mode 100644 command-output/role-trusts/table.txt create mode 100644 command-output/snapshots-disks/output.json create mode 100644 command-output/snapshots-disks/table.txt create mode 100644 command-output/storage/output.json create mode 100644 command-output/storage/table.txt create mode 100644 command-output/tokens-credentials/output.json create mode 100644 command-output/tokens-credentials/table.txt create mode 100644 command-output/vms/output.json create mode 100644 command-output/vms/table.txt create mode 100644 command-output/vmss/output.json create mode 100644 command-output/vmss/table.txt create mode 100644 command-output/whoami/output.json create mode 100644 command-output/whoami/table.txt create mode 100644 command-output/workloads/output.json create mode 100644 command-output/workloads/table.txt create mode 100644 scripts/generate_command_output_examples.py create mode 100644 src/azurefox/role_trust_hints.py create mode 100644 tests/test_command_output_examples.py diff --git a/command-output/README.md b/command-output/README.md new file mode 100644 index 0000000..f0e625a --- /dev/null +++ b/command-output/README.md @@ -0,0 +1,29 @@ +# Command Output Source + +This folder holds generated, trimmed AzureFox example output for downstream wiki work. + +It is intentionally neutral source material: + +- not `wiki/seed` +- not product docs +- not a regression baseline + +Current structure: + +- `command-output//output.json` +- `command-output//table.txt` +- `command-output/chains//output.json` +- `command-output/chains//table.txt` + +Generation rules: + +- examples come from `tests/fixtures/lab_tenant` +- `metadata.generated_at` is normalized to `` +- top-level lists are trimmed to the first `3` entries to keep examples readable +- grouped chain families stay under `command-output/chains/` so they are separate from flat commands + +To refresh the examples from current behavior: + +```bash +python3 scripts/generate_command_output_examples.py +``` diff --git a/command-output/acr/output.json b/command-output/acr/output.json new file mode 100644 index 0000000..4ff3c4d --- /dev/null +++ b/command-output/acr/output.json @@ -0,0 +1,98 @@ +{ + "findings": [], + "issues": [], + "metadata": { + "command": "acr", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "registries": [ + { + "admin_user_enabled": true, + "anonymous_pull_enabled": true, + "broad_webhook_scope_count": 1, + "data_endpoint_enabled": true, + "enabled_webhook_count": 1, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-containers/providers/Microsoft.ContainerRegistry/registries/acr-public-legacy", + "location": "eastus", + "login_server": "acr-public-legacy.azurecr.io", + "name": "acr-public-legacy", + "network_rule_bypass_options": "AzureServices", + "network_rule_default_action": "Allow", + "private_endpoint_connection_count": 0, + "public_network_access": "Enabled", + "quarantine_policy_status": "disabled", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-containers/providers/Microsoft.ContainerRegistry/registries/acr-public-legacy", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-containers/providers/Microsoft.ContainerRegistry/registries/acr-public-legacy/webhooks/push-all", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-containers/providers/Microsoft.ContainerRegistry/registries/acr-public-legacy/webhooks/delete-all" + ], + "replication_count": 0, + "replication_regions": [], + "resource_group": "rg-containers", + "retention_policy_days": null, + "retention_policy_status": "disabled", + "sku_name": "Standard", + "state": "Succeeded", + "summary": "Container Registry 'acr-public-legacy' publishes login server 'acr-public-legacy.azurecr.io' and has no managed identity visible from the current read path. Visible auth posture: admin user enabled, anonymous pull enabled. Visible network posture: public network access Enabled, default action Allow, bypass AzureServices, no private endpoints visible. Visible service shape: SKU Standard, data endpoint enabled. Depth cues: 2 webhooks (1 enabled), 1 broad webhook scope(s), webhook actions delete, push, 0 replications, quarantine disabled, retention disabled, content trust disabled.", + "trust_policy_status": "disabled", + "trust_policy_type": null, + "webhook_action_types": [ + "delete", + "push" + ], + "webhook_count": 2, + "workload_client_id": null, + "workload_identity_ids": [], + "workload_identity_type": null, + "workload_principal_id": null + }, + { + "admin_user_enabled": false, + "anonymous_pull_enabled": false, + "broad_webhook_scope_count": 0, + "data_endpoint_enabled": false, + "enabled_webhook_count": 1, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-containers/providers/Microsoft.ContainerRegistry/registries/acr-ops-01", + "location": "eastus2", + "login_server": "acr-ops-01.azurecr.io", + "name": "acr-ops-01", + "network_rule_bypass_options": "None", + "network_rule_default_action": "Deny", + "private_endpoint_connection_count": 1, + "public_network_access": "Disabled", + "quarantine_policy_status": "enabled", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-containers/providers/Microsoft.ContainerRegistry/registries/acr-ops-01", + "99990000-0000-0000-0000-000000000031", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-containers/providers/Microsoft.ContainerRegistry/registries/acr-ops-01/webhooks/push-internal", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-containers/providers/Microsoft.ContainerRegistry/registries/acr-ops-01/replications/westus2", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-containers/providers/Microsoft.ContainerRegistry/registries/acr-ops-01/replications/northeurope" + ], + "replication_count": 2, + "replication_regions": [ + "northeurope", + "westus2" + ], + "resource_group": "rg-containers", + "retention_policy_days": 30, + "retention_policy_status": "enabled", + "sku_name": "Premium", + "state": "Succeeded", + "summary": "Container Registry 'acr-ops-01' publishes login server 'acr-ops-01.azurecr.io' and uses managed identity (SystemAssigned). Visible auth posture: admin user disabled, anonymous pull disabled. Visible network posture: public network access Disabled, default action Deny, bypass None, 1 private endpoint(s). Visible service shape: SKU Premium, data endpoint disabled. Depth cues: 1 webhooks (1 enabled), webhook actions push, 2 replications across northeurope, westus2, quarantine enabled, retention enabled (30d), content trust enabled (notary).", + "trust_policy_status": "enabled", + "trust_policy_type": "notary", + "webhook_action_types": [ + "push" + ], + "webhook_count": 1, + "workload_client_id": "99990000-0000-0000-0000-000000000032", + "workload_identity_ids": [], + "workload_identity_type": "SystemAssigned", + "workload_principal_id": "99990000-0000-0000-0000-000000000031" + } + ] +} diff --git a/command-output/acr/table.txt b/command-output/acr/table.txt new file mode 100644 index 0000000..58cbcc6 --- /dev/null +++ b/command-output/acr/table.txt @@ -0,0 +1,89 @@ + azurefox acr +┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓ +┃ registry ┃ login server ┃ identity ┃ auth ┃ exposure ┃ depth ┃ posture ┃ why it matters ┃ +┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩ +│ acr-public-legacy │ acr-public-legac… │ - │ admin=yes; │ public=Enabled; │ webhooks=2; │ Standard; │ Container │ +│ │ │ │ anon-pull=yes │ default=Allow; │ enabled=1; │ bypass=AzureServi… │ Registry │ +│ │ │ │ │ pe=0 │ wide-scopes=1; │ data-endpoint=yes; │ 'acr-public-lega… │ +│ │ │ │ │ │ actions=delete,p… │ quarantine=disabl… │ publishes login │ +│ │ │ │ │ │ replications=0 │ retention=disable… │ server │ +│ │ │ │ │ │ │ trust=disabled │ 'acr-public-lega… │ +│ │ │ │ │ │ │ │ and has no │ +│ │ │ │ │ │ │ │ managed identity │ +│ │ │ │ │ │ │ │ visible from the │ +│ │ │ │ │ │ │ │ current read │ +│ │ │ │ │ │ │ │ path. Visible │ +│ │ │ │ │ │ │ │ auth posture: │ +│ │ │ │ │ │ │ │ admin user │ +│ │ │ │ │ │ │ │ enabled, │ +│ │ │ │ │ │ │ │ anonymous pull │ +│ │ │ │ │ │ │ │ enabled. Visible │ +│ │ │ │ │ │ │ │ network posture: │ +│ │ │ │ │ │ │ │ public network │ +│ │ │ │ │ │ │ │ access Enabled, │ +│ │ │ │ │ │ │ │ default action │ +│ │ │ │ │ │ │ │ Allow, bypass │ +│ │ │ │ │ │ │ │ AzureServices, no │ +│ │ │ │ │ │ │ │ private endpoints │ +│ │ │ │ │ │ │ │ visible. Visible │ +│ │ │ │ │ │ │ │ service shape: │ +│ │ │ │ │ │ │ │ SKU Standard, │ +│ │ │ │ │ │ │ │ data endpoint │ +│ │ │ │ │ │ │ │ enabled. Depth │ +│ │ │ │ │ │ │ │ cues: 2 webhooks │ +│ │ │ │ │ │ │ │ (1 enabled), 1 │ +│ │ │ │ │ │ │ │ broad webhook │ +│ │ │ │ │ │ │ │ scope(s), webhook │ +│ │ │ │ │ │ │ │ actions delete, │ +│ │ │ │ │ │ │ │ push, 0 │ +│ │ │ │ │ │ │ │ replications, │ +│ │ │ │ │ │ │ │ quarantine │ +│ │ │ │ │ │ │ │ disabled, │ +│ │ │ │ │ │ │ │ retention │ +│ │ │ │ │ │ │ │ disabled, content │ +│ │ │ │ │ │ │ │ trust disabled. │ +│ acr-ops-01 │ acr-ops-01.azure… │ SystemAssigned │ admin=no; │ public=Disabled; │ webhooks=1; │ Premium; │ Container │ +│ │ │ │ anon-pull=no │ default=Deny; pe=1 │ enabled=1; │ bypass=None; │ Registry │ +│ │ │ │ │ │ actions=push; │ data-endpoint=no; │ 'acr-ops-01' │ +│ │ │ │ │ │ replications=2; │ quarantine=enable… │ publishes login │ +│ │ │ │ │ │ regions=northeur… │ retention=30d; │ server │ +│ │ │ │ │ │ │ trust=notary │ 'acr-ops-01.azur… │ +│ │ │ │ │ │ │ │ and uses managed │ +│ │ │ │ │ │ │ │ identity │ +│ │ │ │ │ │ │ │ (SystemAssigned). │ +│ │ │ │ │ │ │ │ Visible auth │ +│ │ │ │ │ │ │ │ posture: admin │ +│ │ │ │ │ │ │ │ user disabled, │ +│ │ │ │ │ │ │ │ anonymous pull │ +│ │ │ │ │ │ │ │ disabled. Visible │ +│ │ │ │ │ │ │ │ network posture: │ +│ │ │ │ │ │ │ │ public network │ +│ │ │ │ │ │ │ │ access Disabled, │ +│ │ │ │ │ │ │ │ default action │ +│ │ │ │ │ │ │ │ Deny, bypass │ +│ │ │ │ │ │ │ │ None, 1 private │ +│ │ │ │ │ │ │ │ endpoint(s). │ +│ │ │ │ │ │ │ │ Visible service │ +│ │ │ │ │ │ │ │ shape: SKU │ +│ │ │ │ │ │ │ │ Premium, data │ +│ │ │ │ │ │ │ │ endpoint │ +│ │ │ │ │ │ │ │ disabled. Depth │ +│ │ │ │ │ │ │ │ cues: 1 webhooks │ +│ │ │ │ │ │ │ │ (1 enabled), │ +│ │ │ │ │ │ │ │ webhook actions │ +│ │ │ │ │ │ │ │ push, 2 │ +│ │ │ │ │ │ │ │ replications │ +│ │ │ │ │ │ │ │ across │ +│ │ │ │ │ │ │ │ northeurope, │ +│ │ │ │ │ │ │ │ westus2, │ +│ │ │ │ │ │ │ │ quarantine │ +│ │ │ │ │ │ │ │ enabled, │ +│ │ │ │ │ │ │ │ retention enabled │ +│ │ │ │ │ │ │ │ (30d), content │ +│ │ │ │ │ │ │ │ trust enabled │ +│ │ │ │ │ │ │ │ (notary). │ +└───────────────────┴───────────────────┴────────────────┴───────────────────┴────────────────────┴───────────────────┴────────────────────┴───────────────────┘ + +Takeaway: 2 registries visible; 1 keep public network access enabled, 1 allow admin-user auth, 3 webhooks are visible, and 1 registry replicates content into +additional regions. + diff --git a/command-output/aks/output.json b/command-output/aks/output.json new file mode 100644 index 0000000..35122f1 --- /dev/null +++ b/command-output/aks/output.json @@ -0,0 +1,87 @@ +{ + "aks_clusters": [ + { + "aad_managed": false, + "addon_names": [], + "agent_pool_count": 1, + "azure_rbac_enabled": false, + "cluster_client_id": "99990000-0000-0000-0000-000000000021", + "cluster_identity_ids": [], + "cluster_identity_type": "ServicePrincipal", + "cluster_principal_id": null, + "fqdn": "aks-public-legacy-ef567890.hcp.eastus.azmk8s.io", + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ContainerService/managedClusters/aks-public-legacy", + "kubernetes_version": "1.27.9", + "local_accounts_disabled": false, + "location": "eastus", + "name": "aks-public-legacy", + "network_plugin": "kubenet", + "network_policy": null, + "node_resource_group": "MC_rg-workload_aks-public-legacy_eastus", + "oidc_issuer_enabled": false, + "oidc_issuer_url": null, + "outbound_type": "loadBalancer", + "private_cluster_enabled": false, + "private_fqdn": null, + "provisioning_state": "Succeeded", + "public_fqdn_enabled": null, + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ContainerService/managedClusters/aks-public-legacy" + ], + "resource_group": "rg-workload", + "sku_tier": "Free", + "summary": "AKS cluster 'aks-public-legacy' publishes API endpoint 'aks-public-legacy-ef567890.hcp.eastus.azmk8s.io' and uses service principal client '99990000-0000-0000-0000-000000000021'. Visible inventory: Kubernetes 1.27.9, 1 agent pool(s). Visible auth posture: AAD profile not managed, Azure RBAC disabled, local accounts enabled, OIDC issuer disabled, workload identity disabled. Visible network shape: private cluster disabled, network plugin kubenet, outbound loadBalancer, web app routing disabled.", + "web_app_routing_dns_zone_count": 0, + "web_app_routing_enabled": false, + "workload_identity_enabled": false + }, + { + "aad_managed": true, + "addon_names": [ + "azureKeyvaultSecretsProvider" + ], + "agent_pool_count": 2, + "azure_rbac_enabled": true, + "cluster_client_id": "99990000-0000-0000-0000-000000000012", + "cluster_identity_ids": [], + "cluster_identity_type": "SystemAssigned", + "cluster_principal_id": "99990000-0000-0000-0000-000000000011", + "fqdn": "aks-ops-01-abcd1234.hcp.eastus.azmk8s.io", + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ContainerService/managedClusters/aks-ops-01", + "kubernetes_version": "1.29.4", + "local_accounts_disabled": true, + "location": "eastus", + "name": "aks-ops-01", + "network_plugin": "azure", + "network_policy": "calico", + "node_resource_group": "MC_rg-workload_aks-ops-01_eastus", + "oidc_issuer_enabled": true, + "oidc_issuer_url": "https://oidc.prod-aks.azure.example/aks-ops-01", + "outbound_type": "loadBalancer", + "private_cluster_enabled": true, + "private_fqdn": "aks-ops-01-abcd1234.privatelink.eastus.azmk8s.io", + "provisioning_state": "Succeeded", + "public_fqdn_enabled": false, + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ContainerService/managedClusters/aks-ops-01", + "99990000-0000-0000-0000-000000000011" + ], + "resource_group": "rg-workload", + "sku_tier": "Standard", + "summary": "AKS cluster 'aks-ops-01' uses private API endpoint 'aks-ops-01-abcd1234.privatelink.eastus.azmk8s.io' and uses cluster identity (SystemAssigned). Visible inventory: Kubernetes 1.29.4, 2 agent pool(s), addons azureKeyvaultSecretsProvider. Depth cues: OIDC issuer https://oidc.prod-aks.azure.example/aks-ops-01, workload identity enabled, enabled addons azureKeyvaultSecretsProvider. Visible auth posture: AAD-managed auth, Azure RBAC enabled, local accounts disabled, OIDC issuer enabled, workload identity enabled. Visible network shape: private cluster enabled, network plugin azure, network policy calico, outbound loadBalancer, web app routing enabled (1 DNS zone links).", + "web_app_routing_dns_zone_count": 1, + "web_app_routing_enabled": true, + "workload_identity_enabled": true + } + ], + "findings": [], + "issues": [], + "metadata": { + "command": "aks", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + } +} diff --git a/command-output/aks/table.txt b/command-output/aks/table.txt new file mode 100644 index 0000000..d9b8a5e --- /dev/null +++ b/command-output/aks/table.txt @@ -0,0 +1,68 @@ + azurefox aks +┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ cluster ┃ version ┃ endpoint ┃ identity ┃ auth ┃ network ┃ why it matters ┃ +┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━┩ +│ aks-public-legacy │ k8s=1.27.9; pools=1; │ private-api=no; fqdn │ ServicePrincipal; │ aad=no; │ plugin=kubenet; │ AKS cluster │ +│ │ tier=Free │ │ client-id=yes; │ azure-rbac=no; │ outbound=loadBalanc… │ 'aks-public-legacy' │ +│ │ │ │ workload-id=no │ local-accounts=enab… │ webapp-routing=no; │ publishes API │ +│ │ │ │ │ oidc=no │ node-rg=MC_rg-workl… │ endpoint │ +│ │ │ │ │ │ │ 'aks-public-legacy-e… │ +│ │ │ │ │ │ │ and uses service │ +│ │ │ │ │ │ │ principal client │ +│ │ │ │ │ │ │ '99990000-0000-0000-… │ +│ │ │ │ │ │ │ Visible inventory: │ +│ │ │ │ │ │ │ Kubernetes 1.27.9, 1 │ +│ │ │ │ │ │ │ agent pool(s). │ +│ │ │ │ │ │ │ Visible auth posture: │ +│ │ │ │ │ │ │ AAD profile not │ +│ │ │ │ │ │ │ managed, Azure RBAC │ +│ │ │ │ │ │ │ disabled, local │ +│ │ │ │ │ │ │ accounts enabled, │ +│ │ │ │ │ │ │ OIDC issuer disabled, │ +│ │ │ │ │ │ │ workload identity │ +│ │ │ │ │ │ │ disabled. Visible │ +│ │ │ │ │ │ │ network shape: │ +│ │ │ │ │ │ │ private cluster │ +│ │ │ │ │ │ │ disabled, network │ +│ │ │ │ │ │ │ plugin kubenet, │ +│ │ │ │ │ │ │ outbound │ +│ │ │ │ │ │ │ loadBalancer, web app │ +│ │ │ │ │ │ │ routing disabled. │ +│ aks-ops-01 │ k8s=1.29.4; pools=2; │ private-api=yes; │ SystemAssigned; │ aad=yes; │ plugin=azure; │ AKS cluster │ +│ │ tier=Standard │ fqdn; private-fqdn; │ workload-id=yes │ azure-rbac=yes; │ policy=calico; │ 'aks-ops-01' uses │ +│ │ │ public-fqdn=no │ │ local-accounts=disa… │ outbound=loadBalanc… │ private API endpoint │ +│ │ │ │ │ oidc=yes │ addons=1; │ 'aks-ops-01-abcd1234… │ +│ │ │ │ │ │ webapp-routing=yes; │ and uses cluster │ +│ │ │ │ │ │ node-rg=MC_rg-workl… │ identity │ +│ │ │ │ │ │ │ (SystemAssigned). │ +│ │ │ │ │ │ │ Visible inventory: │ +│ │ │ │ │ │ │ Kubernetes 1.29.4, 2 │ +│ │ │ │ │ │ │ agent pool(s), addons │ +│ │ │ │ │ │ │ azureKeyvaultSecrets… │ +│ │ │ │ │ │ │ Depth cues: OIDC │ +│ │ │ │ │ │ │ issuer │ +│ │ │ │ │ │ │ https://oidc.prod-ak… │ +│ │ │ │ │ │ │ workload identity │ +│ │ │ │ │ │ │ enabled, enabled │ +│ │ │ │ │ │ │ addons │ +│ │ │ │ │ │ │ azureKeyvaultSecrets… │ +│ │ │ │ │ │ │ Visible auth posture: │ +│ │ │ │ │ │ │ AAD-managed auth, │ +│ │ │ │ │ │ │ Azure RBAC enabled, │ +│ │ │ │ │ │ │ local accounts │ +│ │ │ │ │ │ │ disabled, OIDC issuer │ +│ │ │ │ │ │ │ enabled, workload │ +│ │ │ │ │ │ │ identity enabled. │ +│ │ │ │ │ │ │ Visible network │ +│ │ │ │ │ │ │ shape: private │ +│ │ │ │ │ │ │ cluster enabled, │ +│ │ │ │ │ │ │ network plugin azure, │ +│ │ │ │ │ │ │ network policy │ +│ │ │ │ │ │ │ calico, outbound │ +│ │ │ │ │ │ │ loadBalancer, web app │ +│ │ │ │ │ │ │ routing enabled (1 │ +│ │ │ │ │ │ │ DNS zone links). │ +└───────────────────┴──────────────────────┴──────────────────────┴──────────────────────┴──────────────────────┴──────────────────────┴───────────────────────┘ + +Takeaway: 2 AKS clusters visible; 1 use private API endpoints, 2 expose cluster identity context, 1 enable Azure RBAC, and 1 show Azure-side federation cues. + diff --git a/command-output/api-mgmt/output.json b/command-output/api-mgmt/output.json new file mode 100644 index 0000000..24d646a --- /dev/null +++ b/command-output/api-mgmt/output.json @@ -0,0 +1,64 @@ +{ + "api_management_services": [ + { + "active_subscription_count": 2, + "api_count": 2, + "api_subscription_required_count": 1, + "backend_count": 1, + "backend_hostnames": [ + "orders-internal.contoso.local" + ], + "developer_portal_status": "Enabled", + "gateway_enabled": true, + "gateway_hostnames": [ + "apim-edge-01.azure-api.net", + "api.contoso.com" + ], + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.ApiManagement/service/apim-edge-01", + "legacy_portal_status": "Disabled", + "location": "eastus", + "management_hostnames": [ + "apim-edge-01.management.azure-api.net" + ], + "name": "apim-edge-01", + "named_value_count": 2, + "named_value_key_vault_count": 1, + "named_value_secret_count": 1, + "portal_hostnames": [ + "portal.apim-edge-01.contoso.com" + ], + "private_ip_addresses": [], + "public_ip_address_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Network/publicIPAddresses/pip-apim-edge-01", + "public_ip_addresses": [ + "52.170.20.30" + ], + "public_network_access": "Enabled", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.ApiManagement/service/apim-edge-01", + "99990000-0000-0000-0000-000000000001", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Network/publicIPAddresses/pip-apim-edge-01" + ], + "resource_group": "rg-apps", + "sku_capacity": 1, + "sku_name": "Developer", + "state": "Succeeded", + "subscription_count": 3, + "summary": "API Management service 'apim-edge-01' publishes gateway hostnames apim-edge-01.azure-api.net, api.contoso.com; management hostnames apim-edge-01.management.azure-api.net; portal hostnames portal.apim-edge-01.contoso.com and uses managed identity (SystemAssigned). Visible inventory: 2 APIs, 1 require subscriptions, 3 subscriptions (2 active), 1 backends, 2 named values. Depth cues: 1 named values marked secret, 1 Key Vault-backed named values, backend hosts orders-internal.contoso.local. Visible posture: public network access Enabled, virtual network type External, SKU Developer, gateway enabled, developer portal Enabled.", + "virtual_network_type": "External", + "workload_client_id": "99990000-0000-0000-0000-000000000002", + "workload_identity_ids": [], + "workload_identity_type": "SystemAssigned", + "workload_principal_id": "99990000-0000-0000-0000-000000000001" + } + ], + "findings": [], + "issues": [], + "metadata": { + "command": "api-mgmt", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + } +} diff --git a/command-output/api-mgmt/table.txt b/command-output/api-mgmt/table.txt new file mode 100644 index 0000000..cd690c6 --- /dev/null +++ b/command-output/api-mgmt/table.txt @@ -0,0 +1,40 @@ + azurefox api-mgmt +┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ service ┃ gateway ┃ identity ┃ inventory ┃ exposure ┃ posture ┃ why it matters ┃ +┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ apim-edge-01 │ apim-edge-01.azure-ap… │ SystemAssigned │ apis=2; │ gateway=2; │ Developer; │ API Management service │ +│ │ api.contoso.com │ │ sub-required=1/2; │ management=1; │ vnet=External; │ 'apim-edge-01' │ +│ │ │ │ subs=3; active-subs=2; │ portal=1; │ gateway=yes; │ publishes gateway │ +│ │ │ │ backends=1; │ public=Enabled; │ devportal=Enabled; │ hostnames │ +│ │ │ │ backend-hosts=1; │ public-ip=1 │ named-secrets=1; │ apim-edge-01.azure-ap… │ +│ │ │ │ named-values=2 │ │ kv-backed=1 │ api.contoso.com; │ +│ │ │ │ │ │ │ management hostnames │ +│ │ │ │ │ │ │ apim-edge-01.manageme… │ +│ │ │ │ │ │ │ portal hostnames │ +│ │ │ │ │ │ │ portal.apim-edge-01.c… │ +│ │ │ │ │ │ │ and uses managed │ +│ │ │ │ │ │ │ identity │ +│ │ │ │ │ │ │ (SystemAssigned). │ +│ │ │ │ │ │ │ Visible inventory: 2 │ +│ │ │ │ │ │ │ APIs, 1 require │ +│ │ │ │ │ │ │ subscriptions, 3 │ +│ │ │ │ │ │ │ subscriptions (2 │ +│ │ │ │ │ │ │ active), 1 backends, 2 │ +│ │ │ │ │ │ │ named values. Depth │ +│ │ │ │ │ │ │ cues: 1 named values │ +│ │ │ │ │ │ │ marked secret, 1 Key │ +│ │ │ │ │ │ │ Vault-backed named │ +│ │ │ │ │ │ │ values, backend hosts │ +│ │ │ │ │ │ │ orders-internal.conto… │ +│ │ │ │ │ │ │ Visible posture: │ +│ │ │ │ │ │ │ public network access │ +│ │ │ │ │ │ │ Enabled, virtual │ +│ │ │ │ │ │ │ network type External, │ +│ │ │ │ │ │ │ SKU Developer, gateway │ +│ │ │ │ │ │ │ enabled, developer │ +│ │ │ │ │ │ │ portal Enabled. │ +└──────────────┴────────────────────────┴────────────────┴─────────────────────────┴────────────────────────┴─────────────────────────┴────────────────────────┘ + +Takeaway: 1 API Management services visible; 1 keep public network access enabled, 1 carry managed identity context, and 2 named values are visible, including 1 +marked secret. + diff --git a/command-output/app-services/output.json b/command-output/app-services/output.json new file mode 100644 index 0000000..2796f18 --- /dev/null +++ b/command-output/app-services/output.json @@ -0,0 +1,64 @@ +{ + "app_services": [ + { + "app_service_plan_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/serverfarms/asp-linux", + "client_cert_enabled": false, + "default_hostname": "app-empty-mi.azurewebsites.net", + "ftps_state": "AllAllowed", + "https_only": false, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-empty-mi", + "location": "eastus", + "min_tls_version": "1.0", + "name": "app-empty-mi", + "public_network_access": "Enabled", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-empty-mi", + "eeee3333-3333-3333-3333-333333333333", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/serverfarms/asp-linux" + ], + "resource_group": "rg-apps", + "runtime_stack": "DOTNETCORE|8.0", + "state": "Running", + "summary": "App Service 'app-empty-mi' publishes hostname 'app-empty-mi.azurewebsites.net', runs runtime 'DOTNETCORE|8.0', and uses managed identity (SystemAssigned). Visible posture: public network access Enabled, HTTPS-only disabled, TLS 1.0, FTPS AllAllowed.", + "workload_client_id": "ffff3333-3333-3333-3333-333333333333", + "workload_identity_ids": [], + "workload_identity_type": "SystemAssigned", + "workload_principal_id": "eeee3333-3333-3333-3333-333333333333" + }, + { + "app_service_plan_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/serverfarms/asp-linux", + "client_cert_enabled": true, + "default_hostname": "app-public-api.azurewebsites.net", + "ftps_state": "Disabled", + "https_only": true, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api", + "location": "eastus", + "min_tls_version": "1.2", + "name": "app-public-api", + "public_network_access": "Enabled", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api", + "aaaa1111-1111-1111-1111-111111111111", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/serverfarms/asp-linux" + ], + "resource_group": "rg-apps", + "runtime_stack": "NODE|20-lts", + "state": "Running", + "summary": "App Service 'app-public-api' publishes hostname 'app-public-api.azurewebsites.net', runs runtime 'NODE|20-lts', and uses managed identity (SystemAssigned). Visible posture: public network access Enabled, HTTPS-only enabled, TLS 1.2, FTPS Disabled.", + "workload_client_id": "bbbb1111-1111-1111-1111-111111111111", + "workload_identity_ids": [], + "workload_identity_type": "SystemAssigned", + "workload_principal_id": "aaaa1111-1111-1111-1111-111111111111" + } + ], + "findings": [], + "issues": [], + "metadata": { + "command": "app-services", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + } +} diff --git a/command-output/app-services/table.txt b/command-output/app-services/table.txt new file mode 100644 index 0000000..fc71f49 --- /dev/null +++ b/command-output/app-services/table.txt @@ -0,0 +1,32 @@ + azurefox app-services +┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ app service ┃ hostname ┃ runtime ┃ identity ┃ exposure ┃ posture ┃ why it matters ┃ +┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ app-empty-mi │ app-empty-mi.azurewebsi… │ DOTNETCORE|8.0 │ SystemAssigned │ hostname; public=Enabled │ https=no; tls=1.0; │ App Service │ +│ │ │ │ │ │ ftps=AllAllowed │ 'app-empty-mi' publishes │ +│ │ │ │ │ │ │ hostname │ +│ │ │ │ │ │ │ 'app-empty-mi.azurewebs… │ +│ │ │ │ │ │ │ runs runtime │ +│ │ │ │ │ │ │ 'DOTNETCORE|8.0', and │ +│ │ │ │ │ │ │ uses managed identity │ +│ │ │ │ │ │ │ (SystemAssigned). │ +│ │ │ │ │ │ │ Visible posture: public │ +│ │ │ │ │ │ │ network access Enabled, │ +│ │ │ │ │ │ │ HTTPS-only disabled, TLS │ +│ │ │ │ │ │ │ 1.0, FTPS AllAllowed. │ +│ app-public-api │ app-public-api.azureweb… │ NODE|20-lts │ SystemAssigned │ hostname; public=Enabled │ https=yes; tls=1.2; │ App Service │ +│ │ │ │ │ │ ftps=Disabled; │ 'app-public-api' │ +│ │ │ │ │ │ client-cert=yes │ publishes hostname │ +│ │ │ │ │ │ │ 'app-public-api.azurewe… │ +│ │ │ │ │ │ │ runs runtime │ +│ │ │ │ │ │ │ 'NODE|20-lts', and uses │ +│ │ │ │ │ │ │ managed identity │ +│ │ │ │ │ │ │ (SystemAssigned). │ +│ │ │ │ │ │ │ Visible posture: public │ +│ │ │ │ │ │ │ network access Enabled, │ +│ │ │ │ │ │ │ HTTPS-only enabled, TLS │ +│ │ │ │ │ │ │ 1.2, FTPS Disabled. │ +└────────────────┴──────────────────────────┴────────────────┴────────────────┴──────────────────────────┴──────────────────────────┴──────────────────────────┘ + +Takeaway: 2 App Service apps visible; 2 keep public network access enabled, 1 enforce HTTPS-only, and 2 carry managed identity context. + diff --git a/command-output/application-gateway/output.json b/command-output/application-gateway/output.json new file mode 100644 index 0000000..8113293 --- /dev/null +++ b/command-output/application-gateway/output.json @@ -0,0 +1,126 @@ +{ + "application_gateways": [ + { + "backend_pool_count": 3, + "backend_target_count": 5, + "firewall_policy_id": null, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-edge/providers/Microsoft.Network/applicationGateways/agw-shared-edge-01", + "listener_count": 4, + "location": "eastus", + "name": "agw-shared-edge-01", + "private_frontend_count": 0, + "private_frontend_ips": [], + "public_frontend_count": 1, + "public_ip_address_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-edge/providers/Microsoft.Network/publicIPAddresses/pip-agw-shared-edge-01" + ], + "public_ip_addresses": [ + "20.30.40.50" + ], + "redirect_configuration_count": 1, + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-edge/providers/Microsoft.Network/applicationGateways/agw-shared-edge-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-edge/providers/Microsoft.Network/publicIPAddresses/pip-agw-shared-edge-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-edge/providers/Microsoft.Network/virtualNetworks/vnet-edge/subnets/appgw-shared" + ], + "request_routing_rule_count": 4, + "resource_group": "rg-edge", + "rewrite_rule_set_count": 0, + "sku_name": "Standard_v2", + "sku_tier": "Standard_v2", + "state": "Running", + "subnet_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-edge/providers/Microsoft.Network/virtualNetworks/vnet-edge/subnets/appgw-shared" + ], + "summary": "Application Gateway 'agw-shared-edge-01' publishes 1 public frontend(s) (20.30.40.50). Visible routing breadth: 4 listener(s), 4 routing rule(s), 3 backend pool(s), 5 backend target(s). Visible WAF protection is disabled. This is a shared front door, so if the edge is weak the apps behind it may deserve review next.", + "url_path_map_count": 1, + "waf_enabled": false, + "waf_mode": null + }, + { + "backend_pool_count": 2, + "backend_target_count": 4, + "firewall_policy_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-edge/providers/Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies/agw-customer-edge-02-policy", + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-edge/providers/Microsoft.Network/applicationGateways/agw-customer-edge-02", + "listener_count": 3, + "location": "eastus2", + "name": "agw-customer-edge-02", + "private_frontend_count": 1, + "private_frontend_ips": [ + "10.20.0.10" + ], + "public_frontend_count": 1, + "public_ip_address_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-edge/providers/Microsoft.Network/publicIPAddresses/pip-agw-customer-edge-02" + ], + "public_ip_addresses": [ + "20.30.40.60" + ], + "redirect_configuration_count": 0, + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-edge/providers/Microsoft.Network/applicationGateways/agw-customer-edge-02", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-edge/providers/Microsoft.Network/publicIPAddresses/pip-agw-customer-edge-02", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-edge/providers/Microsoft.Network/virtualNetworks/vnet-edge/subnets/appgw-customer", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-edge/providers/Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies/agw-customer-edge-02-policy" + ], + "request_routing_rule_count": 3, + "resource_group": "rg-edge", + "rewrite_rule_set_count": 1, + "sku_name": "WAF_v2", + "sku_tier": "WAF_v2", + "state": "Running", + "subnet_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-edge/providers/Microsoft.Network/virtualNetworks/vnet-edge/subnets/appgw-customer" + ], + "summary": "Application Gateway 'agw-customer-edge-02' publishes 1 public frontend(s) (20.30.40.60). Visible routing breadth: 3 listener(s), 3 routing rule(s), 2 backend pool(s), 4 backend target(s). WAF policy is attached and running in Detection mode. This is a shared front door, so if the edge is weak the apps behind it may deserve review next.", + "url_path_map_count": 1, + "waf_enabled": true, + "waf_mode": "Detection" + }, + { + "backend_pool_count": 2, + "backend_target_count": 2, + "firewall_policy_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-payments/providers/Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies/agw-internal-payments-policy", + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-payments/providers/Microsoft.Network/applicationGateways/agw-internal-payments", + "listener_count": 2, + "location": "centralus", + "name": "agw-internal-payments", + "private_frontend_count": 1, + "private_frontend_ips": [ + "10.30.0.15" + ], + "public_frontend_count": 0, + "public_ip_address_ids": [], + "public_ip_addresses": [], + "redirect_configuration_count": 0, + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-payments/providers/Microsoft.Network/applicationGateways/agw-internal-payments", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-payments/providers/Microsoft.Network/virtualNetworks/vnet-payments/subnets/appgw-internal", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-payments/providers/Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies/agw-internal-payments-policy" + ], + "request_routing_rule_count": 2, + "resource_group": "rg-payments", + "rewrite_rule_set_count": 0, + "sku_name": "WAF_v2", + "sku_tier": "WAF_v2", + "state": "Running", + "subnet_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-payments/providers/Microsoft.Network/virtualNetworks/vnet-payments/subnets/appgw-internal" + ], + "summary": "Application Gateway 'agw-internal-payments' is private-only from the current read path (1 private frontend(s)). Visible routing breadth: 2 listener(s), 2 routing rule(s), 2 backend pool(s), 2 backend target(s). WAF policy is attached and running in Prevention mode. This is still useful shared-ingress context, but it is not an obvious internet-first path.", + "url_path_map_count": 0, + "waf_enabled": true, + "waf_mode": "Prevention" + } + ], + "findings": [], + "issues": [], + "metadata": { + "command": "application-gateway", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + } +} diff --git a/command-output/application-gateway/table.txt b/command-output/application-gateway/table.txt new file mode 100644 index 0000000..35b4345 --- /dev/null +++ b/command-output/application-gateway/table.txt @@ -0,0 +1,51 @@ + azurefox application-gateway +┏━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ gateway ┃ exposure ┃ routing ┃ backends ┃ waf ┃ why it matters ┃ +┡━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ agw-shared-edge-01 │ public=1 (20.30.40.50); │ listeners=4; rules=4; │ pools=3; targets=5 │ disabled │ Application Gateway │ +│ │ subnets=1 │ path-maps=1; redirects=1 │ │ │ 'agw-shared-edge-01' │ +│ │ │ │ │ │ publishes 1 public │ +│ │ │ │ │ │ frontend(s) (20.30.40.50). │ +│ │ │ │ │ │ Visible routing breadth: 4 │ +│ │ │ │ │ │ listener(s), 4 routing │ +│ │ │ │ │ │ rule(s), 3 backend pool(s), │ +│ │ │ │ │ │ 5 backend target(s). Visible │ +│ │ │ │ │ │ WAF protection is disabled. │ +│ │ │ │ │ │ This is a shared front door, │ +│ │ │ │ │ │ so if the edge is weak the │ +│ │ │ │ │ │ apps behind it may deserve │ +│ │ │ │ │ │ review next. │ +│ agw-customer-edge-02 │ public=1 (20.30.40.60); │ listeners=3; rules=3; │ pools=2; targets=4 │ policy; detection │ Application Gateway │ +│ │ private=1; subnets=1 │ path-maps=1; rewrites=1 │ │ │ 'agw-customer-edge-02' │ +│ │ │ │ │ │ publishes 1 public │ +│ │ │ │ │ │ frontend(s) (20.30.40.60). │ +│ │ │ │ │ │ Visible routing breadth: 3 │ +│ │ │ │ │ │ listener(s), 3 routing │ +│ │ │ │ │ │ rule(s), 2 backend pool(s), │ +│ │ │ │ │ │ 4 backend target(s). WAF │ +│ │ │ │ │ │ policy is attached and │ +│ │ │ │ │ │ running in Detection mode. │ +│ │ │ │ │ │ This is a shared front door, │ +│ │ │ │ │ │ so if the edge is weak the │ +│ │ │ │ │ │ apps behind it may deserve │ +│ │ │ │ │ │ review next. │ +│ agw-internal-payments │ private=1; subnets=1 │ listeners=2; rules=2 │ pools=2; targets=2 │ policy; prevention │ Application Gateway │ +│ │ │ │ │ │ 'agw-internal-payments' is │ +│ │ │ │ │ │ private-only from the │ +│ │ │ │ │ │ current read path (1 private │ +│ │ │ │ │ │ frontend(s)). Visible │ +│ │ │ │ │ │ routing breadth: 2 │ +│ │ │ │ │ │ listener(s), 2 routing │ +│ │ │ │ │ │ rule(s), 2 backend pool(s), │ +│ │ │ │ │ │ 2 backend target(s). WAF │ +│ │ │ │ │ │ policy is attached and │ +│ │ │ │ │ │ running in Prevention mode. │ +│ │ │ │ │ │ This is still useful │ +│ │ │ │ │ │ shared-ingress context, but │ +│ │ │ │ │ │ it is not an obvious │ +│ │ │ │ │ │ internet-first path. │ +└───────────────────────┴──────────────────────────────┴──────────────────────────────┴────────────────────┴────────────────────┴──────────────────────────────┘ + +Takeaway: 3 Application Gateways visible; 2 have public frontends, 2 look like shared public front doors, and 2 public gateway(s) lack strong visible WAF +coverage. Treat weak shared edge layers as clues that the apps behind them may deserve review next. + diff --git a/command-output/arm-deployments/output.json b/command-output/arm-deployments/output.json new file mode 100644 index 0000000..21bd4bb --- /dev/null +++ b/command-output/arm-deployments/output.json @@ -0,0 +1,127 @@ +{ + "deployments": [ + { + "duration": "PT1M12S", + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-app/providers/Microsoft.Resources/deployments/app-failed", + "mode": "Incremental", + "name": "app-failed", + "output_resource_count": 0, + "outputs_count": 0, + "parameters_link": null, + "providers": [ + "Microsoft.Web" + ], + "provisioning_state": "Failed", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-app/providers/Microsoft.Resources/deployments/app-failed" + ], + "resource_group": "rg-app", + "scope": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-app", + "scope_type": "resource_group", + "summary": "resource group deployment 'app-failed' is Failed with no outputs recorded; 1 providers.", + "template_link": null, + "timestamp": "2026-03-30T19:03:00Z" + }, + { + "duration": "PT42S", + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Resources/deployments/sub-foundation", + "mode": "Incremental", + "name": "sub-foundation", + "output_resource_count": 3, + "outputs_count": 2, + "parameters_link": null, + "providers": [ + "Microsoft.Network", + "Microsoft.Storage" + ], + "provisioning_state": "Succeeded", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Resources/deployments/sub-foundation" + ], + "resource_group": null, + "scope": "/subscriptions/22222222-2222-2222-2222-222222222222", + "scope_type": "subscription", + "summary": "subscription deployment 'sub-foundation' is Succeeded with 2 outputs; 2 providers.", + "template_link": "https://example.blob.core.windows.net/templates/sub-foundation.json", + "timestamp": "2026-03-30T18:42:00Z" + }, + { + "duration": "PT18S", + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.Resources/deployments/kv-secrets", + "mode": "Incremental", + "name": "kv-secrets", + "output_resource_count": 1, + "outputs_count": 1, + "parameters_link": "https://example.blob.core.windows.net/parameters/kv-secrets.parameters.json", + "providers": [ + "Microsoft.KeyVault" + ], + "provisioning_state": "Succeeded", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.Resources/deployments/kv-secrets" + ], + "resource_group": "rg-secrets", + "scope": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets", + "scope_type": "resource_group", + "summary": "resource group deployment 'kv-secrets' is Succeeded with 1 outputs; 1 providers.", + "template_link": null, + "timestamp": "2026-03-30T18:50:00Z" + } + ], + "findings": [ + { + "description": "Deployment 'app-failed' ended in state 'Failed'. Review the deployment history for leaked config context, partial resource creation, or operator troubleshooting artifacts.", + "id": "arm-deployment-failed-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-app/providers/Microsoft.Resources/deployments/app-failed", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-app/providers/Microsoft.Resources/deployments/app-failed" + ], + "severity": "medium", + "title": "Deployment did not complete successfully" + }, + { + "description": "Deployment 'sub-foundation' includes 2 recorded output values. Validate whether any outputs reveal useful endpoints, identifiers, or sensitive configuration.", + "id": "arm-deployment-outputs-/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Resources/deployments/sub-foundation", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Resources/deployments/sub-foundation" + ], + "severity": "medium", + "title": "Deployment exposes output values" + }, + { + "description": "Deployment 'sub-foundation' uses linked template or parameter content. Review those linked artifacts for exposed configuration, trust assumptions, or reusable infrastructure patterns.", + "id": "arm-deployment-remote-link-/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Resources/deployments/sub-foundation", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Resources/deployments/sub-foundation" + ], + "severity": "low", + "title": "Deployment references linked template content" + }, + { + "description": "Deployment 'kv-secrets' includes 1 recorded output values. Validate whether any outputs reveal useful endpoints, identifiers, or sensitive configuration.", + "id": "arm-deployment-outputs-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.Resources/deployments/kv-secrets", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.Resources/deployments/kv-secrets" + ], + "severity": "medium", + "title": "Deployment exposes output values" + }, + { + "description": "Deployment 'kv-secrets' uses linked template or parameter content. Review those linked artifacts for exposed configuration, trust assumptions, or reusable infrastructure patterns.", + "id": "arm-deployment-remote-link-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.Resources/deployments/kv-secrets", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.Resources/deployments/kv-secrets" + ], + "severity": "low", + "title": "Deployment references linked template content" + } + ], + "issues": [], + "metadata": { + "command": "arm-deployments", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + } +} diff --git a/command-output/arm-deployments/table.txt b/command-output/arm-deployments/table.txt new file mode 100644 index 0000000..0ee8323 --- /dev/null +++ b/command-output/arm-deployments/table.txt @@ -0,0 +1,32 @@ + azurefox arm-deployments +┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ deployment ┃ scope ┃ state ┃ outputs ┃ linked refs ┃ why it matters ┃ +┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ app-failed │ rg:rg-app │ Failed │ 0 │ - │ resource group deployment │ +│ │ │ │ │ │ 'app-failed' is Failed with no │ +│ │ │ │ │ │ outputs recorded; 1 providers. │ +│ sub-foundation │ sub:22222222-2222-2222-2222-22222222… │ Succeeded │ 2 │ template=example.blob.core.windows.n… │ subscription deployment │ +│ │ │ │ │ │ 'sub-foundation' is Succeeded with 2 │ +│ │ │ │ │ │ outputs; 2 providers. │ +│ kv-secrets │ rg:rg-secrets │ Succeeded │ 1 │ parameters=example.blob.core.windows… │ resource group deployment │ +│ │ │ │ │ │ 'kv-secrets' is Succeeded with 1 │ +│ │ │ │ │ │ outputs; 1 providers. │ +└────────────────┴───────────────────────────────────────┴───────────┴─────────┴───────────────────────────────────────┴───────────────────────────────────────┘ + +Findings: +- MEDIUM: Deployment did not complete successfully + Deployment 'app-failed' ended in state 'Failed'. Review the deployment history for leaked config context, partial resource creation, or operator +troubleshooting artifacts. +- MEDIUM: Deployment exposes output values + Deployment 'sub-foundation' includes 2 recorded output values. Validate whether any outputs reveal useful endpoints, identifiers, or sensitive configuration. +- LOW: Deployment references linked template content + Deployment 'sub-foundation' uses linked template or parameter content. Review those linked artifacts for exposed configuration, trust assumptions, or reusable +infrastructure patterns. +- MEDIUM: Deployment exposes output values + Deployment 'kv-secrets' includes 1 recorded output values. Validate whether any outputs reveal useful endpoints, identifiers, or sensitive configuration. +- LOW: Deployment references linked template content + Deployment 'kv-secrets' uses linked template or parameter content. Review those linked artifacts for exposed configuration, trust assumptions, or reusable +infrastructure patterns. + +Takeaway: 3 deployments visible; 1 at subscription scope and 5 findings. + diff --git a/command-output/auth-policies/output.json b/command-output/auth-policies/output.json new file mode 100644 index 0000000..e2c66e7 --- /dev/null +++ b/command-output/auth-policies/output.json @@ -0,0 +1,102 @@ +{ + "auth_policies": [ + { + "controls": [], + "name": "Security Defaults", + "policy_type": "security-defaults", + "related_ids": [ + "00000000-0000-0000-0000-000000000005" + ], + "scope": "tenant", + "state": "disabled", + "summary": "Security defaults are disabled for the tenant." + }, + { + "controls": [ + "guest-invites:everyone", + "sspr:enabled", + "legacy-msol-powershell:blocked", + "users-can-register-apps", + "users-can-create-security-groups", + "user-consent:self-service" + ], + "name": "Authorization Policy", + "policy_type": "authorization-policy", + "related_ids": [ + "authorizationPolicy" + ], + "scope": "tenant", + "state": "configured", + "summary": "guest invites: everyone; users can register apps; self-service permission grant policies assigned; legacy MSOL PowerShell blocked" + }, + { + "controls": [ + "block" + ], + "name": "CA002: Block legacy auth", + "policy_type": "conditional-access", + "related_ids": [ + "ca-2" + ], + "scope": "users:all, apps:all", + "state": "disabled", + "summary": "state: disabled; grants: block; scope: users:all, apps:all" + } + ], + "findings": [ + { + "description": "Tenant-wide security defaults are disabled. Review whether Conditional Access policies provide equivalent baseline MFA and legacy-auth controls.", + "id": "auth-policy-security-defaults-disabled", + "related_ids": [ + "00000000-0000-0000-0000-000000000005" + ], + "severity": "medium", + "title": "Security defaults are disabled" + }, + { + "description": "Default user permissions allow application registration. Review whether that app-creation surface is expected for this tenant.", + "id": "auth-policy-users-can-register-apps", + "related_ids": [ + "authorizationPolicy" + ], + "severity": "medium", + "title": "Users can register applications" + }, + { + "description": "The authorization policy allows guest invitations from everyone in the tenant. Validate whether that guest-invite surface is intentional.", + "id": "auth-policy-guest-invites-everyone", + "related_ids": [ + "authorizationPolicy" + ], + "severity": "medium", + "title": "Guest invitations are broadly allowed" + }, + { + "description": "Default user permissions include self-service permission-grant policy assignment. Review which delegated or application access paths that enables.", + "id": "auth-policy-user-consent-enabled", + "related_ids": [ + "authorizationPolicy" + ], + "severity": "medium", + "title": "User consent is available to default users" + }, + { + "description": "Security defaults are disabled and no enabled Conditional Access policies are visible from the current read path. Validate whether stronger auth controls exist outside the currently readable policy surface.", + "id": "auth-policy-no-active-enforcement-visible", + "related_ids": [ + "00000000-0000-0000-0000-000000000005" + ], + "severity": "medium", + "title": "No active auth enforcement visible" + } + ], + "issues": [], + "metadata": { + "command": "auth-policies", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + } +} diff --git a/command-output/auth-policies/table.txt b/command-output/auth-policies/table.txt new file mode 100644 index 0000000..1e86c08 --- /dev/null +++ b/command-output/auth-policies/table.txt @@ -0,0 +1,25 @@ + azurefox auth-policies +┏━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ policy ┃ state ┃ scope ┃ operator signal ┃ +┡━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ Security Defaults │ disabled │ tenant │ Security defaults are disabled for the tenant. │ +│ Authorization Policy │ configured │ tenant │ guest invites: everyone; users can register apps; self-service permission grant policies │ +│ │ │ │ assigned; legacy MSOL PowerShell blocked │ +│ CA002: Block legacy auth │ disabled │ users:all, apps:all │ state: disabled; grants: block; scope: users:all, apps:all │ +└──────────────────────────┴────────────┴─────────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────┘ + +Findings: +- MEDIUM: Security defaults are disabled + Tenant-wide security defaults are disabled. Review whether Conditional Access policies provide equivalent baseline MFA and legacy-auth controls. +- MEDIUM: Users can register applications + Default user permissions allow application registration. Review whether that app-creation surface is expected for this tenant. +- MEDIUM: Guest invitations are broadly allowed + The authorization policy allows guest invitations from everyone in the tenant. Validate whether that guest-invite surface is intentional. +- MEDIUM: User consent is available to default users + Default user permissions include self-service permission-grant policy assignment. Review which delegated or application access paths that enables. +- MEDIUM: No active auth enforcement visible + Security defaults are disabled and no enabled Conditional Access policies are visible from the current read path. Validate whether stronger auth controls +exist outside the currently readable policy surface. + +Takeaway: 3 policy rows, 5 findings, and 0 credential-scope issues visible from current credentials. + diff --git a/command-output/automation/output.json b/command-output/automation/output.json new file mode 100644 index 0000000..a349975 --- /dev/null +++ b/command-output/automation/output.json @@ -0,0 +1,71 @@ +{ + "automation_accounts": [ + { + "certificate_count": 1, + "client_id": "34343434-3434-3434-3434-343434343434", + "connection_count": 2, + "credential_count": 2, + "encrypted_variable_count": 4, + "hybrid_worker_group_count": 1, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-ops/providers/Microsoft.Automation/automationAccounts/aa-hybrid-prod", + "identity_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-ops/providers/Microsoft.Automation/automationAccounts/aa-hybrid-prod/identities/system" + ], + "identity_type": "SystemAssigned", + "job_schedule_count": 5, + "location": "eastus", + "name": "aa-hybrid-prod", + "principal_id": "12121212-1212-1212-1212-121212121212", + "published_runbook_count": 6, + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-ops/providers/Microsoft.Automation/automationAccounts/aa-hybrid-prod", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-ops/providers/Microsoft.Automation/automationAccounts/aa-hybrid-prod/identities/system" + ], + "resource_group": "rg-ops", + "runbook_count": 7, + "schedule_count": 4, + "sku_name": "Basic", + "state": "Ok", + "summary": "Automation account 'aa-hybrid-prod' uses managed identity (SystemAssigned). Visible execution shape: 6/7 published runbook(s); 4 schedule(s), 5 job schedule(s), 2 webhook(s); 1 Hybrid Runbook Worker group(s). Secure asset posture: credentials 2, certificates 1, connections 2, variables 5 (4 encrypted).", + "variable_count": 5, + "webhook_count": 2 + }, + { + "certificate_count": 0, + "client_id": null, + "connection_count": 1, + "credential_count": 0, + "encrypted_variable_count": 1, + "hybrid_worker_group_count": 0, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-lab/providers/Microsoft.Automation/automationAccounts/aa-lab-quiet", + "identity_ids": [], + "identity_type": null, + "job_schedule_count": 1, + "location": "centralus", + "name": "aa-lab-quiet", + "principal_id": null, + "published_runbook_count": 1, + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-lab/providers/Microsoft.Automation/automationAccounts/aa-lab-quiet" + ], + "resource_group": "rg-lab", + "runbook_count": 2, + "schedule_count": 1, + "sku_name": "Basic", + "state": "Ok", + "summary": "Automation account 'aa-lab-quiet' has no managed identity visible from the current read path. Visible execution shape: 1/2 published runbook(s); 1 schedule(s), 1 job schedule(s), 0 webhook(s); no Hybrid Runbook Worker groups visible. Secure asset posture: credentials 0, certificates 0, connections 1, variables 2 (1 encrypted).", + "variable_count": 2, + "webhook_count": 0 + } + ], + "findings": [], + "issues": [], + "metadata": { + "command": "automation", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + } +} diff --git a/command-output/automation/table.txt b/command-output/automation/table.txt new file mode 100644 index 0000000..242f43b --- /dev/null +++ b/command-output/automation/table.txt @@ -0,0 +1,40 @@ + azurefox automation +┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ automation account ┃ identity ┃ execution ┃ triggers ┃ workers ┃ assets ┃ why it matters ┃ +┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ aa-hybrid-prod │ SystemAssigned │ published=6/7; │ schedules=4; webhooks=2 │ groups=1 │ cred=2; cert=1; conn=2; │ Automation account │ +│ │ │ job-schedules=5 │ │ │ vars=5 (4 enc) │ 'aa-hybrid-prod' uses │ +│ │ │ │ │ │ │ managed identity │ +│ │ │ │ │ │ │ (SystemAssigned). Visible │ +│ │ │ │ │ │ │ execution shape: 6/7 │ +│ │ │ │ │ │ │ published runbook(s); 4 │ +│ │ │ │ │ │ │ schedule(s), 5 job │ +│ │ │ │ │ │ │ schedule(s), 2 │ +│ │ │ │ │ │ │ webhook(s); 1 Hybrid │ +│ │ │ │ │ │ │ Runbook Worker group(s). │ +│ │ │ │ │ │ │ Secure asset posture: │ +│ │ │ │ │ │ │ credentials 2, │ +│ │ │ │ │ │ │ certificates 1, │ +│ │ │ │ │ │ │ connections 2, variables │ +│ │ │ │ │ │ │ 5 (4 encrypted). │ +│ aa-lab-quiet │ none │ published=1/2; │ schedules=1; webhooks=0 │ groups=0 │ cred=0; cert=0; conn=1; │ Automation account │ +│ │ │ job-schedules=1 │ │ │ vars=2 (1 enc) │ 'aa-lab-quiet' has no │ +│ │ │ │ │ │ │ managed identity visible │ +│ │ │ │ │ │ │ from the current read │ +│ │ │ │ │ │ │ path. Visible execution │ +│ │ │ │ │ │ │ shape: 1/2 published │ +│ │ │ │ │ │ │ runbook(s); 1 │ +│ │ │ │ │ │ │ schedule(s), 1 job │ +│ │ │ │ │ │ │ schedule(s), 0 │ +│ │ │ │ │ │ │ webhook(s); no Hybrid │ +│ │ │ │ │ │ │ Runbook Worker groups │ +│ │ │ │ │ │ │ visible. Secure asset │ +│ │ │ │ │ │ │ posture: credentials 0, │ +│ │ │ │ │ │ │ certificates 0, │ +│ │ │ │ │ │ │ connections 1, variables │ +│ │ │ │ │ │ │ 2 (1 encrypted). │ +└────────────────────┴────────────────┴───────────────────────────┴─────────────────────────┴──────────┴───────────────────────────┴───────────────────────────┘ + +Takeaway: 2 Automation account(s) visible; 1 carry managed identity context, 1 expose webhook start paths, 1 show Hybrid Runbook Worker reach, and 7 published +runbooks are visible. + diff --git a/command-output/chains/credential-path/output.json b/command-output/chains/credential-path/output.json new file mode 100644 index 0000000..dbc45a4 --- /dev/null +++ b/command-output/chains/credential-path/output.json @@ -0,0 +1,173 @@ +{ + "artifact_preference_order": [ + "loot", + "json" + ], + "backing_commands": [ + "env-vars", + "tokens-credentials", + "databases", + "storage", + "keyvault" + ], + "claim_boundary": "Can claim that the visible evidence suggests a likely credential path and name the most plausible downstream service. Cannot claim the credential works or that the path is confirmed without deeper source evidence.", + "command_state": "extraction-only", + "family": "credential-path", + "grouped_command_name": "chains", + "input_mode": "live", + "issues": [], + "metadata": { + "command": "chains", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "paths": [ + { + "asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders", + "asset_kind": "FunctionApp", + "asset_name": "func-orders", + "chain_id": "credential-path::/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders::payment-api-key::keyvault", + "clue_type": "keyvault-reference", + "evidence_commands": [ + "env-vars", + "tokens-credentials", + "keyvault" + ], + "joined_surface_types": [ + "keyvault-reference" + ], + "location": "eastus", + "missing_confirmation": "The named Key Vault dependency is visible, but current artifacts do not confirm secret read access, secret values, or successful downstream use.", + "next_review": "Check vault access path and referenced secret use.", + "priority": "high", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-identities/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-orders", + "cccc2222-2222-2222-2222-222222222222", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabopen01" + ], + "setting_name": "PAYMENT_API_KEY", + "summary": "FunctionApp 'func-orders' maps setting 'PAYMENT_API_KEY' to the named Key Vault reference 'kvlabopen01.vault.azure.net'. AzureFox can join that reference to visible Key Vault inventory: kvlabopen01.", + "target_count": 1, + "target_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabopen01" + ], + "target_names": [ + "kvlabopen01" + ], + "target_resolution": "named match", + "target_service": "keyvault", + "target_visibility_issue": null, + "visible_path": "Key Vault-backed setting -> named vault" + }, + { + "asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api", + "asset_kind": "AppService", + "asset_name": "app-public-api", + "chain_id": "credential-path::/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api::db-password::database", + "clue_type": "plain-text-secret", + "evidence_commands": [ + "env-vars", + "tokens-credentials", + "databases" + ], + "joined_surface_types": [ + "plain-text-secret" + ], + "location": "eastus", + "missing_confirmation": "The current artifacts do not show a direct database hostname, connection string value, or confirmed successful credential use from this workload.", + "next_review": "Confirm the database target from app config or connection clues.", + "priority": "medium", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api", + "aaaa1111-1111-1111-1111-111111111111", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Sql/servers/sql-public-legacy" + ], + "setting_name": "DB_PASSWORD", + "summary": "AppService 'app-public-api' exposes credential-like setting 'DB_PASSWORD', and the visible naming suggests a database path. AzureFox cannot name the exact database from the setting alone, but it can narrow the next review set to 1 visible database candidate(s) in the same Azure location: sql-public-legacy.", + "target_count": 1, + "target_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Sql/servers/sql-public-legacy" + ], + "target_names": [ + "sql-public-legacy" + ], + "target_resolution": "narrowed candidates", + "target_service": "database", + "target_visibility_issue": null, + "visible_path": "Credential-like setting -> likely database path" + }, + { + "asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders", + "asset_kind": "FunctionApp", + "asset_name": "func-orders", + "chain_id": "credential-path::/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders::azurewebjobsstorage::storage", + "clue_type": "plain-text-secret", + "evidence_commands": [ + "env-vars", + "tokens-credentials", + "storage" + ], + "joined_surface_types": [ + "plain-text-secret" + ], + "location": "eastus", + "missing_confirmation": "The current artifacts do not show a direct storage hostname, connection string value, or confirmed successful credential use from this workload.", + "next_review": "Confirm the storage target from binding or connection clues.", + "priority": "low", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-identities/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-orders", + "cccc2222-2222-2222-2222-222222222222", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stlabpub01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stlabpriv01" + ], + "setting_name": "AzureWebJobsStorage", + "summary": "FunctionApp 'func-orders' exposes credential-like setting 'AzureWebJobsStorage', and the visible naming suggests a storage path. AzureFox cannot name the exact storage from the setting alone, but it can narrow the next review set to 2 visible storage candidate(s) in the same Azure location: stlabpub01, stlabpriv01.", + "target_count": 2, + "target_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stlabpub01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stlabpriv01" + ], + "target_names": [ + "stlabpub01", + "stlabpriv01" + ], + "target_resolution": "narrowed candidates", + "target_service": "storage", + "target_visibility_issue": null, + "visible_path": "Credential-like setting -> likely storage path" + } + ], + "source_artifacts": [ + { + "artifact_type": "loot", + "command": "env-vars", + "path": "/var/folders/k9/wj7jl1095hg9zs7v60p3tlz00000gn/T/azurefox-command-output-jbi16rhc/loot/env-vars.json" + }, + { + "artifact_type": "loot", + "command": "tokens-credentials", + "path": "/var/folders/k9/wj7jl1095hg9zs7v60p3tlz00000gn/T/azurefox-command-output-jbi16rhc/loot/tokens-credentials.json" + }, + { + "artifact_type": "loot", + "command": "databases", + "path": "/var/folders/k9/wj7jl1095hg9zs7v60p3tlz00000gn/T/azurefox-command-output-jbi16rhc/loot/databases.json" + }, + { + "artifact_type": "loot", + "command": "storage", + "path": "/var/folders/k9/wj7jl1095hg9zs7v60p3tlz00000gn/T/azurefox-command-output-jbi16rhc/loot/storage.json" + }, + { + "artifact_type": "loot", + "command": "keyvault", + "path": "/var/folders/k9/wj7jl1095hg9zs7v60p3tlz00000gn/T/azurefox-command-output-jbi16rhc/loot/keyvault.json" + } + ], + "summary": "Follow credential clues from surfaced secret-bearing or token-bearing evidence toward the likely downstream service." +} diff --git a/command-output/chains/credential-path/table.txt b/command-output/chains/credential-path/table.txt new file mode 100644 index 0000000..810da05 --- /dev/null +++ b/command-output/chains/credential-path/table.txt @@ -0,0 +1,19 @@ + azurefox chains +┏━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ priority ┃ asset ┃ setting ┃ target ┃ target resolution ┃ visible targets ┃ next review ┃ note ┃ +┡━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ high │ func-orders │ PAYMENT_API_KEY │ keyvault │ named match │ kvlabopen01 │ Check vault access │ Named target matched │ +│ │ │ │ │ │ │ path and referenced │ visible inventory. │ +│ │ │ │ │ │ │ secret use. │ │ +│ medium │ app-public-api │ DB_PASSWORD │ database │ narrowed candidates │ sql-public-legacy │ Confirm the database │ Secret-shaped clue │ +│ │ │ │ │ │ │ target from app config │ suggests a database │ +│ │ │ │ │ │ │ or connection clues. │ path; exact target │ +│ │ │ │ │ │ │ │ unconfirmed. │ +│ low │ func-orders │ AzureWebJobsStorage │ storage │ narrowed candidates │ stlabpub01,stlabpriv01 │ Confirm the storage │ Secret-shaped clue │ +│ │ │ │ │ │ │ target from binding or │ suggests a storage │ +│ │ │ │ │ │ │ connection clues. │ path; exact target │ +│ │ │ │ │ │ │ │ unconfirmed. │ +└──────────┴────────────────┴─────────────────────┴──────────┴─────────────────────┴────────────────────────┴────────────────────────┴─────────────────────────┘ + +Takeaway: 3 visible credential paths; 1 high, 1 medium, 1 low, 1 database, 1 keyvault, 1 storage. + diff --git a/command-output/cross-tenant/output.json b/command-output/cross-tenant/output.json new file mode 100644 index 0000000..c49366a --- /dev/null +++ b/command-output/cross-tenant/output.json @@ -0,0 +1,61 @@ +{ + "cross_tenant_paths": [ + { + "attack_path": "control", + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.ManagedServices/registrationAssignments/lh-sub-contoso", + "name": "Contoso baseline ops", + "posture": "strongest=Owner; eligible=1; delegated-role-assign=yes", + "priority": "high", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.ManagedServices/registrationAssignments/lh-sub-contoso", + "/subscriptions/22222222-2222-2222-2222-222222222222", + "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.ManagedServices/registrationDefinitions/lh-def-contoso-sub" + ], + "scope": "subscription::22222222-2222-2222-2222-222222222222", + "signal_type": "lighthouse", + "summary": "managed by Contoso Corp.; strongest role Owner; 1 eligible authorization(s)", + "tenant_id": "33333333-3333-3333-3333-333333333333", + "tenant_name": "Contoso Corp." + }, + { + "attack_path": "pivot", + "id": "sp-external-ci", + "name": "external-ci-bridge", + "posture": "roles=Owner; assignments=2; scopes=1", + "priority": "high", + "related_ids": [ + "sp-external-ci" + ], + "scope": "tenant", + "signal_type": "external-sp", + "summary": "Service principal 'external-ci-bridge' appears to be owned by another tenant and holds high-impact Azure role assignments in the current environment.", + "tenant_id": "66666666-6666-6666-6666-666666666666", + "tenant_name": null + }, + { + "attack_path": "entry", + "id": "authorizationPolicy", + "name": "Authorization Policy", + "posture": "guest-invites=everyone; app-registration=yes; user-consent=self-service", + "priority": "high", + "related_ids": [ + "authorizationPolicy" + ], + "scope": "tenant", + "signal_type": "policy", + "summary": "guest invites: everyone; users can register apps; self-service permission grant policies assigned; legacy MSOL PowerShell blocked", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "tenant_name": null + } + ], + "findings": [], + "issues": [], + "metadata": { + "command": "cross-tenant", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + } +} diff --git a/command-output/cross-tenant/table.txt b/command-output/cross-tenant/table.txt new file mode 100644 index 0000000..2e215b4 --- /dev/null +++ b/command-output/cross-tenant/table.txt @@ -0,0 +1,28 @@ + azurefox cross-tenant +┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ signal ┃ type ┃ tenant ┃ scope ┃ posture ┃ attack path ┃ why it matters ┃ +┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━┩ +│ Contoso baseline ops │ lighthouse │ Contoso Corp. │ subscription::2222222… │ priority=high; │ control via lighthouse │ managed by Contoso │ +│ │ │ (33333333-3333-3333-… │ │ strongest=Owner; │ │ Corp.; strongest role │ +│ │ │ │ │ eligible=1; │ │ Owner; 1 eligible │ +│ │ │ │ │ delegated-role-assig… │ │ authorization(s) │ +│ external-ci-bridge │ external-sp │ 66666666-6666-6666-6… │ tenant │ priority=high; │ pivot via external-sp │ Service principal │ +│ │ │ │ │ roles=Owner; │ │ 'external-ci-bridge' │ +│ │ │ │ │ assignments=2; │ │ appears to be owned │ +│ │ │ │ │ scopes=1 │ │ by another tenant and │ +│ │ │ │ │ │ │ holds high-impact │ +│ │ │ │ │ │ │ Azure role │ +│ │ │ │ │ │ │ assignments in the │ +│ │ │ │ │ │ │ current environment. │ +│ Authorization Policy │ policy │ 11111111-1111-1111-1… │ tenant │ priority=high; │ entry via policy │ guest invites: │ +│ │ │ │ │ guest-invites=everyo… │ │ everyone; users can │ +│ │ │ │ │ app-registration=yes; │ │ register apps; │ +│ │ │ │ │ user-consent=self-se… │ │ self-service │ +│ │ │ │ │ │ │ permission grant │ +│ │ │ │ │ │ │ policies assigned; │ +│ │ │ │ │ │ │ legacy MSOL │ +│ │ │ │ │ │ │ PowerShell blocked │ +└──────────────────────┴─────────────┴───────────────────────┴────────────────────────┴───────────────────────┴────────────────────────┴───────────────────────┘ + +Takeaway: 3 cross-tenant signal(s) visible; 3 high priority, 1 delegated management, 1 externally owned service principal, and 1 tenant policy cue. + diff --git a/command-output/databases/output.json b/command-output/databases/output.json new file mode 100644 index 0000000..d833f3b --- /dev/null +++ b/command-output/databases/output.json @@ -0,0 +1,98 @@ +{ + "database_servers": [ + { + "database_count": 2, + "delegated_subnet_resource_id": null, + "engine": "AzureSql", + "fully_qualified_domain_name": "sql-public-legacy.database.windows.net", + "high_availability_mode": null, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Sql/servers/sql-public-legacy", + "location": "eastus", + "minimal_tls_version": "1.2", + "name": "sql-public-legacy", + "private_dns_zone_resource_id": null, + "public_network_access": "Enabled", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Sql/servers/sql-public-legacy" + ], + "resource_group": "rg-data", + "server_version": "12.0", + "state": "Ready", + "summary": "Azure SQL server 'sql-public-legacy' publishes endpoint 'sql-public-legacy.database.windows.net' and has no managed identity visible from the current read path. Visible inventory: 2 user database(s), names: orders, reporting. Visible posture: public network access Enabled, minimal TLS 1.2, server version 12.0.", + "user_database_names": [ + "orders", + "reporting" + ], + "workload_client_id": null, + "workload_identity_ids": [], + "workload_identity_type": null, + "workload_principal_id": null + }, + { + "database_count": 2, + "delegated_subnet_resource_id": null, + "engine": "PostgreSqlFlexible", + "fully_qualified_domain_name": "pg-public-legacy.postgres.database.azure.com", + "high_availability_mode": "disabled", + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.DBforPostgreSQL/flexibleServers/pg-public-legacy", + "location": "eastus2", + "minimal_tls_version": null, + "name": "pg-public-legacy", + "private_dns_zone_resource_id": null, + "public_network_access": "Enabled", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.DBforPostgreSQL/flexibleServers/pg-public-legacy" + ], + "resource_group": "rg-data", + "server_version": "16", + "state": "Ready", + "summary": "PostgreSQL Flexible server 'pg-public-legacy' publishes endpoint 'pg-public-legacy.postgres.database.azure.com' and has no managed identity visible from the current read path. Visible inventory: 2 user database(s), names: app, orders. Visible posture: public network access Enabled, server version 16, HA disabled.", + "user_database_names": [ + "app", + "orders" + ], + "workload_client_id": null, + "workload_identity_ids": [], + "workload_identity_type": null, + "workload_principal_id": null + }, + { + "database_count": 1, + "delegated_subnet_resource_id": null, + "engine": "AzureSql", + "fully_qualified_domain_name": "sql-ops-01.database.windows.net", + "high_availability_mode": null, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Sql/servers/sql-ops-01", + "location": "centralus", + "minimal_tls_version": "1.2", + "name": "sql-ops-01", + "private_dns_zone_resource_id": null, + "public_network_access": "Disabled", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Sql/servers/sql-ops-01", + "99990000-0000-0000-0000-000000000041" + ], + "resource_group": "rg-data", + "server_version": "12.0", + "state": "Ready", + "summary": "Azure SQL server 'sql-ops-01' publishes endpoint 'sql-ops-01.database.windows.net' and uses managed identity (SystemAssigned). Visible inventory: 1 user database(s), names: appdb. Visible posture: public network access Disabled, minimal TLS 1.2, server version 12.0.", + "user_database_names": [ + "appdb" + ], + "workload_client_id": "99990000-0000-0000-0000-000000000042", + "workload_identity_ids": [], + "workload_identity_type": "SystemAssigned", + "workload_principal_id": "99990000-0000-0000-0000-000000000041" + } + ], + "findings": [], + "issues": [], + "metadata": { + "command": "databases", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + } +} diff --git a/command-output/databases/table.txt b/command-output/databases/table.txt new file mode 100644 index 0000000..6f60d28 --- /dev/null +++ b/command-output/databases/table.txt @@ -0,0 +1,67 @@ + azurefox databases +┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓ +┃ server ┃ engine ┃ endpoint ┃ identity ┃ inventory ┃ exposure ┃ posture ┃ why it matters ┃ +┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩ +│ sql-public-legacy │ AzureSql │ sql-public-legac… │ - │ dbs=2; │ fqdn; │ tls=1.2; │ Azure SQL server │ +│ │ │ │ │ orders,reporting │ public=Enabled │ version=12.0; │ 'sql-public-lega… │ +│ │ │ │ │ │ │ state=Ready │ publishes │ +│ │ │ │ │ │ │ │ endpoint │ +│ │ │ │ │ │ │ │ 'sql-public-lega… │ +│ │ │ │ │ │ │ │ and has no │ +│ │ │ │ │ │ │ │ managed identity │ +│ │ │ │ │ │ │ │ visible from the │ +│ │ │ │ │ │ │ │ current read │ +│ │ │ │ │ │ │ │ path. Visible │ +│ │ │ │ │ │ │ │ inventory: 2 user │ +│ │ │ │ │ │ │ │ database(s), │ +│ │ │ │ │ │ │ │ names: orders, │ +│ │ │ │ │ │ │ │ reporting. │ +│ │ │ │ │ │ │ │ Visible posture: │ +│ │ │ │ │ │ │ │ public network │ +│ │ │ │ │ │ │ │ access Enabled, │ +│ │ │ │ │ │ │ │ minimal TLS 1.2, │ +│ │ │ │ │ │ │ │ server version │ +│ │ │ │ │ │ │ │ 12.0. │ +│ pg-public-legacy │ PostgreSqlFlexib… │ pg-public-legacy… │ - │ dbs=2; app,orders │ fqdn; │ version=16; │ PostgreSQL │ +│ │ │ │ │ │ public=Enabled │ ha=disabled; │ Flexible server │ +│ │ │ │ │ │ │ state=Ready │ 'pg-public-legac… │ +│ │ │ │ │ │ │ │ publishes │ +│ │ │ │ │ │ │ │ endpoint │ +│ │ │ │ │ │ │ │ 'pg-public-legac… │ +│ │ │ │ │ │ │ │ and has no │ +│ │ │ │ │ │ │ │ managed identity │ +│ │ │ │ │ │ │ │ visible from the │ +│ │ │ │ │ │ │ │ current read │ +│ │ │ │ │ │ │ │ path. Visible │ +│ │ │ │ │ │ │ │ inventory: 2 user │ +│ │ │ │ │ │ │ │ database(s), │ +│ │ │ │ │ │ │ │ names: app, │ +│ │ │ │ │ │ │ │ orders. Visible │ +│ │ │ │ │ │ │ │ posture: public │ +│ │ │ │ │ │ │ │ network access │ +│ │ │ │ │ │ │ │ Enabled, server │ +│ │ │ │ │ │ │ │ version 16, HA │ +│ │ │ │ │ │ │ │ disabled. │ +│ sql-ops-01 │ AzureSql │ sql-ops-01.datab… │ SystemAssigned │ dbs=1; appdb │ fqdn; │ tls=1.2; │ Azure SQL server │ +│ │ │ │ │ │ public=Disabled │ version=12.0; │ 'sql-ops-01' │ +│ │ │ │ │ │ │ state=Ready │ publishes │ +│ │ │ │ │ │ │ │ endpoint │ +│ │ │ │ │ │ │ │ 'sql-ops-01.data… │ +│ │ │ │ │ │ │ │ and uses managed │ +│ │ │ │ │ │ │ │ identity │ +│ │ │ │ │ │ │ │ (SystemAssigned). │ +│ │ │ │ │ │ │ │ Visible │ +│ │ │ │ │ │ │ │ inventory: 1 user │ +│ │ │ │ │ │ │ │ database(s), │ +│ │ │ │ │ │ │ │ names: appdb. │ +│ │ │ │ │ │ │ │ Visible posture: │ +│ │ │ │ │ │ │ │ public network │ +│ │ │ │ │ │ │ │ access Disabled, │ +│ │ │ │ │ │ │ │ minimal TLS 1.2, │ +│ │ │ │ │ │ │ │ server version │ +│ │ │ │ │ │ │ │ 12.0. │ +└───────────────────┴───────────────────┴───────────────────┴────────────────┴────────────────────┴───────────────────┴────────────────────┴───────────────────┘ + +Takeaway: 3 relational database servers visible across 2 engine families; 2 keep public network access enabled, 1 carry managed identity context, and 5 user +databases are visible. + diff --git a/command-output/devops/output.json b/command-output/devops/output.json new file mode 100644 index 0000000..f625c64 --- /dev/null +++ b/command-output/devops/output.json @@ -0,0 +1,176 @@ +{ + "findings": [], + "issues": [], + "metadata": { + "command": "devops", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "pipelines": [ + { + "azure_service_connection_auth_schemes": [ + "ServicePrincipal" + ], + "azure_service_connection_names": [ + "prod-subscription" + ], + "azure_service_connection_types": [ + "azurerm" + ], + "default_branch": "refs/heads/main", + "definition_id": "17", + "id": "https://dev.azure.com/contoso/prod-platform/_build?definitionId=17", + "key_vault_group_names": [ + "prod-kv-release" + ], + "key_vault_names": [ + "kv-prod-shared" + ], + "name": "deploy-aks-prod", + "partial_read": false, + "path": "\\Production", + "project_id": "4f90f8a6-1111-4444-8888-0a12b45c67de", + "project_name": "prod-platform", + "related_ids": [ + "https://dev.azure.com/contoso/prod-platform/_build?definitionId=17", + "08d28dfa-11d2-4c61-8d71-a9524cfdfdf0", + "91", + "92", + "kv-prod-shared" + ], + "repository_name": "platform-api", + "repository_type": "TfsGit", + "risk_cues": [ + "auto-triggered", + "azure deployment connection", + "secret-bearing variables", + "key vault-backed variables" + ], + "secret_variable_count": 8, + "secret_variable_names": [ + "ACR_PUSH_PASSWORD", + "AKS_CLUSTER_NAME", + "AKS_RESOURCE_GROUP", + "HELM_REPO_PASSWORD", + "PROD_API_TOKEN", + "PROD_CLIENT_SECRET", + "PROD_DB_PASSWORD", + "PROD_SIGNING_KEY" + ], + "summary": "Build definition 'deploy-aks-prod' in project 'prod-platform' looks like a named Azure change path. reads from repo platform-api@refs/heads/main. auto-triggers on source changes. uses Azure-facing service connection(s) prod-subscription. references variable group(s) prod-release-secrets, prod-kv-release. surfaces 8 secret-marked variable name(s). pulls from Key Vault-backed variable support (kv-prod-shared). shows deployment cues for AKS/Kubernetes, ACR/Containers. Check aks for the named deployment target; review permissions and role-trusts for Azure control; review keyvault for the vault-backed support.", + "target_clues": [ + "AKS/Kubernetes", + "ACR/Containers" + ], + "trigger_types": [ + "continuousIntegration" + ], + "variable_group_names": [ + "prod-release-secrets", + "prod-kv-release" + ] + }, + { + "azure_service_connection_auth_schemes": [ + "WorkloadIdentityFederation" + ], + "azure_service_connection_names": [ + "prod-appsvc-wif" + ], + "azure_service_connection_types": [ + "azurerm" + ], + "default_branch": "refs/heads/main", + "definition_id": "23", + "id": "https://dev.azure.com/contoso/prod-platform/_build?definitionId=23", + "key_vault_group_names": [], + "key_vault_names": [], + "name": "deploy-appservice-prod", + "partial_read": false, + "path": "\\Production", + "project_id": "4f90f8a6-1111-4444-8888-0a12b45c67de", + "project_name": "prod-platform", + "related_ids": [ + "https://dev.azure.com/contoso/prod-platform/_build?definitionId=23", + "42b8f88f-2bf0-4c2d-b9db-e4475f9b1176", + "87" + ], + "repository_name": "customer-portal", + "repository_type": "TfsGit", + "risk_cues": [ + "auto-triggered", + "azure deployment connection", + "secret-bearing variables" + ], + "secret_variable_count": 3, + "secret_variable_names": [ + "APPINSIGHTS_KEY", + "PROD_SLOT_PUBLISH_PROFILE", + "WEBSITE_SWAP_AUTH" + ], + "summary": "Build definition 'deploy-appservice-prod' in project 'prod-platform' looks like a named Azure change path. reads from repo customer-portal@refs/heads/main. includes pull-request trigger posture. uses Azure-facing service connection(s) prod-appsvc-wif. references variable group(s) prod-appsvc-release. surfaces 3 secret-marked variable name(s). shows deployment cues for App Service. Check app-services for the named deployment target; review permissions and role-trusts for Azure control.", + "target_clues": [ + "App Service" + ], + "trigger_types": [ + "pullRequest" + ], + "variable_group_names": [ + "prod-appsvc-release" + ] + }, + { + "azure_service_connection_auth_schemes": [ + "ManagedServiceIdentity" + ], + "azure_service_connection_names": [ + "infra-subscription" + ], + "azure_service_connection_types": [ + "azurerm" + ], + "default_branch": "refs/heads/main", + "definition_id": "34", + "id": "https://dev.azure.com/contoso/platform-infra/_build?definitionId=34", + "key_vault_group_names": [ + "platform-kv" + ], + "key_vault_names": [ + "kv-platform-shared" + ], + "name": "plan-infra-prod", + "partial_read": false, + "path": "\\Infra", + "project_id": "145c7175-2222-4bc2-b4d1-f31f0a8f962a", + "project_name": "platform-infra", + "related_ids": [ + "https://dev.azure.com/contoso/platform-infra/_build?definitionId=34", + "7d9fe45d-9114-402c-a49d-f8ac1c1910ea", + "12", + "kv-platform-shared" + ], + "repository_name": "infra-live", + "repository_type": "GitHub", + "risk_cues": [ + "auto-triggered", + "azure deployment connection", + "key vault-backed variables" + ], + "secret_variable_count": 0, + "secret_variable_names": [], + "summary": "Build definition 'plan-infra-prod' in project 'platform-infra' looks like a named Azure change path. reads from repo infra-live@refs/heads/main. runs on a schedule. uses Azure-facing service connection(s) infra-subscription. references variable group(s) platform-kv. pulls from Key Vault-backed variable support (kv-platform-shared). shows deployment cues for ARM/Bicep/Terraform. Check arm-deployments for the named deployment target; review permissions and role-trusts for Azure control; review keyvault for the vault-backed support.", + "target_clues": [ + "ARM/Bicep/Terraform" + ], + "trigger_types": [ + "schedule" + ], + "variable_group_names": [ + "platform-kv" + ] + } + ] +} diff --git a/command-output/devops/table.txt b/command-output/devops/table.txt new file mode 100644 index 0000000..eb14625 --- /dev/null +++ b/command-output/devops/table.txt @@ -0,0 +1,133 @@ + azurefox devops +┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓ +┃ project ┃ pipeline ┃ repo ┃ triggers ┃ azure access ┃ secret support ┃ target clues ┃ next review ┃ why it matters ┃ +┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩ +│ prod-platform │ deploy-aks-prod │ platform-api@r… │ continuousInte… │ conn=prod-subs… │ groups=prod-r… │ AKS/Kubernetes… │ Check aks for │ Build │ +│ │ │ TfsGit │ auto-triggered │ auth=ServicePr… │ secrets=8; │ risk=auto-trig… │ the named │ definition │ +│ │ │ │ │ type=azurerm │ kv=kv-prod-sh… │ deployment │ deployment │ 'deploy-aks-pr… │ +│ │ │ │ │ │ │ connection,sec… │ target; review │ in project │ +│ │ │ │ │ │ │ variables,key │ permissions │ 'prod-platform' │ +│ │ │ │ │ │ │ vault-backed │ and │ looks like a │ +│ │ │ │ │ │ │ variables │ role-trusts │ named Azure │ +│ │ │ │ │ │ │ │ for Azure │ change path. │ +│ │ │ │ │ │ │ │ control; │ reads from repo │ +│ │ │ │ │ │ │ │ review │ platform-api@r… │ +│ │ │ │ │ │ │ │ keyvault for │ auto-triggers │ +│ │ │ │ │ │ │ │ the │ on source │ +│ │ │ │ │ │ │ │ vault-backed │ changes. uses │ +│ │ │ │ │ │ │ │ support. │ Azure-facing │ +│ │ │ │ │ │ │ │ │ service │ +│ │ │ │ │ │ │ │ │ connection(s) │ +│ │ │ │ │ │ │ │ │ prod-subscript… │ +│ │ │ │ │ │ │ │ │ references │ +│ │ │ │ │ │ │ │ │ variable │ +│ │ │ │ │ │ │ │ │ group(s) │ +│ │ │ │ │ │ │ │ │ prod-release-s… │ +│ │ │ │ │ │ │ │ │ prod-kv-releas… │ +│ │ │ │ │ │ │ │ │ surfaces 8 │ +│ │ │ │ │ │ │ │ │ secret-marked │ +│ │ │ │ │ │ │ │ │ variable │ +│ │ │ │ │ │ │ │ │ name(s). pulls │ +│ │ │ │ │ │ │ │ │ from Key │ +│ │ │ │ │ │ │ │ │ Vault-backed │ +│ │ │ │ │ │ │ │ │ variable │ +│ │ │ │ │ │ │ │ │ support │ +│ │ │ │ │ │ │ │ │ (kv-prod-share… │ +│ │ │ │ │ │ │ │ │ shows │ +│ │ │ │ │ │ │ │ │ deployment cues │ +│ │ │ │ │ │ │ │ │ for │ +│ │ │ │ │ │ │ │ │ AKS/Kubernetes, │ +│ │ │ │ │ │ │ │ │ ACR/Containers. │ +│ │ │ │ │ │ │ │ │ Check aks for │ +│ │ │ │ │ │ │ │ │ the named │ +│ │ │ │ │ │ │ │ │ deployment │ +│ │ │ │ │ │ │ │ │ target; review │ +│ │ │ │ │ │ │ │ │ permissions and │ +│ │ │ │ │ │ │ │ │ role-trusts for │ +│ │ │ │ │ │ │ │ │ Azure control; │ +│ │ │ │ │ │ │ │ │ review keyvault │ +│ │ │ │ │ │ │ │ │ for the │ +│ │ │ │ │ │ │ │ │ vault-backed │ +│ │ │ │ │ │ │ │ │ support. │ +│ prod-platform │ deploy-appserv… │ customer-porta… │ pullRequest; │ conn=prod-apps… │ groups=prod-a… │ App Service; │ Check │ Build │ +│ │ │ TfsGit │ auto-triggered │ auth=WorkloadI… │ secrets=3 │ risk=auto-trig… │ app-services │ definition │ +│ │ │ │ │ type=azurerm │ │ deployment │ for the named │ 'deploy-appser… │ +│ │ │ │ │ │ │ connection,sec… │ deployment │ in project │ +│ │ │ │ │ │ │ variables │ target; review │ 'prod-platform' │ +│ │ │ │ │ │ │ │ permissions │ looks like a │ +│ │ │ │ │ │ │ │ and │ named Azure │ +│ │ │ │ │ │ │ │ role-trusts │ change path. │ +│ │ │ │ │ │ │ │ for Azure │ reads from repo │ +│ │ │ │ │ │ │ │ control. │ customer-porta… │ +│ │ │ │ │ │ │ │ │ includes │ +│ │ │ │ │ │ │ │ │ pull-request │ +│ │ │ │ │ │ │ │ │ trigger │ +│ │ │ │ │ │ │ │ │ posture. uses │ +│ │ │ │ │ │ │ │ │ Azure-facing │ +│ │ │ │ │ │ │ │ │ service │ +│ │ │ │ │ │ │ │ │ connection(s) │ +│ │ │ │ │ │ │ │ │ prod-appsvc-wi… │ +│ │ │ │ │ │ │ │ │ references │ +│ │ │ │ │ │ │ │ │ variable │ +│ │ │ │ │ │ │ │ │ group(s) │ +│ │ │ │ │ │ │ │ │ prod-appsvc-re… │ +│ │ │ │ │ │ │ │ │ surfaces 3 │ +│ │ │ │ │ │ │ │ │ secret-marked │ +│ │ │ │ │ │ │ │ │ variable │ +│ │ │ │ │ │ │ │ │ name(s). shows │ +│ │ │ │ │ │ │ │ │ deployment cues │ +│ │ │ │ │ │ │ │ │ for App │ +│ │ │ │ │ │ │ │ │ Service. Check │ +│ │ │ │ │ │ │ │ │ app-services │ +│ │ │ │ │ │ │ │ │ for the named │ +│ │ │ │ │ │ │ │ │ deployment │ +│ │ │ │ │ │ │ │ │ target; review │ +│ │ │ │ │ │ │ │ │ permissions and │ +│ │ │ │ │ │ │ │ │ role-trusts for │ +│ │ │ │ │ │ │ │ │ Azure control. │ +│ platform-infra │ plan-infra-prod │ infra-live@ref… │ schedule; │ conn=infra-sub… │ groups=platfo… │ ARM/Bicep/Terr… │ Check │ Build │ +│ │ │ GitHub │ auto-triggered │ auth=ManagedSe… │ kv=kv-platfor… │ risk=auto-trig… │ arm-deploymen… │ definition │ +│ │ │ │ │ type=azurerm │ │ deployment │ for the named │ 'plan-infra-pr… │ +│ │ │ │ │ │ │ connection,key │ deployment │ in project │ +│ │ │ │ │ │ │ vault-backed │ target; review │ 'platform-infr… │ +│ │ │ │ │ │ │ variables │ permissions │ looks like a │ +│ │ │ │ │ │ │ │ and │ named Azure │ +│ │ │ │ │ │ │ │ role-trusts │ change path. │ +│ │ │ │ │ │ │ │ for Azure │ reads from repo │ +│ │ │ │ │ │ │ │ control; │ infra-live@ref… │ +│ │ │ │ │ │ │ │ review │ runs on a │ +│ │ │ │ │ │ │ │ keyvault for │ schedule. uses │ +│ │ │ │ │ │ │ │ the │ Azure-facing │ +│ │ │ │ │ │ │ │ vault-backed │ service │ +│ │ │ │ │ │ │ │ support. │ connection(s) │ +│ │ │ │ │ │ │ │ │ infra-subscrip… │ +│ │ │ │ │ │ │ │ │ references │ +│ │ │ │ │ │ │ │ │ variable │ +│ │ │ │ │ │ │ │ │ group(s) │ +│ │ │ │ │ │ │ │ │ platform-kv. │ +│ │ │ │ │ │ │ │ │ pulls from Key │ +│ │ │ │ │ │ │ │ │ Vault-backed │ +│ │ │ │ │ │ │ │ │ variable │ +│ │ │ │ │ │ │ │ │ support │ +│ │ │ │ │ │ │ │ │ (kv-platform-s… │ +│ │ │ │ │ │ │ │ │ shows │ +│ │ │ │ │ │ │ │ │ deployment cues │ +│ │ │ │ │ │ │ │ │ for │ +│ │ │ │ │ │ │ │ │ ARM/Bicep/Terr… │ +│ │ │ │ │ │ │ │ │ Check │ +│ │ │ │ │ │ │ │ │ arm-deployments │ +│ │ │ │ │ │ │ │ │ for the named │ +│ │ │ │ │ │ │ │ │ deployment │ +│ │ │ │ │ │ │ │ │ target; review │ +│ │ │ │ │ │ │ │ │ permissions and │ +│ │ │ │ │ │ │ │ │ role-trusts for │ +│ │ │ │ │ │ │ │ │ Azure control; │ +│ │ │ │ │ │ │ │ │ review keyvault │ +│ │ │ │ │ │ │ │ │ for the │ +│ │ │ │ │ │ │ │ │ vault-backed │ +│ │ │ │ │ │ │ │ │ support. │ +└────────────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────┴────────────────┴─────────────────┴────────────────┴─────────────────┘ + +Takeaway: 3 Azure DevOps build definition(s) surfaced; 3 show Azure-facing service connections, 2 carry secret-bearing variable support, 2 use Key Vault-backed +groups, and 3 are auto-triggered. + diff --git a/command-output/dns/output.json b/command-output/dns/output.json new file mode 100644 index 0000000..9a1a027 --- /dev/null +++ b/command-output/dns/output.json @@ -0,0 +1,77 @@ +{ + "dns_zones": [ + { + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-network/providers/Microsoft.Network/dnszones/corp.example.com", + "linked_virtual_network_count": null, + "location": "global", + "max_record_set_count": 10000, + "name": "corp.example.com", + "name_servers": [ + "ns1-01.azure-dns.com", + "ns2-01.azure-dns.net", + "ns3-01.azure-dns.org", + "ns4-01.azure-dns.info" + ], + "private_endpoint_reference_count": null, + "record_set_count": 9, + "registration_virtual_network_count": null, + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-network/providers/Microsoft.Network/dnszones/corp.example.com" + ], + "resource_group": "rg-network", + "summary": "Public DNS zone 'corp.example.com' shows 9 visible record set(s) and delegates authority through 4 visible Azure name server(s).", + "zone_kind": "public" + }, + { + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-edge/providers/Microsoft.Network/dnszones/partner.example.net", + "linked_virtual_network_count": null, + "location": "global", + "max_record_set_count": 10000, + "name": "partner.example.net", + "name_servers": [ + "ns1-08.azure-dns.com", + "ns2-08.azure-dns.net", + "ns3-08.azure-dns.org", + "ns4-08.azure-dns.info" + ], + "private_endpoint_reference_count": null, + "record_set_count": 4, + "registration_virtual_network_count": null, + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-edge/providers/Microsoft.Network/dnszones/partner.example.net" + ], + "resource_group": "rg-edge", + "summary": "Public DNS zone 'partner.example.net' shows 4 visible record set(s) and delegates authority through 4 visible Azure name server(s).", + "zone_kind": "public" + }, + { + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-network/providers/Microsoft.Network/privateDnsZones/privatelink.database.windows.net", + "linked_virtual_network_count": 2, + "location": "global", + "max_record_set_count": 25000, + "name": "privatelink.database.windows.net", + "name_servers": [], + "private_endpoint_reference_count": 2, + "record_set_count": 6, + "registration_virtual_network_count": 1, + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-network/providers/Microsoft.Network/privateDnsZones/privatelink.database.windows.net", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Network/privateEndpoints/pe-sql-primary", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Network/privateEndpoints/pe-sql-replica" + ], + "resource_group": "rg-network", + "summary": "Private DNS zone 'privatelink.database.windows.net' shows 6 visible record set(s) and tracks 2 virtual network link(s), 1 registration-enabled link(s), 2 visible private endpoint reference(s).", + "zone_kind": "private" + } + ], + "findings": [], + "issues": [], + "metadata": { + "command": "dns", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + } +} diff --git a/command-output/dns/table.txt b/command-output/dns/table.txt new file mode 100644 index 0000000..685559e --- /dev/null +++ b/command-output/dns/table.txt @@ -0,0 +1,18 @@ + azurefox dns +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ zone ┃ kind ┃ inventory ┃ namespace ┃ why it matters ┃ +┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ corp.example.com │ public │ records=9/10000 │ ns=4 │ Public DNS zone 'corp.example.com' shows 9 visible │ +│ │ │ │ │ record set(s) and delegates authority through 4 │ +│ │ │ │ │ visible Azure name server(s). │ +│ partner.example.net │ public │ records=4/10000 │ ns=4 │ Public DNS zone 'partner.example.net' shows 4 visible │ +│ │ │ │ │ record set(s) and delegates authority through 4 │ +│ │ │ │ │ visible Azure name server(s). │ +│ privatelink.database.windows.net │ private │ records=6/25000 │ vnet-links=2; reg-links=1; pe-refs=2 │ Private DNS zone 'privatelink.database.windows.net' │ +│ │ │ │ │ shows 6 visible record set(s) and tracks 2 virtual │ +│ │ │ │ │ network link(s), 1 registration-enabled link(s), 2 │ +│ │ │ │ │ visible private endpoint reference(s). │ +└──────────────────────────────────┴─────────┴─────────────────┴──────────────────────────────────────┴────────────────────────────────────────────────────────┘ + +Takeaway: 3 DNS zones visible; 2 public, 1 private, 1 private zone(s) show visible private endpoint references, and 19 record sets are visible. + diff --git a/command-output/endpoints/output.json b/command-output/endpoints/output.json new file mode 100644 index 0000000..ee6a522 --- /dev/null +++ b/command-output/endpoints/output.json @@ -0,0 +1,57 @@ +{ + "endpoints": [ + { + "endpoint": "52.160.10.20", + "endpoint_type": "ip", + "exposure_family": "public-ip", + "ingress_path": "direct-vm-ip", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/networkInterfaces/nic-web-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-app" + ], + "source_asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "source_asset_kind": "VM", + "source_asset_name": "vm-web-01", + "summary": "VM 'vm-web-01' exposes public IP 52.160.10.20. Review direct ingress path alongside NIC and NSG context." + }, + { + "endpoint": "app-empty-mi.azurewebsites.net", + "endpoint_type": "hostname", + "exposure_family": "managed-web-hostname", + "ingress_path": "azurewebsites-default-hostname", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-empty-mi", + "eeee3333-3333-3333-3333-333333333333" + ], + "source_asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-empty-mi", + "source_asset_kind": "AppService", + "source_asset_name": "app-empty-mi", + "summary": "AppService 'app-empty-mi' publishes Azure-managed hostname 'app-empty-mi.azurewebsites.net'. Validate whether that ingress path is intended and how it is constrained." + }, + { + "endpoint": "app-public-api.azurewebsites.net", + "endpoint_type": "hostname", + "exposure_family": "managed-web-hostname", + "ingress_path": "azurewebsites-default-hostname", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api", + "aaaa1111-1111-1111-1111-111111111111" + ], + "source_asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api", + "source_asset_kind": "AppService", + "source_asset_name": "app-public-api", + "summary": "AppService 'app-public-api' publishes Azure-managed hostname 'app-public-api.azurewebsites.net'. Validate whether that ingress path is intended and how it is constrained." + } + ], + "findings": [], + "issues": [], + "metadata": { + "command": "endpoints", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + } +} diff --git a/command-output/endpoints/table.txt b/command-output/endpoints/table.txt new file mode 100644 index 0000000..1312b37 --- /dev/null +++ b/command-output/endpoints/table.txt @@ -0,0 +1,23 @@ + azurefox endpoints +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ endpoint ┃ asset ┃ kind ┃ family ┃ ingress ┃ why it matters ┃ +┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ 52.160.10.20 │ vm-web-01 │ VM │ public-ip │ direct-vm-ip │ VM 'vm-web-01' exposes public IP │ +│ │ │ │ │ │ 52.160.10.20. Review direct ingress │ +│ │ │ │ │ │ path alongside NIC and NSG context. │ +│ app-empty-mi.azurewebsites.net │ app-empty-mi │ AppService │ managed-web-hostname │ azurewebsites-default-hostname │ AppService 'app-empty-mi' publishes │ +│ │ │ │ │ │ Azure-managed hostname │ +│ │ │ │ │ │ 'app-empty-mi.azurewebsites.net'. │ +│ │ │ │ │ │ Validate whether that ingress path │ +│ │ │ │ │ │ is intended and how it is │ +│ │ │ │ │ │ constrained. │ +│ app-public-api.azurewebsites.net │ app-public-api │ AppService │ managed-web-hostname │ azurewebsites-default-hostname │ AppService 'app-public-api' │ +│ │ │ │ │ │ publishes Azure-managed hostname │ +│ │ │ │ │ │ 'app-public-api.azurewebsites.net'. │ +│ │ │ │ │ │ Validate whether that ingress path │ +│ │ │ │ │ │ is intended and how it is │ +│ │ │ │ │ │ constrained. │ +└──────────────────────────────────┴────────────────┴────────────┴──────────────────────┴────────────────────────────────┴─────────────────────────────────────┘ + +Takeaway: 3 reachable surfaces visible; 1 public-ip, 2 managed-web-hostname. + diff --git a/command-output/env-vars/output.json b/command-output/env-vars/output.json new file mode 100644 index 0000000..cd8563e --- /dev/null +++ b/command-output/env-vars/output.json @@ -0,0 +1,95 @@ +{ + "env_vars": [ + { + "asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api", + "asset_kind": "AppService", + "asset_name": "app-public-api", + "key_vault_reference_identity": null, + "location": "eastus", + "looks_sensitive": true, + "reference_target": null, + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api" + ], + "resource_group": "rg-apps", + "setting_name": "DB_PASSWORD", + "summary": "AppService 'app-public-api' stores sensitive-looking setting 'DB_PASSWORD' as plain-text app configuration. Check tokens-credentials first; this likely feeds a database credential path.", + "value_type": "plain-text", + "workload_client_id": "bbbb1111-1111-1111-1111-111111111111", + "workload_identity_ids": [], + "workload_identity_type": "SystemAssigned", + "workload_principal_id": "aaaa1111-1111-1111-1111-111111111111" + }, + { + "asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders", + "asset_kind": "FunctionApp", + "asset_name": "func-orders", + "key_vault_reference_identity": "SystemAssigned", + "location": "eastus", + "looks_sensitive": true, + "reference_target": "kvlabopen01.vault.azure.net/secrets/payment-api-key", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders" + ], + "resource_group": "rg-apps", + "setting_name": "PAYMENT_API_KEY", + "summary": "FunctionApp 'func-orders' maps setting 'PAYMENT_API_KEY' to Key Vault-backed configuration (kvlabopen01.vault.azure.net/secrets/payment-api-key) via SystemAssigned identity. Check keyvault for the referenced secret path; review managed-identities for the workload token path.", + "value_type": "keyvault-ref", + "workload_client_id": "dddd2222-2222-2222-2222-222222222222", + "workload_identity_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-identities/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-orders" + ], + "workload_identity_type": "SystemAssigned, UserAssigned", + "workload_principal_id": "cccc2222-2222-2222-2222-222222222222" + }, + { + "asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api", + "asset_kind": "AppService", + "asset_name": "app-public-api", + "key_vault_reference_identity": null, + "location": "eastus", + "looks_sensitive": false, + "reference_target": null, + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api" + ], + "resource_group": "rg-apps", + "setting_name": "API_BASE_URL", + "summary": "AppService 'app-public-api' exposes setting 'API_BASE_URL' through management-plane app settings (plain-text). Check managed-identities for the workload token path behind this setting.", + "value_type": "plain-text", + "workload_client_id": "bbbb1111-1111-1111-1111-111111111111", + "workload_identity_ids": [], + "workload_identity_type": "SystemAssigned", + "workload_principal_id": "aaaa1111-1111-1111-1111-111111111111" + } + ], + "findings": [ + { + "description": "AppService 'app-public-api' stores setting 'DB_PASSWORD' as plain-text management-plane config.", + "id": "env-var-plain-sensitive-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api-DB_PASSWORD", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api" + ], + "severity": "medium", + "title": "Sensitive-looking app setting is stored in plain text" + }, + { + "description": "FunctionApp 'func-orders' maps setting 'PAYMENT_API_KEY' to Key Vault-backed configuration (kvlabopen01.vault.azure.net/secrets/payment-api-key).", + "id": "env-var-keyvault-ref-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders-PAYMENT_API_KEY", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders" + ], + "severity": "low", + "title": "App setting references Key Vault" + } + ], + "issues": [], + "metadata": { + "command": "env-vars", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + } +} diff --git a/command-output/env-vars/table.txt b/command-output/env-vars/table.txt new file mode 100644 index 0000000..7402dac --- /dev/null +++ b/command-output/env-vars/table.txt @@ -0,0 +1,52 @@ + azurefox env-vars +┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ workload ┃ kind ┃ identity ┃ setting ┃ value type ┃ signal ┃ next review ┃ why it matters ┃ +┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━┩ +│ app-public-api │ AppService │ SystemAssigned │ DB_PASSWORD │ plain-text │ sensitive-name │ Check │ AppService │ +│ │ │ │ │ │ │ tokens-credentials │ 'app-public-api' │ +│ │ │ │ │ │ │ first; this likely │ stores │ +│ │ │ │ │ │ │ feeds a database │ sensitive-looking │ +│ │ │ │ │ │ │ credential path. │ setting 'DB_PASSWORD' │ +│ │ │ │ │ │ │ │ as plain-text app │ +│ │ │ │ │ │ │ │ configuration. Check │ +│ │ │ │ │ │ │ │ tokens-credentials │ +│ │ │ │ │ │ │ │ first; this likely │ +│ │ │ │ │ │ │ │ feeds a database │ +│ │ │ │ │ │ │ │ credential path. │ +│ func-orders │ FunctionApp │ SystemAssigned, │ PAYMENT_API_KEY │ keyvault-ref │ sensitive-name; │ Check keyvault for │ FunctionApp │ +│ │ │ UserAssigned; │ │ │ keyvault-ref; │ the referenced │ 'func-orders' maps │ +│ │ │ user-assigned=1; │ │ │ kvlabopen01.vault.az… │ secret path; review │ setting │ +│ │ │ kv-ref=SystemAssigned │ │ │ │ managed-identities │ 'PAYMENT_API_KEY' to │ +│ │ │ │ │ │ │ for the workload │ Key Vault-backed │ +│ │ │ │ │ │ │ token path. │ configuration │ +│ │ │ │ │ │ │ │ (kvlabopen01.vault.a… │ +│ │ │ │ │ │ │ │ via SystemAssigned │ +│ │ │ │ │ │ │ │ identity. Check │ +│ │ │ │ │ │ │ │ keyvault for the │ +│ │ │ │ │ │ │ │ referenced secret │ +│ │ │ │ │ │ │ │ path; review │ +│ │ │ │ │ │ │ │ managed-identities │ +│ │ │ │ │ │ │ │ for the workload │ +│ │ │ │ │ │ │ │ token path. │ +│ app-public-api │ AppService │ SystemAssigned │ API_BASE_URL │ plain-text │ - │ Check │ AppService │ +│ │ │ │ │ │ │ managed-identities │ 'app-public-api' │ +│ │ │ │ │ │ │ for the workload │ exposes setting │ +│ │ │ │ │ │ │ token path behind │ 'API_BASE_URL' │ +│ │ │ │ │ │ │ this setting. │ through │ +│ │ │ │ │ │ │ │ management-plane app │ +│ │ │ │ │ │ │ │ settings │ +│ │ │ │ │ │ │ │ (plain-text). Check │ +│ │ │ │ │ │ │ │ managed-identities │ +│ │ │ │ │ │ │ │ for the workload │ +│ │ │ │ │ │ │ │ token path behind │ +│ │ │ │ │ │ │ │ this setting. │ +└────────────────┴─────────────┴───────────────────────┴─────────────────┴──────────────┴───────────────────────┴──────────────────────┴───────────────────────┘ + +Findings: +- MEDIUM: Sensitive-looking app setting is stored in plain text + AppService 'app-public-api' stores setting 'DB_PASSWORD' as plain-text management-plane config. +- LOW: App setting references Key Vault + FunctionApp 'func-orders' maps setting 'PAYMENT_API_KEY' to Key Vault-backed configuration (kvlabopen01.vault.azure.net/secrets/payment-api-key). + +Takeaway: 3 settings across 2 workloads; 1 plain-text sensitive settings, 1 Key Vault references, and 2 findings. + diff --git a/command-output/functions/output.json b/command-output/functions/output.json new file mode 100644 index 0000000..7b98c3d --- /dev/null +++ b/command-output/functions/output.json @@ -0,0 +1,48 @@ +{ + "findings": [], + "function_apps": [ + { + "always_on": true, + "app_service_plan_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/serverfarms/asp-functions", + "azure_webjobs_storage_reference_target": null, + "azure_webjobs_storage_value_type": "plain-text", + "client_cert_enabled": false, + "default_hostname": "func-orders.azurewebsites.net", + "ftps_state": "Disabled", + "functions_extension_version": "~4", + "https_only": true, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders", + "key_vault_reference_count": 1, + "location": "eastus", + "min_tls_version": "1.2", + "name": "func-orders", + "public_network_access": "Enabled", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders", + "cccc2222-2222-2222-2222-222222222222", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-identities/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-orders", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/serverfarms/asp-functions" + ], + "resource_group": "rg-apps", + "run_from_package": null, + "runtime_stack": "PYTHON|3.11", + "state": "Running", + "summary": "Function App 'func-orders' publishes hostname 'func-orders.azurewebsites.net', runs runtime 'PYTHON|3.11', targets Functions runtime '~4', and uses managed identity (SystemAssigned, UserAssigned). Deployment signals: AzureWebJobsStorage as plain-text app setting, 1 Key Vault-backed setting(s). Visible posture: public network access Enabled, HTTPS-only enabled, TLS 1.2, FTPS Disabled, Always On enabled.", + "workload_client_id": "dddd2222-2222-2222-2222-222222222222", + "workload_identity_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-identities/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-orders" + ], + "workload_identity_type": "SystemAssigned, UserAssigned", + "workload_principal_id": "cccc2222-2222-2222-2222-222222222222" + } + ], + "issues": [], + "metadata": { + "command": "functions", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + } +} diff --git a/command-output/functions/table.txt b/command-output/functions/table.txt new file mode 100644 index 0000000..813b152 --- /dev/null +++ b/command-output/functions/table.txt @@ -0,0 +1,31 @@ + azurefox functions +┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ function app ┃ hostname ┃ runtime ┃ identity ┃ deployment ┃ posture ┃ why it matters ┃ +┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━┩ +│ func-orders │ func-orders.azureweb… │ PYTHON|3.11; │ SystemAssigned, │ storage=plain-text; │ https=yes; tls=1.2; │ Function App │ +│ │ │ functions=~4 │ UserAssigned; │ kv-refs=1 │ ftps=Disabled; │ 'func-orders' │ +│ │ │ │ user-assigned=1 │ │ always-on=yes │ publishes hostname │ +│ │ │ │ │ │ │ 'func-orders.azurewe… │ +│ │ │ │ │ │ │ runs runtime │ +│ │ │ │ │ │ │ 'PYTHON|3.11', │ +│ │ │ │ │ │ │ targets Functions │ +│ │ │ │ │ │ │ runtime '~4', and │ +│ │ │ │ │ │ │ uses managed identity │ +│ │ │ │ │ │ │ (SystemAssigned, │ +│ │ │ │ │ │ │ UserAssigned). │ +│ │ │ │ │ │ │ Deployment signals: │ +│ │ │ │ │ │ │ AzureWebJobsStorage │ +│ │ │ │ │ │ │ as plain-text app │ +│ │ │ │ │ │ │ setting, 1 Key │ +│ │ │ │ │ │ │ Vault-backed │ +│ │ │ │ │ │ │ setting(s). Visible │ +│ │ │ │ │ │ │ posture: public │ +│ │ │ │ │ │ │ network access │ +│ │ │ │ │ │ │ Enabled, HTTPS-only │ +│ │ │ │ │ │ │ enabled, TLS 1.2, │ +│ │ │ │ │ │ │ FTPS Disabled, Always │ +│ │ │ │ │ │ │ On enabled. │ +└──────────────┴───────────────────────┴───────────────────────┴───────────────────────┴───────────────────────┴───────────────────────┴───────────────────────┘ + +Takeaway: 1 Function Apps visible; 1 carry managed identity context, 0 show run-from-package deployment, and 1 include Key Vault-backed settings. + diff --git a/command-output/inventory/output.json b/command-output/inventory/output.json new file mode 100644 index 0000000..cb4e23f --- /dev/null +++ b/command-output/inventory/output.json @@ -0,0 +1,24 @@ +{ + "issues": [], + "metadata": { + "command": "inventory", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "resource_count": 30, + "resource_group_count": 4, + "subscription": { + "display_name": "azurefox-lab-sub", + "id": "22222222-2222-2222-2222-222222222222", + "state": "Enabled" + }, + "top_resource_types": { + "Microsoft.Compute/virtualMachines": 3, + "Microsoft.ContainerService/managedClusters": 2, + "Microsoft.Network/networkInterfaces": 6, + "Microsoft.Storage/storageAccounts": 2 + } +} diff --git a/command-output/inventory/table.txt b/command-output/inventory/table.txt new file mode 100644 index 0000000..a002cad --- /dev/null +++ b/command-output/inventory/table.txt @@ -0,0 +1,9 @@ + azurefox inventory +┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┓ +┃ resource groups ┃ resources ┃ top type ┃ issues ┃ +┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━┩ +│ 4 │ 30 │ Microsoft.ContainerService/managedClusters │ 0 │ +└─────────────────┴───────────┴────────────────────────────────────────────┴────────┘ + +Takeaway: 30 resources across 4 resource groups. + diff --git a/command-output/keyvault/output.json b/command-output/keyvault/output.json new file mode 100644 index 0000000..4a40168 --- /dev/null +++ b/command-output/keyvault/output.json @@ -0,0 +1,99 @@ +{ + "findings": [ + { + "description": "Key Vault 'kvlabopen01' has public network access enabled, Azure omitted the network ACL object, and no private endpoint is visible. Azure can return that shape for a fully open vault. Review whether that secret-management surface is intentionally internet reachable.", + "id": "keyvault-public-network-open-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabopen01", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabopen01" + ], + "severity": "high", + "title": "Key Vault is broadly reachable on the public network" + }, + { + "description": "Key Vault 'kvlabopen01' does not have purge protection enabled. Validate whether destructive recovery protections are intentionally absent.", + "id": "keyvault-purge-protection-disabled-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabopen01", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabopen01" + ], + "severity": "medium", + "title": "Key Vault purge protection is disabled" + }, + { + "description": "Key Vault 'kvlabdeny01' keeps public network access enabled with default network action 'Deny' and no private endpoint visible. Review whether that public path is still intended.", + "id": "keyvault-public-network-enabled-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabdeny01", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabdeny01" + ], + "severity": "medium", + "title": "Key Vault remains reachable through a public network path" + }, + { + "description": "Key Vault 'kvlabhybrid01' has public network access enabled while a private endpoint is also present. Validate whether the public path is still required.", + "id": "keyvault-public-network-with-private-endpoint-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabhybrid01", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabhybrid01" + ], + "severity": "low", + "title": "Key Vault keeps a public network path alongside Private Link" + } + ], + "issues": [], + "key_vaults": [ + { + "access_policy_count": 2, + "enable_rbac_authorization": false, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabopen01", + "location": "eastus", + "name": "kvlabopen01", + "network_default_action": null, + "private_endpoint_enabled": false, + "public_network_access": "Enabled", + "purge_protection_enabled": false, + "resource_group": "rg-secrets", + "sku_name": "standard", + "soft_delete_enabled": true, + "tenant_id": "11111111-1111-1111-1111-111111111111", + "vault_uri": "https://kvlabopen01.vault.azure.net/" + }, + { + "access_policy_count": 1, + "enable_rbac_authorization": true, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabdeny01", + "location": "eastus", + "name": "kvlabdeny01", + "network_default_action": "Deny", + "private_endpoint_enabled": false, + "public_network_access": "Enabled", + "purge_protection_enabled": true, + "resource_group": "rg-secrets", + "sku_name": "standard", + "soft_delete_enabled": true, + "tenant_id": "11111111-1111-1111-1111-111111111111", + "vault_uri": "https://kvlabdeny01.vault.azure.net/" + }, + { + "access_policy_count": 0, + "enable_rbac_authorization": true, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabhybrid01", + "location": "eastus", + "name": "kvlabhybrid01", + "network_default_action": "Deny", + "private_endpoint_enabled": true, + "public_network_access": "Enabled", + "purge_protection_enabled": true, + "resource_group": "rg-secrets", + "sku_name": "premium", + "soft_delete_enabled": true, + "tenant_id": "11111111-1111-1111-1111-111111111111", + "vault_uri": "https://kvlabhybrid01.vault.azure.net/" + } + ], + "metadata": { + "command": "keyvault", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + } +} diff --git a/command-output/keyvault/table.txt b/command-output/keyvault/table.txt new file mode 100644 index 0000000..13e1f23 --- /dev/null +++ b/command-output/keyvault/table.txt @@ -0,0 +1,23 @@ + azurefox keyvault +┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓ +┃ vault ┃ resource group ┃ public network ┃ default action ┃ private endpoint ┃ purge protection ┃ rbac mode ┃ +┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩ +│ kvlabopen01 │ rg-secrets │ Enabled │ implicit allow (ACL omitted) │ no │ no │ no │ +│ kvlabdeny01 │ rg-secrets │ Enabled │ Deny │ no │ yes │ yes │ +│ kvlabhybrid01 │ rg-secrets │ Enabled │ Deny │ yes │ yes │ yes │ +└───────────────┴────────────────┴────────────────┴──────────────────────────────┴──────────────────┴──────────────────┴───────────┘ + +Findings: +- HIGH: Key Vault is broadly reachable on the public network + Key Vault 'kvlabopen01' has public network access enabled, Azure omitted the network ACL object, and no private endpoint is visible. Azure can return that +shape for a fully open vault. Review whether that secret-management surface is intentionally internet reachable. +- MEDIUM: Key Vault purge protection is disabled + Key Vault 'kvlabopen01' does not have purge protection enabled. Validate whether destructive recovery protections are intentionally absent. +- MEDIUM: Key Vault remains reachable through a public network path + Key Vault 'kvlabdeny01' keeps public network access enabled with default network action 'Deny' and no private endpoint visible. Review whether that public +path is still intended. +- LOW: Key Vault keeps a public network path alongside Private Link + Key Vault 'kvlabhybrid01' has public network access enabled while a private endpoint is also present. Validate whether the public path is still required. + +Takeaway: 3 Key Vault assets visible; 4 exposure or recovery findings. + diff --git a/command-output/lighthouse/output.json b/command-output/lighthouse/output.json new file mode 100644 index 0000000..683f314 --- /dev/null +++ b/command-output/lighthouse/output.json @@ -0,0 +1,131 @@ +{ + "findings": [], + "issues": [], + "lighthouse_delegations": [ + { + "authorization_count": 2, + "definition_provisioning_state": "Succeeded", + "description": "Contoso subscription delegation", + "eligible_authorization_count": 1, + "eligible_principal_count": 1, + "eligible_role_names": [ + "Contributor" + ], + "has_delegated_role_assignments": true, + "has_owner_role": true, + "has_user_access_administrator": true, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.ManagedServices/registrationAssignments/lh-sub-contoso", + "managed_by_tenant_id": "33333333-3333-3333-3333-333333333333", + "managed_by_tenant_name": "Contoso Corp.", + "managee_tenant_id": "11111111-1111-1111-1111-111111111111", + "managee_tenant_name": "AzureFox Lab Tenant", + "name": "lh-sub-contoso", + "plan_name": "contoso-plan", + "plan_product": "ops", + "plan_publisher": "contoso", + "principal_count": 2, + "provisioning_state": "Succeeded", + "registration_definition_id": "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.ManagedServices/registrationDefinitions/lh-def-contoso-sub", + "registration_definition_name": "Contoso baseline ops", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.ManagedServices/registrationAssignments/lh-sub-contoso", + "/subscriptions/22222222-2222-2222-2222-222222222222", + "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.ManagedServices/registrationDefinitions/lh-def-contoso-sub" + ], + "resource_group": null, + "role_names": [ + "Owner", + "User Access Administrator" + ], + "scope_display_name": "22222222-2222-2222-2222-222222222222", + "scope_id": "/subscriptions/22222222-2222-2222-2222-222222222222", + "scope_type": "subscription", + "strongest_role_name": "Owner", + "summary": "managed by Contoso Corp.; strongest role Owner; 1 eligible authorization(s)" + }, + { + "authorization_count": 1, + "definition_provisioning_state": "Succeeded", + "description": "Fabrikam platform support delegation", + "eligible_authorization_count": 0, + "eligible_principal_count": 0, + "eligible_role_names": [], + "has_delegated_role_assignments": false, + "has_owner_role": false, + "has_user_access_administrator": false, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-platform/providers/Microsoft.ManagedServices/registrationAssignments/lh-rg-platform-contrib", + "managed_by_tenant_id": "44444444-4444-4444-4444-444444444444", + "managed_by_tenant_name": "Fabrikam Ops", + "managee_tenant_id": "11111111-1111-1111-1111-111111111111", + "managee_tenant_name": "AzureFox Lab Tenant", + "name": "lh-rg-platform-contrib", + "plan_name": null, + "plan_product": null, + "plan_publisher": null, + "principal_count": 1, + "provisioning_state": "Succeeded", + "registration_definition_id": "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.ManagedServices/registrationDefinitions/lh-def-fabrikam-rg", + "registration_definition_name": "Fabrikam platform support", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-platform/providers/Microsoft.ManagedServices/registrationAssignments/lh-rg-platform-contrib", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-platform", + "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.ManagedServices/registrationDefinitions/lh-def-fabrikam-rg" + ], + "resource_group": "rg-platform", + "role_names": [ + "Contributor" + ], + "scope_display_name": "rg-platform", + "scope_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-platform", + "scope_type": "resource_group", + "strongest_role_name": "Contributor", + "summary": "managed by Fabrikam Ops; strongest role Contributor" + }, + { + "authorization_count": 1, + "definition_provisioning_state": "Succeeded", + "description": "Northwind logging review", + "eligible_authorization_count": 0, + "eligible_principal_count": 0, + "eligible_role_names": [], + "has_delegated_role_assignments": false, + "has_owner_role": false, + "has_user_access_administrator": false, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-logging/providers/Microsoft.ManagedServices/registrationAssignments/lh-rg-logging-reader", + "managed_by_tenant_id": "55555555-5555-5555-5555-555555555555", + "managed_by_tenant_name": "Northwind MSP", + "managee_tenant_id": "11111111-1111-1111-1111-111111111111", + "managee_tenant_name": "AzureFox Lab Tenant", + "name": "lh-rg-logging-reader", + "plan_name": null, + "plan_product": null, + "plan_publisher": null, + "principal_count": 1, + "provisioning_state": "Succeeded", + "registration_definition_id": "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.ManagedServices/registrationDefinitions/lh-def-logging-reader", + "registration_definition_name": "Northwind logging review", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-logging/providers/Microsoft.ManagedServices/registrationAssignments/lh-rg-logging-reader", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-logging", + "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.ManagedServices/registrationDefinitions/lh-def-logging-reader" + ], + "resource_group": "rg-logging", + "role_names": [ + "Reader" + ], + "scope_display_name": "rg-logging", + "scope_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-logging", + "scope_type": "resource_group", + "strongest_role_name": "Reader", + "summary": "managed by Northwind MSP; strongest role Reader" + } + ], + "metadata": { + "command": "lighthouse", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + } +} diff --git a/command-output/lighthouse/table.txt b/command-output/lighthouse/table.txt new file mode 100644 index 0000000..c19b45b --- /dev/null +++ b/command-output/lighthouse/table.txt @@ -0,0 +1,16 @@ + azurefox lighthouse +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ scope ┃ managing tenant ┃ managed tenant ┃ access ┃ state ┃ why it matters ┃ +┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ subscription::22222222-2222-… │ Contoso Corp. │ AzureFox Lab Tenant │ strongest=Owner; auth=2; │ assignment=Succeeded │ managed by Contoso Corp.; │ +│ │ │ │ eligible=1; │ │ strongest role Owner; 1 │ +│ │ │ │ delegated-role-assign=yes; │ │ eligible authorization(s) │ +│ │ │ │ plan=contoso-plan │ │ │ +│ resource-group::rg-platform │ Fabrikam Ops │ AzureFox Lab Tenant │ strongest=Contributor; │ assignment=Succeeded │ managed by Fabrikam Ops; │ +│ │ │ │ auth=1; eligible=0 │ │ strongest role Contributor │ +│ resource-group::rg-logging │ Northwind MSP │ AzureFox Lab Tenant │ strongest=Reader; auth=1; │ assignment=Succeeded │ managed by Northwind MSP; │ +│ │ │ │ eligible=0 │ │ strongest role Reader │ +└───────────────────────────────┴─────────────────┴─────────────────────┴───────────────────────────────┴──────────────────────┴───────────────────────────────┘ + +Takeaway: 3 Azure Lighthouse delegation(s) visible; 1 are subscription-scoped, 1 grant Owner or User Access Administrator, and 1 include eligible access. + diff --git a/command-output/managed-identities/output.json b/command-output/managed-identities/output.json new file mode 100644 index 0000000..707280d --- /dev/null +++ b/command-output/managed-identities/output.json @@ -0,0 +1,51 @@ +{ + "findings": [ + { + "description": "Identity 'ua-app' is assigned one or more high-impact roles (Owner).", + "id": "identity-privileged-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-app", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-app", + "ra-1" + ], + "severity": "high", + "title": "Managed identity has elevated role assignment" + } + ], + "identities": [ + { + "attached_to": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01" + ], + "client_id": "55555555-5555-5555-5555-555555555555", + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-app", + "identity_type": "userAssigned", + "name": "ua-app", + "next_review": "Check permissions for direct control on this identity, then vms for the host context behind the workload pivot.", + "operator_signal": "Public VM workload pivot; direct control visible.", + "principal_id": "33333333-3333-3333-3333-333333333333", + "scope_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222" + ], + "summary": "VM 'vm-web-01' gives a public workload pivot into managed identity 'ua-app'. Current scope already shows direct control through high-impact roles (Owner). Check permissions for direct control on this identity, then vms for the host context behind the workload pivot." + } + ], + "issues": [], + "metadata": { + "command": "managed-identities", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "role_assignments": [ + { + "id": "ra-1", + "principal_id": "33333333-3333-3333-3333-333333333333", + "principal_type": "ServicePrincipal", + "role_definition_id": "rd-owner", + "role_name": "Owner", + "scope_id": "/subscriptions/22222222-2222-2222-2222-222222222222" + } + ] +} diff --git a/command-output/managed-identities/table.txt b/command-output/managed-identities/table.txt new file mode 100644 index 0000000..00604b3 --- /dev/null +++ b/command-output/managed-identities/table.txt @@ -0,0 +1,14 @@ + azurefox managed-identities +┏━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ identity ┃ type ┃ attached to ┃ operator signal ┃ next review ┃ +┡━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ ua-app │ userAssigned │ vm-web-01 │ Public VM workload pivot; direct control visible. │ Check permissions for direct control on this identity, then vms │ +│ │ │ │ │ for the host context behind the workload pivot. │ +└──────────┴──────────────┴─────────────┴───────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────┘ + +Findings: +- HIGH: Managed identity has elevated role assignment + Identity 'ua-app' is assigned one or more high-impact roles (Owner). + +Takeaway: 1 managed identities visible; 1 exposed workload pivots and 1 direct-control cues from current scope. + diff --git a/command-output/network-effective/output.json b/command-output/network-effective/output.json new file mode 100644 index 0000000..d4acffc --- /dev/null +++ b/command-output/network-effective/output.json @@ -0,0 +1,41 @@ +{ + "effective_exposures": [ + { + "asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "asset_name": "vm-web-01", + "constrained_ports": [ + "TCP/443", + "TCP/8080" + ], + "effective_exposure": "high", + "endpoint": "52.160.10.20", + "endpoint_type": "ip", + "internet_exposed_ports": [ + "TCP/22" + ], + "observed_paths": [ + "Any via nic-nsg:rg-workload/nsg-web/allow-ssh-internet", + "AzureLoadBalancer via subnet-nsg:rg-workload/nsg-vnet-app/allow-https-lb", + "10.20.0.0/16 via subnet-nsg:rg-workload/nsg-vnet-app/allow-app-private" + ], + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/networkInterfaces/nic-web-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-app", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/networkSecurityGroups/nsg-web", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/networkSecurityGroups/nsg-vnet-app" + ], + "summary": "Asset 'vm-web-01' endpoint 52.160.10.20 has internet-facing allow evidence on TCP/22 and narrower allow evidence on TCP/443, TCP/8080. Treat this as visible Azure network triage signal, not proof of full effective reachability." + } + ], + "findings": [], + "issues": [], + "metadata": { + "command": "network-effective", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + } +} diff --git a/command-output/network-effective/table.txt b/command-output/network-effective/table.txt new file mode 100644 index 0000000..207432c --- /dev/null +++ b/command-output/network-effective/table.txt @@ -0,0 +1,11 @@ + azurefox network-effective +┏━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ asset ┃ endpoint ┃ priority ┃ internet ports ┃ narrower ports ┃ why it matters ┃ +┡━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ vm-web-01 │ 52.160.10.20 │ high │ TCP/22 │ TCP/443, TCP/8080 │ Asset 'vm-web-01' endpoint 52.160.10.20 has internet-facing allow evidence on │ +│ │ │ │ │ │ TCP/22 and narrower allow evidence on TCP/443, TCP/8080. Treat this as visible │ +│ │ │ │ │ │ Azure network triage signal, not proof of full effective reachability. │ +└───────────┴──────────────┴──────────┴────────────────┴───────────────────┴───────────────────────────────────────────────────────────────────────────────────┘ + +Takeaway: 1 public-IP exposure summaries visible; 1 high, 0 medium, 0 low, and 1 show broad internet-facing allow evidence. + diff --git a/command-output/network-ports/output.json b/command-output/network-ports/output.json new file mode 100644 index 0000000..9239b81 --- /dev/null +++ b/command-output/network-ports/output.json @@ -0,0 +1,62 @@ +{ + "findings": [], + "issues": [], + "metadata": { + "command": "network-ports", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "network_ports": [ + { + "allow_source_summary": "Any via nic-nsg:rg-workload/nsg-web/allow-ssh-internet", + "asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "asset_name": "vm-web-01", + "endpoint": "52.160.10.20", + "exposure_confidence": "high", + "port": "22", + "protocol": "TCP", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/networkInterfaces/nic-web-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/networkSecurityGroups/nsg-web", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-app" + ], + "summary": "Asset 'vm-web-01' has inbound TCP 22 allow evidence for endpoint 52.160.10.20 from Any via nic-nsg:rg-workload/nsg-web/allow-ssh-internet." + }, + { + "allow_source_summary": "AzureLoadBalancer via subnet-nsg:rg-workload/nsg-vnet-app/allow-https-lb", + "asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "asset_name": "vm-web-01", + "endpoint": "52.160.10.20", + "exposure_confidence": "medium", + "port": "443", + "protocol": "TCP", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/networkInterfaces/nic-web-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/networkSecurityGroups/nsg-vnet-app", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-app" + ], + "summary": "Asset 'vm-web-01' has inbound TCP 443 allow evidence for endpoint 52.160.10.20 from AzureLoadBalancer via subnet-nsg:rg-workload/nsg-vnet-app/allow-https-lb." + }, + { + "allow_source_summary": "10.20.0.0/16 via subnet-nsg:rg-workload/nsg-vnet-app/allow-app-private", + "asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "asset_name": "vm-web-01", + "endpoint": "52.160.10.20", + "exposure_confidence": "low", + "port": "8080", + "protocol": "TCP", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/networkInterfaces/nic-web-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/networkSecurityGroups/nsg-vnet-app", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-app" + ], + "summary": "Asset 'vm-web-01' has inbound TCP 8080 allow evidence for endpoint 52.160.10.20 from 10.20.0.0/16 via subnet-nsg:rg-workload/nsg-vnet-app/allow-app-private." + } + ] +} diff --git a/command-output/network-ports/table.txt b/command-output/network-ports/table.txt new file mode 100644 index 0000000..9cd91de --- /dev/null +++ b/command-output/network-ports/table.txt @@ -0,0 +1,19 @@ + azurefox network-ports +┏━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ asset ┃ endpoint ┃ protocol ┃ port ┃ allow source ┃ confidence ┃ why it matters ┃ +┡━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ vm-web-01 │ 52.160.10.20 │ TCP │ 22 │ Any via │ high │ Asset 'vm-web-01' has inbound TCP 22 allow │ +│ │ │ │ │ nic-nsg:rg-workload/nsg-web/allow-ssh-internet │ │ evidence for endpoint 52.160.10.20 from Any via │ +│ │ │ │ │ │ │ nic-nsg:rg-workload/nsg-web/allow-ssh-internet. │ +│ vm-web-01 │ 52.160.10.20 │ TCP │ 443 │ AzureLoadBalancer via │ medium │ Asset 'vm-web-01' has inbound TCP 443 allow │ +│ │ │ │ │ subnet-nsg:rg-workload/nsg-vnet-app/allow-https… │ │ evidence for endpoint 52.160.10.20 from │ +│ │ │ │ │ │ │ AzureLoadBalancer via │ +│ │ │ │ │ │ │ subnet-nsg:rg-workload/nsg-vnet-app/allow-http… │ +│ vm-web-01 │ 52.160.10.20 │ TCP │ 8080 │ 10.20.0.0/16 via │ low │ Asset 'vm-web-01' has inbound TCP 8080 allow │ +│ │ │ │ │ subnet-nsg:rg-workload/nsg-vnet-app/allow-app-p… │ │ evidence for endpoint 52.160.10.20 from │ +│ │ │ │ │ │ │ 10.20.0.0/16 via │ +│ │ │ │ │ │ │ subnet-nsg:rg-workload/nsg-vnet-app/allow-app-… │ +└───────────┴──────────────┴──────────┴──────┴──────────────────────────────────────────────────┴────────────┴─────────────────────────────────────────────────┘ + +Takeaway: 3 port exposure rows visible; 1 high, 1 low, 1 medium. + diff --git a/command-output/nics/output.json b/command-output/nics/output.json new file mode 100644 index 0000000..1916387 --- /dev/null +++ b/command-output/nics/output.json @@ -0,0 +1,51 @@ +{ + "findings": [], + "issues": [], + "metadata": { + "command": "nics", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "nic_assets": [ + { + "attached_asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "attached_asset_name": "vm-web-01", + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/networkInterfaces/nic-web-01", + "name": "nic-web-01", + "network_security_group_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/networkSecurityGroups/nsg-web", + "private_ips": [ + "10.0.1.4" + ], + "public_ip_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/publicIPAddresses/pip-web-01" + ], + "subnet_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/virtualNetworks/vnet-workload/subnets/vnet-app" + ], + "vnet_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/virtualNetworks/vnet-workload" + ] + }, + { + "attached_asset_id": null, + "attached_asset_name": null, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/networkInterfaces/nic-db-01", + "name": "nic-db-01", + "network_security_group_id": null, + "private_ips": [ + "10.0.2.5", + "10.0.2.6" + ], + "public_ip_ids": [], + "subnet_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/virtualNetworks/vnet-workload/subnets/vnet-db" + ], + "vnet_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/virtualNetworks/vnet-workload" + ] + } + ] +} diff --git a/command-output/nics/table.txt b/command-output/nics/table.txt new file mode 100644 index 0000000..65e5d80 --- /dev/null +++ b/command-output/nics/table.txt @@ -0,0 +1,10 @@ + azurefox nics +┏━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┓ +┃ nic ┃ attached asset ┃ private ips ┃ public ip refs ┃ subnet / vnet ┃ nsg ┃ +┡━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━┩ +│ nic-web-01 │ vm-web-01 │ 10.0.1.4 │ pip-web-01 │ subnet=vnet-app; vnet=vnet-workload │ nsg-web │ +│ nic-db-01 │ - │ 10.0.2.5, 10.0.2.6 │ │ subnet=vnet-db; vnet=vnet-workload │ - │ +└────────────┴────────────────┴────────────────────┴────────────────┴─────────────────────────────────────┴─────────┘ + +Takeaway: 2 NICs visible; 1 attached to visible assets and 1 reference public IP resources. + diff --git a/command-output/permissions/output.json b/command-output/permissions/output.json new file mode 100644 index 0000000..65d8a1e --- /dev/null +++ b/command-output/permissions/output.json @@ -0,0 +1,53 @@ +{ + "issues": [], + "metadata": { + "command": "permissions", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "permissions": [ + { + "all_role_names": [ + "Owner" + ], + "display_name": "azurefox-lab-sp", + "high_impact_roles": [ + "Owner" + ], + "is_current_identity": true, + "next_review": "Check privesc for the direct abuse or escalation path behind this current identity.", + "operator_signal": "Direct control visible; current foothold.", + "principal_id": "33333333-3333-3333-3333-333333333333", + "principal_type": "ServicePrincipal", + "privileged": true, + "role_assignment_count": 1, + "scope_count": 1, + "scope_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222" + ], + "summary": "Current identity 'azurefox-lab-sp' already has direct control visible through Owner across subscription-wide. Check privesc for the direct abuse or escalation path behind this current identity." + }, + { + "all_role_names": [ + "Reader" + ], + "display_name": "operator@lab.local", + "high_impact_roles": [], + "is_current_identity": false, + "next_review": "Check rbac for the exact assignment evidence behind this lower-signal row.", + "operator_signal": "Direct control not confirmed.", + "principal_id": "44444444-4444-4444-4444-444444444444", + "principal_type": "User", + "privileged": false, + "role_assignment_count": 1, + "scope_count": 1, + "scope_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222" + ], + "summary": "Principal 'operator@lab.local' does not yet show direct control from visible RBAC. Check rbac for the exact assignment evidence behind this lower-signal row." + } + ] +} diff --git a/command-output/permissions/table.txt b/command-output/permissions/table.txt new file mode 100644 index 0000000..4973f69 --- /dev/null +++ b/command-output/permissions/table.txt @@ -0,0 +1,13 @@ + azurefox permissions +┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ principal ┃ type ┃ high-impact roles ┃ scopes ┃ operator signal ┃ next review ┃ +┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ azurefox-lab-sp │ ServicePrincipal │ Owner │ 1 │ Direct control visible; current foothold. │ Check privesc for the direct abuse or │ +│ │ │ │ │ │ escalation path behind this current │ +│ │ │ │ │ │ identity. │ +│ operator@lab.local │ User │ │ 1 │ Direct control not confirmed. │ Check rbac for the exact assignment │ +│ │ │ │ │ │ evidence behind this lower-signal row. │ +└────────────────────┴──────────────────┴───────────────────┴────────┴───────────────────────────────────────────┴─────────────────────────────────────────────┘ + +Takeaway: 1 of 2 principals hold high-impact RBAC roles; 0 workload-pivot follow-ons and 0 trust-expansion follow-ons. + diff --git a/command-output/principals/output.json b/command-output/principals/output.json new file mode 100644 index 0000000..9bfae82 --- /dev/null +++ b/command-output/principals/output.json @@ -0,0 +1,61 @@ +{ + "issues": [], + "metadata": { + "command": "principals", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "principals": [ + { + "attached_to": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01" + ], + "display_name": "azurefox-lab-sp", + "id": "33333333-3333-3333-3333-333333333333", + "identity_names": [ + "ua-app" + ], + "identity_types": [ + "userAssigned" + ], + "is_current_identity": true, + "principal_type": "ServicePrincipal", + "role_assignment_count": 1, + "role_names": [ + "Owner" + ], + "scope_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222" + ], + "sources": [ + "rbac", + "whoami", + "managed-identities" + ], + "tenant_id": "11111111-1111-1111-1111-111111111111" + }, + { + "attached_to": [], + "display_name": "operator@lab.local", + "id": "44444444-4444-4444-4444-444444444444", + "identity_names": [], + "identity_types": [], + "is_current_identity": false, + "principal_type": "User", + "role_assignment_count": 1, + "role_names": [ + "Reader" + ], + "scope_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222" + ], + "sources": [ + "rbac" + ], + "tenant_id": "11111111-1111-1111-1111-111111111111" + } + ] +} diff --git a/command-output/principals/table.txt b/command-output/principals/table.txt new file mode 100644 index 0000000..b4119fa --- /dev/null +++ b/command-output/principals/table.txt @@ -0,0 +1,10 @@ + azurefox principals +┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┓ +┃ principal ┃ type ┃ roles ┃ assignments ┃ identity context ┃ sources ┃ current ┃ +┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━┩ +│ azurefox-lab-sp │ ServicePrincipal │ Owner │ 1 │ identities=ua-app; attached=1 │ rbac, whoami, managed-identities │ yes │ +│ operator@lab.local │ User │ Reader │ 1 │ - │ rbac │ no │ +└────────────────────┴──────────────────┴────────┴─────────────┴───────────────────────────────┴──────────────────────────────────┴─────────┘ + +Takeaway: 2 principals visible; 1 hold Owner and 1 match the current identity. + diff --git a/command-output/privesc/output.json b/command-output/privesc/output.json new file mode 100644 index 0000000..602c5f9 --- /dev/null +++ b/command-output/privesc/output.json @@ -0,0 +1,49 @@ +{ + "issues": [], + "metadata": { + "command": "privesc", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "paths": [ + { + "asset": null, + "current_identity": true, + "impact_roles": [ + "Owner" + ], + "path_type": "direct-role-abuse", + "principal": "azurefox-lab-sp", + "principal_id": "33333333-3333-3333-3333-333333333333", + "principal_type": "ServicePrincipal", + "related_ids": [ + "33333333-3333-3333-3333-333333333333", + "/subscriptions/22222222-2222-2222-2222-222222222222" + ], + "severity": "high", + "summary": "Principal 'azurefox-lab-sp' already holds high-impact role assignments (Owner) in the current subscription scope." + }, + { + "asset": "vm-web-01", + "current_identity": false, + "impact_roles": [ + "Owner" + ], + "path_type": "public-identity-pivot", + "principal": "ua-app", + "principal_id": "33333333-3333-3333-3333-333333333333", + "principal_type": "ManagedIdentity", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-app", + "33333333-3333-3333-3333-333333333333", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "/subscriptions/22222222-2222-2222-2222-222222222222" + ], + "severity": "high", + "summary": "Public workload 'vm-web-01' carries managed identity 'ua-app' with high-impact role assignments (Owner)." + } + ] +} diff --git a/command-output/privesc/table.txt b/command-output/privesc/table.txt new file mode 100644 index 0000000..dc0b863 --- /dev/null +++ b/command-output/privesc/table.txt @@ -0,0 +1,12 @@ + azurefox privesc +┏━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┓ +┃ severity ┃ principal ┃ path ┃ asset ┃ why it matters ┃ current ┃ +┡━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━┩ +│ high │ azurefox-lab-sp │ direct-role-abuse │ - │ Principal 'azurefox-lab-sp' already holds high-impact role assignments (Owner) in │ yes │ +│ │ │ │ │ the current subscription scope. │ │ +│ high │ ua-app │ public-identity-pivot │ vm-web-01 │ Public workload 'vm-web-01' carries managed identity 'ua-app' with high-impact │ no │ +│ │ │ │ │ role assignments (Owner). │ │ +└──────────┴─────────────────┴───────────────────────┴───────────┴───────────────────────────────────────────────────────────────────────────────────┴─────────┘ + +Takeaway: 2 privilege-escalation paths surfaced; 2 high. + diff --git a/command-output/rbac/output.json b/command-output/rbac/output.json new file mode 100644 index 0000000..9d387f3 --- /dev/null +++ b/command-output/rbac/output.json @@ -0,0 +1,50 @@ +{ + "issues": [], + "metadata": { + "command": "rbac", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "principals": [ + { + "display_name": "azurefox-lab-sp", + "id": "33333333-3333-3333-3333-333333333333", + "principal_type": "ServicePrincipal", + "tenant_id": "11111111-1111-1111-1111-111111111111" + }, + { + "display_name": "operator@lab.local", + "id": "44444444-4444-4444-4444-444444444444", + "principal_type": "User", + "tenant_id": "11111111-1111-1111-1111-111111111111" + } + ], + "role_assignments": [ + { + "id": "ra-1", + "principal_id": "33333333-3333-3333-3333-333333333333", + "principal_type": "ServicePrincipal", + "role_definition_id": "rd-owner", + "role_name": "Owner", + "scope_id": "/subscriptions/22222222-2222-2222-2222-222222222222" + }, + { + "id": "ra-2", + "principal_id": "44444444-4444-4444-4444-444444444444", + "principal_type": "User", + "role_definition_id": "rd-reader", + "role_name": "Reader", + "scope_id": "/subscriptions/22222222-2222-2222-2222-222222222222" + } + ], + "scopes": [ + { + "display_name": "azurefox-lab-sub", + "id": "/subscriptions/22222222-2222-2222-2222-222222222222", + "scope_type": "subscription" + } + ] +} diff --git a/command-output/rbac/table.txt b/command-output/rbac/table.txt new file mode 100644 index 0000000..b603314 --- /dev/null +++ b/command-output/rbac/table.txt @@ -0,0 +1,10 @@ + azurefox rbac +┏━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓ +┃ id ┃ scope id ┃ principal id ┃ principal type ┃ role definition id ┃ role name ┃ +┡━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩ +│ ra-1 │ /subscriptions/22222222-2222-2222-2222-222222222222 │ 33333333-3333-3333-3333-333333333333 │ ServicePrincipal │ rd-owner │ Owner │ +│ ra-2 │ /subscriptions/22222222-2222-2222-2222-222222222222 │ 44444444-4444-4444-4444-444444444444 │ User │ rd-reader │ Reader │ +└──────┴─────────────────────────────────────────────────────┴──────────────────────────────────────┴──────────────────┴────────────────────┴───────────┘ + +Takeaway: 2 RBAC assignments across 2 principals. + diff --git a/command-output/resource-trusts/output.json b/command-output/resource-trusts/output.json new file mode 100644 index 0000000..1ed409b --- /dev/null +++ b/command-output/resource-trusts/output.json @@ -0,0 +1,99 @@ +{ + "findings": [ + { + "description": "Storage account 'stlabpub01' has blob public access enabled. Validate anonymous access and exposed data paths.", + "id": "storage-public-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stlabpub01", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stlabpub01" + ], + "severity": "high", + "title": "Storage account allows public blob access" + }, + { + "description": "Storage account 'stlabpub01' default firewall action is Allow. Review allowed network sources and private endpoint posture.", + "id": "storage-firewall-open-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stlabpub01", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stlabpub01" + ], + "severity": "medium", + "title": "Storage account network default action is Allow" + }, + { + "description": "Key Vault 'kvlabopen01' has public network access enabled, Azure omitted the network ACL object, and no private endpoint is visible. Azure can return that shape for a fully open vault. Review whether that secret-management surface is intentionally internet reachable.", + "id": "keyvault-public-network-open-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabopen01", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabopen01" + ], + "severity": "high", + "title": "Key Vault is broadly reachable on the public network" + }, + { + "description": "Key Vault 'kvlabdeny01' keeps public network access enabled with default network action 'Deny' and no private endpoint visible. Review whether that public path is still intended.", + "id": "keyvault-public-network-enabled-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabdeny01", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabdeny01" + ], + "severity": "medium", + "title": "Key Vault remains reachable through a public network path" + }, + { + "description": "Key Vault 'kvlabhybrid01' has public network access enabled while a private endpoint is also present. Validate whether the public path is still required.", + "id": "keyvault-public-network-with-private-endpoint-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabhybrid01", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabhybrid01" + ], + "severity": "low", + "title": "Key Vault keeps a public network path alongside Private Link" + } + ], + "issues": [], + "metadata": { + "command": "resource-trusts", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "resource_trusts": [ + { + "confidence": "confirmed", + "exposure": "high", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabopen01" + ], + "resource_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabopen01", + "resource_name": "kvlabopen01", + "resource_type": "KeyVault", + "summary": "Key Vault 'kvlabopen01' remains reachable through a public network path.", + "target": "public-network", + "trust_type": "public-network" + }, + { + "confidence": "confirmed", + "exposure": "high", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stlabpub01" + ], + "resource_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stlabpub01", + "resource_name": "stlabpub01", + "resource_type": "StorageAccount", + "summary": "Storage account 'stlabpub01' permits public blob access from the public network.", + "target": "public-network", + "trust_type": "anonymous-blob-access" + }, + { + "confidence": "confirmed", + "exposure": "medium", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabdeny01" + ], + "resource_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/kvlabdeny01", + "resource_name": "kvlabdeny01", + "resource_type": "KeyVault", + "summary": "Key Vault 'kvlabdeny01' remains reachable through a public network path.", + "target": "public-network", + "trust_type": "public-network" + } + ] +} diff --git a/command-output/resource-trusts/table.txt b/command-output/resource-trusts/table.txt new file mode 100644 index 0000000..9941f2e --- /dev/null +++ b/command-output/resource-trusts/table.txt @@ -0,0 +1,26 @@ + azurefox resource-trusts +┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ resource ┃ type ┃ trust ┃ target ┃ exposure ┃ why it matters ┃ +┡━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ kvlabopen01 │ KeyVault │ public-network │ public-network │ high │ Key Vault 'kvlabopen01' remains reachable through a public network path. │ +│ stlabpub01 │ StorageAccount │ anonymous-blob-access │ public-network │ high │ Storage account 'stlabpub01' permits public blob access from the public │ +│ │ │ │ │ │ network. │ +│ kvlabdeny01 │ KeyVault │ public-network │ public-network │ medium │ Key Vault 'kvlabdeny01' remains reachable through a public network path. │ +└─────────────┴────────────────┴───────────────────────┴────────────────┴──────────┴───────────────────────────────────────────────────────────────────────────┘ + +Findings: +- HIGH: Storage account allows public blob access + Storage account 'stlabpub01' has blob public access enabled. Validate anonymous access and exposed data paths. +- MEDIUM: Storage account network default action is Allow + Storage account 'stlabpub01' default firewall action is Allow. Review allowed network sources and private endpoint posture. +- HIGH: Key Vault is broadly reachable on the public network + Key Vault 'kvlabopen01' has public network access enabled, Azure omitted the network ACL object, and no private endpoint is visible. Azure can return that +shape for a fully open vault. Review whether that secret-management surface is intentionally internet reachable. +- MEDIUM: Key Vault remains reachable through a public network path + Key Vault 'kvlabdeny01' keeps public network access enabled with default network action 'Deny' and no private endpoint visible. Review whether that public +path is still intended. +- LOW: Key Vault keeps a public network path alongside Private Link + Key Vault 'kvlabhybrid01' has public network access enabled while a private endpoint is also present. Validate whether the public path is still required. + +Takeaway: 3 resource trust surfaces visible; 2 high, 1 medium. + diff --git a/command-output/role-trusts/output.json b/command-output/role-trusts/output.json new file mode 100644 index 0000000..0d251a2 --- /dev/null +++ b/command-output/role-trusts/output.json @@ -0,0 +1,69 @@ +{ + "issues": [], + "metadata": { + "command": "role-trusts", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "mode": "fast", + "trusts": [ + { + "confidence": "confirmed", + "evidence_type": "graph-federated-credential", + "next_review": "Check permissions for Azure control on service principal 'build-sp'.", + "operator_signal": "Trust expansion visible; privilege confirmation next.", + "related_ids": [ + "55555555-5555-5555-5555-555555555555", + "fic-build-main", + "66666666-6666-6666-6666-666666666666" + ], + "source_name": "build-app", + "source_object_id": "55555555-5555-5555-5555-555555555555", + "source_type": "Application", + "summary": "Application 'build-app' trusts federated subject 'repo:TacoRocket/AzureFox:ref:refs/heads/main' from issuer 'https://token.actions.githubusercontent.com'. This row shows trust expansion into the target identity rather than direct Azure privilege by itself. Check permissions for Azure control on service principal 'build-sp'.", + "target_name": "build-sp", + "target_object_id": "66666666-6666-6666-6666-666666666666", + "target_type": "ServicePrincipal", + "trust_type": "federated-credential" + }, + { + "confidence": "confirmed", + "evidence_type": "graph-owner", + "next_review": "Review ownership around service principal 'build-sp', then confirm Azure control in permissions.", + "operator_signal": "Indirect control visible; ownership review next.", + "related_ids": [ + "88888888-8888-8888-8888-888888888888", + "66666666-6666-6666-6666-666666666666" + ], + "source_name": "automation-runner", + "source_object_id": "88888888-8888-8888-8888-888888888888", + "source_type": "ServicePrincipal", + "summary": "Owner 'automation-runner' can modify service principal 'build-sp'. This is an indirect-control row: ownership is the visible trust path, not direct Azure privilege by itself. Review ownership around service principal 'build-sp', then confirm Azure control in permissions.", + "target_name": "build-sp", + "target_object_id": "66666666-6666-6666-6666-666666666666", + "target_type": "ServicePrincipal", + "trust_type": "service-principal-owner" + }, + { + "confidence": "confirmed", + "evidence_type": "graph-owner", + "next_review": "Review ownership around application 'build-app'; if it backs an Azure-facing identity, confirm that identity in permissions.", + "operator_signal": "Indirect control visible; ownership review next.", + "related_ids": [ + "77777777-7777-7777-7777-777777777777", + "55555555-5555-5555-5555-555555555555" + ], + "source_name": "ci-admin@lab.local", + "source_object_id": "77777777-7777-7777-7777-777777777777", + "source_type": "User", + "summary": "Owner 'ci-admin@lab.local' can modify application 'build-app'. This is an indirect-control row: ownership is the visible trust path, not direct Azure privilege by itself. Review ownership around application 'build-app'; if it backs an Azure-facing identity, confirm that identity in permissions.", + "target_name": "build-app", + "target_object_id": "55555555-5555-5555-5555-555555555555", + "target_type": "Application", + "trust_type": "app-owner" + } + ] +} diff --git a/command-output/role-trusts/table.txt b/command-output/role-trusts/table.txt new file mode 100644 index 0000000..f6b724e --- /dev/null +++ b/command-output/role-trusts/table.txt @@ -0,0 +1,18 @@ + azurefox role-trusts +┏━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ trust ┃ source ┃ target ┃ confidence ┃ operator signal ┃ next review ┃ +┡━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ federated-credential │ build-app │ build-sp │ confirmed │ Trust expansion visible; privilege │ Check permissions for Azure control on │ +│ │ │ │ │ confirmation next. │ service principal 'build-sp'. │ +│ service-principal-owner │ automation-runner │ build-sp │ confirmed │ Indirect control visible; ownership │ Review ownership around service │ +│ │ │ │ │ review next. │ principal 'build-sp', then confirm Azure │ +│ │ │ │ │ │ control in permissions. │ +│ app-owner │ ci-admin@lab.local │ build-app │ confirmed │ Indirect control visible; ownership │ Review ownership around application │ +│ │ │ │ │ review next. │ 'build-app'; if it backs an Azure-facing │ +│ │ │ │ │ │ identity, confirm that identity in │ +│ │ │ │ │ │ permissions. │ +└─────────────────────────┴────────────────────┴───────────┴────────────┴───────────────────────────────────────────┴──────────────────────────────────────────┘ + +Takeaway: 3 trust edges surfaced in fast mode; 1 app-owner, 1 federated-credential, 1 service-principal-owner. 1 privilege-confirmation follow-ons, 2 +ownership-review follow-ons, and 0 outside-tenant follow-ons. Delegated and admin consent grants are out of scope for this command. + diff --git a/command-output/snapshots-disks/output.json b/command-output/snapshots-disks/output.json new file mode 100644 index 0000000..114faea --- /dev/null +++ b/command-output/snapshots-disks/output.json @@ -0,0 +1,100 @@ +{ + "findings": [], + "issues": [], + "metadata": { + "command": "snapshots-disks", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "snapshot_disk_assets": [ + { + "asset_kind": "disk", + "attached_to_id": null, + "attached_to_name": null, + "attachment_state": "detached", + "disk_access_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-legacy/providers/Microsoft.Compute/diskAccesses/legacy-diskaccess", + "disk_encryption_set_id": null, + "disk_role": null, + "encryption_type": "EncryptionAtRestWithPlatformKey", + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-legacy/providers/Microsoft.Compute/disks/data-detached-legacy", + "incremental": null, + "location": "eastus2", + "max_shares": 3, + "name": "data-detached-legacy", + "network_access_policy": "AllowAll", + "os_type": null, + "public_network_access": "Enabled", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-legacy/providers/Microsoft.Compute/diskAccesses/legacy-diskaccess" + ], + "resource_group": "rg-legacy", + "size_gb": 512, + "source_resource_id": null, + "source_resource_kind": null, + "source_resource_name": null, + "summary": "Detached managed disk; public network Enabled, network access AllowAll, max shares 3, disk access resource visible; encryption posture: EncryptionAtRestWithPlatformKey.", + "time_created": "2026-03-20T09:22:00+00:00" + }, + { + "asset_kind": "snapshot", + "attached_to_id": null, + "attached_to_name": null, + "attachment_state": "snapshot", + "disk_access_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-legacy/providers/Microsoft.Compute/diskAccesses/legacy-diskaccess", + "disk_encryption_set_id": null, + "disk_role": null, + "encryption_type": "EncryptionAtRestWithPlatformKey", + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-legacy/providers/Microsoft.Compute/snapshots/data-detached-legacy-snap", + "incremental": true, + "location": "eastus2", + "max_shares": null, + "name": "data-detached-legacy-snap", + "network_access_policy": "AllowAll", + "os_type": null, + "public_network_access": "Enabled", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-legacy/providers/Microsoft.Compute/disks/data-detached-legacy", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-legacy/providers/Microsoft.Compute/diskAccesses/legacy-diskaccess" + ], + "resource_group": "rg-legacy", + "size_gb": 512, + "source_resource_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-legacy/providers/Microsoft.Compute/disks/data-detached-legacy", + "source_resource_kind": "disk", + "source_resource_name": "data-detached-legacy", + "summary": "Snapshot of data-detached-legacy; incremental copy path visible; public network Enabled, network access AllowAll, disk access resource visible; encryption posture: EncryptionAtRestWithPlatformKey.", + "time_created": "2026-04-01T03:15:00+00:00" + }, + { + "asset_kind": "snapshot", + "attached_to_id": null, + "attached_to_name": null, + "attachment_state": "snapshot", + "disk_access_id": null, + "disk_encryption_set_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-sec/providers/Microsoft.Compute/diskEncryptionSets/des-prod", + "disk_role": null, + "encryption_type": "EncryptionAtRestWithCustomerKey", + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/snapshots/vm-web-01-os-snap", + "incremental": true, + "location": "eastus", + "max_shares": null, + "name": "vm-web-01-os-snap", + "network_access_policy": "AllowPrivate", + "os_type": "Linux", + "public_network_access": "Disabled", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/disks/vm-web-01-os", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-sec/providers/Microsoft.Compute/diskEncryptionSets/des-prod" + ], + "resource_group": "rg-workload", + "size_gb": 128, + "source_resource_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/disks/vm-web-01-os", + "source_resource_kind": "disk", + "source_resource_name": "vm-web-01-os", + "summary": "Snapshot of vm-web-01-os; incremental copy path visible; public network Disabled, network access AllowPrivate; encryption posture: EncryptionAtRestWithCustomerKey, disk encryption set linked.", + "time_created": "2026-04-02T06:40:00+00:00" + } + ] +} diff --git a/command-output/snapshots-disks/table.txt b/command-output/snapshots-disks/table.txt new file mode 100644 index 0000000..528c4b8 --- /dev/null +++ b/command-output/snapshots-disks/table.txt @@ -0,0 +1,37 @@ + azurefox snapshots-disks +┏━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ asset ┃ kind ┃ priority ┃ attachment / source ┃ sharing / export ┃ encryption ┃ why it matters ┃ +┡━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ data-detached-legacy │ disk │ detached, public-net, │ detached │ policy=AllowAll; │ type=EncryptionAtRes… │ Detached managed disk; │ +│ │ │ allow-all, shared=3, │ │ public=Enabled; │ des=no; size=512g │ public network │ +│ │ │ disk-access │ │ max-shares=3; │ │ Enabled, network │ +│ │ │ │ │ disk-access=yes │ │ access AllowAll, max │ +│ │ │ │ │ │ │ shares 3, disk access │ +│ │ │ │ │ │ │ resource visible; │ +│ │ │ │ │ │ │ encryption posture: │ +│ │ │ │ │ │ │ EncryptionAtRestWithP… │ +│ data-detached-legacy-… │ snapshot │ offline-copy, │ source=data-detached… │ policy=AllowAll; │ type=EncryptionAtRes… │ Snapshot of │ +│ │ │ public-net, allow-all, │ incremental=yes │ public=Enabled; │ des=no; size=512g │ data-detached-legacy; │ +│ │ │ disk-access │ │ disk-access=yes │ │ incremental copy path │ +│ │ │ │ │ │ │ visible; public │ +│ │ │ │ │ │ │ network Enabled, │ +│ │ │ │ │ │ │ network access │ +│ │ │ │ │ │ │ AllowAll, disk access │ +│ │ │ │ │ │ │ resource visible; │ +│ │ │ │ │ │ │ encryption posture: │ +│ │ │ │ │ │ │ EncryptionAtRestWithP… │ +│ vm-web-01-os-snap │ snapshot │ offline-copy │ source=vm-web-01-os; │ policy=AllowPrivate; │ type=EncryptionAtRes… │ Snapshot of │ +│ │ │ │ incremental=yes │ public=Disabled │ des=yes; os=Linux; │ vm-web-01-os; │ +│ │ │ │ │ │ size=128g │ incremental copy path │ +│ │ │ │ │ │ │ visible; public │ +│ │ │ │ │ │ │ network Disabled, │ +│ │ │ │ │ │ │ network access │ +│ │ │ │ │ │ │ AllowPrivate; │ +│ │ │ │ │ │ │ encryption posture: │ +│ │ │ │ │ │ │ EncryptionAtRestWithC… │ +│ │ │ │ │ │ │ disk encryption set │ +│ │ │ │ │ │ │ linked. │ +└────────────────────────┴──────────┴────────────────────────┴───────────────────────┴────────────────────────┴───────────────────────┴────────────────────────┘ + +Takeaway: 3 disk-backed assets visible; 2 snapshots, 1 detached disk, and 2 show broader sharing or export posture. + diff --git a/command-output/storage/output.json b/command-output/storage/output.json new file mode 100644 index 0000000..5818089 --- /dev/null +++ b/command-output/storage/output.json @@ -0,0 +1,80 @@ +{ + "findings": [ + { + "description": "Storage account 'stlabpub01' has blob public access enabled. Validate anonymous access and exposed data paths.", + "id": "storage-public-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stlabpub01", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stlabpub01" + ], + "severity": "high", + "title": "Storage account allows public blob access" + }, + { + "description": "Storage account 'stlabpub01' default firewall action is Allow. Review allowed network sources and private endpoint posture.", + "id": "storage-firewall-open-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stlabpub01", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stlabpub01" + ], + "severity": "medium", + "title": "Storage account network default action is Allow" + } + ], + "issues": [], + "metadata": { + "command": "storage", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "storage_assets": [ + { + "allow_shared_key_access": true, + "anonymous_access_indicators": [ + "allow_blob_public_access=true", + "network_default_action=Allow" + ], + "container_count": 3, + "dns_endpoint_type": "Standard", + "file_share_count": 1, + "https_traffic_only_enabled": false, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stlabpub01", + "is_hns_enabled": false, + "is_sftp_enabled": false, + "location": "eastus", + "minimum_tls_version": "TLS1_0", + "name": "stlabpub01", + "network_default_action": "Allow", + "nfs_v3_enabled": false, + "private_endpoint_enabled": false, + "public_access": true, + "public_network_access": "Enabled", + "queue_count": 2, + "resource_group": "rg-data", + "table_count": 0 + }, + { + "allow_shared_key_access": false, + "anonymous_access_indicators": [], + "container_count": 2, + "dns_endpoint_type": "Standard", + "file_share_count": 0, + "https_traffic_only_enabled": true, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stlabpriv01", + "is_hns_enabled": true, + "is_sftp_enabled": true, + "location": "eastus", + "minimum_tls_version": "TLS1_2", + "name": "stlabpriv01", + "network_default_action": "Deny", + "nfs_v3_enabled": false, + "private_endpoint_enabled": true, + "public_access": false, + "public_network_access": "Disabled", + "queue_count": 0, + "resource_group": "rg-data", + "table_count": 1 + } + ] +} diff --git a/command-output/storage/table.txt b/command-output/storage/table.txt new file mode 100644 index 0000000..ebb1b94 --- /dev/null +++ b/command-output/storage/table.txt @@ -0,0 +1,22 @@ + azurefox storage +┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ account ┃ resource group ┃ exposure ┃ auth / transport ┃ protocols ┃ inventory ┃ +┡━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ stlabpub01 │ rg-data │ blob-public=yes; │ shared-key=yes; tls=TLS1_0; │ hns=no; sftp=no; nfs=no; │ blob=3; file=1; queue=2; │ +│ │ │ public-net=enabled; │ https-only=no │ dns=standard │ table=0 │ +│ │ │ default=Allow; │ │ │ │ +│ │ │ private-endpoint=no │ │ │ │ +│ stlabpriv01 │ rg-data │ blob-public=no; │ shared-key=no; tls=TLS1_2; │ hns=yes; sftp=yes; nfs=no; │ blob=2; file=0; queue=0; │ +│ │ │ public-net=disabled; │ https-only=yes │ dns=standard │ table=1 │ +│ │ │ default=Deny; │ │ │ │ +│ │ │ private-endpoint=yes │ │ │ │ +└─────────────┴────────────────┴───────────────────────────────┴───────────────────────────────┴───────────────────────────────┴───────────────────────────────┘ + +Findings: +- HIGH: Storage account allows public blob access + Storage account 'stlabpub01' has blob public access enabled. Validate anonymous access and exposed data paths. +- MEDIUM: Storage account network default action is Allow + Storage account 'stlabpub01' default firewall action is Allow. Review allowed network sources and private endpoint posture. + +Takeaway: 2 storage accounts visible; 1 allow public blob access, 1 keep public network access enabled, 1 allow shared-key access. + diff --git a/command-output/tokens-credentials/output.json b/command-output/tokens-credentials/output.json new file mode 100644 index 0000000..b760c99 --- /dev/null +++ b/command-output/tokens-credentials/output.json @@ -0,0 +1,183 @@ +{ + "findings": [ + { + "description": "AppService 'app-public-api' exposes credential-like setting 'DB_PASSWORD' as plain-text management-plane app configuration. Check env-vars for the exact setting context behind this credential clue.", + "id": "tokens-credentials-plain-text-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api-app-setting-setting-db-password", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api", + "aaaa1111-1111-1111-1111-111111111111" + ], + "severity": "high", + "title": "Credential-like value is exposed in plain-text app settings" + }, + { + "description": "FunctionApp 'func-orders' exposes credential-like setting 'AzureWebJobsStorage' as plain-text management-plane app configuration. Check env-vars for the exact setting context behind this credential clue.", + "id": "tokens-credentials-plain-text-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders-app-setting-setting-azurewebjobsstorage", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-identities/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-orders", + "cccc2222-2222-2222-2222-222222222222" + ], + "severity": "high", + "title": "Credential-like value is exposed in plain-text app settings" + }, + { + "description": "VM 'vm-web-01' is publicly reachable and exposes a token minting path through IMDS for its attached managed identity. Check endpoints for the ingress path, then managed-identities and permissions for Azure control.", + "id": "tokens-credentials-managed-identity-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01-imds-public-ip-52-160-10-20-identities-1", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-app" + ], + "severity": "high", + "title": "Publicly reachable workload can mint tokens with managed identity" + }, + { + "description": "AppService 'app-empty-mi' can request tokens through attached managed identity (SystemAssigned). Check managed-identities for the identity path, then permissions for Azure control.", + "id": "tokens-credentials-managed-identity-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-empty-mi-workload-identity-systemassigned", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-empty-mi", + "eeee3333-3333-3333-3333-333333333333" + ], + "severity": "medium", + "title": "Workload can mint tokens with managed identity" + }, + { + "description": "AppService 'app-public-api' can request tokens through attached managed identity (SystemAssigned). Check managed-identities for the identity path, then permissions for Azure control.", + "id": "tokens-credentials-managed-identity-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api-workload-identity-systemassigned", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api", + "aaaa1111-1111-1111-1111-111111111111" + ], + "severity": "medium", + "title": "Workload can mint tokens with managed identity" + }, + { + "description": "FunctionApp 'func-orders' uses setting 'PAYMENT_API_KEY' to reach Key Vault-backed secret material (kvlabopen01.vault.azure.net/secrets/payment-api-key) via SystemAssigned. Check keyvault for the referenced secret boundary, then managed-identities for the backing workload identity.", + "id": "tokens-credentials-keyvault-ref-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders-app-setting-target-kvlabopen01-vault-azure-net-secrets-payment-api-key-identity-systemassigned", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-identities/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-orders", + "cccc2222-2222-2222-2222-222222222222" + ], + "severity": "low", + "title": "Workload setting depends on Key Vault-backed secret retrieval" + }, + { + "description": "FunctionApp 'func-orders' can request tokens through attached managed identity (SystemAssigned, UserAssigned). Check managed-identities for the identity path, then permissions for Azure control.", + "id": "tokens-credentials-managed-identity-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders-workload-identity-systemassigned-userassigned-user-assigned-1", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-identities/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-orders", + "cccc2222-2222-2222-2222-222222222222" + ], + "severity": "medium", + "title": "Workload can mint tokens with managed identity" + }, + { + "description": "Deployment 'kv-secrets' recorded 1 output values in deployment history. Check arm-deployments for the exact output context behind this credential clue.", + "id": "tokens-credentials-deployment-output-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.Resources/deployments/kv-secrets-deployment-history-outputs-1-providers-1", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.Resources/deployments/kv-secrets" + ], + "severity": "medium", + "title": "Deployment history records output values" + }, + { + "description": "Deployment 'sub-foundation' recorded 2 output values in deployment history. Check arm-deployments for the exact output context behind this credential clue.", + "id": "tokens-credentials-deployment-output-/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Resources/deployments/sub-foundation-deployment-history-outputs-2-providers-2", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Resources/deployments/sub-foundation" + ], + "severity": "medium", + "title": "Deployment history records output values" + }, + { + "description": "VMSS 'vmss-edge-01' exposes a token minting path through IMDS for its attached managed identity. Check managed-identities for the identity path, then permissions for Azure control.", + "id": "tokens-credentials-managed-identity-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachineScaleSets/vmss-edge-01-imds-public-ip-none-identities-1", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachineScaleSets/vmss-edge-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachineScaleSets/vmss-edge-01/identities/system" + ], + "severity": "medium", + "title": "Workload can mint tokens with managed identity" + }, + { + "description": "Deployment 'kv-secrets' references remote template or parameter content that may expose reusable configuration or credential context. Check arm-deployments for the linked template or parameter path behind this credential clue.", + "id": "tokens-credentials-linked-content-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.Resources/deployments/kv-secrets-deployment-history-parameters-example-blob-core-windows-net-parameters-kv-secrets-parameters-json", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-secrets/providers/Microsoft.Resources/deployments/kv-secrets" + ], + "severity": "low", + "title": "Deployment history references remote template or parameter content" + }, + { + "description": "Deployment 'sub-foundation' references remote template or parameter content that may expose reusable configuration or credential context. Check arm-deployments for the linked template or parameter path behind this credential clue.", + "id": "tokens-credentials-linked-content-/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Resources/deployments/sub-foundation-deployment-history-template-example-blob-core-windows-net-templates-sub-foundation-json", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Resources/deployments/sub-foundation" + ], + "severity": "low", + "title": "Deployment history references remote template or parameter content" + } + ], + "issues": [], + "metadata": { + "command": "tokens-credentials", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "surfaces": [ + { + "access_path": "app-setting", + "asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api", + "asset_kind": "AppService", + "asset_name": "app-public-api", + "location": "eastus", + "operator_signal": "setting=DB_PASSWORD", + "priority": "high", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api", + "aaaa1111-1111-1111-1111-111111111111" + ], + "resource_group": "rg-apps", + "summary": "AppService 'app-public-api' exposes credential-like setting 'DB_PASSWORD' as plain-text management-plane app configuration. Check env-vars for the exact setting context behind this credential clue.", + "surface_type": "plain-text-secret" + }, + { + "access_path": "app-setting", + "asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders", + "asset_kind": "FunctionApp", + "asset_name": "func-orders", + "location": "eastus", + "operator_signal": "setting=AzureWebJobsStorage", + "priority": "high", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/func-orders", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-identities/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-orders", + "cccc2222-2222-2222-2222-222222222222" + ], + "resource_group": "rg-apps", + "summary": "FunctionApp 'func-orders' exposes credential-like setting 'AzureWebJobsStorage' as plain-text management-plane app configuration. Check env-vars for the exact setting context behind this credential clue.", + "surface_type": "plain-text-secret" + }, + { + "access_path": "imds", + "asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "asset_kind": "VM", + "asset_name": "vm-web-01", + "location": "eastus", + "operator_signal": "public-ip=52.160.10.20; identities=1", + "priority": "high", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-app" + ], + "resource_group": "rg-workload", + "summary": "VM 'vm-web-01' is publicly reachable and exposes a token minting path through IMDS for its attached managed identity. Check endpoints for the ingress path, then managed-identities and permissions for Azure control.", + "surface_type": "managed-identity-token" + } + ] +} diff --git a/command-output/tokens-credentials/table.txt b/command-output/tokens-credentials/table.txt new file mode 100644 index 0000000..d9aef38 --- /dev/null +++ b/command-output/tokens-credentials/table.txt @@ -0,0 +1,57 @@ + azurefox tokens-credentials +┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ asset ┃ kind ┃ surface ┃ access path ┃ priority ┃ operator signal ┃ next review ┃ why it matters ┃ +┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ app-public-api │ AppService │ plain-text-secret │ app-setting │ high │ setting=DB_PASSWORD │ Check env-vars for the │ AppService │ +│ │ │ │ │ │ │ exact setting context │ 'app-public-api' │ +│ │ │ │ │ │ │ behind this credential │ exposes credential-like │ +│ │ │ │ │ │ │ clue. │ setting 'DB_PASSWORD' │ +│ │ │ │ │ │ │ │ as plain-text │ +│ │ │ │ │ │ │ │ management-plane app │ +│ │ │ │ │ │ │ │ configuration. Check │ +│ │ │ │ │ │ │ │ env-vars for the exact │ +│ │ │ │ │ │ │ │ setting context behind │ +│ │ │ │ │ │ │ │ this credential clue. │ +│ func-orders │ FunctionApp │ plain-text-secret │ app-setting │ high │ setting=AzureWebJobsSt… │ Check env-vars for the │ FunctionApp │ +│ │ │ │ │ │ │ exact setting context │ 'func-orders' exposes │ +│ │ │ │ │ │ │ behind this credential │ credential-like setting │ +│ │ │ │ │ │ │ clue. │ 'AzureWebJobsStorage' │ +│ │ │ │ │ │ │ │ as plain-text │ +│ │ │ │ │ │ │ │ management-plane app │ +│ │ │ │ │ │ │ │ configuration. Check │ +│ │ │ │ │ │ │ │ env-vars for the exact │ +│ │ │ │ │ │ │ │ setting context behind │ +│ │ │ │ │ │ │ │ this credential clue. │ +│ vm-web-01 │ VM │ managed-identity-token │ imds │ high │ public-ip=52.160.10.20; │ Check endpoints for the │ VM 'vm-web-01' is │ +│ │ │ │ │ │ identities=1 │ ingress path, then │ publicly reachable and │ +│ │ │ │ │ │ │ managed-identities and │ exposes a token minting │ +│ │ │ │ │ │ │ permissions for Azure │ path through IMDS for │ +│ │ │ │ │ │ │ control. │ its attached managed │ +│ │ │ │ │ │ │ │ identity. Check │ +│ │ │ │ │ │ │ │ endpoints for the │ +│ │ │ │ │ │ │ │ ingress path, then │ +│ │ │ │ │ │ │ │ managed-identities and │ +│ │ │ │ │ │ │ │ permissions for Azure │ +│ │ │ │ │ │ │ │ control. │ +└────────────────┴─────────────┴────────────────────────┴─────────────┴──────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┘ + +Findings: +- HIGH: Credential-like value is exposed in plain-text app settings + AppService 'app-public-api' exposes credential-like setting 'DB_PASSWORD' as plain-text management-plane app configuration. Check env-vars for the exact +setting context behind this credential clue. +- HIGH: Credential-like value is exposed in plain-text app settings + FunctionApp 'func-orders' exposes credential-like setting 'AzureWebJobsStorage' as plain-text management-plane app configuration. Check env-vars for the exact +setting context behind this credential clue. +- HIGH: Publicly reachable workload can mint tokens with managed identity + VM 'vm-web-01' is publicly reachable and exposes a token minting path through IMDS for its attached managed identity. Check endpoints for the ingress path, +then managed-identities and permissions for Azure control. +- MEDIUM: Workload can mint tokens with managed identity + AppService 'app-empty-mi' can request tokens through attached managed identity (SystemAssigned). Check managed-identities for the identity path, then +permissions for Azure control. +- MEDIUM: Workload can mint tokens with managed identity + AppService 'app-public-api' can request tokens through attached managed identity (SystemAssigned). Check managed-identities for the identity path, then +permissions for Azure control. +- ... plus 7 more findings in JSON artifacts. + +Takeaway: 3 token or credential surfaces across 3 assets; 1 managed-identity-token, 2 plain-text-secret and 12 findings. + diff --git a/command-output/vms/output.json b/command-output/vms/output.json new file mode 100644 index 0000000..bd9d7ef --- /dev/null +++ b/command-output/vms/output.json @@ -0,0 +1,45 @@ +{ + "findings": [ + { + "description": "Workload 'vm-web-01' has public IP exposure and one or more managed identities. Validate identity privileges and ingress hardening.", + "id": "vm-public-identity-/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-app" + ], + "severity": "medium", + "title": "Public workload with attached identity" + } + ], + "issues": [], + "metadata": { + "command": "vms", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "vm_assets": [ + { + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "identity_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-app" + ], + "location": "eastus", + "name": "vm-web-01", + "nic_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/networkInterfaces/nic-web-01" + ], + "power_state": "running", + "private_ips": [ + "10.0.1.4" + ], + "public_ips": [ + "52.160.10.20" + ], + "resource_group": "rg-workload", + "vm_type": "vm" + } + ] +} diff --git a/command-output/vms/table.txt b/command-output/vms/table.txt new file mode 100644 index 0000000..363b751 --- /dev/null +++ b/command-output/vms/table.txt @@ -0,0 +1,13 @@ + azurefox vms +┏━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ asset ┃ type ┃ public ips ┃ private ips ┃ identities ┃ +┡━━━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ vm-web-01 │ vm │ 52.160.10.20 │ 10.0.1.4 │ /subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ManagedI… │ +└───────────┴──────┴──────────────┴─────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + +Findings: +- MEDIUM: Public workload with attached identity + Workload 'vm-web-01' has public IP exposure and one or more managed identities. Validate identity privileges and ingress hardening. + +Takeaway: 1 compute assets visible; 1 have public IP exposure. + diff --git a/command-output/vmss/output.json b/command-output/vmss/output.json new file mode 100644 index 0000000..c6fba84 --- /dev/null +++ b/command-output/vmss/output.json @@ -0,0 +1,85 @@ +{ + "findings": [], + "issues": [], + "metadata": { + "command": "vmss", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "vmss_assets": [ + { + "application_gateway_backend_pool_count": 0, + "client_id": "77770000-0000-0000-0000-000000000002", + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachineScaleSets/vmss-edge-01", + "identity_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachineScaleSets/vmss-edge-01/identities/system" + ], + "identity_type": "SystemAssigned", + "inbound_nat_pool_count": 1, + "instance_count": 6, + "load_balancer_backend_pool_count": 1, + "location": "eastus", + "name": "vmss-edge-01", + "nic_configuration_count": 1, + "orchestration_mode": "Uniform", + "overprovision": true, + "principal_id": "77770000-0000-0000-0000-000000000001", + "public_ip_configuration_count": 1, + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachineScaleSets/vmss-edge-01", + "77770000-0000-0000-0000-000000000001", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachineScaleSets/vmss-edge-01/identities/system", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-network/providers/Microsoft.Network/virtualNetworks/vnet-edge/subnets/app", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-network/providers/Microsoft.Network/loadBalancers/lb-edge/backendAddressPools/web", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-network/providers/Microsoft.Network/loadBalancers/lb-edge/inboundNatPools/ssh" + ], + "resource_group": "rg-workload", + "single_placement_group": false, + "sku_name": "Standard_D4s_v5", + "subnet_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-network/providers/Microsoft.Network/virtualNetworks/vnet-edge/subnets/app" + ], + "summary": "Virtual Machine Scale Sets (VMSS) asset 'vmss-edge-01' carries SKU Standard_D4s_v5, 6 configured instance(s) and uses managed identity (SystemAssigned). Visible frontend or network cues: 1 public IP config(s), 1 inbound NAT pool ref(s), 1 LB backend pool ref(s), 1 NIC config(s), 1 subnet ref(s). Visible posture: orchestration Uniform, upgrade Rolling, single-placement-group no, overprovision yes, zone-balance yes, zones 1,2.", + "upgrade_mode": "Rolling", + "zone_balance": true, + "zones": [ + "1", + "2" + ] + }, + { + "application_gateway_backend_pool_count": 0, + "client_id": null, + "id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-batch/providers/Microsoft.Compute/virtualMachineScaleSets/vmss-batch-01", + "identity_ids": [], + "identity_type": null, + "inbound_nat_pool_count": 0, + "instance_count": 2, + "load_balancer_backend_pool_count": 0, + "location": "centralus", + "name": "vmss-batch-01", + "nic_configuration_count": 1, + "orchestration_mode": "Flexible", + "overprovision": false, + "principal_id": null, + "public_ip_configuration_count": 0, + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-batch/providers/Microsoft.Compute/virtualMachineScaleSets/vmss-batch-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-network/providers/Microsoft.Network/virtualNetworks/vnet-batch/subnets/workers" + ], + "resource_group": "rg-batch", + "single_placement_group": true, + "sku_name": "Standard_D2s_v5", + "subnet_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-network/providers/Microsoft.Network/virtualNetworks/vnet-batch/subnets/workers" + ], + "summary": "Virtual Machine Scale Sets (VMSS) asset 'vmss-batch-01' carries SKU Standard_D2s_v5, 2 configured instance(s) and has no managed identity visible from the current read path. Visible frontend or network cues: 1 NIC config(s), 1 subnet ref(s). Visible posture: orchestration Flexible, upgrade Manual, single-placement-group yes, overprovision no, zone-balance no.", + "upgrade_mode": "Manual", + "zone_balance": false, + "zones": [] + } + ] +} diff --git a/command-output/vmss/table.txt b/command-output/vmss/table.txt new file mode 100644 index 0000000..f0b04b9 --- /dev/null +++ b/command-output/vmss/table.txt @@ -0,0 +1,61 @@ + azurefox vmss +┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓ +┃ scale set ┃ location ┃ sku / capacity ┃ orchestration ┃ identity ┃ frontend ┃ network ┃ why it matters ┃ +┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩ +│ vmss-edge-01 │ eastus │ Standard_D4s_v5; │ Uniform; │ SystemAssigned; │ public-ip=1; │ nic-configs=1; │ Virtual Machine │ +│ │ │ instances=6; │ upgrade=Rolling; │ ids=1 │ nat-pools=1; │ subnet=app; │ Scale Sets (VMSS) │ +│ │ │ zones=2 │ spg=no; │ │ lb-backends=1 │ zone-balance=yes │ asset │ +│ │ │ │ overprov=yes │ │ │ │ 'vmss-edge-01' │ +│ │ │ │ │ │ │ │ carries SKU │ +│ │ │ │ │ │ │ │ Standard_D4s_v5, 6 │ +│ │ │ │ │ │ │ │ configured │ +│ │ │ │ │ │ │ │ instance(s) and │ +│ │ │ │ │ │ │ │ uses managed │ +│ │ │ │ │ │ │ │ identity │ +│ │ │ │ │ │ │ │ (SystemAssigned). │ +│ │ │ │ │ │ │ │ Visible frontend │ +│ │ │ │ │ │ │ │ or network cues: 1 │ +│ │ │ │ │ │ │ │ public IP │ +│ │ │ │ │ │ │ │ config(s), 1 │ +│ │ │ │ │ │ │ │ inbound NAT pool │ +│ │ │ │ │ │ │ │ ref(s), 1 LB │ +│ │ │ │ │ │ │ │ backend pool │ +│ │ │ │ │ │ │ │ ref(s), 1 NIC │ +│ │ │ │ │ │ │ │ config(s), 1 │ +│ │ │ │ │ │ │ │ subnet ref(s). │ +│ │ │ │ │ │ │ │ Visible posture: │ +│ │ │ │ │ │ │ │ orchestration │ +│ │ │ │ │ │ │ │ Uniform, upgrade │ +│ │ │ │ │ │ │ │ Rolling, │ +│ │ │ │ │ │ │ │ single-placement-… │ +│ │ │ │ │ │ │ │ no, overprovision │ +│ │ │ │ │ │ │ │ yes, zone-balance │ +│ │ │ │ │ │ │ │ yes, zones 1,2. │ +│ vmss-batch-01 │ centralus │ Standard_D2s_v5; │ Flexible; │ - │ - │ nic-configs=1; │ Virtual Machine │ +│ │ │ instances=2 │ upgrade=Manual; │ │ │ subnet=workers; │ Scale Sets (VMSS) │ +│ │ │ │ spg=yes; │ │ │ zone-balance=no │ asset │ +│ │ │ │ overprov=no │ │ │ │ 'vmss-batch-01' │ +│ │ │ │ │ │ │ │ carries SKU │ +│ │ │ │ │ │ │ │ Standard_D2s_v5, 2 │ +│ │ │ │ │ │ │ │ configured │ +│ │ │ │ │ │ │ │ instance(s) and │ +│ │ │ │ │ │ │ │ has no managed │ +│ │ │ │ │ │ │ │ identity visible │ +│ │ │ │ │ │ │ │ from the current │ +│ │ │ │ │ │ │ │ read path. Visible │ +│ │ │ │ │ │ │ │ frontend or │ +│ │ │ │ │ │ │ │ network cues: 1 │ +│ │ │ │ │ │ │ │ NIC config(s), 1 │ +│ │ │ │ │ │ │ │ subnet ref(s). │ +│ │ │ │ │ │ │ │ Visible posture: │ +│ │ │ │ │ │ │ │ orchestration │ +│ │ │ │ │ │ │ │ Flexible, upgrade │ +│ │ │ │ │ │ │ │ Manual, │ +│ │ │ │ │ │ │ │ single-placement-… │ +│ │ │ │ │ │ │ │ yes, overprovision │ +│ │ │ │ │ │ │ │ no, zone-balance │ +│ │ │ │ │ │ │ │ no. │ +└───────────────┴───────────┴─────────────────────┴─────────────────────┴─────────────────────┴─────────────────────┴─────────────────────┴────────────────────┘ + +Takeaway: 2 VM scale sets visible; 1 show public frontend cues, 1 carry managed identity context, and 8 configured instances are visible. + diff --git a/command-output/whoami/output.json b/command-output/whoami/output.json new file mode 100644 index 0000000..40c3d36 --- /dev/null +++ b/command-output/whoami/output.json @@ -0,0 +1,30 @@ +{ + "effective_scopes": [ + { + "display_name": "azurefox-lab-sub", + "id": "/subscriptions/22222222-2222-2222-2222-222222222222", + "scope_type": "subscription" + } + ], + "issues": [], + "metadata": { + "command": "whoami", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": "fixture" + }, + "principal": { + "display_name": "azurefox-lab-sp", + "id": "33333333-3333-3333-3333-333333333333", + "principal_type": "ServicePrincipal", + "tenant_id": "11111111-1111-1111-1111-111111111111" + }, + "subscription": { + "display_name": "azurefox-lab-sub", + "id": "22222222-2222-2222-2222-222222222222", + "state": "Enabled" + }, + "tenant_id": "11111111-1111-1111-1111-111111111111" +} diff --git a/command-output/whoami/table.txt b/command-output/whoami/table.txt new file mode 100644 index 0000000..48c5a1c --- /dev/null +++ b/command-output/whoami/table.txt @@ -0,0 +1,9 @@ + azurefox whoami +┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┓ +┃ subscription ┃ principal ┃ type ┃ token ┃ scope ┃ +┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━┩ +│ azurefox-lab-sub │ azurefox-lab-sp │ ServicePrincipal │ fixture │ azurefox-lab-sub │ +└──────────────────┴─────────────────┴──────────────────┴─────────┴──────────────────┘ + +Takeaway: Operating as azurefox-lab-sp (ServicePrincipal) in azurefox-lab-sub. + diff --git a/command-output/workloads/output.json b/command-output/workloads/output.json new file mode 100644 index 0000000..1b20324 --- /dev/null +++ b/command-output/workloads/output.json @@ -0,0 +1,92 @@ +{ + "findings": [], + "issues": [], + "metadata": { + "command": "workloads", + "generated_at": "", + "schema_version": "1.2.0", + "subscription_id": "22222222-2222-2222-2222-222222222222", + "tenant_id": "11111111-1111-1111-1111-111111111111", + "token_source": null + }, + "workloads": [ + { + "asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "asset_kind": "VM", + "asset_name": "vm-web-01", + "endpoints": [ + "52.160.10.20" + ], + "exposure_families": [ + "public-ip" + ], + "identity_client_id": null, + "identity_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-app" + ], + "identity_principal_id": null, + "identity_type": "UserAssigned", + "ingress_paths": [ + "direct-vm-ip" + ], + "location": "eastus", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Compute/virtualMachines/vm-web-01", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ua-app", + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-workload/providers/Microsoft.Network/networkInterfaces/nic-web-01" + ], + "resource_group": "rg-workload", + "summary": "VM 'vm-web-01' exposes reachable endpoint '52.160.10.20' and carries managed identity context (UserAssigned). Visible signals: public-ip=1, private-ip=1, nic=1. Use this as a quick workload census pivot before deeper service-specific review." + }, + { + "asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-empty-mi", + "asset_kind": "AppService", + "asset_name": "app-empty-mi", + "endpoints": [ + "app-empty-mi.azurewebsites.net" + ], + "exposure_families": [ + "managed-web-hostname" + ], + "identity_client_id": "ffff3333-3333-3333-3333-333333333333", + "identity_ids": [], + "identity_principal_id": "eeee3333-3333-3333-3333-333333333333", + "identity_type": "SystemAssigned", + "ingress_paths": [ + "azurewebsites-default-hostname" + ], + "location": "eastus", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-empty-mi", + "eeee3333-3333-3333-3333-333333333333" + ], + "resource_group": "rg-apps", + "summary": "AppService 'app-empty-mi' publishes visible endpoint hostname 'app-empty-mi.azurewebsites.net' and carries managed identity context (SystemAssigned). Visible signals: default-hostname. Use this as a quick workload census pivot before deeper service-specific review." + }, + { + "asset_id": "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api", + "asset_kind": "AppService", + "asset_name": "app-public-api", + "endpoints": [ + "app-public-api.azurewebsites.net" + ], + "exposure_families": [ + "managed-web-hostname" + ], + "identity_client_id": "bbbb1111-1111-1111-1111-111111111111", + "identity_ids": [], + "identity_principal_id": "aaaa1111-1111-1111-1111-111111111111", + "identity_type": "SystemAssigned", + "ingress_paths": [ + "azurewebsites-default-hostname" + ], + "location": "eastus", + "related_ids": [ + "/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/rg-apps/providers/Microsoft.Web/sites/app-public-api", + "aaaa1111-1111-1111-1111-111111111111" + ], + "resource_group": "rg-apps", + "summary": "AppService 'app-public-api' publishes visible endpoint hostname 'app-public-api.azurewebsites.net' and carries managed identity context (SystemAssigned). Visible signals: default-hostname. Use this as a quick workload census pivot before deeper service-specific review." + } + ] +} diff --git a/command-output/workloads/table.txt b/command-output/workloads/table.txt new file mode 100644 index 0000000..c021701 --- /dev/null +++ b/command-output/workloads/table.txt @@ -0,0 +1,32 @@ + azurefox workloads +┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ workload ┃ kind ┃ identity ┃ endpoints ┃ ingress ┃ why it matters ┃ +┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ vm-web-01 │ VM │ UserAssigned; ids=1 │ 52.160.10.20 │ direct-vm-ip │ VM 'vm-web-01' exposes reachable │ +│ │ │ │ │ │ endpoint '52.160.10.20' and carries │ +│ │ │ │ │ │ managed identity context │ +│ │ │ │ │ │ (UserAssigned). Visible signals: │ +│ │ │ │ │ │ public-ip=1, private-ip=1, nic=1. │ +│ │ │ │ │ │ Use this as a quick workload census │ +│ │ │ │ │ │ pivot before deeper service-specific │ +│ │ │ │ │ │ review. │ +│ app-empty-mi │ AppService │ SystemAssigned │ app-empty-mi.azurewebsites.net │ azurewebsites-default-hostname │ AppService 'app-empty-mi' publishes │ +│ │ │ │ │ │ visible endpoint hostname │ +│ │ │ │ │ │ 'app-empty-mi.azurewebsites.net' and │ +│ │ │ │ │ │ carries managed identity context │ +│ │ │ │ │ │ (SystemAssigned). Visible signals: │ +│ │ │ │ │ │ default-hostname. Use this as a │ +│ │ │ │ │ │ quick workload census pivot before │ +│ │ │ │ │ │ deeper service-specific review. │ +│ app-public-api │ AppService │ SystemAssigned │ app-public-api.azurewebsites.net │ azurewebsites-default-hostname │ AppService 'app-public-api' │ +│ │ │ │ │ │ publishes visible endpoint hostname │ +│ │ │ │ │ │ 'app-public-api.azurewebsites.net' │ +│ │ │ │ │ │ and carries managed identity context │ +│ │ │ │ │ │ (SystemAssigned). Visible signals: │ +│ │ │ │ │ │ default-hostname. Use this as a │ +│ │ │ │ │ │ quick workload census pivot before │ +│ │ │ │ │ │ deeper service-specific review. │ +└────────────────┴────────────┴─────────────────────┴──────────────────────────────────┴────────────────────────────────┴──────────────────────────────────────┘ + +Takeaway: 3 workloads visible; 3 with visible endpoint paths, 3 with identity context, across 1 compute and 2 web assets. + diff --git a/scripts/generate_command_output_examples.py b/scripts/generate_command_output_examples.py new file mode 100644 index 0000000..57428f1 --- /dev/null +++ b/scripts/generate_command_output_examples.py @@ -0,0 +1,95 @@ +from __future__ import annotations + +import json +import tempfile +from pathlib import Path + +from azurefox.chains import implemented_chain_families, run_chain_family +from azurefox.collectors.provider import FixtureProvider +from azurefox.config import GlobalOptions +from azurefox.models.common import OutputMode +from azurefox.output.writer import PRIMARY_COLLECTION_KEYS +from azurefox.registry import get_command_specs +from azurefox.render.table import render_table + +EXAMPLE_ROW_LIMIT = 3 +OUTPUT_ROOT = Path("command-output") +FIXTURE_ROOT = Path("tests/fixtures/lab_tenant") + + +def normalize_payload(payload: dict) -> dict: + normalized = json.loads(json.dumps(payload)) + metadata = normalized.get("metadata") + if isinstance(metadata, dict): + metadata["generated_at"] = "" + if metadata.get("devops_organization") is None: + metadata.pop("devops_organization", None) + return normalized + + +def trim_payload(command: str, payload: dict, *, limit: int = EXAMPLE_ROW_LIMIT) -> dict: + primary_key = PRIMARY_COLLECTION_KEYS.get(command) + if primary_key is None: + return payload + + trimmed = dict(payload) + primary_value = trimmed.get(primary_key) + if isinstance(primary_value, list): + trimmed[primary_key] = primary_value[:limit] + return trimmed + + +def write_example_files(destination: Path, payload: dict, table_text: str) -> None: + destination.mkdir(parents=True, exist_ok=True) + (destination / "output.json").write_text( + json.dumps(payload, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + (destination / "table.txt").write_text(table_text + "\n", encoding="utf-8") + + +def generate_flat_command_examples(output_root: Path) -> None: + provider = FixtureProvider(FIXTURE_ROOT) + options = GlobalOptions( + tenant="11111111-1111-1111-1111-111111111111", + subscription="22222222-2222-2222-2222-222222222222", + output=OutputMode.TABLE, + outdir=Path("."), + debug=False, + ) + + for spec in get_command_specs(): + if spec.name == "all-checks": + continue + model = spec.collector(provider, options) + payload = trim_payload(spec.name, normalize_payload(model.model_dump(mode="json"))) + table_text = render_table(spec.name, payload) + write_example_files(output_root / spec.name, payload, table_text) + + +def generate_chain_family_examples(output_root: Path) -> None: + provider = FixtureProvider(FIXTURE_ROOT) + with tempfile.TemporaryDirectory(prefix="azurefox-command-output-") as tmp: + options = GlobalOptions( + tenant="11111111-1111-1111-1111-111111111111", + subscription="22222222-2222-2222-2222-222222222222", + output=OutputMode.TABLE, + outdir=Path(tmp), + debug=False, + ) + + for family in implemented_chain_families(): + model = run_chain_family(provider, options, family) + payload = trim_payload("chains", normalize_payload(model.model_dump(mode="json"))) + table_text = render_table("chains", payload) + write_example_files(output_root / "chains" / family, payload, table_text) + + +def main() -> None: + OUTPUT_ROOT.mkdir(parents=True, exist_ok=True) + generate_flat_command_examples(OUTPUT_ROOT) + generate_chain_family_examples(OUTPUT_ROOT) + + +if __name__ == "__main__": + main() diff --git a/src/azurefox/collectors/commands.py b/src/azurefox/collectors/commands.py index b0c955d..38330b9 100644 --- a/src/azurefox/collectors/commands.py +++ b/src/azurefox/collectors/commands.py @@ -60,6 +60,11 @@ permissions_operator_signal, permissions_summary, ) +from azurefox.role_trust_hints import ( + role_trust_next_review_hint, + role_trust_operator_signal, + role_trust_summary, +) def collect_whoami(provider: BaseProvider, options: GlobalOptions) -> WhoAmIOutput: @@ -380,11 +385,13 @@ def collect_privesc(provider: BaseProvider, options: GlobalOptions) -> PrivescOu def collect_role_trusts(provider: BaseProvider, options: GlobalOptions) -> RoleTrustsOutput: data = provider.role_trusts(options.role_trusts_mode) + trusts = _enrich_role_trust_rows(data.get("trusts", [])) trusts = sorted( - data.get("trusts", []), + trusts, key=lambda item: ( str(item.get("confidence") or "").lower() != "confirmed", _role_trust_priority(item), + _role_trust_follow_on_rank(item.get("next_review")), item.get("source_name") or item.get("source_object_id") or "", item.get("target_name") or item.get("target_object_id") or "", ), @@ -864,6 +871,17 @@ def _role_trust_priority(item: dict) -> tuple[int, int]: return trust_rank, evidence_rank +def _role_trust_follow_on_rank(next_review: object) -> int: + text = str(next_review or "").lower() + if "check permissions" in text: + return 0 + if "review ownership" in text: + return 1 + if "check cross-tenant" in text: + return 2 + return 3 + + def _storage_priority_rank(item: dict) -> tuple[int, int, int, int, int, int]: public_access_rank = 0 if item.get("public_access") else 1 @@ -1233,6 +1251,43 @@ def _enrich_permission_rows(permissions: list[dict], principals: list[dict]) -> return sorted(enriched, key=_permission_row_sort_key) +def _enrich_role_trust_rows(trusts: list[dict]) -> list[dict]: + enriched: list[dict] = [] + + for trust in trusts: + item = dict(trust) + trust_type = str(item.get("trust_type") or "") + source_name = item.get("source_name") + target_name = item.get("target_name") + summary = str(item.get("summary") or "") + + item["operator_signal"] = role_trust_operator_signal( + trust_type=trust_type, + source_name=source_name, + target_name=target_name, + summary=summary, + ) + item["next_review"] = role_trust_next_review_hint( + trust_type=trust_type, + source_name=source_name, + source_object_id=item.get("source_object_id") or "unknown", + target_name=target_name, + target_object_id=item.get("target_object_id") or "unknown", + target_type=str(item.get("target_type") or "identity"), + summary=summary, + ) + item["summary"] = role_trust_summary( + trust_type=trust_type, + source_name=source_name, + target_name=target_name, + summary=summary, + next_review=item["next_review"], + ) + enriched.append(item) + + return enriched + + def _permission_row_sort_key(item: dict) -> tuple[bool, int, int, int, int, str, str]: return ( not bool(item.get("privileged")), diff --git a/src/azurefox/help.py b/src/azurefox/help.py index 0ed9fd5..c07c528 100644 --- a/src/azurefox/help.py +++ b/src/azurefox/help.py @@ -701,7 +701,8 @@ class SectionHelpTopic: "source_name", "target_name", "confidence", - "evidence_type", + "operator_signal", + "next_review", ), attack_leads=( AttackLead("Initial Access", "Trusted Relationship"), diff --git a/src/azurefox/models/common.py b/src/azurefox/models/common.py index 8caf6af..f0269c6 100644 --- a/src/azurefox/models/common.py +++ b/src/azurefox/models/common.py @@ -109,6 +109,8 @@ class RoleTrustSummary(BaseModel): target_type: str evidence_type: str confidence: str + operator_signal: str | None = None + next_review: str | None = None summary: str related_ids: list[str] = Field(default_factory=list) diff --git a/src/azurefox/output/style.py b/src/azurefox/output/style.py index 8f339e7..58d5095 100644 --- a/src/azurefox/output/style.py +++ b/src/azurefox/output/style.py @@ -61,7 +61,8 @@ "permissions": "Ranking principals by high-impact RBAC exposure and the next likely follow-on.", "privesc": "Triage likely privilege-escalation and workload identity abuse paths.", "role-trusts": ( - "Reviewing high-signal identity trust edges without implying delegated or admin consent." + "Reviewing high-signal identity trust edges and the clearest next review without " + "implying delegated or admin consent." ), "cross-tenant": ( "Reviewing outside-tenant trust, delegated management, and tenant policy cues that most " diff --git a/src/azurefox/render/table.py b/src/azurefox/render/table.py index 362799f..c81960e 100644 --- a/src/azurefox/render/table.py +++ b/src/azurefox/render/table.py @@ -587,7 +587,8 @@ def _table_spec(command: str, payload: dict) -> tuple[list[tuple[str, str]], lis ("source", "source"), ("target", "target"), ("confidence", "confidence"), - ("why_it_matters", "why it matters"), + ("operator_signal", "operator signal"), + ("next_review", "next review"), ], [ { @@ -595,7 +596,8 @@ def _table_spec(command: str, payload: dict) -> tuple[list[tuple[str, str]], lis "source": item.get("source_name") or item.get("source_object_id"), "target": item.get("target_name") or item.get("target_object_id"), "confidence": item.get("confidence"), - "why_it_matters": item.get("summary"), + "operator_signal": item.get("operator_signal"), + "next_review": item.get("next_review"), } for item in payload.get("trusts", []) ], @@ -957,9 +959,24 @@ def _takeaway_for_command(command: str, payload: dict) -> str: mode = payload.get("mode") or "fast" families = Counter(item.get("trust_type") or "unknown" for item in trusts) counts = ", ".join(f"{count} {name}" for name, count in sorted(families.items())) + privilege_follow_ons = sum( + "privilege confirmation next" in str(item.get("operator_signal") or "").lower() + for item in trusts + ) + ownership_follow_ons = sum( + "ownership review next" in str(item.get("operator_signal") or "").lower() + for item in trusts + ) + outside_follow_ons = sum( + "outside-tenant follow-on" in str(item.get("operator_signal") or "").lower() + for item in trusts + ) return ( f"{len(trusts)} trust edges surfaced in {mode} mode; " f"{counts or 'no trust edges visible'}. " + f"{privilege_follow_ons} privilege-confirmation follow-ons, " + f"{ownership_follow_ons} ownership-review follow-ons, and " + f"{outside_follow_ons} outside-tenant follow-ons. " "Delegated and admin consent grants are out of scope for this command." ) diff --git a/src/azurefox/role_trust_hints.py b/src/azurefox/role_trust_hints.py new file mode 100644 index 0000000..3cbd5d3 --- /dev/null +++ b/src/azurefox/role_trust_hints.py @@ -0,0 +1,153 @@ +from __future__ import annotations + + +def role_trust_operator_signal( + *, + trust_type: str, + source_name: str | None, + target_name: str | None, + summary: str, +) -> str: + if _outside_tenant_follow_on( + trust_type=trust_type, + source_name=source_name, + target_name=target_name, + summary=summary, + ): + return "Trust expansion visible; outside-tenant follow-on." + if trust_type in {"app-owner", "service-principal-owner"}: + return "Indirect control visible; ownership review next." + if trust_type == "federated-credential": + return "Trust expansion visible; privilege confirmation next." + if trust_type == "app-to-service-principal": + return "Indirect control visible; privilege confirmation next." + return "Indirect control visible; privilege confirmation next." + + +def role_trust_next_review_hint( + *, + trust_type: str, + source_name: str | None, + source_object_id: str, + target_name: str | None, + target_object_id: str, + target_type: str, + summary: str, +) -> str: + if _outside_tenant_follow_on( + trust_type=trust_type, + source_name=source_name, + target_name=target_name, + summary=summary, + ): + return ( + "Check cross-tenant for related outside-tenant control or delegated-management " + f"paths around {_identity_ref(target_name, target_object_id, target_type)}." + ) + + if trust_type == "app-owner": + return ( + f"Review ownership around {_identity_ref(target_name, target_object_id, target_type)}; " + "if it backs an Azure-facing identity, confirm that identity in permissions." + ) + + if trust_type == "service-principal-owner": + return ( + f"Review ownership around {_identity_ref(target_name, target_object_id, target_type)}, " + "then confirm Azure control in permissions." + ) + + if trust_type == "federated-credential": + if target_type == "ServicePrincipal": + return ( + "Check permissions for Azure control on " + f"{_identity_ref(target_name, target_object_id, target_type)}." + ) + return ( + "Check permissions for the backing identity behind " + f"{_identity_ref(target_name, target_object_id, target_type)}." + ) + + if trust_type == "app-to-service-principal": + return ( + "Check permissions for Azure control on " + f"{_identity_ref(source_name, source_object_id, 'ServicePrincipal')}." + ) + + return ( + "Check permissions or rbac to confirm whether this trust edge reaches meaningful " + "Azure control." + ) + + +def role_trust_summary( + *, + trust_type: str, + source_name: str | None, + target_name: str | None, + summary: str, + next_review: str, +) -> str: + if _outside_tenant_follow_on( + trust_type=trust_type, + source_name=source_name, + target_name=target_name, + summary=summary, + ): + return ( + f"{summary} This row points to outside-tenant follow-up, not direct Azure role " + f"confirmation first. {next_review}" + ) + + if trust_type in {"app-owner", "service-principal-owner"}: + return ( + f"{summary} This is an indirect-control row: ownership is the visible trust path, " + f"not direct Azure privilege by itself. {next_review}" + ) + + if trust_type == "federated-credential": + return ( + f"{summary} This row shows trust expansion into the target identity rather than " + f"direct Azure privilege by itself. {next_review}" + ) + + if trust_type == "app-to-service-principal": + return ( + f"{summary} This row is a trust-edge and application-permission cue; confirm whether " + f"the same identity also holds Azure control. {next_review}" + ) + + return f"{summary} {next_review}" + + +def _outside_tenant_follow_on( + *, + trust_type: str, + source_name: str | None, + target_name: str | None, + summary: str, +) -> bool: + if trust_type not in {"app-owner", "service-principal-owner", "federated-credential"}: + return False + text = " ".join( + value for value in (summary, source_name or "", target_name or "") if value + ).lower() + return any( + marker in text + for marker in ( + "outside-tenant", + "cross-tenant", + "external tenant", + "externally owned", + "#ext#", + ) + ) + + +def _identity_ref(name: str | None, object_id: str, identity_type: str) -> str: + label = identity_type.replace("ServicePrincipal", "service principal").replace( + "Application", "application" + ) + if name: + return f"{label} '{name}'" + return f"{label} '{object_id}'" diff --git a/tests/golden/role-trusts.json b/tests/golden/role-trusts.json index 388beb7..1cd2d44 100644 --- a/tests/golden/role-trusts.json +++ b/tests/golden/role-trusts.json @@ -13,6 +13,8 @@ { "confidence": "confirmed", "evidence_type": "graph-federated-credential", + "next_review": "Check permissions for Azure control on service principal 'build-sp'.", + "operator_signal": "Trust expansion visible; privilege confirmation next.", "related_ids": [ "55555555-5555-5555-5555-555555555555", "fic-build-main", @@ -21,7 +23,7 @@ "source_name": "build-app", "source_object_id": "55555555-5555-5555-5555-555555555555", "source_type": "Application", - "summary": "Application 'build-app' trusts federated subject 'repo:TacoRocket/AzureFox:ref:refs/heads/main' from issuer 'https://token.actions.githubusercontent.com'.", + "summary": "Application 'build-app' trusts federated subject 'repo:TacoRocket/AzureFox:ref:refs/heads/main' from issuer 'https://token.actions.githubusercontent.com'. This row shows trust expansion into the target identity rather than direct Azure privilege by itself. Check permissions for Azure control on service principal 'build-sp'.", "target_name": "build-sp", "target_object_id": "66666666-6666-6666-6666-666666666666", "target_type": "ServicePrincipal", @@ -30,6 +32,8 @@ { "confidence": "confirmed", "evidence_type": "graph-owner", + "next_review": "Review ownership around service principal 'build-sp', then confirm Azure control in permissions.", + "operator_signal": "Indirect control visible; ownership review next.", "related_ids": [ "88888888-8888-8888-8888-888888888888", "66666666-6666-6666-6666-666666666666" @@ -37,7 +41,7 @@ "source_name": "automation-runner", "source_object_id": "88888888-8888-8888-8888-888888888888", "source_type": "ServicePrincipal", - "summary": "Owner 'automation-runner' can modify service principal 'build-sp'.", + "summary": "Owner 'automation-runner' can modify service principal 'build-sp'. This is an indirect-control row: ownership is the visible trust path, not direct Azure privilege by itself. Review ownership around service principal 'build-sp', then confirm Azure control in permissions.", "target_name": "build-sp", "target_object_id": "66666666-6666-6666-6666-666666666666", "target_type": "ServicePrincipal", @@ -46,6 +50,8 @@ { "confidence": "confirmed", "evidence_type": "graph-owner", + "next_review": "Review ownership around application 'build-app'; if it backs an Azure-facing identity, confirm that identity in permissions.", + "operator_signal": "Indirect control visible; ownership review next.", "related_ids": [ "77777777-7777-7777-7777-777777777777", "55555555-5555-5555-5555-555555555555" @@ -53,7 +59,7 @@ "source_name": "ci-admin@lab.local", "source_object_id": "77777777-7777-7777-7777-777777777777", "source_type": "User", - "summary": "Owner 'ci-admin@lab.local' can modify application 'build-app'.", + "summary": "Owner 'ci-admin@lab.local' can modify application 'build-app'. This is an indirect-control row: ownership is the visible trust path, not direct Azure privilege by itself. Review ownership around application 'build-app'; if it backs an Azure-facing identity, confirm that identity in permissions.", "target_name": "build-app", "target_object_id": "55555555-5555-5555-5555-555555555555", "target_type": "Application", @@ -62,6 +68,8 @@ { "confidence": "confirmed", "evidence_type": "graph-app-role-assignment", + "next_review": "Check permissions for Azure control on service principal 'reporting-sp'.", + "operator_signal": "Indirect control visible; privilege confirmation next.", "related_ids": [ "99999999-9999-9999-9999-999999999999", "app-role-graph-1", @@ -70,7 +78,7 @@ "source_name": "reporting-sp", "source_object_id": "99999999-9999-9999-9999-999999999999", "source_type": "ServicePrincipal", - "summary": "Service principal 'reporting-sp' holds an application permission or app-role assignment to 'Microsoft Graph'.", + "summary": "Service principal 'reporting-sp' holds an application permission or app-role assignment to 'Microsoft Graph'. This row is a trust-edge and application-permission cue; confirm whether the same identity also holds Azure control. Check permissions for Azure control on service principal 'reporting-sp'.", "target_name": "Microsoft Graph", "target_object_id": "00000003-0000-0000-c000-000000000000", "target_type": "ServicePrincipal", diff --git a/tests/test_collectors.py b/tests/test_collectors.py index e951c49..e4424bf 100644 --- a/tests/test_collectors.py +++ b/tests/test_collectors.py @@ -2517,6 +2517,68 @@ def test_collect_role_trusts(fixture_provider, options) -> None: assert output.trusts[0].trust_type == "federated-credential" assert output.trusts[1].trust_type == "service-principal-owner" assert output.trusts[2].trust_type == "app-owner" + assert ( + output.trusts[0].operator_signal + == "Trust expansion visible; privilege confirmation next." + ) + assert ( + output.trusts[0].next_review + == "Check permissions for Azure control on service principal 'build-sp'." + ) + assert ( + output.trusts[1].operator_signal + == "Indirect control visible; ownership review next." + ) + assert ( + output.trusts[1].next_review + == "Review ownership around service principal 'build-sp', then confirm Azure control in permissions." + ) + + +def test_collect_role_trusts_outside_tenant_follow_on_prefers_cross_tenant(options) -> None: + class StubProvider: + def role_trusts(self, mode: RoleTrustsMode) -> dict: + return { + "trusts": [ + { + "trust_type": "federated-credential", + "source_object_id": "app-1", + "source_name": "external-build-app", + "source_type": "Application", + "target_object_id": "sp-1", + "target_name": "external-build-sp", + "target_type": "ServicePrincipal", + "evidence_type": "graph-federated-credential", + "confidence": "confirmed", + "summary": ( + "Application 'external-build-app' trusts an outside-tenant " + "federated subject for service principal 'external-build-sp'." + ), + "related_ids": ["app-1", "sp-1"], + } + ], + "issues": [], + } + + def metadata_context(self) -> dict[str, str | None]: + return {"tenant_id": None, "subscription_id": None, "token_source": None} + + options = GlobalOptions( + tenant=None, + subscription=None, + output=OutputMode.JSON, + outdir=Path("/tmp"), + debug=False, + role_trusts_mode=RoleTrustsMode.FAST, + ) + + output = collect_role_trusts(StubProvider(), options) + + assert output.trusts[0].operator_signal == "Trust expansion visible; outside-tenant follow-on." + assert ( + output.trusts[0].next_review + == "Check cross-tenant for related outside-tenant control or delegated-management paths around service principal 'external-build-sp'." + ) def test_collect_role_trusts_enumerates_graph_edges_without_principal_seed(options) -> None: diff --git a/tests/test_command_output_examples.py b/tests/test_command_output_examples.py new file mode 100644 index 0000000..14a10f4 --- /dev/null +++ b/tests/test_command_output_examples.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +import json +from pathlib import Path + + +def test_command_output_examples_include_flat_and_chain_sources() -> None: + root = Path(__file__).resolve().parent.parent / "command-output" + + role_trusts_payload = json.loads( + (root / "role-trusts" / "output.json").read_text(encoding="utf-8") + ) + assert role_trusts_payload["metadata"]["command"] == "role-trusts" + assert role_trusts_payload["metadata"]["generated_at"] == "" + assert len(role_trusts_payload["trusts"]) == 3 + assert "operator_signal" in role_trusts_payload["trusts"][0] + assert (root / "role-trusts" / "table.txt").is_file() + + credential_path_payload = json.loads( + (root / "chains" / "credential-path" / "output.json").read_text(encoding="utf-8") + ) + assert credential_path_payload["metadata"]["command"] == "chains" + assert credential_path_payload["family"] == "credential-path" + assert len(credential_path_payload["paths"]) == 3 + assert (root / "chains" / "credential-path" / "table.txt").is_file() diff --git a/tests/test_help.py b/tests/test_help.py index acbd166..9d609cf 100644 --- a/tests/test_help.py +++ b/tests/test_help.py @@ -348,6 +348,8 @@ def test_help_command_role_trusts_topic() -> None: assert "delegated or admin consent grants" in result.stdout assert "Fast mode is the default" in result.stdout assert "per-application owner and federated credential lookups" in result.stdout + assert "operator_signal" in result.stdout + assert "next_review" in result.stdout def test_help_command_auth_policies_topic() -> None: diff --git a/tests/test_models.py b/tests/test_models.py index b9d08a0..569085a 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -245,6 +245,8 @@ def test_role_trust_summary_defaults() -> None: summary="test", ) assert trust.source_name is None + assert trust.operator_signal is None + assert trust.next_review is None assert trust.related_ids == [] diff --git a/tests/test_terminal_ux.py b/tests/test_terminal_ux.py index 277950b..7c69daa 100644 --- a/tests/test_terminal_ux.py +++ b/tests/test_terminal_ux.py @@ -24,13 +24,20 @@ def test_role_trusts_table_mode_includes_narration_and_takeaway(tmp_path: Path) assert result.exit_code == 0 assert ( - "Reviewing high-signal identity trust edges without implying delegated or admin consent." + "Reviewing high-signal identity trust edges and the clearest next review without implying" in result.stdout ) - assert "why it matters" in result.stdout + assert "operator signal" in result.stdout + assert "next review" in result.stdout + assert "Trust expansion visible; privilege" in result.stdout + assert "confirmation next." in result.stdout + assert "Check permissions for Azure control" in result.stdout + assert "service principal 'build-sp'." in result.stdout assert "Takeaway: 4 trust edges surfaced in fast mode" in result.stdout + assert "privilege-confirmation follow-ons" in result.stdout assert "Delegated and admin" in result.stdout - assert "out of scope for this command." in result.stdout + assert "out of scope for this" in result.stdout + assert "command." in result.stdout def test_auth_policies_table_mode_surfaces_findings_and_issues(tmp_path: Path) -> None: From adc69aeb850ab75e65915d8b58e3f5e80adad457 Mon Sep 17 00:00:00 2001 From: Colby Farley Date: Mon, 6 Apr 2026 21:58:40 -0500 Subject: [PATCH 2/2] test: wrap role-trusts assertions --- tests/test_collectors.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/test_collectors.py b/tests/test_collectors.py index e4424bf..3423b93 100644 --- a/tests/test_collectors.py +++ b/tests/test_collectors.py @@ -2531,7 +2531,10 @@ def test_collect_role_trusts(fixture_provider, options) -> None: ) assert ( output.trusts[1].next_review - == "Review ownership around service principal 'build-sp', then confirm Azure control in permissions." + == ( + "Review ownership around service principal 'build-sp', then confirm " + "Azure control in permissions." + ) ) @@ -2577,7 +2580,10 @@ def metadata_context(self) -> dict[str, str | None]: assert output.trusts[0].operator_signal == "Trust expansion visible; outside-tenant follow-on." assert ( output.trusts[0].next_review - == "Check cross-tenant for related outside-tenant control or delegated-management paths around service principal 'external-build-sp'." + == ( + "Check cross-tenant for related outside-tenant control or delegated-" + "management paths around service principal 'external-build-sp'." + ) )