diff --git a/Solutions/Cisco Meraki Events via REST API/Data Connectors/CiscoMerakiMultiRule_ccp/CiscoMeraki_PollerConfig.json b/Solutions/Cisco Meraki Events via REST API/Data Connectors/CiscoMerakiMultiRule_ccp/CiscoMeraki_PollerConfig.json index 55d88772941..2b1ceaf2590 100644 --- a/Solutions/Cisco Meraki Events via REST API/Data Connectors/CiscoMerakiMultiRule_ccp/CiscoMeraki_PollerConfig.json +++ b/Solutions/Cisco Meraki Events via REST API/Data Connectors/CiscoMerakiMultiRule_ccp/CiscoMeraki_PollerConfig.json @@ -253,7 +253,8 @@ }, "stepCollectorConfigs": { "air_marshal_events": { - "shouldJoinNestedData": false, + "shouldJoinNestedData": true, + "joinedDataStepName": "expand", "request": { "apiEndpoint": "[[concat(parameters('apiBaseUrl'),'/networks/$networkId$/wireless/airMarshal')]", "httpMethod": "GET", @@ -276,6 +277,11 @@ } } }, + "shouldJoinNestedData": true, + "joinedDataStepName": "network", + "extra": { + "nestedTransformName": "/ASI/Microsoft/MvExpandTransformer" + }, "connectorDefinitionName": "CiscoMerakiConnector", "dataType": "CiscoMerakiAirMarshalEvents", "dcrConfig": { diff --git a/Solutions/Cisco Meraki Events via REST API/Data Connectors/CiscoMerakiMultiRule_ccp/CiscoMeraki_dcr.json b/Solutions/Cisco Meraki Events via REST API/Data Connectors/CiscoMerakiMultiRule_ccp/CiscoMeraki_dcr.json index 8ffb5efc6b6..06b4c07dfbf 100644 --- a/Solutions/Cisco Meraki Events via REST API/Data Connectors/CiscoMerakiMultiRule_ccp/CiscoMeraki_dcr.json +++ b/Solutions/Cisco Meraki Events via REST API/Data Connectors/CiscoMerakiMultiRule_ccp/CiscoMeraki_dcr.json @@ -173,40 +173,12 @@ "Custom-CiscoMerakiAirMarshalEvents_CL": { "columns": [ { - "name": "ssid", - "type": "string" - }, - { - "name": "bssids", + "name": "network", "type": "dynamic" }, { - "name": "channels", + "name": "expand", "type": "dynamic" - }, - { - "name": "firstSeen", - "type": "datetime" - }, - { - "name": "lastSeen", - "type": "datetime" - }, - { - "name": "wiredMacs", - "type": "dynamic" - }, - { - "name": "wiredVlans", - "type": "dynamic" - }, - { - "name": "wiredLastSeen", - "type": "datetime" - }, - { - "name": "networkId", - "type": "string" } ] }, @@ -638,7 +610,7 @@ "clv2ws1" ], "outputStream": "Custom-CiscoMerakiAirMarshalEvents_CL", - "transformKql": "source | extend TimeGenerated = iff(isnull(lastSeen), now(), todatetime(lastSeen)) , Ssid = ['ssid'] , Bssids = ['bssids'] , Channels = ['channels'] , FirstSeen = ['firstSeen'] , LastSeen = ['lastSeen'] , WiredMacs = ['wiredMacs'] , WiredVlans = ['wiredVlans'] , WiredLastSeen = ['wiredLastSeen'] , NetworkId = ['networkId'] | project TimeGenerated , Ssid , Bssids , Channels , FirstSeen , LastSeen , WiredMacs , WiredVlans , WiredLastSeen , NetworkId" + "transformKql": "source | extend Ssid = tostring(expand.ssid) , Bssids = expand.bssids , Channels = expand.channels , FirstSeen = unixtime_seconds_todatetime(tolong(expand.firstSeen)) , LastSeen = unixtime_seconds_todatetime(tolong(expand.lastSeen)) , WiredMacs = expand.wiredMacs , WiredVlans = expand.wiredVlans , WiredLastSeen = unixtime_seconds_todatetime(tolong(expand.wiredLastSeen)) , NetworkId = tostring(network.id) , Types = expand.types | extend TimeGenerated = iff(isnull(LastSeen), now(), LastSeen) | project TimeGenerated , Ssid , Bssids , Channels , FirstSeen , LastSeen , WiredMacs , WiredVlans , WiredLastSeen , NetworkId , Types" }, { "streams": [ diff --git a/Solutions/Cisco Meraki Events via REST API/Data Connectors/CiscoMerakiMultiRule_ccp/README.md b/Solutions/Cisco Meraki Events via REST API/Data Connectors/CiscoMerakiMultiRule_ccp/README.md new file mode 100644 index 00000000000..2a79752b6a4 --- /dev/null +++ b/Solutions/Cisco Meraki Events via REST API/Data Connectors/CiscoMerakiMultiRule_ccp/README.md @@ -0,0 +1,43 @@ +# Cisco Meraki Air Marshal ingestion + +The Air Marshal collector lists an organization's wireless networks and calls +`/networks/{networkId}/wireless/airMarshal` for each network. Cisco's scan results +do not include the network ID. + +The collector retains the parent network record as `network` and the child scan +results as `expand`. The CCF nested `MvExpandTransformer` emits one row per scan +result with its parent network attached. The Air Marshal DCR input stream therefore +contains two dynamic columns, `network` and `expand`. + +The DCR maps `network.id` to `NetworkId`, preserves `expand.types` as the dynamic +`Types` array, and converts `firstSeen`, `lastSeen`, and `wiredLastSeen` from Unix +epoch seconds to UTC datetimes. `TimeGenerated` uses the converted `LastSeen`, +falling back to ingestion time when it is absent. Other event timestamps remain +null when absent. + +All classifications are retained, including empty arrays; ingestion does not +filter out unclassified SSIDs. For example, query classified results with: + +```kusto +CiscoMerakiAirMarshalEvents +| where Types has_any ("rogue", "spoof") +| project TimeGenerated, NetworkId, Ssid, Types, FirstSeen, LastSeen, WiredLastSeen +``` + +## Updating existing installations + +Deploy the updated Air Marshal table schema, DCR, and polling configuration +together. The joined input shape is not compatible with the previous flat DCR +input stream. Updating only the saved-search alias or table schema does not fix +collection. Existing ingested rows with missing values are not repaired by this +change. + +After updating, verify new scan results from multiple networks, including the +same SSID in different networks. Each result must retain its own `NetworkId`, +classification array, and converted timestamps. Also verify networks with no +scan results do not produce event rows. + +References: + +- [Cisco Air Marshal API](https://developer.cisco.com/meraki/api-v1/get-network-wireless-air-marshal/) +- [CCF nested API polling](https://learn.microsoft.com/azure/sentinel/isv/custom-connector-nested-api-polling) diff --git a/Solutions/Cisco Meraki Events via REST API/Data Connectors/CiscoMerakiMultiRule_ccp/table_CiscoMerakiAirMarshalEvents.json b/Solutions/Cisco Meraki Events via REST API/Data Connectors/CiscoMerakiMultiRule_ccp/table_CiscoMerakiAirMarshalEvents.json index 0194b7beb50..432465fd8ae 100644 --- a/Solutions/Cisco Meraki Events via REST API/Data Connectors/CiscoMerakiMultiRule_ccp/table_CiscoMerakiAirMarshalEvents.json +++ b/Solutions/Cisco Meraki Events via REST API/Data Connectors/CiscoMerakiMultiRule_ccp/table_CiscoMerakiAirMarshalEvents.json @@ -62,6 +62,11 @@ "name": "NetworkId", "type": "string", "description": "Network ID where the Air Marshal event was detected." + }, + { + "name": "Types", + "type": "dynamic", + "description": "Array of SSID threat classifications, such as rogue or spoof. Empty when no classification applies." } ] } diff --git a/Solutions/Cisco Meraki Events via REST API/Data/Solution_Cisco Meraki Events via REST API.json b/Solutions/Cisco Meraki Events via REST API/Data/Solution_Cisco Meraki Events via REST API.json index fd5d36a64f0..f1f9003c869 100644 --- a/Solutions/Cisco Meraki Events via REST API/Data/Solution_Cisco Meraki Events via REST API.json +++ b/Solutions/Cisco Meraki Events via REST API/Data/Solution_Cisco Meraki Events via REST API.json @@ -14,7 +14,7 @@ ], "Metadata": "SolutionMetadata.json", "BasePath": "C:\\Github\\Azure-Sentinel\\Solutions\\", - "Version": "3.2.1", + "Version": "3.2.2", "TemplateSpec": true, "Is1PConnector": false } diff --git a/Solutions/Cisco Meraki Events via REST API/Package/3.2.2.zip b/Solutions/Cisco Meraki Events via REST API/Package/3.2.2.zip new file mode 100644 index 00000000000..6669582e714 Binary files /dev/null and b/Solutions/Cisco Meraki Events via REST API/Package/3.2.2.zip differ diff --git a/Solutions/Cisco Meraki Events via REST API/Package/mainTemplate.json b/Solutions/Cisco Meraki Events via REST API/Package/mainTemplate.json index 74a4b9b9f44..9eadbc928b8 100644 --- a/Solutions/Cisco Meraki Events via REST API/Package/mainTemplate.json +++ b/Solutions/Cisco Meraki Events via REST API/Package/mainTemplate.json @@ -47,11 +47,11 @@ "email": "support@microsoft.com", "_email": "[variables('email')]", "_solutionName": "Cisco Meraki Events via REST API", - "_solutionVersion": "3.2.1", + "_solutionVersion": "3.2.2", "solutionId": "azuresentinel.azure-sentinel-solution-ciscomerakinativepoller", "_solutionId": "[variables('solutionId')]", "workspaceResourceId": "[resourceId('microsoft.OperationalInsights/Workspaces', parameters('workspace'))]", - "dataConnectorCCPVersion": "3.2.1", + "dataConnectorCCPVersion": "3.2.2", "_dataConnectorContentIdConnectorDefinition1": "CiscoMerakiConnector", "dataConnectorTemplateNameConnectorDefinition1": "[concat(parameters('workspace'),'-dc-',uniquestring(variables('_dataConnectorContentIdConnectorDefinition1')))]", "_dataConnectorContentIdConnections1": "CiscoMerakiConnectorConnections", @@ -638,40 +638,12 @@ "Custom-CiscoMerakiAirMarshalEvents_CL": { "columns": [ { - "name": "ssid", - "type": "string" - }, - { - "name": "bssids", - "type": "dynamic" - }, - { - "name": "channels", - "type": "dynamic" - }, - { - "name": "firstSeen", - "type": "datetime" - }, - { - "name": "lastSeen", - "type": "datetime" - }, - { - "name": "wiredMacs", + "name": "network", "type": "dynamic" }, { - "name": "wiredVlans", + "name": "expand", "type": "dynamic" - }, - { - "name": "wiredLastSeen", - "type": "datetime" - }, - { - "name": "networkId", - "type": "string" } ] }, @@ -1103,7 +1075,7 @@ "clv2ws1" ], "outputStream": "Custom-CiscoMerakiAirMarshalEvents_CL", - "transformKql": "source | extend TimeGenerated = iff(isnull(lastSeen), now(), todatetime(lastSeen)) , Ssid = ['ssid'] , Bssids = ['bssids'] , Channels = ['channels'] , FirstSeen = ['firstSeen'] , LastSeen = ['lastSeen'] , WiredMacs = ['wiredMacs'] , WiredVlans = ['wiredVlans'] , WiredLastSeen = ['wiredLastSeen'] , NetworkId = ['networkId'] | project TimeGenerated , Ssid , Bssids , Channels , FirstSeen , LastSeen , WiredMacs , WiredVlans , WiredLastSeen , NetworkId" + "transformKql": "source | extend Ssid = tostring(expand.ssid) , Bssids = expand.bssids , Channels = expand.channels , FirstSeen = unixtime_seconds_todatetime(tolong(expand.firstSeen)) , LastSeen = unixtime_seconds_todatetime(tolong(expand.lastSeen)) , WiredMacs = expand.wiredMacs , WiredVlans = expand.wiredVlans , WiredLastSeen = unixtime_seconds_todatetime(tolong(expand.wiredLastSeen)) , NetworkId = tostring(network.id) , Types = expand.types | extend TimeGenerated = iff(isnull(LastSeen), now(), LastSeen) | project TimeGenerated , Ssid , Bssids , Channels , FirstSeen , LastSeen , WiredMacs , WiredVlans , WiredLastSeen , NetworkId , Types" }, { "streams": [ @@ -1308,6 +1280,11 @@ "name": "NetworkId", "type": "string", "description": "Network ID where the Air Marshal event was detected." + }, + { + "name": "Types", + "type": "dynamic", + "description": "Array of SSID threat classifications, such as rogue or spoof. Empty when no classification applies." } ] } @@ -2313,7 +2290,8 @@ }, "stepCollectorConfigs": { "air_marshal_events": { - "shouldJoinNestedData": false, + "shouldJoinNestedData": true, + "joinedDataStepName": "expand", "request": { "apiEndpoint": "[[concat(parameters('apiBaseUrl'),'/networks/$networkId$/wireless/airMarshal')]", "httpMethod": "GET", @@ -2336,6 +2314,11 @@ } } }, + "shouldJoinNestedData": true, + "joinedDataStepName": "network", + "extra": { + "nestedTransformName": "/ASI/Microsoft/MvExpandTransformer" + }, "connectorDefinitionName": "CiscoMerakiConnector", "dataType": "CiscoMerakiAirMarshalEvents", "dcrConfig": { @@ -2617,7 +2600,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "parser_CiscoMerakiOrganizationsAliasFunction Data Parser with template version 3.2.1", + "description": "parser_CiscoMerakiOrganizationsAliasFunction Data Parser with template version 3.2.2", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('parserObject1').parserVersion1]", @@ -2749,7 +2732,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "parser_CiscoMerakiOrganizationNetworksAliasFunction Data Parser with template version 3.2.1", + "description": "parser_CiscoMerakiOrganizationNetworksAliasFunction Data Parser with template version 3.2.2", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('parserObject2').parserVersion2]", @@ -2881,7 +2864,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "parser_CiscoMerakiNetworkClientsAliasFunction Data Parser with template version 3.2.1", + "description": "parser_CiscoMerakiNetworkClientsAliasFunction Data Parser with template version 3.2.2", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('parserObject3').parserVersion3]", @@ -3013,7 +2996,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "parser_CiscoMerakiAirMarshalEventsAliasFunction Data Parser with template version 3.2.1", + "description": "parser_CiscoMerakiAirMarshalEventsAliasFunction Data Parser with template version 3.2.2", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('parserObject4').parserVersion4]", @@ -3141,7 +3124,7 @@ "apiVersion": "2023-04-01-preview", "location": "[parameters('workspace-location')]", "properties": { - "version": "3.2.1", + "version": "3.2.2", "kind": "Solution", "contentSchemaVersion": "3.0.0", "displayName": "Cisco Meraki Events via REST API", diff --git a/Solutions/Cisco Meraki Events via REST API/ReleaseNotes.md b/Solutions/Cisco Meraki Events via REST API/ReleaseNotes.md index e2abe63b937..8e89e3509d9 100644 --- a/Solutions/Cisco Meraki Events via REST API/ReleaseNotes.md +++ b/Solutions/Cisco Meraki Events via REST API/ReleaseNotes.md @@ -1,5 +1,7 @@ | **Version** | **Date Modified (DD-MM-YYYY)** | **Change History** | |-------------|--------------------------------|--------------------------------------------------------------------| +| 3.2.2 | 29-10-2026 | Fixed Data Connector Air Marshal ingestion to retain the parent network ID for each scan result, preserve threat classifications in Types, and convert first-seen, last-seen, and wired-last-seen epoch seconds to UTC datetimes. | +| 3.2.2 | 29-10-2026 | Fixed **Data Connector** Air Marshal ingestion to retain the parent network ID for each scan result, preserve threat classifications in `Types`, and convert first-seen, last-seen, and wired-last-seen epoch seconds to UTC datetimes. | | 3.2.1 | 15-09-2026 | Fixed Cisco Meraki connector regressions by correcting the Organizations/Organization Networks health check window, routing security events except File Scanned to ASIM Network Session logs, separating File Scanned events into a dedicated custom table, restoring query-string support for API request URLs, preserving additional configuration-change context, and restoring ASIM Network Session `DvcAction` mapping from the `blocked` field. Fixed DCR creation errors by replacing unsupported `coalesce` expressions and matching File Scanned `EventCount` to the destination string schema. Corrected Network Clients and Air Marshal nested polling to extract the network ID from each parent record without expanding its properties. | | 3.2.0 | 28-08-2026 | Promoted the updated Cisco Meraki CCF REST API data connector introduced in version 3.1.0 to General Availability (GA). | | 3.1.1 | 24-08-2026 | Standardized all CCF Connector data types to use checkpointed `t0`/`t1` time windowing (UnixTimestamp), aligning the newer Organizations, Organization Networks, Network Clients, and Air Marshal Events data types with the existing ASIM log endpoints to prevent duplicate ingestion.
Updated the polling interval and `t0`/`t1` time window configuration of the **CCF Connector** data types to fix duplicate ingestion.|