"json":"### Help\r\nSelect **Subscription**, **Workspace**, and **Time Range** from the drop-downs above.\r\nThe workbook will display analytics rules according to your selections.\r\n\r\nThe **Time range** filter refers to the time the rule was last modified, and applies to all components.",
"style":"info"
},
"conditionalVisibility":{
"parameterName":"Help",
"comparison":"isEqualTo",
"value":"Yes"
},
"name":"Help text"
},
{
"type":11,
"content":{
"version":"LinkItem/1.0",
"style":"tabs",
"links":[
{
"id":"18c690d7-7cbd-46c1-b677-1f72692d40cd",
"cellValue":"TAB",
"linkTarget":"parameter",
"linkLabel":"Alert Rules",
"subTarget":"Rule",
"preText":"Alert rules",
"style":"link"
},
{
"id":"94d4ab9a-4ec7-4e4b-a69d-b6acb5cccd62",
"cellValue":"TAB",
"linkTarget":"parameter",
"linkLabel":"Alerts",
"subTarget":"Alert",
"style":"link"
},
{
"id":"19b985d3-9819-4d97-aeec-dfc558977b39",
"cellValue":"TAB",
"linkTarget":"parameter",
"linkLabel":"Incidents",
"subTarget":"Incident",
"style":"link"
}
]
},
"name":"Tabs link"
},
{
"type":1,
"content":{
"json":"### Analytics rules\r\nThis table displays all the analytics rules in the chosen subscription, workspace, and time range. Select rules to analyze based on the selected tab. "
},
"name":"Heading-workbook"
},
{
"type":1,
"content":{
"json":"### Help\r\nSelect rules from the table for analysis.\r\nThe workbook is updated dynamically as rules are selected or deselected.",
"json":"## Total number of incidents \nSum of all the incidents generated by all the selected rules over the selected time range"
},
"conditionalVisibility":{
"parameterName":"prop",
"comparison":"isNotEqualTo"
},
"name":"Incindets sum text"
},
{
"type":1,
"content":{
"json":"### Help\r\nThe **Total number of incidents** chart shows the total count of the incidents generated by the selected rules over the selected time range.\r\nYou can use this chart to monitor the incidents load on the SOC.\r\n\r\n",
"style":"info"
},
"conditionalVisibilities":[
{
"parameterName":"Help",
"comparison":"isEqualTo",
"value":"Yes"
},
{
"parameterName":"prop",
"comparison":"isNotEqualTo"
}
],
"name":"Incindets sum text help"
},
{
"type":3,
"content":{
"version":"KqlItem/1.0",
"query":"let alertText = strcat_array(dynamic([{AlertRuleID}]),\",\");\r\nlet isProductMarked = (product:string) {\r\n let productText = strcat_array(dynamic([{ProductName}]),\",\");\r\n array_index_of(split(productText,'\\\"'),product)\r\n};\r\nlet SecurityAlertFiltered= SecurityAlert\r\n| where TimeGenerated >= {TimeRange:start} and TimeGenerated <= {TimeRange:end}\r\n| project ProductName,ExtendedProperties,SystemAlertId\r\n| extend AnalyticRuleIdStr = replace('\\\"','',tostring(todynamic(ExtendedProperties)[\"Analytic Rule Ids\"]))\r\n| extend AnaliticRulesInAlertArray= split(substring(AnalyticRuleIdStr,1,string_size(AnalyticRuleIdStr)-2),\",\")\r\n| mv-expand SingleAnaliticRuleID=AnaliticRulesInAlertArray\r\n| extend SingleAnaliticRuleID=tostring(SingleAnaliticRuleID)\r\n| extend SingleAnaliticRuleID=iff(ProductName==\"Azure Sentinel\",tostring(SingleAnaliticRuleID),ProductName)\r\n| where alertText has SingleAnaliticRuleID or isProductMarked(SingleAnaliticRuleID)!=-1;\r\nlet SecurityIncedentFiltered = SecurityIncident\r\n| where TimeGenerated >= {TimeRange:start} and TimeGenerated <= {TimeRange:end}\r\n| project AlertIds,TimeGenerated\r\n| mv-expand AnalyticAlertId=AlertIds\r\n| extend AnalyticAlertId=tostring(AnalyticAlertId);\r\nSecurityAlertFiltered\r\n| join SecurityIncedentFiltered on $left.SystemAlertId==$right.AnalyticAlertId\r\n| project TimeGenerated,SingleAnaliticRuleID\r\n| summarize incedentAmount=count() by bin(TimeGenerated,1h)\r\n",
"json":"## Amount of incidents by rule \nFor each of the selected rules the total incidents number genereted on the selected time period by closing status "
},
"conditionalVisibility":{
"parameterName":"prop",
"comparison":"isNotEqualTo"
},
"name":"Total amount of Incidents help text"
},
{
"type":1,
"content":{
"json":"### Help\r\nHere you can see the amount of incidents created from each rule you selected.\r\nIn addition you can see the distribution of closing reasons for each of the selected rules. \r\nMonitoring the amount of incidents each detection is creating is essential for the SOC.\r\n",
"style":"info"
},
"conditionalVisibilities":[
{
"parameterName":"Help",
"comparison":"isEqualTo",
"value":"Yes"
},
{
"parameterName":"prop",
"comparison":"isNotEqualTo"
}
],
"name":"Incident by rules text "
},
{
"type":3,
"content":{
"version":"KqlItem/1.0",
"query":"let alertText = strcat_array(dynamic([{AlertRuleID}]),\",\");\r\nlet isProductMarked = (product:string) {\r\n let productText = strcat_array(dynamic([{ProductName}]),\",\");\r\n array_index_of(split(productText,','),product)\r\n};\r\nlet getAmountOfIncedentForRuleId = (classification:string,status:string){\r\nSecurityIncident\r\n | project TimeGenerated, Status, Classification, RelatedAnalyticRuleIds, AdditionalData\r\n | where TimeGenerated >= {TimeRange:start} and TimeGenerated <= {TimeRange:end}\r\n | where Status == status and Classification == classification\r\n | mv-expand RuleId=RelatedAnalyticRuleIds\r\n | extend RuleId=tostring(RuleId)\r\n | extend AlertProductNames = todynamic(AdditionalData)[\"alertProductNames\"]\r\n | mv-expand AlertProductName =AlertProductNames\r\n | extend AlertProductName=tostring(AlertProductName)\r\n | project AlertProductName,RuleId\r\n | where isProductMarked(AlertProductName)!=-1 or alertText has RuleId\r\n | extend RuleId=iff(AlertProductName!= 'Azure Sentinel', AlertProductName, RuleId)\r\n | summarize counter=count() by RuleId\r\n};\r\n\r\nlet falsePositiveClassificationTable = getAmountOfIncedentForRuleId(\"FalsePositive\",\"Closed\") | extend FalsePositiveCounter=counter | project-away counter;\r\nlet undeterminedClassificationTable = getAmountOfIncedentForRuleId(\"Undetermined\",\"Closed\") | extend UndeterminedCounter=counter | project-away counter;\r\nlet benignPositiveClassificationTable = getAmountOfIncedentForRuleId(\"BenignPositive\",\"Closed\") | extend BenignPositiveCounter=counter | project-away counter;\r\nlet truePositiveClassificationTable = getAmountOfIncedentForRuleId(\"TruePositive\",\"Closed\") | extend TruePositiveCounter=counter | project-away counter;\r\nlet activeIncedentTable = getAmountOfIncedentForRuleId(\"\",\"Active\") | extend ActiveIncedentsCounter=counter | project-away counter; \r\nlet newIncedentTable = getAmountOfIncedentForRuleId(\"\",\"New\") | extend NewIncedentsCounter=counter | project-away counter;\r\nlet joinByRuleId = (T:(RuleId:string), S:(RuleId:string)){\r\n T \r\n | join kind=fullouter S on $left.RuleId == $right.RuleId\r\n | extend RuleId= iff(RuleId == '', RuleId1,RuleId)\r\n | project-away RuleId1\r\n};\r\njoinByRuleId(joinByRuleId(joinByRuleId(joinByRuleId(joinByRuleId(falsePositiveClassificationTable, undeterminedClassificationTable) , benignPositiveClassificationTable), truePositiveClassificationTable),activeIncedentTable), newIncedentTable)\r\n// Adding the Rule name to the table(we want to display name and not ID) \r\n| join kind=leftouter \r\n(SecurityAlert | project TimeGenerated, ProductName, ExtendedProperties\r\n| where TimeGenerated >= {TimeRange:start} and TimeGenerated <= {TimeRange:end}\r\n| where ProductName == 'Azure Sentinel'\r\n| extend RuleId = parsejson( tostring(todynamic(ExtendedProperties)['Analytic Rule Ids']))\r\n| mv-expand RuleId=RuleId\r\n| extend RuleId=tostring(RuleId)\r\n| extend RuleName= tostring(todynamic(ExtendedProperties)['Analytic Rule Name'])\r\n| project RuleId,RuleName\r\n| distinct RuleId,RuleName)\r\n on $left.RuleId==$right.RuleId\r\n| extend RuleName=iff(isempty(RuleName),RuleId,RuleName)\r\n| project-away RuleId1\r\n| where alertText has RuleId or isProductMarked(RuleName)!=-1 \r\n| project-away RuleId\r\n| extend TotalAlerts= iff(isempty(FalsePositiveCounter),0,FalsePositiveCounter) + \r\niff(isempty(UndeterminedCounter),0,UndeterminedCounter)+ \r\niff(isempty(BenignPositiveCounter),0,BenignPositiveCounter)+\r\niff(isempty(TruePositiveCounter),0,TruePositiveCounter) +\r\niff(isempty(ActiveIncedentsCounter),0,ActiveIncedentsCounter) +\r\niff(isempty(NewIncedentsCounter),0,NewIncedentsCounter)\r\n| where TotalAlerts>0\r\n| sort by TotalAlerts desc \r\n| project-away TotalAlerts \r\n",
"json":"#### Selected rules by MITRE ATT&CK tactics coverage\nThis chart shows each rule's MITRE ATT&CK tactics, as configured at the rule's creation.\n\nWe aspire to have full coverage. For more complete MITRE ATT&CK coverage, use Azure Sentinel templates."
"json":"### Help\r\nThe **Selected rules by MITRE ATT&CK tactics coverage** chart displays the MITRE ATT&CK tactics coverage of the selected rules.\r\n\r\nWhen creating **scheduled alert rules**, you are able to specify the MITRE ATT&CK tactics associated with each rule. These specified tactics are used to populate this chart.\r\n\r\nThis tactic specification is available only with **scheduled alert** rules, not with other types of analytics rules.\r\n",
"style":"info"
},
"conditionalVisibilities":[
{
"parameterName":"Help",
"comparison":"isEqualTo",
"value":"Yes"
},
{
"parameterName":"prop",
"comparison":"isNotEqualTo"
}
],
"name":"MITRE attack coverage text "
},
{
"type":3,
"content":{
"version":"KqlItem/1.0",
"query":"let MITRE_DEF_TABLE = datatable(Tactic:string)[\"InitialAccess\",\"Execution\",\"Persistence\",\"PrivilegeEscalation\", \"DefenseEvasion\",\"CredentialAccess\",\"Discovery\", \"LateralMovement\",\"Collection\",\"Exfiltration\",\"CommandAndControl\",\"Impact\"];\r\nlet getRuleNameIdTable = (){\r\n let alertText = strcat_array(dynamic([{AlertRuleID}]),\",\");\r\n let RuleName = strcat_array(dynamic([{RuleName}]),\",\");\r\n let rulesData = range x from 0 to array_length(split(alertText,','))-1 step 1\r\n | extend AlertRuleId= tostring(split(alertText,',')[x]),\r\n RuleName=tostring(split(RuleName,',')[x]);\r\n rulesData\r\n};\r\nlet GetAlertRuleTable = (){\r\n let proerties = dynamic([{prop}]);\r\n let TmpRuleTable = datatable (MockColumn:string)[\"Mock\"];\r\n TmpRuleTable\r\n | mv-expand SingleRuleProperties=proerties\r\n | project-away MockColumn\r\n | extend \r\n Product=iff(SingleRuleProperties.productFilter!='',SingleRuleProperties.productFilter,\"Azure Sentinel\"), \r\n RuleName=tostring(SingleRuleProperties.displayName), \r\n MITRE_Tactics=iff(SingleRuleProperties.tactics!='',SingleRuleProperties.tactics,dynamic([])),\r\n Description=SingleRuleProperties.description\r\n | extend Status= iff(SingleRuleProperties.enabled==true,'Enabled',iff(RuleName startswith 'AUTO DISABLED','Auto disabled', 'disabled'))\r\n | project-away SingleRuleProperties\r\n | join getRuleNameIdTable() on $left.RuleName==$right.RuleName\r\n | project-away RuleName1\r\n};\r\nMITRE_DEF_TABLE\r\n| join kind=leftouter (GetAlertRuleTable()\r\n| project MITRE_Tactics\r\n| mv-expand MITRE_Tactics\r\n| extend MITRE_Tactics = tostring(MITRE_Tactics)\r\n| summarize RuleAmount=count() by MITRE_Tactics) on $left.Tactic==$right.MITRE_Tactics\r\n| extend RuleAmount=iff(RuleAmount>0,RuleAmount,0)\r\n| project Tactic,RuleAmount\r\n",
"json":"#### Rules that require attention\nThis chart displays the selected rules that require attention, either because they didn't create alerts during the selected period, or because they were auto-disabled.\n\nAzure Sentinel will automatically disable rules that violate the scheduled alert rules guidelines."
},
"name":"text - 1"
},
{
"type":1,
"content":{
"json":"### Help\r\nThis chart displays rules that require attention.\r\nThese include rules that didn't create alerts during the selected time range, and auto-disabled rules. \r\nRules that are not triggered might have problems, which may result in overestimation of the detection coverage.\r\nAzure Sentinel will automatically disable rules that violate the scheduled alert rules guidelines.\r\n[Learn more](https://docs.microsoft.com/azure/sentinel/tutorial-detect-threats-custom) about scheduled alerts in Azure Sentinel.",
"style":"info"
},
"conditionalVisibilities":[
{
"parameterName":"Help",
"comparison":"isEqualTo",
"value":"Yes"
},
{
"parameterName":"prop",
"comparison":"isNotEqualTo"
}
],
"name":"text - 2"
},
{
"type":3,
"content":{
"version":"KqlItem/1.0",
"query":"let getRuleNameIdTable = (){\r\n let alertText = strcat_array(dynamic([{AlertRuleID}]),\",\");\r\n let RuleName = strcat_array(dynamic([{RuleName}]),\",\");\r\n let rulesData = range x from 0 to array_length(split(alertText,','))-1 step 1\r\n | extend AlertRuleId= tostring(split(alertText,',')[x]),\r\n RuleName=tostring(split(RuleName,',')[x]);\r\n rulesData\r\n};\r\nlet GetAlertRuleTable = (){\r\n let proerties = dynamic([{prop}]);\r\n let TmpRuleTable = datatable (MockColumn:string)[\"Mock\"];\r\n TmpRuleTable\r\n | mv-expand SingleRuleProperties=proerties\r\n | project-away MockColumn\r\n | extend \r\n Product=iff(SingleRuleProperties.productFilter!='',SingleRuleProperties.productFilter,\"Azure Sentinel\"), \r\n RuleName=tostring(SingleRuleProperties.displayName), \r\n MITRE_Tactics=iff(SingleRuleProperties.tactics!='',SingleRuleProperties.tactics,dynamic([])),\r\n Description=SingleRuleProperties.description\r\n | extend Status= iff(SingleRuleProperties.enabled==true,'Enabled',iff(RuleName startswith 'AUTO DISABLED','Auto disabled', 'Disabled'))\r\n | project-away SingleRuleProperties\r\n | join getRuleNameIdTable() on $left.RuleName==$right.RuleName\r\n | project-away RuleName1,x\r\n};\r\nlet AlertAmount = materialize( SecurityAlert\r\n| project ExtendedProperties,ProductName, ProviderName,TimeGenerated\r\n| where TimeGenerated >= {TimeRange:start} and TimeGenerated <= {TimeRange:end}\r\n| extend AnalyticRuleIdStr = replace('\\\"','',tostring(todynamic(ExtendedProperties)[\"Analytic Rule Ids\"]))\r\n| extend AnaliticRulesInAlertArray= split(substring(AnalyticRuleIdStr,1,string_size(AnalyticRuleIdStr)-2),\",\")\r\n| mv-expand SingleAnaliticRuleID=AnaliticRulesInAlertArray\r\n| extend SingleAnaliticRuleID=iff(ProductName==\"Azure Sentinel\",tostring(SingleAnaliticRuleID),ProductName)\r\n| summarize AlertAmount=count() by SingleAnaliticRuleID\r\n| extend AlertAmount=iff(AlertAmount>0,AlertAmount,0));\r\nGetAlertRuleTable()\r\n| join kind=leftouter AlertAmount on $left.AlertRuleId==$right.SingleAnaliticRuleID \r\n| project-away SingleAnaliticRuleID\r\n| extend AlertAmount=iff(AlertAmount>0 or Product!= 'Azure Sentinel',AlertAmount,0)\r\n| join kind=leftouter AlertAmount on $left.Product==$right.SingleAnaliticRuleID\r\n| extend AlertAmount=iff(Product!= 'Azure Sentinel',AlertAmount1,AlertAmount)\r\n| extend AlertAmount=iff(AlertAmount>0,AlertAmount,0)\r\n| project-away AlertAmount1\r\n| sort by AlertAmount desc\r\n| project Status,Product,RuleName,AlertAmount\r\n| where AlertAmount==0 and Status == 'Enabled' or Status==\"Auto disabled\"\r\n| extend Status=iff(Status == 'Enabled' and AlertAmount==0, \"No Alerts\", Status)\r\n| project Status,RuleName, AlertAmount\r\n",
"json":"## Total number of alerts\nSum of the alerts generated by all the selected rules over the selected time range"
},
"conditionalVisibility":{
"parameterName":"prop",
"comparison":"isNotEqualTo"
},
"name":"text Sum of the numbers of alerts generated by all the selected rules"
},
{
"type":1,
"content":{
"json":"### Help\r\nThe **Total number of alerts** chart shows the total count of the alerts generated by the selected rules over the selected time range.\r\nYou can use this chart to monitor the alert load on the SOC.\r\n",
"style":"info"
},
"conditionalVisibilities":[
{
"parameterName":"Help",
"comparison":"isEqualTo",
"value":"Yes"
},
{
"parameterName":"prop",
"comparison":"isNotEqualTo"
}
],
"name":"text alerts generated by the selected rules over the selected time"
},
{
"type":3,
"content":{
"version":"KqlItem/1.0",
"query":"let alertText = strcat_array(dynamic([{AlertRuleID}]),\",\");\r\nlet isProductMarked = (product:string) {\r\n let productText = strcat_array(dynamic([{ProductName}]),\",\");\r\n array_index_of(split(productText,'\\\"'),product)\r\n};\r\nSecurityAlert\r\n| project TimeGenerated,ProductName,ExtendedProperties\r\n| where TimeGenerated >= {TimeRange:start} and TimeGenerated <= {TimeRange:end}\r\n| extend AnalyticRuleIdStr = replace('\\\"','',tostring(todynamic(ExtendedProperties)[\"Analytic Rule Ids\"]))\r\n| extend AnaliticRulesInAlertArray= split(substring(AnalyticRuleIdStr,1,string_size(AnalyticRuleIdStr)-2),\",\")\r\n| mv-expand SingleAnaliticRuleID=AnaliticRulesInAlertArray\r\n| extend SingleAnaliticRuleID=iff(ProductName==\"Azure Sentinel\",tostring(SingleAnaliticRuleID),ProductName)\r\n| extend RuleName= iff(ProductName==\"Azure Sentinel\",tostring(todynamic(ExtendedProperties)['Analytic Rule Name']),ProductName)\r\n| where isProductMarked(ProductName)!=-1 or ProductName == \"Azure Sentinel\" and alertText has SingleAnaliticRuleID\r\n| summarize AlertAmount=count() by bin(TimeGenerated,1h)",
"json":"### Count of alerts, by rule\nThe total number of alerts generated by each rule over the selected time period "
},
"conditionalVisibility":{
"parameterName":"prop",
"comparison":"isNotEqualTo"
},
"name":"heading text alert amount created by each rules "
},
{
"type":1,
"content":{
"json":"### Help\r\nHere you can see the amount of alerts created from each rule you selected. \r\nMonitoring the amount of alert each detection is creating is essential for the SOC.\r\n\r\n",
"style":"info"
},
"customWidth":"80",
"conditionalVisibilities":[
{
"parameterName":"Help",
"comparison":"isEqualTo",
"value":"Yes"
},
{
"parameterName":"prop",
"comparison":"isNotEqualTo"
}
],
"name":"Help text alert amount created by each rules "
},
{
"type":3,
"content":{
"version":"KqlItem/1.0",
"query":"let alertText = strcat_array(dynamic([{AlertRuleID}]),\",\");\r\nlet isProductMarked = (product:string) {\r\n let productText = strcat_array(dynamic([{ProductName}]),\",\");\r\n array_index_of(split(productText,'\\\"'),product)\r\n};\r\nSecurityAlert\r\n| project TimeGenerated,ProductName,ExtendedProperties\r\n| where TimeGenerated >= {TimeRange:start} and TimeGenerated <= {TimeRange:end}\r\n| extend AlertRuleName = parsejson(tostring(todynamic(ExtendedProperties)[\"Analytic Rule Name\"]))\r\n| extend AlertRuleIDArray= parsejson(tostring(todynamic(ExtendedProperties)[\"Analytic Rule Ids\"]))\r\n| mv-expand SingleAnaliticRuleID=AlertRuleIDArray\r\n| extend SingleAnaliticRuleID=tostring(SingleAnaliticRuleID)\r\n| project SingleAnaliticRuleID,AlertRuleName, ProductName\r\n| where isProductMarked(ProductName)!=-1 or ProductName == \"Azure Sentinel\" and alertText has SingleAnaliticRuleID\r\n| extend AlertRuleIdentifier = iff(ProductName==\"Azure Sentinel\", tostring(AlertRuleName), tostring(ProductName))\r\n| summarize AlertAmount=count() by AlertRuleIdentifier",