From ef7e737a82208cb1c2059131126f2ea1736806ab Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Wed, 16 Sep 2026 20:34:20 -0400 Subject: [PATCH 1/3] fix: align Azure Event Hub filter and rule contracts --- filters/audits/azure.md | 40 ++++ filters/azure/azure-eventhub.yml | 194 +++++++++++---- .../testdata/filter-contracts/azure.json | 224 ++++++++++++++++++ 3 files changed, 409 insertions(+), 49 deletions(-) create mode 100644 filters/audits/azure.md create mode 100644 plugins/alerts/testdata/filter-contracts/azure.json diff --git a/filters/audits/azure.md b/filters/audits/azure.md new file mode 100644 index 000000000..73963fee8 --- /dev/null +++ b/filters/audits/azure.md @@ -0,0 +1,40 @@ +# Azure Event Hub normalization and rule review + +Fix SDK configuration and field meanings; normalize result precedence, IPs, severity and HTTP counters. + +This draft targets UTMStack `v11`. It contains 1 filter changes +and 0 rule changes for this technology only. Review covered +1 filter configurations and 46 matching shipped rule files. +Unchanged rules are listed in the regression manifest; they are not duplicated in the diff. + +## Contract and validation + +- Compared exact standard names/types with go-sdk v1.1.31 and the supplied UTMStack dictionaries. +- Checked documented pipeline ordering, rename/move behavior, open vendor log fields, + event-side versus alert-side fields, and surviving fields used by affected rule predicates/history/grouping. +- Strict SDK configuration decoding and actual CEL compilation pass for this scope. +- 11 synthetic normalization cases pass, including SDK Event conversion and any + trigger predicate assertions recorded in the manifest. +- The scoped alerts module tests and `git diff --check` pass with the shared contract runner applied. + +The shared alert-contract PR supplies the reusable Go runner for the manifest in +`plugins/alerts/testdata/filter-contracts/azure.json`. Apply that support before running `go test ./...` in `plugins/alerts`. + +The model starts from synthetic extraction results. It does not run complex grok, +JSON/KV/XML/CSV extraction, time conversion, dynamic plugins, historical OpenSearch +queries, or the closed EventProcessor. Raw vendor logs and resulting alerts must +still be checked in staging before rollout. No customer false-positive reduction +has been measured and no production rollout is included. + + + +## References + +- [SDK schema](https://github.com/threatwinds/go-sdk/blob/v1.1.31/plugins/plugins.proto) +- [Filter steps](https://github.com/threatwinds/go-sdk/wiki/Filter-Steps-Reference) +- [Standard event schema](https://github.com/threatwinds/go-sdk/wiki/Standard-Event-Schema) +- [Rule implementation](https://github.com/threatwinds/go-sdk/wiki/Implementing-Rules) + +`afterEvents`, empty noncapturing grok names, supported numeric strings, and custom +`log.*` fields are accepted. Existing textual protocol casing and vendor action names +are preserved unless a concrete consumer mismatch requires correction. diff --git a/filters/azure/azure-eventhub.yml b/filters/azure/azure-eventhub.yml index 460c25a22..777ef3e1c 100644 --- a/filters/azure/azure-eventhub.yml +++ b/filters/azure/azure-eventhub.yml @@ -7,7 +7,7 @@ # 4- https://learn.microsoft.com/en-us/azure/event-grid/system-topics # pipeline: - - dataTypes: azure + - dataTypes: [azure] steps: - json: source: raw @@ -17,13 +17,13 @@ pipeline: # .......................................................................# - rename: from: - - log.ResponseBodySize - to: origin.bytesSent + - log.ResponseBodySize + to: origin.bytesReceived - rename: from: - - log.ResponseHeaderSize - to: origin.bytesReceived + - log.ResponseHeaderSize + to: log.responseHeaderSize - rename: from: @@ -32,8 +32,8 @@ pipeline: - rename: from: - - log.AccountName - to: origin.host + - log.AccountName + to: log.accountName - rename: from: @@ -47,13 +47,13 @@ pipeline: - rename: from: - - log.StatusText - to: connectionStatus + - log.StatusText + to: log.statusText - rename: from: - - log.Location - to: origin.geolocation.country + - log.Location + to: log.location - rename: from: @@ -795,23 +795,31 @@ pipeline: - log.properties.upstreamSourcePort to: log.propertiesUpstreamSourcePort + # Common Azure resource-log result (kept under log for existing consumers). + - grok: + source: log.resultType + patterns: + - fieldName: log.vendorActionResult + pattern: '{{.greedy}}' + where: exists("log.resultType") + - rename: from: - - log.properties.result - to: actionResult - where: '!regexMatch("log.properties.result", "(?i)\b(?:denied|blocked|failed)\b")' + - log.properties.result + to: log.vendorActionResult + where: '!exists("log.vendorActionResult") && exists("log.properties.result")' - rename: from: - - log.Properties.Result - to: actionResult - where: '!regexMatch("log.properties.result", "(?i)\b(?:denied|blocked|failed)\b")' + - log.Properties.Result + to: log.vendorActionResult + where: '!exists("log.vendorActionResult") && exists("log.Properties.Result")' - rename: from: - - log.properties.resultType - to: actionResult - where: '!regexMatch("log.properties.result", "(?i)\b(?:denied|blocked|failed)\b")' + - log.properties.resultType + to: log.vendorActionResult + where: '!exists("log.vendorActionResult") && exists("log.properties.resultType")' - rename: from: @@ -984,10 +992,10 @@ pipeline: where: '!exists("actionResult") && equalsIgnoreCase("log.data.status", "Succeeded")' - add: - function: 'string' + function: string params: key: actionResult - value: 'failed' + value: failure where: '!exists("actionResult") && equalsIgnoreCase("log.data.status", "Failed")' - add: @@ -998,25 +1006,19 @@ pipeline: where: '!exists("actionResult") && endsWith("log.eventGridEventType", "Success")' - add: - function: 'string' + function: string params: key: actionResult - value: 'failed' + value: failure where: '!exists("actionResult") && endsWith("log.eventGridEventType", "Failure")' - - add: - function: 'string' - params: - key: actionResult - value: 'blocked' - where: '!exists("actionResult") && endsWith("log.eventGridEventType", "Cancel")' - add: - function: 'string' + function: string params: key: severity - value: 'medium' - where: '!exists("severity") && oneOf("actionResult", ["failed", "blocked"])' + value: warning + where: '!exists("severity") && oneOf("actionResult", ["failure", "denied"])' - rename: from: @@ -1046,46 +1048,46 @@ pipeline: # Adding action result - add: - function: 'string' + function: string params: key: actionResult - value: 'denied' - where: regexMatch("log.properties.result", "(?i)\bdenied\b") + value: denied + where: regexMatch("log.vendorActionResult", "(?i)\\bdenied\\b") - add: - function: 'string' + function: string params: key: actionResult - value: 'blocked' - where: regexMatch("log.properties.result", "(?i)\bblocked\b") + value: denied + where: regexMatch("log.vendorActionResult", "(?i)\\bblocked\\b") - add: - function: 'string' + function: string params: key: actionResult - value: 'failed' - where: regexMatch("log.properties.result", "(?i)\bfailed\b") + value: failure + where: regexMatch("log.vendorActionResult", "(?i)\\bfailed\\b") # .......................................................................# # Adding severity based on log.level # .......................................................................# - add: - function: 'string' + function: string params: key: severity - value: 'high' + value: error where: oneOf("log.level", ["ERROR", "Error", "FATAL", "CRITICAL", "Critical"]) - add: - function: 'string' + function: string params: key: severity - value: 'medium' + value: warning where: oneOf("log.level", ["WARN", "Warning"]) - add: - function: 'string' + function: string params: key: severity - value: 'low' + value: info where: oneOf("log.level", ["Information", "Informational", "INFO", "DEBUG", "TRACE"]) # .......................................................................# @@ -1163,4 +1165,98 @@ pipeline: fields: - origin.bytesSent - origin.bytesReceived - to: float \ No newline at end of file + to: float + + # Normalize the source event severity. + - add: + function: string + params: + key: severity + value: critical + where: oneOf("log.level",["FATAL","CRITICAL","Critical"]) + - add: + function: string + params: + key: severity + value: debug + where: oneOf("log.level",["DEBUG","TRACE"]) + + # Normalize explicit outcomes; an unknown outcome remains unset. + - add: + function: string + params: + key: actionResult + value: success + where: '!exists("actionResult") && regexMatch("log.vendorActionResult", "(?i)^(success|succeeded|successful|ok|done|accepted|accept|allowed|allow|permitted|permit|passed|pass|true)$")' + - add: + function: string + params: + key: actionResult + value: failure + where: '!equals("actionResult","denied") && regexMatch("log.vendorActionResult", "(?i)^(failure|failed|fail|error|invalid|timeout|false)$")' + - add: + function: string + params: + key: actionResult + value: denied + where: regexMatch("log.vendorActionResult", "(?i)^(denied|deny|blocked|block|dropped|drop|rejected|reject|forbidden|unauthorized|quarantined)$") + + # Keep addresses in IP fields and retain other source values under log. + - rename: + from: + - origin.ip + to: log.unparsedOriginIp + where: exists("origin.ip") && (!(inCIDR("origin.ip","0.0.0.0/0") || inCIDR("origin.ip","::/0")) || oneOf("origin.ip",["0.0.0.0","::"])) + - add: + function: string + params: + key: protocol + value: ICMP + where: equals("protocol",1) + - add: + function: string + params: + key: protocol + value: TCP + where: equals("protocol",6) + - add: + function: string + params: + key: protocol + value: UDP + where: equals("protocol",17) + - add: + function: string + params: + key: protocol + value: GRE + where: equals("protocol",47) + - add: + function: string + params: + key: protocol + value: ESP + where: equals("protocol",50) + - add: + function: string + params: + key: protocol + value: AH + where: equals("protocol",51) + - add: + function: string + params: + key: protocol + value: ICMPV6 + where: equals("protocol",58) + - add: + function: string + params: + key: protocol + value: SCTP + where: equals("protocol",132) + - rename: + from: + - protocol + to: log.ipProtocolNumber + where: exists("protocol") && greaterOrEqual("protocol",0) diff --git a/plugins/alerts/testdata/filter-contracts/azure.json b/plugins/alerts/testdata/filter-contracts/azure.json new file mode 100644 index 000000000..9e5fc31c4 --- /dev/null +++ b/plugins/alerts/testdata/filter-contracts/azure.json @@ -0,0 +1,224 @@ +{ + "technology": "Azure Event Hub", + "filters": [ + "filters/azure/azure-eventhub.yml" + ], + "rules": [ + "rules/cloud/azure/aks_security_threats.yml", + "rules/cloud/azure/app_registration_abuse.yml", + "rules/cloud/azure/application_gateway_waf_alerts.yml", + "rules/cloud/azure/automation_runbook_abuse.yml", + "rules/cloud/azure/azure_ad_impossible_travel.yml", + "rules/cloud/azure/azure_ad_password_spray.yml", + "rules/cloud/azure/azure_alert_suppression_rule.yml", + "rules/cloud/azure/azure_anomalous_token.yml", + "rules/cloud/azure/azure_app_credential_added.yml", + "rules/cloud/azure/azure_app_privileged_permissions.yml", + "rules/cloud/azure/azure_azurehound_discovery.yml", + "rules/cloud/azure/azure_bulk_role_changes.yml", + "rules/cloud/azure/azure_device_code_auth_abuse.yml", + "rules/cloud/azure/azure_disk_snapshot_exfiltration.yml", + "rules/cloud/azure/azure_federation_modified.yml", + "rules/cloud/azure/azure_impossible_travel.yml", + "rules/cloud/azure/azure_kubernetes_admission_controller.yml", + "rules/cloud/azure/azure_kubernetes_events_deleted.yml", + "rules/cloud/azure/azure_kubernetes_secret_access.yml", + "rules/cloud/azure/azure_laps_credential_dump.yml", + "rules/cloud/azure/azure_leaked_credentials.yml", + "rules/cloud/azure/azure_new_root_ca_added.yml", + "rules/cloud/azure/azure_password_spray_detected.yml", + "rules/cloud/azure/azure_prt_access_attempt.yml", + "rules/cloud/azure/azure_ropc_authentication.yml", + "rules/cloud/azure/azure_sentinel_alert_patterns.yml", + "rules/cloud/azure/azure_subscription_permission_elevation.yml", + "rules/cloud/azure/azure_temporary_access_pass.yml", + "rules/cloud/azure/credential_access_key_vault_modified.yml", + "rules/cloud/azure/defender_cloud_critical_alerts.yml", + "rules/cloud/azure/defense_evasion_azure_application_credential_modification.yml", + "rules/cloud/azure/defense_evasion_azure_diagnostic_settings_deletion.yml", + "rules/cloud/azure/defense_evasion_event_hub_deletion.yml", + "rules/cloud/azure/diagnostic_settings_tampering.yml", + "rules/cloud/azure/golden_saml_federation_abuse.yml", + "rules/cloud/azure/impact_azure_service_principal_credentials_added.yml", + "rules/cloud/azure/initial_access_azure_active_directory_high_risk_signin.yml", + "rules/cloud/azure/initial_access_consent_grant_attack_via_azure_registered_application.yml", + "rules/cloud/azure/key_vault_access_spikes.yml", + "rules/cloud/azure/managed_identity_abuse.yml", + "rules/cloud/azure/mfa_disabled_privileged_users.yml", + "rules/cloud/azure/persistence_azure_pim_user_added_global_admin.yml", + "rules/cloud/azure/persistence_mfa_disabled_for_azure_user.yml", + "rules/cloud/azure/pim_role_activation_abuse.yml", + "rules/cloud/azure/storage_account_public_access.yml", + "rules/cloud/azure/subscription_ownership_transfer.yml" + ], + "fixtures": [ + { + "name": "Azure actual result Denied", + "filter": "azure/azure-eventhub.yml", + "input": { + "log": { + "properties": { + "result": "Denied" + } + } + }, + "expected": { + "actionResult": "denied" + }, + "absent": [], + "rules": {} + }, + { + "name": "Azure actual result Failed", + "filter": "azure/azure-eventhub.yml", + "input": { + "log": { + "properties": { + "result": "Failed" + } + } + }, + "expected": { + "actionResult": "failure" + }, + "absent": [], + "rules": {} + }, + { + "name": "Azure actual result Succeeded", + "filter": "azure/azure-eventhub.yml", + "input": { + "log": { + "properties": { + "result": "Succeeded" + } + } + }, + "expected": { + "actionResult": "success" + }, + "absent": [], + "rules": {} + }, + { + "name": "Azure actual result Running", + "filter": "azure/azure-eventhub.yml", + "input": { + "log": { + "properties": { + "result": "Running" + } + } + }, + "expected": {}, + "absent": [ + "actionResult" + ], + "rules": {} + }, + { + "name": "Azure storage metadata is not host geography", + "filter": "azure/azure-eventhub.yml", + "input": { + "log": { + "AccountName": "storageaccount", + "Location": "eastus", + "StatusText": "Success", + "ResponseBodySize": 100, + "ResponseHeaderSize": 20 + } + }, + "expected": { + "origin.bytesReceived": 100, + "log.accountName": "storageaccount", + "log.location": "eastus" + }, + "absent": [ + "origin.host", + "origin.geolocation", + "connectionStatus", + "origin.bytesSent" + ], + "rules": {} + }, + { + "name": "Azure result Failed", + "filter": "azure/azure-eventhub.yml", + "input": { + "log": { + "resultType": "Failed" + } + }, + "expected": { + "actionResult": "failure" + }, + "absent": [] + }, + { + "name": "Azure result Succeeded", + "filter": "azure/azure-eventhub.yml", + "input": { + "log": { + "resultType": "Succeeded" + } + }, + "expected": { + "actionResult": "success" + }, + "absent": [] + }, + { + "name": "Azure result Denied", + "filter": "azure/azure-eventhub.yml", + "input": { + "log": { + "resultType": "Denied" + } + }, + "expected": { + "actionResult": "denied" + }, + "absent": [] + }, + { + "name": "Azure result Started", + "filter": "azure/azure-eventhub.yml", + "input": { + "log": { + "resultType": "Started" + } + }, + "expected": {}, + "absent": [ + "actionResult" + ] + }, + { + "name": "Azure descriptive denial", + "filter": "azure/azure-eventhub.yml", + "input": { + "log": { + "properties": { + "result": "Access denied" + } + } + }, + "expected": { + "actionResult": "denied" + } + }, + { + "name": "Azure explicit failure overrides success event suffix", + "filter": "azure/azure-eventhub.yml", + "input": { + "log": { + "resultType": "Failed", + "eventType": "Microsoft.ResourceWriteSuccess" + } + }, + "expected": { + "actionResult": "failure" + } + } + ] +} From 210a88ffb27efa1bc9274961fcc7a040ab79671b Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Thu, 17 Sep 2026 21:01:09 -0400 Subject: [PATCH 2/3] fix(azure): align parsed identities outcomes and correlation rules --- filters/audits/azure.md | 197 +- filters/azure/azure-eventhub.yml | 3755 +++++++++++------ plugins/alerts/azure_contract_test.go | 496 +++ plugins/alerts/azure_history_test.go | 290 ++ .../alerts/testdata/azure_changed_rules.json | 42 + plugins/alerts/testdata/azure_raw.json | 1992 +++++++++ .../testdata/filter-contracts/azure.json | 246 +- rules/cloud/azure/aks_security_threats.yml | 90 +- rules/cloud/azure/app_registration_abuse.yml | 86 +- .../azure/application_gateway_waf_alerts.yml | 73 +- .../cloud/azure/automation_runbook_abuse.yml | 53 +- .../azure/azure_ad_impossible_travel.yml | 47 +- rules/cloud/azure/azure_ad_password_spray.yml | 80 +- .../azure/azure_alert_suppression_rule.yml | 39 +- rules/cloud/azure/azure_anomalous_token.yml | 45 +- .../azure/azure_app_credential_added.yml | 50 +- .../azure_app_privileged_permissions.yml | 48 +- .../azure/azure_azurehound_discovery.yml | 44 +- rules/cloud/azure/azure_bulk_role_changes.yml | 78 +- .../azure/azure_device_code_auth_abuse.yml | 45 +- .../azure_disk_snapshot_exfiltration.yml | 47 +- .../cloud/azure/azure_federation_modified.yml | 49 +- rules/cloud/azure/azure_impossible_travel.yml | 45 +- .../azure_kubernetes_admission_controller.yml | 44 +- .../azure/azure_kubernetes_events_deleted.yml | 40 +- .../azure/azure_kubernetes_secret_access.yml | 83 +- .../azure/azure_laps_credential_dump.yml | 77 +- .../cloud/azure/azure_leaked_credentials.yml | 43 +- rules/cloud/azure/azure_new_root_ca_added.yml | 48 +- .../azure/azure_password_spray_detected.yml | 43 +- .../cloud/azure/azure_prt_access_attempt.yml | 47 +- .../cloud/azure/azure_ropc_authentication.yml | 74 +- .../azure/azure_sentinel_alert_patterns.yml | 45 +- ...zure_subscription_permission_elevation.yml | 39 +- .../azure/azure_temporary_access_pass.yml | 45 +- .../credential_access_key_vault_modified.yml | 50 +- .../azure/defender_cloud_critical_alerts.yml | 52 +- ...re_application_credential_modification.yml | 45 - ...ion_azure_diagnostic_settings_deletion.yml | 72 +- .../defense_evasion_event_hub_deletion.yml | 80 +- .../azure/diagnostic_settings_tampering.yml | 35 - .../azure/golden_saml_federation_abuse.yml | 37 - ...re_service_principal_credentials_added.yml | 54 - ...zure_active_directory_high_risk_signin.yml | 40 +- ...ttack_via_azure_registered_application.yml | 41 +- rules/cloud/azure/key_vault_access_spikes.yml | 82 +- rules/cloud/azure/managed_identity_abuse.yml | 43 - .../azure/mfa_disabled_privileged_users.yml | 32 - ...ence_azure_pim_user_added_global_admin.yml | 122 +- ...ersistence_mfa_disabled_for_azure_user.yml | 118 +- .../cloud/azure/pim_role_activation_abuse.yml | 89 +- .../azure/storage_account_public_access.yml | 51 +- .../azure/subscription_ownership_transfer.yml | 54 +- 53 files changed, 6987 insertions(+), 2675 deletions(-) create mode 100644 plugins/alerts/azure_contract_test.go create mode 100644 plugins/alerts/azure_history_test.go create mode 100644 plugins/alerts/testdata/azure_changed_rules.json create mode 100644 plugins/alerts/testdata/azure_raw.json delete mode 100644 rules/cloud/azure/defense_evasion_azure_application_credential_modification.yml delete mode 100644 rules/cloud/azure/diagnostic_settings_tampering.yml delete mode 100644 rules/cloud/azure/golden_saml_federation_abuse.yml delete mode 100644 rules/cloud/azure/impact_azure_service_principal_credentials_added.yml delete mode 100644 rules/cloud/azure/managed_identity_abuse.yml delete mode 100644 rules/cloud/azure/mfa_disabled_privileged_users.yml diff --git a/filters/audits/azure.md b/filters/audits/azure.md index 73963fee8..c68f62b26 100644 --- a/filters/audits/azure.md +++ b/filters/audits/azure.md @@ -1,40 +1,179 @@ -# Azure Event Hub normalization and rule review +# Azure Event Hub review — 17 September 2026 -Fix SDK configuration and field meanings; normalize result precedence, IPs, severity and HTTP counters. +Replacement for historical draft #2597. This draft changes the Azure filter and 40 +surviving rule predicates, consolidates five redundant definitions, and proposes retiring +one unsupported managed-identity heuristic. All 46 original rules were inspected; 40 +remain. Some families have only official-schema/synthetic proof, not native matches. -This draft targets UTMStack `v11`. It contains 1 filter changes -and 0 rule changes for this technology only. Review covered -1 filter configurations and 46 matching shipped rule files. -Unchanged rules are listed in the regression manifest; they are not duplicated in the diff. +## Evidence and deployment -## Contract and validation +The contract is ThreatWinds go-sdk **v1.1.31**, including the protobuf and actual CEL +and SearchRequest implementations, plus the official wiki at commit +`c18b54bd5ea5a34abb0e690458d73f89835edd29`. Local dictionaries are secondary. +The reviewed Azure collector already unwraps JSON arrays and `records` envelopes +before enqueueing each record. No collector change is needed for those wrappers. -- Compared exact standard names/types with go-sdk v1.1.31 and the supplied UTMStack dictionaries. -- Checked documented pipeline ordering, rename/move behavior, open vendor log fields, - event-side versus alert-side fields, and surviving fields used by affected rule predicates/history/grouping. -- Strict SDK configuration decoding and actual CEL compilation pass for this scope. -- 11 synthetic normalization cases pass, including SDK Event conversion and any - trigger predicate assertions recorded in the manifest. -- The scoped alerts module tests and `git diff --check` pass with the shared contract runner applied. +Read-only SSH sampling used port **2135**. Thirty v11 instances answered retained +Azure counts; one was unavailable. Azure data was retained on casbio.utmstack.com +and veritas.utmstack.com. The private raw/normalized review covers 21 records: -The shared alert-contract PR supplies the reusable Go runner for the manifest in -`plugins/alerts/testdata/filter-contracts/azure.json`. Apply that support before running `go test ./...` in `plugins/alerts`. +| Instance | Native class | Representative document IDs | +| --- | --- | --- | +| casbio.utmstack.com | Event Grid resource actions and write | `7f121466-deb9-4311-b444-2ee9ce414123`, `361a46c0-25e1-4418-b710-9579bc424ea0`, `457d5649-d56f-4b70-bd60-5f5d0f7ad32f`, `32511d9a-8211-4102-8dca-e9b67097b3e2` | +| veritas.utmstack.com | App Service console | `af081b1e-04d4-44e2-90ac-e91c026cc756`, `db0b49ac-cbee-4c8e-a8af-6551504eea8f`, `9d556100-2a81-464f-8f7c-79ead36edca5`, `4359695f-123c-49a6-9d76-8762412ada6b` | +| veritas.utmstack.com | App Service HTTP | `7f9d8696-d3e1-472d-9f6f-4889ae926507`, `c6b3ce1c-cc03-4aaf-acf7-90f55691e93f` | +| veritas.utmstack.com | Key Vault Authentication | `cdb26069-04d7-4ac7-8e54-6b24d87a5bae`, `3656b759-9533-447a-ac3c-0643aa1ed7c8` | +| veritas.utmstack.com | Entra audit | `7716268a-dddd-4494-ab94-b6ff779ec7cf`, `6b2784f1-c3df-482b-abdb-89841e441286` | +| veritas.utmstack.com | Entra sign-in | `036f1731-fa1b-40d1-8715-7210f5ef7a68`, `7e03c45e-e8ff-4f0f-bd40-deafc4f2785f` | +| veritas.utmstack.com | AKS audit | `b9f4fe58-21c0-4693-9c40-fc7fab300857`, `f38ea089-c82f-4b03-afc6-cd2947d82c85` | +| veritas.utmstack.com | Defender endpoint process/event export | `114226c3-7b92-40be-a8f8-06c701a57800`, `9ead9961-1769-4800-bd31-5986f4a16219` | +| veritas.utmstack.com | Event Grid role assignment | `a0d8710d-49ed-4891-a985-80d75111aa03` | -The model starts from synthetic extraction results. It does not run complex grok, -JSON/KV/XML/CSV extraction, time conversion, dynamic plugins, historical OpenSearch -queries, or the closed EventProcessor. Raw vendor logs and resulting alerts must -still be checked in staging before rollout. No customer false-positive reduction -has been measured and no production rollout is included. +**Separate deployment gap:** neither inspected active pipeline directory contains an +Azure filter. Each has 35 numeric YAML files; both manager and worker mount that +same directory. Case-insensitive inspection included both YAML suffixes. This is +not an access failure, and the repository filter alone cannot repair that current +configuration. It does not establish the configuration in July or why the definition +is absent. No customer settings or files were changed. +The four July Event Grid documents retain `log.data`; the September representatives +from veritas have no parsed `log` or standard endpoints/results. Consequently these +samples establish incoming formats and a current configuration gap, not execution +of the proposed filter. Raw text searches were used only to select strata: incidental +matches (for example “Administrative” in endpoint telemetry) are not category counts. +## Corrections -## References +- Preserve vendor objects and legacy aliases. Decode the observed JSON strings in + App Service `properties` and AKS `properties.log` without letting nested keys replace + the enclosing event. Temporary Draft namespaces are unconditionally deleted before + finalization; tests also decode the final Draft with strict protobuf validation. +- Promote Event Grid `eventTime`; Entra sign-in and audit actor identities; Event Grid + and documented Key Vault token principals; App Service client/server endpoints, + port and byte directions; and observed endpoint process context. Keep Azure directory, + subscription and resource identifiers separate from the SIEM tenant identity. +- Validate original IP values and numeric fields before promotion/enrichment. Unspecified + addresses, invalid ports/status values and nonfinite/negative bytes stay vendor detail. + An App Service worker's `EventIpAddress` is not assigned to an attacker. Gateway host + headers describe the target. Relative URIs remain vendor detail rather than invented URLs. +- Both sampled Key Vault Authentication records report `resultType: Success` with + `resultSignature: Unauthorized`. Explicit rejection/HTTP failure overrides a success + token. A private before/after replay of historical head `ef7e737a` confirms both + records modeled as `success`/401 before and `denied`/401 after (two native comparisons). + Numeric zero means success only in the sign-in schema. Intermediate Accepted, + Started and In Progress states, WAF Matched/Detected/Allowed, console messages and + security-alert issuance do not establish a completed attacker connection. +- The wiki expressly permits a role in `origin.group`; that Event Grid mapping is retained. + Authorization evidence describes the caller's permission. It is not the granted role: + the sampled role-assignment envelope lacks target-role/request-body details and is + correctly insufficient to establish subscription Owner assignment. +- Rules consume structured audit arrays, sign-in risk fields/arrays, class-specific results, + and parsed AKS verbs/resources/stages. Failed, intermediate and unrelated operations have + negative fixtures. PIM selectors bind the role property name and value to the same item. + No general `Update user` event is equated with MFA disablement, and risk/token labels no + longer imply impossible travel, credential theft, Golden SAML or completed exfiltration + without evidence. Existing numeric protocol aliases and HTTP action vocabulary are retained. -- [SDK schema](https://github.com/threatwinds/go-sdk/blob/v1.1.31/plugins/plugins.proto) -- [Filter steps](https://github.com/threatwinds/go-sdk/wiki/Filter-Steps-Reference) -- [Standard event schema](https://github.com/threatwinds/go-sdk/wiki/Standard-Event-Schema) -- [Rule implementation](https://github.com/threatwinds/go-sdk/wiki/Implementing-Rules) +## Correlation and duplicate alerts -`afterEvents`, empty noncapturing grok names, supported numeric strings, and custom -`log.*` fields are accepted. Existing textual protocol casing and vendor action names -are preserved unless a concrete consumer mismatch requires correction. +Ten corrected histories require collector, namespaced directory/resource scope and +an actor identity; WAF and invalid-password bursts require a real client IP. Vault +and AKS histories additionally stay within the actual vault/cluster resource. Candidate +markers are cleared from input and recomputed from the qualifying event condition. +Unrelated activity, denied vault access, missing identities, other scopes, expired +windows and below-threshold populations cannot satisfy these histories. + +The sign-in burst is **15 invalid-credential failures in 15 minutes**, not proof of +password spraying across distinct accounts. The Secret rule counts **writes/deletes**, +not secret reads. One sampled routine Secret deletion matches that corrected predicate; +no malicious classification, history threshold or live alert is inferred from it. +Routine controllers and administration need environment-specific tuning. + +Retire these duplicate rule definitions when reviewing the combined filter/rule rollout: + +| Retired file stem | Surviving file stem | +| --- | --- | +| diagnostic_settings_tampering | defense_evasion_azure_diagnostic_settings_deletion | +| defense_evasion_azure_application_credential_modification | azure_app_credential_added | +| impact_azure_service_principal_credentials_added | azure_app_credential_added | +| golden_saml_federation_abuse | azure_federation_modified | +| mfa_disabled_privileged_users | persistence_mfa_disabled_for_azure_user | + +The generic risky-sign-in and successful high-risk-sign-in predicates are disjoint; +application consent and application role-assignment predicates are separate. Grouping +and deduplication remain mutually exclusive. Indexed `lastEvent.*` resolution depends +on shared draft #2627. Removing a YAML file is not proof an existing database-loaded +rule has been retired: the team must include that retirement in its reviewed import. + +The separate `managed_identity_abuse` retirement is a coverage decision for review, +not a duplicate consolidation. Its control-plane `message` substring “token” and a +history of any events from the same IP do not establish token issuance or abuse. +A replacement requires a documented relevant producer and defensible signal; none +was demonstrated here. Existing database-loaded copies must be retired explicitly +if the team approves this proposal. + +The remaining families use explicit semantics: certificate/CA creation audit operation +names; successful TAP registration's `AuthenticationMethod` detail; the SecurityAlert +schema and its own ID; actual requested Owner role ID at subscription scope; and +explicit public-access settings in a present request body. Account permission does +not prove every container is public, and granting Owner does not transfer billing ownership. +Absent bodies, false settings, private containers, failed/intermediate requests, other +role IDs and non-subscription scopes have negative fixtures. The caller's authorization +role is never substituted for the requested role. + +## Validation and limits + +- **185 synthetic raw fixtures**, each replayed under both preserved and sanitized nested-key + models; explicit positive and negative assertions cover every changed surviving predicate. + All 40 remaining predicates are evaluated on every fixture. +- **21 private native records** have explicit field/result/match expectations. No raw customer + payloads, addresses or identities are committed. All expected mappings pass; the modeled + Secret deletion is the sole native predicate match. These are offline extraction models, + not execution of the closed EventProcessor. +- **449 passing source test records, zero failures/skips** with the shared contract support + overlaid. The history child process separately asserts ten rule scenarios. +- Actual SDK YAML, CEL, strict final Event conversion, Alert endpoint direction, required + placeholders and **10 SearchRequest histories** are exercised. Histories use an isolated + localhost mock and wall-clock-anchored timestamps, not customer OpenSearch. +- JSON key sanitization placement remains unverified in the closed engine. Both key layouts + are tested; native nested claims retain URI punctuation. External geolocation is not executed. +- No new live alert, customer false-positive reduction, pipeline performance or rollout is + claimed. Profile the additional JSON parsing and alias preservation in staging. +- Existing indexed records do not gain new fields/markers. Deploy filter and consumers together; + the longest corrected history needs **four hours** of new matching records. Review dashboards, + saved searches and custom rules for changed outcome semantics and corrected endpoint roles. + +Certificate/TAP, forwarded SecurityAlert, public-access changes and target Owner grants +were not observed as qualifying native events. Their proof uses official schemas and +explicit synthetic raw positives/negatives. A bounded September 17 count-only search +found no selected CA/TAP/Sentinel/managed-identity/public-access signatures; this is +not exhaustive absence. The single role-assignment hit lacks the target role. +Management messages containing “token” remain insufficient evidence of token abuse. + + +## Primary references + +- [SDK schema](https://github.com/threatwinds/go-sdk/blob/v1.1.31/plugins/plugins.proto), + [Draft lifecycle](https://github.com/threatwinds/go-sdk/wiki/Implementing-Filters), + [filter steps](https://github.com/threatwinds/go-sdk/wiki/Filter-Steps-Reference), + [event schema](https://github.com/threatwinds/go-sdk/wiki/Standard-Event-Schema). +- [Azure resource-log schema](https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema), + [Event Grid events](https://learn.microsoft.com/en-us/azure/event-grid/event-schema-subscriptions), + [Key Vault log example and meanings](https://learn.microsoft.com/en-us/azure/key-vault/general/logging). +- [Entra activity schema](https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-activity-log-schemas), + [audit activities](https://learn.microsoft.com/en-us/entra/identity/monitoring-health/reference-audit-activities), + [sign-in errors](https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes), + [risk detections](https://learn.microsoft.com/en-us/graph/api/resources/riskdetection?view=graph-rest-1.0), + [authentication protocol/token fields](https://learn.microsoft.com/en-us/graph/api/resources/signin?view=graph-rest-beta). +- [App Service HTTP fields](https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/appservicehttplogs), + [console fields](https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/appserviceconsolelogs), + [Gateway access/WAF schemas](https://learn.microsoft.com/en-us/azure/application-gateway/monitor-application-gateway-reference), + [AKS audit schema](https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/aksaudit), + [Defender process fields](https://learn.microsoft.com/en-us/defender-xdr/advanced-hunting-deviceprocessevents-table). + +- [TAP registration](https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass), + [certificate trust-store audit events](https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-certificate-based-authentication), + [SecurityAlert schema](https://learn.microsoft.com/en-us/azure/sentinel/security-alert-schema), + [role-assignment request](https://learn.microsoft.com/en-us/rest/api/authorization/role-assignments/create?view=rest-authorization-2022-04-01), + [built-in Owner role](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/privileged), + [storage account public-access setting](https://learn.microsoft.com/en-us/azure/storage/blobs/anonymous-read-access-configure). diff --git a/filters/azure/azure-eventhub.yml b/filters/azure/azure-eventhub.yml index 777ef3e1c..0c7c22a1f 100644 --- a/filters/azure/azure-eventhub.yml +++ b/filters/azure/azure-eventhub.yml @@ -1,1262 +1,2495 @@ -# Azure Event-Hub filter, version 2.0.7 -# -# Documentations -# 1- https://docs.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log -# 2- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/activity-log-schema -# 3- https://learn.microsoft.com/en-us/azure/event-grid/event-schema-subscriptions -# 4- https://learn.microsoft.com/en-us/azure/event-grid/system-topics -# +# Azure Event Hub filter v2.1.0 +# Vendor aliases and originals are retained; derived history markers are cleared before recomputation. +# JSON extraction into temporary log namespaces models the documented step contract. pipeline: - - dataTypes: [azure] - steps: - - json: - source: raw - - # .......................................................................# - # Renaming fields - # .......................................................................# - - rename: - from: - - log.ResponseBodySize - to: origin.bytesReceived - - - rename: - from: - - log.ResponseHeaderSize - to: log.responseHeaderSize - - - rename: - from: - - log.Uri - to: target.url - - - rename: - from: - - log.AccountName - to: log.accountName - - - rename: - from: - - log.StatusCode - to: statusCode - - - rename: - from: - - log.Protocol - to: protocol - - - rename: - from: - - log.StatusText - to: log.statusText - - - rename: - from: - - log.Location - to: log.location - - - rename: - from: - - log.ResponseCode - to: statusCode - - - rename: - from: - - log.AADClientId - to: log.aadClientId - - - rename: - from: - - log.AADObjectId - to: log.aadObjectId - - - rename: - from: - - log.AADTenantId - to: log.aadTenantId - - - rename: - from: - - log.CorrelationId - to: log.correlationId - - - rename: - from: - - log.IsBillableQuery - to: log.isBillableQuery - - - rename: - from: - - log.QueryText - to: log.queryText - - - rename: - from: - - log.RequestContext - to: log.requestContext - - - rename: - from: - - log.RequestTarget - to: log.requestTarget - - - rename: - from: - - log.ResponseDurationMs - to: log.responseDurationMs - - - rename: - from: - - log.ResponseRowCount - to: log.responseRowCount - - - rename: - from: - - log.StatsCPUTimeMs - to: log.statsCPUTimeMs - - - rename: - from: - - log.StatsRegionCount - to: log.statsRegionCount - - - rename: - from: - - log.StatsWorkspaceCount - to: log.statsWorkspaceCount - - - rename: - from: - - log.TenantId - to: log.tenantId - - - rename: - from: - - log.TimeGenerated - to: log.timeGenerated - - - rename: - from: - - log.Type - to: log.type - - - rename: - from: - - log.WorkspaceId - to: log.workspaceId - - - rename: - from: - - log.ResourceId - to: log.resourceId - - - rename: - from: - - log.callerIpAddress - to: origin.ip - - - rename: - from: - - log.properties.C_DeviceId - to: log.propertiesCDeviceId - - - rename: - from: - - log.properties.C_Iat - to: log.propertiesCIat - - - rename: - from: - - log.properties.C_Idtyp - to: log.propertiesCIdtyp - - - rename: - from: - - log.properties.C_Sid - to: log.propertiesCSid - - - rename: - from: - - log.properties.UserPrincipalObjectID - to: log.propertiesUserPrincipalObjectID - - - rename: - from: - - log.properties.__UDI_RequiredFields_EventTime - to: log.propertiesUDIRequiredFieldsEventTime - - - rename: - from: - - log.properties.__UDI_RequiredFields_RegionScope - to: log.propertiesUDIRequiredFieldsRegionScope - - - rename: - from: - - log.properties.__UDI_RequiredFields_TenantId - to: log.propertiesUDIRequiredFieldsTenantId - - - rename: - from: - - log.properties.__UDI_RequiredFields_UniqueId - to: log.propertiesUDIRequiredFieldsUniqueId - - - rename: - from: - - log.properties.apiVersion - to: log.propertiesApiVersion - - - rename: - from: - - log.properties.appId - to: log.propertiesAppId - - - rename: - from: - - log.properties.atContentH - to: log.propertiesAtContentH - - - rename: - from: - - log.properties.atContentP - to: log.propertiesAtContentP - - - rename: - from: - - log.properties.clientAuthMethod - to: log.propertiesClientAuthMethod - - - rename: - from: - - log.properties.clientRequestId - to: log.propertiesClientRequestId - - - rename: - from: - - log.properties.durationMs - to: log.propertiesDurationMs - - - rename: - from: - - log.properties.identityProvider - to: log.propertiesIdentityProvider - - - rename: - from: - - log.properties.ipAddress - to: log.propertiesIpAddress - - - rename: - from: - - log.properties.location - to: log.propertiesLocation - - - rename: - from: - - log.properties.operationId - to: log.propertiesOperationId - - - rename: - from: - - log.properties.requestId - to: log.propertiesRequestId - - - rename: - from: - - log.properties.requestMethod - to: log.propertiesRequestMethod - - - rename: - from: - - log.properties.requestUri - to: log.propertiesRequestUri - - - rename: - from: - - log.properties.responseSizeBytes - to: origin.bytesReceived - - - rename: - from: - - log.properties.responseStatusCode - to: statusCode - - - rename: - from: - - log.properties.roles - to: log.propertiesRoles - - - rename: - from: - - log.properties.scopes - to: log.propertiesScopes - - - rename: - from: - - log.properties.signInActivityId - to: log.propertiesSignInActivityId - - - rename: - from: - - log.properties.tenantId - to: log.propertiesTenantId - - - rename: - from: - - log.properties.timeGenerated - to: log.propertiesTimeGenerated - - - rename: - from: - - log.properties.tokenIssuedAt - to: log.propertiesTokenIssuedAt - - - rename: - from: - - log.properties.userAgent - to: log.propertiesUserAgent - - - rename: - from: - - log.properties.userId - to: log.propertiesUserId - - - rename: - from: - - log.properties.wids - to: log.propertiesWids - - - rename: - from: - - log.time - to: deviceTime - - - rename: - from: - - log.properties.agent.agentType - to: log.propertiesAgentAgentType - - - rename: - from: - - log.properties.agent.parentAppId - to: log.propertiesAgentParentAppId - - - rename: - from: - - log.properties.appDisplayName - to: log.propertiesAppDisplayName - - - rename: - from: - - log.properties.appOwnerTenantId - to: log.propertiesAppOwnerTenantId - - - rename: - from: - - log.properties.appServicePrincipalId - to: log.propertiesAppServicePrincipalId - - - rename: - from: - - log.properties.authenticationProtocol - to: log.propertiesAuthenticationProtocol - - - rename: - from: - - log.properties.authenticationRequirement - to: log.propertiesAuthenticationRequirement - - - rename: - from: - - log.properties.autonomousSystemNumber - to: log.propertiesAutonomousSystemNumber - - - rename: - from: - - log.properties.clientAppUsed - to: log.propertiesClientAppUsed - - - rename: - from: - - log.properties.clientCredentialType - to: log.propertiesClientCredentialType - - - rename: - from: - - log.properties.conditionalAccessStatus - to: log.propertiesConditionalAccessStatus - - - rename: - from: - - log.properties.correlationId - to: log.propertiesCorrelationId - - - rename: - from: - - log.properties.createdDateTime - to: log.propertiesCreatedDateTime - - - rename: - from: - - log.properties.crossTenantAccessType - to: log.propertiesCrossTenantAccessType - - - rename: - from: - - log.properties.deviceDetail.deviceId - to: log.propertiesDeviceDetailDeviceId - - - rename: - from: - - log.properties.deviceDetail.operatingSystem - to: log.propertiesDeviceDetailOperatingSystem - - - rename: - from: - - log.properties.deviceDetail.browser - to: log.propertiesDeviceDetailBrowser - - - rename: - from: - - log.properties.deviceDetail.displayName - to: log.propertiesDeviceDetailDisplayName - - - rename: - from: - - log.properties.deviceDetail.trustType - to: log.propertiesDeviceDetailTrustType - - - rename: - from: - - log.properties.flaggedForReview - to: log.propertiesFlaggedForReview - - - rename: - from: - - log.properties.homeTenantId - to: log.propertiesHomeTenantId - - - rename: - from: - - log.properties.id - to: log.propertiesId - - - rename: - from: - - log.properties.incomingTokenType - to: log.propertiesIncomingTokenType - - - rename: - from: - - log.properties.isInteractive - to: log.propertiesIsInteractive - - - rename: - from: - - log.properties.isTenantRestricted - to: log.propertiesIsTenantRestricted - - - rename: - from: - - log.properties.isThroughGlobalSecureAccess - to: log.propertiesIsThroughGlobalSecureAccess - - - rename: - from: - - log.properties.originalRequestId - to: log.propertiesOriginalRequestId - - - rename: - from: - - log.properties.originalTransferMethod - to: log.propertiesOriginalTransferMethod - - - rename: - from: - - log.properties.processingTimeInMilliseconds - to: log.propertiesProcessingTimeInMilliseconds - - - rename: - from: - - log.properties.resourceDisplayName - to: log.propertiesResourceDisplayName - - - rename: - from: - - log.properties.resourceId - to: log.propertiesResourceId - - - rename: - from: - - log.properties.processingTimeInMilliseconds - to: log.propertiesProcessingTimeInMilliseconds - - - rename: - from: - - log.properties.resourceOwnerTenantId - to: log.propertiesResourceOwnerTenantId - - - rename: - from: - - log.properties.resourceServicePrincipalId - to: log.propertiesResourceServicePrincipalId - - - rename: - from: - - log.properties.resourceTenantId - to: log.propertiesResourceTenantId - - - rename: - from: - - log.properties.riskDetail - to: log.propertiesRiskDetail - - - rename: - from: - - log.properties.riskLevelAggregated - to: log.propertiesRiskLevelAggregated - - - rename: - from: - - log.properties.processingTimeInMilliseconds - to: log.propertiesRiskLevelDuringSignIn - - - rename: - from: - - log.properties.riskState - to: log.propertiesRiskState - - - rename: - from: - - log.properties.rngcStatus - to: log.propertiesRngcStatus - - - rename: - from: - - log.properties.servicePrincipalId - to: log.propertiesServicePrincipalId - - - rename: - from: - - log.properties.sessionId - to: log.propertiesSessionId - - - rename: - from: - - log.properties.signInTokenProtectionStatus - to: log.propertiesSignInTokenProtectionStatus - - - rename: - from: - - log.properties.ssoExtensionVersion - to: log.propertiesSsoExtensionVersion - - - rename: - from: - - log.properties.status.additionalDetails - to: log.propertiesStatusAdditionalDetails - - - rename: - from: - - log.properties.status.errorCode - to: log.propertiesStatusErrorCode - - - rename: - from: - - log.properties.riskState - to: log.propertiesRiskState - - - rename: - from: - - log.properties.tokenIssuerName - to: log.propertiesTokenIssuerName - - - rename: - from: - - log.properties.tokenIssuerType - to: log.propertiesTokenIssuerType - - - rename: - from: - - log.properties.tokenProtectionStatusDetails.signInSessionStatus - to: log.propertiesTokenProtectionStatusDetailsSignInSessionStatus - - - rename: - from: - - log.properties.tokenProtectionStatusDetails.signInSessionStatusCode - to: log.propertiesTokenProtectionStatusDetailsSignInSessionStatusCode - - - rename: - from: - - log.properties.uniqueTokenIdentifier - to: log.propertiesUniqueTokenIdentifier - - - rename: - from: - - log.properties.userDisplayName - to: log.propertiesUserDisplayName - - - rename: - from: - - log.properties.userPrincipalName - to: log.propertiesUserPrincipalName - - - rename: - from: - - log.properties.userType - to: log.propertiesUserType - - - rename: - from: - - log.properties.WAFEvaluationTime - to: log.propertiesWAFEvaluationTime - - - rename: - from: - - log.properties.WAFMode - to: log.propertiesWAFMode - - - rename: - from: - - log.properties.WAFPolicyID - to: log.propertiesWAFPolicyID - - - rename: - from: - - log.properties.backendSslCipher - to: log.propertiesBackendSslCipher - - - rename: - from: - - log.properties.backendSslProtocol - to: log.propertiesBackendSslProtocol - - - rename: - from: - - log.properties.clientIP - to: origin.ip - - - rename: - from: - - log.properties.clientPort - to: origin.port - - - rename: - from: - - log.properties.clientResponseTime - to: log.propertiesClientResponseTime - - - rename: - from: - - log.properties.connectionSerialNumber - to: log.propertiesConnectionSerialNumber - - - rename: - from: - - log.properties.contentType - to: log.propertiesContentType - - - rename: - from: - - log.properties.error_info - to: log.propertiesErrorInfo - - - rename: - from: - - log.properties.host - to: origin.host - - - rename: - from: - - log.properties.httpMethod - to: log.propertiesHttpMethod - - - rename: - from: - - log.properties.httpStatus - to: statusCode - - - rename: - from: - - log.properties.httpStatusCode - to: statusCode - - - rename: - from: - - log.properties.httpVersion - to: log.propertiesHttpVersion - - - rename: - from: - - log.properties.instanceId - to: log.propertiesInstanceId - - - rename: - from: - - log.properties.noOfConnectionRequests - to: log.propertiesNoOfConnectionRequests - - - rename: - from: - - log.properties.originalHost - to: log.propertiesOriginalHost - - - rename: - from: - - log.properties.originalRequestUriWithArgs - to: log.propertiesOriginalRequestUriWithArgs - - - rename: - from: - - log.properties.receivedBytes - to: log.propertiesReceivedBytes - - - rename: - from: - - log.properties.requestQuery - to: log.propertiesRequestQuery - - - rename: - from: - - log.properties.requestUri - to: log.propertiesRequestUri - - - rename: - from: - - log.properties.sentBytes - to: log.propertiesSentBytes - - - rename: - from: - - log.properties.serverConnectTime - to: log.propertiesServerConnectTime - - - rename: - from: - - log.properties.serverHeaderTime - to: log.propertiesServerHeaderTime - - - rename: - from: - - log.properties.serverResponseLatency - to: log.propertiesServerResponseLatency - - - rename: - from: - - log.properties.serverRouted - to: log.propertiesServerRouted - - - rename: - from: - - log.properties.serverStatus - to: log.propertiesServerStatus - - - rename: - from: - - log.properties.sslCipher - to: log.propertiesSslCipher - - - rename: - from: - - log.properties.sslClientCertificateFingerprint - to: log.propertiesSslClientCertificateFingerprint - - - rename: - from: - - log.properties.sslClientCertificateIssuerName - to: log.propertiesSslClientCertificateIssuerName - - - rename: - from: - - log.properties.sslClientVerify - to: log.propertiesSslClientVerify - - - rename: - from: - - log.properties.sslEnabled - to: log.propertiesSslEnabled - - - rename: - from: - - log.properties.sslProtocol - to: log.propertiesSslProtocol - - - rename: - from: - - log.properties.timeTaken - to: log.propertiesTimeTaken - - - rename: - from: - - log.properties.transactionId - to: log.propertiesTransactionId - - - rename: - from: - - log.properties.upstreamSourcePort - to: log.propertiesUpstreamSourcePort - - # Common Azure resource-log result (kept under log for existing consumers). - - grok: - source: log.resultType - patterns: - - fieldName: log.vendorActionResult - pattern: '{{.greedy}}' - where: exists("log.resultType") - - - rename: - from: - - log.properties.result - to: log.vendorActionResult - where: '!exists("log.vendorActionResult") && exists("log.properties.result")' - - - rename: - from: - - log.Properties.Result - to: log.vendorActionResult - where: '!exists("log.vendorActionResult") && exists("log.Properties.Result")' - - - rename: - from: - - log.properties.resultType - to: log.vendorActionResult - where: '!exists("log.vendorActionResult") && exists("log.properties.resultType")' - - - rename: - from: - - log.properties.userAgent - to: log.propertiesUserAgent - - # .......................................................................# - # ===== EVENT GRID SCHEMA ===== # - # # - # Handles Azure Event Grid System Topic payloads (schema: # - # https://learn.microsoft.com/en-us/azure/event-grid/event-schema-subscriptions). - # .......................................................................# - - - rename: - from: - - log.data.httpRequest.clientIpAddress - - log.data.claims.ipaddr - to: origin.ip - where: '!exists("origin.ip") && (exists("log.data.httpRequest.clientIpAddress") || exists("log.data.claims.ipaddr"))' - - - rename: - from: - - log.data.httpRequest.url - to: target.url - where: '!exists("target.url") && exists("log.data.httpRequest.url")' - - - rename: - from: - - log.data.httpRequest.method - to: log.propertiesRequestMethod - where: '!exists("log.propertiesRequestMethod") && exists("log.data.httpRequest.method")' - - - rename: - from: - - log.data.claims.upn - to: origin.user - where: '!exists("origin.user") && exists("log.data.claims.upn")' - - - rename: - from: - - log.data.claims.name - to: origin.user - where: '!exists("origin.user") && exists("log.data.claims.name")' - - - rename: - from: - - log.data.authorization.evidence.role - to: origin.group - where: '!exists("origin.group") && exists("log.data.authorization.evidence.role")' - - - rename: - from: - - log.data.correlationId - to: log.correlationId - where: '!exists("log.correlationId") && exists("log.data.correlationId")' - - - rename: - from: - - log.data.operationName - to: log.operationName - where: '!exists("log.operationName") && exists("log.data.operationName")' - - - rename: - from: - - log.data.authorization.action - to: log.authorizationAction - where: exists("log.data.authorization.action") - - - rename: - from: - - log.data.authorization.scope - to: log.authorizationScope - where: exists("log.data.authorization.scope") - - - rename: - from: - - log.data.authorization.evidence.principalId - to: log.principalId - where: exists("log.data.authorization.evidence.principalId") - - - rename: - from: - - log.data.authorization.evidence.principalType - to: log.principalType - where: exists("log.data.authorization.evidence.principalType") - - - rename: - from: - - log.data.authorization.evidence.roleAssignmentId - to: log.roleAssignmentId - where: exists("log.data.authorization.evidence.roleAssignmentId") - - - rename: - from: - - log.data.authorization.evidence.roleAssignmentScope - to: log.roleAssignmentScope - where: exists("log.data.authorization.evidence.roleAssignmentScope") - - - rename: - from: - - log.data.authorization.evidence.roleDefinitionId - to: log.roleDefinitionId - where: exists("log.data.authorization.evidence.roleDefinitionId") - - - rename: - from: - - log.data.resourceProvider - to: log.resourceProvider - where: exists("log.data.resourceProvider") - - - rename: - from: - - log.data.resourceUri - to: log.resourceUri - where: exists("log.data.resourceUri") - - - rename: - from: - - log.data.subscriptionId - to: log.subscriptionId - where: exists("log.data.subscriptionId") - - - rename: - from: - - log.data.tenantId - to: log.aadTenantId - where: '!exists("log.aadTenantId") && exists("log.data.tenantId")' - - - rename: - from: - - log.data.httpRequest.clientRequestId - to: log.clientRequestId - where: exists("log.data.httpRequest.clientRequestId") - - - rename: - from: - - log.eventType - to: log.eventGridEventType - where: exists("log.eventType") - - - rename: - from: - - log.subject - to: log.eventGridSubject - where: exists("log.subject") - - - rename: - from: - - log.topic - to: log.eventGridTopic - where: exists("log.topic") - - - rename: - from: - - log.dataVersion - to: log.eventGridDataVersion - where: exists("log.dataVersion") - - - rename: - from: - - log.metadataVersion - to: log.eventGridMetadataVersion - where: exists("log.metadataVersion") - - - add: - function: 'string' - params: - key: actionResult - value: 'success' - where: '!exists("actionResult") && equalsIgnoreCase("log.data.status", "Succeeded")' - - - add: - function: string - params: - key: actionResult - value: failure - where: '!exists("actionResult") && equalsIgnoreCase("log.data.status", "Failed")' - - - add: - function: 'string' - params: - key: actionResult - value: 'success' - where: '!exists("actionResult") && endsWith("log.eventGridEventType", "Success")' - - - add: - function: string - params: - key: actionResult - value: failure - where: '!exists("actionResult") && endsWith("log.eventGridEventType", "Failure")' - - - - add: - function: string - params: - key: severity - value: warning - where: '!exists("severity") && oneOf("actionResult", ["failure", "denied"])' - - - rename: - from: - - log.data.status - to: log.eventGridStatus - where: exists("log.data.status") - - - delete: - fields: - - log.data.claims.aio - - log.data.claims.acrs - - log.data.claims.appidacr - - log.data.claims.aud - - log.data.claims.exp - - log.data.claims.iat - - log.data.claims.iss - - log.data.claims.nbf - - log.data.claims.puid - - log.data.claims.rh - - log.data.claims.uti - - log.data.claims.ver - - log.data.claims.xms_act_fct - - log.data.claims.xms_ftd - - log.data.claims.xms_idrel - - log.data.claims.xms_sub_fct - - log.data.claims.xms_tcdt - - # Adding action result - - add: - function: string - params: - key: actionResult - value: denied - where: regexMatch("log.vendorActionResult", "(?i)\\bdenied\\b") - - - add: - function: string - params: - key: actionResult - value: denied - where: regexMatch("log.vendorActionResult", "(?i)\\bblocked\\b") - - - add: - function: string - params: - key: actionResult - value: failure - where: regexMatch("log.vendorActionResult", "(?i)\\bfailed\\b") - - # .......................................................................# - # Adding severity based on log.level - # .......................................................................# - - add: - function: string - params: - key: severity - value: error - where: oneOf("log.level", ["ERROR", "Error", "FATAL", "CRITICAL", "Critical"]) - - add: - function: string - params: - key: severity - value: warning - where: oneOf("log.level", ["WARN", "Warning"]) - - add: - function: string - params: - key: severity - value: info - where: oneOf("log.level", ["Information", "Informational", "INFO", "DEBUG", "TRACE"]) - - # .......................................................................# - # Add geolocation to remote.ip - # .......................................................................# - - dynamic: - plugin: com.utmstack.geolocation - params: - source: origin.ip - destination: origin.geolocation - where: exists("origin.ip") - - # .......................................................................# - # Normalizing request method and renaming to action - # .......................................................................# - - add: - function: 'string' - params: - key: action - value: 'get' - where: equals("log.propertiesRequestMethod", "GET") - - add: - function: 'string' - params: - key: action - value: 'post' - where: equals("log.propertiesRequestMethod", "POST") - - add: - function: 'string' - params: - key: action - value: 'put' - where: equals("log.propertiesRequestMethod", "PUT") - - add: - function: 'string' - params: - key: action - value: 'delete' - where: equals("log.propertiesRequestMethod", "DELETE") - - add: - function: 'string' - params: - key: action - value: 'patch' - where: equals("log.propertiesRequestMethod", "PATCH") - - add: - function: 'string' - params: - key: action - value: 'request' - where: equals("log.propertiesRequestMethod", "REQUEST") - - # .......................................................................# - # Removing log.propertiesRequestMethod if action was set - # .......................................................................# - - delete: - fields: - - log.propertiesRequestMethod - where: exists("action") - - # .......................................................................# - # Fields conversions - # .......................................................................# - - cast: - fields: - - origin.port - to: int - - - cast: - fields: - - statusCode - to: float - - - cast: - fields: - - origin.bytesSent - - origin.bytesReceived - to: float - - # Normalize the source event severity. - - add: - function: string - params: - key: severity - value: critical - where: oneOf("log.level",["FATAL","CRITICAL","Critical"]) - - add: - function: string - params: - key: severity - value: debug - where: oneOf("log.level",["DEBUG","TRACE"]) - - # Normalize explicit outcomes; an unknown outcome remains unset. - - add: - function: string - params: - key: actionResult - value: success - where: '!exists("actionResult") && regexMatch("log.vendorActionResult", "(?i)^(success|succeeded|successful|ok|done|accepted|accept|allowed|allow|permitted|permit|passed|pass|true)$")' - - add: - function: string - params: - key: actionResult - value: failure - where: '!equals("actionResult","denied") && regexMatch("log.vendorActionResult", "(?i)^(failure|failed|fail|error|invalid|timeout|false)$")' - - add: - function: string - params: - key: actionResult - value: denied - where: regexMatch("log.vendorActionResult", "(?i)^(denied|deny|blocked|block|dropped|drop|rejected|reject|forbidden|unauthorized|quarantined)$") - - # Keep addresses in IP fields and retain other source values under log. - - rename: - from: - - origin.ip - to: log.unparsedOriginIp - where: exists("origin.ip") && (!(inCIDR("origin.ip","0.0.0.0/0") || inCIDR("origin.ip","::/0")) || oneOf("origin.ip",["0.0.0.0","::"])) - - add: - function: string - params: - key: protocol - value: ICMP - where: equals("protocol",1) - - add: - function: string - params: - key: protocol - value: TCP - where: equals("protocol",6) - - add: - function: string - params: - key: protocol - value: UDP - where: equals("protocol",17) - - add: - function: string - params: - key: protocol - value: GRE - where: equals("protocol",47) - - add: - function: string - params: - key: protocol - value: ESP - where: equals("protocol",50) - - add: - function: string - params: - key: protocol - value: AH - where: equals("protocol",51) - - add: - function: string - params: - key: protocol - value: ICMPV6 - where: equals("protocol",58) - - add: - function: string - params: - key: protocol - value: SCTP - where: equals("protocol",132) - - rename: - from: - - protocol - to: log.ipProtocolNumber - where: exists("protocol") && greaterOrEqual("protocol",0) +- dataTypes: + - azure + steps: + - json: + source: raw + - rename: + from: + - log.ResponseHeaderSize + to: log.responseHeaderSize + - rename: + from: + - log.AccountName + to: log.accountName + - rename: + from: + - log.StatusText + to: log.statusText + - rename: + from: + - log.Location + to: log.location + - rename: + from: + - log.AADClientId + to: log.aadClientId + - rename: + from: + - log.AADObjectId + to: log.aadObjectId + - rename: + from: + - log.AADTenantId + to: log.aadTenantId + - rename: + from: + - log.CorrelationId + to: log.correlationId + - rename: + from: + - log.IsBillableQuery + to: log.isBillableQuery + - rename: + from: + - log.QueryText + to: log.queryText + - rename: + from: + - log.RequestContext + to: log.requestContext + - rename: + from: + - log.RequestTarget + to: log.requestTarget + - rename: + from: + - log.ResponseDurationMs + to: log.responseDurationMs + - rename: + from: + - log.ResponseRowCount + to: log.responseRowCount + - rename: + from: + - log.StatsCPUTimeMs + to: log.statsCPUTimeMs + - rename: + from: + - log.StatsRegionCount + to: log.statsRegionCount + - rename: + from: + - log.StatsWorkspaceCount + to: log.statsWorkspaceCount + - rename: + from: + - log.TenantId + to: log.tenantId + - rename: + from: + - log.TimeGenerated + to: log.timeGenerated + - rename: + from: + - log.Type + to: log.type + - rename: + from: + - log.WorkspaceId + to: log.workspaceId + - rename: + from: + - log.ResourceId + to: log.resourceId + - rename: + from: + - log.properties.C_DeviceId + - log.properties.CDeviceId + to: log.propertiesCDeviceId + - rename: + from: + - log.properties.C_Iat + - log.properties.CIat + to: log.propertiesCIat + - rename: + from: + - log.properties.C_Idtyp + - log.properties.CIdtyp + to: log.propertiesCIdtyp + - rename: + from: + - log.properties.C_Sid + - log.properties.CSid + to: log.propertiesCSid + - rename: + from: + - log.properties.UserPrincipalObjectID + to: log.propertiesUserPrincipalObjectID + - rename: + from: + - log.properties.__UDI_RequiredFields_EventTime + - log.properties.UDIRequiredFieldsEventTime + to: log.propertiesUDIRequiredFieldsEventTime + - rename: + from: + - log.properties.__UDI_RequiredFields_RegionScope + - log.properties.UDIRequiredFieldsRegionScope + to: log.propertiesUDIRequiredFieldsRegionScope + - rename: + from: + - log.properties.__UDI_RequiredFields_TenantId + - log.properties.UDIRequiredFieldsTenantId + to: log.propertiesUDIRequiredFieldsTenantId + - rename: + from: + - log.properties.__UDI_RequiredFields_UniqueId + - log.properties.UDIRequiredFieldsUniqueId + to: log.propertiesUDIRequiredFieldsUniqueId + - rename: + from: + - log.properties.apiVersion + to: log.propertiesApiVersion + - rename: + from: + - log.properties.appId + to: log.propertiesAppId + - rename: + from: + - log.properties.atContentH + to: log.propertiesAtContentH + - rename: + from: + - log.properties.atContentP + to: log.propertiesAtContentP + - rename: + from: + - log.properties.clientAuthMethod + to: log.propertiesClientAuthMethod + - rename: + from: + - log.properties.clientRequestId + to: log.propertiesClientRequestId + - rename: + from: + - log.properties.durationMs + to: log.propertiesDurationMs + - rename: + from: + - log.properties.identityProvider + to: log.propertiesIdentityProvider + - rename: + from: + - log.properties.ipAddress + to: log.propertiesIpAddress + - rename: + from: + - log.properties.location + to: log.propertiesLocation + - rename: + from: + - log.properties.operationId + to: log.propertiesOperationId + - rename: + from: + - log.properties.requestId + to: log.propertiesRequestId + - rename: + from: + - log.properties.requestMethod + to: log.propertiesRequestMethod + - rename: + from: + - log.properties.requestUri + to: log.propertiesRequestUri + - rename: + from: + - log.properties.roles + to: log.propertiesRoles + - rename: + from: + - log.properties.scopes + to: log.propertiesScopes + - rename: + from: + - log.properties.signInActivityId + to: log.propertiesSignInActivityId + - rename: + from: + - log.properties.tenantId + to: log.propertiesTenantId + - rename: + from: + - log.properties.timeGenerated + to: log.propertiesTimeGenerated + - rename: + from: + - log.properties.tokenIssuedAt + to: log.propertiesTokenIssuedAt + - rename: + from: + - log.properties.userAgent + to: log.propertiesUserAgent + - rename: + from: + - log.properties.userId + to: log.propertiesUserId + - rename: + from: + - log.properties.wids + to: log.propertiesWids + - rename: + from: + - log.properties.agent.agentType + to: log.propertiesAgentAgentType + - rename: + from: + - log.properties.agent.parentAppId + to: log.propertiesAgentParentAppId + - rename: + from: + - log.properties.appDisplayName + to: log.propertiesAppDisplayName + - rename: + from: + - log.properties.appOwnerTenantId + to: log.propertiesAppOwnerTenantId + - rename: + from: + - log.properties.appServicePrincipalId + to: log.propertiesAppServicePrincipalId + - rename: + from: + - log.properties.authenticationProtocol + to: log.propertiesAuthenticationProtocol + - rename: + from: + - log.properties.authenticationRequirement + to: log.propertiesAuthenticationRequirement + - rename: + from: + - log.properties.autonomousSystemNumber + to: log.propertiesAutonomousSystemNumber + - rename: + from: + - log.properties.clientAppUsed + to: log.propertiesClientAppUsed + - rename: + from: + - log.properties.clientCredentialType + to: log.propertiesClientCredentialType + - rename: + from: + - log.properties.conditionalAccessStatus + to: log.propertiesConditionalAccessStatus + - rename: + from: + - log.properties.correlationId + to: log.propertiesCorrelationId + - rename: + from: + - log.properties.createdDateTime + to: log.propertiesCreatedDateTime + - rename: + from: + - log.properties.crossTenantAccessType + to: log.propertiesCrossTenantAccessType + - rename: + from: + - log.properties.deviceDetail.deviceId + to: log.propertiesDeviceDetailDeviceId + - rename: + from: + - log.properties.deviceDetail.operatingSystem + to: log.propertiesDeviceDetailOperatingSystem + - rename: + from: + - log.properties.deviceDetail.browser + to: log.propertiesDeviceDetailBrowser + - rename: + from: + - log.properties.deviceDetail.displayName + to: log.propertiesDeviceDetailDisplayName + - rename: + from: + - log.properties.deviceDetail.trustType + to: log.propertiesDeviceDetailTrustType + - rename: + from: + - log.properties.flaggedForReview + to: log.propertiesFlaggedForReview + - rename: + from: + - log.properties.homeTenantId + to: log.propertiesHomeTenantId + - rename: + from: + - log.properties.id + to: log.propertiesId + - rename: + from: + - log.properties.incomingTokenType + to: log.propertiesIncomingTokenType + - rename: + from: + - log.properties.isInteractive + to: log.propertiesIsInteractive + - rename: + from: + - log.properties.isTenantRestricted + to: log.propertiesIsTenantRestricted + - rename: + from: + - log.properties.isThroughGlobalSecureAccess + to: log.propertiesIsThroughGlobalSecureAccess + - rename: + from: + - log.properties.originalRequestId + to: log.propertiesOriginalRequestId + - rename: + from: + - log.properties.originalTransferMethod + to: log.propertiesOriginalTransferMethod + - rename: + from: + - log.properties.processingTimeInMilliseconds + to: log.propertiesProcessingTimeInMilliseconds + - rename: + from: + - log.properties.resourceDisplayName + to: log.propertiesResourceDisplayName + - rename: + from: + - log.properties.resourceId + to: log.propertiesResourceId + - rename: + from: + - log.properties.processingTimeInMilliseconds + to: log.propertiesProcessingTimeInMilliseconds + - rename: + from: + - log.properties.resourceOwnerTenantId + to: log.propertiesResourceOwnerTenantId + - rename: + from: + - log.properties.resourceServicePrincipalId + to: log.propertiesResourceServicePrincipalId + - rename: + from: + - log.properties.resourceTenantId + to: log.propertiesResourceTenantId + - rename: + from: + - log.properties.riskDetail + to: log.propertiesRiskDetail + - rename: + from: + - log.properties.riskLevelAggregated + to: log.propertiesRiskLevelAggregated + - rename: + from: + - log.properties.processingTimeInMilliseconds + to: log.propertiesRiskLevelDuringSignIn + - rename: + from: + - log.properties.riskState + to: log.propertiesRiskState + - rename: + from: + - log.properties.rngcStatus + to: log.propertiesRngcStatus + - rename: + from: + - log.properties.servicePrincipalId + to: log.propertiesServicePrincipalId + - rename: + from: + - log.properties.sessionId + to: log.propertiesSessionId + - rename: + from: + - log.properties.signInTokenProtectionStatus + to: log.propertiesSignInTokenProtectionStatus + - rename: + from: + - log.properties.ssoExtensionVersion + to: log.propertiesSsoExtensionVersion + - rename: + from: + - log.properties.status.additionalDetails + to: log.propertiesStatusAdditionalDetails + - rename: + from: + - log.properties.status.errorCode + to: log.propertiesStatusErrorCode + - rename: + from: + - log.properties.riskState + to: log.propertiesRiskState + - rename: + from: + - log.properties.tokenIssuerName + to: log.propertiesTokenIssuerName + - rename: + from: + - log.properties.tokenIssuerType + to: log.propertiesTokenIssuerType + - rename: + from: + - log.properties.tokenProtectionStatusDetails.signInSessionStatus + to: log.propertiesTokenProtectionStatusDetailsSignInSessionStatus + - rename: + from: + - log.properties.tokenProtectionStatusDetails.signInSessionStatusCode + to: log.propertiesTokenProtectionStatusDetailsSignInSessionStatusCode + - rename: + from: + - log.properties.uniqueTokenIdentifier + to: log.propertiesUniqueTokenIdentifier + - rename: + from: + - log.properties.userDisplayName + to: log.propertiesUserDisplayName + - rename: + from: + - log.properties.userPrincipalName + to: log.propertiesUserPrincipalName + - rename: + from: + - log.properties.userType + to: log.propertiesUserType + - rename: + from: + - log.properties.WAFEvaluationTime + to: log.propertiesWAFEvaluationTime + - rename: + from: + - log.properties.WAFMode + to: log.propertiesWAFMode + - rename: + from: + - log.properties.WAFPolicyID + to: log.propertiesWAFPolicyID + - rename: + from: + - log.properties.backendSslCipher + to: log.propertiesBackendSslCipher + - rename: + from: + - log.properties.backendSslProtocol + to: log.propertiesBackendSslProtocol + - rename: + from: + - log.properties.clientResponseTime + to: log.propertiesClientResponseTime + - rename: + from: + - log.properties.connectionSerialNumber + to: log.propertiesConnectionSerialNumber + - rename: + from: + - log.properties.contentType + to: log.propertiesContentType + - rename: + from: + - log.properties.error_info + - log.properties.errorinfo + to: log.propertiesErrorInfo + - rename: + from: + - log.properties.httpMethod + to: log.propertiesHttpMethod + - rename: + from: + - log.properties.httpVersion + to: log.propertiesHttpVersion + - rename: + from: + - log.properties.instanceId + to: log.propertiesInstanceId + - rename: + from: + - log.properties.noOfConnectionRequests + to: log.propertiesNoOfConnectionRequests + - rename: + from: + - log.properties.originalHost + to: log.propertiesOriginalHost + - rename: + from: + - log.properties.originalRequestUriWithArgs + to: log.propertiesOriginalRequestUriWithArgs + - rename: + from: + - log.properties.receivedBytes + to: log.propertiesReceivedBytes + - rename: + from: + - log.properties.requestQuery + to: log.propertiesRequestQuery + - rename: + from: + - log.properties.requestUri + to: log.propertiesRequestUri + - rename: + from: + - log.properties.sentBytes + to: log.propertiesSentBytes + - rename: + from: + - log.properties.serverConnectTime + to: log.propertiesServerConnectTime + - rename: + from: + - log.properties.serverHeaderTime + to: log.propertiesServerHeaderTime + - rename: + from: + - log.properties.serverResponseLatency + to: log.propertiesServerResponseLatency + - rename: + from: + - log.properties.serverRouted + to: log.propertiesServerRouted + - rename: + from: + - log.properties.serverStatus + to: log.propertiesServerStatus + - rename: + from: + - log.properties.sslCipher + to: log.propertiesSslCipher + - rename: + from: + - log.properties.sslClientCertificateFingerprint + to: log.propertiesSslClientCertificateFingerprint + - rename: + from: + - log.properties.sslClientCertificateIssuerName + to: log.propertiesSslClientCertificateIssuerName + - rename: + from: + - log.properties.sslClientVerify + to: log.propertiesSslClientVerify + - rename: + from: + - log.properties.sslEnabled + to: log.propertiesSslEnabled + - rename: + from: + - log.properties.sslProtocol + to: log.propertiesSslProtocol + - rename: + from: + - log.properties.timeTaken + to: log.propertiesTimeTaken + - rename: + from: + - log.properties.transactionId + to: log.propertiesTransactionId + - rename: + from: + - log.properties.upstreamSourcePort + to: log.propertiesUpstreamSourcePort + - rename: + from: + - log.properties.result + to: log.vendorActionResult + where: '!exists("log.vendorActionResult") && exists("log.properties.result")' + - rename: + from: + - log.Properties.Result + to: log.vendorActionResult + where: '!exists("log.vendorActionResult") && exists("log.Properties.Result")' + - rename: + from: + - log.properties.resultType + to: log.vendorActionResult + where: '!exists("log.vendorActionResult") && exists("log.properties.resultType")' + - rename: + from: + - log.properties.userAgent + to: log.propertiesUserAgent + - rename: + from: + - log.data.httpRequest.method + to: log.propertiesRequestMethod + where: '!exists("log.propertiesRequestMethod") && exists("log.data.httpRequest.method")' + - rename: + from: + - log.data.correlationId + to: log.correlationId + where: '!exists("log.correlationId") && exists("log.data.correlationId")' + - rename: + from: + - log.data.operationName + to: log.operationName + where: '!exists("log.operationName") && exists("log.data.operationName")' + - rename: + from: + - log.data.authorization.action + to: log.authorizationAction + where: exists("log.data.authorization.action") + - rename: + from: + - log.data.authorization.scope + to: log.authorizationScope + where: exists("log.data.authorization.scope") + - rename: + from: + - log.data.authorization.evidence.principalId + to: log.principalId + where: exists("log.data.authorization.evidence.principalId") + - rename: + from: + - log.data.authorization.evidence.principalType + to: log.principalType + where: exists("log.data.authorization.evidence.principalType") + - rename: + from: + - log.data.authorization.evidence.roleAssignmentId + to: log.roleAssignmentId + where: exists("log.data.authorization.evidence.roleAssignmentId") + - rename: + from: + - log.data.authorization.evidence.roleAssignmentScope + to: log.roleAssignmentScope + where: exists("log.data.authorization.evidence.roleAssignmentScope") + - rename: + from: + - log.data.authorization.evidence.roleDefinitionId + to: log.roleDefinitionId + where: exists("log.data.authorization.evidence.roleDefinitionId") + - rename: + from: + - log.data.resourceProvider + to: log.resourceProvider + where: exists("log.data.resourceProvider") + - rename: + from: + - log.data.resourceUri + to: log.resourceUri + where: exists("log.data.resourceUri") + - rename: + from: + - log.data.subscriptionId + to: log.subscriptionId + where: exists("log.data.subscriptionId") + - rename: + from: + - log.data.tenantId + to: log.aadTenantId + where: '!exists("log.aadTenantId") && exists("log.data.tenantId")' + - rename: + from: + - log.data.httpRequest.clientRequestId + to: log.clientRequestId + where: exists("log.data.httpRequest.clientRequestId") + - rename: + from: + - log.eventType + to: log.eventGridEventType + where: exists("log.eventType") + - rename: + from: + - log.subject + to: log.eventGridSubject + where: exists("log.subject") + - rename: + from: + - log.topic + to: log.eventGridTopic + where: exists("log.topic") + - rename: + from: + - log.dataVersion + to: log.eventGridDataVersion + where: exists("log.dataVersion") + - rename: + from: + - log.metadataVersion + to: log.eventGridMetadataVersion + where: exists("log.metadataVersion") + - rename: + from: + - log.data.status + to: log.eventGridStatus + where: exists("log.data.status") + - rename: + from: + - origin.ip + to: log.unparsedOriginIp + where: exists("origin.ip") && (!(inCIDR("origin.ip","0.0.0.0/0") || inCIDR("origin.ip","::/0")) || oneOf("origin.ip",["0.0.0.0","::"])) + - rename: + from: + - protocol + to: log.ipProtocolNumber + where: exists("protocol") && greaterOrEqual("protocol",0) + - json: + source: raw + - delete: + fields: &id001 + - log.azureKind + - log.azureProperties + - log.azureKubernetes + - log.azureRequestBody + - log.azureScope + - log.azureScopeType + - log.azureActor + - log.azureActorType + - log.correlationCandidate + - rename: + from: + - log.properties + to: log.azureProperties + where: equalsIgnoreCase("log.category","AppServiceHTTPLogs") && exists("log.properties.CIp") + - add: + function: string + params: + key: azureDecodePending + value: 'yes' + where: (equalsIgnoreCase("log.category","AppServiceHTTPLogs")) && regexMatch("log.properties","^\\s*\\{") + - rename: + from: + - log + to: azureEnvelope + where: equals("azureDecodePending","yes") + - json: + source: azureEnvelope.properties + where: equals("azureDecodePending","yes") + - rename: + from: + - log + to: azureDecoded + where: equals("azureDecodePending","yes") + - rename: + from: + - azureEnvelope + to: log + where: equals("azureDecodePending","yes") + - rename: + from: + - azureDecoded + to: log.azureProperties + where: equals("azureDecodePending","yes") + - delete: + fields: + - azureDecodePending + - azureDecoded + - azureEnvelope + - add: + function: string + params: + key: azureDecodePending + value: 'yes' + where: (oneOf("log.category",["kube-audit","kube-audit-admin"])) && regexMatch("log.properties.log","^\\s*\\{") + - rename: + from: + - log + to: azureEnvelope + where: equals("azureDecodePending","yes") + - json: + source: azureEnvelope.properties.log + where: equals("azureDecodePending","yes") + - rename: + from: + - log + to: azureDecoded + where: equals("azureDecodePending","yes") + - rename: + from: + - azureEnvelope + to: log + where: equals("azureDecodePending","yes") + - rename: + from: + - azureDecoded + to: log.azureKubernetes + where: equals("azureDecodePending","yes") + - delete: + fields: + - azureDecodePending + - azureDecoded + - azureEnvelope + - rename: + from: + - log.properties.requestbody + to: log.azureRequestBody + where: (regexMatch("log.operationName","(?i)^Microsoft\\.(Storage|Authorization)/") || regexMatch("log.operationName.value","(?i)^Microsoft\\.(Storage|Authorization)/")) + && exists("log.properties.requestbody.properties") && !exists("log.azureRequestBody") + - add: + function: string + params: + key: azureDecodePending + value: 'yes' + where: ((regexMatch("log.operationName","(?i)^Microsoft\\.(Storage|Authorization)/") || regexMatch("log.operationName.value","(?i)^Microsoft\\.(Storage|Authorization)/")) + && !exists("log.azureRequestBody")) && regexMatch("log.properties.requestbody","^\\s*\\{") + - rename: + from: + - log + to: azureEnvelope + where: equals("azureDecodePending","yes") + - json: + source: azureEnvelope.properties.requestbody + where: equals("azureDecodePending","yes") + - rename: + from: + - log + to: azureDecoded + where: equals("azureDecodePending","yes") + - rename: + from: + - azureEnvelope + to: log + where: equals("azureDecodePending","yes") + - rename: + from: + - azureDecoded + to: log.azureRequestBody + where: equals("azureDecodePending","yes") + - delete: + fields: + - azureDecodePending + - azureDecoded + - azureEnvelope + - rename: + from: + - log.properties.requestBody + to: log.azureRequestBody + where: (regexMatch("log.operationName","(?i)^Microsoft\\.(Storage|Authorization)/") || regexMatch("log.operationName.value","(?i)^Microsoft\\.(Storage|Authorization)/")) + && exists("log.properties.requestBody.properties") && !exists("log.azureRequestBody") + - add: + function: string + params: + key: azureDecodePending + value: 'yes' + where: ((regexMatch("log.operationName","(?i)^Microsoft\\.(Storage|Authorization)/") || regexMatch("log.operationName.value","(?i)^Microsoft\\.(Storage|Authorization)/")) + && !exists("log.azureRequestBody")) && regexMatch("log.properties.requestBody","^\\s*\\{") + - rename: + from: + - log + to: azureEnvelope + where: equals("azureDecodePending","yes") + - json: + source: azureEnvelope.properties.requestBody + where: equals("azureDecodePending","yes") + - rename: + from: + - log + to: azureDecoded + where: equals("azureDecodePending","yes") + - rename: + from: + - azureEnvelope + to: log + where: equals("azureDecodePending","yes") + - rename: + from: + - azureDecoded + to: log.azureRequestBody + where: equals("azureDecodePending","yes") + - delete: + fields: + - azureDecodePending + - azureDecoded + - azureEnvelope + - rename: + from: + - log.properties.eventProperties.requestbody + to: log.azureRequestBody + where: (regexMatch("log.operationName","(?i)^Microsoft\\.(Storage|Authorization)/") || regexMatch("log.operationName.value","(?i)^Microsoft\\.(Storage|Authorization)/")) + && exists("log.properties.eventProperties.requestbody.properties") && !exists("log.azureRequestBody") + - add: + function: string + params: + key: azureDecodePending + value: 'yes' + where: ((regexMatch("log.operationName","(?i)^Microsoft\\.(Storage|Authorization)/") || regexMatch("log.operationName.value","(?i)^Microsoft\\.(Storage|Authorization)/")) + && !exists("log.azureRequestBody")) && regexMatch("log.properties.eventProperties.requestbody","^\\s*\\{") + - rename: + from: + - log + to: azureEnvelope + where: equals("azureDecodePending","yes") + - json: + source: azureEnvelope.properties.eventProperties.requestbody + where: equals("azureDecodePending","yes") + - rename: + from: + - log + to: azureDecoded + where: equals("azureDecodePending","yes") + - rename: + from: + - azureEnvelope + to: log + where: equals("azureDecodePending","yes") + - rename: + from: + - azureDecoded + to: log.azureRequestBody + where: equals("azureDecodePending","yes") + - delete: + fields: + - azureDecodePending + - azureDecoded + - azureEnvelope + - rename: + from: + - log.properties.eventProperties.requestBody + to: log.azureRequestBody + where: (regexMatch("log.operationName","(?i)^Microsoft\\.(Storage|Authorization)/") || regexMatch("log.operationName.value","(?i)^Microsoft\\.(Storage|Authorization)/")) + && exists("log.properties.eventProperties.requestBody.properties") && !exists("log.azureRequestBody") + - add: + function: string + params: + key: azureDecodePending + value: 'yes' + where: ((regexMatch("log.operationName","(?i)^Microsoft\\.(Storage|Authorization)/") || regexMatch("log.operationName.value","(?i)^Microsoft\\.(Storage|Authorization)/")) + && !exists("log.azureRequestBody")) && regexMatch("log.properties.eventProperties.requestBody","^\\s*\\{") + - rename: + from: + - log + to: azureEnvelope + where: equals("azureDecodePending","yes") + - json: + source: azureEnvelope.properties.eventProperties.requestBody + where: equals("azureDecodePending","yes") + - rename: + from: + - log + to: azureDecoded + where: equals("azureDecodePending","yes") + - rename: + from: + - azureEnvelope + to: log + where: equals("azureDecodePending","yes") + - rename: + from: + - azureDecoded + to: log.azureRequestBody + where: equals("azureDecodePending","yes") + - delete: + fields: + - azureDecodePending + - azureDecoded + - azureEnvelope + - add: + function: string + params: + key: log.azureKind + value: signin + where: oneOf("log.category",["SignInLogs","SigninLogs","NonInteractiveUserSignInLogs","ServicePrincipalSignInLogs","ManagedIdentitySignInLogs"]) + - add: + function: string + params: + key: log.azureKind + value: audit + where: equalsIgnoreCase("log.category","AuditLogs") + - add: + function: string + params: + key: log.azureKind + value: risk + where: oneOf("log.category",["UserRiskEvents","ServicePrincipalRiskEvents"]) + - add: + function: string + params: + key: log.azureKind + value: eventgrid + where: regexMatch("log.eventType","^Microsoft\\.Resources\\.Resource(Write|Delete|Action)(Success|Failure|Cancel)$") || regexMatch("log.type","^Microsoft\\.Resources\\.Resource(Write|Delete|Action)(Success|Failure|Cancel)$") + - add: + function: string + params: + key: log.azureKind + value: activity + where: equalsIgnoreCase("log.category","Administrative") || equalsIgnoreCase("log.category.value","Administrative") || (oneOf("log.category",["Write","Delete","Action"]) + && exists("log.identity.authorization.action") && regexMatch("log.operationName","(?i)^Microsoft\\.")) + - add: + function: string + params: + key: log.azureKind + value: keyvault + where: equalsIgnoreCase("log.category","AuditEvent") && regexMatch("log.resourceId","(?i)/providers/Microsoft\\.KeyVault/vaults/") + - add: + function: string + params: + key: log.azureKind + value: waf + where: equalsIgnoreCase("log.category","ApplicationGatewayFirewallLog") + - add: + function: string + params: + key: log.azureKind + value: gateway + where: equalsIgnoreCase("log.category","ApplicationGatewayAccessLog") + - add: + function: string + params: + key: log.azureKind + value: http + where: equalsIgnoreCase("log.category","AppServiceHTTPLogs") + - add: + function: string + params: + key: log.azureKind + value: console + where: equalsIgnoreCase("log.category","AppServiceConsoleLogs") + - add: + function: string + params: + key: log.azureKind + value: kubernetes + where: oneOf("log.category",["kube-audit","kube-audit-admin"]) && equals("log.azureKubernetes.apiVersion","audit.k8s.io/v1") + && equals("log.azureKubernetes.kind","Event") + - add: + function: string + params: + key: log.azureKind + value: endpoint-process + where: equalsIgnoreCase("log.category","AdvancedHunting-DeviceProcessEvents") + - add: + function: string + params: + key: log.azureKind + value: endpoint-event + where: equalsIgnoreCase("log.category","AdvancedHunting-DeviceEvents") + - add: + function: string + params: + key: log.azureKind + value: securityalert + where: equalsIgnoreCase("log.Type","SecurityAlert") && regexMatch("log.SystemAlertId","^.+$") + - grok: + source: log.eventTime + patterns: + - fieldName: deviceTime + pattern: (?s)^.+$ + where: regexMatch("log.eventTime","(?s)^.+$") && !oneOf("log.eventTime",["-","unknown"]) && (regexMatch("log.eventTime","^[0-9]{4}-[0-9]{2}-[0-9]{2}T")) + && !exists("deviceTime") + - grok: + source: log.time + patterns: + - fieldName: deviceTime + pattern: (?s)^.+$ + where: regexMatch("log.time","(?s)^.+$") && !oneOf("log.time",["-","unknown"]) && (regexMatch("log.time","^[0-9]{4}-[0-9]{2}-[0-9]{2}T")) + && !exists("deviceTime") + - grok: + source: log.properties.activityDateTime + patterns: + - fieldName: deviceTime + pattern: (?s)^.+$ + where: regexMatch("log.properties.activityDateTime","(?s)^.+$") && !oneOf("log.properties.activityDateTime",["-","unknown"]) + && (regexMatch("log.properties.activityDateTime","^[0-9]{4}-[0-9]{2}-[0-9]{2}T")) && !exists("deviceTime") + - grok: + source: log.properties.createdDateTime + patterns: + - fieldName: deviceTime + pattern: (?s)^.+$ + where: regexMatch("log.properties.createdDateTime","(?s)^.+$") && !oneOf("log.properties.createdDateTime",["-","unknown"]) + && (regexMatch("log.properties.createdDateTime","^[0-9]{4}-[0-9]{2}-[0-9]{2}T")) && !exists("deviceTime") + - grok: + source: log.TimeGenerated + patterns: + - fieldName: deviceTime + pattern: (?s)^.+$ + where: regexMatch("log.TimeGenerated","(?s)^.+$") && !oneOf("log.TimeGenerated",["-","unknown"]) && (regexMatch("log.TimeGenerated","^[0-9]{4}-[0-9]{2}-[0-9]{2}T")) + && !exists("deviceTime") + - grok: + source: log.eventTimestamp + patterns: + - fieldName: deviceTime + pattern: (?s)^.+$ + where: regexMatch("log.eventTimestamp","(?s)^.+$") && !oneOf("log.eventTimestamp",["-","unknown"]) && (regexMatch("log.eventTimestamp","^[0-9]{4}-[0-9]{2}-[0-9]{2}T")) + && !exists("deviceTime") + - grok: + source: log.properties.ipAddress + patterns: + - fieldName: origin.ip + pattern: (?s)^.+$ + where: regexMatch("log.properties.ipAddress","(?s)^.+$") && !oneOf("log.properties.ipAddress",["-","unknown"]) && ((equalsIgnoreCase("log.azureKind","signin") + || equalsIgnoreCase("log.azureKind","risk")) && (inCIDR("log.properties.ipAddress","0.0.0.0/0") || inCIDR("log.properties.ipAddress","::/0")) + && !inCIDR("log.properties.ipAddress","0.0.0.0/32") && !inCIDR("log.properties.ipAddress","::/128")) && !exists("origin.ip") + - grok: + source: log.properties.initiatedBy.user.ipAddress + patterns: + - fieldName: origin.ip + pattern: (?s)^.+$ + where: regexMatch("log.properties.initiatedBy.user.ipAddress","(?s)^.+$") && !oneOf("log.properties.initiatedBy.user.ipAddress",["-","unknown"]) + && ((equalsIgnoreCase("log.azureKind","audit")) && (inCIDR("log.properties.initiatedBy.user.ipAddress","0.0.0.0/0") || + inCIDR("log.properties.initiatedBy.user.ipAddress","::/0")) && !inCIDR("log.properties.initiatedBy.user.ipAddress","0.0.0.0/32") + && !inCIDR("log.properties.initiatedBy.user.ipAddress","::/128")) && !exists("origin.ip") + - grok: + source: log.data.httpRequest.clientIpAddress + patterns: + - fieldName: origin.ip + pattern: (?s)^.+$ + where: regexMatch("log.data.httpRequest.clientIpAddress","(?s)^.+$") && !oneOf("log.data.httpRequest.clientIpAddress",["-","unknown"]) + && ((equalsIgnoreCase("log.azureKind","eventgrid")) && (inCIDR("log.data.httpRequest.clientIpAddress","0.0.0.0/0") || inCIDR("log.data.httpRequest.clientIpAddress","::/0")) + && !inCIDR("log.data.httpRequest.clientIpAddress","0.0.0.0/32") && !inCIDR("log.data.httpRequest.clientIpAddress","::/128")) + && !exists("origin.ip") + - grok: + source: log.data.claims.ipaddr + patterns: + - fieldName: origin.ip + pattern: (?s)^.+$ + where: regexMatch("log.data.claims.ipaddr","(?s)^.+$") && !oneOf("log.data.claims.ipaddr",["-","unknown"]) && ((equalsIgnoreCase("log.azureKind","eventgrid")) + && (inCIDR("log.data.claims.ipaddr","0.0.0.0/0") || inCIDR("log.data.claims.ipaddr","::/0")) && !inCIDR("log.data.claims.ipaddr","0.0.0.0/32") + && !inCIDR("log.data.claims.ipaddr","::/128")) && !exists("origin.ip") + - grok: + source: log.azureProperties.CIp + patterns: + - fieldName: origin.ip + pattern: (?s)^.+$ + where: regexMatch("log.azureProperties.CIp","(?s)^.+$") && !oneOf("log.azureProperties.CIp",["-","unknown"]) && ((equalsIgnoreCase("log.azureKind","http")) + && (inCIDR("log.azureProperties.CIp","0.0.0.0/0") || inCIDR("log.azureProperties.CIp","::/0")) && !inCIDR("log.azureProperties.CIp","0.0.0.0/32") + && !inCIDR("log.azureProperties.CIp","::/128")) && !exists("origin.ip") + - grok: + source: log.properties.clientIp + patterns: + - fieldName: origin.ip + pattern: (?s)^.+$ + where: regexMatch("log.properties.clientIp","(?s)^.+$") && !oneOf("log.properties.clientIp",["-","unknown"]) && ((equalsIgnoreCase("log.azureKind","waf")) + && (inCIDR("log.properties.clientIp","0.0.0.0/0") || inCIDR("log.properties.clientIp","::/0")) && !inCIDR("log.properties.clientIp","0.0.0.0/32") + && !inCIDR("log.properties.clientIp","::/128")) && !exists("origin.ip") + - grok: + source: log.properties.clientIP + patterns: + - fieldName: origin.ip + pattern: (?s)^.+$ + where: regexMatch("log.properties.clientIP","(?s)^.+$") && !oneOf("log.properties.clientIP",["-","unknown"]) && ((equalsIgnoreCase("log.azureKind","gateway")) + && (inCIDR("log.properties.clientIP","0.0.0.0/0") || inCIDR("log.properties.clientIP","::/0")) && !inCIDR("log.properties.clientIP","0.0.0.0/32") + && !inCIDR("log.properties.clientIP","::/128")) && !exists("origin.ip") + - grok: + source: log.azureKubernetes.sourceIPs.0 + patterns: + - fieldName: origin.ip + pattern: (?s)^.+$ + where: regexMatch("log.azureKubernetes.sourceIPs.0","(?s)^.+$") && !oneOf("log.azureKubernetes.sourceIPs.0",["-","unknown"]) + && ((equalsIgnoreCase("log.azureKind","kubernetes")) && (inCIDR("log.azureKubernetes.sourceIPs.0","0.0.0.0/0") || inCIDR("log.azureKubernetes.sourceIPs.0","::/0")) + && !inCIDR("log.azureKubernetes.sourceIPs.0","0.0.0.0/32") && !inCIDR("log.azureKubernetes.sourceIPs.0","::/128")) && !exists("origin.ip") + - grok: + source: log.callerIpAddress + patterns: + - fieldName: origin.ip + pattern: (?s)^.+$ + where: regexMatch("log.callerIpAddress","(?s)^.+$") && !oneOf("log.callerIpAddress",["-","unknown"]) && ((true) && (inCIDR("log.callerIpAddress","0.0.0.0/0") + || inCIDR("log.callerIpAddress","::/0")) && !inCIDR("log.callerIpAddress","0.0.0.0/32") && !inCIDR("log.callerIpAddress","::/128")) + && !exists("origin.ip") + - grok: + source: log.properties.userPrincipalName + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.properties.userPrincipalName","(?s)^.+$") && !oneOf("log.properties.userPrincipalName",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","signin") || equalsIgnoreCase("log.azureKind","risk")) && !exists("origin.user") + - grok: + source: log.properties.userId + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.properties.userId","(?s)^.+$") && !oneOf("log.properties.userId",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","signin") + || equalsIgnoreCase("log.azureKind","risk")) && !exists("origin.user") + - grok: + source: log.properties.servicePrincipalId + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.properties.servicePrincipalId","(?s)^.+$") && !oneOf("log.properties.servicePrincipalId",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","signin")) && !exists("origin.user") + - grok: + source: log.properties.initiatedBy.user.userPrincipalName + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.properties.initiatedBy.user.userPrincipalName","(?s)^.+$") && !oneOf("log.properties.initiatedBy.user.userPrincipalName",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","audit")) && !exists("origin.user") + - grok: + source: log.properties.initiatedBy.user.id + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.properties.initiatedBy.user.id","(?s)^.+$") && !oneOf("log.properties.initiatedBy.user.id",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","audit")) && !exists("origin.user") + - grok: + source: log.properties.initiatedBy.app.servicePrincipalId + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.properties.initiatedBy.app.servicePrincipalId","(?s)^.+$") && !oneOf("log.properties.initiatedBy.app.servicePrincipalId",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","audit")) && !exists("origin.user") + - grok: + source: log.properties.initiatedBy.app.appId + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.properties.initiatedBy.app.appId","(?s)^.+$") && !oneOf("log.properties.initiatedBy.app.appId",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","audit")) && !exists("origin.user") + - grok: + source: log.data.claims.upn + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.data.claims.upn","(?s)^.+$") && !oneOf("log.data.claims.upn",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","eventgrid")) + && !exists("origin.user") + - grok: + source: log.data.claims.http://schemas\.xmlsoap\.org/ws/2005/05/identity/claims/upn + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.data.claims.http://schemas\\.xmlsoap\\.org/ws/2005/05/identity/claims/upn","(?s)^.+$") && !oneOf("log.data.claims.http://schemas\\.xmlsoap\\.org/ws/2005/05/identity/claims/upn",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","eventgrid")) && !exists("origin.user") + - grok: + source: log.data.claims.httpschemas\.xmlsoap\.orgws200505identityclaimsupn + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.data.claims.httpschemas\\.xmlsoap\\.orgws200505identityclaimsupn","(?s)^.+$") && !oneOf("log.data.claims.httpschemas\\.xmlsoap\\.orgws200505identityclaimsupn",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","eventgrid")) && !exists("origin.user") + - grok: + source: log.data.claims.http://schemas\.microsoft\.com/identity/claims/objectidentifier + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.data.claims.http://schemas\\.microsoft\\.com/identity/claims/objectidentifier","(?s)^.+$") && !oneOf("log.data.claims.http://schemas\\.microsoft\\.com/identity/claims/objectidentifier",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","eventgrid")) && !exists("origin.user") + - grok: + source: log.data.claims.httpschemas\.microsoft\.comidentityclaimsobjectidentifier + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.data.claims.httpschemas\\.microsoft\\.comidentityclaimsobjectidentifier","(?s)^.+$") && !oneOf("log.data.claims.httpschemas\\.microsoft\\.comidentityclaimsobjectidentifier",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","eventgrid")) && !exists("origin.user") + - grok: + source: log.data.claims.appid + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.data.claims.appid","(?s)^.+$") && !oneOf("log.data.claims.appid",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","eventgrid")) + && !exists("origin.user") + - grok: + source: log.data.claims.name + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.data.claims.name","(?s)^.+$") && !oneOf("log.data.claims.name",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","eventgrid")) + && !exists("origin.user") + - grok: + source: log.azureKubernetes.user.username + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.azureKubernetes.user.username","(?s)^.+$") && !oneOf("log.azureKubernetes.user.username",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","kubernetes")) && !exists("origin.user") + - grok: + source: log.azureProperties.CsUsername + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.azureProperties.CsUsername","(?s)^.+$") && !oneOf("log.azureProperties.CsUsername",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","http")) && !exists("origin.user") + - grok: + source: log.identity.claim.http://schemas\.xmlsoap\.org/ws/2005/05/identity/claims/upn + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.identity.claim.http://schemas\\.xmlsoap\\.org/ws/2005/05/identity/claims/upn","(?s)^.+$") && !oneOf("log.identity.claim.http://schemas\\.xmlsoap\\.org/ws/2005/05/identity/claims/upn",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","keyvault")) && !exists("origin.user") + - grok: + source: log.identity.claim.httpschemas\.xmlsoap\.orgws200505identityclaimsupn + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.identity.claim.httpschemas\\.xmlsoap\\.orgws200505identityclaimsupn","(?s)^.+$") && !oneOf("log.identity.claim.httpschemas\\.xmlsoap\\.orgws200505identityclaimsupn",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","keyvault")) && !exists("origin.user") + - grok: + source: log.identity.claim.http://schemas\.microsoft\.com/identity/claims/objectidentifier + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.identity.claim.http://schemas\\.microsoft\\.com/identity/claims/objectidentifier","(?s)^.+$") && !oneOf("log.identity.claim.http://schemas\\.microsoft\\.com/identity/claims/objectidentifier",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","keyvault")) && !exists("origin.user") + - grok: + source: log.identity.claim.httpschemas\.microsoft\.comidentityclaimsobjectidentifier + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.identity.claim.httpschemas\\.microsoft\\.comidentityclaimsobjectidentifier","(?s)^.+$") && !oneOf("log.identity.claim.httpschemas\\.microsoft\\.comidentityclaimsobjectidentifier",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","keyvault")) && !exists("origin.user") + - grok: + source: log.identity.claim.appid + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.identity.claim.appid","(?s)^.+$") && !oneOf("log.identity.claim.appid",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","keyvault")) + && !exists("origin.user") + - grok: + source: log.identity.claims.http://schemas\.xmlsoap\.org/ws/2005/05/identity/claims/upn + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.identity.claims.http://schemas\\.xmlsoap\\.org/ws/2005/05/identity/claims/upn","(?s)^.+$") && !oneOf("log.identity.claims.http://schemas\\.xmlsoap\\.org/ws/2005/05/identity/claims/upn",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","activity")) && !exists("origin.user") + - grok: + source: log.identity.claims.httpschemas\.xmlsoap\.orgws200505identityclaimsupn + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.identity.claims.httpschemas\\.xmlsoap\\.orgws200505identityclaimsupn","(?s)^.+$") && !oneOf("log.identity.claims.httpschemas\\.xmlsoap\\.orgws200505identityclaimsupn",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","activity")) && !exists("origin.user") + - grok: + source: log.identity.claims.http://schemas\.microsoft\.com/identity/claims/objectidentifier + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.identity.claims.http://schemas\\.microsoft\\.com/identity/claims/objectidentifier","(?s)^.+$") && + !oneOf("log.identity.claims.http://schemas\\.microsoft\\.com/identity/claims/objectidentifier",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","activity")) + && !exists("origin.user") + - grok: + source: log.identity.claims.httpschemas\.microsoft\.comidentityclaimsobjectidentifier + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.identity.claims.httpschemas\\.microsoft\\.comidentityclaimsobjectidentifier","(?s)^.+$") && !oneOf("log.identity.claims.httpschemas\\.microsoft\\.comidentityclaimsobjectidentifier",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","activity")) && !exists("origin.user") + - grok: + source: log.identity.claims.appid + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.identity.claims.appid","(?s)^.+$") && !oneOf("log.identity.claims.appid",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","activity")) + && !exists("origin.user") + - grok: + source: log.azureRequestBody.properties.principalId + patterns: + - fieldName: target.user + pattern: (?s)^.+$ + where: regexMatch("log.azureRequestBody.properties.principalId","(?s)^.+$") && !oneOf("log.azureRequestBody.properties.principalId",["-","unknown"]) + && (equalsIgnoreCase("log.operationName","Microsoft.Authorization/roleAssignments/write")) && !exists("target.user") + - grok: + source: log.data.authorization.evidence.role + patterns: + - fieldName: origin.group + pattern: (?s)^.+$ + where: regexMatch("log.data.authorization.evidence.role","(?s)^.+$") && !oneOf("log.data.authorization.evidence.role",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","eventgrid")) && !exists("origin.group") + - grok: + source: log.properties.deviceDetail.displayName + patterns: + - fieldName: origin.host + pattern: (?s)^.+$ + where: regexMatch("log.properties.deviceDetail.displayName","(?s)^.+$") && !oneOf("log.properties.deviceDetail.displayName",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","signin")) && !exists("origin.host") + - grok: + source: log.properties.targetResources.0.userPrincipalName + patterns: + - fieldName: target.user + pattern: (?s)^.+$ + where: regexMatch("log.properties.targetResources.0.userPrincipalName","(?s)^.+$") && !oneOf("log.properties.targetResources.0.userPrincipalName",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","audit") && equals("log.properties.targetResources.#",1) && equals("log.properties.targetResources.0.type","User")) + && !exists("target.user") + - grok: + source: log.properties.targetResources.0.id + patterns: + - fieldName: target.user + pattern: (?s)^.+$ + where: regexMatch("log.properties.targetResources.0.id","(?s)^.+$") && !oneOf("log.properties.targetResources.0.id",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","audit") && equals("log.properties.targetResources.#",1) && equals("log.properties.targetResources.0.type","User")) + && !exists("target.user") + - grok: + source: log.properties.host + patterns: + - fieldName: target.host + pattern: (?s)^.+$ + where: regexMatch("log.properties.host","(?s)^.+$") && !oneOf("log.properties.host",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","gateway")) + && !exists("target.host") + - grok: + source: log.azureProperties.ComputerName + patterns: + - fieldName: target.host + pattern: (?s)^.+$ + where: regexMatch("log.azureProperties.ComputerName","(?s)^.+$") && !oneOf("log.azureProperties.ComputerName",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","http")) && !exists("target.host") + - grok: + source: log.Host + patterns: + - fieldName: target.host + pattern: (?s)^.+$ + where: regexMatch("log.Host","(?s)^.+$") && !oneOf("log.Host",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","console")) + && !exists("target.host") + - grok: + source: log.azureProperties.CsHost + patterns: + - fieldName: target.host + pattern: (?s)^.+$ + where: regexMatch("log.azureProperties.CsHost","(?s)^.+$") && !oneOf("log.azureProperties.CsHost",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","http")) + && !exists("target.host") + - grok: + source: log.Uri + patterns: + - fieldName: target.url + pattern: (?s)^.+$ + where: regexMatch("log.Uri","(?s)^.+$") && !oneOf("log.Uri",["-","unknown"]) && ((true) && regexMatch("log.Uri","^[A-Za-z][A-Za-z0-9+.-]*://")) + && !exists("target.url") + - grok: + source: log.properties.requestUri + patterns: + - fieldName: target.url + pattern: (?s)^.+$ + where: regexMatch("log.properties.requestUri","(?s)^.+$") && !oneOf("log.properties.requestUri",["-","unknown"]) && ((equalsIgnoreCase("log.azureKind","keyvault") + || equalsIgnoreCase("log.azureKind","gateway")) && regexMatch("log.properties.requestUri","^[A-Za-z][A-Za-z0-9+.-]*://")) + && !exists("target.url") + - grok: + source: log.data.httpRequest.url + patterns: + - fieldName: target.url + pattern: (?s)^.+$ + where: regexMatch("log.data.httpRequest.url","(?s)^.+$") && !oneOf("log.data.httpRequest.url",["-","unknown"]) && ((equalsIgnoreCase("log.azureKind","eventgrid")) + && regexMatch("log.data.httpRequest.url","^[A-Za-z][A-Za-z0-9+.-]*://")) && !exists("target.url") + - grok: + source: log.azureKubernetes.requestURI + patterns: + - fieldName: target.url + pattern: (?s)^.+$ + where: regexMatch("log.azureKubernetes.requestURI","(?s)^.+$") && !oneOf("log.azureKubernetes.requestURI",["-","unknown"]) + && ((equalsIgnoreCase("log.azureKind","kubernetes")) && regexMatch("log.azureKubernetes.requestURI","^[A-Za-z][A-Za-z0-9+.-]*://")) + && !exists("target.url") + - add: + function: string + params: + key: action + value: get + where: equalsIgnoreCase("log.properties.requestMethod","GET") || equalsIgnoreCase("log.azureProperties.CsMethod","GET") + - add: + function: string + params: + key: action + value: post + where: equalsIgnoreCase("log.properties.requestMethod","POST") || equalsIgnoreCase("log.azureProperties.CsMethod","POST") + - add: + function: string + params: + key: action + value: put + where: equalsIgnoreCase("log.properties.requestMethod","PUT") || equalsIgnoreCase("log.azureProperties.CsMethod","PUT") + - add: + function: string + params: + key: action + value: delete + where: equalsIgnoreCase("log.properties.requestMethod","DELETE") || equalsIgnoreCase("log.azureProperties.CsMethod","DELETE") + - add: + function: string + params: + key: action + value: patch + where: equalsIgnoreCase("log.properties.requestMethod","PATCH") || equalsIgnoreCase("log.azureProperties.CsMethod","PATCH") + - add: + function: string + params: + key: action + value: request + where: equalsIgnoreCase("log.properties.requestMethod","REQUEST") || equalsIgnoreCase("log.azureProperties.CsMethod","REQUEST") + - add: + function: string + params: + key: action + value: head + where: equalsIgnoreCase("log.properties.requestMethod","HEAD") || equalsIgnoreCase("log.azureProperties.CsMethod","HEAD") + - add: + function: string + params: + key: action + value: options + where: equalsIgnoreCase("log.properties.requestMethod","OPTIONS") || equalsIgnoreCase("log.azureProperties.CsMethod","OPTIONS") + - add: + function: string + params: + key: action + value: connect + where: equalsIgnoreCase("log.properties.requestMethod","CONNECT") || equalsIgnoreCase("log.azureProperties.CsMethod","CONNECT") + - add: + function: string + params: + key: action + value: trace + where: equalsIgnoreCase("log.properties.requestMethod","TRACE") || equalsIgnoreCase("log.azureProperties.CsMethod","TRACE") + - grok: + source: log.azureKubernetes.verb + patterns: + - fieldName: action + pattern: (?s)^.+$ + where: regexMatch("log.azureKubernetes.verb","(?s)^.+$") && !oneOf("log.azureKubernetes.verb",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","kubernetes")) + && !exists("action") + - grok: + source: log.properties.ActionType + patterns: + - fieldName: action + pattern: (?s)^.+$ + where: regexMatch("log.properties.ActionType","(?s)^.+$") && !oneOf("log.properties.ActionType",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","endpoint-process") + || equalsIgnoreCase("log.azureKind","endpoint-event")) && !exists("action") + - grok: + source: log.data.operationName + patterns: + - fieldName: action + pattern: (?s)^.+$ + where: regexMatch("log.data.operationName","(?s)^.+$") && !oneOf("log.data.operationName",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","eventgrid")) + && !exists("action") + - grok: + source: log.operationName.value + patterns: + - fieldName: action + pattern: (?s)^.+$ + where: regexMatch("log.operationName.value","(?s)^.+$") && !oneOf("log.operationName.value",["-","unknown"]) && (true) && + !exists("action") + - grok: + source: log.operationName + patterns: + - fieldName: action + pattern: (?s)^.+$ + where: regexMatch("log.operationName","(?s)^.+$") && !oneOf("log.operationName",["-","unknown"]) && (true) && !exists("action") + - grok: + source: log.properties.DeviceName + patterns: + - fieldName: origin.host + pattern: (?s)^.+$ + where: regexMatch("log.properties.DeviceName","(?s)^.+$") && !oneOf("log.properties.DeviceName",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","endpoint-process")) + && !exists("origin.host") + - grok: + source: log.properties.InitiatingProcessAccountUpn + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.properties.InitiatingProcessAccountUpn","(?s)^.+$") && !oneOf("log.properties.InitiatingProcessAccountUpn",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","endpoint-process")) && !exists("origin.user") + - grok: + source: log.properties.InitiatingProcessAccountName + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.properties.InitiatingProcessAccountName","(?s)^.+$") && !oneOf("log.properties.InitiatingProcessAccountName",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","endpoint-process")) && !exists("origin.user") + - grok: + source: log.properties.InitiatingProcessAccountDomain + patterns: + - fieldName: origin.domain + pattern: (?s)^.+$ + where: regexMatch("log.properties.InitiatingProcessAccountDomain","(?s)^.+$") && !oneOf("log.properties.InitiatingProcessAccountDomain",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","endpoint-process")) && !exists("origin.domain") + - grok: + source: log.properties.InitiatingProcessFileName + patterns: + - fieldName: origin.process + pattern: (?s)^.+$ + where: regexMatch("log.properties.InitiatingProcessFileName","(?s)^.+$") && !oneOf("log.properties.InitiatingProcessFileName",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","endpoint-process")) && !exists("origin.process") + - grok: + source: log.properties.InitiatingProcessCommandLine + patterns: + - fieldName: origin.command + pattern: (?s)^.+$ + where: regexMatch("log.properties.InitiatingProcessCommandLine","(?s)^.+$") && !oneOf("log.properties.InitiatingProcessCommandLine",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","endpoint-process")) && !exists("origin.command") + - grok: + source: log.properties.ProcessCommandLine + patterns: + - fieldName: target.command + pattern: (?s)^.+$ + where: regexMatch("log.properties.ProcessCommandLine","(?s)^.+$") && !oneOf("log.properties.ProcessCommandLine",["-","unknown"]) + && (equalsIgnoreCase("log.azureKind","endpoint-process")) && !exists("target.command") + - grok: + source: log.properties.FileName + patterns: + - fieldName: target.process + pattern: (?s)^.+$ + where: regexMatch("log.properties.FileName","(?s)^.+$") && !oneOf("log.properties.FileName",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","endpoint-process")) + && !exists("target.process") + - grok: + source: log.properties.FolderPath + patterns: + - fieldName: target.path + pattern: (?s)^.+$ + where: regexMatch("log.properties.FolderPath","(?s)^.+$") && !oneOf("log.properties.FolderPath",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","endpoint-process")) + && !exists("target.path") + - grok: + source: log.properties.SHA256 + patterns: + - fieldName: target.hash + pattern: (?s)^.+$ + where: regexMatch("log.properties.SHA256","(?s)^.+$") && !oneOf("log.properties.SHA256",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","endpoint-process")) + && !exists("target.hash") + - grok: + source: log.properties.SHA1 + patterns: + - fieldName: target.sha1 + pattern: (?s)^.+$ + where: regexMatch("log.properties.SHA1","(?s)^.+$") && !oneOf("log.properties.SHA1",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","endpoint-process")) + && !exists("target.sha1") + - grok: + source: log.properties.MD5 + patterns: + - fieldName: target.md5 + pattern: (?s)^.+$ + where: regexMatch("log.properties.MD5","(?s)^.+$") && !oneOf("log.properties.MD5",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","endpoint-process")) + && !exists("target.md5") + - grok: + source: log.properties.DeviceName + patterns: + - fieldName: origin.host + pattern: (?s)^.+$ + where: regexMatch("log.properties.DeviceName","(?s)^.+$") && !oneOf("log.properties.DeviceName",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","endpoint-event")) + && !exists("origin.host") + - grok: + source: log.properties.AccountUpn + patterns: + - fieldName: origin.user + pattern: (?s)^.+$ + where: regexMatch("log.properties.AccountUpn","(?s)^.+$") && !oneOf("log.properties.AccountUpn",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","endpoint-event")) + && !exists("origin.user") + - cast: + fields: + - log.StatusCode + to: string + where: exists("log.StatusCode") && (true) + - rename: + from: + - log.StatusCode + to: statusCode + where: (true) && !exists("statusCode") && greaterOrEqual("log.StatusCode",0) && lessOrEqual("log.StatusCode",4294967295) + && regexMatch("log.StatusCode","^[0-9]+$") + - cast: + fields: + - statusCode + to: int + where: exists("statusCode") && (true) + - cast: + fields: + - log.ResponseCode + to: string + where: exists("log.ResponseCode") && (true) + - rename: + from: + - log.ResponseCode + to: statusCode + where: (true) && !exists("statusCode") && greaterOrEqual("log.ResponseCode",0) && lessOrEqual("log.ResponseCode",4294967295) + && regexMatch("log.ResponseCode","^[0-9]+$") + - cast: + fields: + - statusCode + to: int + where: exists("statusCode") && (true) + - cast: + fields: + - log.properties.httpStatusCode + to: string + where: exists("log.properties.httpStatusCode") && (equalsIgnoreCase("log.azureKind","keyvault") || equalsIgnoreCase("log.azureKind","gateway")) + - rename: + from: + - log.properties.httpStatusCode + to: statusCode + where: (equalsIgnoreCase("log.azureKind","keyvault") || equalsIgnoreCase("log.azureKind","gateway")) && !exists("statusCode") + && greaterOrEqual("log.properties.httpStatusCode",0) && lessOrEqual("log.properties.httpStatusCode",4294967295) && regexMatch("log.properties.httpStatusCode","^[0-9]+$") + - cast: + fields: + - statusCode + to: int + where: exists("statusCode") && (true) + - cast: + fields: + - log.properties.httpStatus + to: string + where: exists("log.properties.httpStatus") && (equalsIgnoreCase("log.azureKind","gateway")) + - rename: + from: + - log.properties.httpStatus + to: statusCode + where: (equalsIgnoreCase("log.azureKind","gateway")) && !exists("statusCode") && greaterOrEqual("log.properties.httpStatus",0) + && lessOrEqual("log.properties.httpStatus",4294967295) && regexMatch("log.properties.httpStatus","^[0-9]+$") + - cast: + fields: + - statusCode + to: int + where: exists("statusCode") && (true) + - cast: + fields: + - log.properties.responseStatusCode + to: string + where: exists("log.properties.responseStatusCode") && (true) + - rename: + from: + - log.properties.responseStatusCode + to: statusCode + where: (true) && !exists("statusCode") && greaterOrEqual("log.properties.responseStatusCode",0) && lessOrEqual("log.properties.responseStatusCode",4294967295) + && regexMatch("log.properties.responseStatusCode","^[0-9]+$") + - cast: + fields: + - statusCode + to: int + where: exists("statusCode") && (true) + - cast: + fields: + - log.azureProperties.ScStatus + to: string + where: exists("log.azureProperties.ScStatus") && (equalsIgnoreCase("log.azureKind","http")) + - rename: + from: + - log.azureProperties.ScStatus + to: statusCode + where: (equalsIgnoreCase("log.azureKind","http")) && !exists("statusCode") && greaterOrEqual("log.azureProperties.ScStatus",0) + && lessOrEqual("log.azureProperties.ScStatus",4294967295) && regexMatch("log.azureProperties.ScStatus","^[0-9]+$") + - cast: + fields: + - statusCode + to: int + where: exists("statusCode") && (true) + - cast: + fields: + - log.azureKubernetes.responseStatus.code + to: string + where: exists("log.azureKubernetes.responseStatus.code") && (equalsIgnoreCase("log.azureKind","kubernetes")) + - rename: + from: + - log.azureKubernetes.responseStatus.code + to: statusCode + where: (equalsIgnoreCase("log.azureKind","kubernetes")) && !exists("statusCode") && greaterOrEqual("log.azureKubernetes.responseStatus.code",0) + && lessOrEqual("log.azureKubernetes.responseStatus.code",4294967295) && regexMatch("log.azureKubernetes.responseStatus.code","^[0-9]+$") + - cast: + fields: + - statusCode + to: int + where: exists("statusCode") && (true) + - cast: + fields: + - log.properties.clientPort + to: string + where: exists("log.properties.clientPort") && (equalsIgnoreCase("log.azureKind","gateway") || equalsIgnoreCase("log.azureKind","waf")) + - rename: + from: + - log.properties.clientPort + to: origin.port + where: (equalsIgnoreCase("log.azureKind","gateway") || equalsIgnoreCase("log.azureKind","waf")) && !exists("origin.port") + && greaterOrEqual("log.properties.clientPort",0) && lessOrEqual("log.properties.clientPort",65535) && regexMatch("log.properties.clientPort","^[0-9]+$") + - cast: + fields: + - origin.port + to: int + where: exists("origin.port") && (true) + - cast: + fields: + - log.azureProperties.SPort + to: string + where: exists("log.azureProperties.SPort") && (equalsIgnoreCase("log.azureKind","http")) + - rename: + from: + - log.azureProperties.SPort + to: target.port + where: (equalsIgnoreCase("log.azureKind","http")) && !exists("target.port") && greaterOrEqual("log.azureProperties.SPort",0) + && lessOrEqual("log.azureProperties.SPort",65535) && regexMatch("log.azureProperties.SPort","^[0-9]+$") + - cast: + fields: + - target.port + to: int + where: exists("target.port") && (true) + - cast: + fields: + - log.ResponseBodySize + to: string + where: exists("log.ResponseBodySize") && (true) + - rename: + from: + - log.ResponseBodySize + to: origin.bytesReceived + where: (true) && !exists("origin.bytesReceived") && greaterOrEqual("log.ResponseBodySize",0) && lessOrEqual("log.ResponseBodySize",1.7976931348623157e308) + - cast: + fields: + - origin.bytesReceived + to: float + where: exists("origin.bytesReceived") && (true) + - cast: + fields: + - log.properties.responseSizeBytes + to: string + where: exists("log.properties.responseSizeBytes") && (true) + - rename: + from: + - log.properties.responseSizeBytes + to: origin.bytesReceived + where: (true) && !exists("origin.bytesReceived") && greaterOrEqual("log.properties.responseSizeBytes",0) && lessOrEqual("log.properties.responseSizeBytes",1.7976931348623157e308) + - cast: + fields: + - origin.bytesReceived + to: float + where: exists("origin.bytesReceived") && (true) + - cast: + fields: + - log.azureProperties.CsBytes + to: string + where: exists("log.azureProperties.CsBytes") && (equalsIgnoreCase("log.azureKind","http")) + - rename: + from: + - log.azureProperties.CsBytes + to: origin.bytesSent + where: (equalsIgnoreCase("log.azureKind","http")) && !exists("origin.bytesSent") && greaterOrEqual("log.azureProperties.CsBytes",0) + && lessOrEqual("log.azureProperties.CsBytes",1.7976931348623157e308) + - cast: + fields: + - origin.bytesSent + to: float + where: exists("origin.bytesSent") && (true) + - cast: + fields: + - log.azureProperties.ScBytes + to: string + where: exists("log.azureProperties.ScBytes") && (equalsIgnoreCase("log.azureKind","http")) + - rename: + from: + - log.azureProperties.ScBytes + to: origin.bytesReceived + where: (equalsIgnoreCase("log.azureKind","http")) && !exists("origin.bytesReceived") && greaterOrEqual("log.azureProperties.ScBytes",0) + && lessOrEqual("log.azureProperties.ScBytes",1.7976931348623157e308) + - cast: + fields: + - origin.bytesReceived + to: float + where: exists("origin.bytesReceived") && (true) + - cast: + fields: + - log.Protocol + to: string + where: exists("log.Protocol") && (true) + - grok: + source: log.Protocol + patterns: + - fieldName: protocol + pattern: (?s)^.+$ + where: regexMatch("log.Protocol","(?s)^.+$") && !oneOf("log.Protocol",["-","unknown"]) && (true) && !exists("protocol") + - grok: + source: log.azureProperties.Protocol + patterns: + - fieldName: protocol + pattern: (?s)^.+$ + where: regexMatch("log.azureProperties.Protocol","(?s)^.+$") && !oneOf("log.azureProperties.Protocol",["-","unknown"]) && + (equalsIgnoreCase("log.azureKind","http")) && !exists("protocol") + - rename: + from: + - log.azureProperties + to: azureSavedazureProperties + where: exists("log.azureProperties") + - rename: + from: + - log.azureKubernetes + to: azureSavedazureKubernetes + where: exists("log.azureKubernetes") + - rename: + from: + - log.azureRequestBody + to: azureSavedazureRequestBody + where: exists("log.azureRequestBody") + - json: + source: raw + - delete: + fields: *id001 + - rename: + from: + - azureSavedazureProperties + to: log.azureProperties + where: exists("azureSavedazureProperties") + - rename: + from: + - azureSavedazureKubernetes + to: log.azureKubernetes + where: exists("azureSavedazureKubernetes") + - rename: + from: + - azureSavedazureRequestBody + to: log.azureRequestBody + where: exists("azureSavedazureRequestBody") + - add: + function: string + params: + key: log.azureKind + value: signin + where: oneOf("log.category",["SignInLogs","SigninLogs","NonInteractiveUserSignInLogs","ServicePrincipalSignInLogs","ManagedIdentitySignInLogs"]) + - add: + function: string + params: + key: log.azureKind + value: audit + where: equalsIgnoreCase("log.category","AuditLogs") + - add: + function: string + params: + key: log.azureKind + value: risk + where: oneOf("log.category",["UserRiskEvents","ServicePrincipalRiskEvents"]) + - add: + function: string + params: + key: log.azureKind + value: eventgrid + where: regexMatch("log.eventType","^Microsoft\\.Resources\\.Resource(Write|Delete|Action)(Success|Failure|Cancel)$") || regexMatch("log.type","^Microsoft\\.Resources\\.Resource(Write|Delete|Action)(Success|Failure|Cancel)$") + - add: + function: string + params: + key: log.azureKind + value: activity + where: equalsIgnoreCase("log.category","Administrative") || equalsIgnoreCase("log.category.value","Administrative") || (oneOf("log.category",["Write","Delete","Action"]) + && exists("log.identity.authorization.action") && regexMatch("log.operationName","(?i)^Microsoft\\.")) + - add: + function: string + params: + key: log.azureKind + value: keyvault + where: equalsIgnoreCase("log.category","AuditEvent") && regexMatch("log.resourceId","(?i)/providers/Microsoft\\.KeyVault/vaults/") + - add: + function: string + params: + key: log.azureKind + value: waf + where: equalsIgnoreCase("log.category","ApplicationGatewayFirewallLog") + - add: + function: string + params: + key: log.azureKind + value: gateway + where: equalsIgnoreCase("log.category","ApplicationGatewayAccessLog") + - add: + function: string + params: + key: log.azureKind + value: http + where: equalsIgnoreCase("log.category","AppServiceHTTPLogs") + - add: + function: string + params: + key: log.azureKind + value: console + where: equalsIgnoreCase("log.category","AppServiceConsoleLogs") + - add: + function: string + params: + key: log.azureKind + value: kubernetes + where: oneOf("log.category",["kube-audit","kube-audit-admin"]) && equals("log.azureKubernetes.apiVersion","audit.k8s.io/v1") + && equals("log.azureKubernetes.kind","Event") + - add: + function: string + params: + key: log.azureKind + value: endpoint-process + where: equalsIgnoreCase("log.category","AdvancedHunting-DeviceProcessEvents") + - add: + function: string + params: + key: log.azureKind + value: endpoint-event + where: equalsIgnoreCase("log.category","AdvancedHunting-DeviceEvents") + - add: + function: string + params: + key: log.azureKind + value: securityalert + where: equalsIgnoreCase("log.Type","SecurityAlert") && regexMatch("log.SystemAlertId","^.+$") + - grok: + source: log.data.operationName + patterns: + - fieldName: log.operationName + pattern: (?s)^.+$ + where: regexMatch("log.data.operationName","(?s)^.+$") && !oneOf("log.data.operationName",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","eventgrid")) + - grok: + source: log.data.resourceUri + patterns: + - fieldName: log.resourceId + pattern: (?s)^.+$ + where: regexMatch("log.data.resourceUri","(?s)^.+$") && !oneOf("log.data.resourceUri",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","eventgrid")) + - grok: + source: log.operationName.value + patterns: + - fieldName: log.azureOperation + pattern: (?s)^.+$ + where: regexMatch("log.operationName.value","(?s)^.+$") && !oneOf("log.operationName.value",["-","unknown"]) && (true) && + !exists("log.azureOperation") + - grok: + source: log.operationName + patterns: + - fieldName: log.azureOperation + pattern: (?s)^.+$ + where: regexMatch("log.operationName","(?s)^.+$") && !oneOf("log.operationName",["-","unknown"]) && (true) && !exists("log.azureOperation") + - add: + function: string + params: + key: actionResult + value: success + where: (oneOf("log.azureKind",["activity","eventgrid","keyvault","http","gateway","audit"])) && regexMatch("log.resultType","(?i)^(success|succeeded|successful|ok)$") + - add: + function: string + params: + key: actionResult + value: failure + where: (oneOf("log.azureKind",["activity","eventgrid","keyvault","http","gateway","audit"])) && regexMatch("log.resultType","(?i)^(failure|failed|error|timeout|canceled|cancelled)$") + - add: + function: string + params: + key: actionResult + value: denied + where: (oneOf("log.azureKind",["activity","eventgrid","keyvault","http","gateway","audit"])) && regexMatch("log.resultType","(?i)^(denied|blocked|forbidden|unauthorized)$") + - add: + function: string + params: + key: actionResult + value: success + where: (equalsIgnoreCase("log.azureKind","audit")) && regexMatch("log.properties.result","(?i)^(success|succeeded|successful|ok)$") + - add: + function: string + params: + key: actionResult + value: failure + where: (equalsIgnoreCase("log.azureKind","audit")) && regexMatch("log.properties.result","(?i)^(failure|failed|error|timeout|canceled|cancelled)$") + - add: + function: string + params: + key: actionResult + value: denied + where: (equalsIgnoreCase("log.azureKind","audit")) && regexMatch("log.properties.result","(?i)^(denied|blocked|forbidden|unauthorized)$") + - add: + function: string + params: + key: actionResult + value: success + where: (equalsIgnoreCase("log.azureKind","eventgrid")) && regexMatch("log.data.status","(?i)^(success|succeeded|successful|ok)$") + - add: + function: string + params: + key: actionResult + value: failure + where: (equalsIgnoreCase("log.azureKind","eventgrid")) && regexMatch("log.data.status","(?i)^(failure|failed|error|timeout|canceled|cancelled)$") + - add: + function: string + params: + key: actionResult + value: denied + where: (equalsIgnoreCase("log.azureKind","eventgrid")) && regexMatch("log.data.status","(?i)^(denied|blocked|forbidden|unauthorized)$") + - add: + function: string + params: + key: actionResult + value: success + where: (equalsIgnoreCase("log.azureKind","activity")) && regexMatch("log.status.value","(?i)^(success|succeeded|successful|ok)$") + - add: + function: string + params: + key: actionResult + value: failure + where: (equalsIgnoreCase("log.azureKind","activity")) && regexMatch("log.status.value","(?i)^(failure|failed|error|timeout|canceled|cancelled)$") + - add: + function: string + params: + key: actionResult + value: denied + where: (equalsIgnoreCase("log.azureKind","activity")) && regexMatch("log.status.value","(?i)^(denied|blocked|forbidden|unauthorized)$") + - add: + function: string + params: + key: actionResult + value: success + where: equalsIgnoreCase("log.azureKind","signin") && (equals("log.resultType",0) || equals("log.properties.status.errorCode",0)) + - add: + function: string + params: + key: actionResult + value: failure + where: equalsIgnoreCase("log.azureKind","signin") && ((greaterThan("log.resultType",0)) || greaterThan("log.properties.status.errorCode",0)) + - add: + function: string + params: + key: actionResult + value: denied + where: equalsIgnoreCase("log.azureKind","signin") && (equals("log.resultType",53003) || equals("log.properties.status.errorCode",53003)) + - add: + function: string + params: + key: actionResult + value: success + where: equalsIgnoreCase("log.azureKind","eventgrid") && endsWith("log.eventType","Success") && !oneOf("actionResult",["denied","failure"]) + - add: + function: string + params: + key: actionResult + value: failure + where: equalsIgnoreCase("log.azureKind","eventgrid") && (endsWith("log.eventType","Failure") || endsWith("log.eventType","Cancel")) + - add: + function: string + params: + key: actionResult + value: success + where: equalsIgnoreCase("log.azureKind","eventgrid") && endsWith("log.type","Success") && !oneOf("actionResult",["denied","failure"]) + - add: + function: string + params: + key: actionResult + value: failure + where: equalsIgnoreCase("log.azureKind","eventgrid") && (endsWith("log.type","Failure") || endsWith("log.type","Cancel")) + - add: + function: string + params: + key: actionResult + value: success + where: (oneOf("log.azureKind",["http","gateway","keyvault"]) || (equals("log.azureKind","kubernetes") && equals("log.azureKubernetes.stage","ResponseComplete"))) + && greaterOrEqual("statusCode",200) && lessThan("statusCode",400) && !oneOf("actionResult",["denied","failure"]) + - add: + function: string + params: + key: actionResult + value: failure + where: (oneOf("log.azureKind",["http","gateway","keyvault"]) || (equals("log.azureKind","kubernetes") && equals("log.azureKubernetes.stage","ResponseComplete"))) + && greaterOrEqual("statusCode",400) && lessThan("statusCode",600) + - add: + function: string + params: + key: actionResult + value: denied + where: (oneOf("log.azureKind",["http","gateway","keyvault"]) || (equals("log.azureKind","kubernetes") && equals("log.azureKubernetes.stage","ResponseComplete"))) + && (equals("statusCode",401) || equals("statusCode",403)) + - add: + function: string + params: + key: actionResult + value: denied + where: equalsIgnoreCase("log.azureKind","keyvault") && regexMatch("log.resultSignature","(?i)^(Unauthorized|Forbidden|AccessDenied)$") + - add: + function: string + params: + key: actionResult + value: denied + where: equalsIgnoreCase("log.azureKind","waf") && equalsIgnoreCase("log.properties.action","Blocked") + - add: + function: string + params: + key: severity + value: debug + where: regexMatch("log.level","(?i)^(debug|trace)$") + - add: + function: string + params: + key: severity + value: info + where: regexMatch("log.level","(?i)^(info|information|informational)$") + - add: + function: string + params: + key: severity + value: warning + where: regexMatch("log.level","(?i)^(warn|warning)$") + - add: + function: string + params: + key: severity + value: error + where: regexMatch("log.level","(?i)^(error)$") + - add: + function: string + params: + key: severity + value: critical + where: regexMatch("log.level","(?i)^(fatal|critical)$") + - add: + function: string + params: + key: severity + value: info + where: equalsIgnoreCase("log.azureKind","securityalert") && equalsIgnoreCase("log.AlertSeverity","Informational") + - add: + function: string + params: + key: severity + value: info + where: equalsIgnoreCase("log.azureKind","securityalert") && equalsIgnoreCase("log.AlertSeverity","Low") + - add: + function: string + params: + key: severity + value: warning + where: equalsIgnoreCase("log.azureKind","securityalert") && equalsIgnoreCase("log.AlertSeverity","Medium") + - add: + function: string + params: + key: severity + value: error + where: equalsIgnoreCase("log.azureKind","securityalert") && equalsIgnoreCase("log.AlertSeverity","High") + - add: + function: string + params: + key: protocol + value: ICMP + where: equals("protocol",1) + - add: + function: string + params: + key: protocol + value: TCP + where: equals("protocol",6) + - add: + function: string + params: + key: protocol + value: UDP + where: equals("protocol",17) + - add: + function: string + params: + key: protocol + value: GRE + where: equals("protocol",47) + - add: + function: string + params: + key: protocol + value: ESP + where: equals("protocol",50) + - add: + function: string + params: + key: protocol + value: AH + where: equals("protocol",51) + - add: + function: string + params: + key: protocol + value: ICMPV6 + where: equals("protocol",58) + - add: + function: string + params: + key: protocol + value: SCTP + where: equals("protocol",132) + - dynamic: + plugin: com.utmstack.geolocation + params: + source: origin.ip + destination: origin.geolocation + where: exists("origin.ip") + - grok: + source: log.resourceId + patterns: + - fieldName: log.azureScope + pattern: (?s)^.+$ + where: regexMatch("log.resourceId","(?s)^.+$") && !oneOf("log.resourceId",["-","unknown"]) && (oneOf("log.azureKind",["keyvault","kubernetes","waf","gateway"])) + && !exists("log.azureScope") + - add: + function: string + params: + key: log.azureScopeType + value: resource + where: exists("log.azureScope") + - grok: + source: log.tenantId + patterns: + - fieldName: log.azureScope + pattern: (?s)^.+$ + where: regexMatch("log.tenantId","(?s)^.+$") && !oneOf("log.tenantId",["-","unknown"]) && (true) && !exists("log.azureScope") + - add: + function: string + params: + key: log.azureScopeType + value: directory + where: exists("log.azureScope") && !exists("log.azureScopeType") + - grok: + source: log.data.tenantId + patterns: + - fieldName: log.azureScope + pattern: (?s)^.+$ + where: regexMatch("log.data.tenantId","(?s)^.+$") && !oneOf("log.data.tenantId",["-","unknown"]) && (true) && !exists("log.azureScope") + - add: + function: string + params: + key: log.azureScopeType + value: directory + where: exists("log.azureScope") && !exists("log.azureScopeType") + - grok: + source: log.subscriptionId + patterns: + - fieldName: log.azureScope + pattern: (?s)^.+$ + where: regexMatch("log.subscriptionId","(?s)^.+$") && !oneOf("log.subscriptionId",["-","unknown"]) && (true) && !exists("log.azureScope") + - add: + function: string + params: + key: log.azureScopeType + value: subscription + where: exists("log.azureScope") && !exists("log.azureScopeType") + - grok: + source: log.data.subscriptionId + patterns: + - fieldName: log.azureScope + pattern: (?s)^.+$ + where: regexMatch("log.data.subscriptionId","(?s)^.+$") && !oneOf("log.data.subscriptionId",["-","unknown"]) && (true) && + !exists("log.azureScope") + - add: + function: string + params: + key: log.azureScopeType + value: subscription + where: exists("log.azureScope") && !exists("log.azureScopeType") + - grok: + source: log.resourceId + patterns: + - fieldName: log.azureScope + pattern: (?s)^.+$ + where: regexMatch("log.resourceId","(?s)^.+$") && !oneOf("log.resourceId",["-","unknown"]) && (true) && !exists("log.azureScope") + - add: + function: string + params: + key: log.azureScopeType + value: resource + where: exists("log.azureScope") && !exists("log.azureScopeType") + - grok: + source: origin.user + patterns: + - fieldName: log.azureActor + pattern: (?s)^.+$ + where: regexMatch("origin.user","(?s)^.+$") && !oneOf("origin.user",["-","unknown"]) && (true) && !exists("log.azureActor") + - add: + function: string + params: + key: log.azureActorType + value: user + where: exists("log.azureActor") && !exists("log.azureActorType") + - grok: + source: origin.ip + patterns: + - fieldName: log.azureActor + pattern: (?s)^.+$ + where: regexMatch("origin.ip","(?s)^.+$") && !oneOf("origin.ip",["-","unknown"]) && (true) && !exists("log.azureActor") + - add: + function: string + params: + key: log.azureActorType + value: ip + where: exists("log.azureActor") && !exists("log.azureActorType") + - add: + function: string + params: + key: log.correlationCandidate.aks_security_threats + value: 'true' + where: '((equalsIgnoreCase("log.azureKind","activity") || equalsIgnoreCase("log.azureKind","eventgrid")) && equalsIgnoreCase("actionResult","success") + && regexMatch("log.azureOperation","(?i)^Microsoft\\.(ContainerService|Kubernetes)/.+/(write|create|exec)$")) && exists("dataSource") + && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") && exists("log.azureScope") + && !equals("log.azureScope","") && exists("log.azureActorType") && !equals("log.azureActorType","") && exists("log.azureActor") + && !equals("log.azureActor","") + + ' + - add: + function: string + params: + key: log.correlationCandidate.app_registration_abuse + value: 'true' + where: '(equalsIgnoreCase("log.azureKind","audit") && equalsIgnoreCase("actionResult","success") && (equalsIgnoreCase("log.azureOperation","Add + application") || equalsIgnoreCase("log.azureOperation","Add service principal") || equalsIgnoreCase("log.azureOperation","Consent + to application"))) && exists("dataSource") && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") + && exists("log.azureScope") && !equals("log.azureScope","") && exists("log.azureActorType") && !equals("log.azureActorType","") + && exists("log.azureActor") && !equals("log.azureActor","") + + ' + - add: + function: string + params: + key: log.correlationCandidate.application_gateway_waf_alerts + value: 'true' + where: '(equalsIgnoreCase("log.azureKind","waf") && equalsIgnoreCase("actionResult","denied") && exists("log.properties.ruleId")) + && exists("dataSource") && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") + && exists("log.azureScope") && !equals("log.azureScope","") && exists("origin.ip") && !equals("origin.ip","") + + ' + - add: + function: string + params: + key: log.correlationCandidate.azure_ad_password_spray + value: 'true' + where: '(equalsIgnoreCase("log.azureKind","signin") && equalsIgnoreCase("actionResult","failure") && equals("log.properties.status.errorCode",50126)) + && exists("dataSource") && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") + && exists("log.azureScope") && !equals("log.azureScope","") && exists("origin.ip") && !equals("origin.ip","") + + ' + - add: + function: string + params: + key: log.correlationCandidate.azure_bulk_role_changes + value: 'true' + where: '(equalsIgnoreCase("log.azureKind","audit") && equalsIgnoreCase("actionResult","success") && (equalsIgnoreCase("log.azureOperation","Add + member to role") || equalsIgnoreCase("log.azureOperation","Add eligible member to role") || equalsIgnoreCase("log.azureOperation","Add + member to role completed (PIM activation)") || equalsIgnoreCase("log.azureOperation","Add eligible member to role in PIM + completed") || equalsIgnoreCase("log.azureOperation","Add eligible member to role in PIM completed (permanent)") || equalsIgnoreCase("log.azureOperation","Add + eligible member to role in PIM completed (timebound)") || equalsIgnoreCase("log.azureOperation","Add member to role in + PIM completed (permanent)") || equalsIgnoreCase("log.azureOperation","Add member to role in PIM completed (timebound)"))) + && exists("dataSource") && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") + && exists("log.azureScope") && !equals("log.azureScope","") && exists("log.azureActorType") && !equals("log.azureActorType","") + && exists("log.azureActor") && !equals("log.azureActor","") + + ' + - add: + function: string + params: + key: log.correlationCandidate.azure_kubernetes_secret_access + value: 'true' + where: '(equalsIgnoreCase("log.azureKind","kubernetes") && equalsIgnoreCase("log.azureKubernetes.stage","ResponseComplete") + && equalsIgnoreCase("actionResult","success") && equalsIgnoreCase("log.azureKubernetes.objectRef.resource","secrets") && + oneOf("log.azureKubernetes.verb",["create", "update", "delete", "deletecollection", "patch"])) && exists("dataSource") + && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") && exists("log.azureScope") + && !equals("log.azureScope","") && exists("log.azureActorType") && !equals("log.azureActorType","") && exists("log.azureActor") + && !equals("log.azureActor","") + + ' + - add: + function: string + params: + key: log.correlationCandidate.azure_laps_credential_dump + value: 'true' + where: '(equalsIgnoreCase("log.azureKind","audit") && equalsIgnoreCase("actionResult","success") && (equalsIgnoreCase("log.azureOperation","Recover + device local administrator password") || equalsIgnoreCase("log.azureOperation","Read device local administrator password"))) + && exists("dataSource") && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") + && exists("log.azureScope") && !equals("log.azureScope","") && exists("log.azureActorType") && !equals("log.azureActorType","") + && exists("log.azureActor") && !equals("log.azureActor","") + + ' + - add: + function: string + params: + key: log.correlationCandidate.azure_ropc_authentication + value: 'true' + where: '(equalsIgnoreCase("log.azureKind","signin") && equalsIgnoreCase("actionResult","success") && equalsIgnoreCase("log.properties.authenticationProtocol","ropc")) + && exists("dataSource") && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") + && exists("log.azureScope") && !equals("log.azureScope","") && exists("log.azureActorType") && !equals("log.azureActorType","") + && exists("log.azureActor") && !equals("log.azureActor","") + + ' + - add: + function: string + params: + key: log.correlationCandidate.key_vault_access_spikes + value: 'true' + where: '(equalsIgnoreCase("log.azureKind","keyvault") && equalsIgnoreCase("actionResult","success") && (equalsIgnoreCase("log.azureOperation","SecretGet") + || equalsIgnoreCase("log.azureOperation","SecretList") || equalsIgnoreCase("log.azureOperation","KeyGet"))) && exists("dataSource") + && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") && exists("log.azureScope") + && !equals("log.azureScope","") && exists("log.azureActorType") && !equals("log.azureActorType","") && exists("log.azureActor") + && !equals("log.azureActor","") + + ' + - add: + function: string + params: + key: log.correlationCandidate.pim_role_activation_abuse + value: 'true' + where: '(equalsIgnoreCase("log.azureKind","audit") && equalsIgnoreCase("actionResult","success") && (equalsIgnoreCase("log.azureOperation","Add + member to role completed (PIM activation)") || equalsIgnoreCase("log.azureOperation","Add eligible member to role in PIM + completed") || equalsIgnoreCase("log.azureOperation","Add eligible member to role in PIM completed (permanent)") || equalsIgnoreCase("log.azureOperation","Add + eligible member to role in PIM completed (timebound)") || equalsIgnoreCase("log.azureOperation","Add member to role in + PIM completed (permanent)") || equalsIgnoreCase("log.azureOperation","Add member to role in PIM completed (timebound)"))) + && exists("dataSource") && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") + && exists("log.azureScope") && !equals("log.azureScope","") && exists("log.azureActorType") && !equals("log.azureActorType","") + && exists("log.azureActor") && !equals("log.azureActor","") + + ' + - delete: + fields: + - azureEnvelope + - azureDecoded + - azureDecodePending + - azureSavedazureProperties + - azureSavedazureKubernetes + - azureSavedazureRequestBody diff --git a/plugins/alerts/azure_contract_test.go b/plugins/alerts/azure_contract_test.go new file mode 100644 index 000000000..c05906d4e --- /dev/null +++ b/plugins/alerts/azure_contract_test.go @@ -0,0 +1,496 @@ +package main + +// Offline Azure extraction model, not the closed EventProcessor. +// Explicit YAML JSON/key sanitization, grok, rename, add and delete steps are modeled. +// CEL and Event serialization use SDK v1.1.31. History requests are tested separately +// with that SDK. External geolocation is mocked only when a fixture declares it. +import ( + "bytes" + "encoding/json" + "fmt" + "net" + "os" + "path/filepath" + "regexp" + "strings" + "testing" + "text/template" + + "github.com/threatwinds/go-sdk/plugins" + "github.com/threatwinds/go-sdk/utils" + "github.com/tidwall/gjson" + "google.golang.org/protobuf/encoding/protojson" +) + +type azureFixture struct { + Name string `json:"name"` + DataSource string `json:"dataSource"` + Raw string `json:"raw"` + Expected map[string]any `json:"expected"` + Absent []string `json:"absent"` + Matches []string `json:"matches"` + Enrichment map[string]any `json:"enrichment"` +} + +func azurePut(m map[string]any, path string, value any, remove bool) { + p := azurePath(path) + for _, k := range p[:len(p)-1] { + n, ok := m[k].(map[string]any) + if !ok { + if remove { + return + } + n = map[string]any{} + m[k] = n + } + m = n + } + if remove { + delete(m, p[len(p)-1]) + } else { + m[p[len(p)-1]] = value + } +} +func azureGet(m map[string]any, p string) (any, bool) { + b, err := json.Marshal(m) + if err != nil { + panic(err) + } + value := gjson.GetBytes(b, p) + return value.Value(), value.Exists() +} +func azurePath(path string) []string { + var out []string + var part strings.Builder + escaped := false + for _, ch := range path { + if escaped { + part.WriteRune(ch) + escaped = false + } else if ch == '\\' { + escaped = true + } else if ch == '.' { + out = append(out, part.String()) + part.Reset() + } else { + part.WriteRune(ch) + } + } + return append(out, part.String()) +} +func azureConfig(t *testing.T) *plugins.Config { + t.Helper() + b, e := utils.ReadPbYaml("../../filters/azure/azure-eventhub.yml") + if e != nil { + t.Fatal(e) + } + c := new(plugins.Config) + if e = protojson.Unmarshal(b, c); e != nil { + t.Fatal(e) + } + return c +} +func azureRegex(t *testing.T, g *plugins.Grok, cfg *plugins.Config) *regexp.Regexp { + t.Helper() + var pattern strings.Builder + for i, p := range g.Patterns { + if p.FieldName != "" { + fmt.Fprintf(&pattern, "(?P%s)", i, p.Pattern) + } else { + pattern.WriteString("(?:" + p.Pattern + ")") + } + } + pats := map[string]string{"greedy": ".*", "data": ".*?", "word": "[A-Za-z0-9_-]+", "space": "\\s+"} + for k, v := range cfg.Patterns { + pats[k] = v + } + tmpl, e := template.New("grok").Option("missingkey=error").Parse(pattern.String()) + if e != nil { + t.Fatal(e) + } + var b bytes.Buffer + if e = tmpl.Execute(&b, pats); e != nil { + t.Fatal(e) + } + r, e := regexp.Compile(b.String()) + if e != nil { + t.Fatal(e) + } + return r +} +func azureParse(t *testing.T, cfg *plugins.Config, raw string, dataSource string, cache *plugins.CELCache, enrichment ...map[string]any) string { + return azureParseMode(t, cfg, raw, dataSource, cache, false, enrichment...) +} + +// Both modes model the unresolved nested-key behavior of the closed JSON step. +func azureParseMode(t *testing.T, cfg *plugins.Config, raw string, dataSource string, cache *plugins.CELCache, preserveNested bool, enrichment ...map[string]any) string { + t.Helper() + draft := map[string]any{"raw": raw, "dataType": "azure", "dataSource": dataSource, "log": map[string]any{}} + for _, stage := range cfg.Pipeline { + matched := false + for _, dataType := range stage.DataTypes { + if dataType == "azure" { + matched = true + } + } + if !matched { + continue + } + for _, s := range stage.Steps { + b, e := protojson.Marshal(s) + if e != nil { + t.Fatal(e) + } + var step map[string]map[string]any + if e = json.Unmarshal(b, &step); e != nil { + t.Fatal(e) + } + for kind, body := range step { + if w, ok := body["where"].(string); ok && w != "" { + snapshot, err := json.Marshal(draft) + if err != nil { + t.Fatal(err) + } + match, e := cache.Eval(w, string(snapshot)) + if e != nil { + t.Fatal(e) + } + if !match { + continue + } + } + switch kind { + case "grok": + g := s.Grok + src := g.Source + if src == "" { + src = "raw" + } + v, ok := azureGet(draft, src) + if !ok { + continue + } + str, ok := v.(string) + if !ok { + t.Fatalf("non-string grok source %s", src) + } + r := azureRegex(t, g, cfg) + m := r.FindStringSubmatch(str) + if m == nil { + continue + } + for i, p := range g.Patterns { + if p.FieldName != "" { + azurePut(draft, p.FieldName, m[r.SubexpIndex(fmt.Sprintf("f%d", i))], false) + } + } + case "rename": + for _, p := range s.Rename.From { + if v, ok := azureGet(draft, p); ok { + azurePut(draft, s.Rename.To, v, false) + azurePut(draft, p, nil, true) + break + } + } + case "add": + if s.Add.Function != "string" { + t.Fatalf("unsupported add function %s", s.Add.Function) + } + azurePut(draft, s.Add.Params["key"].GetStringValue(), s.Add.Params["value"].AsInterface(), false) + case "delete": + for _, p := range s.Delete.Fields { + azurePut(draft, p, nil, true) + } + case "dynamic": + if s.Dynamic.Plugin != "com.utmstack.geolocation" { + t.Fatalf("unsupported dynamic plugin %s", s.Dynamic.Plugin) + } + field := s.Dynamic.Params["source"].GetStringValue() + v, ok := azureGet(draft, field) + if !ok { + t.Fatalf("missing dynamic source %s", field) + } + ip := net.ParseIP(fmt.Sprint(v)) + if ip == nil || ip.IsUnspecified() { + t.Fatalf("invalid address reaches geolocation: %s", field) + } + // The external geolocation service is not executed. Fixtures may explicitly supply its mocked output. + for _, fields := range enrichment { + for path, value := range fields { + if !strings.HasPrefix(path, "origin.geolocation.") { + t.Fatal("unexpected enrichment field") + } + azurePut(draft, path, value, false) + } + } + case "json": + source, ok := azureGet(draft, s.Json.Source) + if !ok { + continue + } + str, ok := source.(string) + if !ok { + t.Fatalf("JSON source is not a string") + } + var parsed map[string]any + if e := json.Unmarshal([]byte(str), &parsed); e != nil { + t.Fatal(e) + } + normalized := azureSanitizeJSON(parsed) + if preserveNested { + normalized = map[string]any{} + for key, value := range parsed { + utils.SanitizeField(&key) + normalized[key] = value + } + } + for key, value := range normalized { + azurePut(draft, "log."+key, value, false) + } + case "cast": + for _, field := range s.Cast.Fields { + if value, ok := azureGet(draft, field); ok { + switch s.Cast.To { + case "string": + azurePut(draft, field, utils.CastString(value), false) + case "float": + azurePut(draft, field, utils.CastFloat64(value), false) + case "int": + azurePut(draft, field, utils.CastInt64(value), false) + default: + t.Fatalf("unsupported cast %s", s.Cast.To) + } + } + } + case "drop": + return "" + default: + t.Fatalf("unsupported filter step %s", kind) + } + } + } + } + b, e := json.Marshal(draft) + if e != nil { + t.Fatal(e) + } + in := string(b) + ev := new(plugins.Event) + // Check before the permissive SDK converter: no scratch or unknown standard field may survive. + if e = protojson.Unmarshal(b, ev); e != nil { + t.Fatalf("strict final Draft schema: %v", e) + } + if e = utils.StringToProtoMessage(&in, ev); e != nil { + t.Fatal(e) + } + out, e := utils.ProtoMessageToString(ev) + if e != nil { + t.Fatal(e) + } + return *out +} +func azureRules(t *testing.T) map[string]*plugins.Rule { + t.Helper() + paths := []string{} + e := filepath.WalkDir("../../rules/cloud/azure", func(path string, d os.DirEntry, err error) error { + if err != nil { + return err + } + if !d.IsDir() && (strings.HasSuffix(path, ".yml") || strings.HasSuffix(path, ".yaml")) { + paths = append(paths, path) + } + return nil + }) + if e != nil { + t.Fatal(e) + } + + out := map[string]*plugins.Rule{} + for _, p := range paths { + b, e := utils.ReadPbYaml(p) + if e != nil { + t.Fatal(e) + } + r := new(plugins.Rule) + if e = protojson.Unmarshal(b, r); e != nil { + t.Fatal(e) + } + r.Normalize() + out[strings.TrimSuffix(filepath.Base(p), filepath.Ext(p))] = r + } + return out +} + +func azureSanitizeJSON(input map[string]any) map[string]any { + var walk func(any) any + walk = func(input any) any { + switch v := input.(type) { + case map[string]any: + out := map[string]any{} + for key, value := range v { + utils.SanitizeField(&key) + out[key] = walk(value) + } + return out + case []any: + out := make([]any, len(v)) + for i, x := range v { + out[i] = walk(x) + } + return out + default: + return input + } + } + return walk(input).(map[string]any) +} + +func azureFixtures(t *testing.T) []azureFixture { + t.Helper() + b, e := os.ReadFile("testdata/azure_raw.json") + if e != nil { + t.Fatal(e) + } + var cases []azureFixture + if e = json.Unmarshal(b, &cases); e != nil { + t.Fatal(e) + } + return cases +} +func azureAssert(t *testing.T, f azureFixture, out string, rules map[string]*plugins.Rule, cache *plugins.CELCache) map[string]bool { + t.Helper() + for field, want := range f.Expected { + got := gjson.Get(out, field) + b, _ := json.Marshal(want) + if !got.Exists() || got.Raw != string(b) { + if fmt.Sprint(got.Value()) != fmt.Sprint(want) { + t.Errorf("%s got %v want %v", field, got.Value(), want) + } + } + } + for _, field := range f.Absent { + if gjson.Get(out, field).Exists() { + t.Errorf("unexpected %s", field) + } + } + if gjson.Get(out, "raw").String() != f.Raw { + t.Error("raw altered") + } + expected := map[string]bool{} + for _, name := range f.Matches { + expected[name] = true + } + actual := map[string]bool{} + for name, r := range rules { + yes, e := cache.Eval(r.Where, out) + if e != nil { + t.Fatalf("%s: %v", name, e) + } + if yes { + actual[name] = true + } + if yes != expected[name] { + t.Errorf("%s matched %v want %v", name, yes, expected[name]) + } + if yes { + for _, search := range r.Correlation { + for _, term := range search.With { + v := term.Value.GetStringValue() + if strings.HasPrefix(v, "{{.") { + path := strings.TrimSuffix(strings.TrimPrefix(v, "{{."), "}}") + if !gjson.Get(out, path).Exists() { + t.Errorf("%s unresolved %s", name, path) + } + } + } + } + ev := new(plugins.Event) + if e := utils.StringToProtoMessage(&out, ev); e != nil { + t.Fatal(e) + } + if r.Adversary != "origin" { + t.Errorf("unexpected actor direction %s", r.Adversary) + } + alert := &plugins.Alert{Adversary: ev.Origin, Target: ev.Target, Events: []*plugins.Event{ev}} + wire, e := utils.ProtoMessageToString(alert) + if e != nil { + t.Fatal(e) + } + if gjson.Get(*wire, "adversary.ip").String() != gjson.Get(out, "origin.ip").String() { + t.Error("actor IP lost") + } + } + } + return actual +} +func TestAzureRawContracts(t *testing.T) { + cfg, rules, cache := azureConfig(t), azureRules(t), plugins.NewCELCache("azure-raw") + if len(rules) != 40 { + t.Fatalf("rules %d", len(rules)) + } + positive, negative := map[string]int{}, map[string]int{} + for _, f := range azureFixtures(t) { + for _, preserve := range []bool{false, true} { + t.Run(fmt.Sprintf("%s/nested-preserved-%v", f.Name, preserve), func(t *testing.T) { + out := azureParseMode(t, cfg, f.Raw, f.DataSource, cache, preserve, f.Enrichment) + actual := azureAssert(t, f, out, rules, cache) + for name := range rules { + if actual[name] { + positive[name]++ + } else { + negative[name]++ + } + } + }) + } + } + b, e := os.ReadFile("testdata/azure_changed_rules.json") + if e != nil { + t.Fatal(e) + } + var changed []string + if e = json.Unmarshal(b, &changed); e != nil { + t.Fatal(e) + } + for _, name := range changed { + if positive[name] == 0 || negative[name] == 0 { + t.Errorf("%s missing positive/negative raw fixture", name) + } + } +} +func TestAzurePrivateRecords(t *testing.T) { + path := os.Getenv("AZURE_PRIVATE_FIXTURES") + if path == "" { + t.Skip("private native record expectations not supplied") + } + b, e := os.ReadFile(path) + if e != nil { + t.Fatal(e) + } + var cases []azureFixture + if e = json.Unmarshal(b, &cases); e != nil { + t.Fatal(e) + } + cfg, rules, cache := azureConfig(t), azureRules(t), plugins.NewCELCache("azure-private") + var outputs []map[string]any + for _, f := range cases { + t.Run(f.Name, func(t *testing.T) { + out := azureParseMode(t, cfg, f.Raw, f.DataSource, cache, true) + azureAssert(t, f, out, rules, cache) + var v map[string]any + if e = json.Unmarshal([]byte(out), &v); e != nil { + t.Fatal(e) + } + outputs = append(outputs, map[string]any{"id": f.Name, "parsed": v}) + }) + } + if path := os.Getenv("AZURE_PRIVATE_OUTPUT"); path != "" { + b, e := json.MarshalIndent(outputs, "", " ") + if e != nil { + t.Fatal(e) + } + if e = os.WriteFile(path, b, 0600); e != nil { + t.Fatal(e) + } + } +} diff --git a/plugins/alerts/azure_history_test.go b/plugins/alerts/azure_history_test.go new file mode 100644 index 000000000..4f0f44a41 --- /dev/null +++ b/plugins/alerts/azure_history_test.go @@ -0,0 +1,290 @@ +package main + +// Offline history requests use the real SDK and an isolated loopback mock. +// The mock evaluates only the term/not-term/time clauses asserted below. +import ( + "encoding/json" + "fmt" + sdkos "github.com/threatwinds/go-sdk/os" + "github.com/threatwinds/go-sdk/plugins" + "github.com/tidwall/gjson" + "io" + "net/http" + "net/http/httptest" + "os" + "os/exec" + "strings" + "testing" + "time" +) + +func TestAzureSDKHistory(t *testing.T) { + if os.Getenv("UTM_Azure_HISTORY_CHILD") != "1" { + c := exec.Command(os.Args[0], "-test.run=^TestAzureSDKHistory$") + c.Env = append(os.Environ(), "UTM_Azure_HISTORY_CHILD=1") + if b, e := c.CombinedOutput(); e != nil { + t.Fatalf("isolated history: %v\n%s", e, b) + } + return + } + cfg, rules, cache := azureConfig(t), azureRules(t), plugins.NewCELCache("azure-history") + var history []string + var terms, notTerms map[string]string + var window time.Duration + queries := 0 + mapping := map[string]any{"properties": map[string]any{}} + props := mapping["properties"].(map[string]any) + paths := []string{"dataSource", "log.azureScopeType", "log.azureScope", "log.azureActorType", "log.azureActor", "origin.ip"} + for name, r := range rules { + if len(r.Correlation) > 0 { + paths = append(paths, "log.correlationCandidate."+name) + } + } + for _, path := range paths { + node := props + parts := strings.Split(path, ".") + for _, part := range parts[:len(parts)-1] { + if node[part] == nil { + node[part] = map[string]any{"properties": map[string]any{}} + } + node = node[part].(map[string]any)["properties"].(map[string]any) + } + node[parts[len(parts)-1]] = map[string]any{"type": "text", "fields": map[string]any{"keyword": map[string]any{"type": "keyword"}}} + } + props["@timestamp"] = map[string]any{"type": "date"} + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if strings.HasSuffix(r.URL.Path, "/_mapping") { + _ = json.NewEncoder(w).Encode(map[string]any{"v11-log-azure-test": map[string]any{"mappings": mapping}}) + return + } + if r.URL.Path != "/v11-log-azure-*/_search" { + t.Errorf("unexpected request %s", r.URL.Path) + http.Error(w, "bad request", 400) + return + } + queries++ + body, e := io.ReadAll(r.Body) + if e != nil { + t.Error(e) + return + } + q := string(body) + clauses := append(gjson.Get(q, "query.bool.filter").Array(), gjson.Get(q, "query.bool.must").Array()...) + negatives := gjson.Get(q, "query.bool.must_not").Array() + gotTerms := map[string]string{} + gotNot := map[string]string{} + cutoff := time.Time{} + for _, clause := range clauses { + if term := clause.Get("term"); term.Exists() { + for field, value := range term.Map() { + gotTerms[strings.TrimSuffix(field, ".keyword")] = value.Get("value").String() + } + } else if span := clause.Get("range"); span.Exists() { + cutoff, e = time.Parse(time.RFC3339Nano, span.Get("@timestamp.gte").String()) + if e != nil { + t.Error(e) + } + } else { + t.Errorf("unsupported clause %s", clause.Raw) + } + } + for _, clause := range negatives { + if nested := clause.Get("bool.must"); nested.Exists() { + if len(nested.Array()) != 1 { + t.Error("unexpected negative bool") + } + clause = nested.Array()[0] + } + if term := clause.Get("term"); term.Exists() { + for field, value := range term.Map() { + gotNot[strings.TrimSuffix(field, ".keyword")] = value.Get("value").String() + } + } else { + t.Errorf("unsupported negative %s", clause.Raw) + } + } + same := func(a, b map[string]string) bool { + if len(a) != len(b) { + return false + } + for k, v := range a { + if b[k] != v { + return false + } + } + return true + } + expectedTerms := map[string]string{} + for k, v := range terms { + expectedTerms[k] = v + } + + if !same(gotTerms, expectedTerms) || !same(gotNot, notTerms) { + t.Errorf("scope mismatch: terms=%v negatives=%v", gotTerms, gotNot) + } + if delta := time.Since(cutoff) - window; delta < -2*time.Second || delta > 2*time.Second { + t.Errorf("unexpected time cutoff %v", delta) + } + hits := []map[string]any{} + for _, doc := range history { + match := true + for f, v := range gotTerms { + if !gjson.Get(doc, f).Exists() || gjson.Get(doc, f).String() != v { + match = false + } + } + for f, v := range gotNot { + if gjson.Get(doc, f).String() == v { + match = false + } + } + stamp, e := time.Parse(time.RFC3339Nano, gjson.Get(doc, "@timestamp").String()) + if e != nil || stamp.Before(cutoff) { + match = false + } + if match { + hits = append(hits, map[string]any{"_id": fmt.Sprint(len(hits)), "_index": "v11-log-azure-test", "_source": map[string]any{}}) + } + } + _ = json.NewEncoder(w).Encode(map[string]any{"took": 1, "hits": map[string]any{"total": map[string]any{"value": len(hits), "relation": "eq"}, "hits": hits}}) + })) + defer server.Close() + if e := sdkos.Connect([]string{server.URL}, "", ""); e != nil { + t.Fatal(e) + } + mutate := func(doc, path string, value any) string { + var m map[string]any + if e := json.Unmarshal([]byte(doc), &m); e != nil { + t.Fatal(e) + } + azurePut(m, path, value, value == nil) + b, e := json.Marshal(m) + if e != nil { + t.Fatal(e) + } + return string(b) + } + + cases := []struct { + rule, fixture, within string + count uint64 + ip bool + }{ + {"aks_security_threats", "activity-success-0", "30m", 10, false}, + {"app_registration_abuse", "audit-success-0", "1h", 3, false}, + {"application_gateway_waf_alerts", "waf-blocked", "10m", 5, true}, + {"azure_ad_password_spray", "signin-invalid-password", "15m", 15, true}, + {"azure_bulk_role_changes", "audit-success-7", "30m", 10, false}, + {"azure_kubernetes_secret_access", "k8s-secrets", "30m", 5, false}, + {"azure_laps_credential_dump", "audit-success-10", "1h", 3, false}, + {"azure_ropc_authentication", "signin-ropc", "1h", 5, false}, + {"key_vault_access_spikes", "vault-SecretGet", "10m", 20, false}, + {"pim_role_activation_abuse", "audit-success-9", "4h", 3, false}, + } + fixtures := map[string]azureFixture{} + for _, f := range azureFixtures(t) { + fixtures[f.Name] = f + } + for _, tc := range cases { + t.Run(tc.rule, func(t *testing.T) { + r := rules[tc.rule] + if r == nil || len(r.Correlation) != 1 { + t.Fatal("missing or extra history") + } + search := r.Correlation[0] + if search.Count != tc.count || search.Within != tc.within || len(search.Or) != 0 { + t.Fatal("threshold/window changed") + } + var e error + window, e = time.ParseDuration(tc.within) + if e != nil { + t.Fatal(e) + } + f := fixtures[tc.fixture] + out := azureParse(t, cfg, f.Raw, f.DataSource, cache) + if yes, e := cache.Eval(r.Where, out); e != nil || !yes { + t.Fatalf("raw trigger failed: %v %v", yes, e) + } + marker := "log.correlationCandidate." + tc.rule + terms = map[string]string{"dataSource": "collector-test", "log.azureScopeType": "directory", "log.azureScope": "directory-test", marker: "true"} + notTerms = map[string]string{} + if tc.rule == "azure_kubernetes_secret_access" || tc.rule == "application_gateway_waf_alerts" { + terms["log.azureScopeType"] = "resource" + terms["log.azureScope"] = "/subscriptions/sub-test/providers/Microsoft.Example/resources/test" + } + if tc.rule == "key_vault_access_spikes" { + terms["log.azureScopeType"] = "resource" + terms["log.azureScope"] = "/subscriptions/sub-test/providers/Microsoft.KeyVault/vaults/vault-test" + } + if tc.ip { + terms["origin.ip"] = "198.51.100.4" + } else { + terms["log.azureActorType"] = "user" + terms["log.azureActor"] = "admin@example.test" + switch tc.rule { + case "aks_security_threats", "key_vault_access_spikes": + terms["log.azureActorType"] = "ip" + terms["log.azureActor"] = "198.51.100.4" + case "azure_kubernetes_secret_access": + terms["log.azureActor"] = "actor-test" + case "azure_ropc_authentication": + terms["log.azureActor"] = "actor@example.test" + } + } + prior := mutate(out, "@timestamp", time.Now().Add(-time.Minute).UTC().Format(time.RFC3339Nano)) + check := func(name, doc string, count uint64, want bool) { + t.Run(name, func(t *testing.T) { + history = nil + for i := uint64(0); i < count; i++ { + history = append(history, doc) + } + yes, _, e := search.Execute(&out) + if e != nil || yes != want { + t.Fatalf("history %v want %v: %v", yes, want, e) + } + }) + } + check("below_threshold", prior, tc.count-1, false) + check("at_threshold", prior, tc.count, true) + check("expired", mutate(prior, "@timestamp", time.Now().Add(-window-time.Minute).UTC().Format(time.RFC3339Nano)), tc.count, false) + check("inside_window", mutate(prior, "@timestamp", time.Now().Add(-window+time.Minute).UTC().Format(time.RFC3339Nano)), tc.count, true) + for field := range terms { + if field == marker { + continue + } + check("different_"+field, mutate(prior, field, "other"), tc.count, false) + without := mutate(out, field, nil) + if yes, e := cache.Eval(r.Where, without); e != nil || yes { + t.Errorf("predicate accepts missing identity %s: %v", field, e) + } + before := queries + if _, _, e := search.Execute(&without); e == nil { + t.Errorf("missing placeholder %s accepted", field) + } + if queries != before { + t.Error("missing placeholder executes query") + } + } + check("unrelated_population", mutate(prior, marker, nil), tc.count, false) + // Source-derived marker must not be inherited from raw input, even when all identities match. + var raw map[string]any + if e := json.Unmarshal([]byte(f.Raw), &raw); e != nil { + t.Fatal(e) + } + raw["correlationCandidate"] = map[string]any{tc.rule: "true"} + raw["category"] = "AppServiceConsoleLogs" + raw["operationName"] = "Microsoft.Web/sites/log" + delete(raw, "properties") + bytes, _ := json.Marshal(raw) + unrelated := azureParse(t, cfg, string(bytes), f.DataSource, cache) + if gjson.Get(unrelated, marker).Exists() { + t.Error("forged history marker survived") + } + check("unrelated_raw_activity", mutate(unrelated, "@timestamp", time.Now().UTC().Format(time.RFC3339Nano)), tc.count, false) + }) + } + if len(cases) != 10 { + t.Fatal("history coverage") + } +} diff --git a/plugins/alerts/testdata/azure_changed_rules.json b/plugins/alerts/testdata/azure_changed_rules.json new file mode 100644 index 000000000..9316c5f8c --- /dev/null +++ b/plugins/alerts/testdata/azure_changed_rules.json @@ -0,0 +1,42 @@ +[ + "aks_security_threats", + "app_registration_abuse", + "application_gateway_waf_alerts", + "automation_runbook_abuse", + "azure_ad_impossible_travel", + "azure_ad_password_spray", + "azure_alert_suppression_rule", + "azure_anomalous_token", + "azure_app_credential_added", + "azure_app_privileged_permissions", + "azure_azurehound_discovery", + "azure_bulk_role_changes", + "azure_device_code_auth_abuse", + "azure_disk_snapshot_exfiltration", + "azure_federation_modified", + "azure_impossible_travel", + "azure_kubernetes_admission_controller", + "azure_kubernetes_events_deleted", + "azure_kubernetes_secret_access", + "azure_laps_credential_dump", + "azure_leaked_credentials", + "azure_new_root_ca_added", + "azure_password_spray_detected", + "azure_prt_access_attempt", + "azure_ropc_authentication", + "azure_sentinel_alert_patterns", + "azure_subscription_permission_elevation", + "azure_temporary_access_pass", + "credential_access_key_vault_modified", + "defender_cloud_critical_alerts", + "defense_evasion_azure_diagnostic_settings_deletion", + "defense_evasion_event_hub_deletion", + "initial_access_azure_active_directory_high_risk_signin", + "initial_access_consent_grant_attack_via_azure_registered_application", + "key_vault_access_spikes", + "persistence_azure_pim_user_added_global_admin", + "persistence_mfa_disabled_for_azure_user", + "pim_role_activation_abuse", + "storage_account_public_access", + "subscription_ownership_transfer" +] \ No newline at end of file diff --git a/plugins/alerts/testdata/azure_raw.json b/plugins/alerts/testdata/azure_raw.json new file mode 100644 index 000000000..6c7cfe770 --- /dev/null +++ b/plugins/alerts/testdata/azure_raw.json @@ -0,0 +1,1992 @@ +[ + { + "name": "activity-success-0", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.ContainerService/managedClusters/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": { + "action": "Microsoft.ContainerService/managedClusters/write", + "actionResult": "success", + "origin.ip": "198.51.100.4" + }, + "absent": [], + "matches": [ + "aks_security_threats" + ] + }, + { + "name": "activity-Failed-0", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.ContainerService/managedClusters/write\",\"resultType\":\"Failed\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "activity-Started-0", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.ContainerService/managedClusters/write\",\"resultType\":\"Started\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "activity-Accepted-0", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.ContainerService/managedClusters/write\",\"resultType\":\"Accepted\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "activity-success-1", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Automation/automationAccounts/runbooks/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": { + "action": "Microsoft.Automation/automationAccounts/runbooks/write", + "actionResult": "success", + "origin.ip": "198.51.100.4" + }, + "absent": [], + "matches": [ + "automation_runbook_abuse" + ] + }, + { + "name": "activity-Failed-1", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Automation/automationAccounts/runbooks/write\",\"resultType\":\"Failed\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "activity-Started-1", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Automation/automationAccounts/runbooks/write\",\"resultType\":\"Started\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "activity-Accepted-1", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Automation/automationAccounts/runbooks/write\",\"resultType\":\"Accepted\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "activity-success-2", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Compute/disks/beginGetAccess/action\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": { + "action": "Microsoft.Compute/disks/beginGetAccess/action", + "actionResult": "success", + "origin.ip": "198.51.100.4" + }, + "absent": [], + "matches": [ + "azure_disk_snapshot_exfiltration" + ] + }, + { + "name": "activity-Failed-2", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Compute/disks/beginGetAccess/action\",\"resultType\":\"Failed\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "activity-Started-2", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Compute/disks/beginGetAccess/action\",\"resultType\":\"Started\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "activity-Accepted-2", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Compute/disks/beginGetAccess/action\",\"resultType\":\"Accepted\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "activity-success-3", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Authorization/elevateAccess/action\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": { + "action": "Microsoft.Authorization/elevateAccess/action", + "actionResult": "success", + "origin.ip": "198.51.100.4" + }, + "absent": [], + "matches": [ + "azure_subscription_permission_elevation" + ] + }, + { + "name": "activity-Failed-3", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Authorization/elevateAccess/action\",\"resultType\":\"Failed\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "activity-Started-3", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Authorization/elevateAccess/action\",\"resultType\":\"Started\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "activity-Accepted-3", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Authorization/elevateAccess/action\",\"resultType\":\"Accepted\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "activity-success-4", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Security/alertsSuppressionRules/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": { + "action": "Microsoft.Security/alertsSuppressionRules/write", + "actionResult": "success", + "origin.ip": "198.51.100.4" + }, + "absent": [], + "matches": [ + "azure_alert_suppression_rule" + ] + }, + { + "name": "activity-Failed-4", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Security/alertsSuppressionRules/write\",\"resultType\":\"Failed\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "activity-Started-4", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Security/alertsSuppressionRules/write\",\"resultType\":\"Started\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "activity-Accepted-4", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Security/alertsSuppressionRules/write\",\"resultType\":\"Accepted\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "activity-success-5", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.KeyVault/vaults/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": { + "action": "Microsoft.KeyVault/vaults/write", + "actionResult": "success", + "origin.ip": "198.51.100.4" + }, + "absent": [], + "matches": [ + "credential_access_key_vault_modified" + ] + }, + { + "name": "activity-Failed-5", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.KeyVault/vaults/write\",\"resultType\":\"Failed\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "activity-Started-5", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.KeyVault/vaults/write\",\"resultType\":\"Started\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "activity-Accepted-5", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.KeyVault/vaults/write\",\"resultType\":\"Accepted\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "activity-success-6", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Insights/diagnosticSettings/delete\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": { + "action": "Microsoft.Insights/diagnosticSettings/delete", + "actionResult": "success", + "origin.ip": "198.51.100.4" + }, + "absent": [], + "matches": [ + "defense_evasion_azure_diagnostic_settings_deletion" + ] + }, + { + "name": "activity-Failed-6", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Insights/diagnosticSettings/delete\",\"resultType\":\"Failed\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "activity-Started-6", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Insights/diagnosticSettings/delete\",\"resultType\":\"Started\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "activity-Accepted-6", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Insights/diagnosticSettings/delete\",\"resultType\":\"Accepted\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "activity-success-7", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.EventHub/namespaces/eventhubs/delete\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": { + "action": "Microsoft.EventHub/namespaces/eventhubs/delete", + "actionResult": "success", + "origin.ip": "198.51.100.4" + }, + "absent": [], + "matches": [ + "defense_evasion_event_hub_deletion" + ] + }, + { + "name": "activity-Failed-7", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.EventHub/namespaces/eventhubs/delete\",\"resultType\":\"Failed\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "activity-Started-7", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.EventHub/namespaces/eventhubs/delete\",\"resultType\":\"Started\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "activity-Accepted-7", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.EventHub/namespaces/eventhubs/delete\",\"resultType\":\"Accepted\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "eventgrid-eventType", + "dataSource": "collector-test", + "raw": "{\"eventTime\":\"2026-09-17T12:00:00Z\",\"eventType\":\"Microsoft.Resources.ResourceDeleteSuccess\",\"data\":{\"operationName\":\"Microsoft.Insights/diagnosticSettings/delete\",\"status\":\"Succeeded\",\"tenantId\":\"directory-test\",\"resourceUri\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"httpRequest\":{\"clientIpAddress\":\"2001:db8::4\",\"url\":\"https://management.azure.com/resource-test\"},\"claims\":{\"http://schemas.microsoft.com/identity/claims/objectidentifier\":\"principal-test\"},\"authorization\":{\"evidence\":{\"role\":\"Owner\",\"principalId\":\"different-role-assignee\"}}}}", + "expected": { + "origin.ip": "2001:db8::4", + "origin.user": "principal-test", + "origin.group": "Owner", + "deviceTime": "2026-09-17T12:00:00Z", + "actionResult": "success" + }, + "absent": [], + "matches": [ + "defense_evasion_azure_diagnostic_settings_deletion" + ] + }, + { + "name": "eventgrid-eventType-Failure", + "dataSource": "collector-test", + "raw": "{\"eventTime\":\"2026-09-17T12:00:00Z\",\"eventType\":\"Microsoft.Resources.ResourceDeleteFailure\",\"data\":{\"operationName\":\"Microsoft.Insights/diagnosticSettings/delete\",\"status\":\"Failed\",\"tenantId\":\"directory-test\",\"resourceUri\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"httpRequest\":{\"clientIpAddress\":\"2001:db8::4\",\"url\":\"https://management.azure.com/resource-test\"},\"claims\":{\"http://schemas.microsoft.com/identity/claims/objectidentifier\":\"principal-test\"},\"authorization\":{\"evidence\":{\"role\":\"Owner\",\"principalId\":\"different-role-assignee\"}}}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "eventgrid-eventType-Cancel", + "dataSource": "collector-test", + "raw": "{\"eventTime\":\"2026-09-17T12:00:00Z\",\"eventType\":\"Microsoft.Resources.ResourceDeleteCancel\",\"data\":{\"operationName\":\"Microsoft.Insights/diagnosticSettings/delete\",\"status\":\"Canceled\",\"tenantId\":\"directory-test\",\"resourceUri\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"httpRequest\":{\"clientIpAddress\":\"2001:db8::4\",\"url\":\"https://management.azure.com/resource-test\"},\"claims\":{\"http://schemas.microsoft.com/identity/claims/objectidentifier\":\"principal-test\"},\"authorization\":{\"evidence\":{\"role\":\"Owner\",\"principalId\":\"different-role-assignee\"}}}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "eventgrid-type", + "dataSource": "collector-test", + "raw": "{\"type\":\"Microsoft.Resources.ResourceDeleteSuccess\",\"data\":{\"operationName\":\"Microsoft.Insights/diagnosticSettings/delete\",\"status\":\"Succeeded\",\"tenantId\":\"directory-test\",\"resourceUri\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"httpRequest\":{\"clientIpAddress\":\"2001:db8::4\",\"url\":\"https://management.azure.com/resource-test\"},\"claims\":{\"http://schemas.microsoft.com/identity/claims/objectidentifier\":\"principal-test\"},\"authorization\":{\"evidence\":{\"role\":\"Owner\",\"principalId\":\"different-role-assignee\"}}},\"time\":\"2026-09-17T12:00:00Z\"}", + "expected": { + "origin.ip": "2001:db8::4", + "origin.user": "principal-test", + "origin.group": "Owner", + "deviceTime": "2026-09-17T12:00:00Z", + "actionResult": "success" + }, + "absent": [], + "matches": [ + "defense_evasion_azure_diagnostic_settings_deletion" + ] + }, + { + "name": "eventgrid-type-Failure", + "dataSource": "collector-test", + "raw": "{\"type\":\"Microsoft.Resources.ResourceDeleteFailure\",\"data\":{\"operationName\":\"Microsoft.Insights/diagnosticSettings/delete\",\"status\":\"Failed\",\"tenantId\":\"directory-test\",\"resourceUri\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"httpRequest\":{\"clientIpAddress\":\"2001:db8::4\",\"url\":\"https://management.azure.com/resource-test\"},\"claims\":{\"http://schemas.microsoft.com/identity/claims/objectidentifier\":\"principal-test\"},\"authorization\":{\"evidence\":{\"role\":\"Owner\",\"principalId\":\"different-role-assignee\"}}},\"time\":\"2026-09-17T12:00:00Z\"}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "eventgrid-type-Cancel", + "dataSource": "collector-test", + "raw": "{\"type\":\"Microsoft.Resources.ResourceDeleteCancel\",\"data\":{\"operationName\":\"Microsoft.Insights/diagnosticSettings/delete\",\"status\":\"Canceled\",\"tenantId\":\"directory-test\",\"resourceUri\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"httpRequest\":{\"clientIpAddress\":\"2001:db8::4\",\"url\":\"https://management.azure.com/resource-test\"},\"claims\":{\"http://schemas.microsoft.com/identity/claims/objectidentifier\":\"principal-test\"},\"authorization\":{\"evidence\":{\"role\":\"Owner\",\"principalId\":\"different-role-assignee\"}}},\"time\":\"2026-09-17T12:00:00Z\"}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "audit-success-0", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add application\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "origin.user": "admin@example.test", + "actionResult": "success" + }, + "absent": [], + "matches": [ + "app_registration_abuse" + ] + }, + { + "name": "audit-failure-0", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add application\",\"properties\":{\"result\":\"failure\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "audit-success-1", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add service principal\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "origin.user": "admin@example.test", + "actionResult": "success" + }, + "absent": [], + "matches": [ + "app_registration_abuse" + ] + }, + { + "name": "audit-failure-1", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add service principal\",\"properties\":{\"result\":\"failure\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "audit-success-2", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Consent to application\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "origin.user": "admin@example.test", + "actionResult": "success" + }, + "absent": [], + "matches": [ + "app_registration_abuse", + "initial_access_consent_grant_attack_via_azure_registered_application" + ] + }, + { + "name": "audit-failure-2", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Consent to application\",\"properties\":{\"result\":\"failure\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "audit-success-3", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add service principal credentials\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "origin.user": "admin@example.test", + "actionResult": "success" + }, + "absent": [], + "matches": [ + "azure_app_credential_added" + ] + }, + { + "name": "audit-failure-3", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add service principal credentials\",\"properties\":{\"result\":\"failure\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "audit-success-4", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Update application - Certificates and secrets management\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "origin.user": "admin@example.test", + "actionResult": "success" + }, + "absent": [], + "matches": [ + "azure_app_credential_added" + ] + }, + { + "name": "audit-failure-4", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Update application - Certificates and secrets management\",\"properties\":{\"result\":\"failure\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "audit-success-5", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add app role assignment to service principal\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "origin.user": "admin@example.test", + "actionResult": "success" + }, + "absent": [], + "matches": [ + "azure_app_privileged_permissions" + ] + }, + { + "name": "audit-failure-5", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add app role assignment to service principal\",\"properties\":{\"result\":\"failure\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "audit-success-6", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Set federation settings on domain\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "origin.user": "admin@example.test", + "actionResult": "success" + }, + "absent": [], + "matches": [ + "azure_federation_modified" + ] + }, + { + "name": "audit-failure-6", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Set federation settings on domain\",\"properties\":{\"result\":\"failure\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "audit-success-7", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add member to role\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "origin.user": "admin@example.test", + "actionResult": "success" + }, + "absent": [], + "matches": [ + "azure_bulk_role_changes" + ] + }, + { + "name": "audit-failure-7", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add member to role\",\"properties\":{\"result\":\"failure\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "audit-success-8", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add member to role\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"Role\",\"displayName\":\"Global Administrator\"}]}}", + "expected": { + "origin.user": "admin@example.test", + "actionResult": "success" + }, + "absent": [], + "matches": [ + "azure_bulk_role_changes", + "persistence_azure_pim_user_added_global_admin" + ] + }, + { + "name": "audit-failure-8", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add member to role\",\"properties\":{\"result\":\"failure\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"Role\",\"displayName\":\"Global Administrator\"}]}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "audit-success-9", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add member to role completed (PIM activation)\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "origin.user": "admin@example.test", + "actionResult": "success" + }, + "absent": [], + "matches": [ + "azure_bulk_role_changes", + "pim_role_activation_abuse" + ] + }, + { + "name": "audit-failure-9", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add member to role completed (PIM activation)\",\"properties\":{\"result\":\"failure\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "audit-success-10", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Recover device local administrator password\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "origin.user": "admin@example.test", + "actionResult": "success" + }, + "absent": [], + "matches": [ + "azure_laps_credential_dump" + ] + }, + { + "name": "audit-failure-10", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Recover device local administrator password\",\"properties\":{\"result\":\"failure\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "audit-success-11", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Disable Strong Authentication\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "origin.user": "admin@example.test", + "actionResult": "success" + }, + "absent": [], + "matches": [ + "persistence_mfa_disabled_for_azure_user" + ] + }, + { + "name": "audit-failure-11", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Disable Strong Authentication\",\"properties\":{\"result\":\"failure\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "audit-app-identity", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add application\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"app\":{\"servicePrincipalId\":\"application-principal-test\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "origin.user": "application-principal-test" + }, + "absent": [ + "origin.ip" + ], + "matches": [ + "app_registration_abuse" + ] + }, + { + "name": "audit-unrelated-user-update", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Update user\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"modifiedProperties\":[{\"displayName\":\"StrongAuthenticationPhoneAppDetail\",\"newValue\":\"updated\"}]}]}}", + "expected": { + "actionResult": "success" + }, + "absent": [], + "matches": [] + }, + { + "name": "audit-unrelated-app-update", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Update application\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "audit-unrelated-domain-add", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add verified domain\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "audit-no-result", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add application\",\"properties\":{\"result\":null,\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "audit-key-property", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Update application\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"Application\",\"modifiedProperties\":[{\"displayName\":\"KeyCredentials\",\"newValue\":\"[new-key]\"}]}]}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_app_credential_added" + ] + }, + { + "name": "signin-success", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[]}}", + "expected": { + "origin.user": "actor@example.test", + "origin.ip": "198.51.100.4", + "actionResult": "success" + }, + "absent": [], + "matches": [] + }, + { + "name": "signin-highrisk", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":0},\"riskState\":\"atRisk\",\"riskLevelDuringSignIn\":\"high\",\"riskEventTypes_v2\":[]}}", + "expected": {}, + "absent": [], + "matches": [ + "initial_access_azure_active_directory_high_risk_signin" + ] + }, + { + "name": "signin-invalid-password", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"50126\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":50126},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[]}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [ + "azure_ad_password_spray" + ] + }, + { + "name": "signin-non-password-50053", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"50053\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":50053},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[]}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "signin-non-password-50057", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"50057\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":50057},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[]}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [] + }, + { + "name": "signin-non-password-53003", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"53003\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":53003},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[]}}", + "expected": { + "actionResult": "denied" + }, + "absent": [], + "matches": [] + }, + { + "name": "signin-conflicting-status", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":50126},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[]}}", + "expected": { + "actionResult": "failure" + }, + "absent": [], + "matches": [ + "azure_ad_password_spray" + ] + }, + { + "name": "signin-deviceCode", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[],\"authenticationProtocol\":\"deviceCode\"}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "signin-risky-deviceCode", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":0},\"riskState\":\"atRisk\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[],\"authenticationProtocol\":\"deviceCode\"}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_ad_impossible_travel", + "azure_device_code_auth_abuse" + ] + }, + { + "name": "signin-ropc", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[],\"authenticationProtocol\":\"ropc\"}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_ropc_authentication" + ] + }, + { + "name": "signin-risky-ropc", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":0},\"riskState\":\"atRisk\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[],\"authenticationProtocol\":\"ropc\"}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_ad_impossible_travel", + "azure_ropc_authentication" + ] + }, + { + "name": "signin-prt-routine", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[],\"incomingTokenType\":\"primaryRefreshToken\"}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "signin-prt-risky", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":0},\"riskState\":\"atRisk\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[],\"incomingTokenType\":\"primaryRefreshToken\"}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_ad_impossible_travel", + "azure_prt_access_attempt" + ] + }, + { + "name": "signin-azurehound", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[],\"userAgent\":\"AzureHound/v2\"}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_azurehound_discovery" + ] + }, + { + "name": "signin-azurehound-substring", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[],\"userAgent\":\"notazurehoundish\"}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "signin-risk-array-anomalousToken", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[\"anomalousToken\"]}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_anomalous_token" + ] + }, + { + "name": "risk-detection-anomalousToken", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"UserRiskEvents\",\"operationName\":\"User Risk Detection\",\"properties\":{\"riskEventType\":\"anomalousToken\",\"riskState\":\"atRisk\",\"ipAddress\":\"198.51.100.4\",\"userPrincipalName\":\"actor@example.test\"}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_anomalous_token" + ] + }, + { + "name": "risk-remediated-anomalousToken", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"UserRiskEvents\",\"properties\":{\"riskEventType\":\"anomalousToken\",\"riskState\":\"remediated\"}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "signin-risk-array-unlikelyTravel", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[\"unlikelyTravel\"]}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_impossible_travel" + ] + }, + { + "name": "risk-detection-unlikelyTravel", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"UserRiskEvents\",\"operationName\":\"User Risk Detection\",\"properties\":{\"riskEventType\":\"unlikelyTravel\",\"riskState\":\"atRisk\",\"ipAddress\":\"198.51.100.4\",\"userPrincipalName\":\"actor@example.test\"}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_impossible_travel" + ] + }, + { + "name": "risk-remediated-unlikelyTravel", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"UserRiskEvents\",\"properties\":{\"riskEventType\":\"unlikelyTravel\",\"riskState\":\"remediated\"}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "signin-risk-array-impossibleTravel", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[\"impossibleTravel\"]}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_impossible_travel" + ] + }, + { + "name": "risk-detection-impossibleTravel", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"UserRiskEvents\",\"operationName\":\"User Risk Detection\",\"properties\":{\"riskEventType\":\"impossibleTravel\",\"riskState\":\"atRisk\",\"ipAddress\":\"198.51.100.4\",\"userPrincipalName\":\"actor@example.test\"}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_impossible_travel" + ] + }, + { + "name": "risk-remediated-impossibleTravel", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"UserRiskEvents\",\"properties\":{\"riskEventType\":\"impossibleTravel\",\"riskState\":\"remediated\"}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "signin-risk-array-leakedCredentials", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[\"leakedCredentials\"]}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_leaked_credentials" + ] + }, + { + "name": "risk-detection-leakedCredentials", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"UserRiskEvents\",\"operationName\":\"User Risk Detection\",\"properties\":{\"riskEventType\":\"leakedCredentials\",\"riskState\":\"atRisk\",\"ipAddress\":\"198.51.100.4\",\"userPrincipalName\":\"actor@example.test\"}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_leaked_credentials" + ] + }, + { + "name": "risk-remediated-leakedCredentials", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"UserRiskEvents\",\"properties\":{\"riskEventType\":\"leakedCredentials\",\"riskState\":\"remediated\"}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "signin-risk-array-passwordSpray", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[\"passwordSpray\"]}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_password_spray_detected" + ] + }, + { + "name": "risk-detection-passwordSpray", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"UserRiskEvents\",\"operationName\":\"User Risk Detection\",\"properties\":{\"riskEventType\":\"passwordSpray\",\"riskState\":\"atRisk\",\"ipAddress\":\"198.51.100.4\",\"userPrincipalName\":\"actor@example.test\"}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_password_spray_detected" + ] + }, + { + "name": "risk-remediated-passwordSpray", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"UserRiskEvents\",\"properties\":{\"riskEventType\":\"passwordSpray\",\"riskState\":\"remediated\"}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "signin-hidden-risk", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"198.51.100.4\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"hidden\",\"riskEventTypes_v2\":[]}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "k8s-secrets", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"kube-audit-admin\",\"operationName\":\"Microsoft.ContainerService/managedClusters/diagnosticLogs/Read\",\"properties\":{\"log\":\"{\\\"kind\\\": \\\"Event\\\", \\\"apiVersion\\\": \\\"audit.k8s.io/v1\\\", \\\"stage\\\": \\\"ResponseComplete\\\", \\\"verb\\\": \\\"delete\\\", \\\"user\\\": {\\\"username\\\": \\\"actor-test\\\"}, \\\"sourceIPs\\\": [\\\"198.51.100.4\\\"], \\\"responseStatus\\\": {\\\"code\\\": 200}, \\\"objectRef\\\": {\\\"resource\\\": \\\"secrets\\\"}, \\\"requestURI\\\": \\\"/api/v1/secrets\\\"}\"}}", + "expected": { + "origin.user": "actor-test", + "origin.ip": "198.51.100.4", + "statusCode": 200, + "actionResult": "success", + "action": "delete" + }, + "absent": [], + "matches": [ + "azure_kubernetes_secret_access" + ] + }, + { + "name": "k8s-denied-secrets", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"kube-audit-admin\",\"operationName\":\"Microsoft.ContainerService/managedClusters/diagnosticLogs/Read\",\"properties\":{\"log\":\"{\\\"kind\\\": \\\"Event\\\", \\\"apiVersion\\\": \\\"audit.k8s.io/v1\\\", \\\"stage\\\": \\\"ResponseComplete\\\", \\\"verb\\\": \\\"delete\\\", \\\"user\\\": {\\\"username\\\": \\\"actor-test\\\"}, \\\"sourceIPs\\\": [\\\"198.51.100.4\\\"], \\\"responseStatus\\\": {\\\"code\\\": 403}, \\\"objectRef\\\": {\\\"resource\\\": \\\"secrets\\\"}, \\\"requestURI\\\": \\\"/api/v1/secrets\\\"}\"}}", + "expected": { + "actionResult": "denied" + }, + "absent": [], + "matches": [] + }, + { + "name": "k8s-start-secrets", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"kube-audit-admin\",\"operationName\":\"Microsoft.ContainerService/managedClusters/diagnosticLogs/Read\",\"properties\":{\"log\":\"{\\\"kind\\\": \\\"Event\\\", \\\"apiVersion\\\": \\\"audit.k8s.io/v1\\\", \\\"stage\\\": \\\"RequestReceived\\\", \\\"verb\\\": \\\"delete\\\", \\\"user\\\": {\\\"username\\\": \\\"actor-test\\\"}, \\\"sourceIPs\\\": [\\\"198.51.100.4\\\"], \\\"responseStatus\\\": {\\\"code\\\": 200}, \\\"objectRef\\\": {\\\"resource\\\": \\\"secrets\\\"}, \\\"requestURI\\\": \\\"/api/v1/secrets\\\"}\"}}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "k8s-events", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"kube-audit-admin\",\"operationName\":\"Microsoft.ContainerService/managedClusters/diagnosticLogs/Read\",\"properties\":{\"log\":\"{\\\"kind\\\": \\\"Event\\\", \\\"apiVersion\\\": \\\"audit.k8s.io/v1\\\", \\\"stage\\\": \\\"ResponseComplete\\\", \\\"verb\\\": \\\"deletecollection\\\", \\\"user\\\": {\\\"username\\\": \\\"actor-test\\\"}, \\\"sourceIPs\\\": [\\\"198.51.100.4\\\"], \\\"responseStatus\\\": {\\\"code\\\": 200}, \\\"objectRef\\\": {\\\"resource\\\": \\\"events\\\"}, \\\"requestURI\\\": \\\"/api/v1/events\\\"}\"}}", + "expected": { + "origin.user": "actor-test", + "origin.ip": "198.51.100.4", + "statusCode": 200, + "actionResult": "success", + "action": "deletecollection" + }, + "absent": [], + "matches": [ + "azure_kubernetes_events_deleted" + ] + }, + { + "name": "k8s-denied-events", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"kube-audit-admin\",\"operationName\":\"Microsoft.ContainerService/managedClusters/diagnosticLogs/Read\",\"properties\":{\"log\":\"{\\\"kind\\\": \\\"Event\\\", \\\"apiVersion\\\": \\\"audit.k8s.io/v1\\\", \\\"stage\\\": \\\"ResponseComplete\\\", \\\"verb\\\": \\\"deletecollection\\\", \\\"user\\\": {\\\"username\\\": \\\"actor-test\\\"}, \\\"sourceIPs\\\": [\\\"198.51.100.4\\\"], \\\"responseStatus\\\": {\\\"code\\\": 403}, \\\"objectRef\\\": {\\\"resource\\\": \\\"events\\\"}, \\\"requestURI\\\": \\\"/api/v1/events\\\"}\"}}", + "expected": { + "actionResult": "denied" + }, + "absent": [], + "matches": [] + }, + { + "name": "k8s-start-events", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"kube-audit-admin\",\"operationName\":\"Microsoft.ContainerService/managedClusters/diagnosticLogs/Read\",\"properties\":{\"log\":\"{\\\"kind\\\": \\\"Event\\\", \\\"apiVersion\\\": \\\"audit.k8s.io/v1\\\", \\\"stage\\\": \\\"RequestReceived\\\", \\\"verb\\\": \\\"deletecollection\\\", \\\"user\\\": {\\\"username\\\": \\\"actor-test\\\"}, \\\"sourceIPs\\\": [\\\"198.51.100.4\\\"], \\\"responseStatus\\\": {\\\"code\\\": 200}, \\\"objectRef\\\": {\\\"resource\\\": \\\"events\\\"}, \\\"requestURI\\\": \\\"/api/v1/events\\\"}\"}}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "k8s-mutatingwebhookconfigurations", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"kube-audit-admin\",\"operationName\":\"Microsoft.ContainerService/managedClusters/diagnosticLogs/Read\",\"properties\":{\"log\":\"{\\\"kind\\\": \\\"Event\\\", \\\"apiVersion\\\": \\\"audit.k8s.io/v1\\\", \\\"stage\\\": \\\"ResponseComplete\\\", \\\"verb\\\": \\\"patch\\\", \\\"user\\\": {\\\"username\\\": \\\"actor-test\\\"}, \\\"sourceIPs\\\": [\\\"198.51.100.4\\\"], \\\"responseStatus\\\": {\\\"code\\\": 200}, \\\"objectRef\\\": {\\\"resource\\\": \\\"mutatingwebhookconfigurations\\\"}, \\\"requestURI\\\": \\\"/api/v1/mutatingwebhookconfigurations\\\"}\"}}", + "expected": { + "origin.user": "actor-test", + "origin.ip": "198.51.100.4", + "statusCode": 200, + "actionResult": "success", + "action": "patch" + }, + "absent": [], + "matches": [ + "azure_kubernetes_admission_controller" + ] + }, + { + "name": "k8s-denied-mutatingwebhookconfigurations", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"kube-audit-admin\",\"operationName\":\"Microsoft.ContainerService/managedClusters/diagnosticLogs/Read\",\"properties\":{\"log\":\"{\\\"kind\\\": \\\"Event\\\", \\\"apiVersion\\\": \\\"audit.k8s.io/v1\\\", \\\"stage\\\": \\\"ResponseComplete\\\", \\\"verb\\\": \\\"patch\\\", \\\"user\\\": {\\\"username\\\": \\\"actor-test\\\"}, \\\"sourceIPs\\\": [\\\"198.51.100.4\\\"], \\\"responseStatus\\\": {\\\"code\\\": 403}, \\\"objectRef\\\": {\\\"resource\\\": \\\"mutatingwebhookconfigurations\\\"}, \\\"requestURI\\\": \\\"/api/v1/mutatingwebhookconfigurations\\\"}\"}}", + "expected": { + "actionResult": "denied" + }, + "absent": [], + "matches": [] + }, + { + "name": "k8s-start-mutatingwebhookconfigurations", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"kube-audit-admin\",\"operationName\":\"Microsoft.ContainerService/managedClusters/diagnosticLogs/Read\",\"properties\":{\"log\":\"{\\\"kind\\\": \\\"Event\\\", \\\"apiVersion\\\": \\\"audit.k8s.io/v1\\\", \\\"stage\\\": \\\"RequestReceived\\\", \\\"verb\\\": \\\"patch\\\", \\\"user\\\": {\\\"username\\\": \\\"actor-test\\\"}, \\\"sourceIPs\\\": [\\\"198.51.100.4\\\"], \\\"responseStatus\\\": {\\\"code\\\": 200}, \\\"objectRef\\\": {\\\"resource\\\": \\\"mutatingwebhookconfigurations\\\"}, \\\"requestURI\\\": \\\"/api/v1/mutatingwebhookconfigurations\\\"}\"}}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "k8s-unrelated-resource", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"kube-audit-admin\",\"operationName\":\"Microsoft.ContainerService/managedClusters/diagnosticLogs/Read\",\"properties\":{\"log\":\"{\\\"kind\\\": \\\"Event\\\", \\\"apiVersion\\\": \\\"audit.k8s.io/v1\\\", \\\"stage\\\": \\\"ResponseComplete\\\", \\\"verb\\\": \\\"create\\\", \\\"user\\\": {\\\"username\\\": \\\"actor-test\\\"}, \\\"sourceIPs\\\": [\\\"198.51.100.4\\\"], \\\"responseStatus\\\": {\\\"code\\\": 201}, \\\"objectRef\\\": {\\\"resource\\\": \\\"subjectaccessreviews\\\"}, \\\"requestURI\\\": \\\"/api/v1/subjectaccessreviews\\\"}\"}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "k8s-secret-read-is-not-write", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"kube-audit-admin\",\"operationName\":\"Microsoft.ContainerService/managedClusters/diagnosticLogs/Read\",\"properties\":{\"log\":\"{\\\"kind\\\": \\\"Event\\\", \\\"apiVersion\\\": \\\"audit.k8s.io/v1\\\", \\\"stage\\\": \\\"ResponseComplete\\\", \\\"verb\\\": \\\"get\\\", \\\"user\\\": {\\\"username\\\": \\\"actor-test\\\"}, \\\"sourceIPs\\\": [\\\"198.51.100.4\\\"], \\\"responseStatus\\\": {\\\"code\\\": 200}, \\\"objectRef\\\": {\\\"resource\\\": \\\"secrets\\\"}, \\\"requestURI\\\": \\\"/api/v1/secrets\\\"}\"}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "vault-Authentication", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.KeyVault/vaults/vault-test\",\"category\":\"AuditEvent\",\"operationName\":\"Authentication\",\"callerIpAddress\":\"198.51.100.4\",\"resultType\":\"Success\",\"resultSignature\":\"OK\",\"properties\":{\"httpStatusCode\":200,\"requestUri\":\"https://vault-test.vault.azure.net/secret-test\"}}", + "expected": { + "actionResult": "success" + }, + "absent": [], + "matches": [] + }, + { + "name": "vault-denied-Authentication", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.KeyVault/vaults/vault-test\",\"category\":\"AuditEvent\",\"operationName\":\"Authentication\",\"callerIpAddress\":\"198.51.100.4\",\"resultType\":\"Success\",\"resultSignature\":\"Unauthorized\",\"properties\":{\"httpStatusCode\":401,\"requestUri\":\"https://vault-test.vault.azure.net/secret-test\"}}", + "expected": { + "actionResult": "denied", + "statusCode": 401 + }, + "absent": [], + "matches": [] + }, + { + "name": "vault-SecretGet", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.KeyVault/vaults/vault-test\",\"category\":\"AuditEvent\",\"operationName\":\"SecretGet\",\"callerIpAddress\":\"198.51.100.4\",\"resultType\":\"Success\",\"resultSignature\":\"OK\",\"properties\":{\"httpStatusCode\":200,\"requestUri\":\"https://vault-test.vault.azure.net/secret-test\"}}", + "expected": { + "actionResult": "success" + }, + "absent": [], + "matches": [ + "key_vault_access_spikes" + ] + }, + { + "name": "vault-denied-SecretGet", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.KeyVault/vaults/vault-test\",\"category\":\"AuditEvent\",\"operationName\":\"SecretGet\",\"callerIpAddress\":\"198.51.100.4\",\"resultType\":\"Success\",\"resultSignature\":\"Unauthorized\",\"properties\":{\"httpStatusCode\":401,\"requestUri\":\"https://vault-test.vault.azure.net/secret-test\"}}", + "expected": { + "actionResult": "denied", + "statusCode": 401 + }, + "absent": [], + "matches": [] + }, + { + "name": "vault-SecretList", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.KeyVault/vaults/vault-test\",\"category\":\"AuditEvent\",\"operationName\":\"SecretList\",\"callerIpAddress\":\"198.51.100.4\",\"resultType\":\"Success\",\"resultSignature\":\"OK\",\"properties\":{\"httpStatusCode\":200,\"requestUri\":\"https://vault-test.vault.azure.net/secret-test\"}}", + "expected": { + "actionResult": "success" + }, + "absent": [], + "matches": [ + "key_vault_access_spikes" + ] + }, + { + "name": "vault-denied-SecretList", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.KeyVault/vaults/vault-test\",\"category\":\"AuditEvent\",\"operationName\":\"SecretList\",\"callerIpAddress\":\"198.51.100.4\",\"resultType\":\"Success\",\"resultSignature\":\"Unauthorized\",\"properties\":{\"httpStatusCode\":401,\"requestUri\":\"https://vault-test.vault.azure.net/secret-test\"}}", + "expected": { + "actionResult": "denied", + "statusCode": 401 + }, + "absent": [], + "matches": [] + }, + { + "name": "vault-KeyGet", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.KeyVault/vaults/vault-test\",\"category\":\"AuditEvent\",\"operationName\":\"KeyGet\",\"callerIpAddress\":\"198.51.100.4\",\"resultType\":\"Success\",\"resultSignature\":\"OK\",\"properties\":{\"httpStatusCode\":200,\"requestUri\":\"https://vault-test.vault.azure.net/secret-test\"}}", + "expected": { + "actionResult": "success" + }, + "absent": [], + "matches": [ + "key_vault_access_spikes" + ] + }, + { + "name": "vault-denied-KeyGet", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.KeyVault/vaults/vault-test\",\"category\":\"AuditEvent\",\"operationName\":\"KeyGet\",\"callerIpAddress\":\"198.51.100.4\",\"resultType\":\"Success\",\"resultSignature\":\"Unauthorized\",\"properties\":{\"httpStatusCode\":401,\"requestUri\":\"https://vault-test.vault.azure.net/secret-test\"}}", + "expected": { + "actionResult": "denied", + "statusCode": 401 + }, + "absent": [], + "matches": [] + }, + { + "name": "waf-blocked", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayFirewallLog\",\"operationName\":\"ApplicationGatewayFirewallLog\",\"resultType\":\"Success\",\"properties\":{\"clientIp\":\"198.51.100.4\",\"ruleId\":\"942100\",\"action\":\"Blocked\"}}", + "expected": { + "origin.ip": "198.51.100.4", + "actionResult": "denied" + }, + "absent": [], + "matches": [ + "application_gateway_waf_alerts" + ] + }, + { + "name": "waf-Allowed", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayFirewallLog\",\"operationName\":\"ApplicationGatewayFirewallLog\",\"resultType\":\"Success\",\"properties\":{\"clientIp\":\"198.51.100.4\",\"ruleId\":\"942100\",\"action\":\"Allowed\"}}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "waf-Matched", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayFirewallLog\",\"operationName\":\"ApplicationGatewayFirewallLog\",\"resultType\":\"Success\",\"properties\":{\"clientIp\":\"198.51.100.4\",\"ruleId\":\"942100\",\"action\":\"Matched\"}}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "waf-Detected", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayFirewallLog\",\"operationName\":\"ApplicationGatewayFirewallLog\",\"resultType\":\"Success\",\"properties\":{\"clientIp\":\"198.51.100.4\",\"ruleId\":\"942100\",\"action\":\"Detected\"}}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "gateway-endpoints", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientIP\":\"198.51.100.4\",\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":200,\"requestMethod\":\"GET\"}}", + "expected": { + "origin.ip": "198.51.100.4", + "target.host": "app.example.test", + "origin.port": 54321, + "actionResult": "success", + "action": "get" + }, + "absent": [ + "origin.host" + ], + "matches": [] + }, + { + "name": "appservice-http-string", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AppServiceHTTPLogs\",\"Host\":\"worker-test\",\"EventIpAddress\":\"192.0.2.200\",\"properties\":\"{\\\"CIp\\\": \\\"198.51.100.4\\\", \\\"CsHost\\\": \\\"app.example.test\\\", \\\"ComputerName\\\": \\\"worker-test\\\", \\\"SPort\\\": \\\"443\\\", \\\"CsMethod\\\": \\\"GET\\\", \\\"ScStatus\\\": 403, \\\"CsBytes\\\": 120, \\\"ScBytes\\\": 300, \\\"CsUriStem\\\": \\\"/login\\\", \\\"CsUriQuery\\\": \\\"next=home\\\", \\\"Protocol\\\": \\\"HTTP/1.1\\\"}\"}", + "expected": { + "origin.ip": "198.51.100.4", + "target.host": "worker-test", + "target.port": 443, + "origin.bytesSent": 120, + "origin.bytesReceived": 300, + "actionResult": "denied", + "statusCode": 403, + "action": "get" + }, + "absent": [ + "origin.host" + ], + "matches": [] + }, + { + "name": "console-not-connection", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AppServiceConsoleLogs\",\"Host\":\"worker-test\",\"EventIpAddress\":\"192.0.2.200\",\"operationName\":\"Microsoft.Web/sites/log\",\"level\":\"Error\",\"resultType\":\"Success\"}", + "expected": { + "target.host": "worker-test", + "severity": "error" + }, + "absent": [ + "origin.ip", + "actionResult" + ], + "matches": [] + }, + { + "name": "security-high-alert", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Security\",\"operationName\":\"Microsoft.Security/locations/alerts/Activate/action\",\"properties\":{\"severity\":\"High\"}}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [ + "defender_cloud_critical_alerts" + ] + }, + { + "name": "endpoint-process", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AdvancedHunting-DeviceProcessEvents\",\"operationName\":\"Publish\",\"properties\":{\"ActionType\":\"ProcessCreated\",\"DeviceName\":\"device-test\",\"InitiatingProcessAccountUpn\":\"admin@example.test\",\"InitiatingProcessFileName\":\"parent.exe\",\"InitiatingProcessCommandLine\":\"parent.exe /safe\",\"FileName\":\"child.exe\",\"ProcessCommandLine\":\"child.exe /safe\"}}", + "expected": { + "action": "ProcessCreated", + "origin.host": "device-test", + "origin.user": "admin@example.test", + "origin.process": "parent.exe", + "target.process": "child.exe", + "target.command": "child.exe /safe" + }, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "invalid-ip-0.0.0.0", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"0.0.0.0\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[]}}", + "expected": { + "actionResult": "success" + }, + "absent": [ + "origin.ip" + ], + "matches": [] + }, + { + "name": "invalid-ip-__", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"::\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[]}}", + "expected": { + "actionResult": "success" + }, + "absent": [ + "origin.ip" + ], + "matches": [] + }, + { + "name": "invalid-ip-0_0_0_0_0_0_0_0", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"0:0:0:0:0:0:0:0\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[]}}", + "expected": { + "actionResult": "success" + }, + "absent": [ + "origin.ip" + ], + "matches": [] + }, + { + "name": "invalid-ip-__ffff_0.0.0.0", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"::ffff:0.0.0.0\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[]}}", + "expected": { + "actionResult": "success" + }, + "absent": [ + "origin.ip" + ], + "matches": [] + }, + { + "name": "invalid-ip--", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"-\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[]}}", + "expected": { + "actionResult": "success" + }, + "absent": [ + "origin.ip" + ], + "matches": [] + }, + { + "name": "invalid-ip-not-an-ip", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"SignInLogs\",\"operationName\":\"Sign-in activity\",\"resultType\":\"0\",\"properties\":{\"userPrincipalName\":\"actor@example.test\",\"userId\":\"user-test\",\"ipAddress\":\"not-an-ip\",\"status\":{\"errorCode\":0},\"riskState\":\"none\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[]}}", + "expected": { + "actionResult": "success" + }, + "absent": [ + "origin.ip" + ], + "matches": [] + }, + { + "name": "invalid-status--1", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"httpStatus\":-1}}", + "expected": {}, + "absent": [ + "statusCode", + "actionResult" + ], + "matches": [] + }, + { + "name": "invalid-status-3.5", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"httpStatus\":3.5}}", + "expected": {}, + "absent": [ + "statusCode", + "actionResult" + ], + "matches": [] + }, + { + "name": "invalid-status-not-code", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"httpStatus\":\"not-code\"}}", + "expected": {}, + "absent": [ + "statusCode", + "actionResult" + ], + "matches": [] + }, + { + "name": "invalid-status-4294967296", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"httpStatus\":4294967296}}", + "expected": {}, + "absent": [ + "statusCode", + "actionResult" + ], + "matches": [] + }, + { + "name": "untrusted-markers", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AppServiceConsoleLogs\",\"azureKind\":\"signin\",\"azureScope\":\"forged\",\"azureActor\":\"forged\",\"correlationCandidate\":{\"azure_ad_password_spray\":\"true\"}}", + "expected": {}, + "absent": [ + "log.correlationCandidate", + "actionResult" + ], + "matches": [] + }, + { + "name": "unrelated-vendor-string", + "dataSource": "collector-test", + "raw": "{\"message\":\"anomalousToken impossibleTravel Add application\",\"resultType\":\"Started\"}", + "expected": {}, + "absent": [ + "actionResult", + "origin.ip" + ], + "matches": [] + }, + { + "name": "appservice-http-object", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AppServiceHTTPLogs\",\"properties\":{\"CIp\":\"198.51.100.4\",\"CsHost\":\"app.example.test\",\"ComputerName\":\"worker-test\",\"SPort\":\"443\",\"CsMethod\":\"GET\",\"ScStatus\":403,\"CsBytes\":120,\"ScBytes\":300,\"CsUriStem\":\"/login\",\"CsUriQuery\":\"next=home\",\"Protocol\":\"HTTP/1.1\"}}", + "expected": { + "origin.ip": "198.51.100.4", + "target.port": 443, + "actionResult": "denied" + }, + "absent": [], + "matches": [] + }, + { + "name": "security-log-success-not-connection", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Security\",\"operationName\":\"Microsoft.Security/locations/alerts/Activate/action\",\"resultType\":\"Success\",\"properties\":{\"severity\":\"High\"}}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [ + "defender_cloud_critical_alerts" + ] + }, + { + "name": "relative-url-retained", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"kube-audit-admin\",\"operationName\":\"Microsoft.ContainerService/managedClusters/diagnosticLogs/Read\",\"properties\":{\"log\":\"{\\\"kind\\\": \\\"Event\\\", \\\"apiVersion\\\": \\\"audit.k8s.io/v1\\\", \\\"stage\\\": \\\"ResponseComplete\\\", \\\"verb\\\": \\\"get\\\", \\\"user\\\": {\\\"username\\\": \\\"actor-test\\\"}, \\\"sourceIPs\\\": [\\\"198.51.100.4\\\"], \\\"responseStatus\\\": {\\\"code\\\": 200}, \\\"objectRef\\\": {\\\"resource\\\": \\\"pods\\\"}, \\\"requestURI\\\": \\\"/api/v1/pods\\\"}\"}}", + "expected": { + "log.azureKubernetes.requestURI": "/api/v1/pods" + }, + "absent": [ + "target.url" + ], + "matches": [] + }, + { + "name": "vault-token-identity", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.KeyVault/vaults/vault-test\",\"category\":\"AuditEvent\",\"operationName\":\"SecretGet\",\"resultType\":\"Success\",\"identity\":{\"claim\":{\"http://schemas.microsoft.com/identity/claims/objectidentifier\":\"principal-test\"}},\"properties\":{\"httpStatusCode\":200}}", + "expected": { + "origin.user": "principal-test", + "log.azureScopeType": "resource", + "log.azureScope": "/subscriptions/sub-test/providers/Microsoft.KeyVault/vaults/vault-test" + }, + "absent": [ + "origin.ip" + ], + "matches": [ + "key_vault_access_spikes" + ] + }, + { + "name": "protocol-6-int", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"Protocol\":6}", + "expected": { + "protocol": "TCP" + }, + "absent": [], + "matches": [] + }, + { + "name": "protocol-6-str", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"Protocol\":\"6\"}", + "expected": { + "protocol": "TCP" + }, + "absent": [], + "matches": [] + }, + { + "name": "protocol-TCP-str", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"Protocol\":\"TCP\"}", + "expected": { + "protocol": "TCP" + }, + "absent": [], + "matches": [] + }, + { + "name": "pim-eligible-completed-suffix", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add eligible member to role in PIM completed (timebound)\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_bulk_role_changes", + "pim_role_activation_abuse" + ] + }, + { + "name": "pim-request-is-not-completion", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add eligible member to role in PIM requested (timebound)\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "pim-role-property", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add member to role\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"modifiedProperties\":[{\"displayName\":\"Role.DisplayName\",\"newValue\":\"\\\"Global Administrator\\\"\"}]}]}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_bulk_role_changes", + "persistence_azure_pim_user_added_global_admin" + ] + }, + { + "name": "pim-unrelated-property-value", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add member to role\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"modifiedProperties\":[{\"displayName\":\"Role.DisplayName\",\"newValue\":\"\\\"User Administrator\\\"\"},{\"displayName\":\"Unrelated\",\"newValue\":\"\\\"Global Administrator\\\"\"}]}]}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_bulk_role_changes" + ] + }, + { + "name": "nested-json-cannot-rewrite-envelope", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AppServiceHTTPLogs\",\"properties\":\"{\\\"CIp\\\": \\\"198.51.100.4\\\", \\\"CsHost\\\": \\\"app.example.test\\\", \\\"ComputerName\\\": \\\"worker-test\\\", \\\"SPort\\\": \\\"443\\\", \\\"CsMethod\\\": \\\"GET\\\", \\\"ScStatus\\\": 403, \\\"CsBytes\\\": 120, \\\"ScBytes\\\": 300, \\\"CsUriStem\\\": \\\"/login\\\", \\\"CsUriQuery\\\": \\\"next=home\\\", \\\"Protocol\\\": \\\"HTTP/1.1\\\", \\\"category\\\": \\\"SignInLogs\\\", \\\"operationName\\\": \\\"Sign-in activity\\\", \\\"azureEnvelope\\\": {\\\"category\\\": \\\"SignInLogs\\\"}, \\\"azureKind\\\": \\\"signin\\\", \\\"correlationCandidate\\\": {\\\"azure_ad_password_spray\\\": \\\"true\\\"}}\"}", + "expected": { + "log.category": "AppServiceHTTPLogs", + "log.azureKind": "http", + "actionResult": "denied" + }, + "absent": [ + "log.correlationCandidate" + ], + "matches": [] + }, + { + "name": "certificate-config-Create CertificateAuthorityEntity", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Create CertificateAuthorityEntity\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "actionResult": "success" + }, + "absent": [], + "matches": [ + "azure_new_root_ca_added" + ] + }, + { + "name": "certificate-failed-Create CertificateAuthorityEntity", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Create CertificateAuthorityEntity\",\"properties\":{\"result\":\"failure\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "certificate-config-Add CertificateBasedAuthConfiguration", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add CertificateBasedAuthConfiguration\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": { + "actionResult": "success" + }, + "absent": [], + "matches": [ + "azure_new_root_ca_added" + ] + }, + { + "name": "certificate-failed-Add CertificateBasedAuthConfiguration", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Add CertificateBasedAuthConfiguration\",\"properties\":{\"result\":\"failure\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "certificate-unrelated-update", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Update organization settings\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "authentication-method-Temporary Access Pass", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Admin registered security info\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}],\"additionalDetails\":[{\"key\":\"AuthenticationMethod\",\"value\":\"Temporary Access Pass\"}]}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_temporary_access_pass" + ] + }, + { + "name": "authentication-method-TemporaryAccessPass", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Admin registered security info\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}],\"additionalDetails\":[{\"key\":\"AuthenticationMethod\",\"value\":\"TemporaryAccessPass\"}]}}", + "expected": {}, + "absent": [], + "matches": [ + "azure_temporary_access_pass" + ] + }, + { + "name": "authentication-method-PhoneAppNotification", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Admin registered security info\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}],\"additionalDetails\":[{\"key\":\"AuthenticationMethod\",\"value\":\"PhoneAppNotification\"}]}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "tap-wrong-detail-key", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Admin registered security info\",\"properties\":{\"result\":\"success\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}],\"additionalDetails\":[{\"key\":\"Unrelated\",\"value\":\"Temporary Access Pass\"},{\"key\":\"AuthenticationMethod\",\"value\":\"PhoneAppNotification\"}]}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "tap-failed", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AuditLogs\",\"operationName\":\"Admin registered security info\",\"properties\":{\"result\":\"failure\",\"initiatedBy\":{\"user\":{\"userPrincipalName\":\"admin@example.test\",\"ipAddress\":\"198.51.100.4\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}],\"additionalDetails\":[{\"key\":\"AuthenticationMethod\",\"value\":\"Temporary Access Pass\"}]}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "forwarded-alert", + "dataSource": "collector-test", + "raw": "{\"Type\":\"SecurityAlert\",\"TimeGenerated\":\"2026-09-17T12:00:00Z\",\"SystemAlertId\":\"alert-test\",\"VendorName\":\"Microsoft\",\"ProductName\":\"Azure Sentinel\",\"AlertSeverity\":\"High\",\"Status\":\"New\"}", + "expected": { + "severity": "error", + "deviceTime": "2026-09-17T12:00:00Z" + }, + "absent": [ + "origin.ip", + "actionResult" + ], + "matches": [ + "azure_sentinel_alert_patterns" + ] + }, + { + "name": "forwarded-alert-negative-{'Status': 'Resolved'}", + "dataSource": "collector-test", + "raw": "{\"Type\":\"SecurityAlert\",\"SystemAlertId\":\"alert-test\",\"AlertSeverity\":\"High\",\"Status\":\"Resolved\"}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "forwarded-alert-negative-{'AlertSeverity': 'Medium'}", + "dataSource": "collector-test", + "raw": "{\"Type\":\"SecurityAlert\",\"SystemAlertId\":\"alert-test\",\"AlertSeverity\":\"Medium\",\"Status\":\"New\"}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "forwarded-alert-negative-{'Type': 'Unrelated'}", + "dataSource": "collector-test", + "raw": "{\"Type\":\"Unrelated\",\"SystemAlertId\":\"alert-test\",\"AlertSeverity\":\"High\",\"Status\":\"New\"}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "forwarded-alert-negative-{'SystemAlertId': ''}", + "dataSource": "collector-test", + "raw": "{\"Type\":\"SecurityAlert\",\"SystemAlertId\":\"\",\"AlertSeverity\":\"High\",\"Status\":\"New\"}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "storage-public-access-True-bool", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Storage/storageAccounts/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\",\"properties\":{\"requestbody\":\"{\\\"properties\\\": {\\\"allowBlobPublicAccess\\\": true}}\"}}", + "expected": {}, + "absent": [], + "matches": [ + "storage_account_public_access" + ] + }, + { + "name": "storage-public-access-False-bool", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Storage/storageAccounts/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\",\"properties\":{\"requestbody\":\"{\\\"properties\\\": {\\\"allowBlobPublicAccess\\\": false}}\"}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "storage-public-access-true-str", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Storage/storageAccounts/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\",\"properties\":{\"requestbody\":\"{\\\"properties\\\": {\\\"allowBlobPublicAccess\\\": \\\"true\\\"}}\"}}", + "expected": {}, + "absent": [], + "matches": [ + "storage_account_public_access" + ] + }, + { + "name": "storage-public-access-false-str", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Storage/storageAccounts/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\",\"properties\":{\"requestbody\":\"{\\\"properties\\\": {\\\"allowBlobPublicAccess\\\": \\\"false\\\"}}\"}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "storage-container-Blob", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Storage/storageAccounts/blobServices/containers/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\",\"properties\":{\"requestBody\":{\"properties\":{\"publicAccess\":\"Blob\"}}}}", + "expected": {}, + "absent": [], + "matches": [ + "storage_account_public_access" + ] + }, + { + "name": "storage-container-Container", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Storage/storageAccounts/blobServices/containers/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\",\"properties\":{\"requestBody\":{\"properties\":{\"publicAccess\":\"Container\"}}}}", + "expected": {}, + "absent": [], + "matches": [ + "storage_account_public_access" + ] + }, + { + "name": "storage-container-None", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Storage/storageAccounts/blobServices/containers/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\",\"properties\":{\"requestBody\":{\"properties\":{\"publicAccess\":\"None\"}}}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "storage-missing-body", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Storage/storageAccounts/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\"}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "storage-accepted-not-completed", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Storage/storageAccounts/write\",\"resultType\":\"Accepted\",\"callerIpAddress\":\"198.51.100.4\",\"properties\":{\"requestbody\":\"{\\\"properties\\\": {\\\"allowBlobPublicAccess\\\": true}}\"}}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [] + }, + { + "name": "owner-role-8e3af657-a8ff-443c-a75c-2fe8c4bcb635", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Authorization/roleAssignments/assignment-test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Authorization/roleAssignments/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\",\"properties\":{\"requestbody\":\"{\\\"properties\\\": {\\\"roleDefinitionId\\\": \\\"/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635\\\", \\\"principalId\\\": \\\"grantee-test\\\"}}\"}}", + "expected": { + "target.user": "grantee-test" + }, + "absent": [], + "matches": [ + "subscription_ownership_transfer" + ] + }, + { + "name": "not-subscription-owner-8e3af657-a8ff-443c-a75c-2fe8c4bcb635resourceGroups/group-test/providers/Microsoft.Authorization/roleAssignments/assignment-test", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/resourceGroups/group-test/providers/Microsoft.Authorization/roleAssignments/assignment-test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Authorization/roleAssignments/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\",\"properties\":{\"requestbody\":\"{\\\"properties\\\": {\\\"roleDefinitionId\\\": \\\"/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635\\\", \\\"principalId\\\": \\\"grantee-test\\\"}}\"}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "not-subscription-owner-8e3af657-a8ff-443c-a75c-2fe8c4bcb635providers/Microsoft.Authorization/roleAssignments/assignment-test/extra", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Authorization/roleAssignments/assignment-test/extra\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Authorization/roleAssignments/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\",\"properties\":{\"requestbody\":\"{\\\"properties\\\": {\\\"roleDefinitionId\\\": \\\"/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635\\\", \\\"principalId\\\": \\\"grantee-test\\\"}}\"}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "owner-role-other-role-test", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Authorization/roleAssignments/assignment-test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Authorization/roleAssignments/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\",\"properties\":{\"requestbody\":\"{\\\"properties\\\": {\\\"roleDefinitionId\\\": \\\"/providers/Microsoft.Authorization/roleDefinitions/other-role-test\\\", \\\"principalId\\\": \\\"grantee-test\\\"}}\"}}", + "expected": { + "target.user": "grantee-test" + }, + "absent": [], + "matches": [] + }, + { + "name": "not-subscription-owner-other-role-testresourceGroups/group-test/providers/Microsoft.Authorization/roleAssignments/assignment-test", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/resourceGroups/group-test/providers/Microsoft.Authorization/roleAssignments/assignment-test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Authorization/roleAssignments/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\",\"properties\":{\"requestbody\":\"{\\\"properties\\\": {\\\"roleDefinitionId\\\": \\\"/providers/Microsoft.Authorization/roleDefinitions/other-role-test\\\", \\\"principalId\\\": \\\"grantee-test\\\"}}\"}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "not-subscription-owner-other-role-testproviders/Microsoft.Authorization/roleAssignments/assignment-test/extra", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Authorization/roleAssignments/assignment-test/extra\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Authorization/roleAssignments/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\",\"properties\":{\"requestbody\":\"{\\\"properties\\\": {\\\"roleDefinitionId\\\": \\\"/providers/Microsoft.Authorization/roleDefinitions/other-role-test\\\", \\\"principalId\\\": \\\"grantee-test\\\"}}\"}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "actor-owner-is-not-target-role", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Authorization/roleAssignments/assignment-test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.Authorization/roleAssignments/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\",\"identity\":{\"authorization\":{\"evidence\":{\"role\":\"Owner\"}}}}", + "expected": {}, + "absent": [], + "matches": [] + }, + { + "name": "activity-documented-write-category", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Write\",\"operationName\":\"Microsoft.KeyVault/vaults/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\",\"identity\":{\"authorization\":{\"action\":\"Microsoft.KeyVault/vaults/write\"},\"claims\":{\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn\":\"admin@example.test\"}}}", + "expected": { + "origin.user": "admin@example.test", + "actionResult": "success" + }, + "absent": [], + "matches": [ + "credential_access_key_vault_modified" + ] + }, + { + "name": "security-rest-shape", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":{\"value\":\"Security\"},\"operationName\":{\"value\":\"Microsoft.Security/locations/alerts/activate/action\"},\"properties\":{\"Severity\":\"High\"}}", + "expected": {}, + "absent": [ + "actionResult" + ], + "matches": [ + "defender_cloud_critical_alerts" + ] + }, + { + "name": "managed-identity-text-not-abuse", + "dataSource": "collector-test", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"Administrative\",\"operationName\":\"Microsoft.ManagedIdentity/userAssignedIdentities/write\",\"resultType\":\"Succeeded\",\"callerIpAddress\":\"198.51.100.4\",\"categoryValue\":\"Administrative\",\"properties\":{\"message\":\"token configuration metadata\"}}", + "expected": {}, + "absent": [], + "matches": [] + } +] diff --git a/plugins/alerts/testdata/filter-contracts/azure.json b/plugins/alerts/testdata/filter-contracts/azure.json index 9e5fc31c4..4ace3a941 100644 --- a/plugins/alerts/testdata/filter-contracts/azure.json +++ b/plugins/alerts/testdata/filter-contracts/azure.json @@ -34,17 +34,11 @@ "rules/cloud/azure/azure_temporary_access_pass.yml", "rules/cloud/azure/credential_access_key_vault_modified.yml", "rules/cloud/azure/defender_cloud_critical_alerts.yml", - "rules/cloud/azure/defense_evasion_azure_application_credential_modification.yml", "rules/cloud/azure/defense_evasion_azure_diagnostic_settings_deletion.yml", "rules/cloud/azure/defense_evasion_event_hub_deletion.yml", - "rules/cloud/azure/diagnostic_settings_tampering.yml", - "rules/cloud/azure/golden_saml_federation_abuse.yml", - "rules/cloud/azure/impact_azure_service_principal_credentials_added.yml", "rules/cloud/azure/initial_access_azure_active_directory_high_risk_signin.yml", "rules/cloud/azure/initial_access_consent_grant_attack_via_azure_registered_application.yml", "rules/cloud/azure/key_vault_access_spikes.yml", - "rules/cloud/azure/managed_identity_abuse.yml", - "rules/cloud/azure/mfa_disabled_privileged_users.yml", "rules/cloud/azure/persistence_azure_pim_user_added_global_admin.yml", "rules/cloud/azure/persistence_mfa_disabled_for_azure_user.yml", "rules/cloud/azure/pim_role_activation_abuse.yml", @@ -53,171 +47,121 @@ ], "fixtures": [ { - "name": "Azure actual result Denied", + "name": "Azure dictionary severity DEBUG", "filter": "azure/azure-eventhub.yml", "input": { "log": { - "properties": { - "result": "Denied" - } + "level": "DEBUG", + "category": "AppServiceConsoleLogs" } }, "expected": { - "actionResult": "denied" + "severity": "debug" }, - "absent": [], - "rules": {} - }, - { - "name": "Azure actual result Failed", - "filter": "azure/azure-eventhub.yml", - "input": { - "log": { - "properties": { - "result": "Failed" - } - } - }, - "expected": { - "actionResult": "failure" - }, - "absent": [], - "rules": {} - }, - { - "name": "Azure actual result Succeeded", - "filter": "azure/azure-eventhub.yml", - "input": { - "log": { - "properties": { - "result": "Succeeded" - } - } - }, - "expected": { - "actionResult": "success" - }, - "absent": [], - "rules": {} - }, - { - "name": "Azure actual result Running", - "filter": "azure/azure-eventhub.yml", - "input": { - "log": { - "properties": { - "result": "Running" - } - } - }, - "expected": {}, "absent": [ - "actionResult" + "actionResult", + "origin.ip" ], - "rules": {} + "rules": { + "rules/cloud/azure/aks_security_threats.yml": false, + "rules/cloud/azure/app_registration_abuse.yml": false, + "rules/cloud/azure/application_gateway_waf_alerts.yml": false, + "rules/cloud/azure/automation_runbook_abuse.yml": false, + "rules/cloud/azure/azure_ad_impossible_travel.yml": false, + "rules/cloud/azure/azure_ad_password_spray.yml": false, + "rules/cloud/azure/azure_alert_suppression_rule.yml": false, + "rules/cloud/azure/azure_anomalous_token.yml": false, + "rules/cloud/azure/azure_app_credential_added.yml": false, + "rules/cloud/azure/azure_app_privileged_permissions.yml": false, + "rules/cloud/azure/azure_azurehound_discovery.yml": false, + "rules/cloud/azure/azure_bulk_role_changes.yml": false, + "rules/cloud/azure/azure_device_code_auth_abuse.yml": false, + "rules/cloud/azure/azure_disk_snapshot_exfiltration.yml": false, + "rules/cloud/azure/azure_federation_modified.yml": false, + "rules/cloud/azure/azure_impossible_travel.yml": false, + "rules/cloud/azure/azure_kubernetes_admission_controller.yml": false, + "rules/cloud/azure/azure_kubernetes_events_deleted.yml": false, + "rules/cloud/azure/azure_kubernetes_secret_access.yml": false, + "rules/cloud/azure/azure_laps_credential_dump.yml": false, + "rules/cloud/azure/azure_leaked_credentials.yml": false, + "rules/cloud/azure/azure_new_root_ca_added.yml": false, + "rules/cloud/azure/azure_password_spray_detected.yml": false, + "rules/cloud/azure/azure_prt_access_attempt.yml": false, + "rules/cloud/azure/azure_ropc_authentication.yml": false, + "rules/cloud/azure/azure_sentinel_alert_patterns.yml": false, + "rules/cloud/azure/azure_subscription_permission_elevation.yml": false, + "rules/cloud/azure/azure_temporary_access_pass.yml": false, + "rules/cloud/azure/credential_access_key_vault_modified.yml": false, + "rules/cloud/azure/defender_cloud_critical_alerts.yml": false, + "rules/cloud/azure/defense_evasion_azure_diagnostic_settings_deletion.yml": false, + "rules/cloud/azure/defense_evasion_event_hub_deletion.yml": false, + "rules/cloud/azure/initial_access_azure_active_directory_high_risk_signin.yml": false, + "rules/cloud/azure/initial_access_consent_grant_attack_via_azure_registered_application.yml": false, + "rules/cloud/azure/key_vault_access_spikes.yml": false, + "rules/cloud/azure/persistence_azure_pim_user_added_global_admin.yml": false, + "rules/cloud/azure/persistence_mfa_disabled_for_azure_user.yml": false, + "rules/cloud/azure/pim_role_activation_abuse.yml": false, + "rules/cloud/azure/storage_account_public_access.yml": false, + "rules/cloud/azure/subscription_ownership_transfer.yml": false + } }, { - "name": "Azure storage metadata is not host geography", + "name": "Azure dictionary severity CRITICAL", "filter": "azure/azure-eventhub.yml", "input": { "log": { - "AccountName": "storageaccount", - "Location": "eastus", - "StatusText": "Success", - "ResponseBodySize": 100, - "ResponseHeaderSize": 20 + "level": "CRITICAL", + "category": "AppServiceConsoleLogs" } }, "expected": { - "origin.bytesReceived": 100, - "log.accountName": "storageaccount", - "log.location": "eastus" + "severity": "critical" }, "absent": [ - "origin.host", - "origin.geolocation", - "connectionStatus", - "origin.bytesSent" + "actionResult", + "origin.ip" ], - "rules": {} - }, - { - "name": "Azure result Failed", - "filter": "azure/azure-eventhub.yml", - "input": { - "log": { - "resultType": "Failed" - } - }, - "expected": { - "actionResult": "failure" - }, - "absent": [] - }, - { - "name": "Azure result Succeeded", - "filter": "azure/azure-eventhub.yml", - "input": { - "log": { - "resultType": "Succeeded" - } - }, - "expected": { - "actionResult": "success" - }, - "absent": [] - }, - { - "name": "Azure result Denied", - "filter": "azure/azure-eventhub.yml", - "input": { - "log": { - "resultType": "Denied" - } - }, - "expected": { - "actionResult": "denied" - }, - "absent": [] - }, - { - "name": "Azure result Started", - "filter": "azure/azure-eventhub.yml", - "input": { - "log": { - "resultType": "Started" - } - }, - "expected": {}, - "absent": [ - "actionResult" - ] - }, - { - "name": "Azure descriptive denial", - "filter": "azure/azure-eventhub.yml", - "input": { - "log": { - "properties": { - "result": "Access denied" - } - } - }, - "expected": { - "actionResult": "denied" - } - }, - { - "name": "Azure explicit failure overrides success event suffix", - "filter": "azure/azure-eventhub.yml", - "input": { - "log": { - "resultType": "Failed", - "eventType": "Microsoft.ResourceWriteSuccess" - } - }, - "expected": { - "actionResult": "failure" + "rules": { + "rules/cloud/azure/aks_security_threats.yml": false, + "rules/cloud/azure/app_registration_abuse.yml": false, + "rules/cloud/azure/application_gateway_waf_alerts.yml": false, + "rules/cloud/azure/automation_runbook_abuse.yml": false, + "rules/cloud/azure/azure_ad_impossible_travel.yml": false, + "rules/cloud/azure/azure_ad_password_spray.yml": false, + "rules/cloud/azure/azure_alert_suppression_rule.yml": false, + "rules/cloud/azure/azure_anomalous_token.yml": false, + "rules/cloud/azure/azure_app_credential_added.yml": false, + "rules/cloud/azure/azure_app_privileged_permissions.yml": false, + "rules/cloud/azure/azure_azurehound_discovery.yml": false, + "rules/cloud/azure/azure_bulk_role_changes.yml": false, + "rules/cloud/azure/azure_device_code_auth_abuse.yml": false, + "rules/cloud/azure/azure_disk_snapshot_exfiltration.yml": false, + "rules/cloud/azure/azure_federation_modified.yml": false, + "rules/cloud/azure/azure_impossible_travel.yml": false, + "rules/cloud/azure/azure_kubernetes_admission_controller.yml": false, + "rules/cloud/azure/azure_kubernetes_events_deleted.yml": false, + "rules/cloud/azure/azure_kubernetes_secret_access.yml": false, + "rules/cloud/azure/azure_laps_credential_dump.yml": false, + "rules/cloud/azure/azure_leaked_credentials.yml": false, + "rules/cloud/azure/azure_new_root_ca_added.yml": false, + "rules/cloud/azure/azure_password_spray_detected.yml": false, + "rules/cloud/azure/azure_prt_access_attempt.yml": false, + "rules/cloud/azure/azure_ropc_authentication.yml": false, + "rules/cloud/azure/azure_sentinel_alert_patterns.yml": false, + "rules/cloud/azure/azure_subscription_permission_elevation.yml": false, + "rules/cloud/azure/azure_temporary_access_pass.yml": false, + "rules/cloud/azure/credential_access_key_vault_modified.yml": false, + "rules/cloud/azure/defender_cloud_critical_alerts.yml": false, + "rules/cloud/azure/defense_evasion_azure_diagnostic_settings_deletion.yml": false, + "rules/cloud/azure/defense_evasion_event_hub_deletion.yml": false, + "rules/cloud/azure/initial_access_azure_active_directory_high_risk_signin.yml": false, + "rules/cloud/azure/initial_access_consent_grant_attack_via_azure_registered_application.yml": false, + "rules/cloud/azure/key_vault_access_spikes.yml": false, + "rules/cloud/azure/persistence_azure_pim_user_added_global_admin.yml": false, + "rules/cloud/azure/persistence_mfa_disabled_for_azure_user.yml": false, + "rules/cloud/azure/pim_role_activation_abuse.yml": false, + "rules/cloud/azure/storage_account_public_access.yml": false, + "rules/cloud/azure/subscription_ownership_transfer.yml": false } } ] diff --git a/rules/cloud/azure/aks_security_threats.yml b/rules/cloud/azure/aks_security_threats.yml index fbb41f16e..34aab83e5 100644 --- a/rules/cloud/azure/aks_security_threats.yml +++ b/rules/cloud/azure/aks_security_threats.yml @@ -1,48 +1,56 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure AKS Container Security Threat Detection +- azure +name: Repeated AKS Resource Changes impact: - confidentiality: 3 - integrity: 3 - availability: 2 + confidentiality: 2 + integrity: 2 + availability: 1 category: Execution -technique: "T1610 - Deploy Container" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/azure/defender-for-cloud/defender-for-containers-introduction - - https://attack.mitre.org/techniques/T1610/ -description: | - Detects suspicious container operations in Azure Kubernetes Service (AKS) including privileged pod creation, container exec commands, and potential container escape attempts. These activities may indicate an attacker attempting to deploy malicious workloads or escape container isolation. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/azure/defender-for-cloud/defender-for-containers-introduction +- https://attack.mitre.org/techniques/T1610/ +description: 'Repeated successful Azure control-plane changes by the same actor. These operations do not by themselves prove container + compromise. + + ' +where: '((equalsIgnoreCase("log.azureKind","activity") || equalsIgnoreCase("log.azureKind","eventgrid")) && equalsIgnoreCase("actionResult","success") + && regexMatch("log.azureOperation","(?i)^Microsoft\\.(ContainerService|Kubernetes)/.+/(write|create|exec)$")) && exists("dataSource") + && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") && exists("log.azureScope") && + !equals("log.azureScope","") && exists("log.azureActorType") && !equals("log.azureActorType","") && exists("log.azureActor") + && !equals("log.azureActor","") - Next Steps: - 1. Review the Kubernetes audit logs for the specific pod or container operation - 2. Check if the container image is from an approved registry - 3. Verify the service account and RBAC permissions used for the operation - 4. Examine pod security context for privileged flags, host network, or host PID access - 5. Review the container command for suspicious payloads or reverse shells - 6. If unauthorized, delete the pod and investigate the cluster for further compromise - 7. Implement Azure Policy for AKS to enforce pod security standards - 8. Enable Microsoft Defender for Containers for runtime protection -where: | - (contains("log.operationName", "Microsoft.ContainerService") || - contains("log.operationName", "MICROSOFT.KUBERNETES")) && - (contains("log.operationName", "write") || - contains("log.operationName", "create") || - contains("log.operationName", "exec")) && - equals("log.resultType", "Success") -afterEvents: - - indexPattern: v11-log-azure-* - with: - - field: origin.ip - operator: filter_term - value: '{{.origin.ip}}' - - field: log.operationName - operator: filter_match - value: 'Container' - within: 30m - count: 10 + ' groupBy: - - lastEvent.log.operationName - - adversary.ip +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip +correlation: +- indexPattern: v11-log-azure-* + with: + - field: dataSource + operator: filter_term + value: '{{.dataSource}}' + - field: log.azureScopeType + operator: filter_term + value: '{{.log.azureScopeType}}' + - field: log.azureScope + operator: filter_term + value: '{{.log.azureScope}}' + - field: log.azureActorType + operator: filter_term + value: '{{.log.azureActorType}}' + - field: log.azureActor + operator: filter_term + value: '{{.log.azureActor}}' + - field: log.correlationCandidate.aks_security_threats + operator: filter_term + value: 'true' + within: 30m + count: 10 diff --git a/rules/cloud/azure/app_registration_abuse.yml b/rules/cloud/azure/app_registration_abuse.yml index 9c46b8d54..9ce8b77fc 100644 --- a/rules/cloud/azure/app_registration_abuse.yml +++ b/rules/cloud/azure/app_registration_abuse.yml @@ -1,46 +1,56 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure AD App Registration with High-Privilege API Permissions +- azure +name: Repeated Entra Application Registration or Consent impact: - confidentiality: 3 - integrity: 3 + confidentiality: 2 + integrity: 2 availability: 1 category: Persistence -technique: "T1098.001 - Account Manipulation: Additional Cloud Credentials" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals - - https://attack.mitre.org/techniques/T1098/001/ -description: | - Detects creation of new Azure AD application registrations which may be used to establish persistence with high-privilege API permissions. Attackers create app registrations with permissions like Mail.ReadWrite, Directory.ReadWrite.All, or RoleManagement.ReadWrite.Directory to maintain access. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals +- https://attack.mitre.org/techniques/T1098/001/ +description: 'Repeated successful application/service-principal creation or consent by one actor in one directory. Review approved + provisioning and consent workflows. + + ' +where: '(equalsIgnoreCase("log.azureKind","audit") && equalsIgnoreCase("actionResult","success") && (equalsIgnoreCase("log.azureOperation","Add + application") || equalsIgnoreCase("log.azureOperation","Add service principal") || equalsIgnoreCase("log.azureOperation","Consent + to application"))) && exists("dataSource") && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") + && exists("log.azureScope") && !equals("log.azureScope","") && exists("log.azureActorType") && !equals("log.azureActorType","") + && exists("log.azureActor") && !equals("log.azureActor","") - Next Steps: - 1. Review the application registration and its requested API permissions - 2. Verify the creator has authorization to register applications - 3. Check if admin consent was granted for the application's permissions - 4. Review the application's redirect URIs for suspicious external domains - 5. Examine the application's credential types (secrets, certificates) - 6. If unauthorized, delete the application registration and revoke any granted consents - 7. Implement app registration policies to restrict who can create applications - 8. Enable admin consent workflow for application permission requests -where: | - (contains("log.operationName", "Add application") || - contains("log.operationName", "Add service principal") || - contains("log.operationName", "Consent to application")) && - equals("log.categoryValue", "Administrative") -afterEvents: - - indexPattern: v11-log-azure-* - with: - - field: origin.user - operator: filter_term - value: '{{.origin.user}}' - - field: log.categoryValue - operator: filter_term - value: Administrative - within: 1h - count: 3 + ' groupBy: - - lastEvent.log.operationName - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip +correlation: +- indexPattern: v11-log-azure-* + with: + - field: dataSource + operator: filter_term + value: '{{.dataSource}}' + - field: log.azureScopeType + operator: filter_term + value: '{{.log.azureScopeType}}' + - field: log.azureScope + operator: filter_term + value: '{{.log.azureScope}}' + - field: log.azureActorType + operator: filter_term + value: '{{.log.azureActorType}}' + - field: log.azureActor + operator: filter_term + value: '{{.log.azureActor}}' + - field: log.correlationCandidate.app_registration_abuse + operator: filter_term + value: 'true' + within: 1h + count: 3 diff --git a/rules/cloud/azure/application_gateway_waf_alerts.yml b/rules/cloud/azure/application_gateway_waf_alerts.yml index 74b7b33a9..d324e2470 100644 --- a/rules/cloud/azure/application_gateway_waf_alerts.yml +++ b/rules/cloud/azure/application_gateway_waf_alerts.yml @@ -1,41 +1,68 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure +- azure name: Application Gateway WAF Security Alerts impact: confidentiality: 3 integrity: 3 availability: 2 category: Initial Access -technique: "T1190 - Exploit Public-Facing Application" +technique: T1190 - Exploit Public-Facing Application adversary: origin references: - - https://learn.microsoft.com/en-us/azure/web-application-firewall/ag/web-application-firewall-logs - - https://attack.mitre.org/techniques/T1190/ -description: | - Detects Web Application Firewall alerts from Azure Application Gateway indicating potential web attacks or malicious activity. This rule triggers when WAF blocks or detects suspicious requests that match security rules. - +- https://learn.microsoft.com/en-us/azure/application-gateway/monitor-application-gateway-reference +- https://learn.microsoft.com/en-us/azure/web-application-firewall/ag/web-application-firewall-logs +- https://attack.mitre.org/techniques/T1190/ +description: 'Detects Web Application Firewall alerts from Azure Application Gateway indicating potential web attacks or malicious + activity. This rule triggers when WAF blocks or detects suspicious requests that match security rules. + + **Next Steps:** + 1. Review the specific WAF rule ID and message details to understand the attack type + 2. Analyze the source IP address for reputation and geographic location + 3. Examine the request URL, headers, and payload for attack indicators + 4. Check for additional requests from the same source IP within the time window + 5. Verify if this is a legitimate application behavior or actual attack attempt + 6. Consider implementing additional WAF rules or IP blocking if confirmed malicious + 7. Review application logs for any successful bypass attempts -where: | - (equals("log.operationName", "ApplicationGatewayFirewallLog") || equals("log.type", "ApplicationGatewayFirewallLog")) && - equals("log.action", "Blocked") && - exists("log.ruleId") -afterEvents: - - indexPattern: v11-log-azure-* - with: - - field: origin.ip - operator: filter_term - value: '{{.origin.ip}}' - within: 10m - count: 5 + + ' +where: '(equalsIgnoreCase("log.azureKind","waf") && equalsIgnoreCase("actionResult","denied") && exists("log.properties.ruleId")) + && exists("dataSource") && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") && exists("log.azureScope") + && !equals("log.azureScope","") && exists("origin.ip") && !equals("origin.ip","") + + ' groupBy: - - lastEvent.log.ruleId - - adversary.ip +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip +correlation: +- indexPattern: v11-log-azure-* + with: + - field: dataSource + operator: filter_term + value: '{{.dataSource}}' + - field: log.azureScopeType + operator: filter_term + value: '{{.log.azureScopeType}}' + - field: log.azureScope + operator: filter_term + value: '{{.log.azureScope}}' + - field: origin.ip + operator: filter_term + value: '{{.origin.ip}}' + - field: log.correlationCandidate.application_gateway_waf_alerts + operator: filter_term + value: 'true' + within: 10m + count: 5 diff --git a/rules/cloud/azure/automation_runbook_abuse.yml b/rules/cloud/azure/automation_runbook_abuse.yml index 0eb8f9cbf..fcffb7f93 100644 --- a/rules/cloud/azure/automation_runbook_abuse.yml +++ b/rules/cloud/azure/automation_runbook_abuse.yml @@ -1,37 +1,30 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure Automation Runbook Abuse +- azure +name: Azure Automation Runbook, Job or Schedule Changed impact: - confidentiality: 3 - integrity: 3 - availability: 2 + confidentiality: 2 + integrity: 2 + availability: 1 category: Execution -technique: "T1059 - Command and Scripting Interpreter" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/azure/automation/automation-runbook-types - - https://attack.mitre.org/techniques/T1059/ -description: | - Detects creation or modification of Azure Automation runbooks which can be abused for code execution with managed identity privileges. Attackers may create runbooks to execute arbitrary code, establish persistence, or perform lateral movement using the automation account's managed identity. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/azure/automation/automation-runbook-types +- https://attack.mitre.org/techniques/T1059/ +description: 'A successful Azure Automation runbook write/publish, job creation or schedule write. The management operation alone + does not prove malicious execution. + + ' +where: '(equalsIgnoreCase("log.azureKind","activity") || equalsIgnoreCase("log.azureKind","eventgrid")) && equalsIgnoreCase("actionResult","success") + && regexMatch("log.azureOperation","(?i)^Microsoft\\.Automation/automationAccounts/(runbooks/(write|publish/action)|jobs/write|schedules/write)$") - Next Steps: - 1. Review the runbook content for malicious scripts or commands - 2. Verify the automation account's managed identity permissions - 3. Check the user creating or modifying the runbook has authorization - 4. Review the runbook schedule for unauthorized execution times - 5. Examine the runbook's Run As account credentials - 6. If unauthorized, disable the runbook and revoke the automation account's permissions - 7. Review execution history for already-executed malicious runbooks - 8. Implement RBAC to restrict automation account management -where: | - contains("log.operationName", "Microsoft.Automation") && - (contains("log.operationName", "runbooks/write") || - contains("log.operationName", "runbooks/publish") || - contains("log.operationName", "jobs/write") || - contains("log.operationName", "schedules/write")) && - equals("log.resultType", "Success") + ' groupBy: - - lastEvent.log.operationName - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_ad_impossible_travel.yml b/rules/cloud/azure/azure_ad_impossible_travel.yml index aa8c9dfce..ee04974ef 100644 --- a/rules/cloud/azure/azure_ad_impossible_travel.yml +++ b/rules/cloud/azure/azure_ad_impossible_travel.yml @@ -1,36 +1,29 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure AD Impossible Travel Sign-In Detection +- azure +name: Entra Risky Sign-in impact: - confidentiality: 3 + confidentiality: 2 integrity: 2 availability: 1 category: Credential Access -technique: "T1078 - Valid Accounts" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/azure/active-directory/identity-protection/concept-identity-protection-risks - - https://attack.mitre.org/techniques/T1078/ -description: | - Detects Azure AD sign-ins flagged as risky due to impossible travel, anonymous IP usage, or unfamiliar locations. These risk detections indicate potential credential compromise when a user authenticates from geographically impossible locations or through anonymizing services. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/azure/active-directory/identity-protection/concept-identity-protection-risks +- https://attack.mitre.org/techniques/T1078/ +description: 'The sign-in carries a high risk level or an active risk state. This signal does not establish impossible travel. + + ' +where: 'equalsIgnoreCase("log.azureKind","signin") && (equalsIgnoreCase("log.properties.riskState","atRisk") || equalsIgnoreCase("log.properties.riskState","confirmedCompromised") + || equalsIgnoreCase("log.properties.riskLevelDuringSignIn","high")) && !(equalsIgnoreCase("actionResult","success") && equalsIgnoreCase("log.properties.riskLevelDuringSignIn","high")) - Next Steps: - 1. Review the sign-in details including IP addresses and geographic locations - 2. Check if the user employs VPN services that could explain different locations - 3. Verify with the user whether the sign-in attempts are legitimate - 4. Review the risk level and risk detail provided by Azure AD Identity Protection - 5. Check for MFA challenges and their outcomes during the sign-in - 6. If compromised, immediately reset user credentials and revoke active sessions - 7. Enable Conditional Access policies requiring MFA for risky sign-ins - 8. Review Azure AD sign-in logs for other accounts from the same suspicious IPs -where: | - contains("log.operationName", "Sign-in activity") && - (equals("log.properties.riskLevelDuringSignIn", "high") || - equals("log.properties.riskState", "atRisk") || - contains("log.properties.riskEventTypes", "impossibleTravel") || - contains("log.properties.riskEventTypes", "anonymizedIPAddress")) + ' groupBy: - - adversary.ip - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_ad_password_spray.yml b/rules/cloud/azure/azure_ad_password_spray.yml index ae2170e08..e27d44a9b 100644 --- a/rules/cloud/azure/azure_ad_password_spray.yml +++ b/rules/cloud/azure/azure_ad_password_spray.yml @@ -1,47 +1,51 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure AD Password Spray Attack Detection +- azure +name: Repeated Entra Invalid-Credential Sign-ins impact: - confidentiality: 3 + confidentiality: 2 integrity: 2 availability: 1 category: Credential Access -technique: "T1110 - Brute Force" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/azure/active-directory/identity-protection/concept-identity-protection-risks - - https://attack.mitre.org/techniques/T1110/ -description: | - Detects password spray attacks against Azure AD by correlating failed sign-in attempts across multiple usernames from the same source IP within a short time window. Password spraying tries common passwords against many accounts to avoid account lockout thresholds. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/azure/active-directory/identity-protection/concept-identity-protection-risks +- https://attack.mitre.org/techniques/T1110/ +description: 'At least 15 sign-ins report invalid credentials (50126) from one client address in 15 minutes in the same directory + and collector. This threshold does not establish distinct-user password spraying; locked/disabled accounts are excluded. + + ' +where: '(equalsIgnoreCase("log.azureKind","signin") && equalsIgnoreCase("actionResult","failure") && equals("log.properties.status.errorCode",50126)) + && exists("dataSource") && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") && exists("log.azureScope") + && !equals("log.azureScope","") && exists("origin.ip") && !equals("origin.ip","") - Next Steps: - 1. Identify the source IP and check threat intelligence feeds for known malicious sources - 2. Review the list of targeted user accounts for patterns (executives, admins, service accounts) - 3. Check if any of the targeted accounts subsequently had successful logins - 4. Verify that account lockout policies are properly configured - 5. Block the source IP at the network level if confirmed malicious - 6. Enable Azure AD Smart Lockout for brute force protection - 7. Implement Conditional Access policies requiring MFA - 8. Review password policies and enforce complexity requirements -where: | - contains("log.operationName", "Sign-in activity") && - (equals("log.properties.status.errorCode", "50126") || - equals("log.properties.status.errorCode", "50053") || - equals("log.properties.status.errorCode", "50057")) && - exists("origin.ip") -afterEvents: - - indexPattern: v11-log-azure-* - with: - - field: origin.ip - operator: filter_term - value: '{{.origin.ip}}' - - field: log.properties.status.errorCode - operator: filter_match - value: '5005' - within: 15m - count: 15 + ' groupBy: - - lastEvent.log.operationName - - adversary.ip +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip +correlation: +- indexPattern: v11-log-azure-* + with: + - field: dataSource + operator: filter_term + value: '{{.dataSource}}' + - field: log.azureScopeType + operator: filter_term + value: '{{.log.azureScopeType}}' + - field: log.azureScope + operator: filter_term + value: '{{.log.azureScope}}' + - field: origin.ip + operator: filter_term + value: '{{.origin.ip}}' + - field: log.correlationCandidate.azure_ad_password_spray + operator: filter_term + value: 'true' + within: 15m + count: 15 diff --git a/rules/cloud/azure/azure_alert_suppression_rule.yml b/rules/cloud/azure/azure_alert_suppression_rule.yml index 72188d45a..257a1cb17 100644 --- a/rules/cloud/azure/azure_alert_suppression_rule.yml +++ b/rules/cloud/azure/azure_alert_suppression_rule.yml @@ -1,30 +1,45 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure +- azure name: Azure Security Alert Suppression Rule Created impact: confidentiality: 2 integrity: 3 availability: 2 category: Defense Evasion -technique: "T1562 - Impair Defenses" +technique: T1562 - Impair Defenses adversary: origin references: - - https://learn.microsoft.com/en-us/azure/defender-for-cloud/alerts-suppression-rules - - https://attack.mitre.org/techniques/T1562/ -description: | - Detects creation of alert suppression rules in Azure Defender / Microsoft Defender for Cloud. Attackers create suppression rules to hide security alerts generated by their activities. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/azure/defender-for-cloud/alerts-suppression-rules +- https://attack.mitre.org/techniques/T1562/ +description: 'Detects creation of alert suppression rules in Azure Defender / Microsoft Defender for Cloud. Attackers create suppression + rules to hide security alerts generated by their activities. + Next Steps: + 1. Review the suppression rule and what alert types it suppresses + 2. Verify the rule creation was part of an authorized security operations workflow + 3. Check the user identity for legitimate security team membership + 4. Review recent security alerts that may have been suppressed + 5. If unauthorized, delete the suppression rule and review suppressed alerts + 6. Check for other defense evasion activities from the same user -where: | - regexMatch("log.operationName", "(?i)MICROSOFT\\.SECURITY/ALERTSSUPPRESSIONRULES/WRITE") + + ' +where: '(equalsIgnoreCase("log.azureKind","activity") || equalsIgnoreCase("log.azureKind","eventgrid")) && equalsIgnoreCase("actionResult","success") + && equalsIgnoreCase("log.azureOperation","Microsoft.Security/alertsSuppressionRules/write") + + ' groupBy: - - lastEvent.log.operationName - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_anomalous_token.yml b/rules/cloud/azure/azure_anomalous_token.yml index d6340db29..e677c1ad5 100644 --- a/rules/cloud/azure/azure_anomalous_token.yml +++ b/rules/cloud/azure/azure_anomalous_token.yml @@ -1,32 +1,49 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure +- azure name: Azure AD Anomalous Token Detection impact: confidentiality: 3 integrity: 2 availability: 1 category: Credential Access -technique: "T1528 - Steal Application Access Token" +technique: T1528 - Steal Application Access Token adversary: origin references: - - https://learn.microsoft.com/en-us/entra/id-protection/concept-identity-protection-risks - - https://attack.mitre.org/techniques/T1528/ -description: | - Detects Azure Identity Protection alerts for anomalous tokens with unusual lifetime, unfamiliar locations, or other suspicious properties. These indicate potential token theft or manipulation. +- https://learn.microsoft.com/en-us/graph/api/resources/riskdetection?view=graph-rest-1.0 +- https://learn.microsoft.com/en-us/entra/id-protection/concept-identity-protection-risks +- https://attack.mitre.org/techniques/T1528/ +description: 'Detects Azure Identity Protection alerts for anomalous tokens with unusual lifetime, unfamiliar locations, or other + suspicious properties. These indicate potential token theft or manipulation. + Next Steps: + 1. Review the token properties that triggered the anomaly detection - 2. Check the user's recent sign-in activity for suspicious patterns + + 2. Check the user''s recent sign-in activity for suspicious patterns + 3. Verify the source IP and device used for the authentication + 4. Check for impossible travel or unfamiliar location patterns + 5. If compromise is suspected, revoke all refresh tokens for the user + 6. Force MFA re-registration if MFA token was compromised + 7. Review conditional access policies for token protection gaps -where: | - contains("log.operationName", "Anomalous Token") || - (contains("log.properties", "riskEventType") && contains("log.properties", "anomalousToken")) + + ' +where: '(equalsIgnoreCase("log.azureKind","risk") && oneOf("log.properties.riskEventType",["anomalousToken"]) && !oneOf("log.properties.riskState",["dismissed", + "remediated", "confirmedSafe"])) || (equalsIgnoreCase("log.azureKind","signin") && (exists("log.properties.riskEventTypes_v2.#(==\"anomalousToken\")") + || exists("log.properties.riskEventTypesv2.#(==\"anomalousToken\")") || exists("log.properties.riskEventTypes.#(==\"anomalousToken\")")) + && !oneOf("log.properties.riskState",["dismissed", "remediated", "confirmedSafe"])) + + ' groupBy: - - adversary.ip - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_app_credential_added.yml b/rules/cloud/azure/azure_app_credential_added.yml index 235479737..7605f2eff 100644 --- a/rules/cloud/azure/azure_app_credential_added.yml +++ b/rules/cloud/azure/azure_app_credential_added.yml @@ -1,32 +1,32 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure AD Application Credential Added +- azure +name: Entra Application Credential Configuration Changed impact: - confidentiality: 3 - integrity: 3 - availability: 2 + confidentiality: 2 + integrity: 2 + availability: 1 category: Persistence -technique: "T1098.001 - Account Manipulation: Additional Cloud Credentials" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-service-principal-portal - - https://attack.mitre.org/techniques/T1098/001/ -description: | - Detects when new certificates or client secrets are added to Azure AD application registrations. This is the primary Azure AD persistence technique - attackers add credentials to existing apps to maintain access even after password resets. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-service-principal-portal +- https://attack.mitre.org/techniques/T1098/001/ +description: 'A completed explicit credential-management operation or application credential-property update. The property update + may add, replace or remove credentials; inspect the old and new values. + + ' +where: 'equalsIgnoreCase("log.azureKind","audit") && equalsIgnoreCase("actionResult","success") && ((equalsIgnoreCase("log.azureOperation","Add + service principal credentials") || equalsIgnoreCase("log.azureOperation","Update application - Certificates and secrets management")) + || (equalsIgnoreCase("log.azureOperation","Update application") && (exists("log.properties.targetResources.#(modifiedProperties.#(displayName==\"KeyCredentials\"))") + || exists("log.properties.targetResources.#(modifiedProperties.#(displayName==\"PasswordCredentials\"))")))) - Next Steps: - 1. Verify the credential addition was authorized by the application owner - 2. Identify the application and its permissions (especially Graph API permissions) - 3. Check the user identity adding the credential for legitimacy - 4. Review the credential type (certificate vs secret) and expiration - 5. Check for subsequent sign-ins using the new application credential - 6. If unauthorized, remove the credential and rotate all app secrets - 7. Review the application's API permissions for excessive access -where: | - oneOf("log.operationName", ["Add service principal credentials", "Update application - Certificates and secrets management"]) || - (contains("log.operationName", "application") && contains("log.properties", "KeyCredentials")) + ' groupBy: - - lastEvent.log.properties.targetResources - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_app_privileged_permissions.yml b/rules/cloud/azure/azure_app_privileged_permissions.yml index 007833d6d..23cd4c6bb 100644 --- a/rules/cloud/azure/azure_app_privileged_permissions.yml +++ b/rules/cloud/azure/azure_app_privileged_permissions.yml @@ -1,32 +1,30 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure AD Privileged App Role Assignment +- azure +name: Entra Application Role Assignment impact: - confidentiality: 3 - integrity: 3 - availability: 2 + confidentiality: 2 + integrity: 2 + availability: 1 category: Privilege Escalation -technique: "T1098.003 - Account Manipulation: Additional Cloud Roles" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals - - https://attack.mitre.org/techniques/T1098/003/ -description: | - Detects privileged app role assignments to service principals in Azure AD, which is the mechanism used in illicit consent grant attacks. Attackers create or modify applications with high-privilege API permissions to access organizational data. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals +- https://attack.mitre.org/techniques/T1098/003/ +description: 'A successful application role assignment. Permission privilege and malicious intent require reviewing the specific + grant; consent has a separate rule. + + ' +where: 'equalsIgnoreCase("log.azureKind","audit") && equalsIgnoreCase("actionResult","success") && equalsIgnoreCase("log.azureOperation","Add + app role assignment to service principal") - Next Steps: - 1. Review the application and the specific API permissions granted - 2. Verify the consent was authorized by a legitimate administrator - 3. Check if the application is known and trusted - 4. Review the application publisher and redirect URIs - 5. Check for data access using the application's permissions - 6. If unauthorized, remove the role assignment and revoke application consent - 7. Review and restrict user consent settings in Azure AD -where: | - contains("log.operationName", "Add app role assignment to service principal") || - (contains("log.operationName", "Consent to application") && contains("log.properties", "AppRoleAssignment")) + ' groupBy: - - lastEvent.log.properties.targetResources - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_azurehound_discovery.yml b/rules/cloud/azure/azure_azurehound_discovery.yml index 8123bb09d..9ebef2a8a 100644 --- a/rules/cloud/azure/azure_azurehound_discovery.yml +++ b/rules/cloud/azure/azure_azurehound_discovery.yml @@ -1,32 +1,28 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: AzureHound Reconnaissance Tool Detected +- azure +name: Entra Sign-in with AzureHound User Agent impact: confidentiality: 2 - integrity: 1 - availability: 0 + integrity: 2 + availability: 1 category: Discovery -technique: "T1087.004 - Account Discovery: Cloud Account" +technique: '' adversary: origin references: - - https://bloodhound.readthedocs.io/en/latest/data-collection/azurehound.html - - https://attack.mitre.org/techniques/T1087/004/ -description: | - Detects AzureHound user agent in Azure AD sign-in logs. AzureHound is the Azure AD data collector for BloodHound, used to enumerate all users, groups, roles, apps, and relationships in the tenant for attack path analysis. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://bloodhound.readthedocs.io/en/latest/data-collection/azurehound.html +- https://attack.mitre.org/techniques/T1087/004/ +description: 'A sign-in reports an AzureHound user agent. This string can be changed or spoofed and does not prove discovery completed. + + ' +where: 'equalsIgnoreCase("log.azureKind","signin") && regexMatch("log.properties.userAgent","(?i)(^|[^a-z])azurehound([^a-z]|$)") - Next Steps: - 1. Identify the user account running AzureHound - 2. Determine if this is an authorized security assessment - 3. Review the scope of data collected (users, groups, roles, apps) - 4. Check for lateral movement or privilege escalation following the enumeration - 5. If unauthorized, revoke the user's tokens and investigate - 6. Review API permissions that allowed the enumeration - 7. Consider implementing Graph API rate limiting or monitoring -where: | - contains("log.properties", "azurehound") || - contains("log.properties", "AzureHound") + ' deduplicateBy: - - adversary.user - - adversary.ip +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_bulk_role_changes.yml b/rules/cloud/azure/azure_bulk_role_changes.yml index 9ab8cc4ed..9a6682e84 100644 --- a/rules/cloud/azure/azure_bulk_role_changes.yml +++ b/rules/cloud/azure/azure_bulk_role_changes.yml @@ -1,39 +1,75 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure +- azure name: Azure AD Bulk Privileged Role Assignment Changes impact: confidentiality: 3 integrity: 3 availability: 2 category: Privilege Escalation -technique: "T1098 - Account Manipulation" +technique: T1098 - Account Manipulation adversary: origin references: - - https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-resource-roles-assign-roles - - https://attack.mitre.org/techniques/T1098/ -description: | - Detects mass privileged role assignment changes in Azure AD. Multiple role assignments in a short time window indicate an attacker rapidly escalating privileges across multiple accounts for persistence and lateral movement. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-resource-roles-assign-roles +- https://attack.mitre.org/techniques/T1098/ +description: 'Detects mass privileged role assignment changes in Azure AD. Multiple role assignments in a short time window indicate + an attacker rapidly escalating privileges across multiple accounts for persistence and lateral movement. + Next Steps: + 1. Review all role assignments made in the burst + 2. Identify the admin account making the changes + 3. Check if these changes were part of an approved onboarding or migration + 4. Review the specific roles assigned (Global Admin, Exchange Admin, etc.) + 5. If unauthorized, revert all role assignments and investigate the admin account + 6. Enable Azure PIM for just-in-time role activation -where: | - contains("log.operationName", "Add member to role") || - contains("log.operationName", "Add eligible member to role") -afterEvents: - - indexPattern: v11-log-azure-* - with: - - field: origin.ip - operator: filter_term - value: '{{.origin.ip}}' - within: 30m - count: 10 + + ' +where: '(equalsIgnoreCase("log.azureKind","audit") && equalsIgnoreCase("actionResult","success") && (equalsIgnoreCase("log.azureOperation","Add + member to role") || equalsIgnoreCase("log.azureOperation","Add eligible member to role") || equalsIgnoreCase("log.azureOperation","Add + member to role completed (PIM activation)") || equalsIgnoreCase("log.azureOperation","Add eligible member to role in PIM completed") + || equalsIgnoreCase("log.azureOperation","Add eligible member to role in PIM completed (permanent)") || equalsIgnoreCase("log.azureOperation","Add + eligible member to role in PIM completed (timebound)") || equalsIgnoreCase("log.azureOperation","Add member to role in PIM completed + (permanent)") || equalsIgnoreCase("log.azureOperation","Add member to role in PIM completed (timebound)"))) && exists("dataSource") + && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") && exists("log.azureScope") && + !equals("log.azureScope","") && exists("log.azureActorType") && !equals("log.azureActorType","") && exists("log.azureActor") + && !equals("log.azureActor","") + + ' groupBy: - - adversary.ip - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip +correlation: +- indexPattern: v11-log-azure-* + with: + - field: dataSource + operator: filter_term + value: '{{.dataSource}}' + - field: log.azureScopeType + operator: filter_term + value: '{{.log.azureScopeType}}' + - field: log.azureScope + operator: filter_term + value: '{{.log.azureScope}}' + - field: log.azureActorType + operator: filter_term + value: '{{.log.azureActorType}}' + - field: log.azureActor + operator: filter_term + value: '{{.log.azureActor}}' + - field: log.correlationCandidate.azure_bulk_role_changes + operator: filter_term + value: 'true' + within: 30m + count: 10 diff --git a/rules/cloud/azure/azure_device_code_auth_abuse.yml b/rules/cloud/azure/azure_device_code_auth_abuse.yml index feaef879c..50308b8eb 100644 --- a/rules/cloud/azure/azure_device_code_auth_abuse.yml +++ b/rules/cloud/azure/azure_device_code_auth_abuse.yml @@ -1,32 +1,31 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure AD Device Code Authentication Flow Detected +- azure +name: High Risk Device Code Sign-in impact: - confidentiality: 3 + confidentiality: 2 integrity: 2 availability: 1 category: Initial Access -technique: "T1078 - Valid Accounts" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-device-code - - https://attack.mitre.org/techniques/T1078/ -description: | - Detects OAuth device code flow authentication in Azure AD. Device code phishing is a growing attack vector where attackers trick users into authenticating on a device the attacker controls, granting the attacker access tokens. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-device-code +- https://attack.mitre.org/techniques/T1078/ +description: 'A successful device-code sign-in carries high or active risk. Routine device-code use without that risk signal is + excluded. + + ' +where: 'equalsIgnoreCase("log.azureKind","signin") && equalsIgnoreCase("actionResult","success") && equalsIgnoreCase("log.properties.authenticationProtocol","deviceCode") + && (equalsIgnoreCase("log.properties.riskState","atRisk") || equalsIgnoreCase("log.properties.riskState","confirmedCompromised") + || equalsIgnoreCase("log.properties.riskLevelDuringSignIn","high")) - Next Steps: - 1. Verify the device code authentication was initiated by the user on a legitimate device - 2. Check the application requesting the device code for legitimacy - 3. Review the source IP where the token was redeemed - 4. Check for subsequent suspicious activities using the obtained token - 5. If unauthorized, revoke the session and all refresh tokens - 6. Consider blocking device code flow via conditional access policies - 7. Educate users about device code phishing attacks -where: | - (contains("log.properties", "deviceCode") && contains("log.operationName", "Sign-in")) || - contains("log.properties", "urn:ietf:params:oauth:grant-type:device_code") + ' groupBy: - - adversary.ip - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_disk_snapshot_exfiltration.yml b/rules/cloud/azure/azure_disk_snapshot_exfiltration.yml index 4ed4ff513..ddaac470a 100644 --- a/rules/cloud/azure/azure_disk_snapshot_exfiltration.yml +++ b/rules/cloud/azure/azure_disk_snapshot_exfiltration.yml @@ -1,36 +1,29 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure Disk Snapshot Exfiltration +- azure +name: Azure Disk or Snapshot Access Granted impact: - confidentiality: 3 + confidentiality: 2 integrity: 2 availability: 1 category: Data Exfiltration -technique: "T1537 - Transfer Data to Cloud Account" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/azure/virtual-machines/disks-incremental-snapshots - - https://attack.mitre.org/techniques/T1537/ -description: | - Detects Azure disk snapshot operations that could be used for data exfiltration, including sharing snapshots across subscriptions, generating SAS URIs for download, or copying snapshots to external storage accounts. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/azure/virtual-machines/disks-incremental-snapshots +- https://attack.mitre.org/techniques/T1537/ +description: 'A successful disk/snapshot access-export request. This grants access; it does not prove data exfiltration occurred. + + ' +where: '(equalsIgnoreCase("log.azureKind","activity") || equalsIgnoreCase("log.azureKind","eventgrid")) && equalsIgnoreCase("actionResult","success") + && regexMatch("log.azureOperation","(?i)^Microsoft\\.Compute/(snapshots|disks)/(beginGetAccess/action|export/action)$") - Next Steps: - 1. Identify the disk snapshot and the virtual machine it was taken from - 2. Review the target location or account where the snapshot is being shared - 3. Verify the operator has authorization for cross-subscription snapshot operations - 4. Check if a SAS URI was generated that could allow external download - 5. Review the data sensitivity of the affected virtual machine's disk - 6. If unauthorized, revoke any generated SAS tokens and delete shared snapshots - 7. Implement Azure Policy to restrict snapshot sharing across subscriptions - 8. Enable diagnostic logging for disk operations -where: | - (contains("log.operationName", "Microsoft.Compute/snapshots") || - contains("log.operationName", "Microsoft.Compute/disks")) && - (contains("log.operationName", "beginGetAccess") || - contains("log.operationName", "export")) && - equals("log.resultType", "Success") + ' groupBy: - - lastEvent.log.operationName - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_federation_modified.yml b/rules/cloud/azure/azure_federation_modified.yml index bd7c87890..d1c2ee687 100644 --- a/rules/cloud/azure/azure_federation_modified.yml +++ b/rules/cloud/azure/azure_federation_modified.yml @@ -1,32 +1,31 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure AD Federation Settings Modified +- azure +name: Entra Domain Federation Configuration Changed impact: - confidentiality: 3 - integrity: 3 - availability: 3 + confidentiality: 2 + integrity: 2 + availability: 1 category: Credential Access -technique: "T1556 - Modify Authentication Process" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/whatis-fed - - https://attack.mitre.org/techniques/T1556/ -description: | - Detects modifications to Azure AD domain federation settings. Changing federation configuration is a critical attack technique that enables Golden SAML attacks and domain takeover, allowing attackers to forge authentication tokens for any user. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/whatis-fed +- https://attack.mitre.org/techniques/T1556/ +description: 'A successful federation/authentication configuration change. Review the configuration diff and approved administration; + this is not proof of a forged SAML token. + + ' +where: 'equalsIgnoreCase("log.azureKind","audit") && equalsIgnoreCase("actionResult","success") && (equalsIgnoreCase("log.azureOperation","Set + federation settings on domain") || equalsIgnoreCase("log.azureOperation","Set domain authentication") || equalsIgnoreCase("log.azureOperation","Set + DomainFederationSettings")) - Next Steps: - 1. Immediately verify the federation modification was authorized - 2. Check the user identity and source IP performing the change - 3. Review the new federation settings for suspicious IdP configurations - 4. Validate the signing certificate in the federation configuration - 5. Check for subsequent sign-ins using federated authentication - 6. If unauthorized, revert the federation changes and investigate all federated sessions - 7. Review all privileged role assignments that occurred after the change -where: | - contains("log.operationName", "Set federation settings on domain") || - (contains("log.operationName", "Set domain authentication") && contains("log.properties", "Federated")) + ' groupBy: - - lastEvent.log.operationName - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_impossible_travel.yml b/rules/cloud/azure/azure_impossible_travel.yml index 58092cb91..010a61e94 100644 --- a/rules/cloud/azure/azure_impossible_travel.yml +++ b/rules/cloud/azure/azure_impossible_travel.yml @@ -1,32 +1,51 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure +- azure name: Azure AD Impossible Travel Sign-In impact: confidentiality: 3 integrity: 2 availability: 1 category: Initial Access -technique: "T1078 - Valid Accounts" +technique: T1078 - Valid Accounts adversary: origin references: - - https://learn.microsoft.com/en-us/entra/id-protection/concept-identity-protection-risks - - https://attack.mitre.org/techniques/T1078/ -description: | - Detects Azure Identity Protection impossible travel alerts where a user signs in from geographically distant locations in a timeframe that makes physical travel impossible. This strongly indicates credential theft or session hijacking. +- https://learn.microsoft.com/en-us/graph/api/resources/riskdetection?view=graph-rest-1.0 +- https://learn.microsoft.com/en-us/entra/id-protection/concept-identity-protection-risks +- https://attack.mitre.org/techniques/T1078/ +description: 'Detects Azure Identity Protection impossible travel alerts where a user signs in from geographically distant locations + in a timeframe that makes physical travel impossible. This strongly indicates credential theft or session hijacking. + Next Steps: + 1. Contact the user to verify both sign-in locations + 2. Check if a VPN or proxy could explain the geolocation discrepancy + 3. Review the sign-in details (device, browser, app) for both locations + 4. If unauthorized, force password reset and revoke all sessions + 5. Review data access and actions from the suspicious location + 6. Enable location-based conditional access policies + 7. Check for other users with similar patterns from the same locations -where: | - contains("log.operationName", "Impossible Travel") || - (contains("log.properties", "riskEventType") && contains("log.properties", "impossibleTravel")) + + ' +where: '(equalsIgnoreCase("log.azureKind","risk") && oneOf("log.properties.riskEventType",["impossibleTravel", "unlikelyTravel"]) + && !oneOf("log.properties.riskState",["dismissed", "remediated", "confirmedSafe"])) || (equalsIgnoreCase("log.azureKind","signin") + && (exists("log.properties.riskEventTypes_v2.#(==\"impossibleTravel\")") || exists("log.properties.riskEventTypes_v2.#(==\"unlikelyTravel\")") + || exists("log.properties.riskEventTypesv2.#(==\"impossibleTravel\")") || exists("log.properties.riskEventTypesv2.#(==\"unlikelyTravel\")") + || exists("log.properties.riskEventTypes.#(==\"impossibleTravel\")") || exists("log.properties.riskEventTypes.#(==\"unlikelyTravel\")")) + && !oneOf("log.properties.riskState",["dismissed", "remediated", "confirmedSafe"])) + + ' groupBy: - - adversary.ip - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_kubernetes_admission_controller.yml b/rules/cloud/azure/azure_kubernetes_admission_controller.yml index 9d5c9a3eb..67c121061 100644 --- a/rules/cloud/azure/azure_kubernetes_admission_controller.yml +++ b/rules/cloud/azure/azure_kubernetes_admission_controller.yml @@ -1,32 +1,46 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure +- azure name: Azure Kubernetes Admission Webhook Modified impact: confidentiality: 3 integrity: 3 availability: 2 category: Persistence -technique: "T1078.004 - Valid Accounts: Cloud Accounts" +technique: 'T1078.004 - Valid Accounts: Cloud Accounts' adversary: origin references: - - https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/ - - https://attack.mitre.org/techniques/T1078/004/ -description: | - Detects creation or modification of MutatingAdmissionWebhook or ValidatingAdmissionWebhook configurations in Azure Kubernetes Service. Attackers use admission controllers to inject malicious containers or modify workload specifications. +- https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/aksaudit +- https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/ +- https://attack.mitre.org/techniques/T1078/004/ +description: 'Detects creation or modification of MutatingAdmissionWebhook or ValidatingAdmissionWebhook configurations in Azure + Kubernetes Service. Attackers use admission controllers to inject malicious containers or modify workload specifications. + Next Steps: + 1. Review the webhook configuration and its target service + 2. Verify the webhook was created as part of a legitimate deployment - 3. Check the webhook's namespace selector and object selector + + 3. Check the webhook''s namespace selector and object selector + 4. Examine what resources the webhook intercepts (pods, deployments, etc.) + 5. If unauthorized, delete the webhook and audit all recent pod deployments + 6. Review cluster RBAC for excessive admission controller permissions -where: | - contains("log.operationName", "MICROSOFT.CONTAINERSERVICE") && - (contains("log.properties", "MutatingWebhookConfiguration") || contains("log.properties", "ValidatingWebhookConfiguration")) && - (contains("log.properties", "create") || contains("log.properties", "update") || contains("log.properties", "patch")) + + ' +where: 'equalsIgnoreCase("log.azureKind","kubernetes") && equalsIgnoreCase("log.azureKubernetes.stage","ResponseComplete") && equalsIgnoreCase("actionResult","success") + && oneOf("log.azureKubernetes.objectRef.resource",["mutatingwebhookconfigurations", "validatingwebhookconfigurations"]) && oneOf("log.azureKubernetes.verb",["create", + "update", "patch"]) + + ' groupBy: - - lastEvent.log.resourceId - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_kubernetes_events_deleted.yml b/rules/cloud/azure/azure_kubernetes_events_deleted.yml index 0e1b106b1..5ca2c0b50 100644 --- a/rules/cloud/azure/azure_kubernetes_events_deleted.yml +++ b/rules/cloud/azure/azure_kubernetes_events_deleted.yml @@ -1,31 +1,45 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure +- azure name: Azure Kubernetes Events Deleted impact: confidentiality: 1 integrity: 3 availability: 2 category: Defense Evasion -technique: "T1562.001 - Impair Defenses: Disable or Modify Tools" +technique: 'T1562.001 - Impair Defenses: Disable or Modify Tools' adversary: origin references: - - https://learn.microsoft.com/en-us/azure/aks/monitor-aks - - https://attack.mitre.org/techniques/T1562/001/ -description: | - Detects deletion of Kubernetes events in Azure Kubernetes Service (AKS). Attackers delete events to cover traces of their activities within the cluster. +- https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/aksaudit +- https://learn.microsoft.com/en-us/azure/aks/monitor-aks +- https://attack.mitre.org/techniques/T1562/001/ +description: 'Detects deletion of Kubernetes events in Azure Kubernetes Service (AKS). Attackers delete events to cover traces + of their activities within the cluster. + Next Steps: + 1. Identify the user or service account that deleted the events + 2. Check for other suspicious Kubernetes operations from the same identity + 3. Review AKS audit logs for activities that occurred before the event deletion + 4. Verify if this was part of a legitimate cluster maintenance operation + 5. If unauthorized, investigate the cluster for signs of compromise + 6. Review RBAC policies to restrict event deletion permissions -where: | - contains("log.operationName", "MICROSOFT.CONTAINERSERVICE") && - (contains("log.properties", "events") && contains("log.properties", "delete")) + + ' +where: 'equalsIgnoreCase("log.azureKind","kubernetes") && equalsIgnoreCase("log.azureKubernetes.stage","ResponseComplete") && equalsIgnoreCase("actionResult","success") + && equalsIgnoreCase("log.azureKubernetes.objectRef.resource","events") && oneOf("log.azureKubernetes.verb",["delete", "deletecollection"]) + + ' groupBy: - - lastEvent.log.resourceId - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_kubernetes_secret_access.yml b/rules/cloud/azure/azure_kubernetes_secret_access.yml index 0c80809b8..d82626165 100644 --- a/rules/cloud/azure/azure_kubernetes_secret_access.yml +++ b/rules/cloud/azure/azure_kubernetes_secret_access.yml @@ -1,40 +1,57 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure Kubernetes Secret Write or Delete +- azure +name: Repeated AKS Secret Writes or Deletes impact: - confidentiality: 3 - integrity: 3 - availability: 2 + confidentiality: 2 + integrity: 2 + availability: 1 category: Credential Access -technique: "T1552.007 - Unsecured Credentials: Container API" +technique: '' adversary: origin references: - - https://kubernetes.io/docs/concepts/configuration/secret/ - - https://attack.mitre.org/techniques/T1552/007/ -description: | - Detects write or delete operations on Kubernetes Secrets in Azure Kubernetes Service. Secrets contain sensitive data like service account tokens, TLS certificates, and database credentials. Unauthorized access indicates potential credential theft or data tampering. +- https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/aksaudit +- https://kubernetes.io/docs/concepts/configuration/secret/ +- https://attack.mitre.org/techniques/T1552/007/ +description: 'At least five completed successful Secret writes/deletes by one actor in one cluster and collector in 30 minutes. + This detects modification, not secret reading or credential theft; routine controllers and deployment automation require environment-specific + tuning. + + ' +where: '(equalsIgnoreCase("log.azureKind","kubernetes") && equalsIgnoreCase("log.azureKubernetes.stage","ResponseComplete") && + equalsIgnoreCase("actionResult","success") && equalsIgnoreCase("log.azureKubernetes.objectRef.resource","secrets") && oneOf("log.azureKubernetes.verb",["create", + "update", "delete", "deletecollection", "patch"])) && exists("dataSource") && !equals("dataSource","") && exists("log.azureScopeType") + && !equals("log.azureScopeType","") && exists("log.azureScope") && !equals("log.azureScope","") && exists("log.azureActorType") + && !equals("log.azureActorType","") && exists("log.azureActor") && !equals("log.azureActor","") - Next Steps: - 1. Identify the user or service account accessing the secrets - 2. Review which secrets were accessed, modified, or deleted - 3. Check if the operation was part of a legitimate deployment workflow - 4. Audit the RBAC permissions of the identity performing the action - 5. If unauthorized, rotate all affected secrets immediately - 6. Review pod specifications for secrets mounted as volumes or environment variables -where: | - contains("log.operationName", "MICROSOFT.CONTAINERSERVICE") && - contains("log.properties", "secrets") && - (contains("log.properties", "create") || contains("log.properties", "update") || contains("log.properties", "delete") || contains("log.properties", "patch")) -afterEvents: - - indexPattern: v11-log-azure-* - with: - - field: origin.ip - operator: filter_term - value: '{{.origin.ip}}' - within: 30m - count: 5 + ' groupBy: - - lastEvent.log.resourceId - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip +correlation: +- indexPattern: v11-log-azure-* + with: + - field: dataSource + operator: filter_term + value: '{{.dataSource}}' + - field: log.azureScopeType + operator: filter_term + value: '{{.log.azureScopeType}}' + - field: log.azureScope + operator: filter_term + value: '{{.log.azureScope}}' + - field: log.azureActorType + operator: filter_term + value: '{{.log.azureActorType}}' + - field: log.azureActor + operator: filter_term + value: '{{.log.azureActor}}' + - field: log.correlationCandidate.azure_kubernetes_secret_access + operator: filter_term + value: 'true' + within: 30m + count: 5 diff --git a/rules/cloud/azure/azure_laps_credential_dump.yml b/rules/cloud/azure/azure_laps_credential_dump.yml index 54ddc823a..dfe925507 100644 --- a/rules/cloud/azure/azure_laps_credential_dump.yml +++ b/rules/cloud/azure/azure_laps_credential_dump.yml @@ -1,39 +1,56 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure AD LAPS Password Recovery +- azure +name: Repeated Entra LAPS Password Reads impact: - confidentiality: 3 + confidentiality: 2 integrity: 2 availability: 1 category: Credential Access -technique: "T1003 - OS Credential Dumping" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/entra/identity/devices/howto-manage-local-admin-passwords - - https://attack.mitre.org/techniques/T1003/ -description: | - Detects Local Administrator Password Solution (LAPS) password recovery from Entra ID. While LAPS recovery is a legitimate admin operation, excessive or unauthorized recovery attempts indicate credential dumping for lateral movement. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/entra/identity/devices/howto-manage-local-admin-passwords +- https://attack.mitre.org/techniques/T1003/ +description: 'At least three completed LAPS password-read/recovery operations by one actor in one directory and collector in one + hour. Review whether the recoveries were authorized. + + ' +where: '(equalsIgnoreCase("log.azureKind","audit") && equalsIgnoreCase("actionResult","success") && (equalsIgnoreCase("log.azureOperation","Recover + device local administrator password") || equalsIgnoreCase("log.azureOperation","Read device local administrator password"))) + && exists("dataSource") && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") && exists("log.azureScope") + && !equals("log.azureScope","") && exists("log.azureActorType") && !equals("log.azureActorType","") && exists("log.azureActor") + && !equals("log.azureActor","") - Next Steps: - 1. Verify the user recovering the LAPS password has legitimate need - 2. Check the target device and whether the user is responsible for it - 3. Review the frequency of LAPS password recoveries by this user - 4. Correlate with subsequent RDP or SMB connections to the target device - 5. If unauthorized, rotate the LAPS password and investigate the user's activities - 6. Review RBAC for LAPS password read permissions -where: | - contains("log.operationName", "Recover device local administrator password") || - (contains("log.operationName", "Read device local administrator password") && exists("log.properties")) -afterEvents: - - indexPattern: v11-log-azure-* - with: - - field: origin.ip - operator: filter_term - value: '{{.origin.ip}}' - within: 1h - count: 3 + ' groupBy: - - adversary.ip - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip +correlation: +- indexPattern: v11-log-azure-* + with: + - field: dataSource + operator: filter_term + value: '{{.dataSource}}' + - field: log.azureScopeType + operator: filter_term + value: '{{.log.azureScopeType}}' + - field: log.azureScope + operator: filter_term + value: '{{.log.azureScope}}' + - field: log.azureActorType + operator: filter_term + value: '{{.log.azureActorType}}' + - field: log.azureActor + operator: filter_term + value: '{{.log.azureActor}}' + - field: log.correlationCandidate.azure_laps_credential_dump + operator: filter_term + value: 'true' + within: 1h + count: 3 diff --git a/rules/cloud/azure/azure_leaked_credentials.yml b/rules/cloud/azure/azure_leaked_credentials.yml index 29acc06ff..3a1616c77 100644 --- a/rules/cloud/azure/azure_leaked_credentials.yml +++ b/rules/cloud/azure/azure_leaked_credentials.yml @@ -1,32 +1,49 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure +- azure name: Azure AD Leaked Credentials Detection impact: confidentiality: 3 integrity: 3 availability: 2 category: Credential Access -technique: "T1078 - Valid Accounts" +technique: T1078 - Valid Accounts adversary: origin references: - - https://learn.microsoft.com/en-us/entra/id-protection/concept-identity-protection-risks - - https://attack.mitre.org/techniques/T1078/ -description: | - Detects Azure Identity Protection alerts for leaked credentials found on dark web, paste sites, or other sources. This indicates user credentials have been exposed and may be used for unauthorized access. +- https://learn.microsoft.com/en-us/graph/api/resources/riskdetection?view=graph-rest-1.0 +- https://learn.microsoft.com/en-us/entra/id-protection/concept-identity-protection-risks +- https://attack.mitre.org/techniques/T1078/ +description: 'Detects Azure Identity Protection alerts for leaked credentials found on dark web, paste sites, or other sources. + This indicates user credentials have been exposed and may be used for unauthorized access. + Next Steps: + 1. Immediately force a password reset for the affected user + 2. Revoke all active sessions and refresh tokens + 3. Review recent sign-in activity for unauthorized access + 4. Check for any data access or configuration changes after the leak + 5. Enable MFA if not already required for the user + 6. Investigate how the credentials were leaked (phishing, malware, reuse) + 7. Check if the same password was used across other services -where: | - contains("log.operationName", "Leaked Credentials") || - (contains("log.properties", "riskEventType") && contains("log.properties", "leakedCredentials")) + + ' +where: '(equalsIgnoreCase("log.azureKind","risk") && oneOf("log.properties.riskEventType",["leakedCredentials"]) && !oneOf("log.properties.riskState",["dismissed", + "remediated", "confirmedSafe"])) || (equalsIgnoreCase("log.azureKind","signin") && (exists("log.properties.riskEventTypes_v2.#(==\"leakedCredentials\")") + || exists("log.properties.riskEventTypesv2.#(==\"leakedCredentials\")") || exists("log.properties.riskEventTypes.#(==\"leakedCredentials\")")) + && !oneOf("log.properties.riskState",["dismissed", "remediated", "confirmedSafe"])) + + ' groupBy: - - adversary.ip - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_new_root_ca_added.yml b/rules/cloud/azure/azure_new_root_ca_added.yml index 00e0ff327..bdf98340f 100644 --- a/rules/cloud/azure/azure_new_root_ca_added.yml +++ b/rules/cloud/azure/azure_new_root_ca_added.yml @@ -1,32 +1,30 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure AD New Root Certificate Authority Added +- azure +name: Entra Certificate Authentication Configuration Created impact: - confidentiality: 3 - integrity: 3 - availability: 2 + confidentiality: 2 + integrity: 2 + availability: 1 category: Persistence -technique: "T1556 - Modify Authentication Process" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/entra/identity/authentication/concept-certificate-based-authentication - - https://attack.mitre.org/techniques/T1556/ -description: | - Detects when a new root certificate authority is added to the TrustedCAsForPasswordlessAuth configuration in Azure AD. Adding a rogue root CA enables persistent passwordless authentication backdoor access. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/entra/identity/authentication/concept-certificate-based-authentication +- https://attack.mitre.org/techniques/T1556/ +description: 'A successful certificate authority entity or certificate-based authentication configuration creation. This may be + approved PKI administration and does not by itself identify a rogue root CA. + + ' +where: 'equalsIgnoreCase("log.azureKind","audit") && equalsIgnoreCase("actionResult","success") && (equalsIgnoreCase("log.azureOperation","Create + CertificateAuthorityEntity") || equalsIgnoreCase("log.azureOperation","Add CertificateBasedAuthConfiguration")) - Next Steps: - 1. Immediately verify the root CA addition was authorized - 2. Review the certificate details and issuing authority - 3. Check the user identity performing the change - 4. Validate the CA against your organization's known PKI infrastructure - 5. If unauthorized, remove the root CA immediately - 6. Audit all certificate-based authentications since the CA was added - 7. Review Azure AD authentication methods policies -where: | - contains("log.operationName", "TrustedCAsForPasswordlessAuth") || - (contains("log.operationName", "Update organization settings") && contains("log.properties", "certificateAuthorities")) + ' groupBy: - - lastEvent.log.operationName - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_password_spray_detected.yml b/rules/cloud/azure/azure_password_spray_detected.yml index 85ca4c826..323bf6177 100644 --- a/rules/cloud/azure/azure_password_spray_detected.yml +++ b/rules/cloud/azure/azure_password_spray_detected.yml @@ -1,32 +1,49 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure +- azure name: Azure AD Password Spray Attack Detected impact: confidentiality: 3 integrity: 2 availability: 1 category: Credential Access -technique: "T1110.003 - Brute Force: Password Spraying" +technique: 'T1110.003 - Brute Force: Password Spraying' adversary: origin references: - - https://learn.microsoft.com/en-us/entra/id-protection/concept-identity-protection-risks - - https://attack.mitre.org/techniques/T1110/003/ -description: | - Detects Azure Identity Protection password spray attack signals. Microsoft's ML-based detection identifies distributed password spray attempts across multiple accounts using common passwords. +- https://learn.microsoft.com/en-us/graph/api/resources/riskdetection?view=graph-rest-1.0 +- https://learn.microsoft.com/en-us/entra/id-protection/concept-identity-protection-risks +- https://attack.mitre.org/techniques/T1110/003/ +description: 'Detects Azure Identity Protection password spray attack signals. Microsoft''s ML-based detection identifies distributed + password spray attempts across multiple accounts using common passwords. + Next Steps: + 1. Identify all affected user accounts in the password spray + 2. Check if any accounts were successfully compromised + 3. Force password resets for all targeted accounts + 4. Review source IPs for known attack infrastructure + 5. Check for successful sign-ins from the same source IPs + 6. Enable smart lockout policies if not already configured + 7. Review MFA enforcement across all targeted accounts -where: | - contains("log.operationName", "Password Spray") || - (contains("log.properties", "riskEventType") && contains("log.properties", "passwordSpray")) + + ' +where: '(equalsIgnoreCase("log.azureKind","risk") && oneOf("log.properties.riskEventType",["passwordSpray"]) && !oneOf("log.properties.riskState",["dismissed", + "remediated", "confirmedSafe"])) || (equalsIgnoreCase("log.azureKind","signin") && (exists("log.properties.riskEventTypes_v2.#(==\"passwordSpray\")") + || exists("log.properties.riskEventTypesv2.#(==\"passwordSpray\")") || exists("log.properties.riskEventTypes.#(==\"passwordSpray\")")) + && !oneOf("log.properties.riskState",["dismissed", "remediated", "confirmedSafe"])) + + ' groupBy: - - lastEvent.log.operationName - - adversary.ip +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_prt_access_attempt.yml b/rules/cloud/azure/azure_prt_access_attempt.yml index 34ba66609..9c59e2b46 100644 --- a/rules/cloud/azure/azure_prt_access_attempt.yml +++ b/rules/cloud/azure/azure_prt_access_attempt.yml @@ -1,32 +1,31 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure Primary Refresh Token Access Attempt +- azure +name: High Risk Primary Refresh Token Sign-in impact: - confidentiality: 3 - integrity: 3 + confidentiality: 2 + integrity: 2 availability: 1 category: Credential Access -technique: "T1528 - Steal Application Access Token" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/entra/identity/devices/concept-primary-refresh-token - - https://attack.mitre.org/techniques/T1528/ -description: | - Detects attempts to access the Primary Refresh Token (PRT) in Azure AD. PRT theft is a high-confidence compromise indicator as PRTs provide SSO access across all Azure AD-integrated applications and can be used to bypass conditional access policies. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/entra/identity/devices/concept-primary-refresh-token +- https://attack.mitre.org/techniques/T1528/ +description: 'A successful sign-in using a primary refresh token carries high or active risk. Token type alone does not indicate + theft or access to token storage. + + ' +where: 'equalsIgnoreCase("log.azureKind","signin") && equalsIgnoreCase("actionResult","success") && equalsIgnoreCase("log.properties.incomingTokenType","primaryRefreshToken") + && (equalsIgnoreCase("log.properties.riskState","atRisk") || equalsIgnoreCase("log.properties.riskState","confirmedCompromised") + || equalsIgnoreCase("log.properties.riskLevelDuringSignIn","high")) - Next Steps: - 1. Immediately investigate the user account associated with this alert - 2. Check the device from which the PRT access was attempted - 3. Review sign-in logs for the affected user for anomalous patterns - 4. Check for token replay attacks or sessions from unexpected locations - 5. If compromise is confirmed, revoke all refresh tokens for the user - 6. Re-register the device and force re-authentication - 7. Review conditional access policies for PRT-based bypass vulnerabilities -where: | - contains("log.operationName", "Primary Refresh Token") || - (contains("log.properties", "PRT") && contains("log.properties", "access")) + ' groupBy: - - adversary.ip - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_ropc_authentication.yml b/rules/cloud/azure/azure_ropc_authentication.yml index 5c0efef20..d5344e5bb 100644 --- a/rules/cloud/azure/azure_ropc_authentication.yml +++ b/rules/cloud/azure/azure_ropc_authentication.yml @@ -1,39 +1,55 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure AD Resource Owner Password Credentials Flow Detected +- azure +name: Repeated Successful Entra ROPC Sign-ins impact: confidentiality: 2 integrity: 2 availability: 1 category: Credential Access -technique: "T1078 - Valid Accounts" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth-ropc - - https://attack.mitre.org/techniques/T1078/ -description: | - Detects use of the Resource Owner Password Credentials (ROPC) OAuth flow in Azure AD. ROPC sends plain-text credentials directly to the token endpoint, bypassing MFA and conditional access. It is commonly abused by attackers for credential stuffing and automated account compromise. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth-ropc +- https://attack.mitre.org/techniques/T1078/ +description: 'At least five successful ROPC sign-ins by one actor in one directory and collector in one hour. Review legacy authentication + use; this does not itself prove a stolen password. + + ' +where: '(equalsIgnoreCase("log.azureKind","signin") && equalsIgnoreCase("actionResult","success") && equalsIgnoreCase("log.properties.authenticationProtocol","ropc")) + && exists("dataSource") && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") && exists("log.azureScope") + && !equals("log.azureScope","") && exists("log.azureActorType") && !equals("log.azureActorType","") && exists("log.azureActor") + && !equals("log.azureActor","") - Next Steps: - 1. Identify the application using ROPC flow and verify its legitimacy - 2. Check if the application has a legitimate need for ROPC (legacy/headless apps) - 3. Review the source IPs making ROPC requests for suspicious patterns - 4. Check for high volumes of failed ROPC requests (credential stuffing) - 5. Migrate the application to a modern auth flow (authorization code, device code) - 6. If unauthorized, block the application and reset affected user passwords -where: | - contains("log.properties", "urn:ietf:params:oauth:grant-type:password") || - (contains("log.operationName", "Sign-in") && contains("log.properties", "ropc")) -afterEvents: - - indexPattern: v11-log-azure-* - with: - - field: origin.ip - operator: filter_term - value: '{{.origin.ip}}' - within: 1h - count: 5 + ' groupBy: - - adversary.ip - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip +correlation: +- indexPattern: v11-log-azure-* + with: + - field: dataSource + operator: filter_term + value: '{{.dataSource}}' + - field: log.azureScopeType + operator: filter_term + value: '{{.log.azureScopeType}}' + - field: log.azureScope + operator: filter_term + value: '{{.log.azureScope}}' + - field: log.azureActorType + operator: filter_term + value: '{{.log.azureActorType}}' + - field: log.azureActor + operator: filter_term + value: '{{.log.azureActor}}' + - field: log.correlationCandidate.azure_ropc_authentication + operator: filter_term + value: 'true' + within: 1h + count: 5 diff --git a/rules/cloud/azure/azure_sentinel_alert_patterns.yml b/rules/cloud/azure/azure_sentinel_alert_patterns.yml index dfe6b81ea..5345b4594 100644 --- a/rules/cloud/azure/azure_sentinel_alert_patterns.yml +++ b/rules/cloud/azure/azure_sentinel_alert_patterns.yml @@ -1,32 +1,25 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure Sentinel High/Critical Alert Pattern Detection +- azure +name: Forwarded High Severity Security Alert impact: - confidentiality: 3 - integrity: 3 - availability: 2 + confidentiality: 2 + integrity: 2 + availability: 1 category: Threat Detection -technique: "T1562.001 - Impair Defenses: Disable or Modify Tools" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/azure/sentinel/security-alert-schema - - https://attack.mitre.org/techniques/T1562/ -description: | - Detects high-severity or critical alerts from Azure Sentinel that may indicate coordinated attack activity or serious security incidents requiring immediate investigation. This rule identifies new alerts with High or Critical severity levels from Microsoft Sentinel that could represent active threats. - - Next Steps: - 1. Review the alert details and affected resources immediately - 2. Correlate with other security events in the environment - 3. Check for signs of lateral movement or privilege escalation - 4. Verify if the alert represents a true positive through manual investigation - 5. Implement containment measures if attack activity is confirmed - 6. Document findings and update incident response procedures -where: | - oneOf("log.AlertSeverity", ["High", "Critical"]) && - equals("log.VendorName", "Microsoft Sentinel") && - equals("log.Status", "New") +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/azure/sentinel/security-alert-schema +- https://attack.mitre.org/techniques/T1562/ +description: 'A new high severity record in the documented SecurityAlert schema. The vendor/product identity is retained; the schema + does not require VendorName to equal Microsoft Sentinel. This is an upstream finding, not a successful attacker action. + + ' +where: 'equalsIgnoreCase("log.azureKind","securityalert") && equalsIgnoreCase("log.AlertSeverity","High") && equalsIgnoreCase("log.Status","New") + + ' groupBy: - - lastEvent.log.AlertType - - adversary.ip +- lastEvent.dataSource +- lastEvent.log.SystemAlertId diff --git a/rules/cloud/azure/azure_subscription_permission_elevation.yml b/rules/cloud/azure/azure_subscription_permission_elevation.yml index e196170ae..f08e84670 100644 --- a/rules/cloud/azure/azure_subscription_permission_elevation.yml +++ b/rules/cloud/azure/azure_subscription_permission_elevation.yml @@ -1,30 +1,45 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure +- azure name: Azure Subscription Permission Elevation via ElevateAccess impact: confidentiality: 3 integrity: 3 availability: 3 category: Privilege Escalation -technique: "T1078.004 - Valid Accounts: Cloud Accounts" +technique: 'T1078.004 - Valid Accounts: Cloud Accounts' adversary: origin references: - - https://learn.microsoft.com/en-us/azure/role-based-access-control/elevate-access-global-admin - - https://attack.mitre.org/techniques/T1078/004/ -description: | - Detects the MICROSOFT.AUTHORIZATION/ELEVATEACCESS/ACTION operation which grants a Global Administrator access to ALL Azure subscriptions in the tenant. This is an extremely high-impact action that should be very rare and carefully monitored. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/azure/role-based-access-control/elevate-access-global-admin +- https://attack.mitre.org/techniques/T1078/004/ +description: 'Detects the MICROSOFT.AUTHORIZATION/ELEVATEACCESS/ACTION operation which grants a Global Administrator access to + ALL Azure subscriptions in the tenant. This is an extremely high-impact action that should be very rare and carefully monitored. + Next Steps: + 1. Immediately verify this action was authorized by a known Global Administrator + 2. Check if a change request or emergency procedure exists for this action + 3. Review what subscription-level changes were made after the elevation + 4. Check for new role assignments at the management group or subscription level + 5. If unauthorized, remove the User Access Administrator role and audit all changes + 6. Enable Azure PIM (Privileged Identity Management) if not already in use -where: | - regexMatch("log.operationName", "(?i)MICROSOFT\\.AUTHORIZATION/ELEVATEACCESS/ACTION") + + ' +where: '(equalsIgnoreCase("log.azureKind","activity") || equalsIgnoreCase("log.azureKind","eventgrid")) && equalsIgnoreCase("actionResult","success") + && equalsIgnoreCase("log.azureOperation","Microsoft.Authorization/elevateAccess/action") + + ' groupBy: - - adversary.ip - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/azure_temporary_access_pass.yml b/rules/cloud/azure/azure_temporary_access_pass.yml index 1c13b77b8..4724b37f5 100644 --- a/rules/cloud/azure/azure_temporary_access_pass.yml +++ b/rules/cloud/azure/azure_temporary_access_pass.yml @@ -1,32 +1,31 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure AD Temporary Access Pass Registration +- azure +name: Entra Temporary Access Pass Registered impact: - confidentiality: 3 + confidentiality: 2 integrity: 2 availability: 1 category: Credential Access -technique: "T1078.004 - Valid Accounts: Cloud Accounts" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass - - https://attack.mitre.org/techniques/T1078/004/ -description: | - Detects registration of Temporary Access Pass (TAP) in Azure AD. TAPs can be used to bypass MFA requirements and are a growing attack vector for initial access and MFA circumvention. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass +- https://attack.mitre.org/techniques/T1078/004/ +description: 'A successful admin security-info registration explicitly identifies Temporary Access Pass as the authentication method. + Other authentication-method registrations are excluded. + + ' +where: 'equalsIgnoreCase("log.azureKind","audit") && equalsIgnoreCase("actionResult","success") && equalsIgnoreCase("log.azureOperation","Admin + registered security info") && (exists(''log.properties.additionalDetails.#(key=="AuthenticationMethod").value'') && (equalsIgnoreCase("log.properties.additionalDetails.#(key==\"AuthenticationMethod\").value","Temporary + Access Pass") || equalsIgnoreCase("log.properties.additionalDetails.#(key==\"AuthenticationMethod\").value","TemporaryAccessPass"))) - Next Steps: - 1. Verify the TAP was requested through legitimate channels (IT helpdesk) - 2. Check the admin user who created the TAP for legitimacy - 3. Review the target user and reason for TAP issuance - 4. Check for sign-ins using the TAP, especially from unusual locations - 5. Verify MFA registration events following the TAP usage - 6. If unauthorized, revoke the TAP immediately and investigate - 7. Review TAP policy settings for appropriate lifetime and usage limits -where: | - (contains("log.operationName", "Admin registered security info") && contains("log.properties", "Temporary Access Pass")) || - (contains("log.operationName", "Update user") && contains("log.properties", "TemporaryAccessPass")) + ' groupBy: - - lastEvent.log.properties.targetResources - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/credential_access_key_vault_modified.yml b/rules/cloud/azure/credential_access_key_vault_modified.yml index 5d04142d3..c6d8434b3 100644 --- a/rules/cloud/azure/credential_access_key_vault_modified.yml +++ b/rules/cloud/azure/credential_access_key_vault_modified.yml @@ -1,28 +1,30 @@ -# Rule version v1.0.4 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: "Azure Key Vault Modified" +- azure +name: Azure Key Vault Configuration Changed impact: - confidentiality: 3 - integrity: 3 - availability: 2 -category: "Credential Access" -technique: "T1552 - Unsecured Credentials" -adversary: target -description: "Identifies modifications to a Key Vault in Azure. The Key Vault is a service that safeguards encryption keys and secrets like certificates, - connection strings, and passwords. Because this data is sensitive and business critical, access to key vaults should be secured to allow - only authorized applications and users. Adversaries may modify Key Vault configurations to weaken security controls, add unauthorized access policies, - or change network rules to facilitate credential theft and unauthorized access to sensitive secrets." + confidentiality: 2 + integrity: 2 + availability: 1 +category: Credential Access +technique: '' +adversary: origin +description: 'A completed successful vault configuration write. It does not establish access to secrets or keys. + + ' references: - - "https://attack.mitre.org/techniques/T1552/" - - "https://attack.mitre.org/tactics/TA0006/" - - "https://learn.microsoft.com/en-us/azure/key-vault/general/security-features" -where: | - (equalsIgnoreCase("log.category", "Administrative") || contains("log.category", "Activity")) && - (equalsIgnoreCase("log.operationName", "MICROSOFT.KEYVAULT/VAULTS/WRITE") || - contains("log.operationName", "Microsoft.KeyVault/vaults/write")) && - equals("log.resultType", "0") +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://attack.mitre.org/techniques/T1552/ +- https://attack.mitre.org/tactics/TA0006/ +- https://learn.microsoft.com/en-us/azure/key-vault/general/security-features +where: '(equalsIgnoreCase("log.azureKind","activity") || equalsIgnoreCase("log.azureKind","eventgrid")) && equalsIgnoreCase("actionResult","success") + && equalsIgnoreCase("log.azureOperation","Microsoft.KeyVault/vaults/write") + + ' groupBy: - - target.ip - - target.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/defender_cloud_critical_alerts.yml b/rules/cloud/azure/defender_cloud_critical_alerts.yml index 0ffdad56e..5c5830c22 100644 --- a/rules/cloud/azure/defender_cloud_critical_alerts.yml +++ b/rules/cloud/azure/defender_cloud_critical_alerts.yml @@ -1,34 +1,30 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure Defender for Cloud Critical Security Alert +- azure +name: Microsoft Defender for Cloud High Severity Alert impact: - confidentiality: 3 - integrity: 3 - availability: 2 + confidentiality: 2 + integrity: 2 + availability: 1 category: Intrusion Detection -technique: "TA0001 - Initial Access" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/azure/defender-for-cloud/alerts-overview - - https://learn.microsoft.com/en-us/azure/defender-for-cloud/alerts-schemas - - https://attack.mitre.org/tactics/TA0001/ -description: | - Detects critical severity alerts from Azure Defender for Cloud indicating potential active threats, malware infections, or successful breach attempts that require immediate response. - - Next Steps: - 1. Review the full alert details in Azure Defender for Cloud portal - 2. Verify the affected resource and assess the scope of potential compromise - 3. Check for related suspicious activities on the affected resource - 4. Implement immediate containment measures if threat is confirmed - 5. Review security policies and configurations for the affected resource - 6. Document the incident and update security procedures as needed -where: | - (equals("log.eventName", "Microsoft.Security/locations/alerts/Activate/action") || contains("log.operationName", "Microsoft.Security")) && - equals("log.category", "Security") && - oneOf("log.level", ["Critical", "High", "Error"]) && - (equals("log.properties.severity", "High") || equals("log.properties.alertSeverity", "High")) +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/azure/defender-for-cloud/alerts-overview +- https://learn.microsoft.com/en-us/azure/defender-for-cloud/alerts-schemas +- https://attack.mitre.org/tactics/TA0001/ +description: 'A Security-category record reports high alert severity. Alert issuance is not treated as a successful attacker connection. + + ' +where: '(equalsIgnoreCase("log.category","Security") || equalsIgnoreCase("log.category.value","Security")) && (equalsIgnoreCase("log.properties.severity","High") + || equalsIgnoreCase("log.properties.Severity","High") || equalsIgnoreCase("log.properties.alertSeverity","High")) && regexMatch("log.azureOperation","(?i)^Microsoft\\.Security/") + + ' groupBy: - - lastEvent.log.correlationId - - lastEvent.log.eventDataId +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/defense_evasion_azure_application_credential_modification.yml b/rules/cloud/azure/defense_evasion_azure_application_credential_modification.yml deleted file mode 100644 index c6072102b..000000000 --- a/rules/cloud/azure/defense_evasion_azure_application_credential_modification.yml +++ /dev/null @@ -1,45 +0,0 @@ -# Rule version v1.0.4 - -dataTypes: - - azure -name: "Azure Application Credential Modification" -impact: - confidentiality: 3 - integrity: 3 - availability: 2 -category: "Defense Evasion" -technique: "T1098.001 - Account Manipulation: Additional Cloud Credentials" -adversary: target -references: - - "https://attack.mitre.org/techniques/T1098/001/" - - "https://attack.mitre.org/tactics/TA0005/" - - "https://learn.microsoft.com/en-us/azure/active-directory/reports-monitoring/concept-audit-logs" - - "https://learn.microsoft.com/en-us/entra/identity/monitoring-health/reference-audit-activities" -description: | - Detects when a new credential (certificate or secret) is added to an Azure AD application. Applications can use certificates or secret strings to authenticate when requesting tokens. Adversaries may add additional authentication credentials to existing applications to establish persistence, evade defenses, or enable privilege escalation by impersonating legitimate applications. - - This technique is commonly used in post-compromise scenarios where attackers: - - Add secrets to high-privilege applications to maintain access - - Create backdoor authentication methods to evade MFA requirements - - Establish persistence mechanisms that survive password resets - - Enable token-based authentication for automated attacks - - Next Steps: - 1. Verify if the credential modification was authorized and expected - 2. Identify who performed the operation (check InitiatedBy field) - 3. Review the affected application's permissions and access scope - 4. Check for subsequent suspicious sign-in activity using the application - 5. Audit other applications for similar unauthorized modifications - 6. If unauthorized, immediately remove the suspicious credentials - 7. Review application usage logs for potential abuse - 8. Investigate the source IP address and user agent of the modification -where: | - (equalsIgnoreCase("log.category", "AuditLogs") || contains("log.category", "Audit")) && - (contains("log.operationName", "Certificates and secrets management") || - equalsIgnoreCase("log.operationName", "Add service principal credentials") || - equalsIgnoreCase("log.operationName", "Update application") || - equalsIgnoreCase("log.operationName", "Update application - Certificates and secrets management")) && - equalsIgnoreCase("log.resultType", "0") -groupBy: - - target.ip - - target.user diff --git a/rules/cloud/azure/defense_evasion_azure_diagnostic_settings_deletion.yml b/rules/cloud/azure/defense_evasion_azure_diagnostic_settings_deletion.yml index 06bf92c40..d83a18de4 100644 --- a/rules/cloud/azure/defense_evasion_azure_diagnostic_settings_deletion.yml +++ b/rules/cloud/azure/defense_evasion_azure_diagnostic_settings_deletion.yml @@ -1,48 +1,72 @@ -# Rule version v1.0.4 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: "Azure Diagnostic Settings Deletion" +- azure +name: Azure Diagnostic Settings Deletion impact: confidentiality: 1 integrity: 3 availability: 3 -category: "Defense Evasion" -technique: "T1562.008 - Impair Defenses: Disable Cloud Logs" -adversary: target +category: Defense Evasion +technique: 'T1562.008 - Impair Defenses: Disable Cloud Logs' +adversary: origin references: - - "https://attack.mitre.org/techniques/T1562/008/" - - "https://attack.mitre.org/tactics/TA0005/" - - "https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/diagnostic-settings" - - "https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log" -description: | - Detects the deletion of diagnostic settings in Azure, which are critical for sending platform logs, metrics, and activity data to destinations like Log Analytics workspaces, Event Hubs, or storage accounts. Adversaries delete diagnostic settings to evade detection by disabling security monitoring and audit logging capabilities. - +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://attack.mitre.org/techniques/T1562/008/ +- https://attack.mitre.org/tactics/TA0005/ +- https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/diagnostic-settings +- https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log +description: 'Detects the deletion of diagnostic settings in Azure, which are critical for sending platform logs, metrics, and + activity data to destinations like Log Analytics workspaces, Event Hubs, or storage accounts. Adversaries delete diagnostic settings + to evade detection by disabling security monitoring and audit logging capabilities. + + This technique is commonly observed when attackers: + - Attempt to hide malicious activities from security teams + - Disable logging before executing destructive operations + - Remove evidence trails of their presence in the environment + - Prevent detection of lateral movement or data exfiltration - + + Legitimate deletions are rare and typically occur only during: + - Infrastructure decommissioning or major reconfigurations + - Cost optimization initiatives (but should be heavily scrutinized) + - Migration to new monitoring solutions - + + Next Steps: + 1. Immediately verify if the deletion was authorized and documented + 2. Identify who performed the operation and from which IP address + 3. Check if diagnostic settings were immediately recreated (potential test) + 4. Review recent activities on the affected resource for suspicious behavior + 5. Verify if other resources had their diagnostic settings deleted + 6. Restore diagnostic settings immediately to resume monitoring - 7. Investigate the caller's account for potential compromise + + 7. Investigate the caller''s account for potential compromise + 8. Check for other defense evasion techniques in the timeline -where: | - (equalsIgnoreCase("log.category", "Administrative") || contains("log.category", "Activity")) && - (equalsIgnoreCase("log.operationName", "MICROSOFT.INSIGHTS/DIAGNOSTICSETTINGS/DELETE") || - contains("log.operationName", "Delete diagnostic setting")) && - equalsIgnoreCase("log.resultType", "0") + + ' +where: '(equalsIgnoreCase("log.azureKind","activity") || equalsIgnoreCase("log.azureKind","eventgrid")) && equalsIgnoreCase("actionResult","success") + && equalsIgnoreCase("log.azureOperation","Microsoft.Insights/diagnosticSettings/delete") + + ' groupBy: - - target.ip - - target.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/defense_evasion_event_hub_deletion.yml b/rules/cloud/azure/defense_evasion_event_hub_deletion.yml index cfc6f5fc2..1a88c44e5 100644 --- a/rules/cloud/azure/defense_evasion_event_hub_deletion.yml +++ b/rules/cloud/azure/defense_evasion_event_hub_deletion.yml @@ -1,58 +1,92 @@ -# Rule version v1.0.4 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: "Azure Event Hub Deletion" +- azure +name: Azure Event Hub Deletion impact: confidentiality: 1 integrity: 3 availability: 3 -category: "Defense Evasion" -technique: "T1562.008 - Impair Defenses: Disable Cloud Logs" +category: Defense Evasion +technique: 'T1562.008 - Impair Defenses: Disable Cloud Logs' adversary: origin references: - - "https://attack.mitre.org/techniques/T1562/008/" - - "https://attack.mitre.org/tactics/TA0005/" - - "https://learn.microsoft.com/en-us/azure/event-hubs/monitor-event-hubs" - - "https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log" -description: | - Detects the deletion of an Azure Event Hub, which is a critical event processing service that ingests and processes large volumes of events, logs, and telemetry data. Event Hubs are commonly used for security monitoring, log aggregation, and SIEM integration. Adversaries may delete Event Hubs to evade detection by disrupting log collection pipelines and preventing security events from reaching monitoring systems. - +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://attack.mitre.org/techniques/T1562/008/ +- https://attack.mitre.org/tactics/TA0005/ +- https://learn.microsoft.com/en-us/azure/event-hubs/monitor-event-hubs +- https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log +description: 'Detects the deletion of an Azure Event Hub, which is a critical event processing service that ingests and processes + large volumes of events, logs, and telemetry data. Event Hubs are commonly used for security monitoring, log aggregation, and + SIEM integration. Adversaries may delete Event Hubs to evade detection by disrupting log collection pipelines and preventing + security events from reaching monitoring systems. + + Threat Context: + - Event Hubs are often used to stream logs to SIEM solutions + - Deletion interrupts security monitoring and incident detection capabilities + - Can be part of anti-forensics activities to cover tracks + - May indicate an attempt to blind security operations before further attacks - + + Legitimate Use Cases: + - Decommissioning unused Event Hubs during cost optimization + - Infrastructure cleanup during application retirement + - Migration to new Event Hub namespaces or different logging solutions + - Testing and development environment cleanup - + + Suspicious Indicators: + - Event Hub actively receiving logs suddenly deleted + - Deletion performed by non-administrative accounts + - Multiple Event Hubs deleted in quick succession + - Deletion outside change management windows + - Deletion from unusual locations or IP addresses + - Event Hub connected to production SIEM or security monitoring - + + Next Steps: + 1. Verify if the deletion was authorized via change management process + 2. Identify who performed the deletion (caller) and their role + 3. Check if the Event Hub was actively receiving security logs + 4. Determine the impact on security monitoring and log collection + 5. Review recent authentication activity for the caller account + 6. Check for other suspicious activities in the timeline (diagnostic settings changes, etc.) + 7. Verify if backups of the Event Hub configuration exist + 8. If unauthorized, restore the Event Hub and investigate for account compromise + 9. Review authorization rules and access policies for remaining Event Hubs -where: | - (equalsIgnoreCase("log.category", "Administrative") || contains("log.category", "Activity")) && - (equalsIgnoreCase("log.operationName", "MICROSOFT.EVENTHUB/NAMESPACES/EVENTHUBS/DELETE") || - contains("log.operationName", "Delete EventHub")) && - (equalsIgnoreCase("log.resultType", "0") || equalsIgnoreCase("actionResult", "success")) + + ' +where: '(equalsIgnoreCase("log.azureKind","activity") || equalsIgnoreCase("log.azureKind","eventgrid")) && equalsIgnoreCase("actionResult","success") + && equalsIgnoreCase("log.azureOperation","Microsoft.EventHub/namespaces/eventhubs/delete") + + ' groupBy: - - adversary.ip - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/diagnostic_settings_tampering.yml b/rules/cloud/azure/diagnostic_settings_tampering.yml deleted file mode 100644 index abc98ac12..000000000 --- a/rules/cloud/azure/diagnostic_settings_tampering.yml +++ /dev/null @@ -1,35 +0,0 @@ -# Rule version v1.0.0 - -dataTypes: - - azure -name: Azure Diagnostic Settings Tampering -impact: - confidentiality: 2 - integrity: 3 - availability: 2 -category: Defense Evasion -technique: "T1562.008 - Impair Defenses: Disable Cloud Logs" -adversary: origin -references: - - https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/diagnostic-settings - - https://attack.mitre.org/techniques/T1562/008/ -description: | - Detects deletion or modification of Azure diagnostic settings which are used to route platform logs and metrics to monitoring destinations. Attackers may disable diagnostic settings to prevent their activities from being logged and detected. - - Next Steps: - 1. Verify if the diagnostic settings change was authorized through change management - 2. Identify which resources lost their diagnostic logging - 3. Review the identity performing the change and confirm authorization - 4. Check if any suspicious activities occurred after logging was disabled - 5. Restore diagnostic settings for affected resources immediately - 6. Implement Azure Policy to enforce diagnostic settings on all resources - 7. Set up alerts for diagnostic settings modifications - 8. Review Azure Activity Log for other defense evasion activities by the same identity -where: | - contains("log.operationName", "Microsoft.Insights/diagnosticSettings") && - (contains("log.operationName", "delete") || - contains("log.operationName", "Delete")) && - equals("log.resultType", "Success") -groupBy: - - lastEvent.log.resourceId - - adversary.user diff --git a/rules/cloud/azure/golden_saml_federation_abuse.yml b/rules/cloud/azure/golden_saml_federation_abuse.yml deleted file mode 100644 index c48c42926..000000000 --- a/rules/cloud/azure/golden_saml_federation_abuse.yml +++ /dev/null @@ -1,37 +0,0 @@ -# Rule version v1.0.0 - -dataTypes: - - azure -name: Azure AD Golden SAML and Federation Domain Abuse -impact: - confidentiality: 3 - integrity: 3 - availability: 2 -category: Credential Access -technique: "T1606.002 - Forge Web Credentials: SAML Tokens" -adversary: origin -references: - - https://learn.microsoft.com/en-us/azure/active-directory/hybrid/whatis-fed - - https://attack.mitre.org/techniques/T1606/002/ -description: | - Detects additions or modifications of federated domains in Azure AD which could indicate Golden SAML attacks. Attackers who compromise AD FS signing certificates or add rogue federation domains can forge SAML tokens to impersonate any user in the organization. - - Next Steps: - 1. Immediately verify if the federation domain change was authorized - 2. Review the domain being added and its federation metadata endpoint - 3. Check the AD FS signing certificate for unauthorized modifications - 4. Verify the identity of the administrator making the change - 5. Review Azure AD audit logs for other suspicious tenant-level changes - 6. If unauthorized, immediately remove the federated domain and revoke all active sessions - 7. Rotate the AD FS token signing certificate - 8. Enable Certificate Authority revocation checking for federation certificates -where: | - (contains("log.operationName", "Set federation settings on domain") || - contains("log.operationName", "Set domain authentication") || - contains("log.operationName", "Add unverified domain") || - contains("log.operationName", "Add verified domain") || - contains("log.operationName", "Set DomainFederationSettings")) && - equals("log.categoryValue", "Administrative") -groupBy: - - lastEvent.log.operationName - - adversary.user diff --git a/rules/cloud/azure/impact_azure_service_principal_credentials_added.yml b/rules/cloud/azure/impact_azure_service_principal_credentials_added.yml deleted file mode 100644 index 97b5593d9..000000000 --- a/rules/cloud/azure/impact_azure_service_principal_credentials_added.yml +++ /dev/null @@ -1,54 +0,0 @@ -# Rule version v1.0.4 - -dataTypes: - - azure -name: "Azure Service Principal Credentials Added" -impact: - confidentiality: 3 - integrity: 3 - availability: 2 -category: "Persistence" -technique: "T1098.001 - Account Manipulation: Additional Cloud Credentials" -adversary: origin -description: | - Detects when new credentials (certificates or secrets) are added to Azure service principals through Azure AD/Entra ID Audit Logs. - - **Security Context:** - Adversaries may add credentials to service principals to maintain persistent access to victim Azure accounts. By hijacking an application with granted permissions through adding rogue secrets or certificates, attackers can access protected data and bypass MFA requirements. This technique is commonly used after initial compromise to establish long-term persistence. - - **Detection Logic:** - This rule monitors AuditLogs for successful "Add service principal" operations, which indicate new credentials being added to service principals. The operation captures both certificate and secret additions. - - **Investigation Steps:** - 1. Identify the actor who added the credentials: Check log.propertiesInitiatedBy for the user or service principal - 2. Review the target service principal: Examine log.propertiesTargetResources for the affected service principal name and ID - 3. Verify if the action was authorized: Correlate with change management tickets - 4. Check service principal permissions: Review what resources this service principal can access - 5. Examine recent sign-in activity: Look for unusual authentication patterns using the service principal - 6. Review credential type: Determine if a certificate or secret was added via log.propertiesModifiedProperties - - **Recommended Actions:** - - If unauthorized, immediately revoke the newly added credentials - - Review and rotate all credentials for the affected service principal - - Audit all resources accessible by the service principal for signs of compromise - - Enable alerts for future credential additions to critical service principals - - Implement conditional access policies and privileged identity management - - **MITRE ATT&CK Reference:** T1098.001 - Account Manipulation: Additional Cloud Credentials - - **Azure Documentation:** - - AuditLogs table: https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/auditlogs - - Service Principal credentials: https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal - -references: - - "https://attack.mitre.org/techniques/T1098/001/" - - "https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/auditlogs" - - "https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal" - -where: | - equalsIgnoreCase("log.category", "AuditLogs") && - contains("log.operationName", "Add service principal") && - (equals("log.resultType", "0") || equalsIgnoreCase("actionResult", "success")) -groupBy: - - adversary.ip - - adversary.user diff --git a/rules/cloud/azure/initial_access_azure_active_directory_high_risk_signin.yml b/rules/cloud/azure/initial_access_azure_active_directory_high_risk_signin.yml index a45be22f9..caa8a32b5 100644 --- a/rules/cloud/azure/initial_access_azure_active_directory_high_risk_signin.yml +++ b/rules/cloud/azure/initial_access_azure_active_directory_high_risk_signin.yml @@ -1,25 +1,31 @@ -# Rule version v1.0.4 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: "Azure Active Directory High Risk Sign-in" +- azure +name: Azure Active Directory High Risk Sign-in impact: confidentiality: 3 integrity: 3 availability: 2 -category: "Initial Access" -technique: "T1078 - Valid Accounts" +category: Initial Access +technique: T1078 - Valid Accounts adversary: origin -description: "Identifies high risk Azure Active Directory (AD) sign-ins by leveraging Microsoft's Identity Protection machine learning and heuristics. Identity Protection categorizes risk into three tiers: low, medium, and high. While Microsoft does not provide specific details about how risk is calculated, each level brings higher confidence that the user or sign-in is compromised. This rule triggers on 'high' risk level sign-ins, which indicate strong indicators of compromise such as impossible travel, anonymous IP usage, or leaked credentials." +description: 'Identifies high risk Azure Active Directory (AD) sign-ins by leveraging Microsoft''s Identity Protection machine + learning and heuristics. Identity Protection categorizes risk into three tiers: low, medium, and high. While Microsoft does not + provide specific details about how risk is calculated, each level brings higher confidence that the user or sign-in is compromised. + This rule triggers on ''high'' risk level sign-ins, which indicate strong indicators of compromise such as impossible travel, + anonymous IP usage, or leaked credentials.' references: - - "https://attack.mitre.org/techniques/T1078/" - - "https://learn.microsoft.com/en-us/entra/id-protection/concept-identity-protection-risks" - - "https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/signinlogs" -where: | - equalsIgnoreCase("log.category", "SignInLogs") && - equalsIgnoreCase("log.properties.RiskLevelDuringSignIn", "high") && - equalsIgnoreCase("log.propertiesTokenIssuerType", "AzureAD") && - equals("log.resultType", "0") +- https://learn.microsoft.com/en-us/graph/api/resources/riskdetection?view=graph-rest-1.0 +- https://attack.mitre.org/techniques/T1078/ +- https://learn.microsoft.com/en-us/entra/id-protection/concept-identity-protection-risks +- https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/signinlogs +where: 'equalsIgnoreCase("log.azureKind","signin") && equalsIgnoreCase("actionResult","success") && equalsIgnoreCase("log.properties.riskLevelDuringSignIn","high") + + ' groupBy: - - adversary.ip - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/initial_access_consent_grant_attack_via_azure_registered_application.yml b/rules/cloud/azure/initial_access_consent_grant_attack_via_azure_registered_application.yml index ea24a274e..622181c43 100644 --- a/rules/cloud/azure/initial_access_consent_grant_attack_via_azure_registered_application.yml +++ b/rules/cloud/azure/initial_access_consent_grant_attack_via_azure_registered_application.yml @@ -1,24 +1,31 @@ -# Rule version v1.0.4 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: "Possible Consent Grant Attack via Azure-Registered Application" +- azure +name: Possible Consent Grant Attack via Azure-Registered Application impact: confidentiality: 3 integrity: 3 availability: 2 -category: "Initial Access" -technique: "T1078 - Valid Accounts" -adversary: target -description: "Detects when a user grants permissions to an Azure-registered application or when an administrator grants tenant-wide permissions to an application. An adversary may create an Azure-registered application that requests access to data such as contact information, email, or documents. Consent grant attacks are commonly used in phishing campaigns where malicious OAuth applications trick users into granting excessive permissions, enabling data exfiltration or unauthorized access to organizational resources." +category: Initial Access +technique: T1078 - Valid Accounts +adversary: origin +description: Detects when a user grants permissions to an Azure-registered application or when an administrator grants tenant-wide + permissions to an application. An adversary may create an Azure-registered application that requests access to data such as contact + information, email, or documents. Consent grant attacks are commonly used in phishing campaigns where malicious OAuth applications + trick users into granting excessive permissions, enabling data exfiltration or unauthorized access to organizational resources. references: - - "https://attack.mitre.org/techniques/T1566/" - - "https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/manage-consent-requests" - - "https://learn.microsoft.com/en-us/defender-cloud-apps/investigate-risky-oauth" -where: | - (equalsIgnoreCase("log.category", "AuditLogs") || contains("log.category", "Audit")) && - equalsIgnoreCase("log.operationName", "Consent to application") && - equals("log.resultType", "0") +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://attack.mitre.org/techniques/T1566/ +- https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/manage-consent-requests +- https://learn.microsoft.com/en-us/defender-cloud-apps/investigate-risky-oauth +where: 'equalsIgnoreCase("log.azureKind","audit") && equalsIgnoreCase("actionResult","success") && equalsIgnoreCase("log.azureOperation","Consent + to application") + + ' groupBy: - - target.ip - - target.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/key_vault_access_spikes.yml b/rules/cloud/azure/key_vault_access_spikes.yml index aa3e0ae88..1c8c35e98 100644 --- a/rules/cloud/azure/key_vault_access_spikes.yml +++ b/rules/cloud/azure/key_vault_access_spikes.yml @@ -1,42 +1,56 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure Key Vault Excessive Access Detected +- azure +name: Repeated Successful Key Vault Secret or Key Reads impact: - confidentiality: 3 + confidentiality: 2 integrity: 2 availability: 1 category: Collection -technique: "T1530 - Data from Cloud Storage Object" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/azure/key-vault/general/logging - - https://attack.mitre.org/techniques/T1530/ -description: | - Detects unusual spikes in Azure Key Vault access patterns. Monitors for multiple secret retrieval operations from the same source, which could indicate credential harvesting or data exfiltration attempts. - - Next Steps: - 1. Investigate the source IP address and verify if it's a legitimate system or user - 2. Review the specific secrets/keys being accessed and their criticality - 3. Check for any recent changes to Key Vault access policies - 4. Correlate with user authentication logs to identify the account responsible - 5. Verify if the access pattern aligns with normal business operations - 6. Consider implementing additional access controls or monitoring if suspicious activity is confirmed -where: | - equals("log.category", "AuditEvent") && - oneOf("log.operationName", ["SecretGet", "SecretList", "KeyGet"]) -afterEvents: - - indexPattern: v11-log-azure-* - with: - - field: origin.ip - operator: filter_term - value: '{{.origin.ip}}' - - field: log.category - operator: filter_term - value: 'AuditEvent' - within: 10m - count: 20 +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/azure/key-vault/general/logging +- https://attack.mitre.org/techniques/T1530/ +description: 'At least 20 successful SecretGet, SecretList or KeyGet operations by one actor against one vault and collector in + ten minutes. Unrelated AuditEvent operations and denied access cannot satisfy the threshold. + + ' +where: '(equalsIgnoreCase("log.azureKind","keyvault") && equalsIgnoreCase("actionResult","success") && (equalsIgnoreCase("log.azureOperation","SecretGet") + || equalsIgnoreCase("log.azureOperation","SecretList") || equalsIgnoreCase("log.azureOperation","KeyGet"))) && exists("dataSource") + && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") && exists("log.azureScope") && + !equals("log.azureScope","") && exists("log.azureActorType") && !equals("log.azureActorType","") && exists("log.azureActor") + && !equals("log.azureActor","") + + ' groupBy: - - lastEvent.log.resourceId - - adversary.ip +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip +correlation: +- indexPattern: v11-log-azure-* + with: + - field: dataSource + operator: filter_term + value: '{{.dataSource}}' + - field: log.azureScopeType + operator: filter_term + value: '{{.log.azureScopeType}}' + - field: log.azureScope + operator: filter_term + value: '{{.log.azureScope}}' + - field: log.azureActorType + operator: filter_term + value: '{{.log.azureActorType}}' + - field: log.azureActor + operator: filter_term + value: '{{.log.azureActor}}' + - field: log.correlationCandidate.key_vault_access_spikes + operator: filter_term + value: 'true' + within: 10m + count: 20 diff --git a/rules/cloud/azure/managed_identity_abuse.yml b/rules/cloud/azure/managed_identity_abuse.yml deleted file mode 100644 index f63865cb8..000000000 --- a/rules/cloud/azure/managed_identity_abuse.yml +++ /dev/null @@ -1,43 +0,0 @@ -# Rule version v1.0.0 - -dataTypes: - - azure -name: Azure Managed Identity Token Abuse -impact: - confidentiality: 3 - integrity: 3 - availability: 1 -category: Credential Access -technique: "T1078.004 - Valid Accounts: Cloud Accounts" -adversary: origin -references: - - https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview - - https://attack.mitre.org/techniques/T1078/004/ -description: | - Detects suspicious token acquisition from Azure Instance Metadata Service (IMDS) by managed identities. Attackers who compromise an Azure VM can abuse managed identities to obtain access tokens for Azure resources without credentials, enabling lateral movement across the cloud environment. - - Next Steps: - 1. Identify the Azure resource (VM, App Service, Function) where the token was acquired - 2. Review the target resource being accessed with the managed identity token - 3. Check if the managed identity's permissions follow least privilege principles - 4. Investigate the process or application that requested the token - 5. Review Azure Activity logs for actions performed using the managed identity - 6. If unauthorized, restrict the managed identity's role assignments immediately - 7. Investigate the source VM for signs of compromise - 8. Implement Conditional Access policies for workload identities -where: | - contains("log.operationName", "Microsoft.ManagedIdentity") && - equals("log.categoryValue", "Administrative") && - (contains("log.properties.message", "token") || - contains("log.operationName", "tokens")) -afterEvents: - - indexPattern: v11-log-azure-* - with: - - field: origin.ip - operator: filter_term - value: '{{.origin.ip}}' - within: 1h - count: 5 -groupBy: - - lastEvent.log.operationName - - adversary.ip diff --git a/rules/cloud/azure/mfa_disabled_privileged_users.yml b/rules/cloud/azure/mfa_disabled_privileged_users.yml deleted file mode 100644 index 77bc82067..000000000 --- a/rules/cloud/azure/mfa_disabled_privileged_users.yml +++ /dev/null @@ -1,32 +0,0 @@ -# Rule version v1.0.0 - -dataTypes: - - azure -name: MFA Disabled for Privileged Azure AD User -impact: - confidentiality: 3 - integrity: 3 - availability: 1 -category: Defense Evasion -technique: "T1556 - Modify Authentication Process" -adversary: origin -references: - - https://learn.microsoft.com/en-us/entra/identity/authentication/howto-mfa-reporting - - https://attack.mitre.org/techniques/T1556/ -description: | - Detects when Multi-Factor Authentication (MFA) is disabled for privileged users in Azure AD. This could indicate an attempt to weaken security controls for unauthorized access. - - Next Steps: - 1. Verify if the MFA disable action was authorized and legitimate - 2. Check who initiated the change and from which IP address - 3. Review the user's recent login activity and permissions - 4. Ensure the user account has not been compromised - 5. Re-enable MFA if the change was unauthorized - 6. Consider implementing conditional access policies to prevent unauthorized MFA changes -where: | - (equals("log.operationName", "Disable Strong Authentication") || - (equals("log.operationName", "Update user") && contains("log.properties", "StrongAuthenticationMethod"))) && - equals("log.categoryValue", "Administrative") -groupBy: - - lastEvent.log.correlationId - - lastEvent.log.targetUserPrincipalName diff --git a/rules/cloud/azure/persistence_azure_pim_user_added_global_admin.yml b/rules/cloud/azure/persistence_azure_pim_user_added_global_admin.yml index a673bde3a..e5bf5dd1f 100644 --- a/rules/cloud/azure/persistence_azure_pim_user_added_global_admin.yml +++ b/rules/cloud/azure/persistence_azure_pim_user_added_global_admin.yml @@ -1,81 +1,137 @@ -# Rule version v1.0.4 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: "Azure Global Administrator Role Addition to PIM User" +- azure +name: Azure Global Administrator Role Addition to PIM User impact: confidentiality: 3 integrity: 3 availability: 3 -category: "Persistence" -technique: "T1098.001 - Account Manipulation: Additional Cloud Credentials" -adversary: target -description: | - Detects when users are granted Global Administrator (Company Administrator) role assignments through Azure AD/Entra ID Privileged Identity Management (PIM). - +category: Persistence +technique: 'T1098.001 - Account Manipulation: Additional Cloud Credentials' +adversary: origin +description: 'Detects when users are granted Global Administrator (Company Administrator) role assignments through Azure AD/Entra + ID Privileged Identity Management (PIM). + + **Security Context:** - The Global Administrator role is the most powerful administrative role in Azure AD/Entra ID, granting complete control over all aspects of the directory and services that use Azure AD identities. PIM enables just-in-time privileged access through eligible (requires activation) or time-bound assignments. Adversaries who gain sufficient privileges may add themselves or other compromised accounts to this role to establish persistence and maintain full administrative control over the tenant. - + + The Global Administrator role is the most powerful administrative role in Azure AD/Entra ID, granting complete control over all + aspects of the directory and services that use Azure AD identities. PIM enables just-in-time privileged access through eligible + (requires activation) or time-bound assignments. Adversaries who gain sufficient privileges may add themselves or other compromised + accounts to this role to establish persistence and maintain full administrative control over the tenant. + + **Detection Logic:** + This rule monitors AuditLogs for successful PIM role assignments specifically for the Global Administrator role. It detects both: + - **Eligible assignments (permanent)**: User can activate the role when needed + - **Active assignments (time-bound)**: Role is directly active for a specified duration - + + The rule identifies these assignments through the operation names and filters for the Global Administrator role specifically. - + + **Investigation Steps:** + 1. Identify the assignor: Check log.propertiesInitiatedBy for who made the role assignment + 2. Identify the assignee: Examine log.propertiesTargetResources for the user receiving the role + 3. Verify authorization: Confirm if this assignment was part of approved privileged access request - 4. Check assignment type: Determine if it's eligible (requires activation) or time-bound (direct) + + 4. Check assignment type: Determine if it''s eligible (requires activation) or time-bound (direct) + 5. Review duration: For time-bound assignments, check the duration of the assignment + 6. Analyze timing: Determine if assignment follows suspicious authentication or compromise indicators + 7. Review justification: Check if a business justification was provided in log.propertiesAdditionalDetails - 8. Check user history: Review the assignee's account for recent suspicious activity + + 8. Check user history: Review the assignee''s account for recent suspicious activity + 9. Examine recent actions: Look for privileged operations performed immediately after assignment + 10. Correlate with sign-ins: Check for unusual authentication patterns before/after assignment - + + **Recommended Actions:** + - If unauthorized, immediately revoke the Global Administrator role assignment + - Review all recent PIM role assignments for anomalies + - Enable PIM approval workflows for Global Administrator role assignments + - Implement maximum assignment duration limits for time-bound assignments + - Require MFA and justification for all Global Administrator activations + - Enable PIM alerts for high-privilege role assignments + - Audit accounts with Privileged Role Administrator permissions + - Review and limit the number of permanent Global Administrator assignments + - Enable Azure AD Identity Protection to detect compromised credentials + - Implement break-glass emergency access accounts following best practices - + + **PIM Assignment Types:** + - **Eligible (permanent)**: User must activate the role when needed, typically with MFA and justification + - **Active (time-bound)**: Role is directly assigned for a limited duration without activation required + - Both types should be monitored as adversaries may use either for persistence - + + **Common Attack Patterns:** + - Compromised Privileged Role Administrator adding backdoor accounts + - Insider threat establishing persistent administrative access + - Privilege escalation from lower-privilege administrative roles + - Adding service principals or managed identities to Global Administrator role + - Creating long-duration time-bound assignments for sustained access - + + **MITRE ATT&CK Reference:** T1098.001 - Account Manipulation: Additional Cloud Credentials - + + **Azure Documentation:** + - AuditLogs table: https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/auditlogs + - PIM for Azure AD roles: https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-configure + ' references: - - "https://attack.mitre.org/techniques/T1098/001/" - - "https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/auditlogs" - - "https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-configure" - - "https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#global-administrator" - -where: | - equalsIgnoreCase("log.category", "AuditLogs") && - (equals("log.resultType", "0") || equalsIgnoreCase("actionResult", "success")) && - (contains("log.operationName", "Add eligible member to role") || contains("log.operationName", "Add member to role")) && - (contains("log.properties.targetResources.displayName", "Global Administrator") || contains("log.properties.targetResources.displayName", "Company Administrator")) +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://attack.mitre.org/techniques/T1098/001/ +- https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/auditlogs +- https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-configure +- https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#global-administrator +where: 'equalsIgnoreCase("log.azureKind","audit") && equalsIgnoreCase("actionResult","success") && (equalsIgnoreCase("log.azureOperation","Add + member to role") || equalsIgnoreCase("log.azureOperation","Add eligible member to role") || equalsIgnoreCase("log.azureOperation","Add + member to role completed (PIM activation)") || equalsIgnoreCase("log.azureOperation","Add eligible member to role in PIM completed") + || equalsIgnoreCase("log.azureOperation","Add eligible member to role in PIM completed (permanent)") || equalsIgnoreCase("log.azureOperation","Add + eligible member to role in PIM completed (timebound)") || equalsIgnoreCase("log.azureOperation","Add member to role in PIM completed + (permanent)") || equalsIgnoreCase("log.azureOperation","Add member to role in PIM completed (timebound)")) && (exists("log.properties.targetResources.#(displayName==\"Global + Administrator\")") || exists("log.properties.targetResources.#(modifiedProperties.#(displayName==\"Role.DisplayName\").newValue==\"\\\"Global + Administrator\\\"\")") || exists("log.properties.targetResources.#(displayName==\"Company Administrator\")") || exists("log.properties.targetResources.#(modifiedProperties.#(displayName==\"Role.DisplayName\").newValue==\"\\\"Company + Administrator\\\"\")")) + + ' groupBy: - - target.ip - - target.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/persistence_mfa_disabled_for_azure_user.yml b/rules/cloud/azure/persistence_mfa_disabled_for_azure_user.yml index 846740d8b..7a8a980c2 100644 --- a/rules/cloud/azure/persistence_mfa_disabled_for_azure_user.yml +++ b/rules/cloud/azure/persistence_mfa_disabled_for_azure_user.yml @@ -1,84 +1,138 @@ -# Rule version v1.0.4 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: "Multi-Factor Authentication Disabled for an Azure User" +- azure +name: Multi-Factor Authentication Disabled for an Azure User impact: confidentiality: 3 integrity: 3 availability: 2 -category: "Persistence" -technique: "T1556 - Modify Authentication Process" -adversary: target -description: | - Detects when multi-factor authentication (MFA) is disabled for an Azure AD/Entra ID user account through Audit Logs. - +category: Persistence +technique: T1556 - Modify Authentication Process +adversary: origin +description: 'Detects when multi-factor authentication (MFA) is disabled for an Azure AD/Entra ID user account through Audit Logs. + + **Security Context:** - Multi-factor authentication is a critical security control that requires users to provide additional verification beyond just a password. Disabling MFA for user accounts significantly weakens authentication security and is a common technique used by adversaries to maintain persistent access. Once MFA is disabled, attackers can authenticate using only compromised credentials without triggering additional verification steps, making detection more difficult. - + + Multi-factor authentication is a critical security control that requires users to provide additional verification beyond just + a password. Disabling MFA for user accounts significantly weakens authentication security and is a common technique used by adversaries + to maintain persistent access. Once MFA is disabled, attackers can authenticate using only compromised credentials without triggering + additional verification steps, making detection more difficult. + + **Detection Logic:** - This rule monitors AuditLogs for successful "Disable Strong Authentication" operations, which represent the per-user MFA setting being turned off in Azure AD/Entra ID. This operation is distinct from Conditional Access MFA policies and represents the legacy per-user MFA enforcement method. - + + This rule monitors AuditLogs for successful "Disable Strong Authentication" operations, which represent the per-user MFA setting + being turned off in Azure AD/Entra ID. This operation is distinct from Conditional Access MFA policies and represents the legacy + per-user MFA enforcement method. + + **Investigation Steps:** + 1. Identify the disabler: Check log.propertiesInitiatedBy for who disabled MFA + 2. Identify affected user: Examine log.propertiesTargetResources for the user whose MFA was disabled + 3. Verify authorization: Confirm if the MFA disabling was part of legitimate administrative action + 4. Review user privilege: Determine if the affected user has elevated permissions (admins, privileged roles) + 5. Check timing: Analyze if MFA was disabled after suspicious authentication events + 6. Review authentication history: Look for failed authentication attempts before MFA disabling + 7. Check for compromise indicators: Search for unusual sign-in patterns, impossible travel, or risky sign-ins + 8. Examine subsequent logins: Monitor for authentication activity immediately after MFA disabling + 9. Review MFA methods: Check what MFA methods the user had registered before disabling + 10. Correlate with other events: Look for privilege escalation or data access after MFA disabling - + + **Recommended Actions:** + - If unauthorized, immediately re-enable MFA for the affected user + - Force password reset for the affected account + - Review all authentication activity for the affected user + - Check for compromised credentials using Azure AD Identity Protection + - Revoke all active sessions for the affected user + - Enable Conditional Access policies instead of per-user MFA for better control + - Implement PIM approval workflows for modifying MFA settings + - Enable alerts for MFA changes on privileged accounts + - Audit accounts with permissions to modify user authentication settings + - Review and restrict who can disable MFA (typically requires User Administrator or higher) - + + **Modern MFA Management:** + - **Per-user MFA (legacy)**: This detection targets the legacy per-user MFA setting + - **Conditional Access**: Modern approach using policies instead of per-user settings + - **Authentication Methods Policy**: Newer method for managing FIDO2, passwordless, etc. - + + Organizations should migrate from per-user MFA to Conditional Access policies for more granular control. - + + **Common Attack Patterns:** + - Disabling MFA after compromising an administrator account + - Removing MFA from privileged accounts for easier persistent access + - Disabling MFA before credential harvesting or lateral movement + - Insider threats removing MFA from their own accounts + - Disabling MFA on service accounts to enable automated authentication attacks - + + **Related Detections:** + - MFA method removal/changes + - Conditional Access policy modifications + - Authentication methods policy changes + - Privileged role assignments without MFA - + + **MITRE ATT&CK Reference:** T1556 - Modify Authentication Process - + + **Azure Documentation:** + - AuditLogs table: https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/auditlogs + - Per-user MFA: https://learn.microsoft.com/en-us/entra/identity/authentication/howto-mfa-userstates + ' references: - - "https://attack.mitre.org/techniques/T1556/" - - "https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/auditlogs" - - "https://learn.microsoft.com/en-us/entra/identity/authentication/howto-mfa-userstates" - - "https://learn.microsoft.com/en-us/entra/identity/authentication/concept-mfa-licensing" - -where: | - equalsIgnoreCase("log.category", "AuditLogs") && - equalsIgnoreCase("log.operationName", "Disable Strong Authentication") && - (equals("log.resultType", "0") || equalsIgnoreCase("actionResult", "success")) +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://attack.mitre.org/techniques/T1556/ +- https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/auditlogs +- https://learn.microsoft.com/en-us/entra/identity/authentication/howto-mfa-userstates +- https://learn.microsoft.com/en-us/entra/identity/authentication/concept-mfa-licensing +where: 'equalsIgnoreCase("log.azureKind","audit") && equalsIgnoreCase("actionResult","success") && equalsIgnoreCase("log.azureOperation","Disable + Strong Authentication") + + ' groupBy: - - target.ip - - target.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/pim_role_activation_abuse.yml b/rules/cloud/azure/pim_role_activation_abuse.yml index 5a2155db0..3d982f99e 100644 --- a/rules/cloud/azure/pim_role_activation_abuse.yml +++ b/rules/cloud/azure/pim_role_activation_abuse.yml @@ -1,46 +1,59 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure PIM Role Activation Anomaly +- azure +name: Repeated Entra PIM Role Activations impact: - confidentiality: 3 - integrity: 3 + confidentiality: 2 + integrity: 2 availability: 1 category: Privilege Escalation -technique: "T1078 - Valid Accounts" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/azure/active-directory/privileged-identity-management/pim-configure - - https://attack.mitre.org/techniques/T1078/ -description: | - Detects unusual Privileged Identity Management (PIM) role activation patterns including activation of high-privilege roles such as Global Administrator or Privileged Role Administrator. Repeated or unusual PIM activations may indicate an attacker leveraging compromised credentials to escalate privileges. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/azure/active-directory/privileged-identity-management/pim-configure +- https://attack.mitre.org/techniques/T1078/ +description: 'At least three completed eligible-member additions or PIM role activations by one actor in one directory and collector + in four hours. Review approved role activation workflows; count alone does not prove privilege abuse. + + ' +where: '(equalsIgnoreCase("log.azureKind","audit") && equalsIgnoreCase("actionResult","success") && (equalsIgnoreCase("log.azureOperation","Add + member to role completed (PIM activation)") || equalsIgnoreCase("log.azureOperation","Add eligible member to role in PIM completed") + || equalsIgnoreCase("log.azureOperation","Add eligible member to role in PIM completed (permanent)") || equalsIgnoreCase("log.azureOperation","Add + eligible member to role in PIM completed (timebound)") || equalsIgnoreCase("log.azureOperation","Add member to role in PIM completed + (permanent)") || equalsIgnoreCase("log.azureOperation","Add member to role in PIM completed (timebound)"))) && exists("dataSource") + && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") && exists("log.azureScope") && + !equals("log.azureScope","") && exists("log.azureActorType") && !equals("log.azureActorType","") && exists("log.azureActor") + && !equals("log.azureActor","") - Next Steps: - 1. Verify the user activating the PIM role has legitimate business justification - 2. Review the specific role being activated and its scope - 3. Check the activation justification message provided by the user - 4. Review the activation duration and whether it exceeds normal patterns - 5. Check for unusual source IP or device during the activation - 6. If unauthorized, immediately deactivate the role and disable the user account - 7. Review PIM audit logs for other suspicious activations by the same user - 8. Implement PIM access reviews and require approval for critical roles -where: | - (contains("log.operationName", "Add member to role completed (PIM activation)") || - contains("log.operationName", "Add eligible member to role in PIM completed") || - contains("log.operationName", "Activate PIM role")) && - equals("log.categoryValue", "Administrative") -afterEvents: - - indexPattern: v11-log-azure-* - with: - - field: origin.user - operator: filter_term - value: '{{.origin.user}}' - - field: log.categoryValue - operator: filter_term - value: Administrative - within: 4h - count: 3 + ' groupBy: - - lastEvent.log.operationName - - adversary.user +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip +correlation: +- indexPattern: v11-log-azure-* + with: + - field: dataSource + operator: filter_term + value: '{{.dataSource}}' + - field: log.azureScopeType + operator: filter_term + value: '{{.log.azureScopeType}}' + - field: log.azureScope + operator: filter_term + value: '{{.log.azureScope}}' + - field: log.azureActorType + operator: filter_term + value: '{{.log.azureActorType}}' + - field: log.azureActor + operator: filter_term + value: '{{.log.azureActor}}' + - field: log.correlationCandidate.pim_role_activation_abuse + operator: filter_term + value: 'true' + within: 4h + count: 3 diff --git a/rules/cloud/azure/storage_account_public_access.yml b/rules/cloud/azure/storage_account_public_access.yml index 49cd01328..31ecd743d 100644 --- a/rules/cloud/azure/storage_account_public_access.yml +++ b/rules/cloud/azure/storage_account_public_access.yml @@ -1,36 +1,33 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Storage Account Public Access Enabled +- azure +name: Azure Storage Public Access Configuration Written impact: - confidentiality: 3 + confidentiality: 2 integrity: 2 availability: 1 category: Collection -technique: "T1530 - Data from Cloud Storage Object" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log-schema - - https://attack.mitre.org/techniques/T1530/ -description: | - Detects when public access is enabled on Azure Storage Accounts which could lead to unauthorized data exposure. - This configuration change creates a significant security risk as it allows anonymous access to stored data. +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log-schema +- https://attack.mitre.org/techniques/T1530/ +description: 'A successful storage-account write explicitly allows blob public access, or a container write specifies Blob/Container + public access. The request body must be present. An account-level permission alone does not make every blob public, and a written + value does not prove it changed. + + ' +where: '(equalsIgnoreCase("log.azureKind","activity") || equalsIgnoreCase("log.azureKind","eventgrid")) && equalsIgnoreCase("actionResult","success") + && ((equalsIgnoreCase("log.azureOperation","Microsoft.Storage/storageAccounts/write") && equals("log.azureRequestBody.properties.allowBlobPublicAccess","true")) + || (equalsIgnoreCase("log.azureOperation","Microsoft.Storage/storageAccounts/blobServices/containers/write") && oneOf("log.azureRequestBody.properties.publicAccess",["Blob", + "Container"]))) - Next Steps: - 1. Immediately review the affected storage account configuration - 2. Verify if public access was intentionally enabled and properly authorized - 3. Check if any sensitive data is stored in the account - 4. Review access logs for any unauthorized access attempts - 5. Consider disabling public access if not required for business operations - 6. Implement network restrictions and access policies if public access is necessary - 7. Monitor for any data exfiltration activities -where: | - contains("log.operationName", "Microsoft.Storage/storageAccounts") && - (contains("log.operationName", "/write") || contains("log.operationName", "/blobServices/write")) && - equals("log.category", "Administrative") && - equals("log.actionResult", "accepted") && - (contains("log.properties", "allowBlobPublicAccess") || contains("log.properties", "publicAccess")) + ' groupBy: - - lastEvent.log.aadObjectId - - lastEvent.log.resourceId +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip diff --git a/rules/cloud/azure/subscription_ownership_transfer.yml b/rules/cloud/azure/subscription_ownership_transfer.yml index 7d1982a0e..068c59499 100644 --- a/rules/cloud/azure/subscription_ownership_transfer.yml +++ b/rules/cloud/azure/subscription_ownership_transfer.yml @@ -1,35 +1,31 @@ -# Rule version v1.0.0 - +# Azure rule contract review v2.1.0 dataTypes: - - azure -name: Azure Subscription Ownership Transfer Detected +- azure +name: Azure Subscription Owner Role Assigned impact: - confidentiality: 3 - integrity: 3 - availability: 2 + confidentiality: 2 + integrity: 2 + availability: 1 category: Identity and Access Management -technique: "T1078 - Valid Accounts" +technique: '' adversary: origin references: - - https://learn.microsoft.com/en-us/azure/role-based-access-control/change-history-report - - https://attack.mitre.org/techniques/T1078/ -description: | - Detects when ownership of an Azure subscription is transferred by monitoring role assignment changes at the subscription level. This could indicate unauthorized access or insider threat activity. - - Next Steps: - 1. Verify the legitimacy of the ownership transfer with the subscription administrator - 2. Check if the user performing the transfer is authorized for this action - 3. Review the timing and context of the transfer (business hours, planned change) - 4. Examine other recent activities by the same user or from the same source IP - 5. Validate that proper change management procedures were followed - 6. Check for any unusual activity following the ownership transfer - 7. If unauthorized, immediately revoke the new owner's access and escalate to security team -where: | - equals("log.operationName", "Microsoft.Authorization/roleAssignments/write") && - contains("log.properties", "Owner") && - equals("log.category", "Administrative") && - contains("log.resourceId", "/subscriptions/") && - !contains("log.resourceId", "/resourceGroups/") +- https://learn.microsoft.com/en-us/azure/azure-monitor/platform/resource-logs-schema +- https://learn.microsoft.com/en-us/azure/role-based-access-control/change-history-report +- https://attack.mitre.org/techniques/T1078/ +description: 'A successful role assignment at subscription scope explicitly targets the built-in Owner role in the request body. + The actor authorization role is not used as the granted role; a role assignment does not prove billing ownership transfer. + + ' +where: '(equalsIgnoreCase("log.azureKind","activity") || equalsIgnoreCase("log.azureKind","eventgrid")) && equalsIgnoreCase("actionResult","success") + && equalsIgnoreCase("log.azureOperation","Microsoft.Authorization/roleAssignments/write") && regexMatch("log.resourceId","(?i)^/subscriptions/[^/]+/providers/Microsoft\\.Authorization/roleAssignments/[^/]+$") + && regexMatch("log.azureRequestBody.properties.roleDefinitionId","(?i)/providers/Microsoft\\.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635$") + + ' groupBy: - - lastEvent.log.correlationId - - adversary.ip +- lastEvent.dataSource +- lastEvent.log.azureScopeType +- lastEvent.log.azureScope +- lastEvent.log.azureOperation +- adversary.user +- adversary.ip From 4dd316757f991f41b0fba3d7db653ef22558a934 Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Thu, 17 Sep 2026 21:09:31 -0400 Subject: [PATCH 3/3] fix(azure): preserve existing rule impact scores --- filters/audits/azure.md | 2 +- rules/cloud/azure/aks_security_threats.yml | 6 +++--- rules/cloud/azure/app_registration_abuse.yml | 4 ++-- rules/cloud/azure/automation_runbook_abuse.yml | 6 +++--- rules/cloud/azure/azure_ad_impossible_travel.yml | 2 +- rules/cloud/azure/azure_ad_password_spray.yml | 2 +- rules/cloud/azure/azure_app_credential_added.yml | 6 +++--- rules/cloud/azure/azure_app_privileged_permissions.yml | 6 +++--- rules/cloud/azure/azure_azurehound_discovery.yml | 4 ++-- rules/cloud/azure/azure_device_code_auth_abuse.yml | 2 +- rules/cloud/azure/azure_disk_snapshot_exfiltration.yml | 2 +- rules/cloud/azure/azure_federation_modified.yml | 6 +++--- rules/cloud/azure/azure_kubernetes_secret_access.yml | 6 +++--- rules/cloud/azure/azure_laps_credential_dump.yml | 2 +- rules/cloud/azure/azure_new_root_ca_added.yml | 6 +++--- rules/cloud/azure/azure_prt_access_attempt.yml | 4 ++-- rules/cloud/azure/azure_sentinel_alert_patterns.yml | 6 +++--- rules/cloud/azure/azure_temporary_access_pass.yml | 2 +- rules/cloud/azure/credential_access_key_vault_modified.yml | 6 +++--- rules/cloud/azure/defender_cloud_critical_alerts.yml | 6 +++--- rules/cloud/azure/key_vault_access_spikes.yml | 2 +- rules/cloud/azure/pim_role_activation_abuse.yml | 4 ++-- rules/cloud/azure/storage_account_public_access.yml | 2 +- rules/cloud/azure/subscription_ownership_transfer.yml | 6 +++--- 24 files changed, 50 insertions(+), 50 deletions(-) diff --git a/filters/audits/azure.md b/filters/audits/azure.md index c68f62b26..93f3c1d14 100644 --- a/filters/audits/azure.md +++ b/filters/audits/azure.md @@ -72,7 +72,7 @@ matches (for example “Administrative” in endpoint telemetry) are not categor negative fixtures. PIM selectors bind the role property name and value to the same item. No general `Update user` event is equated with MFA disablement, and risk/token labels no longer imply impossible travel, credential theft, Golden SAML or completed exfiltration - without evidence. Existing numeric protocol aliases and HTTP action vocabulary are retained. + without evidence. Existing rule impact scores, numeric protocol aliases and HTTP action vocabulary are retained. ## Correlation and duplicate alerts diff --git a/rules/cloud/azure/aks_security_threats.yml b/rules/cloud/azure/aks_security_threats.yml index 34aab83e5..e94010d4a 100644 --- a/rules/cloud/azure/aks_security_threats.yml +++ b/rules/cloud/azure/aks_security_threats.yml @@ -3,9 +3,9 @@ dataTypes: - azure name: Repeated AKS Resource Changes impact: - confidentiality: 2 - integrity: 2 - availability: 1 + confidentiality: 3 + integrity: 3 + availability: 2 category: Execution technique: '' adversary: origin diff --git a/rules/cloud/azure/app_registration_abuse.yml b/rules/cloud/azure/app_registration_abuse.yml index 9ce8b77fc..5befc0a01 100644 --- a/rules/cloud/azure/app_registration_abuse.yml +++ b/rules/cloud/azure/app_registration_abuse.yml @@ -3,8 +3,8 @@ dataTypes: - azure name: Repeated Entra Application Registration or Consent impact: - confidentiality: 2 - integrity: 2 + confidentiality: 3 + integrity: 3 availability: 1 category: Persistence technique: '' diff --git a/rules/cloud/azure/automation_runbook_abuse.yml b/rules/cloud/azure/automation_runbook_abuse.yml index fcffb7f93..c3756089a 100644 --- a/rules/cloud/azure/automation_runbook_abuse.yml +++ b/rules/cloud/azure/automation_runbook_abuse.yml @@ -3,9 +3,9 @@ dataTypes: - azure name: Azure Automation Runbook, Job or Schedule Changed impact: - confidentiality: 2 - integrity: 2 - availability: 1 + confidentiality: 3 + integrity: 3 + availability: 2 category: Execution technique: '' adversary: origin diff --git a/rules/cloud/azure/azure_ad_impossible_travel.yml b/rules/cloud/azure/azure_ad_impossible_travel.yml index ee04974ef..a58cf14ca 100644 --- a/rules/cloud/azure/azure_ad_impossible_travel.yml +++ b/rules/cloud/azure/azure_ad_impossible_travel.yml @@ -3,7 +3,7 @@ dataTypes: - azure name: Entra Risky Sign-in impact: - confidentiality: 2 + confidentiality: 3 integrity: 2 availability: 1 category: Credential Access diff --git a/rules/cloud/azure/azure_ad_password_spray.yml b/rules/cloud/azure/azure_ad_password_spray.yml index e27d44a9b..b9cf9fc3e 100644 --- a/rules/cloud/azure/azure_ad_password_spray.yml +++ b/rules/cloud/azure/azure_ad_password_spray.yml @@ -3,7 +3,7 @@ dataTypes: - azure name: Repeated Entra Invalid-Credential Sign-ins impact: - confidentiality: 2 + confidentiality: 3 integrity: 2 availability: 1 category: Credential Access diff --git a/rules/cloud/azure/azure_app_credential_added.yml b/rules/cloud/azure/azure_app_credential_added.yml index 7605f2eff..deb8e1a9d 100644 --- a/rules/cloud/azure/azure_app_credential_added.yml +++ b/rules/cloud/azure/azure_app_credential_added.yml @@ -3,9 +3,9 @@ dataTypes: - azure name: Entra Application Credential Configuration Changed impact: - confidentiality: 2 - integrity: 2 - availability: 1 + confidentiality: 3 + integrity: 3 + availability: 2 category: Persistence technique: '' adversary: origin diff --git a/rules/cloud/azure/azure_app_privileged_permissions.yml b/rules/cloud/azure/azure_app_privileged_permissions.yml index 23cd4c6bb..f67b810c2 100644 --- a/rules/cloud/azure/azure_app_privileged_permissions.yml +++ b/rules/cloud/azure/azure_app_privileged_permissions.yml @@ -3,9 +3,9 @@ dataTypes: - azure name: Entra Application Role Assignment impact: - confidentiality: 2 - integrity: 2 - availability: 1 + confidentiality: 3 + integrity: 3 + availability: 2 category: Privilege Escalation technique: '' adversary: origin diff --git a/rules/cloud/azure/azure_azurehound_discovery.yml b/rules/cloud/azure/azure_azurehound_discovery.yml index 9ebef2a8a..2293f2559 100644 --- a/rules/cloud/azure/azure_azurehound_discovery.yml +++ b/rules/cloud/azure/azure_azurehound_discovery.yml @@ -4,8 +4,8 @@ dataTypes: name: Entra Sign-in with AzureHound User Agent impact: confidentiality: 2 - integrity: 2 - availability: 1 + integrity: 1 + availability: 0 category: Discovery technique: '' adversary: origin diff --git a/rules/cloud/azure/azure_device_code_auth_abuse.yml b/rules/cloud/azure/azure_device_code_auth_abuse.yml index 50308b8eb..4562425ae 100644 --- a/rules/cloud/azure/azure_device_code_auth_abuse.yml +++ b/rules/cloud/azure/azure_device_code_auth_abuse.yml @@ -3,7 +3,7 @@ dataTypes: - azure name: High Risk Device Code Sign-in impact: - confidentiality: 2 + confidentiality: 3 integrity: 2 availability: 1 category: Initial Access diff --git a/rules/cloud/azure/azure_disk_snapshot_exfiltration.yml b/rules/cloud/azure/azure_disk_snapshot_exfiltration.yml index ddaac470a..ed978218b 100644 --- a/rules/cloud/azure/azure_disk_snapshot_exfiltration.yml +++ b/rules/cloud/azure/azure_disk_snapshot_exfiltration.yml @@ -3,7 +3,7 @@ dataTypes: - azure name: Azure Disk or Snapshot Access Granted impact: - confidentiality: 2 + confidentiality: 3 integrity: 2 availability: 1 category: Data Exfiltration diff --git a/rules/cloud/azure/azure_federation_modified.yml b/rules/cloud/azure/azure_federation_modified.yml index d1c2ee687..76a3c2776 100644 --- a/rules/cloud/azure/azure_federation_modified.yml +++ b/rules/cloud/azure/azure_federation_modified.yml @@ -3,9 +3,9 @@ dataTypes: - azure name: Entra Domain Federation Configuration Changed impact: - confidentiality: 2 - integrity: 2 - availability: 1 + confidentiality: 3 + integrity: 3 + availability: 3 category: Credential Access technique: '' adversary: origin diff --git a/rules/cloud/azure/azure_kubernetes_secret_access.yml b/rules/cloud/azure/azure_kubernetes_secret_access.yml index d82626165..e03991574 100644 --- a/rules/cloud/azure/azure_kubernetes_secret_access.yml +++ b/rules/cloud/azure/azure_kubernetes_secret_access.yml @@ -3,9 +3,9 @@ dataTypes: - azure name: Repeated AKS Secret Writes or Deletes impact: - confidentiality: 2 - integrity: 2 - availability: 1 + confidentiality: 3 + integrity: 3 + availability: 2 category: Credential Access technique: '' adversary: origin diff --git a/rules/cloud/azure/azure_laps_credential_dump.yml b/rules/cloud/azure/azure_laps_credential_dump.yml index dfe925507..ecba0e7c7 100644 --- a/rules/cloud/azure/azure_laps_credential_dump.yml +++ b/rules/cloud/azure/azure_laps_credential_dump.yml @@ -3,7 +3,7 @@ dataTypes: - azure name: Repeated Entra LAPS Password Reads impact: - confidentiality: 2 + confidentiality: 3 integrity: 2 availability: 1 category: Credential Access diff --git a/rules/cloud/azure/azure_new_root_ca_added.yml b/rules/cloud/azure/azure_new_root_ca_added.yml index bdf98340f..8ff8d8b1e 100644 --- a/rules/cloud/azure/azure_new_root_ca_added.yml +++ b/rules/cloud/azure/azure_new_root_ca_added.yml @@ -3,9 +3,9 @@ dataTypes: - azure name: Entra Certificate Authentication Configuration Created impact: - confidentiality: 2 - integrity: 2 - availability: 1 + confidentiality: 3 + integrity: 3 + availability: 2 category: Persistence technique: '' adversary: origin diff --git a/rules/cloud/azure/azure_prt_access_attempt.yml b/rules/cloud/azure/azure_prt_access_attempt.yml index 9c59e2b46..f8b0f4988 100644 --- a/rules/cloud/azure/azure_prt_access_attempt.yml +++ b/rules/cloud/azure/azure_prt_access_attempt.yml @@ -3,8 +3,8 @@ dataTypes: - azure name: High Risk Primary Refresh Token Sign-in impact: - confidentiality: 2 - integrity: 2 + confidentiality: 3 + integrity: 3 availability: 1 category: Credential Access technique: '' diff --git a/rules/cloud/azure/azure_sentinel_alert_patterns.yml b/rules/cloud/azure/azure_sentinel_alert_patterns.yml index 5345b4594..0e230e63f 100644 --- a/rules/cloud/azure/azure_sentinel_alert_patterns.yml +++ b/rules/cloud/azure/azure_sentinel_alert_patterns.yml @@ -3,9 +3,9 @@ dataTypes: - azure name: Forwarded High Severity Security Alert impact: - confidentiality: 2 - integrity: 2 - availability: 1 + confidentiality: 3 + integrity: 3 + availability: 2 category: Threat Detection technique: '' adversary: origin diff --git a/rules/cloud/azure/azure_temporary_access_pass.yml b/rules/cloud/azure/azure_temporary_access_pass.yml index 4724b37f5..b7549dd0c 100644 --- a/rules/cloud/azure/azure_temporary_access_pass.yml +++ b/rules/cloud/azure/azure_temporary_access_pass.yml @@ -3,7 +3,7 @@ dataTypes: - azure name: Entra Temporary Access Pass Registered impact: - confidentiality: 2 + confidentiality: 3 integrity: 2 availability: 1 category: Credential Access diff --git a/rules/cloud/azure/credential_access_key_vault_modified.yml b/rules/cloud/azure/credential_access_key_vault_modified.yml index c6d8434b3..2a675ee9f 100644 --- a/rules/cloud/azure/credential_access_key_vault_modified.yml +++ b/rules/cloud/azure/credential_access_key_vault_modified.yml @@ -3,9 +3,9 @@ dataTypes: - azure name: Azure Key Vault Configuration Changed impact: - confidentiality: 2 - integrity: 2 - availability: 1 + confidentiality: 3 + integrity: 3 + availability: 2 category: Credential Access technique: '' adversary: origin diff --git a/rules/cloud/azure/defender_cloud_critical_alerts.yml b/rules/cloud/azure/defender_cloud_critical_alerts.yml index 5c5830c22..7c80e011b 100644 --- a/rules/cloud/azure/defender_cloud_critical_alerts.yml +++ b/rules/cloud/azure/defender_cloud_critical_alerts.yml @@ -3,9 +3,9 @@ dataTypes: - azure name: Microsoft Defender for Cloud High Severity Alert impact: - confidentiality: 2 - integrity: 2 - availability: 1 + confidentiality: 3 + integrity: 3 + availability: 2 category: Intrusion Detection technique: '' adversary: origin diff --git a/rules/cloud/azure/key_vault_access_spikes.yml b/rules/cloud/azure/key_vault_access_spikes.yml index 1c8c35e98..67aa8a742 100644 --- a/rules/cloud/azure/key_vault_access_spikes.yml +++ b/rules/cloud/azure/key_vault_access_spikes.yml @@ -3,7 +3,7 @@ dataTypes: - azure name: Repeated Successful Key Vault Secret or Key Reads impact: - confidentiality: 2 + confidentiality: 3 integrity: 2 availability: 1 category: Collection diff --git a/rules/cloud/azure/pim_role_activation_abuse.yml b/rules/cloud/azure/pim_role_activation_abuse.yml index 3d982f99e..fbc145b54 100644 --- a/rules/cloud/azure/pim_role_activation_abuse.yml +++ b/rules/cloud/azure/pim_role_activation_abuse.yml @@ -3,8 +3,8 @@ dataTypes: - azure name: Repeated Entra PIM Role Activations impact: - confidentiality: 2 - integrity: 2 + confidentiality: 3 + integrity: 3 availability: 1 category: Privilege Escalation technique: '' diff --git a/rules/cloud/azure/storage_account_public_access.yml b/rules/cloud/azure/storage_account_public_access.yml index 31ecd743d..c84f6760c 100644 --- a/rules/cloud/azure/storage_account_public_access.yml +++ b/rules/cloud/azure/storage_account_public_access.yml @@ -3,7 +3,7 @@ dataTypes: - azure name: Azure Storage Public Access Configuration Written impact: - confidentiality: 2 + confidentiality: 3 integrity: 2 availability: 1 category: Collection diff --git a/rules/cloud/azure/subscription_ownership_transfer.yml b/rules/cloud/azure/subscription_ownership_transfer.yml index 068c59499..875288b48 100644 --- a/rules/cloud/azure/subscription_ownership_transfer.yml +++ b/rules/cloud/azure/subscription_ownership_transfer.yml @@ -3,9 +3,9 @@ dataTypes: - azure name: Azure Subscription Owner Role Assigned impact: - confidentiality: 2 - integrity: 2 - availability: 1 + confidentiality: 3 + integrity: 3 + availability: 2 category: Identity and Access Management technique: '' adversary: origin