Azure-Sentinel/Workbooks/AcscEssential8.json

3373 строки
209 KiB
JSON

{
"version": "Notebook/1.0",
"items": [
{
"type": 11,
"content": {
"version": "LinkItem/1.0",
"style": "tabs",
"links": [
{
"id": "06cee09a-8f2d-47ba-854f-303ad8ed74cc",
"cellValue": "Tab",
"linkTarget": "parameter",
"linkLabel": "General",
"subTarget": "General",
"style": "link"
},
{
"id": "f2fb78d9-8cf0-4e27-9918-c2d4c6905439",
"cellValue": "Tab",
"linkTarget": "parameter",
"linkLabel": "Application control",
"subTarget": "app control",
"style": "link"
},
{
"id": "841c8f67-62ee-4429-b57b-fc95e97f14bd",
"cellValue": "Tab",
"linkTarget": "parameter",
"linkLabel": "Patch applications",
"subTarget": "patch apps",
"style": "link"
},
{
"id": "8a4bc797-d1ee-455e-998f-ee316c4fbfea",
"cellValue": "Tab",
"linkTarget": "parameter",
"linkLabel": "Configure macro settings",
"subTarget": "macros",
"style": "link"
},
{
"id": "1c823420-79ad-4fa6-9338-377cf6d19dec",
"cellValue": "Tab",
"linkTarget": "parameter",
"linkLabel": "User application hardening",
"subTarget": "ap hardening",
"style": "link"
},
{
"id": "e3c87019-5bab-483b-a418-b6557336f7d0",
"cellValue": "Tab",
"linkTarget": "parameter",
"linkLabel": "Restrict admin privileges",
"subTarget": "admin priv",
"style": "link"
},
{
"id": "11d037f1-3bad-466b-96ad-3f045d70e6d2",
"cellValue": "Tab",
"linkTarget": "parameter",
"linkLabel": "Patch operating systems",
"subTarget": "patch OS",
"style": "link"
},
{
"id": "76711ea9-04fa-4132-8706-a2bc3c0eeca8",
"cellValue": "Tab",
"linkTarget": "parameter",
"linkLabel": "MFA",
"subTarget": "MFA",
"style": "link"
},
{
"id": "4b361307-0e82-417e-9786-a02319f9dcb4",
"cellValue": "Tab",
"linkTarget": "parameter",
"linkLabel": "Regular Backups",
"subTarget": "backups",
"style": "link"
}
]
},
"name": "links - 9"
},
{
"type": 9,
"content": {
"version": "KqlParameterItem/1.0",
"parameters": [
{
"id": "cba48574-e67b-4daa-8cb8-526fc4d7e963",
"version": "KqlParameterItem/1.0",
"name": "hideSubscription",
"type": 1,
"isRequired": true,
"isHiddenWhenLocked": true,
"criteriaData": [
{
"criteriaContext": {
"leftOperand": "Tab",
"operator": "==",
"rightValType": "static",
"rightVal": "app control",
"resultValType": "static",
"resultVal": "True"
}
},
{
"criteriaContext": {
"operator": "Default",
"resultValType": "static",
"resultVal": "False"
}
}
],
"timeContext": {
"durationMs": 86400000
}
},
{
"id": "1f7fb3a4-59af-4d6b-bc39-6d8adffcbc1b",
"version": "KqlParameterItem/1.0",
"name": "hideWorkspace",
"type": 1,
"isRequired": true,
"isHiddenWhenLocked": true,
"criteriaData": [
{
"criteriaContext": {
"leftOperand": "Tab",
"operator": "==",
"rightValType": "static",
"rightVal": "patch apps",
"resultValType": "static",
"resultVal": "True"
}
},
{
"criteriaContext": {
"leftOperand": "Tab",
"operator": "==",
"rightValType": "static",
"rightVal": "admin priv",
"resultValType": "static",
"resultVal": "True"
}
},
{
"criteriaContext": {
"leftOperand": "Tab",
"operator": "==",
"rightValType": "static",
"rightVal": "patch OS",
"resultValType": "static",
"resultVal": "True"
}
},
{
"criteriaContext": {
"leftOperand": "Tab",
"operator": "==",
"rightValType": "static",
"rightVal": "MFA",
"resultValType": "static",
"resultVal": "True"
}
},
{
"criteriaContext": {
"leftOperand": "Tab",
"operator": "==",
"rightValType": "static",
"rightVal": "backups",
"resultValType": "static",
"resultVal": "True"
}
},
{
"criteriaContext": {
"leftOperand": "Tab",
"operator": "==",
"rightValType": "static",
"rightVal": "macros",
"resultValType": "static",
"resultVal": "True"
}
},
{
"criteriaContext": {
"leftOperand": "Tab",
"operator": "==",
"rightValType": "static",
"rightVal": "ap hardening",
"resultValType": "static",
"resultVal": "True"
}
},
{
"criteriaContext": {
"leftOperand": "Tab",
"operator": "==",
"rightValType": "static",
"rightVal": "General",
"resultValType": "static",
"resultVal": "True"
}
},
{
"criteriaContext": {
"operator": "Default",
"resultValType": "static",
"resultVal": "False"
}
}
],
"timeContext": {
"durationMs": 86400000
}
}
],
"style": "pills",
"queryType": 0,
"resourceType": "microsoft.operationalinsights/workspaces"
},
"name": "parameters - 15"
},
{
"type": 9,
"content": {
"version": "KqlParameterItem/1.0",
"parameters": [
{
"id": "e6ded9a1-a83c-4762-938d-5bf8ff3d3d38",
"version": "KqlParameterItem/1.0",
"name": "Subscription",
"type": 6,
"isRequired": true,
"multiSelect": true,
"quote": "'",
"delimiter": ",",
"typeSettings": {
"additionalResourceOptions": [
"value::all"
],
"includeAll": true,
"showDefault": false
},
"defaultValue": "value::all",
"value": [
"value::all"
]
}
],
"style": "above",
"queryType": 0,
"resourceType": "microsoft.operationalinsights/workspaces"
},
"conditionalVisibility": {
"parameterName": "hideSubscription",
"comparison": "isEqualTo",
"value": "False"
},
"customWidth": "33",
"name": "parameters - 10"
},
{
"type": 9,
"content": {
"version": "KqlParameterItem/1.0",
"crossComponentResources": [
"{Subscription}"
],
"parameters": [
{
"id": "6d2d5f84-767c-4d51-82d5-6981e96bacdc",
"version": "KqlParameterItem/1.0",
"name": "Workspace",
"type": 5,
"query": "resources\r\n| where type =~ 'microsoft.operationalinsights/workspaces'\r\n| order by name asc\r\n| summarize Selected = makelist(id, 10), All = makelist(id, 1000)\r\n| mvexpand All limit 100\r\n| project value = tostring(All), label = tostring(All), selected = iff(Selected contains All, true, false)",
"crossComponentResources": [
"{Subscription}"
],
"typeSettings": {
"additionalResourceOptions": []
},
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources"
},
{
"id": "befbf593-c171-4129-b890-7e642265ed0c",
"version": "KqlParameterItem/1.0",
"name": "TimeRange",
"type": 4,
"isRequired": true,
"value": {
"durationMs": 2592000000
},
"typeSettings": {
"selectableValues": [
{
"durationMs": 300000
},
{
"durationMs": 900000
},
{
"durationMs": 1800000
},
{
"durationMs": 3600000
},
{
"durationMs": 14400000
},
{
"durationMs": 43200000
},
{
"durationMs": 86400000
},
{
"durationMs": 172800000
},
{
"durationMs": 259200000
},
{
"durationMs": 604800000
},
{
"durationMs": 1209600000
},
{
"durationMs": 2419200000
},
{
"durationMs": 2592000000
},
{
"durationMs": 5184000000
},
{
"durationMs": 7776000000
}
]
}
}
],
"style": "above",
"queryType": 1,
"resourceType": "microsoft.operationalinsights/workspaces"
},
"conditionalVisibility": {
"parameterName": "hideWorkspace",
"comparison": "isEqualTo",
"value": "False"
},
"customWidth": "50",
"name": "parameters - 8"
},
{
"type": 9,
"content": {
"version": "KqlParameterItem/1.0",
"crossComponentResources": [
"{Workspace}"
],
"parameters": [
{
"id": "e1e2cd0c-519c-44af-887b-3c44866296d0",
"version": "KqlParameterItem/1.0",
"name": "tvmExists",
"type": 1,
"isRequired": true,
"query": "datatable(Count:long)[0] | union isfuzzy = true (table(\"DeviceTvmSecureConfigurationAssessment\") | take 1 | summarize Count = count())\r\n| summarize sum(Count)\r\n| project tvmExists = iff(sum_Count > 0, \"True\", \"False\")",
"crossComponentResources": [
"{Workspace}"
],
"isHiddenWhenLocked": true,
"timeContext": {
"durationMs": 86400000
},
"queryType": 0,
"resourceType": "microsoft.operationalinsights/workspaces"
}
],
"style": "pills",
"queryType": 0,
"resourceType": "microsoft.operationalinsights/workspaces"
},
"name": "parameters - 13"
},
{
"type": 12,
"content": {
"version": "NotebookGroup/1.0",
"groupType": "editable",
"items": [
{
"type": 1,
"content": {
"json": "# Strategy 2: Patch applications"
},
"name": "text - 8"
},
{
"type": 1,
"content": {
"json": "\r\n## Health Legend\r\n#### Healthy: \r\nHealthy Virtual Machines do not have missing updates or patches for Office aplications, web browsers and local utlities.\r\n\r\n#### Unhealthy:\r\nUnhealthy Virtual Machines have missing updates or patches for Office aplications, web browsers and local utlities.\r\n\r\n#### Unknown:\r\nUnknown health for Virtual Machines indicate inability to scan machines for vulnerabilities. This can be due to a number of reasons detailed in the report such as missing, corrupted, or disabled vulnerability assessment solution. </br> This still requires investigation and remediation to ensure required controls are applied and resources are reporting Healthy state.",
"style": "success"
},
"name": "text - 10"
},
{
"type": 1,
"content": {
"json": "## Virtual Machines Health: Summary"
},
"name": "text - 8"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments\"\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| where name == \"1195afff-c881-495e-9bc5-1486211ae03f\" // \"Machines should have vulnerability findings resolved\"\r\n| extend Resource = tostring(properties.resourceDetails.Id)\r\n| extend StatusCode = iff(tostring(properties.status.code) == \"NotApplicable\", \"Unknown\", tostring(properties.status.code)) // change \"NotApplicable\" to \"Unknown\"\r\n| extend StatusDescription = tostring(properties.status.description)\r\n| project subscriptionId, resourceGroup, Resource, StatusCode, StatusDescription\r\n| join kind = fullouter (\r\n // get list of Resource ids that have subassessments related to APP vulns and that are not InSLA\r\n securityresources\r\n | where type == \"microsoft.security/assessments/subassessments\"\r\n | where subscriptionId in~ ({Subscription:subid})\r\n | extend assessmentKey = extract(\".*assessments/(.+?)/.*\",1 , id)\r\n | where assessmentKey == \"1195afff-c881-495e-9bc5-1486211ae03f\" // \"Machines should have vulnerability findings resolved\"\r\n | extend Patchable = tostring(properties.additionalData.patchable)\r\n //| where Patchable !~ \"false\" // uncomment to remove subassessments that are config only\r\n | extend SoftwareVendor = tostring(properties.additionalData.softwareVendor)\r\n | extend Category = tostring(properties.category)\r\n //| where Category !~ \"Security Policy\" // uncomment to remove subassessments that are config only\r\n // | where not(Category in~ (\"Internet Explorer\", \"Local\", \"Security Policy\", \"Windows\")) // IE is treated as part of Windows OS\r\n //| where not(Category =~ \"Update\" and SoftwareVendor in~ (\"ubuntu\", \"debian\")) // these are for OS (rather than application) patches\r\n | where not(Category in~ (\"Internet Explorer\", \"Local\", \"Security Policy\", \"Windows\", \"Update\")) // IE is treated as part of Windows OS\r\n | extend SoftwareName = tostring(properties.additionalData.softwareName)\r\n | extend SoftwareVersion = tostring(properties.additionalData.softwareVersion)\r\n | extend Description = tostring(properties.displayName)\r\n | extend Status = tostring(properties.status.code)\r\n | extend Severity = tostring(properties.status.severity)\r\n | extend Resource = tostring(properties.resourceDetails.id)\r\n | extend ResourceSource = tostring(properties.resourceDetails.source)\r\n | extend ResourceType = tolower(split(id,\"/\").[6])\r\n | extend VulnId = tostring(properties.id)\r\n | extend Cve = parse_json(properties.additionalData.cve)\r\n | extend CveCount = array_length(Cve)\r\n | extend TimeGenerated = tostring(properties.timeGenerated)\r\n | extend PublishedTime = todatetime(properties.additionalData.publishedTime)\r\n | extend Remediation = tostring(properties.remediation)\r\n | extend Impact = tostring(properties.impact)\r\n | extend Threat = tostring(properties.additionalData.threat)\r\n | distinct tenantId, subscriptionId, resourceGroup, Resource, ResourceType, ResourceSource, Category, SoftwareVendor, SoftwareName, SoftwareVersion, Description,\r\n Status, Severity, VulnId, tostring(Cve), CveCount, Patchable, TimeGenerated, PublishedTime, Remediation, Impact, Threat\r\n | mv-expand CveExpand = split (Cve, \"},\") to typeof(string)\r\n | parse CveExpand with * '\"title\":\"' singleCveTitle '\"' *\r\n | parse CveExpand with * '\"severity\":\"' singleCveSeverity '\"' * '\"exploitabilityLevel\":\"' singleCveExploitabilityLevel '\"' * '\"publishedDate\":\"' singleCvePublishedDate:datetime '\"' *\r\n | summarize OldestExploit = minif(singleCvePublishedDate, singleCveExploitabilityLevel startswith \"Exploit\"), OldestNonExploit = minif(singleCvePublishedDate, singleCveExploitabilityLevel =~ \"NoExploit\"), CVEs = tostring(make_list(singleCveTitle)) by tenantId, subscriptionId, resourceGroup, Resource, ResourceType, ResourceSource, Category, SoftwareVendor, SoftwareName, SoftwareVersion, Description, Status, Severity, VulnId, CveCount, Patchable, TimeGenerated, PublishedTime, Remediation, Impact, Threat\r\n | extend InSLA = iff(now() - 14d > PublishedTime, false, true)\r\n | extend InSLA = iff(now() - 14d > OldestNonExploit, false, InSLA)\r\n | extend InSLA = iff(now() - 48h > OldestExploit, false, InSLA)\r\n | where InSLA == false\r\n | distinct Resource\r\n | extend CategorizeAsAppAndNotInSLA = true\r\n) on Resource\r\n| extend StatusCode = iff(StatusCode =~ \"Unhealthy\" and isnull(CategorizeAsAppAndNotInSLA), \"Healthy\", StatusCode) // set \"Unhealthy\" node to \"Healthy\" if it doesn't have any App-related findings that are out of SLA\r\n| extend StatusCode = iff(StatusDescription =~ \"The recommendation is not relevant for Security Appliance\", \"Healthy\", StatusCode) // set platform-managed VMs to \"Healthy\"\r\n| summarize count=dcount(Resource) by StatusCode",
"size": 3,
"noDataMessage": "No unhealthy machines found",
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"visualization": "piechart",
"gridSettings": {
"formatters": [
{
"columnMatch": "ResourceType",
"formatter": 16,
"formatOptions": {
"showIcon": true
}
},
{
"columnMatch": "Count",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "Default",
"thresholdValue": null,
"representation": "4",
"text": "{0}{1}"
}
]
}
}
]
},
"tileSettings": {
"titleContent": {
"columnMatch": "ResourceType",
"formatter": 7,
"formatOptions": {
"linkTarget": "Resource"
}
},
"leftContent": {
"columnMatch": "count",
"formatter": 12,
"formatOptions": {
"palette": "auto"
},
"numberFormat": {
"unit": 17,
"options": {
"style": "decimal",
"maximumFractionDigits": 2,
"maximumSignificantDigits": 3
}
}
},
"showBorder": true,
"sortCriteriaField": "ResourceType",
"sortOrderField": 1,
"size": "auto"
},
"graphSettings": {
"type": 0,
"topContent": {
"columnMatch": "ResourceType",
"formatter": 1
},
"centerContent": {
"columnMatch": "count",
"formatter": 1,
"numberFormat": {
"unit": 17,
"options": {
"maximumSignificantDigits": 3,
"maximumFractionDigits": 2
}
}
}
},
"chartSettings": {
"seriesLabelSettings": [
{
"seriesName": "Healthy",
"color": "green"
},
{
"seriesName": "Unhealthy",
"color": "red"
},
{
"seriesName": "Unknown",
"color": "gray"
}
]
},
"mapSettings": {
"locInfo": "LatLong",
"sizeSettings": "count",
"sizeAggregation": "Sum",
"legendMetric": "count",
"legendAggregation": "Sum",
"itemColorSettings": {
"type": "heatmap",
"colorAggregation": "Sum",
"nodeColorField": "count",
"heatmapPalette": "greenRed"
}
}
},
"customWidth": "40",
"conditionalVisibility": {
"parameterName": "Tab",
"comparison": "isEqualTo",
"value": "patch apps"
},
"name": "Overview-VMCount"
},
{
"type": 1,
"content": {
"json": "Scroll to the bottom of the page to view the reason why specific VMs are reporting an 'Unknown' state."
},
"name": "text - 7"
},
{
"type": 1,
"content": {
"json": "## Virtual Machines Health: Details"
},
"name": "text - 9"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments/subassessments\"\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| extend assessmentKey = extract(\".*assessments/(.+?)/.*\",1 , id)\r\n| where assessmentKey == \"1195afff-c881-495e-9bc5-1486211ae03f\" // \"Machines should have vulnerability findings resolved\"\r\n| extend Patchable = tostring(properties.additionalData.patchable)\r\n//| where Patchable !~ \"false\" // uncomment to remove subassessments that are config only\r\n| extend SoftwareVendor = tostring(properties.additionalData.softwareVendor)\r\n| extend Category = tostring(properties.category)\r\n//| where Category !~ \"Security Policy\" // uncomment to remove subassessments that are config only\r\n// | where not(Category in~ (\"Internet Explorer\", \"Local\", \"Security Policy\", \"Windows\")) // IE is treated as part of Windows OS\r\n// | where not(Category =~ \"Update\" and SoftwareVendor in~ (\"ubuntu\", \"debian\")) // these are for OS (rather than application) patches\r\n| where not(Category in~ (\"Internet Explorer\", \"Local\", \"Security Policy\", \"Windows\", \"Update\")) // IE is treated as part of Windows OS\r\n| extend SoftwareName = tostring(properties.additionalData.softwareName)\r\n| extend SoftwareVersion = tostring(properties.additionalData.softwareVersion)\r\n| extend Description = tostring(properties.displayName)\r\n| extend Status = tostring(properties.status.code)\r\n| extend Severity = tostring(properties.status.severity)\r\n| extend Resource = tostring(properties.resourceDetails.id)\r\n| extend ResourceSource = tostring(properties.resourceDetails.source)\r\n| extend ResourceType = tolower(split(id,\"/\").[6])\r\n| extend VulnId = tostring(properties.id)\r\n| extend Cve = parse_json(properties.additionalData.cve)\r\n| extend CveCount = array_length(Cve)\r\n| extend TimeGenerated = tostring(properties.timeGenerated)\r\n| extend PublishedTime = todatetime(properties.additionalData.publishedTime)\r\n| extend Remediation = tostring(properties.remediation)\r\n| extend Impact = tostring(properties.impact)\r\n| extend Threat = tostring(properties.additionalData.threat)\r\n| distinct tenantId, subscriptionId, resourceGroup, Resource, ResourceType, ResourceSource, Category, SoftwareVendor, SoftwareName, SoftwareVersion, Description,\r\n Status, Severity, VulnId, tostring(Cve), CveCount, Patchable, TimeGenerated, PublishedTime, Remediation, Impact, Threat\r\n| mv-expand CveExpand = split (Cve, \"},\") to typeof(string)\r\n| parse CveExpand with * '\"title\":\"' singleCveTitle '\"' *\r\n| parse CveExpand with * '\"severity\":\"' singleCveSeverity '\"' * '\"exploitabilityLevel\":\"' singleCveExploitabilityLevel '\"' * '\"publishedDate\":\"' singleCvePublishedDate:datetime '\"' *\r\n| summarize OldestExploit = minif(singleCvePublishedDate, singleCveExploitabilityLevel startswith \"Exploit\"), OldestNonExploit = minif(singleCvePublishedDate, singleCveExploitabilityLevel =~ \"NoExploit\"), CVEs = tostring(make_list(singleCveTitle)) by tenantId, subscriptionId, resourceGroup, Resource, ResourceType, ResourceSource, Category, SoftwareVendor, SoftwareName, SoftwareVersion, Description, Status, Severity, VulnId, CveCount, Patchable, TimeGenerated, PublishedTime, Remediation, Impact, Threat\r\n| extend InSLA = iff(now() - 14d > PublishedTime, false, true)\r\n| extend InSLA = iff(now() - 14d > OldestNonExploit, false, InSLA)\r\n| extend InSLA = iff(now() - 48h > OldestExploit, false, InSLA)\r\n| where InSLA == false\r\n| summarize Total = count(), sevH = countif(Severity == \"High\"), sevM = countif(Severity == \"Medium\"), sevL = countif(Severity == \"Low\"), CVEcount = sum(CveCount) by subscriptionId, resourceGroup, Resource",
"size": 0,
"title": "Overview | Select one or more machines to view the list of vulnerabilities",
"exportMultipleValues": true,
"exportedParameters": [
{
"fieldName": "Resource",
"parameterName": "selectedServer",
"parameterType": 5
}
],
"showExportToExcel": true,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"gridSettings": {
"formatters": [
{
"columnMatch": "$gen_group",
"formatter": 15,
"formatOptions": {
"linkColumn": "Resource",
"linkTarget": "Resource",
"showIcon": true,
"customColumnWidthSetting": "20%"
}
},
{
"columnMatch": "subscriptionId",
"formatter": 5
},
{
"columnMatch": "resourceGroup",
"formatter": 5
},
{
"columnMatch": "Total",
"formatter": 1,
"formatOptions": {
"customColumnWidthSetting": "10ch"
}
},
{
"columnMatch": "sevH",
"formatter": 4,
"formatOptions": {
"palette": "redBright",
"customColumnWidthSetting": "12ch"
}
},
{
"columnMatch": "sevM",
"formatter": 4,
"formatOptions": {
"palette": "yellow",
"customColumnWidthSetting": "13ch"
}
},
{
"columnMatch": "sevL",
"formatter": 4,
"formatOptions": {
"palette": "blueDark",
"customColumnWidthSetting": "10ch"
}
},
{
"columnMatch": "CVEcount",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "Default",
"thresholdValue": null,
"representation": "4",
"text": "{0}{1}"
}
],
"customColumnWidthSetting": "10ch"
}
}
],
"filter": true,
"hierarchySettings": {
"treeType": 1,
"groupBy": [
"subscriptionId",
"resourceGroup"
],
"expandTopLevel": true
},
"labelSettings": [
{
"columnId": "subscriptionId",
"label": "Subscription"
},
{
"columnId": "resourceGroup",
"label": "Resource Group"
},
{
"columnId": "sevH",
"label": "High"
},
{
"columnId": "sevM",
"label": "Medium"
},
{
"columnId": "sevL",
"label": "Low"
},
{
"columnId": "CVEcount",
"label": "CVEs"
}
]
}
},
"customWidth": "75",
"name": "query - 7"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments/subassessments\"\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| extend assessmentKey = extract(\".*assessments/(.+?)/.*\",1 , id)\r\n| where assessmentKey == \"1195afff-c881-495e-9bc5-1486211ae03f\" // \"Machines should have vulnerability findings resolved\"\r\n| extend Patchable = tostring(properties.additionalData.patchable)\r\n//| where Patchable !~ \"false\" // uncomment to remove subassessments that are config only\r\n| extend SoftwareVendor = tostring(properties.additionalData.softwareVendor)\r\n| extend Category = tostring(properties.category)\r\n//| where Category !~ \"Security Policy\" // uncomment to remove subassessments that are config only\r\n// | where not(Category in~ (\"Internet Explorer\", \"Local\", \"Security Policy\", \"Windows\")) // IE is treated as part of Windows OS\r\n// | where not(Category =~ \"Update\" and SoftwareVendor in~ (\"ubuntu\", \"debian\")) // these are for OS (rather than application) patches\r\n| where not(Category in~ (\"Internet Explorer\", \"Local\", \"Security Policy\", \"Windows\", \"Update\")) // IE is treated as part of Windows OS\r\n| extend SoftwareName = tostring(properties.additionalData.softwareName)\r\n| extend SoftwareVersion = tostring(properties.additionalData.softwareVersion)\r\n| extend Description = tostring(properties.displayName)\r\n| extend Status = tostring(properties.status.code)\r\n| extend Severity = tostring(properties.status.severity)\r\n| extend Resource = tostring(properties.resourceDetails.id)\r\n//| where '{selectedServer}' == 'All' or Resource == '{selectedServer}'\r\n| where Resource in~ ({selectedServer})\r\n| extend ResourceSource = tostring(properties.resourceDetails.source)\r\n| extend ResourceType = tolower(split(id,\"/\").[6])\r\n| extend VulnId = tostring(properties.id)\r\n| extend Cve = parse_json(properties.additionalData.cve)\r\n| extend CveCount = array_length(Cve)\r\n| extend TimeGenerated = tostring(properties.timeGenerated)\r\n| extend PublishedTime = todatetime(properties.additionalData.publishedTime)\r\n| extend Remediation = tostring(properties.remediation)\r\n| extend Impact = tostring(properties.impact)\r\n| extend Threat = tostring(properties.additionalData.threat)\r\n| distinct tenantId, subscriptionId, resourceGroup, Resource, ResourceType, ResourceSource, Category, SoftwareVendor, SoftwareName, SoftwareVersion, Description,\r\n Status, Severity, VulnId, tostring(Cve), CveCount, Patchable, TimeGenerated, PublishedTime, Remediation, Impact, Threat\r\n| mv-expand CveExpand = split (Cve, \"},\") to typeof(string)\r\n| parse CveExpand with * '\"title\":\"' singleCveTitle '\"' *\r\n| parse CveExpand with * '\"severity\":\"' singleCveSeverity '\"' * '\"exploitabilityLevel\":\"' singleCveExploitabilityLevel '\"' * '\"publishedDate\":\"' singleCvePublishedDate:datetime '\"' *\r\n| summarize OldestExploit = minif(singleCvePublishedDate, singleCveExploitabilityLevel startswith \"Exploit\"), OldestNonExploit = minif(singleCvePublishedDate, singleCveExploitabilityLevel =~ \"NoExploit\"), CVEs = tostring(make_list(singleCveTitle)) by tenantId, subscriptionId, resourceGroup, Resource, ResourceType, ResourceSource, Category, SoftwareVendor, SoftwareName, SoftwareVersion, Description, Status, Severity, VulnId, CveCount, Patchable, TimeGenerated, PublishedTime, Remediation, Impact, Threat\r\n| extend InSLA = iff(now() - 14d > PublishedTime, false, true)\r\n| extend InSLA = iff(now() - 14d > OldestNonExploit, false, InSLA)\r\n| extend InSLA = iff(now() - 48h > OldestExploit, false, InSLA)\r\n| where InSLA == false",
"size": 0,
"title": "Vulnerability details of selected machines",
"noDataMessage": "Please select one or more servers to view vulnerability details",
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"gridSettings": {
"formatters": [
{
"columnMatch": "tenantId",
"formatter": 5
},
{
"columnMatch": "subscriptionId",
"formatter": 15,
"formatOptions": {
"linkTarget": null,
"showIcon": true
}
},
{
"columnMatch": "resourceGroup",
"formatter": 14,
"formatOptions": {
"linkTarget": null,
"showIcon": true
}
},
{
"columnMatch": "Status",
"formatter": 5
},
{
"columnMatch": "InSLA",
"formatter": 5
}
],
"labelSettings": [
{
"columnId": "subscriptionId",
"label": "Subscription"
},
{
"columnId": "resourceGroup",
"label": "Resource Group"
}
]
}
},
"conditionalVisibility": {
"parameterName": "selectedServer",
"comparison": "isNotEqualTo"
},
"name": "query - 12"
},
{
"type": 1,
"content": {
"json": "## Supporting information: Cause of Virtual Machines in 'Unknown' state"
},
"name": "text - 6"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments\"\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| where name == \"1195afff-c881-495e-9bc5-1486211ae03f\" // \"Machines should have vulnerability findings resolved\"\r\n| where tostring(properties.status.code) =~ \"NotApplicable\"\r\n| extend StatusDescription = tostring(properties.status.description)\r\n| where StatusDescription !~ \"The recommendation is not relevant for Security Appliance\"\r\n| extend Resource = tostring(properties.resourceDetails.Id)\r\n| project subscriptionId, Resource, resourceGroup, StatusDescription",
"size": 0,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"gridSettings": {
"formatters": [
{
"columnMatch": "$gen_group",
"formatter": 15,
"formatOptions": {
"linkTarget": null,
"showIcon": true,
"customColumnWidthSetting": "20%"
}
},
{
"columnMatch": "subscriptionId",
"formatter": 5
},
{
"columnMatch": "resourceGroup",
"formatter": 5
},
{
"columnMatch": "StatusDescription",
"formatter": 0,
"formatOptions": {
"customColumnWidthSetting": "60%"
}
}
],
"hierarchySettings": {
"treeType": 1,
"groupBy": [
"subscriptionId",
"resourceGroup"
],
"expandTopLevel": true
},
"sortBy": [
{
"itemKey": "StatusDescription",
"sortOrder": 2
}
],
"labelSettings": [
{
"columnId": "subscriptionId",
"label": "Subscription"
},
{
"columnId": "resourceGroup",
"label": "Resource Group"
},
{
"columnId": "StatusDescription",
"label": "Status Description"
}
]
},
"sortBy": [
{
"itemKey": "StatusDescription",
"sortOrder": 2
}
]
},
"name": "query - 5"
}
]
},
"conditionalVisibility": {
"parameterName": "Tab",
"comparison": "isEqualTo",
"value": "patch apps"
},
"name": "group - 9"
},
{
"type": 12,
"content": {
"version": "NotebookGroup/1.0",
"groupType": "editable",
"items": [
{
"type": 1,
"content": {
"json": "# Strategy 6: Patch Operating Systems"
},
"name": "text - 8"
},
{
"type": 1,
"content": {
"json": "## Health Legend \r\n#### Healthy:\r\nHealthy Virtual Machines do not have missing system updates or OS level vulnerabilities.\r\n\r\n#### Unhealthy:\r\nUnhealthy Virtual Machines have missing system updates or OS level vulnerabilities.\r\n\r\n#### Unknown:\r\nUnknown health for Virtual Machines indicate inability to scan machines for vulnerabilities. This can be due to a number of reasons detailed in the report such as missing, corrupted, or disabled vulnerability assessment solution. \r\n\r\nThis still requires investigation and remediation to ensure required controls are applied and resources are reporting Healthy state.\r\n",
"style": "success"
},
"name": "text - 10"
},
{
"type": 1,
"content": {
"json": "## Virtual Machines Health: Summary"
},
"name": "text - 8"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments\"\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| where name == \"1195afff-c881-495e-9bc5-1486211ae03f\" // \"Machines should have vulnerability findings resolved\"\r\n| extend Resource = tostring(properties.resourceDetails.Id)\r\n| extend StatusCode = iff(tostring(properties.status.code) == \"NotApplicable\", \"Unknown\", tostring(properties.status.code)) // change \"NotApplicable\" to \"Unknown\"\r\n| extend StatusDescription = tostring(properties.status.description)\r\n| project subscriptionId, resourceGroup, Resource, StatusCode, StatusDescription\r\n| join kind = fullouter (\r\n // get list of Resource ids that have subassessments related to OS vulns and that are not InSLA\r\n securityresources\r\n | where type == \"microsoft.security/assessments/subassessments\"\r\n | where subscriptionId in~ ({Subscription:subid})\r\n | extend assessmentKey = extract(\".*assessments/(.+?)/.*\",1 , id)\r\n | where assessmentKey == \"1195afff-c881-495e-9bc5-1486211ae03f\" // \"Machines should have vulnerability findings resolved\"\r\n | extend Patchable = tostring(properties.additionalData.patchable)\r\n //| where Patchable !~ \"false\" // uncomment to remove subassessments that are config only\r\n | extend SoftwareVendor = tostring(properties.additionalData.softwareVendor)\r\n | extend Category = tostring(properties.category)\r\n //| where Category !~ \"Security Policy\" // uncomment to remove subassessments that are config only\r\n// | where (Category in~ (\"Internet Explorer\", \"Local\", \"Security Policy\", \"Windows\")) or (Category =~ \"Update\" and SoftwareVendor in~ (\"ubuntu\", \"debian\")) // these are for OS (rather than application) patches. IE is treated as part of Windows OS\r\n | where (Category in~ (\"Internet Explorer\", \"Local\", \"Security Policy\", \"Windows\", \"Update\")) // these are for OS (rather than application) patches. IE is treated as part of Windows OS\r\n | extend SoftwareName = tostring(properties.additionalData.softwareName)\r\n | extend SoftwareVersion = tostring(properties.additionalData.softwareVersion)\r\n | extend Description = tostring(properties.displayName)\r\n | extend Status = tostring(properties.status.code)\r\n | extend Severity = tostring(properties.status.severity)\r\n | extend Resource = tostring(properties.resourceDetails.id)\r\n | extend ResourceSource = tostring(properties.resourceDetails.source)\r\n | extend ResourceType = tolower(split(id,\"/\").[6])\r\n | extend VulnId = tostring(properties.id)\r\n | extend Cve = parse_json(properties.additionalData.cve)\r\n | extend CveCount = array_length(Cve)\r\n | extend TimeGenerated = tostring(properties.timeGenerated)\r\n | extend PublishedTime = todatetime(properties.additionalData.publishedTime)\r\n | extend Remediation = tostring(properties.remediation)\r\n | extend Impact = tostring(properties.impact)\r\n | extend Threat = tostring(properties.additionalData.threat)\r\n | distinct tenantId, subscriptionId, resourceGroup, Resource, ResourceType, ResourceSource, Category, SoftwareVendor, SoftwareName, SoftwareVersion, Description,\r\n Status, Severity, VulnId, tostring(Cve), CveCount, Patchable, TimeGenerated, PublishedTime, Remediation, Impact, Threat\r\n | mv-expand CveExpand = split (Cve, \"},\") to typeof(string)\r\n | parse CveExpand with * '\"title\":\"' singleCveTitle '\"' *\r\n | parse CveExpand with * '\"severity\":\"' singleCveSeverity '\"' * '\"exploitabilityLevel\":\"' singleCveExploitabilityLevel '\"' * '\"publishedDate\":\"' singleCvePublishedDate:datetime '\"' *\r\n | summarize OldestExploit = minif(singleCvePublishedDate, singleCveExploitabilityLevel startswith \"Exploit\"), OldestNonExploit = minif(singleCvePublishedDate, singleCveExploitabilityLevel =~ \"NoExploit\"), CVEs = tostring(make_list(singleCveTitle)) by tenantId, subscriptionId, resourceGroup, Resource, ResourceType, ResourceSource, Category, SoftwareVendor, SoftwareName, SoftwareVersion, Description, Status, Severity, VulnId, CveCount, Patchable, TimeGenerated, PublishedTime, Remediation, Impact, Threat\r\n | extend InSLA = iff(now() - 14d > PublishedTime, false, true)\r\n | extend InSLA = iff(now() - 14d > OldestNonExploit, false, InSLA)\r\n | extend InSLA = iff(now() - 48h > OldestExploit, false, InSLA)\r\n | where InSLA == false\r\n | distinct Resource\r\n | extend CategorizeAsOsAndNotInSLA = true\r\n) on Resource\r\n| extend StatusCode = iff(StatusCode =~ \"Unhealthy\" and isnull(CategorizeAsOsAndNotInSLA), \"Healthy\", StatusCode) // set \"Unhealthy\" node to \"Healthy\" if it doesn't have any OS-related findings that are out of SLA\r\n| extend StatusCode = iff(StatusDescription =~ \"The recommendation is not relevant for Security Appliance\", \"Healthy\", StatusCode) // set platform-managed VMs to \"Healthy\"\r\n| summarize count=dcount(Resource) by StatusCode",
"size": 3,
"noDataMessage": "No unhealthy machines found",
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"visualization": "piechart",
"gridSettings": {
"formatters": [
{
"columnMatch": "ResourceType",
"formatter": 16,
"formatOptions": {
"showIcon": true
}
},
{
"columnMatch": "Count",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "Default",
"thresholdValue": null,
"representation": "4",
"text": "{0}{1}"
}
]
}
}
]
},
"tileSettings": {
"titleContent": {
"columnMatch": "ResourceType",
"formatter": 7,
"formatOptions": {
"linkTarget": "Resource"
}
},
"leftContent": {
"columnMatch": "count",
"formatter": 12,
"formatOptions": {
"palette": "auto"
},
"numberFormat": {
"unit": 17,
"options": {
"style": "decimal",
"maximumFractionDigits": 2,
"maximumSignificantDigits": 3
}
}
},
"showBorder": true,
"sortCriteriaField": "ResourceType",
"sortOrderField": 1,
"size": "auto"
},
"graphSettings": {
"type": 0,
"topContent": {
"columnMatch": "ResourceType",
"formatter": 1
},
"centerContent": {
"columnMatch": "count",
"formatter": 1,
"numberFormat": {
"unit": 17,
"options": {
"maximumSignificantDigits": 3,
"maximumFractionDigits": 2
}
}
}
},
"chartSettings": {
"seriesLabelSettings": [
{
"seriesName": "Healthy",
"color": "green"
},
{
"seriesName": "Unhealthy",
"color": "red"
},
{
"seriesName": "Unknown",
"color": "gray"
}
]
},
"mapSettings": {
"locInfo": "LatLong",
"sizeSettings": "count",
"sizeAggregation": "Sum",
"legendMetric": "count",
"legendAggregation": "Sum",
"itemColorSettings": {
"type": "heatmap",
"colorAggregation": "Sum",
"nodeColorField": "count",
"heatmapPalette": "greenRed"
}
}
},
"customWidth": "40",
"conditionalVisibility": {
"parameterName": "Tab",
"comparison": "isEqualTo",
"value": "patch OS"
},
"name": "Overview-VMCount"
},
{
"type": 1,
"content": {
"json": "Scroll to the bottom of the page to view the reason why specific VMs are reporting an 'Unknown' state."
},
"name": "text - 7"
},
{
"type": 1,
"content": {
"json": "## Virtual Machines Health: Details"
},
"name": "text - 9"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments/subassessments\"\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| extend assessmentKey = extract(\".*assessments/(.+?)/.*\",1 , id)\r\n| where assessmentKey == \"1195afff-c881-495e-9bc5-1486211ae03f\" // \"Machines should have vulnerability findings resolved\"\r\n| extend Patchable = tostring(properties.additionalData.patchable)\r\n//| where Patchable !~ \"false\" // uncomment to remove subassessments that are config only\r\n| extend SoftwareVendor = tostring(properties.additionalData.softwareVendor)\r\n| extend Category = tostring(properties.category)\r\n//| where Category !~ \"Security Policy\" // uncomment to remove subassessments that are config only\r\n// | where (Category in~ (\"Internet Explorer\", \"Local\", \"Security Policy\", \"Windows\")) or (Category =~ \"Update\" and SoftwareVendor in~ (\"ubuntu\", \"debian\")) // these are for OS (rather than application) patches. IE is treated as part of Windows OS\r\n| where (Category in~ (\"Internet Explorer\", \"Local\", \"Security Policy\", \"Windows\", \"Update\")) // these are for OS (rather than application) patches. IE is treated as part of Windows OS\r\n| extend SoftwareName = tostring(properties.additionalData.softwareName)\r\n| extend SoftwareVersion = tostring(properties.additionalData.softwareVersion)\r\n| extend Description = tostring(properties.displayName)\r\n| extend Status = tostring(properties.status.code)\r\n| extend Severity = tostring(properties.status.severity)\r\n| extend Resource = tostring(properties.resourceDetails.id)\r\n| extend ResourceSource = tostring(properties.resourceDetails.source)\r\n| extend ResourceType = tolower(split(id,\"/\").[6])\r\n| extend VulnId = tostring(properties.id)\r\n| extend Cve = parse_json(properties.additionalData.cve)\r\n| extend CveCount = array_length(Cve)\r\n| extend TimeGenerated = tostring(properties.timeGenerated)\r\n| extend PublishedTime = todatetime(properties.additionalData.publishedTime)\r\n| extend Remediation = tostring(properties.remediation)\r\n| extend Impact = tostring(properties.impact)\r\n| extend Threat = tostring(properties.additionalData.threat)\r\n| distinct tenantId, subscriptionId, resourceGroup, Resource, ResourceType, ResourceSource, Category, SoftwareVendor, SoftwareName, SoftwareVersion, Description,\r\n Status, Severity, VulnId, tostring(Cve), CveCount, Patchable, TimeGenerated, PublishedTime, Remediation, Impact, Threat\r\n| mv-expand CveExpand = split (Cve, \"},\") to typeof(string)\r\n| parse CveExpand with * '\"title\":\"' singleCveTitle '\"' *\r\n| parse CveExpand with * '\"severity\":\"' singleCveSeverity '\"' * '\"exploitabilityLevel\":\"' singleCveExploitabilityLevel '\"' * '\"publishedDate\":\"' singleCvePublishedDate:datetime '\"' *\r\n| summarize OldestExploit = minif(singleCvePublishedDate, singleCveExploitabilityLevel startswith \"Exploit\"), OldestNonExploit = minif(singleCvePublishedDate, singleCveExploitabilityLevel =~ \"NoExploit\"), CVEs = tostring(make_list(singleCveTitle)) by tenantId, subscriptionId, resourceGroup, Resource, ResourceType, ResourceSource, Category, SoftwareVendor, SoftwareName, SoftwareVersion, Description, Status, Severity, VulnId, CveCount, Patchable, TimeGenerated, PublishedTime, Remediation, Impact, Threat\r\n| extend InSLA = iff(now() - 14d > PublishedTime, false, true)\r\n| extend InSLA = iff(now() - 14d > OldestNonExploit, false, InSLA)\r\n| extend InSLA = iff(now() - 48h > OldestExploit, false, InSLA)\r\n| where InSLA == false\r\n| summarize Total = count(), sevH = countif(Severity == \"High\"), sevM = countif(Severity == \"Medium\"), sevL = countif(Severity == \"Low\"), CVEcount = sum(CveCount) by subscriptionId, resourceGroup, Resource",
"size": 0,
"title": "Overview | Select one or more machines to view the list of vulnerabilities",
"exportMultipleValues": true,
"exportedParameters": [
{
"fieldName": "Resource",
"parameterName": "selectedServer",
"parameterType": 5
}
],
"showExportToExcel": true,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"gridSettings": {
"formatters": [
{
"columnMatch": "$gen_group",
"formatter": 15,
"formatOptions": {
"linkTarget": null,
"showIcon": true,
"customColumnWidthSetting": "20%"
}
},
{
"columnMatch": "subscriptionId",
"formatter": 5
},
{
"columnMatch": "resourceGroup",
"formatter": 5
},
{
"columnMatch": "Total",
"formatter": 1,
"formatOptions": {
"customColumnWidthSetting": "10ch"
}
},
{
"columnMatch": "sevH",
"formatter": 4,
"formatOptions": {
"palette": "redBright",
"customColumnWidthSetting": "12ch"
}
},
{
"columnMatch": "sevM",
"formatter": 4,
"formatOptions": {
"palette": "yellow",
"customColumnWidthSetting": "13ch"
}
},
{
"columnMatch": "sevL",
"formatter": 4,
"formatOptions": {
"palette": "blueDark",
"customColumnWidthSetting": "10ch"
}
},
{
"columnMatch": "CVEcount",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "Default",
"thresholdValue": null,
"representation": "4",
"text": "{0}{1}"
}
]
}
}
],
"filter": true,
"hierarchySettings": {
"treeType": 1,
"groupBy": [
"subscriptionId",
"resourceGroup"
],
"expandTopLevel": true
},
"labelSettings": [
{
"columnId": "subscriptionId",
"label": "Subscription"
},
{
"columnId": "resourceGroup",
"label": "Resource Group"
},
{
"columnId": "sevH",
"label": "High"
},
{
"columnId": "sevM",
"label": "Medium"
},
{
"columnId": "sevL",
"label": "Low"
},
{
"columnId": "CVEcount",
"label": "CVEs"
}
]
}
},
"name": "query - 7"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments/subassessments\"\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| extend assessmentKey = extract(\".*assessments/(.+?)/.*\",1 , id)\r\n| where assessmentKey == \"1195afff-c881-495e-9bc5-1486211ae03f\" // \"Machines should have vulnerability findings resolved\"\r\n| extend Patchable = tostring(properties.additionalData.patchable)\r\n//| where Patchable !~ \"false\" // uncomment to remove subassessments that are config only\r\n| extend SoftwareVendor = tostring(properties.additionalData.softwareVendor)\r\n| extend Category = tostring(properties.category)\r\n//| where Category !~ \"Security Policy\" // uncomment to remove subassessments that are config only\r\n// | where (Category in~ (\"Internet Explorer\", \"Local\", \"Security Policy\", \"Windows\")) or (Category =~ \"Update\" and SoftwareVendor in~ (\"ubuntu\", \"debian\")) // these are for OS (rather than application) patches. IE is treated as part of Windows OS\r\n| where (Category in~ (\"Internet Explorer\", \"Local\", \"Security Policy\", \"Windows\", \"Update\")) // these are for OS (rather than application) patches. IE is treated as part of Windows OS\r\n| extend SoftwareName = tostring(properties.additionalData.softwareName)\r\n| extend SoftwareVersion = tostring(properties.additionalData.softwareVersion)\r\n| extend Description = tostring(properties.displayName)\r\n| extend Status = tostring(properties.status.code)\r\n| extend Severity = tostring(properties.status.severity)\r\n| extend Resource = tostring(properties.resourceDetails.id)\r\n//| where '{selectedServer}' == 'All' or Resource == '{selectedServer}'\r\n| where Resource in~ ({selectedServer})\r\n| extend ResourceSource = tostring(properties.resourceDetails.source)\r\n| extend ResourceType = tolower(split(id,\"/\").[6])\r\n| extend VulnId = tostring(properties.id)\r\n| extend Cve = parse_json(properties.additionalData.cve)\r\n| extend CveCount = array_length(Cve)\r\n| extend TimeGenerated = tostring(properties.timeGenerated)\r\n| extend PublishedTime = todatetime(properties.additionalData.publishedTime)\r\n| extend Remediation = tostring(properties.remediation)\r\n| extend Impact = tostring(properties.impact)\r\n| extend Threat = tostring(properties.additionalData.threat)\r\n| distinct tenantId, subscriptionId, resourceGroup, Resource, ResourceType, ResourceSource, Category, SoftwareVendor, SoftwareName, SoftwareVersion, Description,\r\n Status, Severity, VulnId, tostring(Cve), CveCount, Patchable, TimeGenerated, PublishedTime, Remediation, Impact, Threat\r\n| mv-expand CveExpand = split (Cve, \"},\") to typeof(string)\r\n| parse CveExpand with * '\"title\":\"' singleCveTitle '\"' *\r\n| parse CveExpand with * '\"severity\":\"' singleCveSeverity '\"' * '\"exploitabilityLevel\":\"' singleCveExploitabilityLevel '\"' * '\"publishedDate\":\"' singleCvePublishedDate:datetime '\"' *\r\n| summarize OldestExploit = minif(singleCvePublishedDate, singleCveExploitabilityLevel startswith \"Exploit\"), OldestNonExploit = minif(singleCvePublishedDate, singleCveExploitabilityLevel =~ \"NoExploit\"), CVEs = tostring(make_list(singleCveTitle)) by tenantId, subscriptionId, resourceGroup, Resource, ResourceType, ResourceSource, Category, SoftwareVendor, SoftwareName, SoftwareVersion, Description, Status, Severity, VulnId, CveCount, Patchable, TimeGenerated, PublishedTime, Remediation, Impact, Threat\r\n| extend InSLA = iff(now() - 14d > PublishedTime, false, true)\r\n| extend InSLA = iff(now() - 14d > OldestNonExploit, false, InSLA)\r\n| extend InSLA = iff(now() - 48h > OldestExploit, false, InSLA)\r\n| where InSLA == false",
"size": 0,
"title": "Vulnerability details of selected machines",
"noDataMessage": "Please select one or more servers to view vulnerability details",
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"gridSettings": {
"formatters": [
{
"columnMatch": "tenantId",
"formatter": 5
},
{
"columnMatch": "InSLA",
"formatter": 5
}
],
"labelSettings": [
{
"columnId": "subscriptionId",
"label": "Subscription"
}
]
}
},
"conditionalVisibility": {
"parameterName": "selectedServer",
"comparison": "isNotEqualTo"
},
"name": "query - 12"
},
{
"type": 1,
"content": {
"json": "## Supporting information: Cause of Virtual Machines in 'Unknown' state"
},
"name": "text - 5"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments\"\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| where name == \"1195afff-c881-495e-9bc5-1486211ae03f\" // \"Machines should have vulnerability findings resolved\"\r\n| where tostring(properties.status.code) =~ \"NotApplicable\"\r\n| extend StatusDescription = tostring(properties.status.description)\r\n| where StatusDescription !~ \"The recommendation is not relevant for Security Appliance\" // filter out resources that are not applicable\r\n| extend Resource = tostring(properties.resourceDetails.Id)\r\n| project subscriptionId, Resource, resourceGroup, StatusDescription",
"size": 0,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"gridSettings": {
"formatters": [
{
"columnMatch": "$gen_group",
"formatter": 15,
"formatOptions": {
"linkTarget": null,
"showIcon": true,
"customColumnWidthSetting": "20%"
}
},
{
"columnMatch": "subscriptionId",
"formatter": 5
},
{
"columnMatch": "resourceGroup",
"formatter": 5
},
{
"columnMatch": "StatusDescription",
"formatter": 0,
"formatOptions": {
"customColumnWidthSetting": "60%"
}
}
],
"hierarchySettings": {
"treeType": 1,
"groupBy": [
"subscriptionId",
"resourceGroup"
],
"expandTopLevel": true
},
"labelSettings": [
{
"columnId": "subscriptionId",
"label": "Subscription"
},
{
"columnId": "resourceGroup",
"label": "Resource Group"
},
{
"columnId": "StatusDescription",
"label": "Status Description"
}
]
}
},
"name": "query - 6"
}
]
},
"conditionalVisibility": {
"parameterName": "Tab",
"comparison": "isEqualTo",
"value": "patch OS"
},
"name": "group - 9 - Copy"
},
{
"type": 12,
"content": {
"version": "NotebookGroup/1.0",
"groupType": "editable",
"items": [
{
"type": 1,
"content": {
"json": "# Strategy 1: Application Control"
},
"name": "text - 0"
},
{
"type": 1,
"content": {
"json": "## Coming soon - pending product update",
"style": "success"
},
"name": "text - 1"
}
]
},
"conditionalVisibility": {
"parameterName": "Tab",
"comparison": "isEqualTo",
"value": "app control"
},
"name": "group - 5"
},
{
"type": 12,
"content": {
"version": "NotebookGroup/1.0",
"groupType": "editable",
"items": [
{
"type": 1,
"content": {
"json": "# Strategy 5: Restrict Administrative Privileges"
},
"name": "text - 0"
},
{
"type": 1,
"content": {
"json": "## Health Legend (for Subscriptions)\r\n#### Healthy:\r\nHealthy Subscriptions are compliant with **all of the following** policies:\r\n- A maximum of 3 owners should be designated for subscriptions\r\n- Blocked accounts with owner permissions on Azure resources should be removed\r\n- Blocked accounts with read and write permissions on Azure resources should be removed\r\n- Guest accounts with owner permissions on Azure resources should be removed\r\n- Guest accounts with write permissions on Azure resources should be removed\r\n\r\n#### Unhealthy:\r\nUnhealthy Subscriptions are not compliant with **at least one** of the above policies. If the health state of at least one control is Unhealthy, the subscription will reported as Unhealthy.\r\n\r\n#### Unknown:\r\nUnknown health for Subscriptions indicate inability to retrieve compliance data for the respective policy due to missing pre-requisites. This still requires investigation and remediation to ensure required controls are applied and resources are reporting Healthy state.\r\n\r\n",
"style": "success"
},
"name": "text - 1"
},
{
"type": 1,
"content": {
"json": "## Subscriptions Health: Summary"
},
"name": "text - 8"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments\"\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| where name in~ (\r\n \"6f90a6d6-d4d6-0794-0ec1-98fa77878c2e\", // A maximum of 3 owners should be designated for subscriptions\r\n \"050ac097-3dda-4d24-ab6d-82568e7a50cf\", // Blocked accounts with owner permissions on Azure resources should be removed\r\n \"1ff0b4c9-ed56-4de6-be9c-d7ab39645926\", // Blocked accounts with read and write permissions on Azure resources should be removed \r\n \"20606e75-05c4-48c0-9d97-add6daa2109a\", // Guest accounts with owner permissions on Azure resources should be removed\r\n// \"fde1c0c9-0fd2-4ecc-87b5-98956cbc1095\", // Guest accounts with read permissions on Azure resources should be removed\r\n \"0354476c-a12a-4fcc-a79d-f0ab7ffffdbb\" // Guest accounts with write permissions on Azure resources should be removed\r\n)\r\n| extend DisplayName = tostring(properties.displayName)\r\n| extend ResourceId = tostring(properties.resourceDetails.Id)\r\n| extend StatusCode = tostring(properties.status.code)\r\n| extend StatusCause = tostring(properties.status.cause)\r\n| extend StatusDescription = tostring(properties.status.description)\r\n| extend RecommendationId = case (\r\n name =~ \"6f90a6d6-d4d6-0794-0ec1-98fa77878c2e\", \"Max_3_Owners\", // A maximum of 3 owners should be designated for subscriptions\r\n name =~ \"050ac097-3dda-4d24-ab6d-82568e7a50cf\", \"Remove_Blocked_Owner\", // Blocked accounts with owner permissions on Azure resources should be removed\r\n name =~ \"1ff0b4c9-ed56-4de6-be9c-d7ab39645926\", \"Remove_Blocked_ReadWrite\", // Blocked accounts with read and write permissions on Azure resources should be removed \r\n name =~ \"20606e75-05c4-48c0-9d97-add6daa2109a\", \"Remove_Guest_Owner\", // Guest accounts with owner permissions on Azure resources should be removed\r\n name =~ \"fde1c0c9-0fd2-4ecc-87b5-98956cbc1095\", \"Remove_Guest_Read\", // Guest accounts with read permissions on Azure resources should be removed\r\n name =~ \"0354476c-a12a-4fcc-a79d-f0ab7ffffdbb\", \"Remove_Guest_Write\", // Guest accounts with write permissions on Azure resources should be removed\r\n \"Unknown Recommendation Id\"\r\n)\r\n| summarize Recommendations = make_bag(pack(RecommendationId, StatusCode)) by ResourceId\r\n//| evaluate bag_unpack(Recommendations) // evaluate operator not supported on Resource Graph, need to do manually\r\n| extend Max_3_Owners = iff(Recommendations.Max_3_Owners == \"\", \"Unknown\", Recommendations.Max_3_Owners)\r\n| extend Remove_Blocked_Owner = iff(Recommendations.Remove_Blocked_Owner == \"\", \"Unknown\", Recommendations.Remove_Blocked_Owner)\r\n| extend Remove_Blocked_ReadWrite = iff(Recommendations.Remove_Blocked_ReadWrite == \"\", \"Unknown\", Recommendations.Remove_Blocked_ReadWrite)\r\n| extend Remove_Guest_Owner = iff(Recommendations.Remove_Guest_Owner == \"\", \"Unknown\", Recommendations.Remove_Guest_Owner)\r\n//| extend Remove_Guest_Read = iff(Recommendations.Remove_Guest_Read == \"\", \"Unknown\", Recommendations.Remove_Guest_Read)\r\n| extend Remove_Guest_Write = iff(Recommendations.Remove_Guest_Write == \"\", \"Unknown\", Recommendations.Remove_Guest_Write)\r\n| extend PackedString=tostring(pack_all())\r\n| extend State = iff(PackedString contains \"NotApplicable\" or\r\n PackedString contains \"Unknown\", \"Unknown\", \"Healthy\")\r\n| extend State = iff(PackedString contains \"Unhealthy\", \"Unhealthy\", State)\r\n| summarize count=dcount(ResourceId) by State",
"size": 3,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"visualization": "piechart",
"chartSettings": {
"seriesLabelSettings": [
{
"seriesName": "Healthy",
"color": "green"
},
{
"seriesName": "Unknown",
"color": "gray"
},
{
"seriesName": "Unhealthy",
"color": "red"
}
]
}
},
"name": "query - 2"
},
{
"type": 1,
"content": {
"json": "## Subscriptions Health: Details"
},
"name": "text - 7"
},
{
"type": 9,
"content": {
"version": "KqlParameterItem/1.0",
"parameters": [
{
"id": "e5fba300-e548-40ac-986c-9f970e9d5cf2",
"version": "KqlParameterItem/1.0",
"name": "HealthStateP",
"type": 2,
"isRequired": true,
"multiSelect": true,
"quote": "'",
"delimiter": ",",
"typeSettings": {
"additionalResourceOptions": [
"value::all"
],
"showDefault": false
},
"jsonData": "[\"Healthy\", \"Unknown\", \"Unhealthy\"]",
"timeContext": {
"durationMs": 86400000
},
"value": [
"value::all"
],
"label": "Health State"
}
],
"style": "pills",
"queryType": 0,
"resourceType": "microsoft.operationalinsights/workspaces"
},
"name": "parameters - 6"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments\"\r\n| where name in~ (\r\n \"6f90a6d6-d4d6-0794-0ec1-98fa77878c2e\", // A maximum of 3 owners should be designated for subscriptions\r\n \"050ac097-3dda-4d24-ab6d-82568e7a50cf\", // Blocked accounts with owner permissions on Azure resources should be removed\r\n \"1ff0b4c9-ed56-4de6-be9c-d7ab39645926\", // Blocked accounts with read and write permissions on Azure resources should be removed \r\n \"20606e75-05c4-48c0-9d97-add6daa2109a\", // Guest accounts with owner permissions on Azure resources should be removed\r\n// \"fde1c0c9-0fd2-4ecc-87b5-98956cbc1095\", // Guest accounts with read permissions on Azure resources should be removed\r\n \"0354476c-a12a-4fcc-a79d-f0ab7ffffdbb\" // Guest accounts with write permissions on Azure resources should be removed\r\n)\r\n| extend DisplayName = tostring(properties.displayName)\r\n| extend ResourceId = tostring(properties.resourceDetails.Id)\r\n| extend StatusCode = tostring(properties.status.code)\r\n| extend StatusCause = tostring(properties.status.cause)\r\n| extend StatusDescription = tostring(properties.status.description)\r\n| extend RecommendationId = case (\r\n name =~ \"6f90a6d6-d4d6-0794-0ec1-98fa77878c2e\", \"Max_3_Owners\", // A maximum of 3 owners should be designated for subscriptions\r\n name =~ \"050ac097-3dda-4d24-ab6d-82568e7a50cf\", \"Remove_Blocked_Owner\", // Blocked accounts with owner permissions on Azure resources should be removed\r\n name =~ \"1ff0b4c9-ed56-4de6-be9c-d7ab39645926\", \"Remove_Blocked_ReadWrite\", // Blocked accounts with read and write permissions on Azure resources should be removed \r\n name =~ \"20606e75-05c4-48c0-9d97-add6daa2109a\", \"Remove_Guest_Owner\", // Guest accounts with owner permissions on Azure resources should be removed\r\n name =~ \"fde1c0c9-0fd2-4ecc-87b5-98956cbc1095\", \"Remove_Guest_Read\", // Guest accounts with read permissions on Azure resources should be removed\r\n name =~ \"0354476c-a12a-4fcc-a79d-f0ab7ffffdbb\", \"Remove_Guest_Write\", // Guest accounts with write permissions on Azure resources should be removed\r\n \"Unknown Recommendation Id\"\r\n)\r\n| summarize Recommendations = make_bag(pack(RecommendationId, StatusCode)) by ResourceId\r\n//| evaluate bag_unpack(Recommendations) // evaluate operator not supported on Resource Graph, need to do manually\r\n| extend Max_3_Owners = iff(Recommendations.Max_3_Owners == \"\", \"Unknown\", Recommendations.Max_3_Owners)\r\n| extend Remove_Blocked_Owner = iff(Recommendations.Remove_Blocked_Owner == \"\", \"Unknown\", Recommendations.Remove_Blocked_Owner)\r\n| extend Remove_Blocked_ReadWrite = iff(Recommendations.Remove_Blocked_ReadWrite == \"\", \"Unknown\", Recommendations.Remove_Blocked_ReadWrite)\r\n| extend Remove_Guest_Owner = iff(Recommendations.Remove_Guest_Owner == \"\", \"Unknown\", Recommendations.Remove_Guest_Owner)\r\n//| extend Remove_Guest_Read = iff(Recommendations.Remove_Guest_Read == \"\", \"Unknown\", Recommendations.Remove_Guest_Read)\r\n| extend Remove_Guest_Write = iff(Recommendations.Remove_Guest_Write == \"\", \"Unknown\", Recommendations.Remove_Guest_Write)\r\n| extend PackedString=tostring(pack_all())\r\n| extend HealthState = iff(PackedString contains \"NotApplicable\" or\r\n PackedString contains \"Unknown\", \"Unknown\", \"Healthy\")\r\n| extend HealthState = iff(PackedString contains \"Unhealthy\", \"Unhealthy\", HealthState)\r\n| where HealthState in~ ({HealthStateP})\r\n| project ResourceId, HealthState, Max_3_Owners, Remove_Blocked_Owner, Remove_Blocked_ReadWrite, Remove_Guest_Owner, Remove_Guest_Write",
"size": 1,
"title": "Subscriptions - Health State. Select one or more subscriptions to see cause of \"NotApplicable\" state.",
"exportMultipleValues": true,
"exportedParameters": [
{
"fieldName": "ResourceId",
"parameterName": "selectedSubscriptions",
"parameterType": 6
}
],
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"gridSettings": {
"formatters": [
{
"columnMatch": "State",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "colors",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "green",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "red",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "gray",
"text": "{0}{1}"
}
]
}
},
{
"columnMatch": "Max_3_Owners",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "success",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "critical",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "unknown",
"text": "{0}{1}"
}
]
}
},
{
"columnMatch": "Remove_Blocked_Owner",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "success",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "critical",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "unknown",
"text": "{0}{1}"
}
]
}
},
{
"columnMatch": "Remove_Blocked_ReadWrite",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "success",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "critical",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "unknown",
"text": "{0}{1}"
}
]
}
},
{
"columnMatch": "Remove_Deprecated_Owner",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "success",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "critical",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "unknown",
"text": "{0}{1}"
}
]
}
},
{
"columnMatch": "Remove_External_Owner",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "success",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "critical",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "unknown",
"text": "{0}{1}"
}
]
}
},
{
"columnMatch": "Remove_External_Write",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "success",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "4",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "unknown",
"text": "{0}{1}"
}
]
}
},
{
"columnMatch": "Remove_Guest_Owner",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "success",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "4",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "unknown",
"text": "{0}{1}"
}
]
}
},
{
"columnMatch": "Remove_Guest_Write",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "success",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "critical",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "unknown",
"text": "{0}{1}"
}
]
}
}
],
"labelSettings": [
{
"columnId": "ResourceId",
"label": "Subscription"
},
{
"columnId": "HealthState",
"label": "Health State"
}
]
}
},
"name": "query - 3",
"styleSettings": {
"margin": "25",
"padding": "25"
}
},
{
"type": 1,
"content": {
"json": "## Supporting information: Cause of \"NotApplicable\" state on selected subscriptions"
},
"name": "text - 13"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments\"\r\n| where name in~ (\r\n \"6f90a6d6-d4d6-0794-0ec1-98fa77878c2e\", // A maximum of 3 owners should be designated for subscriptions\r\n \"050ac097-3dda-4d24-ab6d-82568e7a50cf\", // Blocked accounts with owner permissions on Azure resources should be removed\r\n \"1ff0b4c9-ed56-4de6-be9c-d7ab39645926\", // Blocked accounts with read and write permissions on Azure resources should be removed \r\n// \"00c6d40b-e990-6acf-d4f3-471e747a27c4\", // Deprecated accounts should be removed from subscriptions\r\n \"e52064aa-6853-e252-a11e-dffc675689c2\", // Deprecated accounts with owner permissions should be removed from subscriptions\r\n \"c3b6ae71-f1f0-31b4-e6c1-d5951285d03d\", // External accounts with owner permissions should be removed from subscriptions\r\n// \"a8c6a4ad-d51e-88fe-2979-d3ee3c864f8b\", // External accounts with read permissions should be removed from subscriptions\r\n \"04e7147b-0deb-9796-2e5c-0336343ceb3d\", // External accounts with write permissions should be removed from subscriptions\r\n \"20606e75-05c4-48c0-9d97-add6daa2109a\", // Guest accounts with owner permissions on Azure resources should be removed\r\n// \"fde1c0c9-0fd2-4ecc-87b5-98956cbc1095\", // Guest accounts with read permissions on Azure resources should be removed\r\n \"0354476c-a12a-4fcc-a79d-f0ab7ffffdbb\" // Guest accounts with write permissions on Azure resources should be removed\r\n)\r\n| extend DisplayName = tostring(properties.displayName)\r\n| extend ResourceId = tostring(properties.resourceDetails.Id)\r\n| extend StatusCode = tostring(properties.status.code)\r\n| extend StatusCause = tostring(properties.status.cause)\r\n| extend StatusDescription = tostring(properties.status.description)\r\n| where StatusCode == \"NotApplicable\"\r\n| project ResourceId, DisplayName, StatusCode, StatusDescription",
"size": 1,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{selectedSubscriptions}"
]
},
"name": "query - 12"
},
{
"type": 1,
"content": {
"json": "## Health Legend (for Virtual Machines)\r\n#### Healthy:\r\nHealthy Virtual Machines have Just-In-Time access restricterd based on Port, IP, and for limited time to be requested again after expiry (max 24 hrs).\r\n\r\n#### Unhealthy:\r\nUnhealthy Healthy Virtual Machines do not have Just-In-Time access restricterd based on Port, IP, and for limited time to be requested again after expiry (max 24 hrs).\r\n\r\n#### Not Applicable:\r\n Not Applicable health for Virtual Machines indicate inability to retrieve compliance data for the respective policy due to missing pre-requisites. This still requires investigation and remediation to ensure required controls are applied and resources are reporting Healthy state.",
"style": "success"
},
"name": "text - 14"
},
{
"type": 1,
"content": {
"json": "## Virtual Machines Health: Summary"
},
"name": "text - 9"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments\"\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| where name in~ (\r\n \"805651bc-6ecd-4c73-9b55-97a19d0582d0\" // Management ports of virtual machines should be protected with just-in-time network access control\r\n)\r\n| extend DisplayName = tostring(properties.displayName)\r\n| extend ResourceId = tostring(properties.resourceDetails.Id)\r\n| extend StatusCode = tostring(properties.status.code)\r\n| extend StatusCause = tostring(properties.status.cause)\r\n| extend StatusDescription = tostring(properties.status.description)\r\n// change the status of VMs without NSG to \"Unhealthy\"\r\n| extend StatusCode = iff(StatusDescription =~ \"This recommendation is relevant only for VMs protected by a network security group or Azure Firewall\", \"Unhealthy\", StatusCode)\r\n// change NA status to \"Unknown\"\r\n| extend StatusCode = iff(StatusCode =~ \"NotApplicable\", \"Unknown\", StatusCode)\r\n| project subscriptionId, resourceGroup, ResourceId, DisplayName, StatusCode, StatusCause, StatusDescription\r\n| summarize count=dcount(ResourceId) by StatusCode",
"size": 3,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"visualization": "piechart",
"chartSettings": {
"seriesLabelSettings": [
{
"seriesName": "Healthy",
"color": "green"
},
{
"seriesName": "Unhealthy",
"color": "red"
},
{
"seriesName": "Unknown",
"color": "gray"
}
]
}
},
"name": "query - 4"
},
{
"type": 9,
"content": {
"version": "KqlParameterItem/1.0",
"parameters": [
{
"id": "3db5e9dd-d34d-4c17-bff3-c7fea521f1ef",
"version": "KqlParameterItem/1.0",
"name": "HealthStateP",
"label": "Health State",
"type": 2,
"isRequired": true,
"multiSelect": true,
"quote": "'",
"delimiter": ",",
"typeSettings": {
"additionalResourceOptions": [
"value::all"
],
"showDefault": false
},
"jsonData": "[\"Healthy\", \"Unhealthy\", \"Unknown\"]",
"timeContext": {
"durationMs": 86400000
},
"value": [
"Unhealthy"
]
}
],
"style": "pills",
"queryType": 0,
"resourceType": "microsoft.operationalinsights/workspaces"
},
"name": "parameters - 11"
},
{
"type": 1,
"content": {
"json": "## Virtual Machines Health: Details"
},
"name": "text - 10"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments\"\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| where name in~ (\r\n \"805651bc-6ecd-4c73-9b55-97a19d0582d0\" // Management ports of virtual machines should be protected with just-in-time network access control\r\n)\r\n| extend DisplayName = tostring(properties.displayName)\r\n| extend ResourceId = tostring(properties.resourceDetails.Id)\r\n| extend StatusCode = tostring(properties.status.code)\r\n| extend StatusCause = tostring(properties.status.cause)\r\n| extend StatusDescription = tostring(properties.status.description)\r\n// change the status of VMs without NSG to \"Unhealthy\"\r\n| extend StatusCode = iff(StatusDescription =~ \"This recommendation is relevant only for VMs protected by a network security group or Azure Firewall\", \"Unhealthy\", StatusCode)\r\n// change NA status to \"Unknown\"\r\n| extend StatusCode = iff(StatusCode =~ \"NotApplicable\", \"Unknown\", StatusCode)\r\n| where StatusCode in~ ({HealthStateP})\r\n| project subscriptionId, resourceGroup, ResourceId, DisplayName, StatusCode, StatusCause, StatusDescription",
"size": 0,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"visualization": "table",
"gridSettings": {
"formatters": [
{
"columnMatch": "$gen_group",
"formatter": 15,
"formatOptions": {
"linkTarget": null,
"showIcon": true
}
},
{
"columnMatch": "subscriptionId",
"formatter": 5
},
{
"columnMatch": "resourceGroup",
"formatter": 5
},
{
"columnMatch": "StatusCode",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "colors",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "green",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "red",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "gray",
"text": "{0}{1}"
}
]
}
},
{
"columnMatch": "StatusCause",
"formatter": 5
}
],
"hierarchySettings": {
"treeType": 1,
"groupBy": [
"subscriptionId",
"resourceGroup"
],
"expandTopLevel": true
},
"labelSettings": [
{
"columnId": "subscriptionId",
"label": "Subscription"
},
{
"columnId": "resourceGroup",
"label": "Resource Group"
},
{
"columnId": "ResourceId",
"label": "Virtual Machine"
},
{
"columnId": "DisplayName",
"label": "Setting"
},
{
"columnId": "StatusCode",
"label": "Health State"
},
{
"columnId": "StatusCause",
"label": "Cause"
},
{
"columnId": "StatusDescription",
"label": "Description"
}
]
},
"chartSettings": {
"seriesLabelSettings": [
{
"seriesName": "NotApplicable",
"color": "gray"
},
{
"seriesName": "Healthy",
"color": "blue"
},
{
"seriesName": "Unhealthy",
"color": "orange"
}
]
}
},
"name": "query - 5",
"styleSettings": {
"margin": "25",
"padding": "25"
}
}
]
},
"conditionalVisibility": {
"parameterName": "Tab",
"comparison": "isEqualTo",
"value": "admin priv"
},
"name": "group - 6"
},
{
"type": 12,
"content": {
"version": "NotebookGroup/1.0",
"groupType": "editable",
"items": [
{
"type": 1,
"content": {
"json": "# Strategy 7: Multi-Factor Authentication\r\n\r\n"
},
"name": "text - 0"
},
{
"type": 1,
"content": {
"json": "## Health Legend (for Subscriptions)\r\n\r\n#### Healthy\r\nHealthy Subscriptions have MFA enabled for all accounts with read, write, or owner permissions on the subscription. The health state of **all controls** must be Healthy for the subscription to be reported as Healthy.\r\n\r\n#### Unhealthy:\r\nUnhealthy Subscriptions do not have MFA enabled for at least one account with read, write, or owner permissions on the subscription. If the health state of **at least one control** is Unhealthy, the subscription will reported as Unhealthy.\r\n\r\n#### Unknown:\r\nUnknown health for Subscriptions indicate inability to retrieve compliance data for the respective policy due to missing pre-requisites. This still requires investigation and remediation to ensure required controls are applied and resources are reporting Healthy state.",
"style": "success"
},
"name": "text - 1"
},
{
"type": 1,
"content": {
"json": "## Subscriptions Health: Summary"
},
"name": "text - 6"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments\"\r\n| where name in~ (\r\n \"6240402e-f77c-46fa-9060-a7ce53997754\", // Accounts with owner permissions on Azure resources should be MFA enabled\r\n \"dabc9bc4-b8a8-45bd-9a5a-43000df8aa1c\", // Accounts with read permissions on Azure resources should be MFA enabled\r\n \"c0cb17b2-0607-48a7-b0e0-903ed22de39b\" // Accounts with write permissions on Azure resources should be MFA enabled\r\n)\r\n| extend DisplayName = tostring(properties.displayName)\r\n| extend ResourceId = tostring(properties.resourceDetails.Id)\r\n| extend StatusCode = tostring(properties.status.code)\r\n| extend StatusCause = tostring(properties.status.cause)\r\n| extend StatusDescription = tostring(properties.status.description)\r\n| extend RecommendationId = case (\r\n name =~ \"6240402e-f77c-46fa-9060-a7ce53997754\", \"Res_Owner_MFA\", // Accounts with owner permissions on Azure resources should be MFA enabled\r\n name =~ \"dabc9bc4-b8a8-45bd-9a5a-43000df8aa1c\", \"Res_Read_MFA\", // Accounts with read permissions on Azure resources should be MFA enabled\r\n name =~ \"c0cb17b2-0607-48a7-b0e0-903ed22de39b\", \"Res_Write_MFA\", // Accounts with write permissions on Azure resources should be MFA enabled\r\n \"Unknown Recommendation Id\"\r\n)\r\n| summarize Recommendations = make_bag(pack(RecommendationId, StatusCode)) by ResourceId\r\n//| evaluate bag_unpack(Recommendations) // evaluate operator not supported on Resource Graph, need to do manually\r\n| extend Res_Owner_MFA = iff(Recommendations.Res_Owner_MFA == \"\", \"Unknown\", Recommendations.Res_Owner_MFA)\r\n| extend Res_Read_MFA = iff(Recommendations.Res_Read_MFA == \"\", \"Unknown\", Recommendations.Res_Read_MFA)\r\n| extend Res_Write_MFA = iff(Recommendations.Res_Write_MFA == \"\", \"Unknown\", Recommendations.Res_Write_MFA)\r\n| extend PackedString=tostring(pack_all())\r\n| extend State = iff(PackedString contains \"NotApplicable\" or\r\n PackedString contains \"Unknown\", \"Unknown\", \"Healthy\")\r\n| extend State = iff(PackedString contains \"Unhealthy\", \"Unhealthy\", State)\r\n| summarize count = dcount(ResourceId) by State",
"size": 3,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"value::all"
],
"visualization": "piechart",
"chartSettings": {
"seriesLabelSettings": [
{
"seriesName": "Healthy",
"color": "green"
},
{
"seriesName": "Unknown",
"color": "gray"
},
{
"seriesName": "Unhealthy",
"color": "red"
}
]
}
},
"name": "query - 2"
},
{
"type": 1,
"content": {
"json": "## Subscriptions Health: Details"
},
"name": "text - 7"
},
{
"type": 9,
"content": {
"version": "KqlParameterItem/1.0",
"parameters": [
{
"id": "146ba2cd-eeb2-470c-8880-39b6c537ac36",
"version": "KqlParameterItem/1.0",
"name": "HealthStateP",
"type": 2,
"isRequired": true,
"multiSelect": true,
"quote": "'",
"delimiter": ",",
"typeSettings": {
"additionalResourceOptions": [
"value::all"
],
"showDefault": false
},
"jsonData": "[\"Healthy\", \"Unknown\", \"Unhealthy\"]",
"timeContext": {
"durationMs": 86400000
},
"defaultValue": "value::all",
"value": [
"value::all"
],
"label": "HealthState"
}
],
"style": "pills",
"queryType": 0,
"resourceType": "microsoft.operationalinsights/workspaces"
},
"name": "parameters - 5"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments\"\r\n| where name in~ (\r\n \"6240402e-f77c-46fa-9060-a7ce53997754\", // Accounts with owner permissions on Azure resources should be MFA enabled\r\n \"dabc9bc4-b8a8-45bd-9a5a-43000df8aa1c\", // Accounts with read permissions on Azure resources should be MFA enabled\r\n \"c0cb17b2-0607-48a7-b0e0-903ed22de39b\" // Accounts with write permissions on Azure resources should be MFA enabled\r\n)\r\n| extend DisplayName = tostring(properties.displayName)\r\n| extend ResourceId = tostring(properties.resourceDetails.Id)\r\n| extend StatusCode = tostring(properties.status.code)\r\n| extend StatusCause = tostring(properties.status.cause)\r\n| extend StatusDescription = tostring(properties.status.description)\r\n| extend RecommendationId = case (\r\n name =~ \"6240402e-f77c-46fa-9060-a7ce53997754\", \"Res_Owner_MFA\", // Accounts with owner permissions on Azure resources should be MFA enabled\r\n name =~ \"dabc9bc4-b8a8-45bd-9a5a-43000df8aa1c\", \"Res_Read_MFA\", // Accounts with read permissions on Azure resources should be MFA enabled\r\n name =~ \"c0cb17b2-0607-48a7-b0e0-903ed22de39b\", \"Res_Write_MFA\", // Accounts with write permissions on Azure resources should be MFA enabled\r\n \"Unknown Recommendation Id\"\r\n)\r\n| summarize Recommendations = make_bag(pack(RecommendationId, StatusCode)) by ResourceId\r\n//| evaluate bag_unpack(Recommendations) // evaluate operator not supported on Resource Graph, need to do manually\r\n| extend Res_Owner_MFA = iff(Recommendations.Res_Owner_MFA == \"\", \"Unknown\", Recommendations.Res_Owner_MFA)\r\n| extend Res_Read_MFA = iff(Recommendations.Res_Read_MFA == \"\", \"Unknown\", Recommendations.Res_Read_MFA)\r\n| extend Res_Write_MFA = iff(Recommendations.Res_Write_MFA == \"\", \"Unknown\", Recommendations.Res_Write_MFA)\r\n| extend PackedString=tostring(pack_all())\r\n| extend State = iff(PackedString contains \"NotApplicable\" or\r\n PackedString contains \"Unknown\", \"Unknown\", \"Healthy\")\r\n| extend State = iff(PackedString contains \"Unhealthy\", \"Unhealthy\", State)\r\n| where State in~ ({HealthStateP})\r\n| project ResourceId, State, Res_Owner_MFA, Res_Read_MFA, Res_Write_MFA",
"size": 1,
"exportMultipleValues": true,
"exportedParameters": [
{
"fieldName": "ResourceId",
"parameterName": "selectedSubscriptions",
"parameterType": 6
}
],
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"value::all"
],
"gridSettings": {
"formatters": [
{
"columnMatch": "State",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "colors",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "red",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "green",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "gray",
"text": "{0}{1}"
}
],
"compositeBarSettings": {
"labelText": "",
"columnSettings": [
{
"columnName": "State",
"color": "blue"
}
]
}
}
},
{
"columnMatch": "Res_Owner_MFA",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "success",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "4",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "unknown",
"text": "{0}{1}"
}
]
}
},
{
"columnMatch": "Res_Read_MFA",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "success",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "4",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "unknown",
"text": "{0}{1}"
}
]
}
},
{
"columnMatch": "Res_Write_MFA",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "success",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "4",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "unknown",
"text": "{0}{1}"
}
]
}
},
{
"columnMatch": "Sub_Owner_MFA",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "success",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "4",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "unknown",
"text": "{0}{1}"
}
]
}
},
{
"columnMatch": "Sub_Read_MFA",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "success",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "4",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "unknown",
"text": "{0}{1}"
}
]
}
},
{
"columnMatch": "Sub_Write_MFA",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "icons",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "success",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "4",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "unknown",
"text": "{0}{1}"
}
]
}
},
{
"columnMatch": "subscriptionId",
"formatter": 15,
"formatOptions": {
"linkTarget": null,
"showIcon": true,
"customColumnWidthSetting": "15%"
}
},
{
"columnMatch": "DisplayName",
"formatter": 0,
"formatOptions": {
"customColumnWidthSetting": "35%"
}
},
{
"columnMatch": "StatusCode",
"formatter": 0,
"formatOptions": {
"customColumnWidthSetting": "5%"
}
},
{
"columnMatch": "StatusDescription",
"formatter": 0,
"formatOptions": {
"customColumnWidthSetting": "45%"
}
}
],
"labelSettings": [
{
"columnId": "ResourceId",
"label": "Subscription"
},
{
"columnId": "State",
"label": "Health State"
}
]
}
},
"name": "query - 4"
},
{
"type": 1,
"content": {
"json": "## Supporting information: Cause of \"NotApplicable\" state on selected subscriptions"
},
"name": "text - 7"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments\"\r\n| where name in~ (\r\n \"6240402e-f77c-46fa-9060-a7ce53997754\", // Accounts with owner permissions on Azure resources should be MFA enabled\r\n \"dabc9bc4-b8a8-45bd-9a5a-43000df8aa1c\", // Accounts with read permissions on Azure resources should be MFA enabled\r\n \"c0cb17b2-0607-48a7-b0e0-903ed22de39b\", // Accounts with write permissions on Azure resources should be MFA enabled\r\n \"94290b00-4d0c-d7b4-7cea-064a9554e681\", // MFA should be enabled on accounts with owner permissions on subscriptions\r\n \"151e82c5-5341-a74b-1eb0-bc38d2c84bb5\", // MFA should be enabled on accounts with read permissions on subscriptions\r\n \"57e98606-6b1e-6193-0e3d-fe621387c16b\" // MFA should be enabled on accounts with write permissions on subscriptions\r\n)\r\n| extend DisplayName = tostring(properties.displayName)\r\n| extend ResourceId = tostring(properties.resourceDetails.Id)\r\n| extend StatusCode = tostring(properties.status.code)\r\n| extend StatusCause = tostring(properties.status.cause)\r\n| extend StatusDescription = tostring(properties.status.description)\r\n| where StatusCode == \"NotApplicable\"\r\n| project ResourceId, DisplayName, StatusCode, StatusDescription",
"size": 1,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{selectedSubscriptions}"
]
},
"name": "query - 8"
}
]
},
"conditionalVisibility": {
"parameterName": "Tab",
"comparison": "isEqualTo",
"value": "MFA"
},
"name": "group - 7"
},
{
"type": 12,
"content": {
"version": "NotebookGroup/1.0",
"groupType": "editable",
"items": [
{
"type": 1,
"content": {
"json": "# Strategy 8: Regular Backups\r\n"
},
"name": "text - 0"
},
{
"type": 1,
"content": {
"json": "## Health Legend\r\n\r\n#### Healthy:\r\nHealthy Virtual Machines have Azure Backup enabled.\r\n\r\n#### Unhealthy:\r\nUnhealthy Virtual Machines do not have Azure Backup enabled.",
"style": "success"
},
"conditionalVisibility": {
"parameterName": "Tab",
"comparison": "isEqualTo",
"value": "backups"
},
"name": "text - 1"
},
{
"type": 1,
"content": {
"json": "## Virtual Machines Health: Summary"
},
"name": "text - 6"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| where type == \"microsoft.security/assessments\"\r\n| where name == \"f2f595ec-5dc6-68b4-82ef-b63563e9c610\" // \"Azure Backup should be enabled for virtual machines\"\r\n| extend ResourceId = tostring(properties.resourceDetails.Id)\r\n| extend state = tostring(properties.status.code)\r\n| summarize count=dcount(ResourceId) by state",
"size": 3,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"visualization": "piechart",
"chartSettings": {
"seriesLabelSettings": [
{
"seriesName": "Unhealthy",
"color": "red"
},
{
"seriesName": "Healthy",
"color": "green"
}
]
}
},
"name": "query - 2"
},
{
"type": 1,
"content": {
"json": "## Virtual Machines Health: Details"
},
"name": "text - 5"
},
{
"type": 9,
"content": {
"version": "KqlParameterItem/1.0",
"parameters": [
{
"id": "3a68de01-9f93-481f-8fee-68cbca0c19b3",
"version": "KqlParameterItem/1.0",
"name": "HealthStateP",
"type": 2,
"isRequired": true,
"multiSelect": true,
"quote": "'",
"delimiter": ",",
"value": [
"Unhealthy"
],
"typeSettings": {
"additionalResourceOptions": [
"value::all"
],
"selectAllValue": "",
"showDefault": false
},
"jsonData": "[\"Healthy\", \"Unhealthy\"]",
"timeContext": {
"durationMs": 86400000
},
"defaultValue": "value::all",
"label": "Health State"
}
],
"style": "pills",
"queryType": 0,
"resourceType": "microsoft.operationalinsights/workspaces"
},
"name": "parameters - 4"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| where type == \"microsoft.security/assessments\"\r\n| where name == \"f2f595ec-5dc6-68b4-82ef-b63563e9c610\" // \"Azure Backup should be enabled for virtual machines\"\r\n| extend ResourceId = tostring(properties.resourceDetails.Id)\r\n| extend state = tostring(properties.status.code)\r\n| where state in~ ({HealthStateP})\r\n| project subscriptionId, resourceGroup, ResourceId, state\r\n",
"size": 2,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"gridSettings": {
"formatters": [
{
"columnMatch": "$gen_group",
"formatter": 15,
"formatOptions": {
"linkTarget": null,
"showIcon": true,
"customColumnWidthSetting": "20%"
}
},
{
"columnMatch": "subscriptionId",
"formatter": 5
},
{
"columnMatch": "resourceGroup",
"formatter": 5
},
{
"columnMatch": "state",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "colors",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "green",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "red",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "gray",
"text": "{0}{1}"
}
]
}
}
],
"hierarchySettings": {
"treeType": 1,
"groupBy": [
"subscriptionId",
"resourceGroup"
],
"expandTopLevel": false
},
"labelSettings": [
{
"columnId": "subscriptionId",
"label": "Subscription"
},
{
"columnId": "resourceGroup",
"label": "Resource Group"
},
{
"columnId": "ResourceId",
"label": "Resource"
},
{
"columnId": "state",
"label": "Health State"
}
]
}
},
"name": "query - 3"
}
]
},
"conditionalVisibility": {
"parameterName": "Tab",
"comparison": "isEqualTo",
"value": "backups"
},
"name": "group - 8"
},
{
"type": 12,
"content": {
"version": "NotebookGroup/1.0",
"groupType": "editable",
"items": [
{
"type": 1,
"content": {
"json": "# Strategy 3: Configure Microsoft Office macro settings"
},
"name": "text - 0"
},
{
"type": 12,
"content": {
"version": "NotebookGroup/1.0",
"groupType": "editable",
"items": [
{
"type": 1,
"content": {
"json": "## Health Legend\r\nThe \"Configure Microsoft Office macro settings\" strategy is applicable to client systems used by interactive users. \r\nAs this workbook targets servers that do not typically match this profile, the health state will be determined by the presence or absence of Microsoft Office applications that are capable of executing macros.\r\n\r\n#### Healthy:\r\nHealthy Virtual Machines do not have any Microsoft Office applications installed.\r\n\r\n#### Unhealthy:\r\nUnhealthy Virtual Machines have one or more Microsoft Office applications installed.\r\n\r\n#### Unknown:\r\nSoftware inventory data is not available for this VM. Verify that vulnerability scanning is enabled on this VM.",
"style": "success"
},
"name": "text - 1"
},
{
"type": 1,
"content": {
"json": "## Virtual Machines Health: Summary"
},
"name": "text - 5"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/softwareinventories\"\r\n| parse id with resourceId \"/providers/Microsoft.Security/softwareInventories/\" *\r\n| parse resourceId with * \"/providers/\" resourceType1 \"/\" resourceType2 \"/\" *\r\n| extend resourceType = strcat(resourceType1, \"/\", resourceType2)\r\n| where resourceType in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n| distinct subscriptionId, resourceGroup, resourceId, resourceType\r\n| join kind = leftouter (\r\n securityresources\r\n | where type == \"microsoft.security/softwareinventories\"\r\n | parse id with resourceId \"/providers/Microsoft.Security/softwareInventories/\" *\r\n | parse resourceId with * \"/providers/\" resourceType1 \"/\" resourceType2 \"/\" *\r\n | extend resourceType = strcat(resourceType1, \"/\", resourceType2)\r\n | where resourceType in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n | extend vendor = tostring(properties.vendor),\r\n software = tostring(properties.softwareName),\r\n version = tostring(properties.version)\r\n | where vendor =~ \"microsoft\" and software in~ (\"office\", \"office_web_components\")\r\n | summarize applications = make_list(pack(\"Vendor\", vendor, \"Software\", software, \"Version\", version)) by subscriptionId, resourceGroup, resourceId, resourceType\r\n) on resourceId\r\n| extend appCompliant = iff(isnull(applications), \"Healthy\", \"Unhealthy\")\r\n| project subscriptionId, resourceGroup, resourceId = tolower(resourceId), resourceType, appCompliant, applications\r\n| join kind = fullouter (\r\n resources\r\n //| where type in~ (\"microsoft.compute/virtualmachines\", \"microsoft.classiccompute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n | where type in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n | extend osType = iff(type =~ \"microsoft.compute/virtualmachines\", tostring(properties.storageProfile.osDisk.osType), tostring(properties.osType)),\r\n osName = iff(type =~ \"microsoft.compute/virtualmachines\", tostring(properties.extended.instanceView.osName), tostring(properties.osName)), // blank if machine stopped, deallocated\r\n osVersion = iff(type =~ \"microsoft.compute/virtualmachines\", tostring(properties.extended.instanceView.osVersion), tostring(properties.osVersion)), // blank if machine stopped, deallocated\r\n publisher = tostring(properties.storageProfile.imageReference.publisher),\r\n exactVersion = tostring(properties.storageProfile.imageReference.exactVersion),\r\n sku = iff(type =~ \"microsoft.compute/virtualmachines\", tostring(properties.storageProfile.imageReference.sku), tostring(properties.osSku)),\r\n offer = tostring(properties.storageProfile.imageReference.offer)\r\n | project subscriptionId, resourceGroup, resourceId = tolower(id), resourceType = type, name, osType, osName, osVersion, sku, publisher, exactVersion, offer\r\n) on resourceId\r\n| extend subscriptionId = iff(isempty(subscriptionId), subscriptionId1, subscriptionId)\r\n| extend resourceGroup = iff(isempty(resourceGroup), resourceGroup1, resourceGroup)\r\n| extend resourceId = iff(isempty(resourceId), resourceId1, resourceId)\r\n| extend resourceType = iff(isempty(resourceType), resourceType1, resourceType)\r\n//| project-away subscriptionId1, resourceGroup1, resourceId1, resourceType1\r\n| extend appCompliant = iff(isempty(appCompliant), \"Unknown\", appCompliant)\r\n| summarize count = dcount(resourceId) by appCompliant",
"size": 3,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"visualization": "piechart",
"chartSettings": {
"seriesLabelSettings": [
{
"seriesName": "Unhealthy",
"color": "red"
},
{
"seriesName": "Healthy",
"color": "green"
},
{
"seriesName": "Unknown",
"color": "gray"
}
]
}
},
"name": "query - 6"
},
{
"type": 1,
"content": {
"json": "## Virtual Machines Health: Details"
},
"name": "text - 6"
},
{
"type": 9,
"content": {
"version": "KqlParameterItem/1.0",
"parameters": [
{
"id": "19582757-cbb9-4e41-8e76-252f56b52506",
"version": "KqlParameterItem/1.0",
"name": "HealthState",
"label": "Health State",
"type": 2,
"isRequired": true,
"multiSelect": true,
"quote": "'",
"delimiter": ",",
"typeSettings": {
"additionalResourceOptions": [
"value::all"
],
"showDefault": false
},
"jsonData": "[\"Healthy\", \"Unhealthy\", \"Unknown\"]",
"timeContext": {
"durationMs": 86400000
},
"defaultValue": "value::all",
"value": [
"Unknown"
]
}
],
"style": "pills",
"queryType": 0,
"resourceType": "microsoft.operationalinsights/workspaces"
},
"name": "parameters - 4"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/softwareinventories\"\r\n| parse id with resourceId \"/providers/Microsoft.Security/softwareInventories/\" *\r\n| parse resourceId with * \"/providers/\" resourceType1 \"/\" resourceType2 \"/\" *\r\n| extend resourceType = strcat(resourceType1, \"/\", resourceType2)\r\n| where resourceType in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n| distinct subscriptionId, resourceGroup, resourceId, resourceType\r\n| join kind = leftouter (\r\n securityresources\r\n | where type == \"microsoft.security/softwareinventories\"\r\n | parse id with resourceId \"/providers/Microsoft.Security/softwareInventories/\" *\r\n | parse resourceId with * \"/providers/\" resourceType1 \"/\" resourceType2 \"/\" *\r\n | extend resourceType = strcat(resourceType1, \"/\", resourceType2)\r\n | where resourceType in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n | extend vendor = tostring(properties.vendor),\r\n software = tostring(properties.softwareName),\r\n version = tostring(properties.version)\r\n | where vendor =~ \"microsoft\" and software in~ (\"office\", \"office_web_components\")\r\n | summarize applications = make_list(pack(\"Vendor\", vendor, \"Software\", software, \"Version\", version)) by subscriptionId, resourceGroup, resourceId, resourceType\r\n) on resourceId\r\n| extend appCompliant = iff(isnull(applications), \"Healthy\", \"Unhealthy\")\r\n| project subscriptionId, resourceGroup, resourceId = tolower(resourceId), resourceType, appCompliant, applications\r\n| join kind = fullouter (\r\n resources\r\n //| where type in~ (\"microsoft.compute/virtualmachines\", \"microsoft.classiccompute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n | where type in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n | extend osType = iff(type =~ \"microsoft.compute/virtualmachines\", tostring(properties.storageProfile.osDisk.osType), tostring(properties.osType)),\r\n osName = iff(type =~ \"microsoft.compute/virtualmachines\", tostring(properties.extended.instanceView.osName), tostring(properties.osName)), // blank if machine stopped, deallocated\r\n osVersion = iff(type =~ \"microsoft.compute/virtualmachines\", tostring(properties.extended.instanceView.osVersion), tostring(properties.osVersion)), // blank if machine stopped, deallocated\r\n publisher = tostring(properties.storageProfile.imageReference.publisher),\r\n exactVersion = tostring(properties.storageProfile.imageReference.exactVersion),\r\n sku = iff(type =~ \"microsoft.compute/virtualmachines\", tostring(properties.storageProfile.imageReference.sku), tostring(properties.osSku)),\r\n offer = tostring(properties.storageProfile.imageReference.offer)\r\n | project subscriptionId, resourceGroup, resourceId = tolower(id), resourceType = type, name, osType, osName, osVersion, sku, publisher, exactVersion, offer\r\n) on resourceId\r\n| extend subscriptionId = iff(isempty(subscriptionId), subscriptionId1, subscriptionId)\r\n| extend resourceGroup = iff(isempty(resourceGroup), resourceGroup1, resourceGroup)\r\n| extend resourceId = iff(isempty(resourceId), resourceId1, resourceId)\r\n| extend resourceType = iff(isempty(resourceType), resourceType1, resourceType)\r\n| project-away subscriptionId1, resourceGroup1, resourceId1, resourceType1\r\n| extend appCompliant = iff(isempty(appCompliant), \"Unknown\", appCompliant)\r\n| where appCompliant in~ ({HealthState})\r\n",
"size": 0,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"gridSettings": {
"formatters": [
{
"columnMatch": "$gen_group",
"formatter": 15,
"formatOptions": {
"linkTarget": "Resource",
"showIcon": true
}
},
{
"columnMatch": "subscriptionId",
"formatter": 5
},
{
"columnMatch": "resourceGroup",
"formatter": 5
},
{
"columnMatch": "appCompliant",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "colors",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "green",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "red",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unknown",
"representation": "gray",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "gray",
"text": "{0}{1}"
}
]
}
}
],
"hierarchySettings": {
"treeType": 1,
"groupBy": [
"subscriptionId",
"resourceGroup"
],
"expandTopLevel": true
},
"labelSettings": [
{
"columnId": "subscriptionId",
"label": "Subscription"
},
{
"columnId": "resourceId",
"label": "Resource"
},
{
"columnId": "resourceType",
"label": "Resource Type"
},
{
"columnId": "appCompliant",
"label": "Health State"
},
{
"columnId": "applications",
"label": "Non-compliant Apps"
},
{
"columnId": "name",
"label": "VM name"
},
{
"columnId": "osType",
"label": "OS Type"
},
{
"columnId": "osName",
"label": "OS Name"
},
{
"columnId": "osVersion",
"label": "OS Version"
},
{
"columnId": "sku",
"label": "SKU"
},
{
"columnId": "publisher",
"label": "Publisher"
},
{
"columnId": "exactVersion",
"label": "Exact Version"
},
{
"columnId": "offer",
"label": "Offer"
}
]
}
},
"name": "query - 6"
}
]
},
"name": "group - 7"
}
]
},
"conditionalVisibility": {
"parameterName": "Tab",
"comparison": "isEqualTo",
"value": "macros"
},
"name": "group - 9"
},
{
"type": 12,
"content": {
"version": "NotebookGroup/1.0",
"groupType": "editable",
"items": [
{
"type": 1,
"content": {
"json": "# Strategy 4: User Application Hardening\r\n"
},
"name": "text - 0"
},
{
"type": 12,
"content": {
"version": "NotebookGroup/1.0",
"groupType": "editable",
"items": [
{
"type": 1,
"content": {
"json": "## Health Legend\r\nThe \"User application hardening\" strategy is applicable to client systems used by interactive users. \r\nAs this workbook targets servers that do not typically match this profile, the health state will be determined by the presence or absence of web browsers, Microsoft Office and PDF applications, and whether an endpoint protection solution is enabled and in a healthy state.\r\n\r\n#### Healthy:\r\nHealthy Virtual Machines:\r\n- Do not have any (non-Microsoft) web browsers (*), Microsoft Office or PDF applications installed.\r\n- Have a [supported endpoint protection solution](https://learn.microsoft.com/en-gb/azure/defender-for-cloud/support-matrix-defender-for-servers#endpoint-protection-support) installed and in a healthy state.\r\n\r\n#### Unhealthy:\r\nUnhealthy Virtual Machines do not meet one or both of the above conditions.\r\n\r\n#### Unknown:\r\nOne or both of the following is true for a VM:\r\n- Software inventory data is not available for this VM. Verify that vulnerability scanning is enabled on this VM.\r\n- Endpoint protection data is not available for this VM.\r\n\r\n(*) Microsoft browsers are not counted as installed apps as they are included with the OS.",
"style": "success"
},
"name": "text - 1"
},
{
"type": 1,
"content": {
"json": "## Virtual Machines Health: Summary"
},
"name": "text - 8"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/softwareinventories\"\r\n| parse id with resourceId \"/providers/Microsoft.Security/softwareInventories/\" *\r\n| parse resourceId with * \"/providers/\" resourceType1 \"/\" resourceType2 \"/\" *\r\n| extend resourceType = strcat(resourceType1, \"/\", resourceType2)\r\n| where resourceType in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n| distinct subscriptionId, resourceGroup, resourceId, resourceType\r\n| join kind = leftouter (\r\n securityresources\r\n | where type == \"microsoft.security/softwareinventories\"\r\n | parse id with resourceId \"/providers/Microsoft.Security/softwareInventories/\" *\r\n | parse resourceId with * \"/providers/\" resourceType1 \"/\" resourceType2 \"/\" *\r\n | extend resourceType = strcat(resourceType1, \"/\", resourceType2)\r\n | where resourceType in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n | extend vendor = tostring(properties.vendor),\r\n software = tostring(properties.softwareName),\r\n version = tostring(properties.version)\r\n | where (vendor =~ \"adobe\" and software contains \"acrobat\") or \r\n (vendor =~ \"google\" and software contains \"chrome\") or\r\n (vendor =~ \"mozilla\" and software contains \"firefox\") or\r\n //(vendor =~ \"microsoft\" and software in~ (\"office\", \"office_web_components\", \"edge_chromium-based\", \"internet_explorer\", \"teams\"))\r\n (vendor =~ \"microsoft\" and software in~ (\"office\", \"office_web_components\", \"teams\"))\r\n | summarize applications = make_list(pack(\"Vendor\", vendor, \"Software\", software, \"Version\", version)) by subscriptionId, resourceGroup, resourceId, resourceType\r\n) on resourceId\r\n| extend appCompliant = iff(isnull(applications), \"Healthy\", \"Unhealthy\")\r\n| project subscriptionId, resourceGroup, resourceId = tolower(resourceId), resourceType, appCompliant, applications\r\n| join kind = fullouter (\r\n securityresources\r\n | where type == \"microsoft.security/assessments\"\r\n | where tostring(name) in~ (\r\n \"83f577bd-a1b6-b7e1-0891-12ca19d1e6df\", // \"Install endpoint protection solution on virtual machines\" (legacy)\r\n \"4fb67663-9ab9-475d-b026-8c544cced439\", // \"Endpoint protection should be installed on machines\"\r\n \"383cf3bc-fdf9-4a02-120a-3e7e36c6bfee\", // \"Endpoint protection should be installed on machines\" (legacy)\r\n \"21300918-b2e3-0346-785f-c77ff57d243b\", // \"Endpoint protection should be installed on virtual machine scale sets\"\r\n \"37a3689a-818e-4a0e-82ac-b1392b9bb000\", // \"Endpoint protection health issues on machines should be resolved\"\r\n \"3bcd234d-c9c7-c2a2-89e0-c01f419c1a8a\", // \"Endpoint protection health issues on machines should be resolved\" (legacy)\r\n \"e71020c2-860c-3235-cd39-04f3f8c936d2\" // \"Endpoint protection health issues on virtual machine scale sets should be resolved\"\r\n )\r\n | parse id with resourceId \"/providers/Microsoft.Security/assessments/\" *\r\n | parse resourceId with * \"/providers/\" resourceType1 \"/\" resourceType2 \"/\" *\r\n | extend resourceType = strcat(resourceType1, \"/\", resourceType2)\r\n | where resourceType in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n | extend resourceId = tolower(resourceId)\r\n | extend displayName = tostring(properties.displayName)\r\n //| extend statusChangeDate = format_datetime(todatetime(properties.status.statusChangeDate), \"yyyy-MM-dd\")\r\n | extend statusCode = tostring(properties.status.code)\r\n | extend statusCause = tostring(properties.status.cause)\r\n | extend statusDescription = tostring(properties.status.description)\r\n | extend statusChangeDate = todatetime(properties.status.statusChangeDate)\r\n | extend osName = tostring(properties.additionalData.OSName)\r\n | extend link = tostring(properties.additionalData.subAssessmentsLink)\r\n | project subscriptionId, resourceGroup, resourceId, resourceType, displayName, statusCode, statusCause, statusDescription, statusChangeDate, osName, link, name\r\n | extend recommendation = case (\r\n name =~ \"83f577bd-a1b6-b7e1-0891-12ca19d1e6df\", \"EP_Installed\", // \"Install endpoint protection solution on virtual machines\" (legacy)\r\n name =~ \"4fb67663-9ab9-475d-b026-8c544cced439\", \"EP_Installed\", // \"Endpoint protection should be installed on machines\"\r\n name =~ \"383cf3bc-fdf9-4a02-120a-3e7e36c6bfee\", \"EP_Installed\", // \"Endpoint protection should be installed on machines\" (legacy)\r\n name =~ \"21300918-b2e3-0346-785f-c77ff57d243b\", \"EP_Installed\", // \"Endpoint protection should be installed on virtual machine scale sets\"\r\n name =~ \"37a3689a-818e-4a0e-82ac-b1392b9bb000\", \"EP_Status\", // \"Endpoint protection health issues on machines should be resolved\"\r\n name =~ \"3bcd234d-c9c7-c2a2-89e0-c01f419c1a8a\", \"EP_Status\", // \"Endpoint protection health issues on machines should be resolved\" (legacy)\r\n name =~ \"e71020c2-860c-3235-cd39-04f3f8c936d2\", \"EP_Status\", // \"Endpoint protection health issues on virtual machine scale sets should be resolved\"\r\n \"Unknown\"\r\n )\r\n | summarize arg_max(statusChangeDate, *) by resourceId, recommendation\r\n | extend statusCode = iff(statusCode =~ \"NotApplicable\", strcat(\"NA-\", statusCause), statusCode)\r\n | summarize endpointProtection = make_bag(pack(recommendation, statusCode)) by subscriptionId, resourceGroup, resourceId, resourceType\r\n) on resourceId\r\n| extend subscriptionId = iff(isempty(subscriptionId), subscriptionId1, subscriptionId)\r\n| extend resourceGroup = iff(isempty(resourceGroup), resourceGroup1, resourceGroup)\r\n| extend resourceId = iff(isempty(resourceId), resourceId1, resourceId)\r\n| extend resourceType = iff(isempty(resourceType), resourceType1, resourceType)\r\n| project-away subscriptionId1, resourceGroup1, resourceId1, resourceType1\r\n| extend appCompliant = iff(isempty(appCompliant), \"Unknown-NoSoftwareInventory\", appCompliant)\r\n| extend endpointProtection = iff(isnull(endpointProtection), parse_json('{\"EP_Installed\":\"Unknown\",\"EP_Status\":\"Unknown\"}'), endpointProtection)\r\n| extend EP_Installed = tostring(endpointProtection.EP_Installed)\r\n| extend EP_Status = tostring(endpointProtection.EP_Status)\r\n| project-away endpointProtection\r\n| extend status = appCompliant\r\n| extend status = iff(status == \"Healthy\" and EP_Installed == \"Unhealthy\", \"Unhealthy\", status)\r\n| extend status = iff(status == \"Healthy\" and EP_Status == \"Unhealthy\", \"Unhealthy\", status)\r\n| extend status = iff(status == \"Healthy\" and EP_Installed == \"Unknown\", \"Unknown-NoEndpointProtectionData\", status)\r\n| extend status = iff(status == \"Healthy\" and EP_Installed == \"NA-OffByPolicy\", \"Unknown-OffByPolicy\", status)\r\n| extend status = iff(status == \"Unknown-NoSoftwareInventory\" and EP_Installed == \"Unhealthy\", \"Unhealthy\", status)\r\n| extend status = iff(status == \"Unknown-NoSoftwareInventory\" and EP_Status == \"Unhealthy\", \"Unhealthy\", status)\r\n| extend status = iff(status startswith \"Unknown\", \"Unknown\", status)\r\n| summarize count = dcount(resourceId) by status",
"size": 3,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"visualization": "piechart",
"chartSettings": {
"seriesLabelSettings": [
{
"seriesName": "Unhealthy",
"color": "red"
},
{
"seriesName": "Healthy",
"color": "green"
},
{
"seriesName": "Unknown",
"color": "gray"
}
]
}
},
"name": "query - 6"
},
{
"type": 1,
"content": {
"json": "## Virtual Machines Health: Details\r\nSee below for description of Configuration items."
},
"name": "text - 10"
},
{
"type": 9,
"content": {
"version": "KqlParameterItem/1.0",
"parameters": [
{
"id": "f0024cf2-8e70-44b3-8415-0f36d4d25db4",
"version": "KqlParameterItem/1.0",
"name": "HealthState",
"label": "Health State",
"type": 2,
"isRequired": true,
"multiSelect": true,
"quote": "'",
"delimiter": ",",
"typeSettings": {
"additionalResourceOptions": [
"value::all"
],
"showDefault": false
},
"jsonData": "[\"Healthy\", \"Unhealthy\", \"Unknown-NoSoftwareInventory\", \"Unknown-NoEndpointProtectionData\"]",
"timeContext": {
"durationMs": 86400000
},
"defaultValue": "value::all",
"value": [
"Unknown-NoEndpointProtectionData"
]
}
],
"style": "pills",
"queryType": 0,
"resourceType": "microsoft.operationalinsights/workspaces"
},
"name": "parameters - 9"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/softwareinventories\"\r\n| parse id with resourceId \"/providers/Microsoft.Security/softwareInventories/\" *\r\n| parse resourceId with * \"/providers/\" resourceType1 \"/\" resourceType2 \"/\" *\r\n| extend resourceType = strcat(resourceType1, \"/\", resourceType2)\r\n| where resourceType in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n| distinct subscriptionId, resourceGroup, resourceId, resourceType\r\n| join kind = leftouter (\r\n securityresources\r\n | where type == \"microsoft.security/softwareinventories\"\r\n | parse id with resourceId \"/providers/Microsoft.Security/softwareInventories/\" *\r\n | parse resourceId with * \"/providers/\" resourceType1 \"/\" resourceType2 \"/\" *\r\n | extend resourceType = strcat(resourceType1, \"/\", resourceType2)\r\n | where resourceType in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n | extend vendor = tostring(properties.vendor),\r\n software = tostring(properties.softwareName),\r\n version = tostring(properties.version)\r\n | where (vendor =~ \"adobe\" and software contains \"acrobat\") or \r\n (vendor =~ \"google\" and software contains \"chrome\") or\r\n (vendor =~ \"mozilla\" and software contains \"firefox\") or\r\n //(vendor =~ \"microsoft\" and software in~ (\"office\", \"office_web_components\", \"edge_chromium-based\", \"internet_explorer\", \"teams\"))\r\n (vendor =~ \"microsoft\" and software in~ (\"office\", \"office_web_components\", \"teams\"))\r\n | summarize applications = make_list(pack(\"Vendor\", vendor, \"Software\", software, \"Version\", version)) by subscriptionId, resourceGroup, resourceId, resourceType\r\n) on resourceId\r\n| extend appCompliant = iff(isnull(applications), \"Healthy\", \"Unhealthy\")\r\n| project subscriptionId, resourceGroup, resourceId = tolower(resourceId), resourceType, appCompliant, applications\r\n| join kind = fullouter (\r\n securityresources\r\n | where type == \"microsoft.security/assessments\"\r\n | where tostring(name) in~ (\r\n \"83f577bd-a1b6-b7e1-0891-12ca19d1e6df\", // \"Install endpoint protection solution on virtual machines\" (legacy)\r\n \"4fb67663-9ab9-475d-b026-8c544cced439\", // \"Endpoint protection should be installed on machines\"\r\n \"383cf3bc-fdf9-4a02-120a-3e7e36c6bfee\", // \"Endpoint protection should be installed on machines\" (legacy)\r\n \"21300918-b2e3-0346-785f-c77ff57d243b\", // \"Endpoint protection should be installed on virtual machine scale sets\"\r\n \"37a3689a-818e-4a0e-82ac-b1392b9bb000\", // \"Endpoint protection health issues on machines should be resolved\"\r\n \"3bcd234d-c9c7-c2a2-89e0-c01f419c1a8a\", // \"Endpoint protection health issues on machines should be resolved\" (legacy)\r\n \"e71020c2-860c-3235-cd39-04f3f8c936d2\" // \"Endpoint protection health issues on virtual machine scale sets should be resolved\"\r\n )\r\n | parse id with resourceId \"/providers/Microsoft.Security/assessments/\" *\r\n | parse resourceId with * \"/providers/\" resourceType1 \"/\" resourceType2 \"/\" *\r\n | extend resourceType = strcat(resourceType1, \"/\", resourceType2)\r\n | where resourceType in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n | extend resourceId = tolower(resourceId)\r\n | extend displayName = tostring(properties.displayName)\r\n //| extend statusChangeDate = format_datetime(todatetime(properties.status.statusChangeDate), \"yyyy-MM-dd\")\r\n | extend statusCode = tostring(properties.status.code)\r\n | extend statusCause = tostring(properties.status.cause)\r\n | extend statusDescription = tostring(properties.status.description)\r\n | extend statusChangeDate = todatetime(properties.status.statusChangeDate)\r\n | extend osName = tostring(properties.additionalData.OSName)\r\n | extend link = tostring(properties.additionalData.subAssessmentsLink)\r\n | project subscriptionId, resourceGroup, resourceId, resourceType, displayName, statusCode, statusCause, statusDescription, statusChangeDate, osName, link, name\r\n | extend recommendation = case (\r\n name =~ \"83f577bd-a1b6-b7e1-0891-12ca19d1e6df\", \"EP_Installed\", // \"Install endpoint protection solution on virtual machines\" (legacy)\r\n name =~ \"4fb67663-9ab9-475d-b026-8c544cced439\", \"EP_Installed\", // \"Endpoint protection should be installed on machines\"\r\n name =~ \"383cf3bc-fdf9-4a02-120a-3e7e36c6bfee\", \"EP_Installed\", // \"Endpoint protection should be installed on machines\" (legacy)\r\n name =~ \"21300918-b2e3-0346-785f-c77ff57d243b\", \"EP_Installed\", // \"Endpoint protection should be installed on virtual machine scale sets\"\r\n name =~ \"37a3689a-818e-4a0e-82ac-b1392b9bb000\", \"EP_Status\", // \"Endpoint protection health issues on machines should be resolved\"\r\n name =~ \"3bcd234d-c9c7-c2a2-89e0-c01f419c1a8a\", \"EP_Status\", // \"Endpoint protection health issues on machines should be resolved\" (legacy)\r\n name =~ \"e71020c2-860c-3235-cd39-04f3f8c936d2\", \"EP_Status\", // \"Endpoint protection health issues on virtual machine scale sets should be resolved\"\r\n \"Unknown\"\r\n )\r\n | summarize arg_max(statusChangeDate, *) by resourceId, recommendation\r\n | extend statusCode = iff(statusCode =~ \"NotApplicable\", strcat(\"NA-\", statusCause), statusCode)\r\n | summarize endpointProtection = make_bag(pack(recommendation, statusCode)) by subscriptionId, resourceGroup, resourceId, resourceType\r\n) on resourceId\r\n| extend subscriptionId = iff(isempty(subscriptionId), subscriptionId1, subscriptionId)\r\n| extend resourceGroup = iff(isempty(resourceGroup), resourceGroup1, resourceGroup)\r\n| extend resourceId = iff(isempty(resourceId), resourceId1, resourceId)\r\n| extend resourceType = iff(isempty(resourceType), resourceType1, resourceType)\r\n| project-away subscriptionId1, resourceGroup1, resourceId1, resourceType1\r\n| extend appCompliant = iff(isempty(appCompliant), \"Unknown-NoSoftwareInventory\", appCompliant)\r\n| extend endpointProtection = iff(isnull(endpointProtection), parse_json('{\"EP_Installed\":\"Unknown\",\"EP_Status\":\"Unknown\"}'), endpointProtection)\r\n| extend EP_Installed = tostring(endpointProtection.EP_Installed)\r\n| extend EP_Status = tostring(endpointProtection.EP_Status)\r\n| project-away endpointProtection\r\n| extend status = appCompliant\r\n| extend status = iff(status == \"Healthy\" and EP_Installed == \"Unhealthy\", \"Unhealthy\", status)\r\n| extend status = iff(status == \"Healthy\" and EP_Status == \"Unhealthy\", \"Unhealthy\", status)\r\n| extend status = iff(status == \"Healthy\" and EP_Installed == \"Unknown\", \"Unknown-NoEndpointProtectionData\", status)\r\n| extend status = iff(status == \"Healthy\" and EP_Installed == \"NA-OffByPolicy\", \"Unknown-OffByPolicy\", status)\r\n| extend status = iff(status == \"Unknown-NoSoftwareInventory\" and EP_Installed == \"Unhealthy\", \"Unhealthy\", status)\r\n| extend status = iff(status == \"Unknown-NoSoftwareInventory\" and EP_Status == \"Unhealthy\", \"Unhealthy\", status)\r\n| where status in~ ({HealthState})\r\n| project subscriptionId, resourceGroup, resourceId, resourceType, status, appCompliant, applications, EP_Installed, EP_Status",
"size": 0,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"gridSettings": {
"formatters": [
{
"columnMatch": "$gen_group",
"formatter": 15,
"formatOptions": {
"linkTarget": "Resource",
"showIcon": true
}
},
{
"columnMatch": "subscriptionId",
"formatter": 5
},
{
"columnMatch": "resourceGroup",
"formatter": 5
},
{
"columnMatch": "status",
"formatter": 18,
"formatOptions": {
"thresholdsOptions": "colors",
"thresholdsGrid": [
{
"operator": "==",
"thresholdValue": "Healthy",
"representation": "green",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unhealthy",
"representation": "red",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unknown-NoSoftwareInventory",
"representation": "gray",
"text": "{0}{1}"
},
{
"operator": "==",
"thresholdValue": "Unknown-NoEndpointProtectionData",
"representation": "grayBlue",
"text": "{0}{1}"
},
{
"operator": "Default",
"thresholdValue": null,
"representation": "gray",
"text": "{0}{1}"
}
]
}
}
],
"hierarchySettings": {
"treeType": 1,
"groupBy": [
"subscriptionId",
"resourceGroup"
]
},
"labelSettings": [
{
"columnId": "subscriptionId",
"label": "Subscription"
},
{
"columnId": "resourceId",
"label": "Resource"
},
{
"columnId": "resourceType",
"label": "Resource Type"
},
{
"columnId": "status",
"label": "Health State"
},
{
"columnId": "appCompliant",
"label": "App Compliance"
},
{
"columnId": "applications",
"label": "Non-compliant Apps"
}
]
}
},
"name": "query - 7"
}
]
},
"name": "group - 9"
}
]
},
"conditionalVisibility": {
"parameterName": "Tab",
"comparison": "isEqualTo",
"value": "ap hardening"
},
"name": "group - 10"
},
{
"type": 1,
"content": {
"json": "# ACSC Essential 8 - Health Report\r\nThis workbook provides insights on the health state of Azure resources against requirements by the ACSC Essential 8. This is achieved by querying logs from the Log Analytics Workspace or querying resources using Azure Resource Graph </br> to monitor for specific controls and configurations on the resources in alignment with the Essential 8 requirements.\r\n\r\nRecognising that the Essential 8 is designed to protect infrastructure resources, the current scope of this workbook is mainly Azure Virtual Machines (Windows and Linux) and Azure Subscriptions (where relevant and required). </br> While automating the monitoring for all of the controls in Essential 8 is not possible, a subset of controls within Essential 8 Maturity Level 2 are targeted in this workbook. Organisations can review these controls and customise as required.\r\n\r\nThe workbook includes this General tab, offering an executive summary, and 8 other tabs, one for each Essential 8 strategy. Each tab includes guidance on what controls/configurations are monitored and how resources are interpreted as healthy/unhealthy. </br> The Essential 8 tabs include a chart indicating a percentage of healthy resources and additional details on the resources in scope.\r\n\r\nThe report can be shared via link, printed, or saved as PDF.",
"style": "upsell"
},
"conditionalVisibility": {
"parameterName": "Tab",
"comparison": "isEqualTo",
"value": "General"
},
"name": "text - 13",
"styleSettings": {
"margin": "25px"
}
},
{
"type": 12,
"content": {
"version": "NotebookGroup/1.0",
"groupType": "editable",
"items": [
{
"type": 1,
"content": {
"json": "# Summary by Subscription #"
},
"name": "text - 12"
},
{
"type": 1,
"content": {
"json": "### Multi-factor Authentication ###"
},
"customWidth": "50",
"name": "text - 14"
},
{
"type": 1,
"content": {
"json": "### Restrict Admin Privileges ###"
},
"customWidth": "50",
"name": "text - 15"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments\"\r\n| where name in~ (\r\n \"6240402e-f77c-46fa-9060-a7ce53997754\", // Accounts with owner permissions on Azure resources should be MFA enabled\r\n \"dabc9bc4-b8a8-45bd-9a5a-43000df8aa1c\", // Accounts with read permissions on Azure resources should be MFA enabled\r\n \"c0cb17b2-0607-48a7-b0e0-903ed22de39b\" // Accounts with write permissions on Azure resources should be MFA enabled\r\n)\r\n| extend DisplayName = tostring(properties.displayName)\r\n| extend ResourceId = tostring(properties.resourceDetails.Id)\r\n| extend StatusCode = tostring(properties.status.code)\r\n| extend StatusCause = tostring(properties.status.cause)\r\n| extend StatusDescription = tostring(properties.status.description)\r\n| extend RecommendationId = case (\r\n name =~ \"6240402e-f77c-46fa-9060-a7ce53997754\", \"Res_Owner_MFA\", // Accounts with owner permissions on Azure resources should be MFA enabled\r\n name =~ \"dabc9bc4-b8a8-45bd-9a5a-43000df8aa1c\", \"Res_Read_MFA\", // Accounts with read permissions on Azure resources should be MFA enabled\r\n name =~ \"c0cb17b2-0607-48a7-b0e0-903ed22de39b\", \"Res_Write_MFA\", // Accounts with write permissions on Azure resources should be MFA enabled\r\n \"Unknown Recommendation Id\"\r\n)\r\n| summarize Recommendations = make_bag(pack(RecommendationId, StatusCode)) by ResourceId\r\n//| evaluate bag_unpack(Recommendations) // evaluate operator not supported on Resource Graph, need to do manually\r\n| extend Res_Owner_MFA = iff(Recommendations.Res_Owner_MFA == \"\", \"Unknown\", Recommendations.Res_Owner_MFA)\r\n| extend Res_Read_MFA = iff(Recommendations.Res_Read_MFA == \"\", \"Unknown\", Recommendations.Res_Read_MFA)\r\n| extend Res_Write_MFA = iff(Recommendations.Res_Write_MFA == \"\", \"Unknown\", Recommendations.Res_Write_MFA)\r\n| extend PackedString=tostring(pack_all())\r\n| extend State = iff(PackedString contains \"NotApplicable\" or\r\n PackedString contains \"Unknown\", \"Unknown\", \"Healthy\")\r\n| extend State = iff(PackedString contains \"Unhealthy\", \"Unhealthy\", State)\r\n| summarize count = dcount(ResourceId) by State",
"size": 4,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"visualization": "piechart",
"chartSettings": {
"seriesLabelSettings": [
{
"seriesName": "Unknown",
"color": "gray"
},
{
"seriesName": "Healthy",
"color": "green"
},
{
"seriesName": "Unhealthy",
"color": "red"
}
]
}
},
"customWidth": "50",
"name": "query - 16"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments\"\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| where name in~ (\r\n \"6f90a6d6-d4d6-0794-0ec1-98fa77878c2e\", // A maximum of 3 owners should be designated for subscriptions\r\n \"050ac097-3dda-4d24-ab6d-82568e7a50cf\", // Blocked accounts with owner permissions on Azure resources should be removed\r\n \"1ff0b4c9-ed56-4de6-be9c-d7ab39645926\", // Blocked accounts with read and write permissions on Azure resources should be removed \r\n \"20606e75-05c4-48c0-9d97-add6daa2109a\", // Guest accounts with owner permissions on Azure resources should be removed\r\n// \"fde1c0c9-0fd2-4ecc-87b5-98956cbc1095\", // Guest accounts with read permissions on Azure resources should be removed\r\n \"0354476c-a12a-4fcc-a79d-f0ab7ffffdbb\" // Guest accounts with write permissions on Azure resources should be removed\r\n)\r\n| extend DisplayName = tostring(properties.displayName)\r\n| extend ResourceId = tostring(properties.resourceDetails.Id)\r\n| extend StatusCode = tostring(properties.status.code)\r\n| extend StatusCause = tostring(properties.status.cause)\r\n| extend StatusDescription = tostring(properties.status.description)\r\n| extend RecommendationId = case (\r\n name =~ \"6f90a6d6-d4d6-0794-0ec1-98fa77878c2e\", \"Max_3_Owners\", // A maximum of 3 owners should be designated for subscriptions\r\n name =~ \"050ac097-3dda-4d24-ab6d-82568e7a50cf\", \"Remove_Blocked_Owner\", // Blocked accounts with owner permissions on Azure resources should be removed\r\n name =~ \"1ff0b4c9-ed56-4de6-be9c-d7ab39645926\", \"Remove_Blocked_ReadWrite\", // Blocked accounts with read and write permissions on Azure resources should be removed \r\n name =~ \"20606e75-05c4-48c0-9d97-add6daa2109a\", \"Remove_Guest_Owner\", // Guest accounts with owner permissions on Azure resources should be removed\r\n name =~ \"fde1c0c9-0fd2-4ecc-87b5-98956cbc1095\", \"Remove_Guest_Read\", // Guest accounts with read permissions on Azure resources should be removed\r\n name =~ \"0354476c-a12a-4fcc-a79d-f0ab7ffffdbb\", \"Remove_Guest_Write\", // Guest accounts with write permissions on Azure resources should be removed\r\n \"Unknown Recommendation Id\"\r\n)\r\n| summarize Recommendations = make_bag(pack(RecommendationId, StatusCode)) by ResourceId\r\n//| evaluate bag_unpack(Recommendations) // evaluate operator not supported on Resource Graph, need to do manually\r\n| extend Max_3_Owners = iff(Recommendations.Max_3_Owners == \"\", \"Unknown\", Recommendations.Max_3_Owners)\r\n| extend Remove_Blocked_Owner = iff(Recommendations.Remove_Blocked_Owner == \"\", \"Unknown\", Recommendations.Remove_Blocked_Owner)\r\n| extend Remove_Blocked_ReadWrite = iff(Recommendations.Remove_Blocked_ReadWrite == \"\", \"Unknown\", Recommendations.Remove_Blocked_ReadWrite)\r\n| extend Remove_Guest_Owner = iff(Recommendations.Remove_Guest_Owner == \"\", \"Unknown\", Recommendations.Remove_Guest_Owner)\r\n//| extend Remove_Guest_Read = iff(Recommendations.Remove_Guest_Read == \"\", \"Unknown\", Recommendations.Remove_Guest_Read)\r\n| extend Remove_Guest_Write = iff(Recommendations.Remove_Guest_Write == \"\", \"Unknown\", Recommendations.Remove_Guest_Write)\r\n| extend PackedString=tostring(pack_all())\r\n| extend State = iff(PackedString contains \"NotApplicable\" or\r\n PackedString contains \"Unknown\", \"Unknown\", \"Healthy\")\r\n| extend State = iff(PackedString contains \"Unhealthy\", \"Unhealthy\", State)\r\n| summarize count=dcount(ResourceId) by State",
"size": 4,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"visualization": "piechart",
"chartSettings": {
"seriesLabelSettings": [
{
"seriesName": "Unhealthy",
"color": "red"
},
{
"seriesName": "Unknown",
"color": "gray"
},
{
"seriesName": "Healthy",
"color": "green"
}
]
}
},
"customWidth": "50",
"name": "query - 13"
},
{
"type": 1,
"content": {
"json": "<br>\r\n<br>\r\n<br>"
},
"name": "text - 14"
},
{
"type": 1,
"content": {
"json": "# Summary by Machine #"
},
"name": "text - 8"
},
{
"type": 1,
"content": {
"json": "### Patch Applications ###"
},
"customWidth": "50",
"name": "text - 9"
},
{
"type": 1,
"content": {
"json": "### Patch Operating Systems ###"
},
"customWidth": "50",
"name": "text - 10"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments\"\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| where name == \"1195afff-c881-495e-9bc5-1486211ae03f\" // \"Machines should have vulnerability findings resolved\"\r\n| extend Resource = tostring(properties.resourceDetails.Id)\r\n| extend StatusCode = iff(tostring(properties.status.code) == \"NotApplicable\", \"Unknown\", tostring(properties.status.code)) // change \"NotApplicable\" to \"Unknown\"\r\n| extend StatusDescription = tostring(properties.status.description)\r\n| project subscriptionId, resourceGroup, Resource, StatusCode, StatusDescription\r\n| join kind = fullouter (\r\n // get list of Resource ids that have subassessments related to APP vulns and that are not InSLA\r\n securityresources\r\n | where type == \"microsoft.security/assessments/subassessments\"\r\n | where subscriptionId in~ ({Subscription:subid})\r\n | extend assessmentKey = extract(\".*assessments/(.+?)/.*\",1 , id)\r\n | where assessmentKey == \"1195afff-c881-495e-9bc5-1486211ae03f\" // \"Machines should have vulnerability findings resolved\"\r\n | extend Patchable = tostring(properties.additionalData.patchable)\r\n //| where Patchable !~ \"false\" // uncomment to remove subassessments that are config only\r\n | extend SoftwareVendor = tostring(properties.additionalData.softwareVendor)\r\n | extend Category = tostring(properties.category)\r\n //| where Category !~ \"Security Policy\" // uncomment to remove subassessments that are config only\r\n // | where not(Category in~ (\"Internet Explorer\", \"Local\", \"Security Policy\", \"Windows\")) // IE is treated as part of Windows OS\r\n //| where not(Category =~ \"Update\" and SoftwareVendor in~ (\"ubuntu\", \"debian\")) // these are for OS (rather than application) patches\r\n | where not(Category in~ (\"Internet Explorer\", \"Local\", \"Security Policy\", \"Windows\", \"Update\")) // IE is treated as part of Windows OS\r\n | extend SoftwareName = tostring(properties.additionalData.softwareName)\r\n | extend SoftwareVersion = tostring(properties.additionalData.softwareVersion)\r\n | extend Description = tostring(properties.displayName)\r\n | extend Status = tostring(properties.status.code)\r\n | extend Severity = tostring(properties.status.severity)\r\n | extend Resource = tostring(properties.resourceDetails.id)\r\n | extend ResourceSource = tostring(properties.resourceDetails.source)\r\n | extend ResourceType = tolower(split(id,\"/\").[6])\r\n | extend VulnId = tostring(properties.id)\r\n | extend Cve = parse_json(properties.additionalData.cve)\r\n | extend CveCount = array_length(Cve)\r\n | extend TimeGenerated = tostring(properties.timeGenerated)\r\n | extend PublishedTime = todatetime(properties.additionalData.publishedTime)\r\n | extend Remediation = tostring(properties.remediation)\r\n | extend Impact = tostring(properties.impact)\r\n | extend Threat = tostring(properties.additionalData.threat)\r\n | distinct tenantId, subscriptionId, resourceGroup, Resource, ResourceType, ResourceSource, Category, SoftwareVendor, SoftwareName, SoftwareVersion, Description,\r\n Status, Severity, VulnId, tostring(Cve), CveCount, Patchable, TimeGenerated, PublishedTime, Remediation, Impact, Threat\r\n | mv-expand CveExpand = split (Cve, \"},\") to typeof(string)\r\n | parse CveExpand with * '\"title\":\"' singleCveTitle '\"' *\r\n | parse CveExpand with * '\"severity\":\"' singleCveSeverity '\"' * '\"exploitabilityLevel\":\"' singleCveExploitabilityLevel '\"' * '\"publishedDate\":\"' singleCvePublishedDate:datetime '\"' *\r\n | summarize OldestExploit = minif(singleCvePublishedDate, singleCveExploitabilityLevel startswith \"Exploit\"), OldestNonExploit = minif(singleCvePublishedDate, singleCveExploitabilityLevel =~ \"NoExploit\"), CVEs = tostring(make_list(singleCveTitle)) by tenantId, subscriptionId, resourceGroup, Resource, ResourceType, ResourceSource, Category, SoftwareVendor, SoftwareName, SoftwareVersion, Description, Status, Severity, VulnId, CveCount, Patchable, TimeGenerated, PublishedTime, Remediation, Impact, Threat\r\n | extend InSLA = iff(now() - 14d > PublishedTime, false, true)\r\n | extend InSLA = iff(now() - 14d > OldestNonExploit, false, InSLA)\r\n | extend InSLA = iff(now() - 48h > OldestExploit, false, InSLA)\r\n | where InSLA == false\r\n | distinct Resource\r\n | extend CategorizeAsAppAndNotInSLA = true\r\n) on Resource\r\n| extend StatusCode = iff(StatusCode =~ \"Unhealthy\" and isnull(CategorizeAsAppAndNotInSLA), \"Healthy\", StatusCode) // set \"Unhealthy\" node to \"Healthy\" if it doesn't have any App-related findings that are out of SLA\r\n| extend StatusCode = iff(StatusDescription =~ \"The recommendation is not relevant for Security Appliance\", \"Healthy\", StatusCode) // set platform-managed VMs to \"Healthy\"\r\n| summarize count=dcount(Resource) by StatusCode",
"size": 4,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"visualization": "piechart",
"chartSettings": {
"seriesLabelSettings": [
{
"seriesName": "Unknown",
"color": "gray"
},
{
"seriesName": "Healthy",
"color": "green"
},
{
"seriesName": "Unhealthy",
"color": "red"
}
]
}
},
"customWidth": "50",
"name": "query - 11"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments\"\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| where name == \"1195afff-c881-495e-9bc5-1486211ae03f\" // \"Machines should have vulnerability findings resolved\"\r\n| extend Resource = tostring(properties.resourceDetails.Id)\r\n| extend StatusCode = iff(tostring(properties.status.code) == \"NotApplicable\", \"Unknown\", tostring(properties.status.code)) // change \"NotApplicable\" to \"Unknown\"\r\n| extend StatusDescription = tostring(properties.status.description)\r\n| project subscriptionId, resourceGroup, Resource, StatusCode, StatusDescription\r\n| join kind = fullouter (\r\n // get list of Resource ids that have subassessments related to OS vulns and that are not InSLA\r\n securityresources\r\n | where type == \"microsoft.security/assessments/subassessments\"\r\n | where subscriptionId in~ ({Subscription:subid})\r\n | extend assessmentKey = extract(\".*assessments/(.+?)/.*\",1 , id)\r\n | where assessmentKey == \"1195afff-c881-495e-9bc5-1486211ae03f\" // \"Machines should have vulnerability findings resolved\"\r\n | extend Patchable = tostring(properties.additionalData.patchable)\r\n //| where Patchable !~ \"false\" // uncomment to remove subassessments that are config only\r\n | extend SoftwareVendor = tostring(properties.additionalData.softwareVendor)\r\n | extend Category = tostring(properties.category)\r\n //| where Category !~ \"Security Policy\" // uncomment to remove subassessments that are config only\r\n// | where (Category in~ (\"Internet Explorer\", \"Local\", \"Security Policy\", \"Windows\")) or (Category =~ \"Update\" and SoftwareVendor in~ (\"ubuntu\", \"debian\")) // these are for OS (rather than application) patches. IE is treated as part of Windows OS\r\n | where (Category in~ (\"Internet Explorer\", \"Local\", \"Security Policy\", \"Windows\", \"Update\")) // these are for OS (rather than application) patches. IE is treated as part of Windows OS\r\n | extend SoftwareName = tostring(properties.additionalData.softwareName)\r\n | extend SoftwareVersion = tostring(properties.additionalData.softwareVersion)\r\n | extend Description = tostring(properties.displayName)\r\n | extend Status = tostring(properties.status.code)\r\n | extend Severity = tostring(properties.status.severity)\r\n | extend Resource = tostring(properties.resourceDetails.id)\r\n | extend ResourceSource = tostring(properties.resourceDetails.source)\r\n | extend ResourceType = tolower(split(id,\"/\").[6])\r\n | extend VulnId = tostring(properties.id)\r\n | extend Cve = parse_json(properties.additionalData.cve)\r\n | extend CveCount = array_length(Cve)\r\n | extend TimeGenerated = tostring(properties.timeGenerated)\r\n | extend PublishedTime = todatetime(properties.additionalData.publishedTime)\r\n | extend Remediation = tostring(properties.remediation)\r\n | extend Impact = tostring(properties.impact)\r\n | extend Threat = tostring(properties.additionalData.threat)\r\n | distinct tenantId, subscriptionId, resourceGroup, Resource, ResourceType, ResourceSource, Category, SoftwareVendor, SoftwareName, SoftwareVersion, Description,\r\n Status, Severity, VulnId, tostring(Cve), CveCount, Patchable, TimeGenerated, PublishedTime, Remediation, Impact, Threat\r\n | mv-expand CveExpand = split (Cve, \"},\") to typeof(string)\r\n | parse CveExpand with * '\"title\":\"' singleCveTitle '\"' *\r\n | parse CveExpand with * '\"severity\":\"' singleCveSeverity '\"' * '\"exploitabilityLevel\":\"' singleCveExploitabilityLevel '\"' * '\"publishedDate\":\"' singleCvePublishedDate:datetime '\"' *\r\n | summarize OldestExploit = minif(singleCvePublishedDate, singleCveExploitabilityLevel startswith \"Exploit\"), OldestNonExploit = minif(singleCvePublishedDate, singleCveExploitabilityLevel =~ \"NoExploit\"), CVEs = tostring(make_list(singleCveTitle)) by tenantId, subscriptionId, resourceGroup, Resource, ResourceType, ResourceSource, Category, SoftwareVendor, SoftwareName, SoftwareVersion, Description, Status, Severity, VulnId, CveCount, Patchable, TimeGenerated, PublishedTime, Remediation, Impact, Threat\r\n | extend InSLA = iff(now() - 14d > PublishedTime, false, true)\r\n | extend InSLA = iff(now() - 14d > OldestNonExploit, false, InSLA)\r\n | extend InSLA = iff(now() - 48h > OldestExploit, false, InSLA)\r\n | where InSLA == false\r\n | distinct Resource\r\n | extend CategorizeAsOsAndNotInSLA = true\r\n) on Resource\r\n| extend StatusCode = iff(StatusCode =~ \"Unhealthy\" and isnull(CategorizeAsOsAndNotInSLA), \"Healthy\", StatusCode) // set \"Unhealthy\" node to \"Healthy\" if it doesn't have any OS-related findings that are out of SLA\r\n| extend StatusCode = iff(StatusDescription =~ \"The recommendation is not relevant for Security Appliance\", \"Healthy\", StatusCode) // set platform-managed VMs to \"Healthy\"\r\n| summarize count=dcount(Resource) by StatusCode",
"size": 4,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"visualization": "piechart",
"chartSettings": {
"seriesLabelSettings": [
{
"seriesName": "Unknown",
"color": "gray"
},
{
"seriesName": "Unhealthy",
"color": "red"
},
{
"seriesName": "Healthy",
"color": "green"
}
]
}
},
"customWidth": "50",
"name": "query - 12"
},
{
"type": 1,
"content": {
"json": "### Restrict Admin Privileges ###"
},
"customWidth": "50",
"name": "text - 13"
},
{
"type": 1,
"content": {
"json": "### Regular Backups ###"
},
"customWidth": "50",
"name": "text - 14"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/assessments\"\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| where name in~ (\r\n \"805651bc-6ecd-4c73-9b55-97a19d0582d0\" // Management ports of virtual machines should be protected with just-in-time network access control\r\n)\r\n| extend DisplayName = tostring(properties.displayName)\r\n| extend ResourceId = tostring(properties.resourceDetails.Id)\r\n| extend StatusCode = tostring(properties.status.code)\r\n| extend StatusCause = tostring(properties.status.cause)\r\n| extend StatusDescription = tostring(properties.status.description)\r\n// change the status of VMs without NSG to \"Unhealthy\"\r\n| extend StatusCode = iff(StatusDescription =~ \"This recommendation is relevant only for VMs protected by a network security group or Azure Firewall\", \"Unhealthy\", StatusCode)\r\n// change NA status to \"Unknown\"\r\n| extend StatusCode = iff(StatusCode =~ \"NotApplicable\", \"Unknown\", StatusCode)\r\n| project subscriptionId, resourceGroup, ResourceId, DisplayName, StatusCode, StatusCause, StatusDescription\r\n| summarize count=dcount(ResourceId) by StatusCode",
"size": 4,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"visualization": "piechart",
"chartSettings": {
"seriesLabelSettings": [
{
"seriesName": "Healthy",
"color": "green"
},
{
"seriesName": "Unhealthy",
"color": "red"
},
{
"seriesName": "Unknown",
"color": "gray"
}
]
}
},
"customWidth": "50",
"name": "query - 15"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where subscriptionId in~ ({Subscription:subid})\r\n| where type == \"microsoft.security/assessments\"\r\n| where name == \"f2f595ec-5dc6-68b4-82ef-b63563e9c610\" // \"Azure Backup should be enabled for virtual machines\"\r\n| extend ResourceId = tostring(properties.resourceDetails.Id)\r\n| extend state = tostring(properties.status.code)\r\n| summarize count=dcount(ResourceId) by state",
"size": 4,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"visualization": "piechart",
"chartSettings": {
"seriesLabelSettings": [
{
"seriesName": "Unhealthy",
"color": "red"
},
{
"seriesName": "Healthy",
"color": "green"
}
]
}
},
"customWidth": "50",
"name": "query - 16"
},
{
"type": 12,
"content": {
"version": "NotebookGroup/1.0",
"groupType": "editable",
"items": [
{
"type": 1,
"content": {
"json": "### Configure Macro Settings ###"
},
"customWidth": "50",
"name": "text - 17"
},
{
"type": 1,
"content": {
"json": "### User Application Hardening ###"
},
"customWidth": "50",
"name": "text - 18"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/softwareinventories\"\r\n| parse id with resourceId \"/providers/Microsoft.Security/softwareInventories/\" *\r\n| parse resourceId with * \"/providers/\" resourceType1 \"/\" resourceType2 \"/\" *\r\n| extend resourceType = strcat(resourceType1, \"/\", resourceType2)\r\n| where resourceType in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n| distinct subscriptionId, resourceGroup, resourceId, resourceType\r\n| join kind = leftouter (\r\n securityresources\r\n | where type == \"microsoft.security/softwareinventories\"\r\n | parse id with resourceId \"/providers/Microsoft.Security/softwareInventories/\" *\r\n | parse resourceId with * \"/providers/\" resourceType1 \"/\" resourceType2 \"/\" *\r\n | extend resourceType = strcat(resourceType1, \"/\", resourceType2)\r\n | where resourceType in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n | extend vendor = tostring(properties.vendor),\r\n software = tostring(properties.softwareName),\r\n version = tostring(properties.version)\r\n | where vendor =~ \"microsoft\" and software in~ (\"office\", \"office_web_components\")\r\n | summarize applications = make_list(pack(\"Vendor\", vendor, \"Software\", software, \"Version\", version)) by subscriptionId, resourceGroup, resourceId, resourceType\r\n) on resourceId\r\n| extend appCompliant = iff(isnull(applications), \"Healthy\", \"Unhealthy\")\r\n| project subscriptionId, resourceGroup, resourceId = tolower(resourceId), resourceType, appCompliant, applications\r\n| join kind = fullouter (\r\n resources\r\n //| where type in~ (\"microsoft.compute/virtualmachines\", \"microsoft.classiccompute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n | where type in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n | extend osType = iff(type =~ \"microsoft.compute/virtualmachines\", tostring(properties.storageProfile.osDisk.osType), tostring(properties.osType)),\r\n osName = iff(type =~ \"microsoft.compute/virtualmachines\", tostring(properties.extended.instanceView.osName), tostring(properties.osName)), // blank if machine stopped, deallocated\r\n osVersion = iff(type =~ \"microsoft.compute/virtualmachines\", tostring(properties.extended.instanceView.osVersion), tostring(properties.osVersion)), // blank if machine stopped, deallocated\r\n publisher = tostring(properties.storageProfile.imageReference.publisher),\r\n exactVersion = tostring(properties.storageProfile.imageReference.exactVersion),\r\n sku = iff(type =~ \"microsoft.compute/virtualmachines\", tostring(properties.storageProfile.imageReference.sku), tostring(properties.osSku)),\r\n offer = tostring(properties.storageProfile.imageReference.offer)\r\n | project subscriptionId, resourceGroup, resourceId = tolower(id), resourceType = type, name, osType, osName, osVersion, sku, publisher, exactVersion, offer\r\n) on resourceId\r\n| extend subscriptionId = iff(isempty(subscriptionId), subscriptionId1, subscriptionId)\r\n| extend resourceGroup = iff(isempty(resourceGroup), resourceGroup1, resourceGroup)\r\n| extend resourceId = iff(isempty(resourceId), resourceId1, resourceId)\r\n| extend resourceType = iff(isempty(resourceType), resourceType1, resourceType)\r\n//| project-away subscriptionId1, resourceGroup1, resourceId1, resourceType1\r\n| extend appCompliant = iff(isempty(appCompliant), \"Unknown\", appCompliant)\r\n| summarize count = dcount(resourceId) by appCompliant",
"size": 4,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"visualization": "piechart",
"chartSettings": {
"seriesLabelSettings": [
{
"seriesName": "Unhealthy",
"color": "red"
},
{
"seriesName": "Healthy",
"color": "green"
},
{
"seriesName": "Unknown",
"color": "gray"
}
]
}
},
"customWidth": "50",
"name": "query - 20"
},
{
"type": 3,
"content": {
"version": "KqlItem/1.0",
"query": "securityresources\r\n| where type == \"microsoft.security/softwareinventories\"\r\n| parse id with resourceId \"/providers/Microsoft.Security/softwareInventories/\" *\r\n| parse resourceId with * \"/providers/\" resourceType1 \"/\" resourceType2 \"/\" *\r\n| extend resourceType = strcat(resourceType1, \"/\", resourceType2)\r\n| where resourceType in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n| distinct subscriptionId, resourceGroup, resourceId, resourceType\r\n| join kind = leftouter (\r\n securityresources\r\n | where type == \"microsoft.security/softwareinventories\"\r\n | parse id with resourceId \"/providers/Microsoft.Security/softwareInventories/\" *\r\n | parse resourceId with * \"/providers/\" resourceType1 \"/\" resourceType2 \"/\" *\r\n | extend resourceType = strcat(resourceType1, \"/\", resourceType2)\r\n | where resourceType in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n | extend vendor = tostring(properties.vendor),\r\n software = tostring(properties.softwareName),\r\n version = tostring(properties.version)\r\n | where (vendor =~ \"adobe\" and software contains \"acrobat\") or \r\n (vendor =~ \"google\" and software contains \"chrome\") or\r\n (vendor =~ \"mozilla\" and software contains \"firefox\") or\r\n //(vendor =~ \"microsoft\" and software in~ (\"office\", \"office_web_components\", \"edge_chromium-based\", \"internet_explorer\", \"teams\"))\r\n (vendor =~ \"microsoft\" and software in~ (\"office\", \"office_web_components\", \"teams\"))\r\n | summarize applications = make_list(pack(\"Vendor\", vendor, \"Software\", software, \"Version\", version)) by subscriptionId, resourceGroup, resourceId, resourceType\r\n) on resourceId\r\n| extend appCompliant = iff(isnull(applications), \"Healthy\", \"Unhealthy\")\r\n| project subscriptionId, resourceGroup, resourceId = tolower(resourceId), resourceType, appCompliant, applications\r\n| join kind = fullouter (\r\n securityresources\r\n | where type == \"microsoft.security/assessments\"\r\n | where tostring(name) in~ (\r\n \"83f577bd-a1b6-b7e1-0891-12ca19d1e6df\", // \"Install endpoint protection solution on virtual machines\" (legacy)\r\n \"4fb67663-9ab9-475d-b026-8c544cced439\", // \"Endpoint protection should be installed on machines\"\r\n \"383cf3bc-fdf9-4a02-120a-3e7e36c6bfee\", // \"Endpoint protection should be installed on machines\" (legacy)\r\n \"21300918-b2e3-0346-785f-c77ff57d243b\", // \"Endpoint protection should be installed on virtual machine scale sets\"\r\n \"37a3689a-818e-4a0e-82ac-b1392b9bb000\", // \"Endpoint protection health issues on machines should be resolved\"\r\n \"3bcd234d-c9c7-c2a2-89e0-c01f419c1a8a\", // \"Endpoint protection health issues on machines should be resolved\" (legacy)\r\n \"e71020c2-860c-3235-cd39-04f3f8c936d2\" // \"Endpoint protection health issues on virtual machine scale sets should be resolved\"\r\n )\r\n | parse id with resourceId \"/providers/Microsoft.Security/assessments/\" *\r\n | parse resourceId with * \"/providers/\" resourceType1 \"/\" resourceType2 \"/\" *\r\n | extend resourceType = strcat(resourceType1, \"/\", resourceType2)\r\n | where resourceType in~ (\"microsoft.compute/virtualmachines\", \"microsoft.hybridcompute/machines\")\r\n | extend resourceId = tolower(resourceId)\r\n | extend displayName = tostring(properties.displayName)\r\n //| extend statusChangeDate = format_datetime(todatetime(properties.status.statusChangeDate), \"yyyy-MM-dd\")\r\n | extend statusCode = tostring(properties.status.code)\r\n | extend statusCause = tostring(properties.status.cause)\r\n | extend statusDescription = tostring(properties.status.description)\r\n | extend statusChangeDate = todatetime(properties.status.statusChangeDate)\r\n | extend osName = tostring(properties.additionalData.OSName)\r\n | extend link = tostring(properties.additionalData.subAssessmentsLink)\r\n | project subscriptionId, resourceGroup, resourceId, resourceType, displayName, statusCode, statusCause, statusDescription, statusChangeDate, osName, link, name\r\n | extend recommendation = case (\r\n name =~ \"83f577bd-a1b6-b7e1-0891-12ca19d1e6df\", \"EP_Installed\", // \"Install endpoint protection solution on virtual machines\" (legacy)\r\n name =~ \"4fb67663-9ab9-475d-b026-8c544cced439\", \"EP_Installed\", // \"Endpoint protection should be installed on machines\"\r\n name =~ \"383cf3bc-fdf9-4a02-120a-3e7e36c6bfee\", \"EP_Installed\", // \"Endpoint protection should be installed on machines\" (legacy)\r\n name =~ \"21300918-b2e3-0346-785f-c77ff57d243b\", \"EP_Installed\", // \"Endpoint protection should be installed on virtual machine scale sets\"\r\n name =~ \"37a3689a-818e-4a0e-82ac-b1392b9bb000\", \"EP_Status\", // \"Endpoint protection health issues on machines should be resolved\"\r\n name =~ \"3bcd234d-c9c7-c2a2-89e0-c01f419c1a8a\", \"EP_Status\", // \"Endpoint protection health issues on machines should be resolved\" (legacy)\r\n name =~ \"e71020c2-860c-3235-cd39-04f3f8c936d2\", \"EP_Status\", // \"Endpoint protection health issues on virtual machine scale sets should be resolved\"\r\n \"Unknown\"\r\n )\r\n | summarize arg_max(statusChangeDate, *) by resourceId, recommendation\r\n | extend statusCode = iff(statusCode =~ \"NotApplicable\", strcat(\"NA-\", statusCause), statusCode)\r\n | summarize endpointProtection = make_bag(pack(recommendation, statusCode)) by subscriptionId, resourceGroup, resourceId, resourceType\r\n) on resourceId\r\n| extend subscriptionId = iff(isempty(subscriptionId), subscriptionId1, subscriptionId)\r\n| extend resourceGroup = iff(isempty(resourceGroup), resourceGroup1, resourceGroup)\r\n| extend resourceId = iff(isempty(resourceId), resourceId1, resourceId)\r\n| extend resourceType = iff(isempty(resourceType), resourceType1, resourceType)\r\n| project-away subscriptionId1, resourceGroup1, resourceId1, resourceType1\r\n| extend appCompliant = iff(isempty(appCompliant), \"Unknown-NoSoftwareInventory\", appCompliant)\r\n| extend endpointProtection = iff(isnull(endpointProtection), parse_json('{\"EP_Installed\":\"Unknown\",\"EP_Status\":\"Unknown\"}'), endpointProtection)\r\n| extend EP_Installed = tostring(endpointProtection.EP_Installed)\r\n| extend EP_Status = tostring(endpointProtection.EP_Status)\r\n| project-away endpointProtection\r\n| extend status = appCompliant\r\n| extend status = iff(status == \"Healthy\" and EP_Installed == \"Unhealthy\", \"Unhealthy\", status)\r\n| extend status = iff(status == \"Healthy\" and EP_Status == \"Unhealthy\", \"Unhealthy\", status)\r\n| extend status = iff(status == \"Healthy\" and EP_Installed == \"Unknown\", \"Unknown-NoEndpointProtectionData\", status)\r\n| extend status = iff(status == \"Healthy\" and EP_Installed == \"NA-OffByPolicy\", \"Unknown-OffByPolicy\", status)\r\n| extend status = iff(status == \"Unknown-NoSoftwareInventory\" and EP_Installed == \"Unhealthy\", \"Unhealthy\", status)\r\n| extend status = iff(status == \"Unknown-NoSoftwareInventory\" and EP_Status == \"Unhealthy\", \"Unhealthy\", status)\r\n| extend status = iff(status startswith \"Unknown\", \"Unknown\", status)\r\n| summarize count = dcount(resourceId) by status",
"size": 4,
"queryType": 1,
"resourceType": "microsoft.resourcegraph/resources",
"crossComponentResources": [
"{Subscription}"
],
"visualization": "piechart",
"chartSettings": {
"seriesLabelSettings": [
{
"seriesName": "Unhealthy",
"color": "red"
},
{
"seriesName": "Healthy",
"color": "green"
},
{
"seriesName": "Unknown",
"color": "gray"
}
]
}
},
"customWidth": "50",
"name": "query - 19"
}
]
},
"name": "group - 12"
}
]
},
"conditionalVisibility": {
"parameterName": "Tab",
"comparison": "isEqualTo",
"value": "General"
},
"name": "group - 14",
"styleSettings": {
"margin": "25px"
}
}
],
"fallbackResourceIds": [],
"fromTemplateId": "sentinel-AcscEssential8",
"$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json"
}