From fde05417373118634b8a0ae6bc174d36a7cefda2 Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Wed, 23 Sep 2026 11:24:27 -0400 Subject: [PATCH 1/2] Derive final Azure outcomes and safely extract nested audit fields --- filters/azure/azure-eventhub.yml | 1334 +++++++++++++---- plugins/alerts/azure_action_result_test.go | 57 + plugins/alerts/azure_contract_test.go | 20 +- .../alerts/testdata/azure_action_result.json | 623 ++++++++ plugins/alerts/testdata/azure_raw.json | 16 +- 5 files changed, 1734 insertions(+), 316 deletions(-) create mode 100644 plugins/alerts/azure_action_result_test.go create mode 100644 plugins/alerts/testdata/azure_action_result.json diff --git a/filters/azure/azure-eventhub.yml b/filters/azure/azure-eventhub.yml index 0c7c22a1f..dcfcc700e 100644 --- a/filters/azure/azure-eventhub.yml +++ b/filters/azure/azure-eventhub.yml @@ -1,4 +1,4 @@ -# Azure Event Hub filter v2.1.0 +# Azure Event Hub filter v2.2.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: @@ -731,243 +731,502 @@ pipeline: - log.azureActor - log.azureActorType - log.correlationCandidate - - rename: - from: - - log.properties - to: log.azureProperties - where: equalsIgnoreCase("log.category","AppServiceHTTPLogs") && exists("log.properties.CIp") + # Parse embedded JSON into temporary scalar leaves, then restore the original + # envelope before using the values. Embedded keys cannot spoof status/category. - add: function: string params: - key: azureDecodePending + key: azureHTTPDecodePending value: 'yes' - where: (equalsIgnoreCase("log.category","AppServiceHTTPLogs")) && regexMatch("log.properties","^\\s*\\{") + where: >- + equalsIgnoreCase("log.category","AppServiceHTTPLogs") && (exists("log.properties.ScStatus") || exists("log.properties.CIp") || regexMatch("log.properties","^\\s*\\{")) + - json: + source: log.properties + where: equals("azureHTTPDecodePending","yes") + - rename: + from: + - log.CIp + to: azureNestedHTTPCIp + where: equals("azureHTTPDecodePending","yes") && exists("log.CIp") + - rename: + from: + - log.CsUsername + to: azureNestedHTTPCsUsername + where: equals("azureHTTPDecodePending","yes") && exists("log.CsUsername") + - rename: + from: + - log.ComputerName + to: azureNestedHTTPComputerName + where: equals("azureHTTPDecodePending","yes") && exists("log.ComputerName") + - rename: + from: + - log.CsHost + to: azureNestedHTTPCsHost + where: equals("azureHTTPDecodePending","yes") && exists("log.CsHost") + - rename: + from: + - log.CsMethod + to: azureNestedHTTPCsMethod + where: equals("azureHTTPDecodePending","yes") && exists("log.CsMethod") + - rename: + from: + - log.SPort + to: azureNestedHTTPSPort + where: equals("azureHTTPDecodePending","yes") && exists("log.SPort") - rename: from: + - log.ScStatus + to: azureNestedHTTPScStatus + where: equals("azureHTTPDecodePending","yes") && exists("log.ScStatus") + - rename: + from: + - log.CsBytes + to: azureNestedHTTPCsBytes + where: equals("azureHTTPDecodePending","yes") && exists("log.CsBytes") + - rename: + from: + - log.ScBytes + to: azureNestedHTTPScBytes + where: equals("azureHTTPDecodePending","yes") && exists("log.ScBytes") + - rename: + from: + - log.Protocol + to: azureNestedHTTPProtocol + where: equals("azureHTTPDecodePending","yes") && exists("log.Protocol") + - delete: + fields: - log - to: azureEnvelope - where: equals("azureDecodePending","yes") + where: equals("azureHTTPDecodePending","yes") - json: - source: azureEnvelope.properties - where: equals("azureDecodePending","yes") + source: raw + where: equals("azureHTTPDecodePending","yes") - rename: from: - - log - to: azureDecoded - where: equals("azureDecodePending","yes") + - azureNestedHTTPCIp + to: log.azureProperties.CIp + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPCIp") - rename: from: - - azureEnvelope - to: log - where: equals("azureDecodePending","yes") + - azureNestedHTTPCsUsername + to: log.azureProperties.CsUsername + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPCsUsername") - rename: from: - - azureDecoded - to: log.azureProperties - where: equals("azureDecodePending","yes") + - azureNestedHTTPComputerName + to: log.azureProperties.ComputerName + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPComputerName") + - rename: + from: + - azureNestedHTTPCsHost + to: log.azureProperties.CsHost + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPCsHost") + - rename: + from: + - azureNestedHTTPCsMethod + to: log.azureProperties.CsMethod + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPCsMethod") + - rename: + from: + - azureNestedHTTPSPort + to: log.azureProperties.SPort + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPSPort") + - rename: + from: + - azureNestedHTTPScStatus + to: log.azureProperties.ScStatus + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPScStatus") + - rename: + from: + - azureNestedHTTPCsBytes + to: log.azureProperties.CsBytes + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPCsBytes") + - rename: + from: + - azureNestedHTTPScBytes + to: log.azureProperties.ScBytes + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPScBytes") + - rename: + from: + - azureNestedHTTPProtocol + to: log.azureProperties.Protocol + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPProtocol") - delete: fields: - - azureDecodePending - - azureDecoded - - azureEnvelope + - azureHTTPDecodePending - add: function: string params: - key: azureDecodePending + key: azureKubeDecodePending value: 'yes' - where: (oneOf("log.category",["kube-audit","kube-audit-admin"])) && regexMatch("log.properties.log","^\\s*\\{") + where: >- + oneOf("log.category",["kube-audit","kube-audit-admin"]) && regexMatch("log.properties.log","^\\s*\\{") + - json: + source: log.properties.log + where: equals("azureKubeDecodePending","yes") + - json: + source: log.responseStatus + where: >- + equals("azureKubeDecodePending","yes") && + !exists("log.responseStatus.code") && regexMatch("log.responseStatus","^\\s*\\{") - rename: from: - - log - to: azureEnvelope - where: equals("azureDecodePending","yes") + - log.code + to: azureNestedKuberesponseStatuscode + where: equals("azureKubeDecodePending","yes") && exists("log.code") - json: - source: azureEnvelope.properties.log - where: equals("azureDecodePending","yes") + source: log.objectRef + where: >- + equals("azureKubeDecodePending","yes") && + !exists("log.objectRef.resource") && regexMatch("log.objectRef","^\\s*\\{") - rename: from: - - log - to: azureDecoded - where: equals("azureDecodePending","yes") + - log.resource + to: azureNestedKubeobjectRefresource + where: equals("azureKubeDecodePending","yes") && exists("log.resource") + - json: + source: log.user + where: >- + equals("azureKubeDecodePending","yes") && + !exists("log.user.username") && regexMatch("log.user","^\\s*\\{") - rename: from: - - azureEnvelope - to: log - where: equals("azureDecodePending","yes") + - log.username + to: azureNestedKubeuserusername + where: equals("azureKubeDecodePending","yes") && exists("log.username") - rename: from: - - azureDecoded - to: log.azureKubernetes - where: equals("azureDecodePending","yes") + - log.apiVersion + to: azureNestedKubeapiVersion + where: equals("azureKubeDecodePending","yes") && exists("log.apiVersion") + - rename: + from: + - log.kind + to: azureNestedKubekind + where: equals("azureKubeDecodePending","yes") && exists("log.kind") + - rename: + from: + - log.stage + to: azureNestedKubestage + where: equals("azureKubeDecodePending","yes") && exists("log.stage") + - rename: + from: + - log.verb + to: azureNestedKubeverb + where: equals("azureKubeDecodePending","yes") && exists("log.verb") + - rename: + from: + - log.user.username + to: azureNestedKubeuserusername + where: equals("azureKubeDecodePending","yes") && exists("log.user.username") + - rename: + from: + - log.responseStatus.code + to: azureNestedKuberesponseStatuscode + where: equals("azureKubeDecodePending","yes") && exists("log.responseStatus.code") + - rename: + from: + - log.objectRef.resource + to: azureNestedKubeobjectRefresource + where: equals("azureKubeDecodePending","yes") && exists("log.objectRef.resource") + - rename: + from: + - log.requestURI + to: azureNestedKuberequestURI + where: equals("azureKubeDecodePending","yes") && exists("log.requestURI") + - grok: + source: log.sourceIPs.0 + patterns: + - fieldName: azureNestedKubeSourceIp + pattern: (?s)^.+$ + where: equals("azureKubeDecodePending","yes") && regexMatch("log.sourceIPs.0","(?s)^.+$") - delete: fields: - - azureDecodePending - - azureDecoded - - azureEnvelope + - log + where: equals("azureKubeDecodePending","yes") + - json: + source: raw + where: equals("azureKubeDecodePending","yes") + - rename: + from: + - azureNestedKubeapiVersion + to: log.azureKubernetes.apiVersion + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKubeapiVersion") + - rename: + from: + - azureNestedKubekind + to: log.azureKubernetes.kind + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKubekind") + - rename: + from: + - azureNestedKubestage + to: log.azureKubernetes.stage + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKubestage") + - rename: + from: + - azureNestedKubeverb + to: log.azureKubernetes.verb + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKubeverb") + - rename: + from: + - azureNestedKubeuserusername + to: log.azureKubernetes.user.username + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKubeuserusername") + - rename: + from: + - azureNestedKuberesponseStatuscode + to: log.azureKubernetes.responseStatus.code + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKuberesponseStatuscode") + - rename: + from: + - azureNestedKubeobjectRefresource + to: log.azureKubernetes.objectRef.resource + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKubeobjectRefresource") + - rename: + from: + - azureNestedKuberequestURI + to: log.azureKubernetes.requestURI + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKuberequestURI") - 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") + - azureNestedKubeSourceIp + to: log.azureKubernetes.sourceIp + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKubeSourceIp") + - delete: + fields: + - azureKubeDecodePending + # Decode only the request-body leaves consumed by standard mappings/rules. + # Restore raw first so embedded keys cannot replace the authoritative envelope. - 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*\\{") + key: azureRequestDecoded + value: 'variant0' + where: >- + (regexMatch("log.operationName","(?i)^Microsoft\\.(Storage|Authorization)/") || regexMatch("log.operationName.value","(?i)^Microsoft\\.(Storage|Authorization)/")) && !exists("azureRequestDecoded") && (exists("log.properties.requestbody.properties") || regexMatch("log.properties.requestbody","^\\s*\\{")) + - json: + source: log.properties.requestbody + where: equals("azureRequestDecoded","variant0") - rename: from: - - log - to: azureEnvelope - where: equals("azureDecodePending","yes") - - json: - source: azureEnvelope.properties.requestbody - where: equals("azureDecodePending","yes") + - log.properties.principalId + to: azureRequestprincipalId + where: equals("azureRequestDecoded","variant0") && exists("log.properties.principalId") - rename: from: - - log - to: azureDecoded - where: equals("azureDecodePending","yes") + - log.properties.roleDefinitionId + to: azureRequestroleDefinitionId + where: equals("azureRequestDecoded","variant0") && exists("log.properties.roleDefinitionId") - rename: from: - - azureEnvelope - to: log - where: equals("azureDecodePending","yes") + - log.properties.allowBlobPublicAccess + to: azureRequestallowBlobPublicAccess + where: equals("azureRequestDecoded","variant0") && exists("log.properties.allowBlobPublicAccess") - rename: from: - - azureDecoded - to: log.azureRequestBody - where: equals("azureDecodePending","yes") + - log.properties.publicAccess + to: azureRequestpublicAccess + where: equals("azureRequestDecoded","variant0") && exists("log.properties.publicAccess") - delete: fields: - - azureDecodePending - - azureDecoded - - azureEnvelope + - log + where: equals("azureRequestDecoded","variant0") + - json: + source: raw + where: equals("azureRequestDecoded","variant0") - 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") + - azureRequestprincipalId + to: log.azureRequestBody.properties.principalId + where: equals("azureRequestDecoded","variant0") && exists("azureRequestprincipalId") + - rename: + from: + - azureRequestroleDefinitionId + to: log.azureRequestBody.properties.roleDefinitionId + where: equals("azureRequestDecoded","variant0") && exists("azureRequestroleDefinitionId") + - rename: + from: + - azureRequestallowBlobPublicAccess + to: log.azureRequestBody.properties.allowBlobPublicAccess + where: equals("azureRequestDecoded","variant0") && exists("azureRequestallowBlobPublicAccess") + - rename: + from: + - azureRequestpublicAccess + to: log.azureRequestBody.properties.publicAccess + where: equals("azureRequestDecoded","variant0") && exists("azureRequestpublicAccess") - 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*\\{") + key: azureRequestDecoded + value: 'variant1' + where: >- + (regexMatch("log.operationName","(?i)^Microsoft\\.(Storage|Authorization)/") || regexMatch("log.operationName.value","(?i)^Microsoft\\.(Storage|Authorization)/")) && !exists("azureRequestDecoded") && (exists("log.properties.requestBody.properties") || regexMatch("log.properties.requestBody","^\\s*\\{")) + - json: + source: log.properties.requestBody + where: equals("azureRequestDecoded","variant1") - rename: from: - - log - to: azureEnvelope - where: equals("azureDecodePending","yes") - - json: - source: azureEnvelope.properties.requestBody - where: equals("azureDecodePending","yes") + - log.properties.principalId + to: azureRequestprincipalId + where: equals("azureRequestDecoded","variant1") && exists("log.properties.principalId") - rename: from: - - log - to: azureDecoded - where: equals("azureDecodePending","yes") + - log.properties.roleDefinitionId + to: azureRequestroleDefinitionId + where: equals("azureRequestDecoded","variant1") && exists("log.properties.roleDefinitionId") - rename: from: - - azureEnvelope - to: log - where: equals("azureDecodePending","yes") + - log.properties.allowBlobPublicAccess + to: azureRequestallowBlobPublicAccess + where: equals("azureRequestDecoded","variant1") && exists("log.properties.allowBlobPublicAccess") - rename: from: - - azureDecoded - to: log.azureRequestBody - where: equals("azureDecodePending","yes") + - log.properties.publicAccess + to: azureRequestpublicAccess + where: equals("azureRequestDecoded","variant1") && exists("log.properties.publicAccess") - delete: fields: - - azureDecodePending - - azureDecoded - - azureEnvelope + - log + where: equals("azureRequestDecoded","variant1") + - json: + source: raw + where: equals("azureRequestDecoded","variant1") + - rename: + from: + - azureRequestprincipalId + to: log.azureRequestBody.properties.principalId + where: equals("azureRequestDecoded","variant1") && exists("azureRequestprincipalId") + - rename: + from: + - azureRequestroleDefinitionId + to: log.azureRequestBody.properties.roleDefinitionId + where: equals("azureRequestDecoded","variant1") && exists("azureRequestroleDefinitionId") - 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") + - azureRequestallowBlobPublicAccess + to: log.azureRequestBody.properties.allowBlobPublicAccess + where: equals("azureRequestDecoded","variant1") && exists("azureRequestallowBlobPublicAccess") + - rename: + from: + - azureRequestpublicAccess + to: log.azureRequestBody.properties.publicAccess + where: equals("azureRequestDecoded","variant1") && exists("azureRequestpublicAccess") - 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*\\{") + key: azureRequestDecoded + value: 'variant2' + where: >- + (regexMatch("log.operationName","(?i)^Microsoft\\.(Storage|Authorization)/") || regexMatch("log.operationName.value","(?i)^Microsoft\\.(Storage|Authorization)/")) && !exists("azureRequestDecoded") && (exists("log.properties.eventProperties.requestbody.properties") || regexMatch("log.properties.eventProperties.requestbody","^\\s*\\{")) + - json: + source: log.properties.eventProperties.requestbody + where: equals("azureRequestDecoded","variant2") - rename: from: - - log - to: azureEnvelope - where: equals("azureDecodePending","yes") - - json: - source: azureEnvelope.properties.eventProperties.requestbody - where: equals("azureDecodePending","yes") + - log.properties.principalId + to: azureRequestprincipalId + where: equals("azureRequestDecoded","variant2") && exists("log.properties.principalId") - rename: from: - - log - to: azureDecoded - where: equals("azureDecodePending","yes") + - log.properties.roleDefinitionId + to: azureRequestroleDefinitionId + where: equals("azureRequestDecoded","variant2") && exists("log.properties.roleDefinitionId") - rename: from: - - azureEnvelope - to: log - where: equals("azureDecodePending","yes") + - log.properties.allowBlobPublicAccess + to: azureRequestallowBlobPublicAccess + where: equals("azureRequestDecoded","variant2") && exists("log.properties.allowBlobPublicAccess") - rename: from: - - azureDecoded - to: log.azureRequestBody - where: equals("azureDecodePending","yes") + - log.properties.publicAccess + to: azureRequestpublicAccess + where: equals("azureRequestDecoded","variant2") && exists("log.properties.publicAccess") - delete: fields: - - azureDecodePending - - azureDecoded - - azureEnvelope + - log + where: equals("azureRequestDecoded","variant2") + - json: + source: raw + where: equals("azureRequestDecoded","variant2") - 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") + - azureRequestprincipalId + to: log.azureRequestBody.properties.principalId + where: equals("azureRequestDecoded","variant2") && exists("azureRequestprincipalId") + - rename: + from: + - azureRequestroleDefinitionId + to: log.azureRequestBody.properties.roleDefinitionId + where: equals("azureRequestDecoded","variant2") && exists("azureRequestroleDefinitionId") + - rename: + from: + - azureRequestallowBlobPublicAccess + to: log.azureRequestBody.properties.allowBlobPublicAccess + where: equals("azureRequestDecoded","variant2") && exists("azureRequestallowBlobPublicAccess") + - rename: + from: + - azureRequestpublicAccess + to: log.azureRequestBody.properties.publicAccess + where: equals("azureRequestDecoded","variant2") && exists("azureRequestpublicAccess") - 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*\\{") + key: azureRequestDecoded + value: 'variant3' + where: >- + (regexMatch("log.operationName","(?i)^Microsoft\\.(Storage|Authorization)/") || regexMatch("log.operationName.value","(?i)^Microsoft\\.(Storage|Authorization)/")) && !exists("azureRequestDecoded") && (exists("log.properties.eventProperties.requestBody.properties") || regexMatch("log.properties.eventProperties.requestBody","^\\s*\\{")) + - json: + source: log.properties.eventProperties.requestBody + where: equals("azureRequestDecoded","variant3") - rename: from: + - log.properties.principalId + to: azureRequestprincipalId + where: equals("azureRequestDecoded","variant3") && exists("log.properties.principalId") + - rename: + from: + - log.properties.roleDefinitionId + to: azureRequestroleDefinitionId + where: equals("azureRequestDecoded","variant3") && exists("log.properties.roleDefinitionId") + - rename: + from: + - log.properties.allowBlobPublicAccess + to: azureRequestallowBlobPublicAccess + where: equals("azureRequestDecoded","variant3") && exists("log.properties.allowBlobPublicAccess") + - rename: + from: + - log.properties.publicAccess + to: azureRequestpublicAccess + where: equals("azureRequestDecoded","variant3") && exists("log.properties.publicAccess") + - delete: + fields: - log - to: azureEnvelope - where: equals("azureDecodePending","yes") + where: equals("azureRequestDecoded","variant3") - json: - source: azureEnvelope.properties.eventProperties.requestBody - where: equals("azureDecodePending","yes") + source: raw + where: equals("azureRequestDecoded","variant3") - rename: from: - - log - to: azureDecoded - where: equals("azureDecodePending","yes") + - azureRequestprincipalId + to: log.azureRequestBody.properties.principalId + where: equals("azureRequestDecoded","variant3") && exists("azureRequestprincipalId") - rename: from: - - azureEnvelope - to: log - where: equals("azureDecodePending","yes") + - azureRequestroleDefinitionId + to: log.azureRequestBody.properties.roleDefinitionId + where: equals("azureRequestDecoded","variant3") && exists("azureRequestroleDefinitionId") - rename: from: - - azureDecoded - to: log.azureRequestBody - where: equals("azureDecodePending","yes") + - azureRequestallowBlobPublicAccess + to: log.azureRequestBody.properties.allowBlobPublicAccess + where: equals("azureRequestDecoded","variant3") && exists("azureRequestallowBlobPublicAccess") + - rename: + from: + - azureRequestpublicAccess + to: log.azureRequestBody.properties.publicAccess + where: equals("azureRequestDecoded","variant3") && exists("azureRequestpublicAccess") - delete: fields: - - azureDecodePending - - azureDecoded - - azureEnvelope + - azureRequestDecoded - add: function: string params: @@ -1155,13 +1414,13 @@ pipeline: && (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 + source: log.azureKubernetes.sourceIp 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") + where: regexMatch("log.azureKubernetes.sourceIp","(?s)^.+$") && !oneOf("log.azureKubernetes.sourceIp",["-","unknown"]) + && ((equalsIgnoreCase("log.azureKind","kubernetes")) && (inCIDR("log.azureKubernetes.sourceIp","0.0.0.0/0") || inCIDR("log.azureKubernetes.sourceIp","::/0")) + && !inCIDR("log.azureKubernetes.sourceIp","0.0.0.0/32") && !inCIDR("log.azureKubernetes.sourceIp","::/128")) && !exists("origin.ip") - grok: source: log.callerIpAddress patterns: @@ -1864,40 +2123,506 @@ pipeline: pattern: (?s)^.+$ where: regexMatch("log.azureProperties.Protocol","(?s)^.+$") && !oneOf("log.azureProperties.Protocol",["-","unknown"]) && (equalsIgnoreCase("log.azureKind","http")) && !exists("protocol") + - json: + source: raw + - delete: + fields: *id001 + # Parse embedded JSON into temporary scalar leaves, then restore the original + # envelope before using the values. Embedded keys cannot spoof status/category. + - add: + function: string + params: + key: azureHTTPDecodePending + value: 'yes' + where: >- + equalsIgnoreCase("log.category","AppServiceHTTPLogs") && (exists("log.properties.ScStatus") || exists("log.properties.CIp") || regexMatch("log.properties","^\\s*\\{")) + - json: + source: log.properties + where: equals("azureHTTPDecodePending","yes") - rename: from: - - log.azureProperties - to: azureSavedazureProperties - where: exists("log.azureProperties") + - log.CIp + to: azureNestedHTTPCIp + where: equals("azureHTTPDecodePending","yes") && exists("log.CIp") - rename: from: - - log.azureKubernetes - to: azureSavedazureKubernetes - where: exists("log.azureKubernetes") + - log.CsUsername + to: azureNestedHTTPCsUsername + where: equals("azureHTTPDecodePending","yes") && exists("log.CsUsername") - rename: from: - - log.azureRequestBody - to: azureSavedazureRequestBody - where: exists("log.azureRequestBody") + - log.ComputerName + to: azureNestedHTTPComputerName + where: equals("azureHTTPDecodePending","yes") && exists("log.ComputerName") + - rename: + from: + - log.CsHost + to: azureNestedHTTPCsHost + where: equals("azureHTTPDecodePending","yes") && exists("log.CsHost") + - rename: + from: + - log.CsMethod + to: azureNestedHTTPCsMethod + where: equals("azureHTTPDecodePending","yes") && exists("log.CsMethod") + - rename: + from: + - log.SPort + to: azureNestedHTTPSPort + where: equals("azureHTTPDecodePending","yes") && exists("log.SPort") + - rename: + from: + - log.ScStatus + to: azureNestedHTTPScStatus + where: equals("azureHTTPDecodePending","yes") && exists("log.ScStatus") + - rename: + from: + - log.CsBytes + to: azureNestedHTTPCsBytes + where: equals("azureHTTPDecodePending","yes") && exists("log.CsBytes") + - rename: + from: + - log.ScBytes + to: azureNestedHTTPScBytes + where: equals("azureHTTPDecodePending","yes") && exists("log.ScBytes") + - rename: + from: + - log.Protocol + to: azureNestedHTTPProtocol + where: equals("azureHTTPDecodePending","yes") && exists("log.Protocol") + - delete: + fields: + - log + where: equals("azureHTTPDecodePending","yes") - json: source: raw + where: equals("azureHTTPDecodePending","yes") + - rename: + from: + - azureNestedHTTPCIp + to: log.azureProperties.CIp + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPCIp") + - rename: + from: + - azureNestedHTTPCsUsername + to: log.azureProperties.CsUsername + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPCsUsername") + - rename: + from: + - azureNestedHTTPComputerName + to: log.azureProperties.ComputerName + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPComputerName") + - rename: + from: + - azureNestedHTTPCsHost + to: log.azureProperties.CsHost + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPCsHost") + - rename: + from: + - azureNestedHTTPCsMethod + to: log.azureProperties.CsMethod + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPCsMethod") + - rename: + from: + - azureNestedHTTPSPort + to: log.azureProperties.SPort + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPSPort") + - rename: + from: + - azureNestedHTTPScStatus + to: log.azureProperties.ScStatus + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPScStatus") + - rename: + from: + - azureNestedHTTPCsBytes + to: log.azureProperties.CsBytes + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPCsBytes") + - rename: + from: + - azureNestedHTTPScBytes + to: log.azureProperties.ScBytes + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPScBytes") + - rename: + from: + - azureNestedHTTPProtocol + to: log.azureProperties.Protocol + where: equals("azureHTTPDecodePending","yes") && exists("azureNestedHTTPProtocol") - delete: - fields: *id001 + fields: + - azureHTTPDecodePending + - add: + function: string + params: + key: azureKubeDecodePending + value: 'yes' + where: >- + oneOf("log.category",["kube-audit","kube-audit-admin"]) && regexMatch("log.properties.log","^\\s*\\{") + - json: + source: log.properties.log + where: equals("azureKubeDecodePending","yes") + - json: + source: log.responseStatus + where: >- + equals("azureKubeDecodePending","yes") && + !exists("log.responseStatus.code") && regexMatch("log.responseStatus","^\\s*\\{") - rename: from: - - azureSavedazureProperties - to: log.azureProperties - where: exists("azureSavedazureProperties") + - log.code + to: azureNestedKuberesponseStatuscode + where: equals("azureKubeDecodePending","yes") && exists("log.code") + - json: + source: log.objectRef + where: >- + equals("azureKubeDecodePending","yes") && + !exists("log.objectRef.resource") && regexMatch("log.objectRef","^\\s*\\{") - rename: from: - - azureSavedazureKubernetes - to: log.azureKubernetes - where: exists("azureSavedazureKubernetes") + - log.resource + to: azureNestedKubeobjectRefresource + where: equals("azureKubeDecodePending","yes") && exists("log.resource") + - json: + source: log.user + where: >- + equals("azureKubeDecodePending","yes") && + !exists("log.user.username") && regexMatch("log.user","^\\s*\\{") + - rename: + from: + - log.username + to: azureNestedKubeuserusername + where: equals("azureKubeDecodePending","yes") && exists("log.username") + - rename: + from: + - log.apiVersion + to: azureNestedKubeapiVersion + where: equals("azureKubeDecodePending","yes") && exists("log.apiVersion") + - rename: + from: + - log.kind + to: azureNestedKubekind + where: equals("azureKubeDecodePending","yes") && exists("log.kind") + - rename: + from: + - log.stage + to: azureNestedKubestage + where: equals("azureKubeDecodePending","yes") && exists("log.stage") + - rename: + from: + - log.verb + to: azureNestedKubeverb + where: equals("azureKubeDecodePending","yes") && exists("log.verb") + - rename: + from: + - log.user.username + to: azureNestedKubeuserusername + where: equals("azureKubeDecodePending","yes") && exists("log.user.username") + - rename: + from: + - log.responseStatus.code + to: azureNestedKuberesponseStatuscode + where: equals("azureKubeDecodePending","yes") && exists("log.responseStatus.code") + - rename: + from: + - log.objectRef.resource + to: azureNestedKubeobjectRefresource + where: equals("azureKubeDecodePending","yes") && exists("log.objectRef.resource") + - rename: + from: + - log.requestURI + to: azureNestedKuberequestURI + where: equals("azureKubeDecodePending","yes") && exists("log.requestURI") + - grok: + source: log.sourceIPs.0 + patterns: + - fieldName: azureNestedKubeSourceIp + pattern: (?s)^.+$ + where: equals("azureKubeDecodePending","yes") && regexMatch("log.sourceIPs.0","(?s)^.+$") + - delete: + fields: + - log + where: equals("azureKubeDecodePending","yes") + - json: + source: raw + where: equals("azureKubeDecodePending","yes") + - rename: + from: + - azureNestedKubeapiVersion + to: log.azureKubernetes.apiVersion + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKubeapiVersion") + - rename: + from: + - azureNestedKubekind + to: log.azureKubernetes.kind + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKubekind") + - rename: + from: + - azureNestedKubestage + to: log.azureKubernetes.stage + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKubestage") + - rename: + from: + - azureNestedKubeverb + to: log.azureKubernetes.verb + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKubeverb") + - rename: + from: + - azureNestedKubeuserusername + to: log.azureKubernetes.user.username + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKubeuserusername") + - rename: + from: + - azureNestedKuberesponseStatuscode + to: log.azureKubernetes.responseStatus.code + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKuberesponseStatuscode") + - rename: + from: + - azureNestedKubeobjectRefresource + to: log.azureKubernetes.objectRef.resource + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKubeobjectRefresource") + - rename: + from: + - azureNestedKuberequestURI + to: log.azureKubernetes.requestURI + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKuberequestURI") + - rename: + from: + - azureNestedKubeSourceIp + to: log.azureKubernetes.sourceIp + where: equals("azureKubeDecodePending","yes") && exists("azureNestedKubeSourceIp") + - delete: + fields: + - azureKubeDecodePending + # Decode only the request-body leaves consumed by standard mappings/rules. + # Restore raw first so embedded keys cannot replace the authoritative envelope. + - add: + function: string + params: + key: azureRequestDecoded + value: 'variant0' + where: >- + (regexMatch("log.operationName","(?i)^Microsoft\\.(Storage|Authorization)/") || regexMatch("log.operationName.value","(?i)^Microsoft\\.(Storage|Authorization)/")) && !exists("azureRequestDecoded") && (exists("log.properties.requestbody.properties") || regexMatch("log.properties.requestbody","^\\s*\\{")) + - json: + source: log.properties.requestbody + where: equals("azureRequestDecoded","variant0") + - rename: + from: + - log.properties.principalId + to: azureRequestprincipalId + where: equals("azureRequestDecoded","variant0") && exists("log.properties.principalId") + - rename: + from: + - log.properties.roleDefinitionId + to: azureRequestroleDefinitionId + where: equals("azureRequestDecoded","variant0") && exists("log.properties.roleDefinitionId") + - rename: + from: + - log.properties.allowBlobPublicAccess + to: azureRequestallowBlobPublicAccess + where: equals("azureRequestDecoded","variant0") && exists("log.properties.allowBlobPublicAccess") + - rename: + from: + - log.properties.publicAccess + to: azureRequestpublicAccess + where: equals("azureRequestDecoded","variant0") && exists("log.properties.publicAccess") + - delete: + fields: + - log + where: equals("azureRequestDecoded","variant0") + - json: + source: raw + where: equals("azureRequestDecoded","variant0") + - rename: + from: + - azureRequestprincipalId + to: log.azureRequestBody.properties.principalId + where: equals("azureRequestDecoded","variant0") && exists("azureRequestprincipalId") + - rename: + from: + - azureRequestroleDefinitionId + to: log.azureRequestBody.properties.roleDefinitionId + where: equals("azureRequestDecoded","variant0") && exists("azureRequestroleDefinitionId") + - rename: + from: + - azureRequestallowBlobPublicAccess + to: log.azureRequestBody.properties.allowBlobPublicAccess + where: equals("azureRequestDecoded","variant0") && exists("azureRequestallowBlobPublicAccess") + - rename: + from: + - azureRequestpublicAccess + to: log.azureRequestBody.properties.publicAccess + where: equals("azureRequestDecoded","variant0") && exists("azureRequestpublicAccess") + - add: + function: string + params: + key: azureRequestDecoded + value: 'variant1' + where: >- + (regexMatch("log.operationName","(?i)^Microsoft\\.(Storage|Authorization)/") || regexMatch("log.operationName.value","(?i)^Microsoft\\.(Storage|Authorization)/")) && !exists("azureRequestDecoded") && (exists("log.properties.requestBody.properties") || regexMatch("log.properties.requestBody","^\\s*\\{")) + - json: + source: log.properties.requestBody + where: equals("azureRequestDecoded","variant1") + - rename: + from: + - log.properties.principalId + to: azureRequestprincipalId + where: equals("azureRequestDecoded","variant1") && exists("log.properties.principalId") + - rename: + from: + - log.properties.roleDefinitionId + to: azureRequestroleDefinitionId + where: equals("azureRequestDecoded","variant1") && exists("log.properties.roleDefinitionId") + - rename: + from: + - log.properties.allowBlobPublicAccess + to: azureRequestallowBlobPublicAccess + where: equals("azureRequestDecoded","variant1") && exists("log.properties.allowBlobPublicAccess") + - rename: + from: + - log.properties.publicAccess + to: azureRequestpublicAccess + where: equals("azureRequestDecoded","variant1") && exists("log.properties.publicAccess") + - delete: + fields: + - log + where: equals("azureRequestDecoded","variant1") + - json: + source: raw + where: equals("azureRequestDecoded","variant1") + - rename: + from: + - azureRequestprincipalId + to: log.azureRequestBody.properties.principalId + where: equals("azureRequestDecoded","variant1") && exists("azureRequestprincipalId") + - rename: + from: + - azureRequestroleDefinitionId + to: log.azureRequestBody.properties.roleDefinitionId + where: equals("azureRequestDecoded","variant1") && exists("azureRequestroleDefinitionId") + - rename: + from: + - azureRequestallowBlobPublicAccess + to: log.azureRequestBody.properties.allowBlobPublicAccess + where: equals("azureRequestDecoded","variant1") && exists("azureRequestallowBlobPublicAccess") + - rename: + from: + - azureRequestpublicAccess + to: log.azureRequestBody.properties.publicAccess + where: equals("azureRequestDecoded","variant1") && exists("azureRequestpublicAccess") + - add: + function: string + params: + key: azureRequestDecoded + value: 'variant2' + where: >- + (regexMatch("log.operationName","(?i)^Microsoft\\.(Storage|Authorization)/") || regexMatch("log.operationName.value","(?i)^Microsoft\\.(Storage|Authorization)/")) && !exists("azureRequestDecoded") && (exists("log.properties.eventProperties.requestbody.properties") || regexMatch("log.properties.eventProperties.requestbody","^\\s*\\{")) + - json: + source: log.properties.eventProperties.requestbody + where: equals("azureRequestDecoded","variant2") + - rename: + from: + - log.properties.principalId + to: azureRequestprincipalId + where: equals("azureRequestDecoded","variant2") && exists("log.properties.principalId") + - rename: + from: + - log.properties.roleDefinitionId + to: azureRequestroleDefinitionId + where: equals("azureRequestDecoded","variant2") && exists("log.properties.roleDefinitionId") + - rename: + from: + - log.properties.allowBlobPublicAccess + to: azureRequestallowBlobPublicAccess + where: equals("azureRequestDecoded","variant2") && exists("log.properties.allowBlobPublicAccess") + - rename: + from: + - log.properties.publicAccess + to: azureRequestpublicAccess + where: equals("azureRequestDecoded","variant2") && exists("log.properties.publicAccess") + - delete: + fields: + - log + where: equals("azureRequestDecoded","variant2") + - json: + source: raw + where: equals("azureRequestDecoded","variant2") + - rename: + from: + - azureRequestprincipalId + to: log.azureRequestBody.properties.principalId + where: equals("azureRequestDecoded","variant2") && exists("azureRequestprincipalId") + - rename: + from: + - azureRequestroleDefinitionId + to: log.azureRequestBody.properties.roleDefinitionId + where: equals("azureRequestDecoded","variant2") && exists("azureRequestroleDefinitionId") + - rename: + from: + - azureRequestallowBlobPublicAccess + to: log.azureRequestBody.properties.allowBlobPublicAccess + where: equals("azureRequestDecoded","variant2") && exists("azureRequestallowBlobPublicAccess") + - rename: + from: + - azureRequestpublicAccess + to: log.azureRequestBody.properties.publicAccess + where: equals("azureRequestDecoded","variant2") && exists("azureRequestpublicAccess") + - add: + function: string + params: + key: azureRequestDecoded + value: 'variant3' + where: >- + (regexMatch("log.operationName","(?i)^Microsoft\\.(Storage|Authorization)/") || regexMatch("log.operationName.value","(?i)^Microsoft\\.(Storage|Authorization)/")) && !exists("azureRequestDecoded") && (exists("log.properties.eventProperties.requestBody.properties") || regexMatch("log.properties.eventProperties.requestBody","^\\s*\\{")) + - json: + source: log.properties.eventProperties.requestBody + where: equals("azureRequestDecoded","variant3") + - rename: + from: + - log.properties.principalId + to: azureRequestprincipalId + where: equals("azureRequestDecoded","variant3") && exists("log.properties.principalId") + - rename: + from: + - log.properties.roleDefinitionId + to: azureRequestroleDefinitionId + where: equals("azureRequestDecoded","variant3") && exists("log.properties.roleDefinitionId") + - rename: + from: + - log.properties.allowBlobPublicAccess + to: azureRequestallowBlobPublicAccess + where: equals("azureRequestDecoded","variant3") && exists("log.properties.allowBlobPublicAccess") + - rename: + from: + - log.properties.publicAccess + to: azureRequestpublicAccess + where: equals("azureRequestDecoded","variant3") && exists("log.properties.publicAccess") + - delete: + fields: + - log + where: equals("azureRequestDecoded","variant3") + - json: + source: raw + where: equals("azureRequestDecoded","variant3") + - rename: + from: + - azureRequestprincipalId + to: log.azureRequestBody.properties.principalId + where: equals("azureRequestDecoded","variant3") && exists("azureRequestprincipalId") - rename: from: - - azureSavedazureRequestBody - to: log.azureRequestBody - where: exists("azureSavedazureRequestBody") + - azureRequestroleDefinitionId + to: log.azureRequestBody.properties.roleDefinitionId + where: equals("azureRequestDecoded","variant3") && exists("azureRequestroleDefinitionId") + - rename: + from: + - azureRequestallowBlobPublicAccess + to: log.azureRequestBody.properties.allowBlobPublicAccess + where: equals("azureRequestDecoded","variant3") && exists("azureRequestallowBlobPublicAccess") + - rename: + from: + - azureRequestpublicAccess + to: log.azureRequestBody.properties.publicAccess + where: equals("azureRequestDecoded","variant3") && exists("azureRequestpublicAccess") + - delete: + fields: + - azureRequestDecoded - add: function: string params: @@ -2009,153 +2734,135 @@ pipeline: - fieldName: log.azureOperation pattern: (?s)^.+$ where: regexMatch("log.operationName","(?s)^.+$") && !oneOf("log.operationName",["-","unknown"]) && (true) && !exists("log.azureOperation") + # Final outcome only. Positive evidence must be final and all present aliases must agree. + # Denial overrides failure; failure overrides a success signal. + - delete: + fields: + - actionResult - 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"]) + where: >- + (equalsIgnoreCase("log.azureKind","signin") && ((oneOf("log.resultType",[0]) || + oneOf("log.properties.status.errorCode",[0])) && (!exists("log.resultType") || + oneOf("log.resultType",[0])) && (!exists("log.properties.status.errorCode") || + oneOf("log.properties.status.errorCode",[0])) && (!exists("log.properties.conditionalAccessStatus") || + regexMatch("log.properties.conditionalAccessStatus","(?i)^(success|notApplied)$")))) || + (equalsIgnoreCase("log.azureKind","audit") && ((regexMatch("log.resultType","(?i)^(success|succeeded|successful|ok)$") || + regexMatch("log.properties.result","(?i)^(success|succeeded|successful|ok)$")) && (!exists("log.resultType") || + regexMatch("log.resultType","(?i)^(success|succeeded|successful|ok)$")) && (!exists("log.properties.result") || + regexMatch("log.properties.result","(?i)^(success|succeeded|successful|ok)$")))) || + (equalsIgnoreCase("log.azureKind","activity") && ((regexMatch("log.resultType","(?i)^(success|succeeded|successful|ok)$") || + regexMatch("log.status.value","(?i)^(success|succeeded|successful|ok)$")) && (!exists("log.resultType") || + regexMatch("log.resultType","(?i)^(success|succeeded|successful|ok)$")) && (!exists("log.status") || + regexMatch("log.status.value","(?i)^(success|succeeded|successful|ok)$")))) || + (equalsIgnoreCase("log.azureKind","eventgrid") && ((regexMatch("log.resultType","(?i)^(success|succeeded|successful|ok)$") || + regexMatch("log.data.status","(?i)^(success|succeeded|successful|ok)$") || + regexMatch("log.eventType","^Microsoft\\.Resources\\.Resource(Write|Delete|Action)Success$") || + regexMatch("log.type","^Microsoft\\.Resources\\.Resource(Write|Delete|Action)Success$")) && (!exists("log.resultType") || + regexMatch("log.resultType","(?i)^(success|succeeded|successful|ok)$")) && (!exists("log.data.status") || + regexMatch("log.data.status","(?i)^(success|succeeded|successful|ok)$")) && (!exists("log.eventType") || + regexMatch("log.eventType","^Microsoft\\.Resources\\.Resource(Write|Delete|Action)Success$")) && (!exists("log.type") || + regexMatch("log.type","^Microsoft\\.Resources\\.Resource(Write|Delete|Action)Success$")))) || + (equalsIgnoreCase("log.azureKind","http") && (oneOf("statusCode",[200,201,203,204,205,206,208,226]) && (!exists("log.StatusCode") || + oneOf("log.StatusCode",[200,201,203,204,205,206,208,226])) && (!exists("log.ResponseCode") || + oneOf("log.ResponseCode",[200,201,203,204,205,206,208,226])) && (!exists("log.properties.httpStatusCode") || + oneOf("log.properties.httpStatusCode",[200,201,203,204,205,206,208,226])) && (!exists("log.properties.httpStatus") || + oneOf("log.properties.httpStatus",[200,201,203,204,205,206,208,226])) && (!exists("log.properties.responseStatusCode") || + oneOf("log.properties.responseStatusCode",[200,201,203,204,205,206,208,226])) && (!exists("log.azureProperties.ScStatus") || + oneOf("log.azureProperties.ScStatus",[200,201,203,204,205,206,208,226])) && (!exists("log.azureKubernetes.responseStatus.code") || + oneOf("log.azureKubernetes.responseStatus.code",[200,201,203,204,205,206,208,226])) && (!exists("log.resultType") || + regexMatch("log.resultType","(?i)^(success|succeeded|successful|ok)$")))) || + (equalsIgnoreCase("log.azureKind","gateway") && (oneOf("statusCode",[200,201,203,204,205,206,208,226]) && (!exists("log.StatusCode") || + oneOf("log.StatusCode",[200,201,203,204,205,206,208,226])) && (!exists("log.ResponseCode") || + oneOf("log.ResponseCode",[200,201,203,204,205,206,208,226])) && (!exists("log.properties.httpStatusCode") || + oneOf("log.properties.httpStatusCode",[200,201,203,204,205,206,208,226])) && (!exists("log.properties.httpStatus") || + oneOf("log.properties.httpStatus",[200,201,203,204,205,206,208,226])) && (!exists("log.properties.responseStatusCode") || + oneOf("log.properties.responseStatusCode",[200,201,203,204,205,206,208,226])) && (!exists("log.azureProperties.ScStatus") || + oneOf("log.azureProperties.ScStatus",[200,201,203,204,205,206,208,226])) && (!exists("log.azureKubernetes.responseStatus.code") || + oneOf("log.azureKubernetes.responseStatus.code",[200,201,203,204,205,206,208,226])) && (!exists("log.resultType") || + regexMatch("log.resultType","(?i)^(success|succeeded|successful|ok)$")))) || + (equalsIgnoreCase("log.azureKind","keyvault") && ((regexMatch("log.resultType","(?i)^(success|succeeded|successful|ok)$") || + oneOf("statusCode",[200,201,203,204,205,206,208,226])) && (!exists("log.resultType") || + regexMatch("log.resultType","(?i)^(success|succeeded|successful|ok)$")) && (!exists("statusCode") || + oneOf("statusCode",[200,201,203,204,205,206,208,226]) && (!exists("log.StatusCode") || + oneOf("log.StatusCode",[200,201,203,204,205,206,208,226])) && (!exists("log.ResponseCode") || + oneOf("log.ResponseCode",[200,201,203,204,205,206,208,226])) && (!exists("log.properties.httpStatusCode") || + oneOf("log.properties.httpStatusCode",[200,201,203,204,205,206,208,226])) && (!exists("log.properties.httpStatus") || + oneOf("log.properties.httpStatus",[200,201,203,204,205,206,208,226])) && (!exists("log.properties.responseStatusCode") || + oneOf("log.properties.responseStatusCode",[200,201,203,204,205,206,208,226])) && (!exists("log.azureProperties.ScStatus") || + oneOf("log.azureProperties.ScStatus",[200,201,203,204,205,206,208,226])) && (!exists("log.azureKubernetes.responseStatus.code") || + oneOf("log.azureKubernetes.responseStatus.code",[200,201,203,204,205,206,208,226]))))) || + (equalsIgnoreCase("log.azureKind","kubernetes") && (equals("log.azureKubernetes.stage","ResponseComplete") && oneOf("statusCode",[200,201,203,204,205,206,208,226]) && (!exists("log.StatusCode") || + oneOf("log.StatusCode",[200,201,203,204,205,206,208,226])) && (!exists("log.ResponseCode") || + oneOf("log.ResponseCode",[200,201,203,204,205,206,208,226])) && (!exists("log.properties.httpStatusCode") || + oneOf("log.properties.httpStatusCode",[200,201,203,204,205,206,208,226])) && (!exists("log.properties.httpStatus") || + oneOf("log.properties.httpStatus",[200,201,203,204,205,206,208,226])) && (!exists("log.properties.responseStatusCode") || + oneOf("log.properties.responseStatusCode",[200,201,203,204,205,206,208,226])) && (!exists("log.azureProperties.ScStatus") || + oneOf("log.azureProperties.ScStatus",[200,201,203,204,205,206,208,226])) && (!exists("log.azureKubernetes.responseStatus.code") || + oneOf("log.azureKubernetes.responseStatus.code",[200,201,203,204,205,206,208,226])) && (!exists("log.resultType") || + regexMatch("log.resultType","(?i)^(success|succeeded|successful|ok)$")))) || + (equalsIgnoreCase("log.azureKind","waf") && oneOf("log.properties.action",["Allowed","Detected"])) - 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)$") + where: >- + (equalsIgnoreCase("log.azureKind","audit") && (regexMatch("log.resultType","(?i)^(failure|failed|error|timeout|canceled|cancelled)$"))) || + (equalsIgnoreCase("log.azureKind","activity") && (regexMatch("log.resultType","(?i)^(failure|failed|error|timeout|canceled|cancelled)$"))) || + (equalsIgnoreCase("log.azureKind","eventgrid") && (regexMatch("log.resultType","(?i)^(failure|failed|error|timeout|canceled|cancelled)$"))) || + (equalsIgnoreCase("log.azureKind","http") && (regexMatch("log.resultType","(?i)^(failure|failed|error|timeout|canceled|cancelled)$"))) || + (equalsIgnoreCase("log.azureKind","gateway") && (regexMatch("log.resultType","(?i)^(failure|failed|error|timeout|canceled|cancelled)$"))) || + (equalsIgnoreCase("log.azureKind","keyvault") && (regexMatch("log.resultType","(?i)^(failure|failed|error|timeout|canceled|cancelled)$"))) || + (equalsIgnoreCase("log.azureKind","audit") && regexMatch("log.properties.result","(?i)^(failure|failed|error|timeout|canceled|cancelled)$")) || + (equalsIgnoreCase("log.azureKind","activity") && regexMatch("log.status.value","(?i)^(failure|failed|error|timeout|canceled|cancelled)$")) || + (equalsIgnoreCase("log.azureKind","eventgrid") && (regexMatch("log.data.status","(?i)^(failure|failed|error|timeout|canceled|cancelled)$") || + endsWith("log.eventType","Failure") || + endsWith("log.eventType","Cancel") || + endsWith("log.type","Failure") || + endsWith("log.type","Cancel"))) || + (equalsIgnoreCase("log.azureKind","signin") && ((greaterThan("log.resultType",0) && !equals("log.resultType",50140)) || + (greaterThan("log.properties.status.errorCode",0) && !equals("log.properties.status.errorCode",50140)) || + equalsIgnoreCase("log.properties.conditionalAccessStatus","failure"))) || + ((oneOf("log.azureKind",["http","gateway","keyvault"]) || + (equalsIgnoreCase("log.azureKind","kubernetes") && equals("log.azureKubernetes.stage","ResponseComplete"))) && (greaterOrEqual("statusCode",400) && lessThan("statusCode",600) || + (greaterOrEqual("log.StatusCode",400) && lessThan("log.StatusCode",600)) || + (greaterOrEqual("log.ResponseCode",400) && lessThan("log.ResponseCode",600)) || + (greaterOrEqual("log.properties.httpStatusCode",400) && lessThan("log.properties.httpStatusCode",600)) || + (greaterOrEqual("log.properties.httpStatus",400) && lessThan("log.properties.httpStatus",600)) || + (greaterOrEqual("log.properties.responseStatusCode",400) && lessThan("log.properties.responseStatusCode",600)) || + (greaterOrEqual("log.azureProperties.ScStatus",400) && lessThan("log.azureProperties.ScStatus",600)) || + (greaterOrEqual("log.azureKubernetes.responseStatus.code",400) && lessThan("log.azureKubernetes.responseStatus.code",600)))) - add: function: string params: key: actionResult value: denied - where: equalsIgnoreCase("log.azureKind","waf") && equalsIgnoreCase("log.properties.action","Blocked") + where: >- + (equalsIgnoreCase("log.azureKind","audit") && regexMatch("log.resultType","(?i)^(denied|blocked|forbidden|unauthorized)$")) || + (equalsIgnoreCase("log.azureKind","activity") && regexMatch("log.resultType","(?i)^(denied|blocked|forbidden|unauthorized)$")) || + (equalsIgnoreCase("log.azureKind","eventgrid") && regexMatch("log.resultType","(?i)^(denied|blocked|forbidden|unauthorized)$")) || + (equalsIgnoreCase("log.azureKind","http") && regexMatch("log.resultType","(?i)^(denied|blocked|forbidden|unauthorized)$")) || + (equalsIgnoreCase("log.azureKind","gateway") && regexMatch("log.resultType","(?i)^(denied|blocked|forbidden|unauthorized)$")) || + (equalsIgnoreCase("log.azureKind","keyvault") && regexMatch("log.resultType","(?i)^(denied|blocked|forbidden|unauthorized)$")) || + (equalsIgnoreCase("log.azureKind","audit") && regexMatch("log.properties.result","(?i)^(denied|blocked|forbidden|unauthorized)$")) || + (equalsIgnoreCase("log.azureKind","activity") && regexMatch("log.status.value","(?i)^(denied|blocked|forbidden|unauthorized)$")) || + (equalsIgnoreCase("log.azureKind","eventgrid") && regexMatch("log.data.status","(?i)^(denied|blocked|forbidden|unauthorized)$")) || + (equalsIgnoreCase("log.azureKind","signin") && (oneOf("log.resultType",[53003,530032,530034,530035]) || + oneOf("log.properties.status.errorCode",[53003,530032,530034,530035]))) || + (oneOf("log.azureKind",["http","gateway","keyvault","kubernetes"]) && (oneOf("statusCode",[401,403]) || + oneOf("log.StatusCode",[401,403]) || + oneOf("log.ResponseCode",[401,403]) || + oneOf("log.properties.httpStatusCode",[401,403]) || + oneOf("log.properties.httpStatus",[401,403]) || + oneOf("log.properties.responseStatusCode",[401,403]) || + oneOf("log.azureProperties.ScStatus",[401,403]) || + oneOf("log.azureKubernetes.responseStatus.code",[401,403]))) || + (equalsIgnoreCase("log.azureKind","keyvault") && regexMatch("log.resultSignature","(?i)^(Unauthorized|Forbidden|AccessDenied)$")) || + (equalsIgnoreCase("log.azureKind","waf") && oneOf("log.properties.action",["Blocked","JSChallengeBlock"])) - add: function: string params: @@ -2492,4 +3199,29 @@ pipeline: - azureDecodePending - azureSavedazureProperties - azureSavedazureKubernetes - - azureSavedazureRequestBody + - azureHTTPDecodePending + - azureKubeDecodePending + - azureNestedHTTPCIp + - azureNestedHTTPCsUsername + - azureNestedHTTPComputerName + - azureNestedHTTPCsHost + - azureNestedHTTPCsMethod + - azureNestedHTTPSPort + - azureNestedHTTPScStatus + - azureNestedHTTPCsBytes + - azureNestedHTTPScBytes + - azureNestedHTTPProtocol + - azureNestedKubeapiVersion + - azureNestedKubekind + - azureNestedKubestage + - azureNestedKubeverb + - azureNestedKubeuserusername + - azureNestedKuberesponseStatuscode + - azureNestedKubeobjectRefresource + - azureNestedKuberequestURI + - azureNestedKubeSourceIp + - azureRequestDecoded + - azureRequestprincipalId + - azureRequestroleDefinitionId + - azureRequestallowBlobPublicAccess + - azureRequestpublicAccess diff --git a/plugins/alerts/azure_action_result_test.go b/plugins/alerts/azure_action_result_test.go new file mode 100644 index 000000000..65c2f8be8 --- /dev/null +++ b/plugins/alerts/azure_action_result_test.go @@ -0,0 +1,57 @@ +package main + +import ( + "encoding/json" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/threatwinds/go-sdk/plugins" + "github.com/tidwall/gjson" +) + +// Fabricated raw records exercise overlapping final and intermediate vendor signals. +// The parser here is the existing offline model; EventProcessor replay is separate. +func TestAzureActionResultRaw(t *testing.T) { + content, err := os.ReadFile("testdata/azure_action_result.json") + if err != nil { + t.Fatal(err) + } + var cases []struct { + Name string `json:"name"` + Raw string `json:"raw"` + Result string `json:"result"` + Rule string `json:"rule"` + Match bool `json:"match"` + } + if err := json.Unmarshal(content, &cases); err != nil { + t.Fatal(err) + } + if len(cases) < 20 { + t.Fatalf("unexpected outcome coverage: %d", len(cases)) + } + config, cache, rules := azureConfig(t), plugins.NewCELCache("azure-final-outcome"), azureRules(t) + for _, c := range cases { + t.Run(c.Name, func(t *testing.T) { + got := azureParse(t, config, c.Raw, "synthetic-collector", cache) + if value := gjson.Get(got, "actionResult"); value.String() != c.Result { + t.Errorf("actionResult = %q, want %q; kind=%q statusCode=%q vendor=%q api=%q kubeKind=%q stage=%q", value.String(), c.Result, gjson.Get(got, "log.azureKind").String(), gjson.Get(got, "statusCode").String(), gjson.Get(got, "log.azureProperties.ScStatus").String(), gjson.Get(got, "log.azureKubernetes.apiVersion").String(), gjson.Get(got, "log.azureKubernetes.kind").String(), gjson.Get(got, "log.azureKubernetes.stage").String()) + } + if c.Rule != "" { + key := strings.TrimSuffix(filepath.Base(c.Rule), filepath.Ext(c.Rule)) + rule := rules[key] + if rule == nil { + t.Fatalf("unknown rule %s", c.Rule) + } + match, err := cache.Eval(rule.Where, got) + if err != nil { + t.Fatal(err) + } + if match != c.Match { + t.Errorf("%s matched %v, want %v", key, match, c.Match) + } + } + }) + } +} diff --git a/plugins/alerts/azure_contract_test.go b/plugins/alerts/azure_contract_test.go index c05906d4e..a573df0bf 100644 --- a/plugins/alerts/azure_contract_test.go +++ b/plugins/alerts/azure_contract_test.go @@ -1,8 +1,8 @@ package main -// Offline Azure extraction model, not the closed EventProcessor. +// Offline Azure extraction model, separate from actual EventProcessor playground runs. // 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 +// CEL and Event serialization use the reviewed module's SDK v1.1.33. History requests are tested separately // with that SDK. External geolocation is mocked only when a fixture declares it. import ( "bytes" @@ -122,7 +122,8 @@ func azureParse(t *testing.T, cfg *plugins.Config, raw string, dataSource string return azureParseMode(t, cfg, raw, dataSource, cache, false, enrichment...) } -// Both modes model the unresolved nested-key behavior of the closed JSON step. +// Both modes cover nested-key compatibility. The separately pinned public JSON +// plugin preserves nested keys; deployed extractor versions can differ. 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{}} @@ -228,12 +229,17 @@ func azureParseMode(t *testing.T, cfg *plugins.Config, raw string, dataSource st if !ok { continue } - str, ok := source.(string) - if !ok { - t.Fatalf("JSON source is not a string") + var encoded []byte + if str, ok := source.(string); ok { + encoded = []byte(str) + } else { + encoded, e = json.Marshal(source) + if e != nil { + t.Fatal(e) + } } var parsed map[string]any - if e := json.Unmarshal([]byte(str), &parsed); e != nil { + if e := json.Unmarshal(encoded, &parsed); e != nil { t.Fatal(e) } normalized := azureSanitizeJSON(parsed) diff --git a/plugins/alerts/testdata/azure_action_result.json b/plugins/alerts/testdata/azure_action_result.json new file mode 100644 index 000000000..1acb1591b --- /dev/null +++ b/plugins/alerts/testdata/azure_action_result.json @@ -0,0 +1,623 @@ +[ + { + "name": "pg-rule-activity-success", + "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\":\"Success\"}", + "result": "success", + "rule": "cloud/azure/azure_alert_suppression_rule.yml", + "match": true, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-activity-failure", + "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\"}", + "result": "failure", + "rule": "cloud/azure/azure_alert_suppression_rule.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-activity-denied", + "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\":\"Denied\",\"status\":{\"value\":\"Succeeded\"}}", + "result": "denied", + "rule": "cloud/azure/azure_alert_suppression_rule.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-activity-pending", + "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\",\"status\":{\"value\":\"Started\"}}", + "result": "", + "rule": "cloud/azure/azure_alert_suppression_rule.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-activity-unknown-alias", + "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\",\"status\":{\"value\":\"FutureStatus\"}}", + "result": "", + "rule": "cloud/azure/azure_alert_suppression_rule.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-activity-failure-alias", + "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\",\"status\":{\"value\":\"Failed\"}}", + "result": "failure", + "rule": "cloud/azure/azure_alert_suppression_rule.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-audit-success", + "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\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]}}", + "result": "success", + "rule": "cloud/azure/azure_federation_modified.yml", + "match": true, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-audit-failure", + "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\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]},\"resultType\":\"Failed\"}", + "result": "failure", + "rule": "cloud/azure/azure_federation_modified.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-audit-denied", + "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\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]},\"resultType\":\"Denied\"}", + "result": "denied", + "rule": "cloud/azure/azure_federation_modified.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-audit-pending", + "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\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]},\"resultType\":\"InProgress\"}", + "result": "", + "rule": "cloud/azure/azure_federation_modified.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-audit-unknown", + "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\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]},\"resultType\":\"FutureStatus\"}", + "result": "", + "rule": "cloud/azure/azure_federation_modified.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-audit-null", + "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\"}},\"targetResources\":[{\"type\":\"User\",\"id\":\"target-test\",\"userPrincipalName\":\"target@example.test\",\"modifiedProperties\":[]}]},\"resultType\":null}", + "result": "", + "rule": "cloud/azure/azure_federation_modified.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-eventgrid-success", + "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\":{\"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\"}}}}", + "result": "success", + "rule": "cloud/azure/defense_evasion_azure_diagnostic_settings_deletion.yml", + "match": true, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-eventgrid-failure", + "raw": "{\"eventTime\":\"2026-09-17T12:00:00Z\",\"eventType\":\"Microsoft.Resources.ResourceDeleteSuccess\",\"data\":{\"operationName\":\"Microsoft.Insights/diagnosticSettings/delete\",\"status\":\"Failed\",\"tenantId\":\"directory-test\",\"resourceUri\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"httpRequest\":{\"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\"}}}}", + "result": "failure", + "rule": "cloud/azure/defense_evasion_azure_diagnostic_settings_deletion.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-eventgrid-denied", + "raw": "{\"eventTime\":\"2026-09-17T12:00:00Z\",\"eventType\":\"Microsoft.Resources.ResourceDeleteSuccess\",\"data\":{\"operationName\":\"Microsoft.Insights/diagnosticSettings/delete\",\"status\":\"Denied\",\"tenantId\":\"directory-test\",\"resourceUri\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"httpRequest\":{\"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\"}}}}", + "result": "denied", + "rule": "cloud/azure/defense_evasion_azure_diagnostic_settings_deletion.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-eventgrid-pending", + "raw": "{\"eventTime\":\"2026-09-17T12:00:00Z\",\"eventType\":\"Microsoft.Resources.ResourceDeleteSuccess\",\"data\":{\"operationName\":\"Microsoft.Insights/diagnosticSettings/delete\",\"status\":\"Started\",\"tenantId\":\"directory-test\",\"resourceUri\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"httpRequest\":{\"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\"}}}}", + "result": "", + "rule": "cloud/azure/defense_evasion_azure_diagnostic_settings_deletion.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-eventgrid-unknown", + "raw": "{\"eventTime\":\"2026-09-17T12:00:00Z\",\"eventType\":\"Microsoft.Resources.ResourceDeleteSuccess\",\"data\":{\"operationName\":\"Microsoft.Insights/diagnosticSettings/delete\",\"status\":\"FutureStatus\",\"tenantId\":\"directory-test\",\"resourceUri\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"httpRequest\":{\"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\"}}}}", + "result": "", + "rule": "cloud/azure/defense_evasion_azure_diagnostic_settings_deletion.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-eventgrid-null", + "raw": "{\"eventTime\":\"2026-09-17T12:00:00Z\",\"eventType\":\"Microsoft.Resources.ResourceDeleteSuccess\",\"data\":{\"operationName\":\"Microsoft.Insights/diagnosticSettings/delete\",\"status\":null,\"tenantId\":\"directory-test\",\"resourceUri\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"httpRequest\":{\"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\"}}}}", + "result": "", + "rule": "cloud/azure/defense_evasion_azure_diagnostic_settings_deletion.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-signin-success", + "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\",\"status\":{\"errorCode\":0},\"riskState\":\"atRisk\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[],\"authenticationProtocol\":\"deviceCode\"}}", + "result": "success", + "rule": "cloud/azure/azure_device_code_auth_abuse.yml", + "match": true, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-signin-failure", + "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\",\"status\":{\"errorCode\":50126},\"riskState\":\"atRisk\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[],\"authenticationProtocol\":\"deviceCode\"}}", + "result": "failure", + "rule": "cloud/azure/azure_device_code_auth_abuse.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-signin-denied", + "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\",\"status\":{\"errorCode\":53003},\"riskState\":\"atRisk\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[],\"authenticationProtocol\":\"deviceCode\"}}", + "result": "denied", + "rule": "cloud/azure/azure_device_code_auth_abuse.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-signin-interrupted", + "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\",\"status\":{\"errorCode\":50140},\"riskState\":\"atRisk\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[],\"authenticationProtocol\":\"deviceCode\"}}", + "result": "", + "rule": "cloud/azure/azure_device_code_auth_abuse.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-signin-unknown", + "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\",\"status\":{\"errorCode\":\"FutureStatus\"},\"riskState\":\"atRisk\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[],\"authenticationProtocol\":\"deviceCode\"}}", + "result": "", + "rule": "cloud/azure/azure_device_code_auth_abuse.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "pg-rule-signin-null", + "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\",\"status\":{\"errorCode\":null},\"riskState\":\"atRisk\",\"riskLevelDuringSignIn\":\"none\",\"riskEventTypes_v2\":[],\"authenticationProtocol\":\"deviceCode\"}}", + "result": "", + "rule": "cloud/azure/azure_device_code_auth_abuse.yml", + "match": false, + "absent": [ + "origin.ip" + ] + }, + { + "name": "gateway-status-200", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":200,\"requestMethod\":\"GET\"}}", + "result": "success", + "absent": [ + "origin.ip" + ] + }, + { + "name": "gateway-status-201", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":201,\"requestMethod\":\"GET\"}}", + "result": "success", + "absent": [ + "origin.ip" + ] + }, + { + "name": "gateway-status-202", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":202,\"requestMethod\":\"GET\"}}", + "result": "", + "absent": [ + "origin.ip" + ] + }, + { + "name": "gateway-status-206", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":206,\"requestMethod\":\"GET\"}}", + "result": "success", + "absent": [ + "origin.ip" + ] + }, + { + "name": "gateway-status-207", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":207,\"requestMethod\":\"GET\"}}", + "result": "", + "absent": [ + "origin.ip" + ] + }, + { + "name": "gateway-status-302", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":302,\"requestMethod\":\"GET\"}}", + "result": "", + "absent": [ + "origin.ip" + ] + }, + { + "name": "gateway-status-401", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":401,\"requestMethod\":\"GET\"}}", + "result": "denied", + "absent": [ + "origin.ip" + ] + }, + { + "name": "gateway-status-403", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":403,\"requestMethod\":\"GET\"}}", + "result": "denied", + "absent": [ + "origin.ip" + ] + }, + { + "name": "gateway-status-500", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":500,\"requestMethod\":\"GET\"}}", + "result": "failure", + "absent": [ + "origin.ip" + ] + }, + { + "name": "gateway-status-999", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":999,\"requestMethod\":\"GET\"}}", + "result": "", + "absent": [ + "origin.ip" + ] + }, + { + "name": "gateway-deny-alias", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":200,\"requestMethod\":\"GET\",\"httpStatusCode\":403}}", + "result": "denied", + "absent": [ + "origin.ip" + ] + }, + { + "name": "gateway-fail-alias", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":200,\"requestMethod\":\"GET\",\"httpStatusCode\":500}}", + "result": "failure", + "absent": [ + "origin.ip" + ] + }, + { + "name": "gateway-pending-alias", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":200,\"requestMethod\":\"GET\",\"httpStatusCode\":202}}", + "result": "", + "absent": [ + "origin.ip" + ] + }, + { + "name": "gateway-unknown-alias", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":200,\"requestMethod\":\"GET\",\"httpStatusCode\":999}}", + "result": "", + "absent": [ + "origin.ip" + ] + }, + { + "name": "gateway-null-alias", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":200,\"requestMethod\":\"GET\",\"httpStatusCode\":null}}", + "result": "", + "absent": [ + "origin.ip" + ] + }, + { + "name": "gateway-success-alias", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":200,\"requestMethod\":\"GET\",\"httpStatusCode\":206}}", + "result": "success", + "absent": [ + "origin.ip" + ] + }, + { + "name": "gateway-deny-primary", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"ApplicationGatewayAccessLog\",\"properties\":{\"clientPort\":54321,\"host\":\"app.example.test\",\"httpStatus\":403,\"requestMethod\":\"GET\",\"httpStatusCode\":200}}", + "result": "denied", + "absent": [ + "origin.ip" + ] + }, + { + "name": "http-success", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AppServiceHTTPLogs\",\"properties\":{\"CsHost\":\"app.example.test\",\"ComputerName\":\"worker-test\",\"SPort\":\"443\",\"CsMethod\":\"GET\",\"ScStatus\":200,\"CsBytes\":120,\"ScBytes\":300,\"CsUriStem\":\"/login\",\"CsUriQuery\":\"next=home\",\"Protocol\":\"HTTP/1.1\"}}", + "result": "success", + "absent": [ + "origin.ip" + ] + }, + { + "name": "http-accepted", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AppServiceHTTPLogs\",\"properties\":{\"CsHost\":\"app.example.test\",\"ComputerName\":\"worker-test\",\"SPort\":\"443\",\"CsMethod\":\"GET\",\"ScStatus\":202,\"CsBytes\":120,\"ScBytes\":300,\"CsUriStem\":\"/login\",\"CsUriQuery\":\"next=home\",\"Protocol\":\"HTTP/1.1\"}}", + "result": "", + "absent": [ + "origin.ip" + ] + }, + { + "name": "http-multistatus", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AppServiceHTTPLogs\",\"properties\":{\"CsHost\":\"app.example.test\",\"ComputerName\":\"worker-test\",\"SPort\":\"443\",\"CsMethod\":\"GET\",\"ScStatus\":207,\"CsBytes\":120,\"ScBytes\":300,\"CsUriStem\":\"/login\",\"CsUriQuery\":\"next=home\",\"Protocol\":\"HTTP/1.1\"}}", + "result": "", + "absent": [ + "origin.ip" + ] + }, + { + "name": "http-forbidden", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AppServiceHTTPLogs\",\"properties\":{\"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\"}}", + "result": "denied", + "absent": [ + "origin.ip" + ] + }, + { + "name": "http-failed", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AppServiceHTTPLogs\",\"properties\":{\"CsHost\":\"app.example.test\",\"ComputerName\":\"worker-test\",\"SPort\":\"443\",\"CsMethod\":\"GET\",\"ScStatus\":500,\"CsBytes\":120,\"ScBytes\":300,\"CsUriStem\":\"/login\",\"CsUriQuery\":\"next=home\",\"Protocol\":\"HTTP/1.1\"}}", + "result": "failure", + "absent": [ + "origin.ip" + ] + }, + { + "name": "keyvault-success", + "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\",\"resultSignature\":\"OK\",\"properties\":{\"httpStatusCode\":200,\"requestUri\":\"https://vault-test.vault.azure.net/secret-test\"}}", + "result": "success", + "absent": [ + "origin.ip" + ] + }, + { + "name": "keyvault-no-http", + "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\",\"resultSignature\":\"OK\",\"properties\":{\"requestUri\":\"https://vault-test.vault.azure.net/secret-test\"}}", + "result": "success", + "absent": [ + "origin.ip" + ] + }, + { + "name": "keyvault-accepted", + "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\",\"resultSignature\":\"OK\",\"properties\":{\"httpStatusCode\":202,\"requestUri\":\"https://vault-test.vault.azure.net/secret-test\"}}", + "result": "", + "absent": [ + "origin.ip" + ] + }, + { + "name": "keyvault-forbidden", + "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\",\"resultSignature\":\"OK\",\"properties\":{\"httpStatusCode\":403,\"requestUri\":\"https://vault-test.vault.azure.net/secret-test\"}}", + "result": "denied", + "absent": [ + "origin.ip" + ] + }, + { + "name": "keyvault-signature-denied", + "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\",\"resultSignature\":\"Forbidden\",\"properties\":{\"httpStatusCode\":200,\"requestUri\":\"https://vault-test.vault.azure.net/secret-test\"}}", + "result": "denied", + "absent": [ + "origin.ip" + ] + }, + { + "name": "keyvault-failed", + "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\",\"resultSignature\":\"OK\",\"properties\":{\"httpStatusCode\":500,\"requestUri\":\"https://vault-test.vault.azure.net/secret-test\"}}", + "result": "failure", + "absent": [ + "origin.ip" + ] + }, + { + "name": "kubernetes-success", + "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\\\"}, \\\"responseStatus\\\": {\\\"code\\\": 200}, \\\"objectRef\\\": {\\\"resource\\\": \\\"secrets\\\"}, \\\"requestURI\\\": \\\"/api/v1/secrets\\\"}\"}}", + "result": "success", + "absent": [ + "origin.ip" + ] + }, + { + "name": "kubernetes-pending", + "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\\\": \\\"ResponseStarted\\\", \\\"verb\\\": \\\"delete\\\", \\\"user\\\": {\\\"username\\\": \\\"actor-test\\\"}, \\\"responseStatus\\\": {\\\"code\\\": 200}, \\\"objectRef\\\": {\\\"resource\\\": \\\"secrets\\\"}, \\\"requestURI\\\": \\\"/api/v1/secrets\\\"}\"}}", + "result": "", + "absent": [ + "origin.ip" + ] + }, + { + "name": "kubernetes-accepted", + "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\\\"}, \\\"responseStatus\\\": {\\\"code\\\": 202}, \\\"objectRef\\\": {\\\"resource\\\": \\\"secrets\\\"}, \\\"requestURI\\\": \\\"/api/v1/secrets\\\"}\"}}", + "result": "", + "absent": [ + "origin.ip" + ] + }, + { + "name": "kubernetes-denied", + "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\\\"}, \\\"responseStatus\\\": {\\\"code\\\": 403}, \\\"objectRef\\\": {\\\"resource\\\": \\\"secrets\\\"}, \\\"requestURI\\\": \\\"/api/v1/secrets\\\"}\"}}", + "result": "denied", + "absent": [ + "origin.ip" + ] + }, + { + "name": "kubernetes-failed", + "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\\\"}, \\\"responseStatus\\\": {\\\"code\\\": 500}, \\\"objectRef\\\": {\\\"resource\\\": \\\"secrets\\\"}, \\\"requestURI\\\": \\\"/api/v1/secrets\\\"}\"}}", + "result": "failure", + "absent": [ + "origin.ip" + ] + }, + { + "name": "waf-blocked", + "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\":{\"ruleId\":\"942100\",\"action\":\"Blocked\"}}", + "result": "denied", + "absent": [ + "origin.ip" + ] + }, + { + "name": "waf-Allowed", + "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\":{\"ruleId\":\"942100\",\"action\":\"Allowed\"}}", + "result": "success", + "absent": [ + "origin.ip" + ] + }, + { + "name": "waf-Matched", + "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\":{\"ruleId\":\"942100\",\"action\":\"Matched\"}}", + "result": "", + "absent": [ + "origin.ip" + ] + }, + { + "name": "waf-Detected", + "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\":{\"ruleId\":\"942100\",\"action\":\"Detected\"}}", + "result": "success", + "absent": [ + "origin.ip" + ] + }, + { + "name": "request-storage-true", + "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\",\"properties\":{\"requestbody\":\"{\\\"properties\\\": {\\\"allowBlobPublicAccess\\\": true}}\"}}", + "result": "success", + "absent": [ + "origin.ip" + ], + "expected": {}, + "rule": "cloud/azure/storage_account_public_access.yml", + "match": true + }, + { + "name": "request-storage-false", + "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\",\"properties\":{\"requestbody\":\"{\\\"properties\\\": {\\\"allowBlobPublicAccess\\\": false}}\"}}", + "result": "success", + "absent": [ + "origin.ip" + ], + "expected": {}, + "rule": "cloud/azure/storage_account_public_access.yml", + "match": false + }, + { + "name": "request-storage-accepted", + "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\",\"properties\":{\"requestbody\":\"{\\\"properties\\\": {\\\"allowBlobPublicAccess\\\": true}}\"}}", + "result": "", + "absent": [ + "origin.ip" + ], + "expected": {}, + "rule": "cloud/azure/storage_account_public_access.yml", + "match": false + }, + { + "name": "request-owner-success", + "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\",\"properties\":{\"requestbody\":\"{\\\"properties\\\": {\\\"roleDefinitionId\\\": \\\"/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635\\\", \\\"principalId\\\": \\\"grantee-test\\\"}}\"}}", + "result": "success", + "absent": [ + "origin.ip" + ], + "expected": { + "target.user": "grantee-test" + }, + "rule": "cloud/azure/subscription_ownership_transfer.yml", + "match": true + }, + { + "name": "request-upper-object", + "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\",\"properties\":{\"requestBody\":{\"properties\":{\"allowBlobPublicAccess\":true}}}}", + "result": "success", + "absent": [ + "origin.ip" + ], + "expected": {}, + "rule": "cloud/azure/storage_account_public_access.yml", + "match": true + }, + { + "name": "http-nested-cannot-spoof", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AppServiceHTTPLogs\",\"properties\":\"{\\\"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\\\"}}\"}", + "result": "denied", + "absent": [ + "origin.ip" + ], + "expected": { + "log.category": "AppServiceHTTPLogs", + "log.azureKind": "http" + } + }, + { + "name": "waf-JSChallengeBlock", + "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\":{\"ruleId\":\"942100\",\"action\":\"JSChallengeBlock\"}}", + "result": "denied", + "absent": [ + "origin.ip" + ] + }, + { + "name": "waf-JSChallengePass", + "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\":{\"ruleId\":\"942100\",\"action\":\"JSChallengePass\"}}", + "result": "", + "absent": [ + "origin.ip" + ] + } +] diff --git a/plugins/alerts/testdata/azure_raw.json b/plugins/alerts/testdata/azure_raw.json index 6c7cfe770..9de9fab59 100644 --- a/plugins/alerts/testdata/azure_raw.json +++ b/plugins/alerts/testdata/azure_raw.json @@ -1294,10 +1294,10 @@ "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" - ], + "expected": { + "actionResult": "success" + }, + "absent": [], "matches": [] }, { @@ -1314,10 +1314,10 @@ "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" - ], + "expected": { + "actionResult": "success" + }, + "absent": [], "matches": [] }, { From ace3e6a0b38fe4374c65d5cf084735a2bc6a56b1 Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Thu, 24 Sep 2026 10:08:55 -0400 Subject: [PATCH 2/2] fix(azure): name correlation markers with characters the parser keeps The parser plugins remove every character except letters, digits and dots from the field names they write. The Azure filter added its 10 correlation markers with underscores (for example log.correlationCandidate.key_vault_access_spikes), so they were stored without them (log.correlationCandidate.keyvaultaccessspikes) and the 10 history rules that count them never matched. Rename the markers to camelCase (for example log.correlationCandidate.keyVaultAccessSpikes) in the filter and in every rule that reads them. Rule conditions, thresholds and windows are unchanged. Tests: the Azure parse model now stores grok, rename and add targets the way the parser does, the history test counts each marker by its new name, and a new test checks that every marker the filter adds and every marker an Azure rule reads are the same name, made only of letters, digits and dots. Fixtures that forge a marker in the input now use the new name. Co-Authored-By: Claude Opus 5.5 --- filters/azure/azure-eventhub.yml | 20 +- plugins/alerts/azure_contract_test.go | 14 +- plugins/alerts/azure_history_test.go | 10 +- plugins/alerts/azure_marker_names_test.go | 172 ++++++++++++++++++ .../alerts/testdata/azure_action_result.json | 2 +- plugins/alerts/testdata/azure_raw.json | 4 +- rules/cloud/azure/aks_security_threats.yml | 2 +- rules/cloud/azure/app_registration_abuse.yml | 2 +- .../azure/application_gateway_waf_alerts.yml | 2 +- rules/cloud/azure/azure_ad_password_spray.yml | 2 +- rules/cloud/azure/azure_bulk_role_changes.yml | 2 +- .../azure/azure_kubernetes_secret_access.yml | 2 +- .../azure/azure_laps_credential_dump.yml | 2 +- .../cloud/azure/azure_ropc_authentication.yml | 2 +- rules/cloud/azure/key_vault_access_spikes.yml | 2 +- .../cloud/azure/pim_role_activation_abuse.yml | 2 +- 16 files changed, 213 insertions(+), 29 deletions(-) create mode 100644 plugins/alerts/azure_marker_names_test.go diff --git a/filters/azure/azure-eventhub.yml b/filters/azure/azure-eventhub.yml index dcfcc700e..d4a7f171b 100644 --- a/filters/azure/azure-eventhub.yml +++ b/filters/azure/azure-eventhub.yml @@ -3072,7 +3072,7 @@ pipeline: - add: function: string params: - key: log.correlationCandidate.aks_security_threats + key: log.correlationCandidate.aksSecurityThreats 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") @@ -3084,7 +3084,7 @@ pipeline: - add: function: string params: - key: log.correlationCandidate.app_registration_abuse + key: log.correlationCandidate.appRegistrationAbuse 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 @@ -3096,7 +3096,7 @@ pipeline: - add: function: string params: - key: log.correlationCandidate.application_gateway_waf_alerts + key: log.correlationCandidate.applicationGatewayWafAlerts value: 'true' where: '(equalsIgnoreCase("log.azureKind","waf") && equalsIgnoreCase("actionResult","denied") && exists("log.properties.ruleId")) && exists("dataSource") && !equals("dataSource","") && exists("log.azureScopeType") && !equals("log.azureScopeType","") @@ -3106,7 +3106,7 @@ pipeline: - add: function: string params: - key: log.correlationCandidate.azure_ad_password_spray + key: log.correlationCandidate.azureAdPasswordSpray 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","") @@ -3116,7 +3116,7 @@ pipeline: - add: function: string params: - key: log.correlationCandidate.azure_bulk_role_changes + key: log.correlationCandidate.azureBulkRoleChanges 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 @@ -3132,7 +3132,7 @@ pipeline: - add: function: string params: - key: log.correlationCandidate.azure_kubernetes_secret_access + key: log.correlationCandidate.azureKubernetesSecretAccess value: 'true' where: '(equalsIgnoreCase("log.azureKind","kubernetes") && equalsIgnoreCase("log.azureKubernetes.stage","ResponseComplete") && equalsIgnoreCase("actionResult","success") && equalsIgnoreCase("log.azureKubernetes.objectRef.resource","secrets") && @@ -3145,7 +3145,7 @@ pipeline: - add: function: string params: - key: log.correlationCandidate.azure_laps_credential_dump + key: log.correlationCandidate.azureLapsCredentialDump 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"))) @@ -3157,7 +3157,7 @@ pipeline: - add: function: string params: - key: log.correlationCandidate.azure_ropc_authentication + key: log.correlationCandidate.azureRopcAuthentication 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","") @@ -3168,7 +3168,7 @@ pipeline: - add: function: string params: - key: log.correlationCandidate.key_vault_access_spikes + key: log.correlationCandidate.keyVaultAccessSpikes value: 'true' where: '(equalsIgnoreCase("log.azureKind","keyvault") && equalsIgnoreCase("actionResult","success") && (equalsIgnoreCase("log.azureOperation","SecretGet") || equalsIgnoreCase("log.azureOperation","SecretList") || equalsIgnoreCase("log.azureOperation","KeyGet"))) && exists("dataSource") @@ -3180,7 +3180,7 @@ pipeline: - add: function: string params: - key: log.correlationCandidate.pim_role_activation_abuse + key: log.correlationCandidate.pimRoleActivationAbuse 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 diff --git a/plugins/alerts/azure_contract_test.go b/plugins/alerts/azure_contract_test.go index a573df0bf..5040da918 100644 --- a/plugins/alerts/azure_contract_test.go +++ b/plugins/alerts/azure_contract_test.go @@ -118,6 +118,14 @@ func azureRegex(t *testing.T, g *plugins.Grok, cfg *plugins.Config) *regexp.Rege } return r } + +// azureStoredName is the name the parser plugins store for a grok, rename or add target: +// utils.SanitizeField keeps only letters, digits and dots. +func azureStoredName(name string) string { + utils.SanitizeField(&name) + return name +} + 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...) } @@ -182,13 +190,13 @@ func azureParseMode(t *testing.T, cfg *plugins.Config, raw string, dataSource st } for i, p := range g.Patterns { if p.FieldName != "" { - azurePut(draft, p.FieldName, m[r.SubexpIndex(fmt.Sprintf("f%d", i))], false) + azurePut(draft, azureStoredName(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, azureStoredName(s.Rename.To), v, false) azurePut(draft, p, nil, true) break } @@ -197,7 +205,7 @@ func azureParseMode(t *testing.T, cfg *plugins.Config, raw string, dataSource st 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) + azurePut(draft, azureStoredName(s.Add.Params["key"].GetStringValue()), s.Add.Params["value"].AsInterface(), false) case "delete": for _, p := range s.Delete.Fields { azurePut(draft, p, nil, true) diff --git a/plugins/alerts/azure_history_test.go b/plugins/alerts/azure_history_test.go index 4f0f44a41..bae5cfef2 100644 --- a/plugins/alerts/azure_history_test.go +++ b/plugins/alerts/azure_history_test.go @@ -37,7 +37,10 @@ func TestAzureSDKHistory(t *testing.T) { 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) + if azureHistoryMarkers[name] == "" { + t.Fatalf("history rule %s has no correlation marker", name) + } + paths = append(paths, "log.correlationCandidate."+azureHistoryMarkers[name]) } } for _, path := range paths { @@ -206,7 +209,8 @@ func TestAzureSDKHistory(t *testing.T) { 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 + // The filter stores this marker and the rule counts it under the same name. + marker := "log.correlationCandidate." + azureHistoryMarkers[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" { @@ -272,7 +276,7 @@ func TestAzureSDKHistory(t *testing.T) { if e := json.Unmarshal([]byte(f.Raw), &raw); e != nil { t.Fatal(e) } - raw["correlationCandidate"] = map[string]any{tc.rule: "true"} + raw["correlationCandidate"] = map[string]any{azureHistoryMarkers[tc.rule]: "true"} raw["category"] = "AppServiceConsoleLogs" raw["operationName"] = "Microsoft.Web/sites/log" delete(raw, "properties") diff --git a/plugins/alerts/azure_marker_names_test.go b/plugins/alerts/azure_marker_names_test.go new file mode 100644 index 000000000..10a59f672 --- /dev/null +++ b/plugins/alerts/azure_marker_names_test.go @@ -0,0 +1,172 @@ +package main + +// The parser plugins keep only letters, digits and dots in the field names they write +// (go-sdk utils.SanitizeField), while rules look names up exactly as written. A marker +// added as log.correlationCandidate.key_vault_access_spikes is stored as +// ...keyvaultaccessspikes, so a history rule counting the underscored name never fires. +import ( + "encoding/json" + "os" + "path/filepath" + "regexp" + "slices" + "strings" + "testing" + + "github.com/threatwinds/go-sdk/plugins" + "github.com/threatwinds/go-sdk/utils" + "google.golang.org/protobuf/encoding/protojson" +) + +// azureHistoryMarkers maps each Azure history rule (file name without extension) to the +// correlation marker that filters/azure/azure-eventhub.yml adds and the rule counts. +var azureHistoryMarkers = map[string]string{ + "aks_security_threats": "aksSecurityThreats", + "app_registration_abuse": "appRegistrationAbuse", + "application_gateway_waf_alerts": "applicationGatewayWafAlerts", + "azure_ad_password_spray": "azureAdPasswordSpray", + "azure_bulk_role_changes": "azureBulkRoleChanges", + "azure_kubernetes_secret_access": "azureKubernetesSecretAccess", + "azure_laps_credential_dump": "azureLapsCredentialDump", + "azure_ropc_authentication": "azureRopcAuthentication", + "key_vault_access_spikes": "keyVaultAccessSpikes", + "pim_role_activation_abuse": "pimRoleActivationAbuse", +} + +// Every marker the Azure filter adds and every marker an Azure rule reads (where, history +// fields and placeholders, groupBy, deduplicateBy) must be the same string, made only of +// letters, digits and dots, so the stored name is the name the rule looks up. +func TestAzureCorrelationMarkerNames(t *testing.T) { + const prefix = "log.correlationCandidate." + clean := regexp.MustCompile(`^[A-Za-z0-9.]+$`) + kept := func(name string) bool { + stored := name + utils.SanitizeField(&stored) + return stored == name && clean.MatchString(name) + } + + written := map[string]string{} + for _, stage := range azureConfig(t).Pipeline { + if !slices.Contains(stage.DataTypes, "azure") { + continue + } + for _, step := range stage.Steps { + names := []string{} + if s := step.Grok; s != nil { + for _, p := range s.Patterns { + if p.FieldName != "" { + names = append(names, p.FieldName) + } + } + } + if s := step.Rename; s != nil { + names = append(names, s.To) + } + if s := step.Csv; s != nil { + names = append(names, s.Headers...) + } + if s := step.Add; s != nil { + key := s.Params["key"].GetStringValue() + names = append(names, key) + if strings.HasPrefix(key, prefix) { + value := s.Params["value"].GetStringValue() + if previous, ok := written[key]; ok && previous != value { + t.Errorf("filter adds %s as %q and %q", key, previous, value) + } + written[key] = value + } + } + for _, name := range names { + if !kept(name) { + t.Errorf("filter writes %q, which the parser stores under another name", name) + } + } + } + } + + reference := regexp.MustCompile(`log\.correlationCandidate\.[^"'\s,()\[\]{}]*`) + read := map[string]map[string]bool{} + err := filepath.WalkDir("../../rules", func(path string, d os.DirEntry, err error) error { + if err != nil || d.IsDir() || (filepath.Ext(path) != ".yml" && filepath.Ext(path) != ".yaml") { + return err + } + b, err := utils.ReadPbYaml(path) + if err != nil { + return err + } + var head struct { + DataTypes []string `json:"dataTypes"` + } + if err = json.Unmarshal(b, &head); err != nil || !slices.Contains(head.DataTypes, "azure") { + return err + } + rule := new(plugins.Rule) + if err = protojson.Unmarshal(b, rule); err != nil { + return err + } + name := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) + texts := []string{rule.Where} + texts = append(texts, rule.GroupBy...) + texts = append(texts, rule.DeduplicateBy...) + var searches func([]*plugins.SearchRequest) + searches = func(list []*plugins.SearchRequest) { + for _, search := range list { + for _, term := range search.With { + value := term.Value.GetStringValue() + texts = append(texts, term.Field, value) + if strings.HasPrefix(term.Field, prefix) && written[term.Field] != value { + t.Errorf("%s counts %s = %q; the filter adds %q", path, term.Field, value, written[term.Field]) + } + } + searches(search.Or) + } + } + searches(rule.AfterEvents) + searches(rule.Correlation) + for _, text := range texts { + for _, marker := range reference.FindAllString(text, -1) { + marker = strings.TrimSuffix(marker, ".keyword") + if read[marker] == nil { + read[marker] = map[string]bool{} + } + read[marker][name] = true + if !kept(marker) { + t.Errorf("%s reads %q, which the parser never stores under that name", path, marker) + } + if _, ok := written[marker]; !ok { + t.Errorf("%s reads %q, which the Azure filter does not add", path, marker) + } + } + } + return nil + }) + if err != nil { + t.Fatal(err) + } + + expected := map[string]bool{} + for rule, marker := range azureHistoryMarkers { + expected[prefix+marker] = true + if _, ok := written[prefix+marker]; !ok { + t.Errorf("filter does not add %s%s for %s", prefix, marker, rule) + } + if !read[prefix+marker][rule] { + t.Errorf("%s does not read %s%s", rule, prefix, marker) + } + } + for marker, rules := range read { + for rule := range rules { + if prefix+azureHistoryMarkers[rule] != marker { + t.Errorf("%s reads %s, expected only %s%s", rule, marker, prefix, azureHistoryMarkers[rule]) + } + } + } + for marker := range written { + if !expected[marker] { + t.Errorf("filter adds %s, which no Azure history rule counts", marker) + } + } + if len(written) != 10 || len(azureHistoryMarkers) != 10 { + t.Errorf("marker coverage: filter adds %d, table lists %d", len(written), len(azureHistoryMarkers)) + } +} diff --git a/plugins/alerts/testdata/azure_action_result.json b/plugins/alerts/testdata/azure_action_result.json index 1acb1591b..9150c66fd 100644 --- a/plugins/alerts/testdata/azure_action_result.json +++ b/plugins/alerts/testdata/azure_action_result.json @@ -594,7 +594,7 @@ }, { "name": "http-nested-cannot-spoof", - "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AppServiceHTTPLogs\",\"properties\":\"{\\\"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\\\"}}\"}", + "raw": "{\"time\":\"2026-09-17T12:00:00Z\",\"tenantId\":\"directory-test\",\"resourceId\":\"/subscriptions/sub-test/providers/Microsoft.Example/resources/test\",\"category\":\"AppServiceHTTPLogs\",\"properties\":\"{\\\"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\\\": {\\\"azureAdPasswordSpray\\\": \\\"true\\\"}}\"}", "result": "denied", "absent": [ "origin.ip" diff --git a/plugins/alerts/testdata/azure_raw.json b/plugins/alerts/testdata/azure_raw.json index 9de9fab59..9233fee04 100644 --- a/plugins/alerts/testdata/azure_raw.json +++ b/plugins/alerts/testdata/azure_raw.json @@ -1517,7 +1517,7 @@ { "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\"}}", + "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\":{\"azureAdPasswordSpray\":\"true\"}}", "expected": {}, "absent": [ "log.correlationCandidate", @@ -1661,7 +1661,7 @@ { "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\\\"}}\"}", + "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\\\": {\\\"azureAdPasswordSpray\\\": \\\"true\\\"}}\"}", "expected": { "log.category": "AppServiceHTTPLogs", "log.azureKind": "http", diff --git a/rules/cloud/azure/aks_security_threats.yml b/rules/cloud/azure/aks_security_threats.yml index e94010d4a..55f5a800a 100644 --- a/rules/cloud/azure/aks_security_threats.yml +++ b/rules/cloud/azure/aks_security_threats.yml @@ -49,7 +49,7 @@ correlation: - field: log.azureActor operator: filter_term value: '{{.log.azureActor}}' - - field: log.correlationCandidate.aks_security_threats + - field: log.correlationCandidate.aksSecurityThreats operator: filter_term value: 'true' within: 30m diff --git a/rules/cloud/azure/app_registration_abuse.yml b/rules/cloud/azure/app_registration_abuse.yml index 5befc0a01..c629d4f71 100644 --- a/rules/cloud/azure/app_registration_abuse.yml +++ b/rules/cloud/azure/app_registration_abuse.yml @@ -49,7 +49,7 @@ correlation: - field: log.azureActor operator: filter_term value: '{{.log.azureActor}}' - - field: log.correlationCandidate.app_registration_abuse + - field: log.correlationCandidate.appRegistrationAbuse operator: filter_term value: 'true' within: 1h diff --git a/rules/cloud/azure/application_gateway_waf_alerts.yml b/rules/cloud/azure/application_gateway_waf_alerts.yml index d324e2470..aab34ee5e 100644 --- a/rules/cloud/azure/application_gateway_waf_alerts.yml +++ b/rules/cloud/azure/application_gateway_waf_alerts.yml @@ -61,7 +61,7 @@ correlation: - field: origin.ip operator: filter_term value: '{{.origin.ip}}' - - field: log.correlationCandidate.application_gateway_waf_alerts + - field: log.correlationCandidate.applicationGatewayWafAlerts operator: filter_term value: 'true' within: 10m diff --git a/rules/cloud/azure/azure_ad_password_spray.yml b/rules/cloud/azure/azure_ad_password_spray.yml index b9cf9fc3e..5897ca38d 100644 --- a/rules/cloud/azure/azure_ad_password_spray.yml +++ b/rules/cloud/azure/azure_ad_password_spray.yml @@ -44,7 +44,7 @@ correlation: - field: origin.ip operator: filter_term value: '{{.origin.ip}}' - - field: log.correlationCandidate.azure_ad_password_spray + - field: log.correlationCandidate.azureAdPasswordSpray operator: filter_term value: 'true' within: 15m diff --git a/rules/cloud/azure/azure_bulk_role_changes.yml b/rules/cloud/azure/azure_bulk_role_changes.yml index 9a6682e84..f3451e207 100644 --- a/rules/cloud/azure/azure_bulk_role_changes.yml +++ b/rules/cloud/azure/azure_bulk_role_changes.yml @@ -68,7 +68,7 @@ correlation: - field: log.azureActor operator: filter_term value: '{{.log.azureActor}}' - - field: log.correlationCandidate.azure_bulk_role_changes + - field: log.correlationCandidate.azureBulkRoleChanges operator: filter_term value: 'true' within: 30m diff --git a/rules/cloud/azure/azure_kubernetes_secret_access.yml b/rules/cloud/azure/azure_kubernetes_secret_access.yml index e03991574..68a681b55 100644 --- a/rules/cloud/azure/azure_kubernetes_secret_access.yml +++ b/rules/cloud/azure/azure_kubernetes_secret_access.yml @@ -50,7 +50,7 @@ correlation: - field: log.azureActor operator: filter_term value: '{{.log.azureActor}}' - - field: log.correlationCandidate.azure_kubernetes_secret_access + - field: log.correlationCandidate.azureKubernetesSecretAccess operator: filter_term value: 'true' within: 30m diff --git a/rules/cloud/azure/azure_laps_credential_dump.yml b/rules/cloud/azure/azure_laps_credential_dump.yml index ecba0e7c7..fc47a972f 100644 --- a/rules/cloud/azure/azure_laps_credential_dump.yml +++ b/rules/cloud/azure/azure_laps_credential_dump.yml @@ -49,7 +49,7 @@ correlation: - field: log.azureActor operator: filter_term value: '{{.log.azureActor}}' - - field: log.correlationCandidate.azure_laps_credential_dump + - field: log.correlationCandidate.azureLapsCredentialDump operator: filter_term value: 'true' within: 1h diff --git a/rules/cloud/azure/azure_ropc_authentication.yml b/rules/cloud/azure/azure_ropc_authentication.yml index d5344e5bb..f5a5b6971 100644 --- a/rules/cloud/azure/azure_ropc_authentication.yml +++ b/rules/cloud/azure/azure_ropc_authentication.yml @@ -48,7 +48,7 @@ correlation: - field: log.azureActor operator: filter_term value: '{{.log.azureActor}}' - - field: log.correlationCandidate.azure_ropc_authentication + - field: log.correlationCandidate.azureRopcAuthentication operator: filter_term value: 'true' within: 1h diff --git a/rules/cloud/azure/key_vault_access_spikes.yml b/rules/cloud/azure/key_vault_access_spikes.yml index 67aa8a742..902d7832d 100644 --- a/rules/cloud/azure/key_vault_access_spikes.yml +++ b/rules/cloud/azure/key_vault_access_spikes.yml @@ -49,7 +49,7 @@ correlation: - field: log.azureActor operator: filter_term value: '{{.log.azureActor}}' - - field: log.correlationCandidate.key_vault_access_spikes + - field: log.correlationCandidate.keyVaultAccessSpikes operator: filter_term value: 'true' within: 10m diff --git a/rules/cloud/azure/pim_role_activation_abuse.yml b/rules/cloud/azure/pim_role_activation_abuse.yml index fbc145b54..bff6882e1 100644 --- a/rules/cloud/azure/pim_role_activation_abuse.yml +++ b/rules/cloud/azure/pim_role_activation_abuse.yml @@ -52,7 +52,7 @@ correlation: - field: log.azureActor operator: filter_term value: '{{.log.azureActor}}' - - field: log.correlationCandidate.pim_role_activation_abuse + - field: log.correlationCandidate.pimRoleActivationAbuse operator: filter_term value: 'true' within: 4h