I was recently asked to look at the logs for an Azure Key Vault to extract all of the IP addresses that had been connecting to it.
This would have been a simple task if the logs were in a Log Analytics Workspace but they had been configured to go into a Azure Storage Account.
The problem with having the data in an Azure Storage Account is that it isn't structured in way that makes it easy to get at the data due to the number of folders:
{ "time": "2024-10-31T16:04:55.4510338Z", "category": "AuditEvent", "operationName": "VaultGet", "resultType": "Success", "correlationId": "abcdef12-ab12-34cd-1234-abcdef123456", "callerIpAddress": "1.1.1.1", "identity": {"claim":{"http://schemas.microsoft.com/identity/claims/objectidentifier":"ea1ad6fd-e8e9-41d3-b6c8-f062fd2d36fb","http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn":"live.com#[email protected]","http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name":"live.com#xxxx.xx.xx","appid":"abcdef12-ab12-34cd-1234-abcdef123456"}}, "properties": {"id":"https://grussexamplekv.vault.azure.net/","clientInfo":"Mozilla/5.0","requestUri":"https://management.azure.com/subscriptions/abcdef12-ab12-34cd-1234-abcdef123456/resourceGroups/grussBlog01/providers/Microsoft.KeyVault/vaults/GrussExampleKV?api-version=2018-02-14","httpStatusCode":200,"properties":{"sku":{"Family":"A","Name":"Standard","Capacity":null},"tenantId":"abcdef12-ab12-34cd-1234-abcdef123456","networkAcls":{"bypass":"None","defaultAction":"Allow"},"enabledForDeployment":false,"enabledForDiskEncryption":false,"enabledForTemplateDeployment":false,"enableSoftDelete":true,"softDeleteRetentionInDays":7,"enableRbacAuthorization":true,"enablePurgeProtection":null}}, "resourceId": "/SUBSCRIPTIONS/abcdef12-ab12-34cd-1234-abcdef123456/RESOURCEGROUPS/GRUSSBLOG01/PROVIDERS/MICROSOFT.KEYVAULT/VAULTS/GRUSSEXAMPLEKV", "operationVersion": "2018-02-14", "resultSignature": "OK", "durationMs": "32"}
{ "time": "2024-10-31T16:15:03.5039974Z", "category": "AuditEvent", "operationName": "VaultGet", "resultType": "Success", "correlationId": "abcdef12-ab12-34cd-1234-abcdef123456", "callerIpAddress": "1.1.1.1", "identity": {"claim":{"http://schemas.microsoft.com/identity/claims/objectidentifier":"ea1ad6fd-e8e9-41d3-b6c8-f062fd2d36fb","http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn":"live.com#xxxx.xx.xx","http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name":"live.com#xxxx.xx.xx","appid":"abcdef12-ab12-34cd-1234-abcdef123456"}}, "properties": {"id":"https://grussexamplekv.vault.azure.net/","clientInfo":"python/3.6.6","requestUri":"https://management.azure.com/subscriptions/abcdef12-ab12-34cd-1234-abcdef123456/resourceGroups/grussBlog01/providers/Microsoft.KeyVault/vaults/GrussExampleKV?api-version=2019-09-01","httpStatusCode":200,"properties":{"sku":{"Family":"A","Name":"Standard","Capacity":null},"tenantId":"abcdef12-ab12-34cd-1234-abcdef123456","networkAcls":{"bypass":"None","defaultAction":"Allow"},"enabledForDeployment":false,"enabledForDiskEncryption":false,"enabledForTemplateDeployment":false,"enableSoftDelete":true,"softDeleteRetentionInDays":7,"enableRbacAuthorization":true,"enablePurgeProtection":null}}, "resourceId": "/SUBSCRIPTIONS/abcdef12-ab12-34cd-1234-abcdef123456/RESOURCEGROUPS/GRUSSBLOG01/PROVIDERS/MICROSOFT.KEYVAULT/VAULTS/GRUSSEXAMPLEKV", "operationVersion": "2019-09-01", "resultSignature": "OK", "durationMs": "51"}
{ "time": "2024-10-31T16:04:49.1252392Z", "category": "AuditEvent", "operationName": "VaultGet", "resultType": "Success", "correlationId": "abcdef12-ab12-34cd-1234-abcdef123456", "callerIpAddress": "1.1.1.1", "identity": {"claim":{"http://schemas.microsoft.com/identity/claims/objectidentifier":"ea1ad6fd-e8e9-41d3-b6c8-f062fd2d36fb","http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn":"live.com#xxxx.xx.xx","http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name":"live.com#xxxx.xx.xx","appid":"abcdef12-ab12-34cd-1234-abcdef123456"}}, "properties": {"id":"https://grussexamplekv.vault.azure.net/","clientInfo":"Mozilla/5.0","requestUri":"https://management.azure.com/subscriptions/abcdef12-ab12-34cd-1234-abcdef123456/resourceGroups/grussBlog01/providers/Microsoft.KeyVault/vaults/GrussExampleKV?api-version=2023-08-01-preview","httpStatusCode":200,"properties":{"sku":{"Family":"A","Name":"Standard","Capacity":null},"tenantId":"abcdef12-ab12-34cd-1234-abcdef123456","networkAcls":{"bypass":"None","defaultAction":"Allow"},"enabledForDeployment":false,"enabledForDiskEncryption":false,"enabledForTemplateDeployment":false,"enableSoftDelete":true,"softDeleteRetentionInDays":7,"enableRbacAuthorization":true,"enablePurgeProtection":null}}, "resourceId": "/SUBSCRIPTIONS/abcdef12-ab12-34cd-1234-abcdef123456/RESOURCEGROUPS/GRUSSBLOG01/PROVIDERS/MICROSOFT.KEYVAULT/VAULTS/GRUSSEXAMPLEKV", "operationVersion": "2023-08-01-preview", "resultSignature": "OK", "durationMs": "38"}
{ "time": "2024-10-31T16:04:44.1125905Z", "category": "AuditEvent", "operationName": "VaultGet", "resultType": "Success", "correlationId": "abcdef12-ab12-34cd-1234-abcdef123456", "callerIpAddress": "1.1.1.1", "identity": {"claim":{"http://schemas.microsoft.com/identity/claims/objectidentifier":"ea1ad6fd-e8e9-41d3-b6c8-f062fd2d36fb","http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn":"live.com#xxxx.xx.xx","http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name":"live.com#xxxx.xx.xx","appid":"abcdef12-ab12-34cd-1234-abcdef123456"}}, "properties": {"id":"https://grussexamplekv.vault.azure.net/","clientInfo":"Mozilla/5.0","requestUri":"https://management.azure.com/subscriptions/abcdef12-ab12-34cd-1234-abcdef123456/resourceGroups/grussBlog01/providers/Microsoft.KeyVault/vaults/GrussExampleKV?api-version=2023-08-01-preview","httpStatusCode":200,"properties":{"sku":{"Family":"A","Name":"Standard","Capacity":null},"tenantId":"abcdef12-ab12-34cd-1234-abcdef123456","networkAcls":{"bypass":"None","defaultAction":"Allow"},"enabledForDeployment":false,"enabledForDiskEncryption":false,"enabledForTemplateDeployment":false,"enableSoftDelete":true,"softDeleteRetentionInDays":7,"enableRbacAuthorization":true,"enablePurgeProtection":null}}, "resourceId": "/SUBSCRIPTIONS/abcdef12-ab12-34cd-1234-abcdef123456/RESOURCEGROUPS/GRUSSBLOG01/PROVIDERS/MICROSOFT.KEYVAULT/VAULTS/GRUSSEXAMPLEKV", "operationVersion": "2023-08-01-preview", "resultSignature": "OK", "durationMs": "30"}
To extract the callerIpAddress meant that a script was required to navigate through the folders (recursively) then read each file and extract the information we needed.
$logsDirectory = '<< location of directory >>'
# Loop through each folder and output the full path of any *.json files.
Function Get-JsonFile {
Param ($jsonDirectory)
Get-ChildItem -Path $jsonDirectory | ForEach-Object {
# Write-Output $_.Name
If ($_.Name -like "*.json") {
$_.FullName
} ElseIf ($_.PSIsContainer) {
Get-JsonFile -jsonDirectory $_.FullName
}
}
}
# Treat each row as a json document and output the callerIpAddress
Function ExtractIPFromJsonFile {
Param ($jsonFileName)
$jsonFile = Get-Content $jsonFileName
ForEach ($row in $jsonFile) {
$jsonRow = $row | ConvertFrom-Json
Write-Output $jsonRow.callerIpAddress
}
}
# Get a list of all of the files using the above function
$files = Get-JsonFile $logsDirectory
ForEach ($file in $files) {
$IPs = ExtractIPFromJsonFile -jsonFileName $file
# Add the IP addresses found to a file.
Add-Content -path "$logsDirectory/IPs.txt" -Value $IPs
}
# Now all IPs are written to file
# read it back in and get distinct IPs (and counts)
$allIPs = Get-Content -path "$logsDirectory/IPs.txt" | Group-Object
$allIPs | ForEach-Object {
[PSCustomObject]@{
Line = $_.Name
Count = $_.Count
}
} | Sort-Object -Property Count -Descending