This document details the steps needed to create a new Azure Data Explorer table for ingested logs from Azure Active Directory.
All ingested logs from AAD are written to a table in ADX named MyAADLogs, this table is overwritten over and over thus the need to create a parsing function which is used to filter the new ingested logs by their category and construct new records out of it to then write them to their corresponding tables.
First step is to query the MyAADLogs table filtering by the record.category property and expanding those properties of interest from each record. We can query the same logs using Log Analytics for comparison. For example, for NonInteractiveUserSignInLogs:
MyAADLogs
| mv-expand record = data.records
| where tostring(record.category) == 'NonInteractiveUserSignInLogs'
| extend LocationDetails = todynamic(record.properties.['location'])
| project
TimeGenerated = todatetime(record.['time']),
OperationName = record.['operationName'],
OperationVersion = record.['operationVersion'],
Category = record.['category'],
ResultType = record.['resultType'],
ResultSignature = record.['resultSignature'],
ResultDescription = record.['resultDescription'],
DurationMs = record.['durationMs'],
CorrelationId = record.['correlationId'],
ResourceGroup = split(record.['resourceId'], '/')[-1],
Identity = record.['identity'],
Location = LocationDetails['countryOrRegion'],
AppDisplayName = record.properties.['appDisplayName'],
AppId = record.properties.['appId'],
AuthenticationContextClassReferences = record.properties['authenticationContextClassReferences'],
AuthenticationDetails = record.properties['authenticationDetails'],
AuthenticationProcessingDetails = record.properties.['authenticationProcessingDetails'],
AuthenticationProtocol = record.properties.['authenticationProtocol'],
AuthenticationRequirement = record.properties.['authenticationRequirement'],
AuthenticationRequirementPolicies = record.properties.['authenticationRequirementPolicies'],
AutonomousSystemNumber = record.properties.['autonomousSystemNumber'],
ClientAppUsed = record.properties.['clientAppUsed'],
CreatedDateTime = todatetime(record.properties.['createdDateTime']),
CrossTenantAccessType = record.properties.['crossTenantAccessType'],
DeviceDetail = record.properties.['deviceDetail'],
HomeTenantId = record.properties['homeTenantId'],
Id = record.properties.['id'],
IPAddress = record.callerIpAddress,
IsInteractive = record.properties.['isInteractive'],
LocationDetails,
MfaDetail = record.properties.['mfaDetail'],
NetworkLocationDetails = record.properties.['networkLocationDetails'],
OriginalRequestId = record.properties.['originalRequestId'],
ProcessingTimeInMs = record.properties.['processingTimeInMilliseconds'],
ResourceDisplayName = record.properties.['resourceDisplayName'],
ResourceIdentity = record.properties.['resourceId'],
ResourceServicePrincipalId = record.properties.['resourceServicePrincipalId'],
ResourceTenantId = record.properties.['resourceTenantId'],
RiskDetail = record.properties.['riskDetail'],
RiskEventTypes = record.properties.['riskEventTypes'],
RiskLevelAggregated = record.properties.['riskLevelAggregated'],
RiskState = record.properties.['riskState'],
SessionLifetimePolicies = record.properties.['sessionLifetimePolicies'],
Status = record.properties.['status'],
TokenIssuerType = record.properties.['tokenIssuerType'],
UniqueTokenIdentifier = record.properties.['uniqueTokenIdentifier'],
UserAgent = record.properties.['userAgent'],
UserDisplayName = record.properties.['userDisplayName'],
UserId = record.properties.['userId'],
UserPrincipalName = record.properties.['userPrincipalName'],
UserType = record.properties.['userType'],
Type = 'AADNonInteractiveUserSignInLogs'Once we have the new query we can use the .create-or-alter function command to create a new Azure Data Explorer function. This command can also be used to update an existing query.
.create-or-alter function AADNonInteractiveUserSignInLogsParsing() {
AADLogs
| mv-expand record = data.records
| where tostring(record.category) == 'NonInteractiveUserSignInLogs'
| project
...
...
}After running the control command we should see the new function being created in the Functions table. Now we can re-use this function:
AADNonInteractiveUserSignInLogsParsing
| take 1Next step is to create the table where the new logs are going to be written to. For this we can use the .set control command:
.set AADNonInteractiveUserSignInLogs <|
AADNonInteractiveUserSignInLogsParsing()
| take 0The | take 0 suffix is meant to make sure the command actually appends no records to the target table. We just need to create the schema for this new table.
Last step is to change the table update policy, here we define which query is going to be used to update the table and the source table. For this we can use the .alter table policy update command:
.alter table AADNonInteractiveUserSignInLogs policy update
```
[
{
"IsEnabled": true,
"Source": "MyAADLogs",
"Query": "AADNonInteractiveUserSignInLogsParsing",
"IsTransactional": true,
"PropagateIngestionProperties": false
}
]
```